From 2dc4397c1b5d8f2b7c715abb3d074f35b151201c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 14 Feb 2025 22:54:06 -0800 Subject: [PATCH 01/74] Vita: Allow using rear touch pads as L2/L3/R2/R3 (#3054) --- CHANGES | 1 + src/platform/psp2/main.c | 2 ++ src/platform/psp2/psp2-context.c | 52 ++++++++++++++++++++++++-------- src/platform/psp2/psp2-context.h | 2 ++ 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/CHANGES b/CHANGES index d7b13c1aa..6b2bff781 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,7 @@ Features: - New option to lock the maximum frame size - Memory access and information logging - 3DS: Add faster "loose" sync mode, default enabled + - Vita: Allow using rear touch pads as L2/L3/R2/R3 - 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: New `image` and `canvas` APIs for drawing images and displaying on-screen diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index 7b5d0855f..ad07bc9da 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -46,6 +46,7 @@ static uint32_t _pollInput(const struct mInputMap* map) { SceCtrlData pad; sceCtrlPeekBufferPositiveExt2(0, &pad, 1); int input = mInputMapKeyBits(map, PSP2_INPUT, pad.buttons, 0); + input |= mPSP2ReadTouchLR(map); if (pad.buttons & SCE_CTRL_UP || pad.ly < 64) { input |= 1 << GUI_INPUT_UP; @@ -248,6 +249,7 @@ int main() { }; sceTouchSetSamplingState(SCE_TOUCH_PORT_FRONT, SCE_TOUCH_SAMPLING_STATE_START); + sceTouchSetSamplingState(SCE_TOUCH_PORT_BACK, SCE_TOUCH_SAMPLING_STATE_START); sceCtrlSetSamplingMode(SCE_CTRL_MODE_ANALOG_WIDE); sceCtrlSetSamplingModeExt(SCE_CTRL_MODE_ANALOG_WIDE); sceSysmoduleLoadModule(SCE_SYSMODULE_PHOTO_EXPORT); diff --git a/src/platform/psp2/psp2-context.c b/src/platform/psp2/psp2-context.c index be9ff4495..61549e383 100644 --- a/src/platform/psp2/psp2-context.c +++ b/src/platform/psp2/psp2-context.c @@ -33,6 +33,7 @@ #include #include #include +#include #include @@ -57,6 +58,7 @@ static double fpsRatio = 1; static bool interframeBlending = false; static bool sgbCrop = false; static bool blurry = false; +static SceTouchPanelInfo panelInfo[SCE_TOUCH_PORT_MAX_NUM]; static struct mSceRotationSource { struct mRotationSource d; @@ -290,6 +292,8 @@ uint16_t mPSP2PollInput(struct mGUIRunner* runner) { if (angles != GBA_KEY_NONE) { activeKeys |= 1 << angles; } + activeKeys |= mPSP2ReadTouchLR(&runner->core->inputMap); + return activeKeys; } @@ -313,6 +317,9 @@ void mPSP2Setup(struct mGUIRunner* runner) { mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo", 1); mCoreLoadForeignConfig(runner->core, &runner->config); + sceTouchGetPanelInfo(SCE_TOUCH_PORT_FRONT, &panelInfo[SCE_TOUCH_PORT_FRONT]); + sceTouchGetPanelInfo(SCE_TOUCH_PORT_BACK, &panelInfo[SCE_TOUCH_PORT_BACK]); + mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_CROSS, GBA_KEY_A); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_CIRCLE, GBA_KEY_B); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_START, GBA_KEY_START); @@ -406,18 +413,6 @@ void mPSP2LoadROM(struct mGUIRunner* runner) { mCoreConfigGetBoolValue(&runner->config, "interframeBlending", &interframeBlending); - // Backcompat: Old versions of mGBA use an older binding system that has different mappings for L/R - if (!sceKernelIsPSVitaTV()) { - int key = mInputMapKey(&runner->core->inputMap, PSP2_INPUT, __builtin_ctz(SCE_CTRL_L2)); - if (key >= 0) { - mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_L1, key); - } - key = mInputMapKey(&runner->core->inputMap, PSP2_INPUT, __builtin_ctz(SCE_CTRL_R2)); - if (key >= 0) { - mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_R1, key); - } - } - MutexInit(&audioContext.mutex); ConditionInit(&audioContext.cond); mAudioBufferClear(&audioContext.buffer); @@ -664,6 +659,39 @@ bool mPSP2SystemPoll(struct mGUIRunner* runner) { return true; } +int mPSP2ReadTouchLR(const struct mInputMap* map) { + SceTouchData touch[4]; + int activeKeys = 0; + int touches = sceTouchPeek(SCE_TOUCH_PORT_BACK, touch, 4); + int i; + for (i = 0; i < touches; ++i) { + if (touch[i].reportNum < 1) { + continue; + } + bool left = touch[i].report[0].x < (panelInfo[SCE_TOUCH_PORT_BACK].maxAaX - panelInfo[SCE_TOUCH_PORT_BACK].minAaX) / 2; + bool top = touch[i].report[0].y < (panelInfo[SCE_TOUCH_PORT_BACK].maxAaY - panelInfo[SCE_TOUCH_PORT_BACK].minAaY) / 2; + int button; + if (left) { + if (top) { + button = __builtin_ctz(SCE_CTRL_L2); + } else { + button = __builtin_ctz(SCE_CTRL_L3); + } + } else { + if (top) { + button = __builtin_ctz(SCE_CTRL_R2); + } else { + button = __builtin_ctz(SCE_CTRL_R3); + } + } + int key = mInputMapKey(map, PSP2_INPUT, button); + if (key != -1) { + activeKeys |= 1 << key; + } + } + return activeKeys; +} + __attribute__((noreturn, weak)) void __assert_func(const char* file, int line, const char* func, const char* expr) { printf("ASSERT FAILED: %s in %s at %s:%i\n", expr, func, file, line); exit(1); diff --git a/src/platform/psp2/psp2-context.h b/src/platform/psp2/psp2-context.h index 316dbd434..e45ea671a 100644 --- a/src/platform/psp2/psp2-context.h +++ b/src/platform/psp2/psp2-context.h @@ -28,4 +28,6 @@ void mPSP2SetFrameLimiter(struct mGUIRunner* runner, bool limit); uint16_t mPSP2PollInput(struct mGUIRunner* runner); bool mPSP2SystemPoll(struct mGUIRunner* runner); +int mPSP2ReadTouchLR(const struct mInputMap* map); + #endif From f108dd801de31c04472cab2056fc9cef74ce8b76 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 16 Feb 2025 23:22:41 -0800 Subject: [PATCH 02/74] res: Add Game Boy/Color metadata to .desktop file --- res/mgba-qt.desktop | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/mgba-qt.desktop b/res/mgba-qt.desktop index 2dc96901c..3f14542e2 100644 --- a/res/mgba-qt.desktop +++ b/res/mgba-qt.desktop @@ -8,6 +8,6 @@ Name=mGBA GenericName=Game Boy Advance Emulator Comment=Nintendo Game Boy Advance Emulator Categories=Game;Emulator; -MimeType=application/x-gameboy-advance-rom;application/x-agb-rom;application/x-gba-rom; -Keywords=emulator;Nintendo;advance;gba;Game Boy Advance; +MimeType=application/x-gameboy-advance-rom;application/x-agb-rom;application/x-gba-rom;application/x-gameboy-rom;application/x-gameboy-color-rom; +Keywords=emulator;Nintendo;advance;gba;gbc;gb;Game Boy Advance;Game Boy Color; Game Boy; StartupWMClass=mGBA From 5fd0ba0d6725d6e0f9da2a3ff86b4c28fb085bc1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 23 Feb 2025 22:49:08 -0800 Subject: [PATCH 03/74] GBA Cheats: Let VBA-style codes patch ROM (fixes #3423) --- CHANGES | 1 + src/gba/cheats.c | 27 +++++++++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index 6b2bff781..f998666bc 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,7 @@ Other fixes: - FFmpeg: Fix failing to record videos with CRF video (fixes mgba.io/i/3368) - GB Core: Fix cloning savedata when backing file is outdated (fixes mgba.io/i/3388) - GBA: Fix getting game info for multiboot ROMs + - GBA Cheats: Let VBA-style codes patch ROM (fixes mgba.io/i/3423) - GBA Core: Fix booting into BIOS when skip BIOS is enabled - GBA Hardware: Fix loading states unconditionally overwriting GPIO memory - mGUI: Load parent directory if last used directory is missing (fixes mgba.io/i/3379) diff --git a/src/gba/cheats.c b/src/gba/cheats.c index d055194be..4fb23b1de 100644 --- a/src/gba/cheats.c +++ b/src/gba/cheats.c @@ -180,14 +180,25 @@ bool GBACheatAddVBALine(struct GBACheatSet* cheats, const char* line) { return false; } - struct mCheat* cheat = mCheatListAppend(&cheats->d.list); - cheat->address = address; - cheat->operandOffset = 0; - cheat->addressOffset = 0; - cheat->repeat = 1; - cheat->type = CHEAT_ASSIGN; - cheat->width = width; - cheat->operand = value; + if (address < GBA_BASE_ROM0 || address >= GBA_BASE_SRAM) { + struct mCheat* cheat = mCheatListAppend(&cheats->d.list); + memset(cheat, 0, sizeof(*cheat)); + cheat->address = address; + cheat->operandOffset = 0; + cheat->addressOffset = 0; + cheat->repeat = 1; + cheat->type = CHEAT_ASSIGN; + cheat->width = width; + cheat->operand = value; + } else { + struct mCheatPatch* patch = mCheatPatchListAppend(&cheats->d.romPatches); + memset(patch, 0, sizeof(*patch)); + patch->width = width; + patch->address = address; + patch->segment = 0; + patch->value = value; + patch->check = false; + } return true; } From 9923d1b3a148f21f64e3d4c6161845c6ec621ce3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 26 Feb 2025 23:44:49 -0800 Subject: [PATCH 04/74] Util: Cap internal buffer size when unzipping files (fixes #3404) --- CHANGES | 1 + src/util/vfs/vfs-zip.c | 61 ++++++++++++++++++++++++++++++++---------- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/CHANGES b/CHANGES index f998666bc..675d8c08d 100644 --- a/CHANGES +++ b/CHANGES @@ -37,6 +37,7 @@ Other fixes: - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) - Qt: Fix potential crash when configuring shortcuts - Qt: Fix regression where loading BIOS creates a save file (fixes mgba.io/i/3359) + - Wii: Fix crash on loading large ZIP files (fixes mgba.io/i/3404) Misc: - Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826) - Core: Improve rumble emulation by averaging state over entire frame (fixes mgba.io/i/3232) diff --git a/src/util/vfs/vfs-zip.c b/src/util/vfs/vfs-zip.c index bd803d7bb..49a54a178 100644 --- a/src/util/vfs/vfs-zip.c +++ b/src/util/vfs/vfs-zip.c @@ -11,6 +11,12 @@ #ifdef USE_LIBZIP #include +#ifndef FIXED_ROM_BUFFER +#define MAX_BUFFER_SIZE 0x80000000 +#else +#define MAX_BUFFER_SIZE 0x00200000 +#endif + struct VDirEntryZip { struct VDirEntry d; struct zip* z; @@ -36,6 +42,7 @@ struct VFileZip { size_t fileSize; char* name; bool write; + size_t bufferStart; }; enum { @@ -250,8 +257,9 @@ bool _vfzClose(struct VFile* vf) { zip_source_free(source); return false; } - free(vfz->name); } + free(vfz->name); + vfz->name = NULL; if (vfz->zf && zip_fclose(vfz->zf) < 0) { return false; } @@ -286,6 +294,17 @@ off_t _vfzSeek(struct VFile* vf, off_t offset, int whence) { return -1; } + if (position < vfz->bufferStart) { + if (zip_fclose(vfz->zf) < 0) { + return -1; + } + vfz->zf = zip_fopen(vfz->z, vfz->name, 0); + vfz->bufferStart = 0; + vfz->offset = 0; + vfz->readSize = 0; + vfz->writeSize = 0; + } + if (position <= vfz->offset) { vfz->offset = position; return position; @@ -315,7 +334,7 @@ ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size) { while (bytesRead < size) { if (vfz->offset < vfz->readSize) { size_t diff = vfz->readSize - vfz->offset; - void* start = &((uint8_t*) vfz->buffer)[vfz->offset]; + void* start = &((uint8_t*) vfz->buffer)[vfz->offset - vfz->bufferStart]; if (diff > size - bytesRead) { diff = size - bytesRead; } @@ -330,16 +349,25 @@ ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size) { } } // offset == readSize - if (vfz->readSize == vfz->bufferSize) { - vfz->bufferSize *= 2; - if (vfz->bufferSize > vfz->fileSize) { - vfz->bufferSize = vfz->fileSize; + if (vfz->readSize == vfz->bufferSize + vfz->bufferStart) { + size_t bufferSize = vfz->bufferSize * 2; + void* newBuffer = NULL; + if (bufferSize <= MAX_BUFFER_SIZE) { + if (bufferSize > vfz->fileSize) { + bufferSize = vfz->fileSize; + } + newBuffer = realloc(vfz->buffer, bufferSize); + } + if (newBuffer) { + vfz->bufferSize = bufferSize; + vfz->buffer = newBuffer; + } else { + vfz->bufferStart += vfz->bufferSize; } - vfz->buffer = realloc(vfz->buffer, vfz->bufferSize); } - if (vfz->readSize < vfz->bufferSize) { - void* start = &((uint8_t*) vfz->buffer)[vfz->readSize]; - size_t toRead = vfz->bufferSize - vfz->readSize; + if (vfz->readSize < vfz->bufferSize + vfz->bufferStart) { + void* start = &((uint8_t*) vfz->buffer)[vfz->readSize - vfz->bufferStart]; + size_t toRead = vfz->bufferSize - vfz->readSize - vfz->bufferStart; if (toRead > BLOCK_SIZE) { toRead = BLOCK_SIZE; } @@ -391,9 +419,16 @@ void* _vfzMap(struct VFile* vf, size_t size, int flags) { struct VFileZip* vfz = (struct VFileZip*) vf; UNUSED(flags); + if (size > vfz->fileSize) { + return NULL; + } + size_t start = vfz->bufferStart; if (size > vfz->readSize) { vf->read(vf, 0, size - vfz->readSize); } + if (vfz->bufferStart != start) { + return NULL; + } return vfz->buffer; } @@ -470,10 +505,8 @@ struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode) { vfz->zf = zf; vfz->z = vdz->z; vfz->fileSize = s.size; - if ((mode & O_ACCMODE) == O_WRONLY) { - vfz->name = strdup(path); - vfz->write = true; - } + vfz->name = strdup(path); + vfz->write = (mode & O_ACCMODE) == O_WRONLY; vfz->d.close = _vfzClose; vfz->d.seek = _vfzSeek; From abb46602caf705a58514cdae7d8f5f28eb4a3c14 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 27 Feb 2025 02:38:04 -0800 Subject: [PATCH 05/74] GB: Allow use of CGB-E and AGB-0 BIOS versions (closes #3427) --- CHANGES | 1 + src/gb/gb.c | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGES b/CHANGES index 675d8c08d..d64aec336 100644 --- a/CHANGES +++ b/CHANGES @@ -44,6 +44,7 @@ Misc: - Core: Add MD5 hashing for ROMs - Core: Add support for specifying an arbitrary portable directory - GB: Prevent incompatible BIOSes from being used on differing models + - GB: Allow use of CGB-E and AGB-0 BIOS versions (closes mgba.io/i/3427) - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs - GBA Audio: Remove broken XQ audio pending rewrite diff --git a/src/gb/gb.c b/src/gb/gb.c index 875cbf86e..53b63f498 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -35,7 +35,9 @@ static const uint8_t _registeredTrademark[] = {0x3C, 0x42, 0xB9, 0xA5, 0xB9, 0xA #define SGB2_BIOS_CHECKSUM 0X53D0DD63 #define CGB_BIOS_CHECKSUM 0x41884E46 #define CGB0_BIOS_CHECKSUM 0xE8EF5318 +#define CGBE_BIOS_CHECKSUM 0xE95DC95D #define AGB_BIOS_CHECKSUM 0xFFD6B0F1 +#define AGB0_BIOS_CHECKSUM 0x570337EA mLOG_DEFINE_CATEGORY(GB, "GB", "gb"); @@ -550,7 +552,9 @@ bool GBIsBIOS(struct VFile* vf) { case SGB2_BIOS_CHECKSUM: case CGB_BIOS_CHECKSUM: case CGB0_BIOS_CHECKSUM: + case CGBE_BIOS_CHECKSUM: case AGB_BIOS_CHECKSUM: + case AGB0_BIOS_CHECKSUM: return true; default: return false; @@ -567,7 +571,9 @@ bool GBIsCompatibleBIOS(struct VFile* vf, enum GBModel model) { return model < GB_MODEL_CGB; case CGB_BIOS_CHECKSUM: case CGB0_BIOS_CHECKSUM: + case CGBE_BIOS_CHECKSUM: case AGB_BIOS_CHECKSUM: + case AGB0_BIOS_CHECKSUM: return model >= GB_MODEL_CGB; default: return false; @@ -862,9 +868,11 @@ void GBDetectModel(struct GB* gb) { break; case CGB_BIOS_CHECKSUM: case CGB0_BIOS_CHECKSUM: + case CGBE_BIOS_CHECKSUM: gb->model = GB_MODEL_CGB; break; case AGB_BIOS_CHECKSUM: + case AGB0_BIOS_CHECKSUM: gb->model = GB_MODEL_AGB; break; default: From c302d99d1bd36af05c28a847e9f28a1819f92fa5 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 28 Feb 2025 03:09:15 -0800 Subject: [PATCH 06/74] Libretro: Add missing SCGB model BIOS name --- src/platform/libretro/libretro.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index d2a871546..48d92f6dd 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -960,6 +960,7 @@ bool retro_load_game(const struct retro_game_info* game) { switch (gb->model) { case GB_MODEL_AGB: case GB_MODEL_CGB: + case GB_MODEL_SCGB: biosName = "gbc_bios.bin"; break; case GB_MODEL_SGB: From 51e813aa9a3fbf061c044f2d54d7159ec9bbe524 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 28 Feb 2025 16:18:02 -0800 Subject: [PATCH 07/74] Core: Add ENABLE_DIRECTORIES to optionally slim down VFS further --- CMakeLists.txt | 4 +-- include/mgba-util/vfs.h | 6 ++-- include/mgba/core/cheats.h | 2 +- include/mgba/core/core.h | 8 ++++-- include/mgba/core/directories.h | 2 +- src/core/cheats.c | 2 +- src/core/core.c | 51 +++++++++++++++++++++++---------- src/core/directories.c | 2 +- src/core/flags.h.in | 4 +++ src/gb/core.c | 8 ++++-- src/gba/core.c | 6 ++-- src/util/vfs.c | 4 ++- 12 files changed, 66 insertions(+), 33 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ad464bab0..8c7413c1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -857,7 +857,7 @@ if(ENABLE_SCRIPTING) endif() if(ENABLE_VFS) - list(APPEND ENABLES VFS) + list(APPEND ENABLES VFS DIRECTORIES) endif() foreach(FEATURE IN LISTS FEATURES) @@ -1001,7 +1001,7 @@ endif() if(BUILD_LIBRETRO) file(GLOB RETRO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/libretro/*.c) - add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC} ${VFS_SRC}) + add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC} ${CORE_VFS_SRC}) add_dependencies(${BINARY_NAME}_libretro ${BINARY_NAME}-version-info) set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "__LIBRETRO__;COLOR_16_BIT;COLOR_5_6_5;DISABLE_THREADING;MGBA_STANDALONE;${OS_DEFINES};${FUNCTION_DEFINES};ENABLE_VFS;MINIMAL_CORE=2") target_link_libraries(${BINARY_NAME}_libretro ${OS_LIB}) diff --git a/include/mgba-util/vfs.h b/include/mgba-util/vfs.h index 0279fb755..c23515e5d 100644 --- a/include/mgba-util/vfs.h +++ b/include/mgba-util/vfs.h @@ -50,7 +50,7 @@ struct VFile { bool (*sync)(struct VFile* vf, void* buffer, size_t size); }; -#ifdef ENABLE_VFS +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) struct VDirEntry { const char* (*name)(struct VDirEntry* vde); enum VFSType (*type)(struct VDirEntry* vde); @@ -64,7 +64,9 @@ struct VDir { struct VDir* (*openDir)(struct VDir* vd, const char* name); bool (*deleteFile)(struct VDir* vd, const char* name); }; +#endif +#ifdef ENABLE_VFS struct VFile* VFileOpen(const char* path, int flags); #endif @@ -85,7 +87,7 @@ struct VFile* VFileMemChunk(const void* mem, size_t size); struct mCircleBuffer; struct VFile* VFileFIFO(struct mCircleBuffer* backing); -#ifdef ENABLE_VFS +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) struct VDir* VDirOpen(const char* path); struct VDir* VDirOpenArchive(const char* path); diff --git a/include/mgba/core/cheats.h b/include/mgba/core/cheats.h index a1c6c555c..9f16b24ea 100644 --- a/include/mgba/core/cheats.h +++ b/include/mgba/core/cheats.h @@ -118,7 +118,7 @@ bool mCheatSaveFile(struct mCheatDevice*, struct VFile*); bool mCheatParseLibretroFile(struct mCheatDevice*, struct VFile*); bool mCheatParseEZFChtFile(struct mCheatDevice*, struct VFile*); -#ifdef ENABLE_VFS +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) void mCheatAutosave(struct mCheatDevice*); #endif diff --git a/include/mgba/core/core.h b/include/mgba/core/core.h index 94dfb1114..64eda6627 100644 --- a/include/mgba/core/core.h +++ b/include/mgba/core/core.h @@ -47,7 +47,7 @@ struct mCore { struct mDebuggerSymbols* symbolTable; struct mVideoLogger* videoLogger; -#ifdef ENABLE_VFS +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) struct mDirectorySet dirs; #endif #ifndef MINIMAL_CORE @@ -188,18 +188,20 @@ bool mCorePreloadFile(struct mCore* core, const char* path); bool mCorePreloadVFCB(struct mCore* core, struct VFile* vf, void (cb)(size_t, size_t, void*), void* context); bool mCorePreloadFileCB(struct mCore* core, const char* path, void (cb)(size_t, size_t, void*), void* context); +bool mCoreLoadSaveFile(struct mCore* core, const char* path, bool temporary); + +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) bool mCoreAutoloadSave(struct mCore* core); bool mCoreAutoloadPatch(struct mCore* core); bool mCoreAutoloadCheats(struct mCore* core); -bool mCoreLoadSaveFile(struct mCore* core, const char* path, bool temporary); - bool mCoreSaveState(struct mCore* core, int slot, int flags); bool mCoreLoadState(struct mCore* core, int slot, int flags); struct VFile* mCoreGetState(struct mCore* core, int slot, bool write); void mCoreDeleteState(struct mCore* core, int slot); void mCoreTakeScreenshot(struct mCore* core); +#endif bool mCoreTakeScreenshotVF(struct mCore* core, struct VFile* vf); #endif diff --git a/include/mgba/core/directories.h b/include/mgba/core/directories.h index f9f59cb8d..5d65be8a3 100644 --- a/include/mgba/core/directories.h +++ b/include/mgba/core/directories.h @@ -10,7 +10,7 @@ CXX_GUARD_START -#ifdef ENABLE_VFS +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) struct VDir; struct mDirectorySet { diff --git a/src/core/cheats.c b/src/core/cheats.c index 2fc97c3ac..977d0b7fa 100644 --- a/src/core/cheats.c +++ b/src/core/cheats.c @@ -622,7 +622,7 @@ bool mCheatSaveFile(struct mCheatDevice* device, struct VFile* vf) { return true; } -#ifdef ENABLE_VFS +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) void mCheatAutosave(struct mCheatDevice* device) { if (!device->autosave) { return; diff --git a/src/core/core.c b/src/core/core.c index ba7ef914c..5422a09d3 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -96,8 +96,9 @@ struct mCore* mCoreCreate(enum mPlatform platform) { #endif struct mCore* mCoreFind(const char* path) { - struct VDir* archive = VDirOpenArchive(path); struct mCore* core = NULL; +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) + struct VDir* archive = VDirOpenArchive(path); if (archive) { struct VDirEntry* dirent = archive->listNext(archive); while (dirent) { @@ -114,7 +115,9 @@ struct mCore* mCoreFind(const char* path) { dirent = archive->listNext(archive); } archive->close(archive); - } else { + } else +#endif + { struct VFile* vf = VFileOpen(path, O_RDONLY); if (!vf) { return NULL; @@ -133,7 +136,15 @@ bool mCoreLoadFile(struct mCore* core, const char* path) { #ifdef FIXED_ROM_BUFFER return mCorePreloadFile(core, path); #else +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) struct VFile* rom = mDirectorySetOpenPath(&core->dirs, path, core->isROM); +#else + struct VFile* rom = VFileOpen(path, O_RDONLY); + if (rom && !core->isROM(rom)) { + rom->close(rom); + rom = NULL; + } +#endif if (!rom) { return false; } @@ -210,7 +221,15 @@ bool mCorePreloadVFCB(struct mCore* core, struct VFile* vf, void (cb)(size_t, si } bool mCorePreloadFileCB(struct mCore* core, const char* path, void (cb)(size_t, size_t, void*), void* context) { +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) struct VFile* rom = mDirectorySetOpenPath(&core->dirs, path, core->isROM); +#else + struct VFile* rom = VFileOpen(path, O_RDONLY); + if (rom && !core->isROM(rom)) { + rom->close(rom); + rom = NULL; + } +#endif if (!rom) { return false; } @@ -222,6 +241,19 @@ bool mCorePreloadFileCB(struct mCore* core, const char* path, void (cb)(size_t, return ret; } +bool mCoreLoadSaveFile(struct mCore* core, const char* path, bool temporary) { + struct VFile* vf = VFileOpen(path, O_CREAT | O_RDWR); + if (!vf) { + return false; + } + if (temporary) { + return core->loadTemporarySave(core, vf); + } else { + return core->loadSave(core, vf); + } +} + +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) bool mCoreAutoloadSave(struct mCore* core) { if (!core->dirs.save) { return false; @@ -277,18 +309,6 @@ bool mCoreAutoloadCheats(struct mCore* core) { return success; } -bool mCoreLoadSaveFile(struct mCore* core, const char* path, bool temporary) { - struct VFile* vf = VFileOpen(path, O_CREAT | O_RDWR); - if (!vf) { - return false; - } - if (temporary) { - return core->loadTemporarySave(core, vf); - } else { - return core->loadSave(core, vf); - } -} - bool mCoreSaveState(struct mCore* core, int slot, int flags) { struct VFile* vf = mCoreGetState(core, slot, true); if (!vf) { @@ -373,6 +393,7 @@ void mCoreTakeScreenshot(struct mCore* core) { mLOG(STATUS, WARN, "Failed to take screenshot"); } #endif +#endif bool mCoreTakeScreenshotVF(struct mCore* core, struct VFile* vf) { #ifdef USE_PNG @@ -406,7 +427,7 @@ void mCoreLoadConfig(struct mCore* core) { void mCoreLoadForeignConfig(struct mCore* core, const struct mCoreConfig* config) { mCoreConfigMap(config, &core->opts); -#ifdef ENABLE_VFS +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) mDirectorySetMapOptions(&core->dirs, &core->opts); #endif if (core->opts.audioBuffers) { diff --git a/src/core/directories.c b/src/core/directories.c index e0748f829..2337f5d47 100644 --- a/src/core/directories.c +++ b/src/core/directories.c @@ -8,7 +8,7 @@ #include #include -#ifdef ENABLE_VFS +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) void mDirectorySetInit(struct mDirectorySet* dirs) { dirs->base = NULL; dirs->archive = NULL; diff --git a/src/core/flags.h.in b/src/core/flags.h.in index 3507df032..1071d42c3 100644 --- a/src/core/flags.h.in +++ b/src/core/flags.h.in @@ -53,6 +53,10 @@ #cmakedefine ENABLE_DEBUGGERS #endif +#ifndef ENABLE_DIRECTORIES +#cmakedefine ENABLE_DIRECTORIES +#endif + #ifndef ENABLE_GDB_STUB #cmakedefine ENABLE_GDB_STUB #endif diff --git a/src/gb/core.c b/src/gb/core.c index f71b570e0..a97dd175f 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -150,7 +150,7 @@ static bool _GBCoreInit(struct mCore* core) { gbcore->keys = 0; gb->keySource = &gbcore->keys; -#ifdef ENABLE_VFS +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) mDirectorySetInit(&core->dirs); #endif @@ -162,7 +162,7 @@ static void _GBCoreDeinit(struct mCore* core) { GBDestroy(core->board); mappedMemoryFree(core->cpu, sizeof(struct SM83Core)); mappedMemoryFree(core->board, sizeof(struct GB)); -#ifdef ENABLE_VFS +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) mDirectorySetDeinit(&core->dirs); #endif #ifdef ENABLE_DEBUGGERS @@ -651,6 +651,7 @@ static void _GBCoreReset(struct mCore* core) { bios = NULL; } } +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) if (!found) { char path[PATH_MAX]; mCoreConfigDirectory(path, PATH_MAX); @@ -679,6 +680,7 @@ static void _GBCoreReset(struct mCore* core) { bios = NULL; } } +#endif if (found && bios) { GBLoadBIOS(gb, bios); } @@ -1128,7 +1130,7 @@ static void _GBCoreLoadSymbols(struct mCore* core, struct VFile* vf) { if (!core->symbolTable) { core->symbolTable = mDebuggerSymbolTableCreate(); } -#ifdef ENABLE_VFS +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) if (!vf && core->dirs.base) { vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".sym", O_RDONLY); } diff --git a/src/gba/core.c b/src/gba/core.c index b369c92df..65ec999f2 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -296,7 +296,7 @@ static bool _GBACoreInit(struct mCore* core) { gbacore->proxyRenderer.logger = NULL; #endif -#ifdef ENABLE_VFS +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) mDirectorySetInit(&core->dirs); #endif @@ -308,7 +308,7 @@ static void _GBACoreDeinit(struct mCore* core) { GBADestroy(core->board); mappedMemoryFree(core->cpu, sizeof(struct ARMCore)); mappedMemoryFree(core->board, sizeof(struct GBA)); -#ifdef ENABLE_VFS +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) mDirectorySetDeinit(&core->dirs); #endif #ifdef ENABLE_DEBUGGERS @@ -1352,7 +1352,7 @@ static void _GBACoreLoadSymbols(struct mCore* core, struct VFile* vf) { seek = vf->seek(vf, 0, SEEK_CUR); vf->seek(vf, 0, SEEK_SET); } -#ifdef ENABLE_VFS +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) #ifdef USE_ELF if (!vf && core->dirs.base) { closeAfter = true; diff --git a/src/util/vfs.c b/src/util/vfs.c index a14100bbf..cb9ed40ba 100644 --- a/src/util/vfs.c +++ b/src/util/vfs.c @@ -103,6 +103,7 @@ struct VFile* VFileOpen(const char* path, int flags) { #endif } +#ifdef ENABLE_DIRECTORIES struct VDir* VDirOpenArchive(const char* path) { struct VDir* dir = 0; UNUSED(path); @@ -119,6 +120,7 @@ struct VDir* VDirOpenArchive(const char* path) { return dir; } #endif +#endif ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size) { size_t bytesRead = 0; @@ -250,7 +252,7 @@ void makeAbsolute(const char* path, const char* base, char* out) { strncpy(out, buf, PATH_MAX); } -#ifdef ENABLE_VFS +#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES) struct VFile* VDirFindFirst(struct VDir* dir, bool (*filter)(struct VFile*)) { dir->rewind(dir); struct VDirEntry* dirent = dir->listNext(dir); From eeb52e40274a8240fe066a16b93d9d46c4b677b9 Mon Sep 17 00:00:00 2001 From: Eric Warmenhoven Date: Fri, 24 Jan 2025 11:43:28 -0500 Subject: [PATCH 08/74] libretro: change accelerometer values to be same as for switch --- src/platform/libretro/libretro.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 48d92f6dd..ea05cac05 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -1386,8 +1386,8 @@ static void _updateRotation(struct mRotationSource* source) { gyroZ = 0; _initSensors(); if (tiltEnabled) { - tiltX = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_X) * -2e8f; - tiltY = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_Y) * 2e8f; + tiltX = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_X) * 3e8f; + tiltY = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_Y) * -3e8f; } if (gyroEnabled) { gyroZ = sensorGetCallback(0, RETRO_SENSOR_GYROSCOPE_Z) * -5.5e8f; From 056f53ff4ac0eb78ab2f6c1f0c650955d557e51a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 28 Feb 2025 21:37:04 -0800 Subject: [PATCH 09/74] Feature: Fix memory leak when recording a video log --- src/feature/video-logger.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/feature/video-logger.c b/src/feature/video-logger.c index ac5ef1777..1116ebb96 100644 --- a/src/feature/video-logger.c +++ b/src/feature/video-logger.c @@ -417,6 +417,7 @@ static void _compress(struct VFile* dest, struct VFile* src) { } dest->write(dest, compressBuffer, sizeof(compressBuffer) - zstr.avail_out); } while (sizeof(compressBuffer) - zstr.avail_out); + deflateEnd(&zstr); } static bool _decompress(struct VFile* dest, struct VFile* src, size_t compressedLength) { From 2c71435c4311427c54391ecc0745b45710d5bd73 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 3 Mar 2025 19:18:12 -0800 Subject: [PATCH 10/74] SDL: Add a small bit of allowable extra audio buffering --- src/platform/sdl/sdl-audio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/sdl/sdl-audio.c b/src/platform/sdl/sdl-audio.c index d90ad95d3..8a335609f 100644 --- a/src/platform/sdl/sdl-audio.c +++ b/src/platform/sdl/sdl-audio.c @@ -109,7 +109,7 @@ static void _mSDLAudioCallback(void* context, Uint8* data, int len) { fauxClock = mCoreCalculateFramerateRatio(audioContext->core, audioContext->sync->fpsTarget); } mCoreSyncLockAudio(audioContext->sync); - audioContext->sync->audioHighWater = audioContext->samples + audioContext->resampler.highWaterMark + audioContext->resampler.lowWaterMark; + audioContext->sync->audioHighWater = audioContext->samples + audioContext->resampler.highWaterMark + audioContext->resampler.lowWaterMark + (audioContext->samples >> 6); audioContext->sync->audioHighWater *= sampleRate / (fauxClock * audioContext->obtainedSpec.freq); } mAudioResamplerSetSource(&audioContext->resampler, buffer, sampleRate / fauxClock, true); From ae3a5988d08026083af64e97f348e9b0bd62d222 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 7 Mar 2025 23:03:43 -0800 Subject: [PATCH 11/74] Qt: Placate Coverity a bit --- src/platform/qt/SaveConverter.cpp | 6 +++--- src/platform/qt/Window.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/qt/SaveConverter.cpp b/src/platform/qt/SaveConverter.cpp index 205a7563d..3383e006e 100644 --- a/src/platform/qt/SaveConverter.cpp +++ b/src/platform/qt/SaveConverter.cpp @@ -395,7 +395,7 @@ SaveConverter::AnnotatedSave::AnnotatedSave(mPlatform platform, std::shared_ptr< : container(container) , platform(platform) , size(vf->size()) - , backing(vf) + , backing(std::move(vf)) , endianness(endianness) { } @@ -405,7 +405,7 @@ SaveConverter::AnnotatedSave::AnnotatedSave(GBASavedataType type, std::shared_pt : container(container) , platform(mPLATFORM_GBA) , size(vf->size()) - , backing(vf) + , backing(std::move(vf)) , endianness(endianness) , gba({type}) { @@ -417,7 +417,7 @@ SaveConverter::AnnotatedSave::AnnotatedSave(GBMemoryBankControllerType type, std : container(container) , platform(mPLATFORM_GB) , size(vf->size()) - , backing(vf) + , backing(std::move(vf)) , endianness(endianness) , gb({type}) { diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index a224c3ba4..97d77ed84 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1101,7 +1101,7 @@ void Window::reloadDisplayDriver() { if (!proxy) { proxy = std::make_shared(); } - m_display->setVideoProxy(proxy); + m_display->setVideoProxy(std::move(proxy)); #ifdef ENABLE_SCRIPTING if (m_scripting) { m_scripting->setVideoBackend(m_display->videoBackend()); From 1bbdbd4482dee4e110527092ce92d4fcc8b70f24 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 8 Mar 2025 02:09:28 -0800 Subject: [PATCH 12/74] Util: Placate Coverity a bit more --- src/util/image.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util/image.c b/src/util/image.c index e5ee9f9e2..a6df0e846 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -904,10 +904,10 @@ uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat return color; } - int r = 0; - int g = 0; - int b = 0; - int a = 0xFF; + uint32_t r = 0; + uint32_t g = 0; + uint32_t b = 0; + uint32_t a = 0xFF; switch (from) { case mCOLOR_ARGB8: From b698d4e317d61a4aa00bdd4c291b864bc4f13208 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 8 Mar 2025 02:09:54 -0800 Subject: [PATCH 13/74] ARM: Placate Coverity even more --- src/arm/isa-arm.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/arm/isa-arm.c b/src/arm/isa-arm.c index aaecf2517..dba4dfe94 100644 --- a/src/arm/isa-arm.c +++ b/src/arm/isa-arm.c @@ -743,11 +743,11 @@ DEFINE_INSTRUCTION_ARM(MRSR, \ cpu->gprs[rd] = cpu->spsr.packed;) DEFINE_INSTRUCTION_ARM(MSRI, - int c = opcode & 0x00010000; - int f = opcode & 0x00080000; - int rotate = (opcode & 0x00000F00) >> 7; - int32_t operand = ROR(opcode & 0x000000FF, rotate); - int32_t mask = (c ? 0x000000FF : 0) | (f ? 0xFF000000 : 0); + uint32_t c = opcode & 0x00010000; + uint32_t f = opcode & 0x00080000; + uint32_t rotate = (opcode & 0x00000F00) >> 7; + uint32_t operand = ROR(opcode & 0x000000FF, rotate); + uint32_t mask = (c ? 0x000000FF : 0) | (f ? 0xFF000000 : 0); if (mask & PSR_USER_MASK) { cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_USER_MASK) | (operand & PSR_USER_MASK); } @@ -769,11 +769,11 @@ DEFINE_INSTRUCTION_ARM(MSRI, }) DEFINE_INSTRUCTION_ARM(MSRRI, - int c = opcode & 0x00010000; - int f = opcode & 0x00080000; - int rotate = (opcode & 0x00000F00) >> 7; - int32_t operand = ROR(opcode & 0x000000FF, rotate); - int32_t mask = (c ? 0x000000FF : 0) | (f ? 0xFF000000 : 0); + uint32_t c = opcode & 0x00010000; + uint32_t f = opcode & 0x00080000; + uint32_t rotate = (opcode & 0x00000F00) >> 7; + uint32_t operand = ROR(opcode & 0x000000FF, rotate); + uint32_t mask = (c ? 0x000000FF : 0) | (f ? 0xFF000000 : 0); mask &= PSR_USER_MASK | PSR_PRIV_MASK | PSR_STATE_MASK; cpu->spsr.packed = (cpu->spsr.packed & ~mask) | (operand & mask) | 0x00000010;) From 2751cc23f52bc7d5486907cc9794fba5dbc180d6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 8 Mar 2025 20:07:46 -0800 Subject: [PATCH 14/74] CHANGES: Update for 0.10.5 --- CHANGES | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index d64aec336..cf0083bbc 100644 --- a/CHANGES +++ b/CHANGES @@ -24,27 +24,19 @@ Emulation fixes: - GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) - GBA Video: Improve emulation of window start/end conditions (fixes mgba.io/i/1945) Other fixes: - - ARM Debugger: Fix disassembly of ror r0 barrel shift (fixes mgba.io/i/3412) - Core: Fix inconsistencies with setting game-specific overrides (fixes mgba.io/i/2963) - Debugger: Fix writing to specific segment in command-line debugger - - FFmpeg: Fix failing to record videos with CRF video (fixes mgba.io/i/3368) - - GB Core: Fix cloning savedata when backing file is outdated (fixes mgba.io/i/3388) - GBA: Fix getting game info for multiboot ROMs - - GBA Cheats: Let VBA-style codes patch ROM (fixes mgba.io/i/3423) - - GBA Core: Fix booting into BIOS when skip BIOS is enabled - - GBA Hardware: Fix loading states unconditionally overwriting GPIO memory - mGUI: Load parent directory if last used directory is missing (fixes mgba.io/i/3379) - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) - Qt: Fix potential crash when configuring shortcuts - Qt: Fix regression where loading BIOS creates a save file (fixes mgba.io/i/3359) - - Wii: Fix crash on loading large ZIP files (fixes mgba.io/i/3404) Misc: - Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826) - Core: Improve rumble emulation by averaging state over entire frame (fixes mgba.io/i/3232) - Core: Add MD5 hashing for ROMs - Core: Add support for specifying an arbitrary portable directory - GB: Prevent incompatible BIOSes from being used on differing models - - GB: Allow use of CGB-E and AGB-0 BIOS versions (closes mgba.io/i/3427) - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs - GBA Audio: Remove broken XQ audio pending rewrite @@ -69,7 +61,19 @@ Misc: - Res: Port NSO-gba-colors shader (closes mgba.io/i/2834) - Res: Update gba-colors shader (closes mgba.io/i/2976) - Scripting: Add `callbacks:oneshot` for single-call callbacks + +0.10.5: (2025-03-08) +Other fixes: + - ARM Debugger: Fix disassembly of ror r0 barrel shift (fixes mgba.io/i/3412) + - FFmpeg: Fix failing to record videos with CRF video (fixes mgba.io/i/3368) + - GB Core: Fix cloning savedata when backing file is outdated (fixes mgba.io/i/3388) + - GBA Cheats: Let VBA-style codes patch ROM (fixes mgba.io/i/3423) + - GBA Core: Fix booting into BIOS when skip BIOS is enabled + - GBA Hardware: Fix loading states unconditionally overwriting GPIO memory - Updater: Fix rewriting folders and files on Windows (fixes mgba.io/i/3384) + - Wii: Fix crash on loading large ZIP files (fixes mgba.io/i/3404) +Misc: + - GB: Allow use of CGB-E and AGB-0 BIOS versions (closes mgba.io/i/3427) 0.10.4: (2024-12-07) Emulation fixes: From aa5f4bc3a4aeeeb4135f46a83a89c91ca2ccfb29 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 11 Mar 2025 14:51:04 -0700 Subject: [PATCH 15/74] 3DS: Change title ID to avoid conflict with commercial title (fixes #3023) --- CHANGES | 1 + src/platform/3ds/cia.rsf.in | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index cf0083bbc..faf7b1e9c 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,7 @@ Other fixes: - Qt: Fix potential crash when configuring shortcuts - Qt: Fix regression where loading BIOS creates a save file (fixes mgba.io/i/3359) Misc: + - 3DS: Change title ID to avoid conflict with commercial title (fixes mgba.io/i/3023) - Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826) - Core: Improve rumble emulation by averaging state over entire frame (fixes mgba.io/i/3232) - Core: Add MD5 hashing for ROMs diff --git a/src/platform/3ds/cia.rsf.in b/src/platform/3ds/cia.rsf.in index adc36ea0a..3aab5cb6b 100644 --- a/src/platform/3ds/cia.rsf.in +++ b/src/platform/3ds/cia.rsf.in @@ -9,7 +9,7 @@ BasicInfo: TitleInfo: Category : Application - UniqueId : 0x1A1E + UniqueId : 0xD721 Option: UseOnSD : true # true if App is to be installed to SD From 05d1cff6ebef1a4052855d17cf72e22366d7d858 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 11 Mar 2025 15:08:47 -0700 Subject: [PATCH 16/74] FFmpeg: Add Ut Video option --- CHANGES | 1 + src/platform/qt/VideoView.cpp | 1 + src/platform/qt/VideoView.ui | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/CHANGES b/CHANGES index faf7b1e9c..25485133c 100644 --- a/CHANGES +++ b/CHANGES @@ -37,6 +37,7 @@ Misc: - Core: Improve rumble emulation by averaging state over entire frame (fixes mgba.io/i/3232) - Core: Add MD5 hashing for ROMs - Core: Add support for specifying an arbitrary portable directory + - FFmpeg: Add Ut Video option - 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 diff --git a/src/platform/qt/VideoView.cpp b/src/platform/qt/VideoView.cpp index b03949c2f..21e50f396 100644 --- a/src/platform/qt/VideoView.cpp +++ b/src/platform/qt/VideoView.cpp @@ -65,6 +65,7 @@ VideoView::VideoView(QWidget* parent) s_vcodecMap["hevc"] = "libx265"; s_vcodecMap["hevc nvenc"] = "hevc_nvenc"; s_vcodecMap["theora"] = "libtheora"; + s_vcodecMap["ut video"] = "utvideo"; s_vcodecMap["vp8"] = "libvpx"; s_vcodecMap["vp9"] = "libvpx-vp9"; s_vcodecMap["xvid"] = "libxvid"; diff --git a/src/platform/qt/VideoView.ui b/src/platform/qt/VideoView.ui index c56c5e1b5..3e8775e5c 100644 --- a/src/platform/qt/VideoView.ui +++ b/src/platform/qt/VideoView.ui @@ -302,6 +302,11 @@ VP9 + + + Ut Video + + FFV1 From 95043a58648bc054be9b677bbae9cd69e43fef32 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 14 Mar 2025 13:55:14 -0700 Subject: [PATCH 17/74] Qt: Nahimic sucks --- src/platform/qt/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/qt/main.cpp b/src/platform/qt/main.cpp index 48f1984f7..b091b9705 100644 --- a/src/platform/qt/main.cpp +++ b/src/platform/qt/main.cpp @@ -50,6 +50,7 @@ Q_IMPORT_PLUGIN(QWaylandIntegrationPlugin); #ifdef Q_OS_WIN #include #include +extern "C" __declspec (dllexport) DWORD NoHotPatch = 0x1; #else #include #endif From cfe04fff88445d528889da5b9388cfdbd3f9a3e5 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 14 Mar 2025 14:55:29 -0700 Subject: [PATCH 18/74] OpenGL: Fix layer texture generation --- src/platform/opengl/gl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index de8354c1c..e0a752beb 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -41,7 +41,7 @@ static void mGLContextInit(struct VideoBackend* v, WHandle handle) { _initTex(); context->activeTex = 0; - glGenTextures(VIDEO_LAYER_MAX, context->tex); + glGenTextures(VIDEO_LAYER_MAX, context->layers); int i; for (i = 0; i < VIDEO_LAYER_MAX; ++i) { glBindTexture(GL_TEXTURE_2D, context->layers[i]); From 84704502bd1842791e632fd4c9dcb18118d4aa7f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 14 Mar 2025 14:57:01 -0700 Subject: [PATCH 19/74] Qt: Properly activate context when resizing layers (fixes #3435) --- src/platform/qt/DisplayGL.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index b3a7d62c8..ec69acdea 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -699,7 +699,13 @@ void PainterGL::resizeContext() { dequeueAll(false); mRectangle dims = {0, 0, size.width(), size.height()}; + if (!m_started) { + makeCurrent(); + } m_backend->setLayerDimensions(m_backend, VIDEO_LAYER_IMAGE, &dims); + if (!m_started) { + m_gl->doneCurrent(); + } recenterLayers(); m_dims = size; } From 50a314913f622a8da776972dcc89f2321106a1e0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 15 Mar 2025 01:32:37 -0700 Subject: [PATCH 20/74] GB MBC: Ensure SRAM size is properly updated if GBMBCInit is called again --- src/gb/gb.c | 1 + src/gb/mbc.c | 35 ++++++++++++++++++----------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/gb/gb.c b/src/gb/gb.c index 53b63f498..25ff31419 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -87,6 +87,7 @@ static void GBInit(void* cpu, struct mCPUComponent* component) { gb->isPristine = false; gb->pristineRomSize = 0; gb->yankedRomSize = 0; + gb->sramSize = 0; memset(&gb->gbx, 0, sizeof(gb->gbx)); diff --git a/src/gb/mbc.c b/src/gb/mbc.c index 2a19575a8..551071548 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -274,6 +274,7 @@ void GBMBCSwitchSramHalfBank(struct GB* gb, int half, int bank) { void GBMBCInit(struct GB* gb) { const struct GBCartridge* cart = (const struct GBCartridge*) &gb->memory.rom[0x100]; + size_t sramSize = 0; if (gb->memory.rom && gb->memory.romSize) { if (gb->memory.romSize >= 0x8000) { const struct GBCartridge* cartFooter = (const struct GBCartridge*) &gb->memory.rom[gb->memory.romSize - 0x7F00]; @@ -282,25 +283,25 @@ void GBMBCInit(struct GB* gb) { } } if (gb->gbx.romSize) { - gb->sramSize = gb->gbx.ramSize; + sramSize = gb->gbx.ramSize; gb->memory.mbcType = gb->gbx.mbc; } else { switch (cart->ramSize) { case 0: - gb->sramSize = 0; + sramSize = 0; break; default: case 2: - gb->sramSize = 0x2000; + sramSize = 0x2000; break; case 3: - gb->sramSize = 0x8000; + sramSize = 0x8000; break; case 4: - gb->sramSize = 0x20000; + sramSize = 0x20000; break; case 5: - gb->sramSize = 0x10000; + sramSize = 0x10000; break; } } @@ -399,7 +400,7 @@ void GBMBCInit(struct GB* gb) { gb->memory.mbcWrite = _GBMBC2; gb->memory.mbcRead = _GBMBC2Read; gb->memory.directSramAccess = false; - gb->sramSize = 0x100; + sramSize = 0x100; break; case GB_MBC3: gb->memory.mbcWrite = _GBMBC3; @@ -414,15 +415,15 @@ void GBMBCInit(struct GB* gb) { gb->memory.mbcWrite = _GBMBC6; gb->memory.mbcRead = _GBMBC6Read; gb->memory.directSramAccess = false; - if (!gb->sramSize) { - gb->sramSize = GB_SIZE_EXTERNAL_RAM; // Force minimum size for convenience + if (!sramSize) { + sramSize = GB_SIZE_EXTERNAL_RAM; // Force minimum size for convenience } - gb->sramSize += GB_SIZE_MBC6_FLASH; // Flash is concatenated at the end + sramSize += GB_SIZE_MBC6_FLASH; // Flash is concatenated at the end break; case GB_MBC7: gb->memory.mbcWrite = _GBMBC7; gb->memory.mbcRead = _GBMBC7Read; - gb->sramSize = 0x100; + sramSize = 0x100; break; case GB_MMM01: gb->memory.mbcWrite = _GBMMM01; @@ -440,7 +441,7 @@ void GBMBCInit(struct GB* gb) { gb->memory.mbcState.tama5.rtcAlarmPage[GBTAMA6_RTC_PAGE] = 1; gb->memory.mbcState.tama5.rtcFreePage0[GBTAMA6_RTC_PAGE] = 2; gb->memory.mbcState.tama5.rtcFreePage1[GBTAMA6_RTC_PAGE] = 3; - gb->sramSize = 0x20; + sramSize = 0x20; break; case GB_MBC3_RTC: memset(gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs)); @@ -452,8 +453,8 @@ void GBMBCInit(struct GB* gb) { case GB_POCKETCAM: gb->memory.mbcWrite = _GBPocketCam; gb->memory.mbcRead = _GBPocketCamRead; - if (!gb->sramSize) { - gb->sramSize = GB_SIZE_EXTERNAL_RAM; // Force minimum size for convenience + if (!sramSize) { + sramSize = GB_SIZE_EXTERNAL_RAM; // Force minimum size for convenience } if (gb->memory.cam && gb->memory.cam->startRequestImage) { gb->memory.cam->startRequestImage(gb->memory.cam, GBCAM_WIDTH, GBCAM_HEIGHT, mCOLOR_ANY); @@ -508,7 +509,7 @@ void GBMBCInit(struct GB* gb) { gb->memory.mbcReadBank1 = true; gb->memory.mbcReadHigh = true; gb->memory.mbcWriteHigh = true; - if (gb->sramSize) { + if (sramSize) { gb->memory.sramAccess = true; } break; @@ -516,7 +517,7 @@ void GBMBCInit(struct GB* gb) { gb->memory.mbcWrite = _GBSintax; gb->memory.mbcRead = _GBSintaxRead; gb->memory.mbcReadBank1 = true; - if (gb->sramSize) { + if (sramSize) { gb->memory.sramAccess = true; } break; @@ -539,7 +540,7 @@ void GBMBCInit(struct GB* gb) { } memset(&gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs)); - GBResizeSram(gb, gb->sramSize); + GBResizeSram(gb, sramSize); if (gb->memory.mbcType == GB_MBC3_RTC) { GBMBCRTCRead(gb); From 0b0961b80490af4bc5acac6ecf9791832e690f27 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 15 Mar 2025 02:30:29 -0700 Subject: [PATCH 21/74] Qt: Fix up memory view size hints --- src/platform/qt/MemoryModel.cpp | 4 ++++ src/platform/qt/MemoryView.ui | 8 +------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/platform/qt/MemoryModel.cpp b/src/platform/qt/MemoryModel.cpp index 9bcf54223..ca264d4c1 100644 --- a/src/platform/qt/MemoryModel.cpp +++ b/src/platform/qt/MemoryModel.cpp @@ -80,13 +80,17 @@ MemoryModel::MemoryModel(QWidget* parent) setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + int hintWidth; m_margins = QMargins(3, m_cellHeight + 1, 3, 0); #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) m_margins += QMargins(metrics.horizontalAdvance("0FFFFFF0 "), 0, metrics.horizontalAdvance(" AAAAAAAAAAAAAAAA"), 0); + hintWidth = metrics.horizontalAdvance(" 0FFFFFF0 FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF AAAAAAAAAAAAAAAA"); #else m_margins += QMargins(metrics.width("0FFFFFF0 "), 0, metrics.width(" AAAAAAAAAAAAAAAA"), 0); + hintWidth = metrics.width(" 0FFFFFF0 FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF AAAAAAAAAAAAAAAA"); #endif m_cellSize = QSizeF((viewport()->size().width() - (m_margins.left() + m_margins.right())) / 16.0, m_cellHeight); + setMinimumWidth(hintWidth); connect(verticalScrollBar(), &QSlider::sliderMoved, [this](int position) { m_top = position; diff --git a/src/platform/qt/MemoryView.ui b/src/platform/qt/MemoryView.ui index 73407fdd1..0b9ef2e68 100644 --- a/src/platform/qt/MemoryView.ui +++ b/src/platform/qt/MemoryView.ui @@ -17,17 +17,11 @@ - + 0 0 - - - 200 - 0 - - From 360a163ad66a47c1dc4fe2aece3691def931993f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 15 Mar 2025 02:36:54 -0700 Subject: [PATCH 22/74] Qt: Mark Ut Video string as notr --- src/platform/qt/VideoView.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/VideoView.ui b/src/platform/qt/VideoView.ui index 3e8775e5c..604d63aca 100644 --- a/src/platform/qt/VideoView.ui +++ b/src/platform/qt/VideoView.ui @@ -304,7 +304,7 @@ - Ut Video + Ut Video From 10eb794cfd602eac6f70f01838a420eebd9b8267 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 15 Mar 2025 03:02:09 -0700 Subject: [PATCH 23/74] GB: Better handling of SRAM and ROM unloading/reloading --- src/gb/gb.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/gb/gb.c b/src/gb/gb.c index 25ff31419..102e4cfa2 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -233,7 +233,7 @@ static void GBSramDeinit(struct GB* gb) { } else if (gb->memory.sram) { mappedMemoryFree(gb->memory.sram, gb->sramSize); } - gb->memory.sram = 0; + gb->memory.sram = NULL; } bool GBLoadSave(struct GB* gb, struct VFile* vf) { @@ -342,6 +342,7 @@ void GBResizeSram(struct GB* gb, size_t size) { mappedMemoryFree(gb->memory.sram, gb->sramSize); } else { memset(newSram, 0xFF, size); + gb->sramSize = size; } gb->memory.sram = newSram; } @@ -438,6 +439,8 @@ void GBUnloadROM(struct GB* gb) { gb->memory.rom = NULL; gb->memory.mbcType = GB_MBC_AUTODETECT; gb->isPristine = false; + gb->pristineRomSize = 0; + gb->memory.romSize = 0; if (!gb->sramDirty) { gb->sramMaskWriteback = false; @@ -447,6 +450,7 @@ void GBUnloadROM(struct GB* gb) { if (gb->sramRealVf) { gb->sramRealVf->close(gb->sramRealVf); } + gb->sramSize = 0; gb->sramRealVf = NULL; gb->sramVf = NULL; if (gb->memory.cam && gb->memory.cam->stopRequestImage) { From 410fbccb5a9ff4991a2f5faf7cb29aa840d19972 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 26 Mar 2025 22:46:45 -0700 Subject: [PATCH 24/74] GBA e-Reader: Attempt to improve scan consistency --- src/gba/cart/ereader.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gba/cart/ereader.c b/src/gba/cart/ereader.c index 1712f2833..92bb3e92e 100644 --- a/src/gba/cart/ereader.c +++ b/src/gba/cart/ereader.c @@ -650,7 +650,7 @@ void _eReaderWriteControl0(struct GBACartEReader* ereader, uint8_t value) { } ereader->registerControl0 = control; if (!EReaderControl0IsScan(oldControl) && EReaderControl0IsScan(control)) { - if (ereader->scanX > 1000) { + if (ereader->scanX > 0) { _eReaderScanCard(ereader); } ereader->scanX = 0; @@ -668,7 +668,7 @@ void _eReaderWriteControl1(struct GBACartEReader* ereader, uint8_t value) { ++ereader->scanY; if (ereader->scanY == (ereader->serial[0x15] | (ereader->serial[0x14] << 8))) { ereader->scanY = 0; - if (ereader->scanX < 3400) { + if (ereader->scanX < 4050) { ereader->scanX += 210; } } From 62247f0dce6b6959a426e0bc53e7ccbf0893f06c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 30 Mar 2025 15:47:59 -0700 Subject: [PATCH 25/74] res: Update No-Intro db --- res/nointro.dat | 1194 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 918 insertions(+), 276 deletions(-) diff --git a/res/nointro.dat b/res/nointro.dat index 48047026f..a8ac6c381 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 20240807-214821 - author "aci68, Arctic Circle System, Aringon, Bent, BigFred, bikerspade, C. V. Reynolds, chillerecke, DeadSkullzJr, Densetsu, DeriLoko3, einstein95, ElBarto, Enker, FakeShemp, Flashfire42, fuzzball, Gefflon, Hiccup, hking0036, hydr0x, InternalLoss, Jack, jimmsu, Just001Kim, kazumi213, Larsenv, Lesserkuma, Madeline, MeguCocoa, Money_114, NESBrew12, niemand, nnssxx, norkmetnoil577, NovaAurora, omonim2007, Powerpuff, PPLToast, Prominos, Psychofox11, psykopat, rarenight, relax, RetroGamer, Rifu, sCZther, SonGoku, Tauwasser, Tescu, togemet2, ufocrossing, Vallaine01, Whovian9369, xprism, xuom2, zg" + version 20250329-151034 + author "aci68, Arctic Circle System, Aringon, Bent, BigFred, bikerspade, buckwheat, C. V. Reynolds, chillerecke, DeadSkullzJr, Densetsu, DeriLoko3, einstein95, ElBarto, Enker, FakeShemp, Flashfire42, fuzzball, Gefflon, Hiccup, hking0036, hydr0x, InternalLoss, Jack, jimmsu, Just001Kim, kazumi213, Larsenv, Lesserkuma, Madeline, MeguCocoa, Money_114, NESBrew12, niemand, nnssxx, norkmetnoil577, NovaAurora, number, omonim2007, Powerpuff, PPLToast, Prominos, Psychofox11, psykopat, 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 @@ -12,6 +12,12 @@ emulator ( name "datafile" ) +game ( + name "[BIOS] EXEQ - GameBox (World) (Pirate)" + description "[BIOS] EXEQ - GameBox (World) (Pirate)" + rom ( name "[BIOS] EXEQ - GameBox (World) (Pirate).bin" size 16384 crc ac16d18d sha1 dca614d5ab7ab238043f8097c88944fa48381c71 flags verified ) +) + game ( name "[BIOS] Game Boy Advance (World) (GameCube)" description "[BIOS] Game Boy Advance (World) (GameCube)" @@ -31,9 +37,15 @@ game ( ) game ( - name "[BIOS] Game Boy Advance (GBC Mode) (World)" - description "[BIOS] Game Boy Advance (GBC Mode) (World)" - rom ( name "[BIOS] Game Boy Advance (GBC Mode) (World).gbc" size 2304 crc ffd6b0f1 sha1 fa5287e24b0fa533b3b5ef2b28a81245346c1a0f ) + name "[BIOS] Game Boy Color Mode Boot ROM (World) (Rev 1)" + description "[BIOS] Game Boy Color Mode Boot ROM (World) (Rev 1)" + rom ( name "[BIOS] Game Boy Color Mode Boot ROM (World) (Rev 1).gbc" size 2304 crc ffd6b0f1 sha1 fa5287e24b0fa533b3b5ef2b28a81245346c1a0f ) +) + +game ( + name "[BIOS] Game Boy Color Mode Boot ROM (World)" + description "[BIOS] Game Boy Color Mode Boot ROM (World)" + rom ( name "[BIOS] Game Boy Color Mode Boot ROM (World).gbc" size 2304 crc 570337ea sha1 0daac31acb6cb346fc954368acb02acb3adcc3ab ) ) game ( @@ -1266,6 +1278,48 @@ game ( rom ( name "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (En,Fr,De,Es,It).gba" size 8388608 crc 698377d2 sha1 4701c8d26cc608109bc01d0ee189ab1fb646ead8 flags verified ) ) +game ( + name "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2005-10-04)" + description "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2005-10-04)" + rom ( name "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2005-10-04).gba" size 8388608 crc 8df1b63b sha1 3ffc0108d6b09c5b06c37c835f318a2db2a7c85e ) +) + +game ( + name "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2005-11-03)" + description "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2005-11-03)" + rom ( name "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2005-11-03).gba" size 8388608 crc a9384458 sha1 beb9c0f8be393a425640ca5b561dae79a4646511 ) +) + +game ( + name "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2005-11-28)" + description "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2005-11-28)" + rom ( name "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2005-11-28).gba" size 8388608 crc da9d458f sha1 d6de45ed929ea2a162691b0da0fb4c4fb870523b ) +) + +game ( + name "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2005-12-10)" + description "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2005-12-10)" + rom ( name "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2005-12-10).gba" size 8388608 crc 29afe447 sha1 af18e424a5b1b2bab61a3ee4eab309982ac2a7c2 ) +) + +game ( + name "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2005-12-27)" + description "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2005-12-27)" + rom ( name "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2005-12-27).gba" size 8388608 crc 8f9253bb sha1 7cb9a96ec864810d67935fc21db045433432563b ) +) + +game ( + name "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2006-02-07)" + description "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2006-02-07)" + rom ( name "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (Beta) (2006-02-07).gba" size 4469328 crc b96c5a0b sha1 9f1c6f855b66cf297cfc19393422b409563da3f2 ) +) + +game ( + name "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (En,Fr,De,Es,It) (Beta) (2006-04-25)" + description "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (En,Fr,De,Es,It) (Beta) (2006-04-25)" + rom ( name "American Dragon - Jake Long - Rise of the Huntsclan (USA, Europe) (En,Fr,De,Es,It) (Beta) (2006-04-25).gba" size 7551852 crc bdcc074a sha1 0b922d09a8662d119eac9f4c39bede425a3a84ff ) +) + game ( name "American Idol (USA)" description "American Idol (USA)" @@ -1362,18 +1416,18 @@ game ( rom ( name "Ant Bully, The (Europe) (En,Fr,De,Es,It).gba" size 8388608 crc 52dc386b sha1 a735364887c31a19fa7d250ec830d02220d6a6cd ) ) -game ( - name "Antz - Extreme Racing (Europe) (En,Fr,De,Es,It,Nl)" - description "Antz - Extreme Racing (Europe) (En,Fr,De,Es,It,Nl)" - rom ( name "Antz - Extreme Racing (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc 367927ed sha1 68fca49cba74dc108f50ec762ab91b6dff5a92b8 ) -) - game ( name "Antz - Extreme Racing (USA)" description "Antz - Extreme Racing (USA)" rom ( name "Antz - Extreme Racing (USA).gba" size 4194304 crc f4efc5ed sha1 ad6ded0f643457d652292bb97e30b1ad442e41da ) ) +game ( + name "Antz - Extreme Racing (Europe) (En,Fr,De,Es,It,Nl)" + description "Antz - Extreme Racing (Europe) (En,Fr,De,Es,It,Nl)" + rom ( name "Antz - Extreme Racing (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc 367927ed sha1 68fca49cba74dc108f50ec762ab91b6dff5a92b8 ) +) + game ( name "Ao-Zora to Nakama-tachi - Yume no Bouken (Japan)" description "Ao-Zora to Nakama-tachi - Yume no Bouken (Japan)" @@ -1506,18 +1560,18 @@ 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) (Beta)" - description "Atomic Betty (USA, Europe) (Beta)" - rom ( name "Atomic Betty (USA, Europe) (Beta).gba" size 8388608 crc b79472b8 sha1 1dfa6ad1bdc0ee52b4e53c78f29d92ec208db1b1 ) -) - 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)" + rom ( name "Atomic Betty (USA, Europe) (Beta).gba" size 8388608 crc b79472b8 sha1 1dfa6ad1bdc0ee52b4e53c78f29d92ec208db1b1 ) +) + game ( name "Atomix (World) (Unl)" description "Atomix (World) (Unl)" @@ -1938,6 +1992,12 @@ game ( rom ( name "Barnyard (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc f2b06467 sha1 2fe05e2c9dfc02efe415c735f95fa1d4cd12eeb7 flags verified ) ) +game ( + name "Barnyard (USA) (En,Fr,De,Es,It,Nl) (Beta)" + description "Barnyard (USA) (En,Fr,De,Es,It,Nl) (Beta)" + rom ( name "Barnyard (USA) (En,Fr,De,Es,It,Nl) (Beta).gba" size 8388608 crc 178cae0d sha1 f5b6e4da35f904a2b290d9852babb9b2346d63b1 ) +) + game ( name "Baseball Advance (USA)" description "Baseball Advance (USA)" @@ -1986,12 +2046,6 @@ game ( rom ( name "Battle B-Daman - Fire Spirits! (USA).gba" size 8388608 crc 5789f441 sha1 d57084c399bbd4b37d145b712ff6ca22db9b1c49 ) ) -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 ) -) - game ( name "Battle Network - Rockman EXE (Japan)" description "Battle Network - Rockman EXE (Japan)" @@ -1999,9 +2053,9 @@ game ( ) 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 (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 ( @@ -2023,9 +2077,15 @@ game ( ) 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 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 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 ( @@ -2035,9 +2095,15 @@ game ( ) 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)" + 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 ) ) game ( @@ -2052,12 +2118,6 @@ game ( 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) (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 Picross (World) (Unl)" description "Battle Picross (World) (Unl)" @@ -2226,12 +2286,6 @@ 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)" @@ -2244,6 +2298,12 @@ 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)" @@ -2253,7 +2313,7 @@ game ( game ( name "Bionicle Heroes (Europe) (En,Fr,De,Es,It,Da)" description "Bionicle Heroes (Europe) (En,Fr,De,Es,It,Da)" - rom ( name "Bionicle Heroes (Europe) (En,Fr,De,Es,It,Da).gba" size 16777216 crc b8dc715b sha1 102304aab2816c3483618498cab65c1626f1ced5 ) + rom ( name "Bionicle Heroes (Europe) (En,Fr,De,Es,It,Da).gba" size 16777216 crc b8dc715b sha1 102304aab2816c3483618498cab65c1626f1ced5 flags verified ) ) game ( @@ -3024,6 +3084,12 @@ game ( rom ( name "Cars - Motori Ruggenti (Italy).gba" size 8388608 crc 23cb1a36 sha1 98f3733112e85eff63fcef6aefe73d1f19694151 ) ) +game ( + name "Cartoon Network Block Party (USA) (Beta) (2003-06-17)" + description "Cartoon Network Block Party (USA) (Beta) (2003-06-17)" + rom ( name "Cartoon Network Block Party (USA) (Beta) (2003-06-17).gba" size 4900420 crc 5cfac364 sha1 9933db873fa7c7ed8a5fada443aba2d97c81c3b5 ) +) + game ( name "Cartoon Network Block Party (USA)" description "Cartoon Network Block Party (USA)" @@ -3033,7 +3099,7 @@ game ( game ( name "Cartoon Network Speedway (USA)" description "Cartoon Network Speedway (USA)" - rom ( name "Cartoon Network Speedway (USA).gba" size 4194304 crc 066a2705 sha1 26afa157c527dcaa5a4fa0eccc772426156320d8 ) + rom ( name "Cartoon Network Speedway (USA).gba" size 4194304 crc 066a2705 sha1 26afa157c527dcaa5a4fa0eccc772426156320d8 flags verified ) ) game ( @@ -3457,15 +3523,15 @@ game ( ) game ( - name "Cinnamon Game Series 2 - Yume no Daibouken (Japan)" - description "Cinnamon Game Series 2 - Yume no Daibouken (Japan)" - rom ( name "Cinnamon Game Series 2 - Yume no Daibouken (Japan).gba" size 4194304 crc 5a2cada1 sha1 ffa0b28b9cf3e3a96713b41abca4c076123702ea ) + name "Cinnamon - Fuwafuwa Daisakusen (Japan)" + description "Cinnamon - Fuwafuwa Daisakusen (Japan)" + rom ( name "Cinnamon - Fuwafuwa Daisakusen (Japan).gba" size 4194304 crc a1cba145 sha1 7ae7c0ffc7086b7d7c1170cf52eb63c9f2b5b5e3 ) ) game ( - name "Cinnamon Game Series 3 - Fuwafuwa Daisakusen (Japan)" - description "Cinnamon Game Series 3 - Fuwafuwa Daisakusen (Japan)" - rom ( name "Cinnamon Game Series 3 - Fuwafuwa Daisakusen (Japan).gba" size 4194304 crc a1cba145 sha1 7ae7c0ffc7086b7d7c1170cf52eb63c9f2b5b5e3 ) + name "Cinnamon - Yume no Daibouken (Japan)" + description "Cinnamon - Yume no Daibouken (Japan)" + rom ( name "Cinnamon - Yume no Daibouken (Japan).gba" size 4194304 crc 5a2cada1 sha1 ffa0b28b9cf3e3a96713b41abca4c076123702ea ) ) game ( @@ -3909,7 +3975,7 @@ game ( game ( name "Croket! 3 - Granu Oukoku no Nazo (Japan)" description "Croket! 3 - Granu Oukoku no Nazo (Japan)" - rom ( name "Croket! 3 - Granu Oukoku no Nazo (Japan).gba" size 16777216 crc aec6d07b sha1 40944c9e38cecaa18496bdff6b4c0b34ed58906f ) + rom ( name "Croket! 3 - Granu Oukoku no Nazo (Japan).gba" size 16777216 crc aec6d07b sha1 40944c9e38cecaa18496bdff6b4c0b34ed58906f flags verified ) ) game ( @@ -4914,6 +4980,12 @@ game ( rom ( name "Dora the Explorer Double Pack (USA).gba" size 8388608 crc 1f9d002e sha1 bc11d9c123f7390f8b0d691528f2a9a5fcf423af ) ) +game ( + name "Dora the Explorer Double Pack (USA) (Beta)" + description "Dora the Explorer Double Pack (USA) (Beta)" + rom ( name "Dora the Explorer Double Pack (USA) (Beta).gba" size 8388608 crc 185f34f5 sha1 5418a9f4be35df3b26b448042755af69ed7f2305 ) +) + game ( name "Doraemon - Dokodemo Walker (Japan)" description "Doraemon - Dokodemo Walker (Japan)" @@ -5154,18 +5226,6 @@ game ( rom ( name "Dragon Ball Z - The Legacy of Goku (Europe) (En,Fr,De,Es,It).gba" size 8388608 crc ca6e149c sha1 c2691355247b03083578071d4d4a017f5599be20 flags verified ) ) -game ( - name "Dragon Ball Z - The Legacy of Goku II (Europe) (En,Fr,De,Es,It)" - description "Dragon Ball Z - The Legacy of Goku II (Europe) (En,Fr,De,Es,It)" - rom ( name "Dragon Ball Z - The Legacy of Goku II (Europe) (En,Fr,De,Es,It).gba" size 8388608 crc 20684433 sha1 db36ff52fcd63f753f9d66439aa3d2216701c326 flags verified ) -) - -game ( - name "Dragon Ball Z - The Legacy of Goku II (USA)" - description "Dragon Ball Z - The Legacy of Goku II (USA)" - rom ( name "Dragon Ball Z - The Legacy of Goku II (USA).gba" size 8388608 crc 204142e1 sha1 18e0715dec419f3501c301511530d2edcd590f8b flags verified ) -) - game ( name "Dragon Ball Z - The Legacy of Goku II (USA) (Beta) (2003-01-23)" description "Dragon Ball Z - The Legacy of Goku II (USA) (Beta) (2003-01-23)" @@ -5178,6 +5238,18 @@ game ( rom ( name "Dragon Ball Z - The Legacy of Goku II (USA) (Beta) (2003-01-31).gba" size 7279012 crc 25f34dbc sha1 fc76f3477f19af7415a42a6f38bd5bda43c640d2 ) ) +game ( + name "Dragon Ball Z - The Legacy of Goku II (Europe) (En,Fr,De,Es,It)" + description "Dragon Ball Z - The Legacy of Goku II (Europe) (En,Fr,De,Es,It)" + rom ( name "Dragon Ball Z - The Legacy of Goku II (Europe) (En,Fr,De,Es,It).gba" size 8388608 crc 20684433 sha1 db36ff52fcd63f753f9d66439aa3d2216701c326 flags verified ) +) + +game ( + name "Dragon Ball Z - The Legacy of Goku II (USA)" + description "Dragon Ball Z - The Legacy of Goku II (USA)" + rom ( name "Dragon Ball Z - The Legacy of Goku II (USA).gba" size 8388608 crc 204142e1 sha1 18e0715dec419f3501c301511530d2edcd590f8b flags verified ) +) + game ( name "Dragon Ball Z - The Legacy of Goku II International (Japan)" description "Dragon Ball Z - The Legacy of Goku II International (Japan)" @@ -5226,6 +5298,12 @@ game ( rom ( name "Drake & Josh (USA) (En,Fr).gba" size 4194304 crc 63a4422e sha1 07499bf3329bcd38f4dd8bb7e392e44957243534 ) ) +game ( + name "Drake & Josh (USA) (En,Fr) (Beta) (2007-01-23)" + description "Drake & Josh (USA) (En,Fr) (Beta) (2007-01-23)" + rom ( name "Drake & Josh (USA) (En,Fr) (Beta) (2007-01-23).gba" size 4175464 crc e91e21f8 sha1 0042d7e1434c87cb81b53d26059b0a462d45244c ) +) + game ( name "Drill Dozer (USA)" description "Drill Dozer (USA)" @@ -5419,9 +5497,9 @@ game ( ) game ( - name "Duke Nukem Advance (World) (En,Fr,De,It) (Evercade) (Unl)" - description "Duke Nukem Advance (World) (En,Fr,De,It) (Evercade) (Unl)" - rom ( name "Duke Nukem Advance (World) (En,Fr,De,It) (Evercade) (Unl).gba" size 8388608 crc 2b077058 sha1 926b6c74d408cb3ff37a5276ddc3be8e2512c0b3 ) + name "Duke Nukem Advance (World) (En,Fr,De,It) (Evercade)" + description "Duke Nukem Advance (World) (En,Fr,De,It) (Evercade)" + rom ( name "Duke Nukem Advance (World) (En,Fr,De,It) (Evercade).gba" size 8388608 crc 2b077058 sha1 926b6c74d408cb3ff37a5276ddc3be8e2512c0b3 ) ) game ( @@ -6051,7 +6129,7 @@ game ( game ( name "Famicom Mini 01 - Super Mario Bros. (Japan) (En) (Rev 1)" description "Famicom Mini 01 - Super Mario Bros. (Japan) (En) (Rev 1)" - rom ( name "Famicom Mini 01 - Super Mario Bros. (Japan) (En) (Rev 1).gba" size 1048576 crc cd2604dd sha1 f08b1f60e41fc2080c50c65ea2b2af912661ed99 ) + rom ( name "Famicom Mini 01 - Super Mario Bros. (Japan) (En) (Rev 1).gba" size 1048576 crc cd2604dd sha1 f08b1f60e41fc2080c50c65ea2b2af912661ed99 flags verified ) ) game ( @@ -7053,7 +7131,7 @@ game ( game ( name "Gachasute! Dino Device 2 - Phoenix (Japan)" description "Gachasute! Dino Device 2 - Phoenix (Japan)" - rom ( name "Gachasute! Dino Device 2 - Phoenix (Japan).gba" size 4194304 crc 67e21bfe sha1 a209ab6e216a1054961bb25c26aef79c749e2d67 ) + rom ( name "Gachasute! Dino Device 2 - Phoenix (Japan).gba" size 4194304 crc 67e21bfe sha1 a209ab6e216a1054961bb25c26aef79c749e2d67 flags verified ) ) game ( @@ -7356,6 +7434,12 @@ game ( rom ( name "Get! - Boku no Mushi Tsukamaete (Japan).gba" size 8388608 crc 41ea70a3 sha1 0491bf0e9fe5b240c1de93460c41bfeb63b2ed1b ) ) +game ( + name "Get's!! Loto Club (Japan) (Proto)" + description "Get's!! Loto Club (Japan) (Proto)" + rom ( name "Get's!! Loto Club (Japan) (Proto).gba" size 33554433 crc 7dbccfba sha1 3107f839aaeb33cf08e8fbde284df1ea86051aae ) +) + game ( name "GetBackers Dakkanya - Jagan Fuuin! (Japan)" description "GetBackers Dakkanya - Jagan Fuuin! (Japan)" @@ -7971,7 +8055,7 @@ game ( game ( name "Harry Potter and the Goblet of Fire (USA, Europe) (En,Fr,De,Es,It,Nl,Da)" description "Harry Potter and the Goblet of Fire (USA, Europe) (En,Fr,De,Es,It,Nl,Da)" - rom ( name "Harry Potter and the Goblet of Fire (USA, Europe) (En,Fr,De,Es,It,Nl,Da).gba" size 33554432 crc 052708d7 sha1 05cb9e9fb38e18e6e9b547168efedb25343d5ad0 ) + rom ( name "Harry Potter and the Goblet of Fire (USA, Europe) (En,Fr,De,Es,It,Nl,Da).gba" size 33554432 crc 052708d7 sha1 05cb9e9fb38e18e6e9b547168efedb25343d5ad0 flags verified ) ) game ( @@ -8863,9 +8947,9 @@ game ( ) 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)" - rom ( name "Jonny Moseley Mad Trix (USA, Europe) (En,Fr,De,Es,It).gba" size 8388608 crc 96488632 sha1 6ecf01dc70782184e4e45e4db280fb24f840fd6f flags verified ) + name "Jonny Moseley Mad Trix (USA) (En,Fr,De,Es,It)" + description "Jonny Moseley Mad Trix (USA) (En,Fr,De,Es,It)" + rom ( name "Jonny Moseley Mad Trix (USA) (En,Fr,De,Es,It).gba" size 8388608 crc 96488632 sha1 6ecf01dc70782184e4e45e4db280fb24f840fd6f flags verified ) ) game ( @@ -9783,7 +9867,7 @@ game ( game ( name "Lady Sia (USA) (En,Fr,De,Es,It,Nl)" description "Lady Sia (USA) (En,Fr,De,Es,It,Nl)" - rom ( name "Lady Sia (USA) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc 139e95bd sha1 5d5a209a16e6b5548dc1d1a0375de8f024dfe6c3 ) + rom ( name "Lady Sia (USA) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc 139e95bd sha1 5d5a209a16e6b5548dc1d1a0375de8f024dfe6c3 flags verified ) ) game ( @@ -10983,7 +11067,7 @@ game ( game ( name "Mawaru - Made in Wario (Japan)" description "Mawaru - Made in Wario (Japan)" - rom ( name "Mawaru - Made in Wario (Japan).gba" size 16777216 crc e69964f1 sha1 a389fa50e2e842b264b980cbe30e980c69d93a5b ) + rom ( name "Mawaru - Made in Wario (Japan).gba" size 16777216 crc e69964f1 sha1 a389fa50e2e842b264b980cbe30e980c69d93a5b flags verified ) ) game ( @@ -11943,7 +12027,7 @@ game ( game ( name "Minna de Puyo Puyo (Japan) (En,Ja)" description "Minna de Puyo Puyo (Japan) (En,Ja)" - rom ( name "Minna de Puyo Puyo (Japan) (En,Ja).gba" size 8388608 crc 857fb1ef sha1 927bd890ed5e1573022f9806f91e554a12de5429 ) + rom ( name "Minna de Puyo Puyo (Japan) (En,Ja).gba" size 8388608 crc 857fb1ef sha1 927bd890ed5e1573022f9806f91e554a12de5429 flags verified ) ) game ( @@ -12357,7 +12441,7 @@ game ( game ( name "Mother 1+2 (Japan)" description "Mother 1+2 (Japan)" - rom ( name "Mother 1+2 (Japan).gba" size 16777216 crc 0a44569c sha1 f27336b9c96ca2d06c34e07a61a78538deac32b3 ) + rom ( name "Mother 1+2 (Japan).gba" size 16777216 crc 0a44569c sha1 f27336b9c96ca2d06c34e07a61a78538deac32b3 flags verified ) ) game ( @@ -12795,7 +12879,7 @@ game ( game ( name "Need for Speed - Porsche Unleashed (Europe) (En,Fr,De,Es,It)" description "Need for Speed - Porsche Unleashed (Europe) (En,Fr,De,Es,It)" - rom ( name "Need for Speed - Porsche Unleashed (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 1b6b8f88 sha1 01191bf203a21cc7e91ebc8ffa4c167625ede952 ) + rom ( name "Need for Speed - Porsche Unleashed (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 1b6b8f88 sha1 01191bf203a21cc7e91ebc8ffa4c167625ede952 flags verified ) ) game ( @@ -14061,7 +14145,7 @@ game ( game ( name "Pokemon - Version Emeraude (France)" description "Pokemon - Version Emeraude (France)" - rom ( name "Pokemon - Version Emeraude (France).gba" size 16777216 crc a3fdccb1 sha1 ca666651374d89ca439007bed54d839eb7bd14d0 ) + rom ( name "Pokemon - Version Emeraude (France).gba" size 16777216 crc a3fdccb1 sha1 ca666651374d89ca439007bed54d839eb7bd14d0 flags verified ) ) game ( @@ -14418,6 +14502,12 @@ game ( rom ( name "Powerpuff Girls, The - Mojo Jojo A-Go-Go (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc 1172d0a7 sha1 a3d9260a80a6e38165587265405d892c3f87039b ) ) +game ( + name "Prehistorik Kart (USA, Europe) (Proto)" + description "Prehistorik Kart (USA, Europe) (Proto)" + rom ( name "Prehistorik Kart (USA, Europe) (Proto).gba" size 561236 crc 8496bc19 sha1 f274c1ddb46f0e07e47683cde2379febc570eb79 ) +) + game ( name "Prehistorik Man (USA, Europe) (En,Fr,De,Es,It,Nl)" description "Prehistorik Man (USA, Europe) (En,Fr,De,Es,It,Nl)" @@ -14436,18 +14526,18 @@ game ( rom ( name "Premier Action Soccer (Europe) (En,Fr,De,Es,It).gba" size 8388608 crc b5432fa4 sha1 3f066eb236e8fa22e8507ca9dc502be85ccafec3 ) ) -game ( - name "Premier Manager 2003-04 (Europe) (En,Fr,De,It)" - description "Premier Manager 2003-04 (Europe) (En,Fr,De,It)" - rom ( name "Premier Manager 2003-04 (Europe) (En,Fr,De,It).gba" size 4194304 crc a959f638 sha1 674f9259e3f24c81aecd14a19103e90606077fb9 flags verified ) -) - game ( name "Premier Manager 2003-04 (Europe) (En,Fr,De,It) (Rev 1)" description "Premier Manager 2003-04 (Europe) (En,Fr,De,It) (Rev 1)" rom ( name "Premier Manager 2003-04 (Europe) (En,Fr,De,It) (Rev 1).gba" size 4194304 crc ca893bc4 sha1 480d3accdbc7a3fd2923c5c743561b3170d8924c ) ) +game ( + name "Premier Manager 2003-04 (Europe) (En,Fr,De,It)" + description "Premier Manager 2003-04 (Europe) (En,Fr,De,It)" + rom ( name "Premier Manager 2003-04 (Europe) (En,Fr,De,It).gba" size 4194304 crc a959f638 sha1 674f9259e3f24c81aecd14a19103e90606077fb9 flags verified ) +) + game ( name "Premier Manager 2004-2005 (Europe) (En,Fr,De,It)" description "Premier Manager 2004-2005 (Europe) (En,Fr,De,It)" @@ -14563,9 +14653,9 @@ game ( ) game ( - name "Punch King - Arcade Boxing (World) (Evercade) (Unl)" - description "Punch King - Arcade Boxing (World) (Evercade) (Unl)" - rom ( name "Punch King - Arcade Boxing (World) (Evercade) (Unl).gba" size 8388608 crc 2afd6ff9 sha1 9e145758d3727c664c0612407f4b4d04f7b1733d ) + name "Punch King - Arcade Boxing (World) (Evercade)" + description "Punch King - Arcade Boxing (World) (Evercade)" + rom ( name "Punch King - Arcade Boxing (World) (Evercade).gba" size 8388608 crc 2afd6ff9 sha1 9e145758d3727c664c0612407f4b4d04f7b1733d ) ) game ( @@ -14671,9 +14761,9 @@ game ( ) game ( - name "Racing Fever (World) (En,De,Es,It) (Evercade) (Unl)" - description "Racing Fever (World) (En,De,Es,It) (Evercade) (Unl)" - rom ( name "Racing Fever (World) (En,De,Es,It) (Evercade) (Unl).gba" size 4194304 crc dbd09923 sha1 b10861c12a08826c58dffdbd3071ff72f753b76e ) + name "Racing Fever (World) (En,De,Es,It) (Evercade)" + description "Racing Fever (World) (En,De,Es,It) (Evercade)" + rom ( name "Racing Fever (World) (En,De,Es,It) (Evercade).gba" size 4194304 crc dbd09923 sha1 b10861c12a08826c58dffdbd3071ff72f753b76e ) ) game ( @@ -16377,7 +16467,7 @@ game ( game ( name "Sims 2, The - Pets (Europe) (En,Fr,De,Es,It,Nl)" description "Sims 2, The - Pets (Europe) (En,Fr,De,Es,It,Nl)" - rom ( name "Sims 2, The - Pets (Europe) (En,Fr,De,Es,It,Nl).gba" size 33554432 crc 87f8599c sha1 c708fb879bd42407f06441258ff5fe4cf733fb68 ) + rom ( name "Sims 2, The - Pets (Europe) (En,Fr,De,Es,It,Nl).gba" size 33554432 crc 87f8599c sha1 c708fb879bd42407f06441258ff5fe4cf733fb68 flags verified ) ) game ( @@ -16476,6 +16566,12 @@ game ( rom ( name "Smashing Drive (Europe) (En,Fr,De,Es,It).gba" size 8388608 crc 1034d672 sha1 ef2657c476294983430e8e8a6c119c847133c932 ) ) +game ( + name "SMSAdvance (World) (Pirate)" + description "SMSAdvance (World) (Pirate)" + rom ( name "SMSAdvance (World) (Pirate).gba" size 33554432 crc 3e1705e2 sha1 df39f500b3d33d120933c32a794ee97a75bd54d2 ) +) + game ( name "Smuggler's Run (USA)" description "Smuggler's Run (USA)" @@ -16554,6 +16650,12 @@ game ( rom ( name "Sonic Advance (USA) (En,Ja) (Beta) (2001-10-31).gba" size 8388608 crc a80de3b2 sha1 aae6516f72309920acd9fafd03a9d09e35cf755f ) ) +game ( + name "Sonic Advance (Japan) (En,Ja) (Rev 1)" + description "Sonic Advance (Japan) (En,Ja) (Rev 1)" + rom ( name "Sonic Advance (Japan) (En,Ja) (Rev 1).gba" size 8388608 crc 85957a24 sha1 f43faca5d8df354a63471aebfea3be125e797e51 ) +) + game ( name "Sonic Advance (Japan) (En,Ja)" description "Sonic Advance (Japan) (En,Ja)" @@ -16572,24 +16674,6 @@ game ( rom ( name "Sonic Advance (Europe) (En,Ja,Fr,De,Es).gba" size 8388608 crc 6232839b sha1 eb00f101af23d728075ac2117e27ecd8a4b4c3e9 flags verified ) ) -game ( - name "Sonic Advance (Japan) (En,Ja) (Rev 1)" - description "Sonic Advance (Japan) (En,Ja) (Rev 1)" - rom ( name "Sonic Advance (Japan) (En,Ja) (Rev 1).gba" size 8388608 crc 85957a24 sha1 f43faca5d8df354a63471aebfea3be125e797e51 ) -) - -game ( - name "Sonic Advance 2 (Japan) (En,Ja,Fr,De,Es,It) (Virtual Console)" - description "Sonic Advance 2 (Japan) (En,Ja,Fr,De,Es,It) (Virtual Console)" - rom ( name "Sonic Advance 2 (Japan) (En,Ja,Fr,De,Es,It) (Virtual Console).gba" size 16777216 crc 6c6c65a3 sha1 2aa6ee2cf2b0ebfafbea6d6d24165b252a7e329e ) -) - -game ( - 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 ( name "Sonic Advance 2 (Japan) (En,Ja,Fr,De,Es,It)" description "Sonic Advance 2 (Japan) (En,Ja,Fr,De,Es,It)" @@ -16609,9 +16693,27 @@ game ( ) game ( - name "Sonic Advance 3 (Europe) (En,Ja,Fr,De,Es,It) (Beta)" - description "Sonic Advance 3 (Europe) (En,Ja,Fr,De,Es,It) (Beta)" - rom ( name "Sonic Advance 3 (Europe) (En,Ja,Fr,De,Es,It) (Beta).gba" size 16777216 crc 4c93dac6 sha1 dcdd05854b47c52a74ff13f8d50cb1c7f612e376 ) + name "Sonic Advance 2 (Japan) (En,Ja,Fr,De,Es,It) (Virtual Console)" + description "Sonic Advance 2 (Japan) (En,Ja,Fr,De,Es,It) (Virtual Console)" + rom ( name "Sonic Advance 2 (Japan) (En,Ja,Fr,De,Es,It) (Virtual Console).gba" size 16777216 crc 6c6c65a3 sha1 2aa6ee2cf2b0ebfafbea6d6d24165b252a7e329e ) +) + +game ( + 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 ( + name "Sonic Advance 3 (Japan) (En,Ja,Fr,De,Es,It) (Virtual Console)" + description "Sonic Advance 3 (Japan) (En,Ja,Fr,De,Es,It) (Virtual Console)" + rom ( name "Sonic Advance 3 (Japan) (En,Ja,Fr,De,Es,It) (Virtual Console).gba" size 16777216 crc 1f36892f sha1 9fc8e926fc5472a1e34e3251c5d316526dc7a7a9 ) +) + +game ( + name "Sonic Advance 3 (Japan) (Demo) (Kiosk, GameCube)" + description "Sonic Advance 3 (Japan) (Demo) (Kiosk, GameCube)" + rom ( name "Sonic Advance 3 (Japan) (Demo) (Kiosk, GameCube).gba" size 16777216 crc 0594d496 sha1 9c3f18112c126ca08403408a844a3a83967eb4dc flags verified ) ) game ( @@ -16633,15 +16735,9 @@ game ( ) game ( - name "Sonic Advance 3 (Japan) (En,Ja,Fr,De,Es,It) (Virtual Console)" - description "Sonic Advance 3 (Japan) (En,Ja,Fr,De,Es,It) (Virtual Console)" - rom ( name "Sonic Advance 3 (Japan) (En,Ja,Fr,De,Es,It) (Virtual Console).gba" size 16777216 crc 1f36892f sha1 9fc8e926fc5472a1e34e3251c5d316526dc7a7a9 ) -) - -game ( - name "Sonic Advance 3 (Japan) (Demo) (Kiosk, GameCube)" - description "Sonic Advance 3 (Japan) (Demo) (Kiosk, GameCube)" - rom ( name "Sonic Advance 3 (Japan) (Demo) (Kiosk, GameCube).gba" size 16777216 crc 0594d496 sha1 9c3f18112c126ca08403408a844a3a83967eb4dc flags verified ) + name "Sonic Advance 3 (Europe) (En,Ja,Fr,De,Es,It) (Beta)" + description "Sonic Advance 3 (Europe) (En,Ja,Fr,De,Es,It) (Beta)" + rom ( name "Sonic Advance 3 (Europe) (En,Ja,Fr,De,Es,It) (Beta).gba" size 16777216 crc 4c93dac6 sha1 dcdd05854b47c52a74ff13f8d50cb1c7f612e376 ) ) game ( @@ -16848,18 +16944,18 @@ game ( rom ( name "Spider-Man 3 (USA) (Unl).gba" size 33554432 crc 3e1b693a sha1 54d22c0fe81e78380fc2d839aa800f36fc1ef311 ) ) -game ( - name "Spirit - Der Wilde Mustang - Auf der Suche nach Homeland (Germany)" - description "Spirit - Der Wilde Mustang - Auf der Suche nach Homeland (Germany)" - rom ( name "Spirit - Der Wilde Mustang - Auf der Suche nach Homeland (Germany).gba" size 4194304 crc 4785eee0 sha1 ce8f271e143456616a9649fee7d47ef1602ee497 ) -) - game ( name "Spirit - Der Wilde Mustang - Auf der Suche nach Homeland (Germany) (Beta)" description "Spirit - Der Wilde Mustang - Auf der Suche nach Homeland (Germany) (Beta)" rom ( name "Spirit - Der Wilde Mustang - Auf der Suche nach Homeland (Germany) (Beta).gba" size 4183576 crc caaaefb6 sha1 53c4bb950d9b92918f26aeb7b0105604d0263ba1 ) ) +game ( + name "Spirit - Der Wilde Mustang - Auf der Suche nach Homeland (Germany)" + description "Spirit - Der Wilde Mustang - Auf der Suche nach Homeland (Germany)" + rom ( name "Spirit - Der Wilde Mustang - Auf der Suche nach Homeland (Germany).gba" size 4194304 crc 4785eee0 sha1 ce8f271e143456616a9649fee7d47ef1602ee497 ) +) + game ( name "Spirit - L'Etalon des Plaines - A la Recherche de la Terre Natale (France)" description "Spirit - L'Etalon des Plaines - A la Recherche de la Terre Natale (France)" @@ -17094,10 +17190,16 @@ game ( rom ( name "SpongeBob SquarePants Movie, The (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 5d9d52de sha1 08161f0f5ca8884a94e73a526afd5a35a5da93de flags verified ) ) +game ( + name "SpongeBob's Atlantis SquarePantis (USA) (Beta)" + description "SpongeBob's Atlantis SquarePantis (USA) (Beta)" + rom ( name "SpongeBob's Atlantis SquarePantis (USA) (Beta).gba" size 4194304 crc a39248a2 sha1 0b634ec9a79f027fb780f3cbda7e48fbc917f032 ) +) + game ( name "SpongeBob's Atlantis SquarePantis (USA)" description "SpongeBob's Atlantis SquarePantis (USA)" - rom ( name "SpongeBob's Atlantis SquarePantis (USA).gba" size 4194304 crc 190b1653 sha1 24596f49fb46cf5e3624450f599df670acc72967 ) + rom ( name "SpongeBob's Atlantis SquarePantis (USA).gba" size 4194304 crc 190b1653 sha1 24596f49fb46cf5e3624450f599df670acc72967 flags verified ) ) game ( @@ -17118,18 +17220,18 @@ game ( rom ( name "Sportsmans Pack 2 in 1 - Cabela's Big Game Hunter + Rapala Pro Fishing (USA).gba" size 8388608 crc 2e725460 sha1 90f0d361aa0282968e2e1320f9e6200cc3d843d3 ) ) -game ( - name "Spy Hunter (Europe) (En,Ja,Fr,De,Es)" - description "Spy Hunter (Europe) (En,Ja,Fr,De,Es)" - rom ( name "Spy Hunter (Europe) (En,Ja,Fr,De,Es).gba" size 8388608 crc 4f196612 sha1 40afc8143490745d8724ee1436acf0601f178d3f ) -) - game ( name "Spy Hunter (USA) (En,Ja,Fr,De,Es)" description "Spy Hunter (USA) (En,Ja,Fr,De,Es)" rom ( name "Spy Hunter (USA) (En,Ja,Fr,De,Es).gba" size 8388608 crc b04892c9 sha1 0cd7527b1ac7e23f14c1eea8b7c58d800d859b38 ) ) +game ( + name "Spy Hunter (Europe) (En,Ja,Fr,De,Es)" + description "Spy Hunter (Europe) (En,Ja,Fr,De,Es)" + rom ( name "Spy Hunter (Europe) (En,Ja,Fr,De,Es).gba" size 8388608 crc 4f196612 sha1 40afc8143490745d8724ee1436acf0601f178d3f ) +) + game ( name "Spy Kids 3-D - Game Over (USA)" description "Spy Kids 3-D - Game Over (USA)" @@ -17563,9 +17665,9 @@ game ( ) game ( - name "Super Bubble Pop (World) (Evercade) (Unl)" - description "Super Bubble Pop (World) (Evercade) (Unl)" - rom ( name "Super Bubble Pop (World) (Evercade) (Unl).gba" size 4194304 crc 940f385e sha1 fa2c3b84014a84143f519609a8808d13ca33bf22 ) + name "Super Bubble Pop (World) (Evercade)" + description "Super Bubble Pop (World) (Evercade)" + rom ( name "Super Bubble Pop (World) (Evercade).gba" size 4194304 crc 940f385e sha1 fa2c3b84014a84143f519609a8808d13ca33bf22 ) ) game ( @@ -17715,7 +17817,7 @@ game ( game ( name "Super Mario Advance 2 - Super Mario World + Mario Brothers (Japan)" description "Super Mario Advance 2 - Super Mario World + Mario Brothers (Japan)" - rom ( name "Super Mario Advance 2 - Super Mario World + Mario Brothers (Japan).gba" size 4194304 crc 901ce373 sha1 8f3d3c33c77872db9818620f5e581ec0fa342d72 ) + rom ( name "Super Mario Advance 2 - Super Mario World + Mario Brothers (Japan).gba" size 4194304 crc 901ce373 sha1 8f3d3c33c77872db9818620f5e581ec0fa342d72 flags verified ) ) game ( @@ -18087,7 +18189,7 @@ game ( game ( name "Sword of Mana (Europe) (Fr,De)" description "Sword of Mana (Europe) (Fr,De)" - rom ( name "Sword of Mana (Europe) (Fr,De).gba" size 16777216 crc 5ee17ed1 sha1 7f7055642417d4526f63f49c21cd40a9df24a11d ) + rom ( name "Sword of Mana (Europe) (Fr,De).gba" size 16777216 crc 5ee17ed1 sha1 7f7055642417d4526f63f49c21cd40a9df24a11d flags verified ) ) game ( @@ -19326,6 +19428,78 @@ game ( rom ( name "Ultraman - Confrontation Between Justice and Evil (Russia) (Unl).gba" size 33554432 crc 4683dbb5 sha1 08a9530ce82a76e1be5e408ae99bdd90c42748e9 ) ) +game ( + name "Unfabulous (USA) (Beta) (2006-01-10)" + description "Unfabulous (USA) (Beta) (2006-01-10)" + rom ( name "Unfabulous (USA) (Beta) (2006-01-10).gba" size 1077252 crc cbbe4a57 sha1 f84d89631eca069e2e88b9cc03ef17d6cb1ee544 ) +) + +game ( + name "Unfabulous (USA) (Beta) (2006-03-03)" + description "Unfabulous (USA) (Beta) (2006-03-03)" + rom ( name "Unfabulous (USA) (Beta) (2006-03-03).gba" size 1419168 crc bc86eec8 sha1 cbe7503c62f14b1085412b537955119e99f45f8e ) +) + +game ( + name "Unfabulous (USA) (Beta) (2006-03-24T1132)" + description "Unfabulous (USA) (Beta) (2006-03-24T1132)" + rom ( name "Unfabulous (USA) (Beta) (2006-03-24T1132).gba" size 1551692 crc 0845be78 sha1 c85148eb7bc1f133b097e3e4d76ce602bdcef7a8 ) +) + +game ( + name "Unfabulous (USA) (Beta) (2006-03-24T1206)" + description "Unfabulous (USA) (Beta) (2006-03-24T1206)" + rom ( name "Unfabulous (USA) (Beta) (2006-03-24T1206).gba" size 1551740 crc 662f11d0 sha1 6886093df944a323b52827d98d0b0a97e841122d ) +) + +game ( + name "Unfabulous (USA) (Beta) (2006-04-21)" + description "Unfabulous (USA) (Beta) (2006-04-21)" + rom ( name "Unfabulous (USA) (Beta) (2006-04-21).gba" size 2708408 crc 057e4bbc sha1 7937806abdf3fdb25f5dee6994705a50b2d716fa ) +) + +game ( + name "Unfabulous (USA) (Beta) (2006-05-12)" + description "Unfabulous (USA) (Beta) (2006-05-12)" + rom ( name "Unfabulous (USA) (Beta) (2006-05-12).gba" size 3406884 crc f52e65e2 sha1 80566d8edbbd067a4d327c78dcd68dd82d5d4ea6 ) +) + +game ( + name "Unfabulous (USA) (Beta) (2006-06-09)" + description "Unfabulous (USA) (Beta) (2006-06-09)" + rom ( name "Unfabulous (USA) (Beta) (2006-06-09).gba" size 4809124 crc d4ea1af8 sha1 cf48cd5bf82bc963ae4f0601ecf05bec0b92912e ) +) + +game ( + name "Unfabulous (USA) (Beta) (2006-07-13)" + description "Unfabulous (USA) (Beta) (2006-07-13)" + rom ( name "Unfabulous (USA) (Beta) (2006-07-13).gba" size 4247508 crc 76589323 sha1 642696c8421ec05abba99a248b9b3da6f3b0dbe0 ) +) + +game ( + name "Unfabulous (USA) (Beta) (2006-07-14)" + description "Unfabulous (USA) (Beta) (2006-07-14)" + rom ( name "Unfabulous (USA) (Beta) (2006-07-14).gba" size 4204052 crc e0c3e150 sha1 22fea1033236401e3c5d37d99fed34e4cade94d3 ) +) + +game ( + name "Unfabulous (USA) (Beta) (2006-07-15)" + description "Unfabulous (USA) (Beta) (2006-07-15)" + rom ( name "Unfabulous (USA) (Beta) (2006-07-15).gba" size 4154388 crc 1c85d06e sha1 2765e775e8b599191e3a1d9484fb6c5c51b31c70 ) +) + +game ( + name "Unfabulous (USA) (Beta) (2006-07-17)" + description "Unfabulous (USA) (Beta) (2006-07-17)" + rom ( name "Unfabulous (USA) (Beta) (2006-07-17).gba" size 4171048 crc 77a0663d sha1 08058775a176f07d2074146005ef4cdeff2cf2b3 ) +) + +game ( + name "Unfabulous (USA) (Beta) (2006-07-20)" + description "Unfabulous (USA) (Beta) (2006-07-20)" + rom ( name "Unfabulous (USA) (Beta) (2006-07-20).gba" size 4402956 crc d3e36338 sha1 fe149ab00165aad7f343436969c9af337974c5d8 ) +) + game ( name "Unfabulous (USA)" description "Unfabulous (USA)" @@ -19951,9 +20125,9 @@ game ( ) game ( - name "Worms Blast (World) (En,Fr,De,Es,It) (Evercade) (Unl)" - description "Worms Blast (World) (En,Fr,De,Es,It) (Evercade) (Unl)" - rom ( name "Worms Blast (World) (En,Fr,De,Es,It) (Evercade) (Unl).gba" size 4194304 crc 55c25c4a sha1 da4e430b5fef43bdb8a4c52a8fdec5ae98243ddb ) + name "Worms Blast (World) (En,Fr,De,Es,It) (Evercade)" + description "Worms Blast (World) (En,Fr,De,Es,It) (Evercade)" + rom ( name "Worms Blast (World) (En,Fr,De,Es,It) (Evercade).gba" size 4194304 crc 55c25c4a sha1 da4e430b5fef43bdb8a4c52a8fdec5ae98243ddb ) ) game ( @@ -20187,7 +20361,7 @@ game ( game ( name "Yu-Gi-Oh! - Destiny Board Traveler (USA)" description "Yu-Gi-Oh! - Destiny Board Traveler (USA)" - rom ( name "Yu-Gi-Oh! - Destiny Board Traveler (USA).gba" size 8388608 crc 611e7bbd sha1 df001a60b7ab52e9735906fcd80f6c8b834ed684 ) + rom ( name "Yu-Gi-Oh! - Destiny Board Traveler (USA).gba" size 8388608 crc 611e7bbd sha1 df001a60b7ab52e9735906fcd80f6c8b834ed684 flags verified ) ) game ( @@ -20205,7 +20379,7 @@ game ( game ( name "Yu-Gi-Oh! - Dungeon Dice Monsters (Japan)" description "Yu-Gi-Oh! - Dungeon Dice Monsters (Japan)" - rom ( name "Yu-Gi-Oh! - Dungeon Dice Monsters (Japan).gba" size 8388608 crc 51b35e87 sha1 a0ad0cbff3d74bb3e234abcff866994ea602c43a ) + rom ( name "Yu-Gi-Oh! - Dungeon Dice Monsters (Japan).gba" size 8388608 crc 51b35e87 sha1 a0ad0cbff3d74bb3e234abcff866994ea602c43a flags verified ) ) game ( @@ -20307,7 +20481,7 @@ game ( game ( name "Yu-Gi-Oh! Duel Monsters 5 Expert 1 (Japan)" description "Yu-Gi-Oh! Duel Monsters 5 Expert 1 (Japan)" - rom ( name "Yu-Gi-Oh! Duel Monsters 5 Expert 1 (Japan).gba" size 8388608 crc f3041e7e sha1 dc25f733cee913afdf187ec03df30909fe28b03c ) + rom ( name "Yu-Gi-Oh! Duel Monsters 5 Expert 1 (Japan).gba" size 8388608 crc f3041e7e sha1 dc25f733cee913afdf187ec03df30909fe28b03c flags verified ) ) game ( @@ -21191,8 +21365,8 @@ game ( clrmamepro ( name "Nintendo - Game Boy Advance (Video)" description "Nintendo - Game Boy Advance (Video)" - version 20240727-194101 - author "BigFred, C. V. Reynolds, DeadSkullzJr, Hiccup, kazumi213, omonim2007, Psychofox11, psykopat, relax, SonGoku, xuom2" + version 20241213-211743 + author "BigFred, C. V. Reynolds, DeadSkullzJr, Gefflon, Hiccup, kazumi213, omonim2007, Psychofox11, psykopat, relax, SonGoku, xuom2" homepage No-Intro url "https://www.no-intro.org" forcenodump required @@ -21223,7 +21397,7 @@ game ( 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 ) + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Edition Speciale (France).gba" size 33554432 crc 71154d42 sha1 73cbdd82640f166737173b0e8197d771d7906a91 flags verified ) ) game ( @@ -21469,8 +21643,8 @@ game ( clrmamepro ( name "Nintendo - Game Boy Advance (Aftermarket)" description "Nintendo - Game Boy Advance (Aftermarket)" - version 20240807-214821 - author "aci68, Arctic Circle System, Aringon, Bent, BigFred, bikerspade, C. V. Reynolds, chillerecke, DeadSkullzJr, Densetsu, DeriLoko3, einstein95, ElBarto, Enker, FakeShemp, Flashfire42, fuzzball, Gefflon, Hiccup, hking0036, hydr0x, InternalLoss, Jack, jimmsu, Just001Kim, kazumi213, Larsenv, Lesserkuma, Madeline, MeguCocoa, Money_114, NESBrew12, niemand, nnssxx, norkmetnoil577, NovaAurora, omonim2007, Powerpuff, PPLToast, Prominos, Psychofox11, psykopat, rarenight, relax, RetroGamer, Rifu, sCZther, SonGoku, Tauwasser, Tescu, togemet2, ufocrossing, Vallaine01, Whovian9369, xprism, xuom2, zg" + version 20250329-151034 + author "aci68, Arctic Circle System, Aringon, Bent, BigFred, bikerspade, buckwheat, C. V. Reynolds, chillerecke, DeadSkullzJr, Densetsu, DeriLoko3, einstein95, ElBarto, Enker, FakeShemp, Flashfire42, fuzzball, Gefflon, Hiccup, hking0036, hydr0x, InternalLoss, Jack, jimmsu, Just001Kim, kazumi213, Larsenv, Lesserkuma, Madeline, MeguCocoa, Money_114, NESBrew12, niemand, nnssxx, norkmetnoil577, NovaAurora, number, omonim2007, Powerpuff, PPLToast, Prominos, Psychofox11, psykopat, 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 @@ -21576,6 +21750,12 @@ game ( rom ( name "Apotris (World) (v3.4.5) (Aftermarket) (Unl).gba" size 4194304 crc 55ae4312 sha1 fb7142bcc30f71f187cc51b7fcbc5a3958374c6c ) ) +game ( + name "Apotris (World) (v4.0.2) (Aftermarket) (Unl)" + description "Apotris (World) (v4.0.2) (Aftermarket) (Unl)" + rom ( name "Apotris (World) (v4.0.2) (Aftermarket) (Unl).gba" size 4194304 crc 107f69e0 sha1 7b4be37971335759ecc828b290338ab35e047017 ) +) + game ( name "Armadillo Run (World) (Demo) (Aftermarket) (Unl)" description "Armadillo Run (World) (Demo) (Aftermarket) (Unl)" @@ -22206,6 +22386,18 @@ game ( rom ( name "uCity Advance (World) (v1.0.1) (GBA Jam 2021) (Aftermarket) (Unl).gba" size 1925124 crc a6e47443 sha1 c3e8f7fe01e05eda8bdb52a35db0d6dc92554e42 ) ) +game ( + name "uCity Advance (World) (v1.0.2) (Aftermarket) (Unl)" + description "uCity Advance (World) (v1.0.2) (Aftermarket) (Unl)" + rom ( name "uCity Advance (World) (v1.0.2) (Aftermarket) (Unl).gba" size 1930492 crc 534a0f4d sha1 262ae7904b78b57ad1bd514b2a46ca5854f70e0c ) +) + +game ( + name "uCity Advance (World) (Aftermarket) (Unl)" + description "uCity Advance (World) (Aftermarket) (Unl)" + rom ( name "uCity Advance (World) (Aftermarket) (Unl).gba" size 1924948 crc 9cc88417 sha1 706fe5408d9c3fde6dfa47c7a483753ffc52005d ) +) + game ( name "Varooom 3D (World) (GBA Jam 2021) (Aftermarket) (Unl)" description "Varooom 3D (World) (GBA Jam 2021) (Aftermarket) (Unl)" @@ -22251,8 +22443,8 @@ game ( clrmamepro ( name "Nintendo - Game Boy Advance (Video) (Aftermarket)" description "Nintendo - Game Boy Advance (Video) (Aftermarket)" - version 20240727-194101 - author "BigFred, C. V. Reynolds, DeadSkullzJr, Hiccup, kazumi213, omonim2007, Psychofox11, psykopat, relax, SonGoku, xuom2" + version 20241213-211743 + author "BigFred, C. V. Reynolds, DeadSkullzJr, Gefflon, Hiccup, kazumi213, omonim2007, Psychofox11, psykopat, relax, SonGoku, xuom2" homepage No-Intro url "https://www.no-intro.org" forcenodump required @@ -22649,8 +22841,8 @@ game ( clrmamepro ( name "Nintendo - Game Boy" description "Nintendo - Game Boy" - version 20240809-004429 - 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, Jack, jimmsu, Just001Kim, kazumi213, leekindo, Lesserkuma, Madeline, NESBrew12, NGEfreak, nnssxx, norkmetnoil577, NovaAurora, omonim2007, Powerpuff, PPLToast, Psychofox11, psykopat, rarenight, relax, RetroUprising, rpg2813, sCZther, SonGoku, Tauwasser, togemet2, UnlockerPT, xNo, xprism, xuom2" + version 20250328-062132 + 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, Jack, jimmsu, Just001Kim, kazumi213, leekindo, Lesserkuma, Madeline, NESBrew12, NGEfreak, nnssxx, norkmetnoil577, NovaAurora, number, omonim2007, Powerpuff, PPLToast, Psychofox11, psykopat, rarenight, relax, RetroUprising, rpg2813, sCZther, SonGoku, Tauwasser, togemet2, UnlockerPT, xNo, xprism, xuom2" homepage No-Intro url "https://www.no-intro.org" forcenodump required @@ -22969,7 +23161,7 @@ game ( game ( name "Alien Olympics (Europe)" description "Alien Olympics (Europe)" - rom ( name "Alien Olympics (Europe).gb" size 131072 crc 583c0e4e sha1 c6a69416d3f18071942d222d528b1c9d25f980b7 ) + rom ( name "Alien Olympics (Europe).gb" size 131072 crc 583c0e4e sha1 c6a69416d3f18071942d222d528b1c9d25f980b7 flags verified ) ) game ( @@ -23923,7 +24115,7 @@ game ( game ( name "Brain Drain (Europe) (SGB Enhanced)" description "Brain Drain (Europe) (SGB Enhanced)" - rom ( name "Brain Drain (Europe) (SGB Enhanced).gb" size 131072 crc 8a7fb0e6 sha1 67e1796ed410ecc19efc2f73ef7f5226414cc8bb ) + rom ( name "Brain Drain (Europe) (SGB Enhanced).gb" size 131072 crc 8a7fb0e6 sha1 67e1796ed410ecc19efc2f73ef7f5226414cc8bb flags verified ) ) game ( @@ -23950,6 +24142,12 @@ game ( rom ( name "BreakThru! (USA).gb" size 131072 crc 5b8f0df2 sha1 d9a49a71e6b554c2fccce08c446274e886225d50 ) ) +game ( + name "Bubble Bobble (USA, Europe) (Beta) (1990-12-28)" + description "Bubble Bobble (USA, Europe) (Beta) (1990-12-28)" + rom ( name "Bubble Bobble (USA, Europe) (Beta) (1990-12-28).gb" size 131072 crc fdb7e8da sha1 8ed992b8053303453b4afd078684849015667db2 ) +) + game ( name "Bubble Bobble (Japan)" description "Bubble Bobble (Japan)" @@ -24676,6 +24874,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)" @@ -24688,12 +24892,6 @@ game ( rom ( name "Darkwing Duck (Spain).gb" size 131072 crc a1d4c544 sha1 3d4b301aab995bdf19b15ed5040df7426a2e8057 ) ) -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 "David Crane's The Rescue of Princess Blobette (World) (Limited Run Games)" description "David Crane's The Rescue of Princess Blobette (World) (Limited Run Games)" @@ -24712,6 +24910,12 @@ game ( rom ( name "David Crane's The Rescue of Princess Blobette (USA).gb" size 65536 crc 8210a03f sha1 0a45d1b98646fd7832b5119b04bc8d6d6d0f657a ) ) +game ( + name "David Crane's The Rescue of Princess Blobette (USA) (Beta) (1990-09-26)" + description "David Crane's The Rescue of Princess Blobette (USA) (Beta) (1990-09-26)" + rom ( name "David Crane's The Rescue of Princess Blobette (USA) (Beta) (1990-09-26).gb" size 65536 crc da5411d5 sha1 ef26375863e3bd5a765fdca1861ab700835cbe4a ) +) + game ( name "Days of Thunder (USA, Europe)" description "Days of Thunder (USA, Europe)" @@ -24772,6 +24976,12 @@ game ( rom ( name "Dexterity (USA, Europe).gb" size 65536 crc 659e2283 sha1 85f0a9ff87ece93097a855d238bc6c7014893c08 flags verified ) ) +game ( + name "Dexterity (USA, Europe) (Beta) (1990-03-30)" + description "Dexterity (USA, Europe) (Beta) (1990-03-30)" + rom ( name "Dexterity (USA, Europe) (Beta) (1990-03-30).gb" size 65536 crc c9e34ca0 sha1 4f0ded0dcd67c2ae8f44482ffc1d469618451e7a ) +) + game ( name "Diablo (USA) (Proto)" description "Diablo (USA) (Proto)" @@ -25465,7 +25675,7 @@ game ( game ( name "FIFA International Soccer (USA, Europe) (En,Fr,De,Es) (SGB Enhanced)" description "FIFA International Soccer (USA, Europe) (En,Fr,De,Es) (SGB Enhanced)" - rom ( name "FIFA International Soccer (USA, Europe) (En,Fr,De,Es) (SGB Enhanced).gb" size 524288 crc e5989908 sha1 6f0affdec339d7beb9c565dc51abe59ee0398b7b ) + rom ( name "FIFA International Soccer (USA, Europe) (En,Fr,De,Es) (SGB Enhanced).gb" size 524288 crc e5989908 sha1 6f0affdec339d7beb9c565dc51abe59ee0398b7b flags verified ) ) game ( @@ -25679,9 +25889,9 @@ game ( ) game ( - name "Funny Field (Japan)" - description "Funny Field (Japan)" - rom ( name "Funny Field (Japan).gb" size 65536 crc bfd87aa4 sha1 0b74e84ce50454057f78e4178be2599e57c91855 ) + name "Funny Field (Japan) (En)" + description "Funny Field (Japan) (En)" + rom ( name "Funny Field (Japan) (En).gb" size 65536 crc bfd87aa4 sha1 0b74e84ce50454057f78e4178be2599e57c91855 ) ) game ( @@ -26548,6 +26758,12 @@ game ( rom ( name "Hit the Ice - VHL - The Official Video Hockey League (USA, Europe).gb" size 131072 crc 2c77f399 sha1 f1631e0a97fd60a285feba1b2fc9082bca3be829 ) ) +game ( + name "Hit the Ice - VHL - The Official Video Hockey League (USA, Europe) (Beta)" + description "Hit the Ice - VHL - The Official Video Hockey League (USA, Europe) (Beta)" + rom ( name "Hit the Ice - VHL - The Official Video Hockey League (USA, Europe) (Beta).gb" size 131072 crc 37627030 sha1 c75a236ca74d60c1239f9e9b5beef929cfb0360e ) +) + game ( name "Hitori de Dekirumon! - Cooking Densetsu (Japan)" description "Hitori de Dekirumon! - Cooking Densetsu (Japan)" @@ -26617,7 +26833,7 @@ game ( game ( name "Hook (Europe)" description "Hook (Europe)" - rom ( name "Hook (Europe).gb" size 131072 crc 370a2c2f sha1 0e0784c238b8a16666157e1ac99cee17feb52c63 ) + rom ( name "Hook (Europe).gb" size 131072 crc 370a2c2f sha1 0e0784c238b8a16666157e1ac99cee17feb52c63 flags verified ) ) game ( @@ -26845,7 +27061,7 @@ game ( game ( name "International Superstar Soccer (USA, Europe) (SGB Enhanced)" description "International Superstar Soccer (USA, Europe) (SGB Enhanced)" - rom ( name "International Superstar Soccer (USA, Europe) (SGB Enhanced).gb" size 262144 crc 94757be8 sha1 13f2fc0945fb7a90f4d87d8c4e310dec9af6b792 ) + rom ( name "International Superstar Soccer (USA, Europe) (SGB Enhanced).gb" size 262144 crc 94757be8 sha1 13f2fc0945fb7a90f4d87d8c4e310dec9af6b792 flags verified ) ) game ( @@ -27784,18 +28000,6 @@ game ( rom ( name "Legend of Zelda, The - Link's Awakening DX (Germany) (Proto) (1998-11-16).gb" size 524288 crc f7c59f5c sha1 d1a10a3f129bf060f16d1fb5f991ef6e2f28b97c ) ) -game ( - name "Lemmings (Europe) (Rev 1)" - description "Lemmings (Europe) (Rev 1)" - rom ( name "Lemmings (Europe) (Rev 1).gb" size 131072 crc 560d71eb sha1 c156d7ee57860a23754e87d42f952b17077994ad ) -) - -game ( - name "Lemmings (Europe) (Beta) (1993-05-19)" - description "Lemmings (Europe) (Beta) (1993-05-19)" - rom ( name "Lemmings (Europe) (Beta) (1993-05-19).gb" size 131072 crc e2a65174 sha1 4f23d9dcc315f5dbd5ef2350f6623b6cd077b393 ) -) - game ( name "Lemmings (Europe)" description "Lemmings (Europe)" @@ -27814,24 +28018,36 @@ game ( rom ( name "Lemmings (USA).gb" size 131072 crc f2d1c19d sha1 1754fde0b1c40752a5716a291e842e38401baf08 ) ) +game ( + name "Lemmings (Europe) (Rev 1)" + description "Lemmings (Europe) (Rev 1)" + rom ( name "Lemmings (Europe) (Rev 1).gb" size 131072 crc 560d71eb sha1 c156d7ee57860a23754e87d42f952b17077994ad ) +) + +game ( + name "Lemmings (Europe) (Beta) (1993-05-19)" + description "Lemmings (Europe) (Beta) (1993-05-19)" + rom ( name "Lemmings (Europe) (Beta) (1993-05-19).gb" size 131072 crc e2a65174 sha1 4f23d9dcc315f5dbd5ef2350f6623b6cd077b393 ) +) + game ( name "Lemmings 2 - The Tribes (Europe)" description "Lemmings 2 - The Tribes (Europe)" rom ( name "Lemmings 2 - The Tribes (Europe).gb" size 524288 crc 9800bd49 sha1 76ccf9ac82faebc7f9c4a4c8b5cd47ddea46c486 flags verified ) ) -game ( - name "Lethal Weapon (USA, Europe) (Beta)" - description "Lethal Weapon (USA, Europe) (Beta)" - rom ( name "Lethal Weapon (USA, Europe) (Beta).gb" size 131072 crc d585ab23 sha1 2221ba322acffc9d7ae5400752e16d0455fda3ef ) -) - game ( name "Lethal Weapon (USA, Europe)" description "Lethal Weapon (USA, Europe)" rom ( name "Lethal Weapon (USA, Europe).gb" size 131072 crc 1f8d207c sha1 8b4621471b376a6262fbed70c1191416ab3be915 ) ) +game ( + name "Lethal Weapon (USA, Europe) (Beta)" + description "Lethal Weapon (USA, Europe) (Beta)" + rom ( name "Lethal Weapon (USA, Europe) (Beta).gb" size 131072 crc d585ab23 sha1 2221ba322acffc9d7ae5400752e16d0455fda3ef ) +) + game ( name "Lingo (Europe) (En,Fr,De,Nl)" description "Lingo (Europe) (En,Fr,De,Nl)" @@ -27856,6 +28072,30 @@ game ( rom ( name "Lion King, The (World) (Disney Classic Games).gb" size 524288 crc e435ed72 sha1 170c071db25da7a2b39dd1fb2675aceb8eeb87a1 flags verified ) ) +game ( + name "Lion King, The (USA, Europe) (Beta) (1994-03-07)" + description "Lion King, The (USA, Europe) (Beta) (1994-03-07)" + rom ( name "Lion King, The (USA, Europe) (Beta) (1994-03-07).gb" size 131072 crc 4267aac1 sha1 2a5c3bc3c6e8c853d20c1960948441de289740a2 ) +) + +game ( + name "Lion King, The (USA, Europe) (Beta) (1994-07-13)" + description "Lion King, The (USA, Europe) (Beta) (1994-07-13)" + rom ( name "Lion King, The (USA, Europe) (Beta) (1994-07-13).gb" size 262144 crc ba4993be sha1 a727db6463e9297e062bf81b30cf24f14d3a190f ) +) + +game ( + name "Lion King, The (USA, Europe) (Beta) (1994-10-07)" + description "Lion King, The (USA, Europe) (Beta) (1994-10-07)" + rom ( name "Lion King, The (USA, Europe) (Beta) (1994-10-07).gb" size 524288 crc 03bd0d82 sha1 671dcc37642e9832803dcf866ab1616e8816b673 ) +) + +game ( + name "Lion King, The (USA, Europe) (Beta) (1994-10-12)" + description "Lion King, The (USA, Europe) (Beta) (1994-10-12)" + rom ( name "Lion King, The (USA, Europe) (Beta) (1994-10-12).gb" size 524288 crc eeb5f705 sha1 5f58001042ec929deb6024c6dd97024811f51acd ) +) + game ( name "Litti's Summer Sports (Germany)" description "Litti's Summer Sports (Germany)" @@ -28231,13 +28471,13 @@ game ( game ( name "Medarot - Parts Collection (Japan) (SGB Enhanced)" description "Medarot - Parts Collection (Japan) (SGB Enhanced)" - rom ( name "Medarot - Parts Collection (Japan) (SGB Enhanced).gb" size 524288 crc f4cab596 sha1 eec1245abb1d97cd2df976fdf179c924a4efa720 ) + rom ( name "Medarot - Parts Collection (Japan) (SGB Enhanced).gb" size 524288 crc f4cab596 sha1 eec1245abb1d97cd2df976fdf179c924a4efa720 flags verified ) ) game ( name "Medarot - Parts Collection 2 (Japan) (SGB Enhanced)" description "Medarot - Parts Collection 2 (Japan) (SGB Enhanced)" - rom ( name "Medarot - Parts Collection 2 (Japan) (SGB Enhanced).gb" size 524288 crc 89f94482 sha1 edd74afbfca5e3d3366fb231c4bedd80fcf8a81e ) + rom ( name "Medarot - Parts Collection 2 (Japan) (SGB Enhanced).gb" size 524288 crc 89f94482 sha1 edd74afbfca5e3d3366fb231c4bedd80fcf8a81e flags verified ) ) game ( @@ -29056,6 +29296,12 @@ game ( rom ( name "MVP Baseball (Japan).gb" size 262144 crc 38c126aa sha1 f0d921d13689d2afe7838eee127bf670439c8d8d ) ) +game ( + name "Mysterium (USA) (v1.00AI) (Beta) (1991-01-23)" + description "Mysterium (USA) (v1.00AI) (Beta) (1991-01-23)" + rom ( name "Mysterium (USA) (v1.00AI) (Beta) (1991-01-23).gb" size 131072 crc 57b3e6c3 sha1 c67bb1e061d638bca7dc1c71b808277e6fdf4fa6 ) +) + game ( name "Mysterium (Japan)" description "Mysterium (Japan)" @@ -29069,9 +29315,9 @@ game ( ) game ( - name "Mysterium (USA) (Beta)" - description "Mysterium (USA) (Beta)" - rom ( name "Mysterium (USA) (Beta).gb" size 131072 crc 004c1af7 sha1 82e058a7608a04a2837d0d8fe810270f804b43f3 ) + name "Mysterium (USA) (v1.00AU) (Beta)" + description "Mysterium (USA) (v1.00AU) (Beta)" + rom ( name "Mysterium (USA) (v1.00AU) (Beta).gb" size 131072 crc 004c1af7 sha1 82e058a7608a04a2837d0d8fe810270f804b43f3 ) ) game ( @@ -29443,7 +29689,7 @@ game ( game ( name "NHL Hockey 95 (USA, Europe) (SGB Enhanced)" description "NHL Hockey 95 (USA, Europe) (SGB Enhanced)" - rom ( name "NHL Hockey 95 (USA, Europe) (SGB Enhanced).gb" size 524288 crc bcabd2d2 sha1 baea6987f69b4c8792ff4e453ea88006803a3ef9 ) + rom ( name "NHL Hockey 95 (USA, Europe) (SGB Enhanced).gb" size 524288 crc bcabd2d2 sha1 baea6987f69b4c8792ff4e453ea88006803a3ef9 flags verified ) ) game ( @@ -31211,15 +31457,15 @@ game ( ) game ( - name "RoboCop versus The Terminator (Europe)" - description "RoboCop versus The Terminator (Europe)" - rom ( name "RoboCop versus The Terminator (Europe).gb" size 131072 crc 9e3b05c4 sha1 a5abfff1026fc65c8da95b40423c4d136f4bcfba ) + name "RoboCop Versus The Terminator (Europe)" + description "RoboCop Versus The Terminator (Europe)" + rom ( name "RoboCop Versus The Terminator (Europe).gb" size 131072 crc 9e3b05c4 sha1 a5abfff1026fc65c8da95b40423c4d136f4bcfba ) ) game ( - name "RoboCop versus The Terminator (USA)" - description "RoboCop versus The Terminator (USA)" - rom ( name "RoboCop versus The Terminator (USA).gb" size 131072 crc f82d7223 sha1 ca0484363d1d427474de028f037c7d566e9c1fea ) + name "RoboCop Versus The Terminator (USA)" + description "RoboCop Versus The Terminator (USA)" + rom ( name "RoboCop Versus The Terminator (USA).gb" size 131072 crc f82d7223 sha1 ca0484363d1d427474de028f037c7d566e9c1fea ) ) game ( @@ -31276,6 +31522,18 @@ game ( rom ( name "Rockman World 5 (Japan) (SGB Enhanced).gb" size 524288 crc eeabd3c6 sha1 f3904d2069a888e45ca44878461324e4c2a8b03d flags verified ) ) +game ( + name "Rod Land (World) (Retro-Bit)" + description "Rod Land (World) (Retro-Bit)" + rom ( name "Rod Land (World) (Retro-Bit).gb" size 65536 crc a5f649f8 sha1 aa9adb6192b117f395db8ed75117cda7ac703122 ) +) + +game ( + name "Rodland (Europe) (Beta)" + description "Rodland (Europe) (Beta)" + rom ( name "Rodland (Europe) (Beta).gb" size 65536 crc 4c157387 sha1 6d6d77914e353e67c59627359ba092fb2edd81b1 ) +) + game ( name "Rodland (Europe)" description "Rodland (Europe)" @@ -31300,6 +31558,18 @@ game ( rom ( name "Rolan's Curse (USA).gb" size 65536 crc 1a602590 sha1 d5eeb34b24691eb6895d3349a05e2a75d910cf16 ) ) +game ( + name "Rolan's Curse (USA) (Beta)" + description "Rolan's Curse (USA) (Beta)" + rom ( name "Rolan's Curse (USA) (Beta).gb" size 65536 crc d9b226db sha1 a88c6ef7c505f5ae2753bc596f76244ac00c0f27 ) +) + +game ( + name "Rolan's Curse II (USA) (Beta)" + description "Rolan's Curse II (USA) (Beta)" + rom ( name "Rolan's Curse II (USA) (Beta).gb" size 131072 crc be7887b5 sha1 d3235e508bb32730f6d22ad6c2d3bdb334822acc ) +) + game ( name "Rolan's Curse II (USA)" description "Rolan's Curse II (USA)" @@ -32638,6 +32908,18 @@ game ( rom ( name "Sword of Hope II, The (USA).gb" size 262144 crc 5b7ff38c sha1 3ccb37fd8e6e39a9e8421ee6f1ae199b4586afc1 flags verified ) ) +game ( + name "Sword of Hope II, The (USA) (Beta)" + description "Sword of Hope II, The (USA) (Beta)" + rom ( name "Sword of Hope II, The (USA) (Beta).gb" size 262144 crc 5f717afa sha1 780f666105f1913e3cb2181f89c1035c7698afb5 ) +) + +game ( + name "Sword of Hope, The (USA) (Beta) (1990-12-28)" + description "Sword of Hope, The (USA) (Beta) (1990-12-28)" + rom ( name "Sword of Hope, The (USA) (Beta) (1990-12-28).gb" size 131072 crc 011f675a sha1 947a747b3669a43d330dddb2eecdfce0def09eef ) +) + game ( name "Sword of Hope, The (Germany)" description "Sword of Hope, The (Germany)" @@ -32950,6 +33232,12 @@ game ( rom ( name "Tekkyu Fight! - The Great Battle Gaiden (Japan).gb" size 131072 crc b8da8eb5 sha1 06a6ab3ba59286772f915053cfcd6e1e873358b1 ) ) +game ( + name "Tekkyu Fight! - The Great Battle Gaiden (Japan) (Beta) (1993-04-12)" + description "Tekkyu Fight! - The Great Battle Gaiden (Japan) (Beta) (1993-04-12)" + rom ( name "Tekkyu Fight! - The Great Battle Gaiden (Japan) (Beta) (1993-04-12).gb" size 131072 crc ebbb6e14 sha1 7b42c9c4f537dd74f5a9665f9d76d249fbffad58 ) +) + game ( name "Tenchi o Kurau (Japan)" description "Tenchi o Kurau (Japan)" @@ -33109,7 +33397,7 @@ game ( game ( name "Tiny Toon Adventures - Wacky Sports (Europe)" description "Tiny Toon Adventures - Wacky Sports (Europe)" - rom ( name "Tiny Toon Adventures - Wacky Sports (Europe).gb" size 131072 crc d731bda2 sha1 5b3e522f92c87e67a6d6c1922ce8df4314f7f504 ) + rom ( name "Tiny Toon Adventures - Wacky Sports (Europe).gb" size 131072 crc d731bda2 sha1 5b3e522f92c87e67a6d6c1922ce8df4314f7f504 flags verified ) ) game ( @@ -34249,8 +34537,8 @@ game ( clrmamepro ( name "Nintendo - Game Boy (Aftermarket)" description "Nintendo - Game Boy (Aftermarket)" - version 20240809-004429 - 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, Jack, jimmsu, Just001Kim, kazumi213, leekindo, Lesserkuma, Madeline, NESBrew12, NGEfreak, nnssxx, norkmetnoil577, NovaAurora, omonim2007, Powerpuff, PPLToast, Psychofox11, psykopat, rarenight, relax, RetroUprising, rpg2813, sCZther, SonGoku, Tauwasser, togemet2, UnlockerPT, xNo, xprism, xuom2" + version 20250328-062132 + 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, Jack, jimmsu, Just001Kim, kazumi213, leekindo, Lesserkuma, Madeline, NESBrew12, NGEfreak, nnssxx, norkmetnoil577, NovaAurora, number, omonim2007, Powerpuff, PPLToast, Psychofox11, psykopat, rarenight, relax, RetroUprising, rpg2813, sCZther, SonGoku, Tauwasser, togemet2, UnlockerPT, xNo, xprism, xuom2" homepage No-Intro url "https://www.no-intro.org" forcenodump required @@ -34266,12 +34554,24 @@ game ( rom ( name "14 Juillet (World) (Fr) (Aftermarket) (Unl).gb" size 1048576 crc 7b66bee4 sha1 02f387457a779cbd2f493e52743cd32c169c098e ) ) +game ( + name "A Fairy Without Wings (World) (v1.3.0) (Aftermarket) (Unl)" + description "A Fairy Without Wings (World) (v1.3.0) (Aftermarket) (Unl)" + rom ( name "A Fairy Without Wings (World) (v1.3.0) (Aftermarket) (Unl).gb" size 131072 crc 84ec7e22 sha1 a9f7a41da30a574aab91a15fc530fbd0a0aafe9c ) +) + 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 flags verified ) ) +game ( + name "Alien Invasion (World) (Aftermarket) (Unl)" + description "Alien Invasion (World) (Aftermarket) (Unl)" + rom ( name "Alien Invasion (World) (Aftermarket) (Unl).gb" size 32768 crc 0b0041fb sha1 af79b5c10ca19d135d3917df6e22aabb46262795 ) +) + game ( name "Alley (World) (Aftermarket) (Unl)" description "Alley (World) (Aftermarket) (Unl)" @@ -34356,6 +34656,12 @@ game ( rom ( name "Auto Zone (World) (Aftermarket) (Unl).gb" size 524288 crc cee73c14 sha1 3070ec215014633dac5dbbb487aade2e2993c049 ) ) +game ( + name "Beta Soul (World) (v1.1) (Aftermarket) (Unl)" + description "Beta Soul (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Beta Soul (World) (v1.1) (Aftermarket) (Unl).gb" size 524288 crc f8405ff2 sha1 66354c5e074eaeaf4af8370c681eeaa354001de7 ) +) + game ( name "Binding of Isaac, The - Game Boy Edition (World) (Aftermarket) (Unl)" description "Binding of Isaac, The - Game Boy Edition (World) (Aftermarket) (Unl)" @@ -34428,6 +34734,12 @@ game ( rom ( name "Brimstone (World) (Demo) (Aftermarket) (Unl).gb" size 262144 crc f18f44a6 sha1 f193af733f6dfdf8d4403be1d9caa67d7e6f3c37 ) ) +game ( + name "Bubblemania (World) (v0.7) (Aftermarket) (Unl)" + description "Bubblemania (World) (v0.7) (Aftermarket) (Unl)" + rom ( name "Bubblemania (World) (v0.7) (Aftermarket) (Unl).gb" size 65536 crc d969ed61 sha1 d6d136d6fff47b963f9ad77fe81434a50bcb59ca ) +) + game ( name "Bug Bites! (World) (Aftermarket) (Unl)" description "Bug Bites! (World) (Aftermarket) (Unl)" @@ -34524,6 +34836,30 @@ game ( rom ( name "Commando (World) (Aftermarket) (Unl).gb" size 262144 crc 48173941 sha1 c861858e9f2cf7470e739c26ae9f17d3834ce464 ) ) +game ( + name "Commodore 16+4 Pack (World) (Demo) (Aftermarket) (Unl)" + description "Commodore 16+4 Pack (World) (Demo) (Aftermarket) (Unl)" + rom ( name "Commodore 16+4 Pack (World) (Demo) (Aftermarket) (Unl).gb" size 4194304 crc 21c00c57 sha1 4dabb9aa0cc8d4c5cffbe705dfc4e3c5d116a8a3 ) +) + +game ( + name "Commodore 16+4 Pack (World) (Demo 2) (Aftermarket) (Unl)" + description "Commodore 16+4 Pack (World) (Demo 2) (Aftermarket) (Unl)" + rom ( name "Commodore 16+4 Pack (World) (Demo 2) (Aftermarket) (Unl).gb" size 4194304 crc a49a2ec5 sha1 0f2c4bb703909b42386f45629e4f5fc4d0871165 flags verified ) +) + +game ( + name "Conefuse (World) (Rev 1) (Aftermarket) (Unl)" + description "Conefuse (World) (Rev 1) (Aftermarket) (Unl)" + rom ( name "Conefuse (World) (Rev 1) (Aftermarket) (Unl).gb" size 1048576 crc 75f4b99e sha1 fbf0cfce434c26c0b783cfce332ec9966cc32609 ) +) + +game ( + name "Conefuse (World) (Aftermarket) (Unl)" + description "Conefuse (World) (Aftermarket) (Unl)" + rom ( name "Conefuse (World) (Aftermarket) (Unl).gb" size 1048576 crc f4f55d48 sha1 c6aee5677eef50ab4123442a9a6ad191554e425a ) +) + game ( name "Cosmic Courier - Trapped in Limbo (World) (Aftermarket) (Unl)" description "Cosmic Courier - Trapped in Limbo (World) (Aftermarket) (Unl)" @@ -34542,6 +34878,12 @@ game ( rom ( name "Counting Sheep (World) (Aftermarket) (Unl).GB" size 65536 crc 6e97c837 sha1 e7e251ad86fa00803837cf871de62d3f100c83ce ) ) +game ( + name "Cryohazard (World) (Aftermarket) (Unl)" + description "Cryohazard (World) (Aftermarket) (Unl)" + rom ( name "Cryohazard (World) (Aftermarket) (Unl).gb" size 524288 crc 52773bf6 sha1 9d7b8101202904918e0db6cd268b3b3eeb74d829 ) +) + game ( name "Cuthbert in the Cooler (World) (Aftermarket) (Unl)" description "Cuthbert in the Cooler (World) (Aftermarket) (Unl)" @@ -34596,6 +34938,18 @@ game ( rom ( name "Deadeus (World) (v1.1.0) (Aftermarket) (Unl).gb" size 1048576 crc 818a7db7 sha1 43a93dc6f7bef002271e583edaeaf2e7162b8af4 ) ) +game ( + name "Death Planet (World) (Aftermarket) (Unl)" + description "Death Planet (World) (Aftermarket) (Unl)" + rom ( name "Death Planet (World) (Aftermarket) (Unl).gb" size 32768 crc 956d9497 sha1 70c711f073c73f237ca5937e7ac5dc86200bcae5 ) +) + +game ( + name "Decline (World) (v1.1) (Aftermarket) (Unl)" + description "Decline (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Decline (World) (v1.1) (Aftermarket) (Unl).gb" size 1048576 crc f0f79077 sha1 145b03118821f65582b10d91b749f64364d25523 ) +) + game ( name "Deep Forest (World) (v1.1) (Aftermarket) (Unl)" description "Deep Forest (World) (v1.1) (Aftermarket) (Unl)" @@ -34608,6 +34962,12 @@ game ( rom ( name "DiaMaze (World) (Aftermarket) (Unl).gb" size 262144 crc 956fa901 sha1 f41b98ac4669920ffede1736ddbd9e1f62d4cb0d ) ) +game ( + name "Die And Retry (World) (Aftermarket) (Unl)" + description "Die And Retry (World) (Aftermarket) (Unl)" + rom ( name "Die And Retry (World) (Aftermarket) (Unl).gb" size 32768 crc 05650804 sha1 f754f5763cd8c4ef181a6316e8b1b6e47aa56299 ) +) + game ( name "Dijon Gameboy (World) (En,Fr) (Demo) (Aftermarket) (Unl)" description "Dijon Gameboy (World) (En,Fr) (Demo) (Aftermarket) (Unl)" @@ -34758,6 +35118,12 @@ game ( rom ( name "Footballer of the Year (World) (v1.0) (Aftermarket) (Unl).gb" size 1048576 crc 91faa9b1 sha1 b7542729c8665a1a2af47f58fc59cc90f66fd77d ) ) +game ( + name "Friend of the Void (World) (Aftermarket) (Unl)" + description "Friend of the Void (World) (Aftermarket) (Unl)" + rom ( name "Friend of the Void (World) (Aftermarket) (Unl).gb" size 524288 crc 10697ce3 sha1 1192e03f448503d4594a81644ee8fc9b7e80e085 ) +) + game ( name "Fydo's Magic Tiles (World) (2022-09-01) (Aftermarket) (Unl)" description "Fydo's Magic Tiles (World) (2022-09-01) (Aftermarket) (Unl)" @@ -34782,12 +35148,24 @@ game ( rom ( name "GameBoy WORDLE (World) (Aftermarket) (Unl).gb" size 32768 crc cc971c0f sha1 ba93939b93ab3f3aa5f7aa451d50d9b89220adbc ) ) +game ( + name "GB Pixel Jam 2023 Gallery (World) (Digital) (Aftermarket) (Unl)" + description "GB Pixel Jam 2023 Gallery (World) (Digital) (Aftermarket) (Unl)" + rom ( name "GB Pixel Jam 2023 Gallery (World) (Digital) (Aftermarket) (Unl).gb" size 1048576 crc 250d665a sha1 d6c41777318c35e0e1874de0b06613a26600d21b ) +) + 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 "Genesis 2 (World) (Aftermarket) (Unl)" + description "Genesis 2 (World) (Aftermarket) (Unl)" + rom ( name "Genesis 2 (World) (Aftermarket) (Unl).gb" size 262144 crc a9b982bd sha1 13c769ae53c8337a091a950020ba821ff5b02a6d ) +) + game ( name "Genesis II (World) (Demo) (Aftermarket) (Unl)" description "Genesis II (World) (Demo) (Aftermarket) (Unl)" @@ -34830,6 +35208,12 @@ game ( rom ( name "Gunman Clive (World) (Demo) (Aftermarket) (Unl).gb" size 65536 crc 11f5fded sha1 ec03763db2c0d754e2eb7e98384ed92fc8aeeb1d ) ) +game ( + name "Guns&Riders (World) (Aftermarket) (Unl)" + description "Guns&Riders (World) (Aftermarket) (Unl)" + rom ( name "Guns&Riders (World) (Aftermarket) (Unl).gb" size 32768 crc af58a3b0 sha1 c5bb09e1c094e256e611f96595976b67692f5bd4 ) +) + game ( name "Gunship (World) (Aftermarket) (Unl)" description "Gunship (World) (Aftermarket) (Unl)" @@ -34849,9 +35233,9 @@ game ( ) game ( - name "Hermano (World) (v1.1) (Aftermarket) (Unl)" - description "Hermano (World) (v1.1) (Aftermarket) (Unl)" - rom ( name "Hermano (World) (v1.1) (Aftermarket) (Unl).gb" size 262144 crc 74a0419b sha1 c42cfe91a1ec593a76291e031c47137bd794ffda ) + name "Hermano (World) (v1.1) (Demo) (GB Compo Version) (Aftermarket) (Unl)" + description "Hermano (World) (v1.1) (Demo) (GB Compo Version) (Aftermarket) (Unl)" + rom ( name "Hermano (World) (v1.1) (Demo) (GB Compo Version) (Aftermarket) (Unl).gb" size 262144 crc 74a0419b sha1 c42cfe91a1ec593a76291e031c47137bd794ffda ) ) game ( @@ -34866,12 +35250,24 @@ game ( rom ( name "If (World) (Aftermarket) (Unl).gb" size 1048576 crc be7e4454 sha1 c11d8dc9ce96133f679678b07822a82f985e16f9 ) ) +game ( + name "illuminated (Unknown) (Aftermarket) (Unl)" + description "illuminated (Unknown) (Aftermarket) (Unl)" + rom ( name "illuminated (Unknown) (Aftermarket) (Unl).gb" size 32768 crc ffb432b9 sha1 7f65e29ba37f48c8507047f75475c548af36ff2d ) +) + game ( name "Impossible Gameboy (World) (Aftermarket) (Unl)" description "Impossible Gameboy (World) (Aftermarket) (Unl)" rom ( name "Impossible Gameboy (World) (Aftermarket) (Unl).gb" size 131072 crc ab65b738 sha1 d31cedd6227b23cf3d8ef81c73f133ab0b57e4f4 ) ) +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 "Interblocked (World) (Aftermarket) (Unl)" description "Interblocked (World) (Aftermarket) (Unl)" @@ -34947,7 +35343,7 @@ game ( game ( name "Kudzu (World) (v0.96) (Demo) (Aftermarket) (Unl)" description "Kudzu (World) (v0.96) (Demo) (Aftermarket) (Unl)" - rom ( name "Kudzu (World) (v0.96) (Demo) (Aftermarket) (Unl).gb" size 2097152 crc 0610049a sha1 a6fbe1a524547bebabc5e6b3baff1dff8f13dbd0 ) + rom ( name "Kudzu (World) (v0.96) (Demo) (Aftermarket) (Unl).gb" size 2097152 crc 0610049a sha1 a6fbe1a524547bebabc5e6b3baff1dff8f13dbd0 flags verified ) ) game ( @@ -34956,6 +35352,12 @@ game ( rom ( name "Kudzu (World) (v1.1c) (Demo) (Aftermarket) (Unl).gb" size 2097152 crc 4462ff70 sha1 18a2a762d7b994865c845ac2aa0a0cf7655af74e flags verified ) ) +game ( + name "Kuru Kuru Pixel Logic (World) (Aftermarket) (Unl)" + description "Kuru Kuru Pixel Logic (World) (Aftermarket) (Unl)" + rom ( name "Kuru Kuru Pixel Logic (World) (Aftermarket) (Unl).gb" size 131072 crc 45689c8b sha1 b7a3c7586afcb3489501b75f87f02c20bcd66cd5 ) +) + game ( name "Laser Squad Alter (World) (Aftermarket) (Unl)" description "Laser Squad Alter (World) (Aftermarket) (Unl)" @@ -35059,9 +35461,9 @@ game ( ) game ( - name "Machine, The (World) (v1.0) (Aftermarket) (Unl)" - description "Machine, The (World) (v1.0) (Aftermarket) (Unl)" - rom ( name "Machine, The (World) (v1.0) (Aftermarket) (Unl).gb" size 2097152 crc b06036a9 sha1 50c5d1eb7c8946ac2d7e2c566b1ea4a9b0d58e90 ) + 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 ( @@ -35142,6 +35544,12 @@ game ( rom ( name "Muncher (World) (Aftermarket) (Unl).gb" size 262144 crc 444d3d5e sha1 c3e09aff66c7e395bf95a1864963fbe979d5cb9b ) ) +game ( + name "My Dinner with Andre (World) (v1.1) (Aftermarket) (Unl)" + description "My Dinner with Andre (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "My Dinner with Andre (World) (v1.1) (Aftermarket) (Unl).gb" size 262144 crc 0010b680 sha1 86c8413550007e1e57166d2c2313e65878a8053b ) +) + game ( name "My Husband is a WITCH (World) (v1.1) (Aftermarket) (Unl)" description "My Husband is a WITCH (World) (v1.1) (Aftermarket) (Unl)" @@ -35172,6 +35580,12 @@ game ( rom ( name "Neko Can Dream (World) (En) (Demo) (Aftermarket) (Unl).gb" size 2097152 crc dc011d07 sha1 43312b149355d20944e8a366584c69e5cb3be868 ) ) +game ( + name "Ninja Twins (World) (Digital) (SGB Enhanced) (Aftermarket) (Unl)" + description "Ninja Twins (World) (Digital) (SGB Enhanced) (Aftermarket) (Unl)" + rom ( name "Ninja Twins (World) (Digital) (SGB Enhanced) (Aftermarket) (Unl).gb" size 32768 crc 9e7c919f sha1 23603f18337bdf997bf33cec3a015dbe106a4eee ) +) + game ( name "Nocptern (World) (v1.0) (Demo) (Aftermarket) (Unl)" description "Nocptern (World) (v1.0) (Demo) (Aftermarket) (Unl)" @@ -35220,6 +35634,12 @@ game ( rom ( name "Olympic Skier (World) (Aftermarket) (Unl).gb" size 524288 crc 93174431 sha1 7bcdf10533f8fe053e22534c4962ed4b1a5cf2e2 ) ) +game ( + name "Oni (World) (Digital) (Aftermarket) (Unl)" + description "Oni (World) (Digital) (Aftermarket) (Unl)" + rom ( name "Oni (World) (Digital) (Aftermarket) (Unl).gb" size 2097152 crc d3819a3b sha1 7f074a33e814bcd25f5b12cbcfb5991d01955221 ) +) + game ( name "Opossum a la Mode (World) (Aftermarket) (Unl)" description "Opossum a la Mode (World) (Aftermarket) (Unl)" @@ -35346,6 +35766,12 @@ game ( rom ( name "Porklike (World) (v1.05) (SGB Enhanced) (Aftermarket) (Unl).gb" size 65536 crc 801f097b sha1 5da110a88799765707e027bcedec0875a637509b ) ) +game ( + name "Porklike (World) (v1.0.13) (Aftermarket) (Unl)" + description "Porklike (World) (v1.0.13) (Aftermarket) (Unl)" + rom ( name "Porklike (World) (v1.0.13) (Aftermarket) (Unl).gb" size 65536 crc ebf49afb sha1 522883a56d9a5a228ce9e39086d7ac1826f34255 ) +) + game ( name "Pull of the Void (World) (Aftermarket) (Unl)" description "Pull of the Void (World) (Aftermarket) (Unl)" @@ -35430,6 +35856,12 @@ game ( rom ( name "Robby's Day Out (World) (Aftermarket) (Unl).gb" size 262144 crc c3c89cd9 sha1 9d38b69ab9a4ff7bf387c88f2afed410d450f2e0 ) ) +game ( + name "Rocket Man (World) (v1.2.1) (Demo) (Aftermarket) (Unl)" + description "Rocket Man (World) (v1.2.1) (Demo) (Aftermarket) (Unl)" + rom ( name "Rocket Man (World) (v1.2.1) (Demo) (Aftermarket) (Unl).gb" size 262144 crc a80fb7f0 sha1 5a83261706c6039cf1598888ce3f47839ce4b173 ) +) + game ( name "Roommate Simulator (World) (Aftermarket) (Unl)" description "Roommate Simulator (World) (Aftermarket) (Unl)" @@ -35551,9 +35983,15 @@ game ( ) game ( - name "Song of Morus - Gala of Battle (World) (En,Ja) (Aftermarket) (Unl)" - description "Song of Morus - Gala of Battle (World) (En,Ja) (Aftermarket) (Unl)" - rom ( name "Song of Morus - Gala of Battle (World) (En,Ja) (Aftermarket) (Unl).gb" size 262144 crc 665ad70a sha1 c5b6eb610caa2213d8db7ab204bad44d60f7d63a ) + name "Song of Morus - Gala of Battle (World) (En,Ja) (Beta) (Digital) (Aftermarket) (Unl)" + description "Song of Morus - Gala of Battle (World) (En,Ja) (Beta) (Digital) (Aftermarket) (Unl)" + rom ( name "Song of Morus - Gala of Battle (World) (En,Ja) (Beta) (Digital) (Aftermarket) (Unl).gb" size 262144 crc 665ad70a sha1 c5b6eb610caa2213d8db7ab204bad44d60f7d63a ) +) + +game ( + name "Song of Morus - Gala of Battle (World) (En,Ja) (Demo) (SGB Enhanced) (Aftermarket) (Unl)" + description "Song of Morus - Gala of Battle (World) (En,Ja) (Demo) (SGB Enhanced) (Aftermarket) (Unl)" + rom ( name "Song of Morus - Gala of Battle (World) (En,Ja) (Demo) (SGB Enhanced) (Aftermarket) (Unl).gb" size 524288 crc dbaf0247 sha1 f537ce67866a87a844eeeaa2dcc44551565da299 ) ) game ( @@ -35688,6 +36126,12 @@ game ( rom ( name "Touch the Cat (World) (Aftermarket) (Unl).gb" size 131072 crc 351fd00a sha1 9dc7b51a44aec9055924596c161972f366caf067 ) ) +game ( + name "Tower of Hanoi (World) (v1.1) (Aftermarket) (Unl)" + description "Tower of Hanoi (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Tower of Hanoi (World) (v1.1) (Aftermarket) (Unl).gb" size 32768 crc c85947ef sha1 3bb4e1e460660711547a4e76b6d072e2f868a459 ) +) + game ( name "Traumatarium (World) (Demo) (2021-12-09) (Aftermarket) (Unl)" description "Traumatarium (World) (Demo) (2021-12-09) (Aftermarket) (Unl)" @@ -35724,6 +36168,18 @@ game ( rom ( name "Treasure Island (World) (Aftermarket) (Unl).gb" size 262144 crc cbdec393 sha1 adeaba2723d286d143e0987f51ce9c8ad2f5e839 ) ) +game ( + name "Tuff (World) (Aftermarket) (Unl)" + description "Tuff (World) (Aftermarket) (Unl)" + rom ( name "Tuff (World) (Aftermarket) (Unl).gb" size 65536 crc e9140a3b sha1 cd1b425bcc7668328ba58fed6474ea8aaf4fd78d ) +) + +game ( + name "Turn 'Em Off (World) (Aftermarket) (Unl)" + description "Turn 'Em Off (World) (Aftermarket) (Unl)" + rom ( name "Turn 'Em Off (World) (Aftermarket) (Unl).gb" size 262144 crc 526a4d25 sha1 65ae6f910b167d686d734533d43ac5a0bd0a3708 ) +) + game ( name "Vampire Night Shift (World) (Aftermarket) (Unl)" description "Vampire Night Shift (World) (Aftermarket) (Unl)" @@ -35748,6 +36204,18 @@ game ( rom ( name "Windows93 Adventure (World) (Aftermarket) (Unl).gb" size 1048576 crc e1ad0b6f sha1 3ad7327f6f94976c3d12054cf6deb2666ba9fa66 ) ) +game ( + name "Wishing Sarah (World) (v1.1) (Aftermarket) (Unl)" + description "Wishing Sarah (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Wishing Sarah (World) (v1.1) (Aftermarket) (Unl).gb" size 524288 crc efa345cd sha1 0e32eb4ef5cc3c456072892c376c5ee5d4fc78f1 ) +) + +game ( + name "Wishing Sarah (World) (Pt-BR) (v1.1) (Aftermarket) (Unl)" + description "Wishing Sarah (World) (Pt-BR) (v1.1) (Aftermarket) (Unl)" + rom ( name "Wishing Sarah (World) (Pt-BR) (v1.1) (Aftermarket) (Unl).gb" size 524288 crc ab6cf21e sha1 575b8b0f6fdb2d41623e7f2fe9902c87236f1a36 ) +) + game ( name "Witchscape (World) (Proto) (Aftermarket) (Unl)" description "Witchscape (World) (Proto) (Aftermarket) (Unl)" @@ -35811,8 +36279,8 @@ game ( clrmamepro ( name "Nintendo - Game Boy Color" description "Nintendo - Game Boy Color" - version 20240810-021134 - author "akubi, Arctic Circle System, Aringon, baldjared, Bent, BigFred, bikerspade, BitLooter, C. V. Reynolds, chillerecke, coraz, darthcloud, DeadSkullzJr, Densetsu, DeriLoko3, Flashfire42, foxe, fuzzball, Gefflon, gordonj, Hiccup, hking0036, InternalLoss, Just001Kim, kazumi213, Lesserkuma, Madeline, Money_114, NESBrew12, NGEfreak, nnssxx, norkmetnoil577, NovaAurora, omonim2007, PPLToast, Psychofox11, psykopat, rarenight, relax, Rifu, sCZther, SonGoku, Tauwasser, togemet2, UnlockerPT, Whovian9369, xprism, xuom2, zg" + version 20250329-080448 + author "akubi, Arctic Circle System, Aringon, baldjared, Bent, BigFred, bikerspade, BitLooter, buckwheat, C. V. Reynolds, chillerecke, ChuckD, coraz, darthcloud, DeadSkullzJr, Densetsu, DeriLoko3, Flashfire42, foxe, fuzzball, Gefflon, gordonj, Hiccup, hking0036, InternalLoss, Just001Kim, kazumi213, Lesserkuma, Madeline, Money_114, NESBrew12, NGEfreak, nnssxx, norkmetnoil577, NovaAurora, number, omonim2007, PPLToast, Psychofox11, psykopat, rarenight, relax, Rifu, sCZther, SonGoku, Tauwasser, togemet2, UnlockerPT, Whovian9369, xprism, xuom2, zg" homepage No-Intro url "https://www.no-intro.org" forcenodump required @@ -35853,9 +36321,9 @@ game ( ) game ( - name "[BIOS] Nintendo Game Boy Color Boot ROM (World) (Beta) (Virtual Console)" - description "[BIOS] Nintendo Game Boy Color Boot ROM (World) (Beta) (Virtual Console)" - rom ( name "[BIOS] Nintendo Game Boy Color Boot ROM (World) (Beta) (Virtual Console).gbc" size 2304 crc e95dc95d sha1 f5f33729a956131d9c44310f0ae3bb0599e9ec3e ) + name "[BIOS] Nintendo Game Boy Color Boot ROM (World) (Rev 2)" + description "[BIOS] Nintendo Game Boy Color Boot ROM (World) (Rev 2)" + rom ( name "[BIOS] Nintendo Game Boy Color Boot ROM (World) (Rev 2).gbc" size 2304 crc e95dc95d sha1 f5f33729a956131d9c44310f0ae3bb0599e9ec3e ) ) game ( @@ -36377,7 +36845,7 @@ game ( game ( name "Atlantis - The Lost Empire (Europe) (Fr,De,Nl)" description "Atlantis - The Lost Empire (Europe) (Fr,De,Nl)" - rom ( name "Atlantis - The Lost Empire (Europe) (Fr,De,Nl).gbc" size 2097152 crc 4e052510 sha1 19df06e2a8be1336a6aec8c86649b95b206dc54f ) + rom ( name "Atlantis - The Lost Empire (Europe) (Fr,De,Nl).gbc" size 2097152 crc 4e052510 sha1 19df06e2a8be1336a6aec8c86649b95b206dc54f flags verified ) ) game ( @@ -36557,7 +37025,7 @@ game ( game ( name "Bakuten Shoot Beyblade (Japan)" description "Bakuten Shoot Beyblade (Japan)" - rom ( name "Bakuten Shoot Beyblade (Japan).gbc" size 2097152 crc bc306ea4 sha1 68d41bca8d2ac8e9a6855460b15e6045acd341a1 ) + rom ( name "Bakuten Shoot Beyblade (Japan).gbc" size 2097152 crc bc306ea4 sha1 68d41bca8d2ac8e9a6855460b15e6045acd341a1 flags verified ) ) game ( @@ -37502,6 +37970,18 @@ game ( rom ( name "Conker's Pocket Tales (USA, Europe) (En,Fr,De) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc a50be9a8 sha1 e9c3b1bb20ea74f363191ea9144009d1b13246bb flags verified ) ) +game ( + name "Cool Bricks (Europe) (Beta) (1999-03-24)" + description "Cool Bricks (Europe) (Beta) (1999-03-24)" + rom ( name "Cool Bricks (Europe) (Beta) (1999-03-24).gbc" size 32768 crc 9754006e sha1 784fffacd2ce97c7a91971d8522e01fe5596de0f ) +) + +game ( + name "Cool Bricks (Europe) (Beta) (1999-05-10)" + description "Cool Bricks (Europe) (Beta) (1999-05-10)" + rom ( name "Cool Bricks (Europe) (Beta) (1999-05-10).gbc" size 131072 crc 59d4a387 sha1 17d58a23a911813866923773a17cc92a142ae950 ) +) + game ( name "Cool Bricks (Europe) (En,Fr,De,Es,It)" description "Cool Bricks (Europe) (En,Fr,De,Es,It)" @@ -39242,6 +39722,12 @@ game ( rom ( name "Get Chuu Club - Minna no Konchuu Daizukan (Japan) (Rumble Version) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 9ebdb6c6 sha1 8a04a1c6374df1d6393a8af395422b63aad2e3d1 ) ) +game ( + name "Get!! Loto Club (Japan) (Proto)" + description "Get!! Loto Club (Japan) (Proto)" + rom ( name "Get!! Loto Club (Japan) (Proto).gbc" size 1048576 crc 910e6fde sha1 c22b9e28cbd3d90d8c76dac743e917d6dbe9db05 ) +) + game ( name "Gex - Enter the Gecko (USA, Europe) (GB Compatible)" description "Gex - Enter the Gecko (USA, Europe) (GB Compatible)" @@ -40253,7 +40739,7 @@ game ( game ( name "Jisedai Begoma Battle Beyblade (Japan)" description "Jisedai Begoma Battle Beyblade (Japan)" - rom ( name "Jisedai Begoma Battle Beyblade (Japan).gbc" size 2097152 crc 9c56977e sha1 fc2af4ebd10ca20158d231089a8378f6c4641329 ) + rom ( name "Jisedai Begoma Battle Beyblade (Japan).gbc" size 2097152 crc 9c56977e sha1 fc2af4ebd10ca20158d231089a8378f6c4641329 flags verified ) ) game ( @@ -40553,7 +41039,7 @@ game ( game ( name "Kinniku Banzuke GB 3 - Shinseiki Survival Retsuden! (Japan)" description "Kinniku Banzuke GB 3 - Shinseiki Survival Retsuden! (Japan)" - rom ( name "Kinniku Banzuke GB 3 - Shinseiki Survival Retsuden! (Japan).gbc" size 4194304 crc e2f6253e sha1 2e5d5adcdbedf45c6ebda0f90ccfa787a24c24d0 ) + rom ( name "Kinniku Banzuke GB 3 - Shinseiki Survival Retsuden! (Japan).gbc" size 4194304 crc e2f6253e sha1 2e5d5adcdbedf45c6ebda0f90ccfa787a24c24d0 flags verified ) ) game ( @@ -40688,6 +41174,12 @@ game ( rom ( name "Konchuu Hakase 2 (Japan) (Rev 1) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc d390430e sha1 59fa1707247f12b0c48eeb2e8fec274b9231d968 ) ) +game ( + name "Konchuu Hakase 3 (Japan) (Beta)" + description "Konchuu Hakase 3 (Japan) (Beta)" + rom ( name "Konchuu Hakase 3 (Japan) (Beta).gbc" size 2097152 crc fbed99bc sha1 cb09509e97f433f3edcbd29cb42a37a36e3e49be ) +) + game ( name "Konchuu Hakase 3 (Japan)" description "Konchuu Hakase 3 (Japan)" @@ -41657,7 +42149,7 @@ game ( game ( name "Medarot 2 - Kabuto Version (Japan) (SGB Enhanced) (GB Compatible)" description "Medarot 2 - Kabuto Version (Japan) (SGB Enhanced) (GB Compatible)" - rom ( name "Medarot 2 - Kabuto Version (Japan) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc bf6bd446 sha1 01adf621d6e2cbfec46306d69882fc2eb3d92de5 ) + rom ( name "Medarot 2 - Kabuto Version (Japan) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc bf6bd446 sha1 01adf621d6e2cbfec46306d69882fc2eb3d92de5 flags verified ) ) game ( @@ -41669,19 +42161,19 @@ game ( game ( name "Medarot 2 - Parts Collection (Japan) (SGB Enhanced) (GB Compatible)" description "Medarot 2 - Parts Collection (Japan) (SGB Enhanced) (GB Compatible)" - rom ( name "Medarot 2 - Parts Collection (Japan) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc 159673db sha1 62e6e2800025918a0df9ceb31418dcbfe9e64289 ) + rom ( name "Medarot 2 - Parts Collection (Japan) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc 159673db sha1 62e6e2800025918a0df9ceb31418dcbfe9e64289 flags verified ) ) game ( name "Medarot 3 - Kabuto Version (Japan)" description "Medarot 3 - Kabuto Version (Japan)" - rom ( name "Medarot 3 - Kabuto Version (Japan).gbc" size 4194304 crc e655632c sha1 5478069c840b5b13e2413771f35fdc844d1974f1 ) + rom ( name "Medarot 3 - Kabuto Version (Japan).gbc" size 4194304 crc e655632c sha1 5478069c840b5b13e2413771f35fdc844d1974f1 flags verified ) ) game ( name "Medarot 3 - Kuwagata Version (Japan)" description "Medarot 3 - Kuwagata Version (Japan)" - rom ( name "Medarot 3 - Kuwagata Version (Japan).gbc" size 4194304 crc bc617834 sha1 5207698702d206046dd105b07e0c0bbdbc6ed39c ) + rom ( name "Medarot 3 - Kuwagata Version (Japan).gbc" size 4194304 crc bc617834 sha1 5207698702d206046dd105b07e0c0bbdbc6ed39c flags verified ) ) game ( @@ -41693,25 +42185,25 @@ game ( game ( name "Medarot 4 - Kabuto Version (Japan)" description "Medarot 4 - Kabuto Version (Japan)" - rom ( name "Medarot 4 - Kabuto Version (Japan).gbc" size 4194304 crc c192a368 sha1 a62a00ee6095b3dcdf347fbb7c536b51976a109f ) + rom ( name "Medarot 4 - Kabuto Version (Japan).gbc" size 4194304 crc c192a368 sha1 a62a00ee6095b3dcdf347fbb7c536b51976a109f flags verified ) ) game ( name "Medarot 4 - Kuwagata Version (Japan)" description "Medarot 4 - Kuwagata Version (Japan)" - rom ( name "Medarot 4 - Kuwagata Version (Japan).gbc" size 4194304 crc 6a29b9d8 sha1 10b3e69d19897fd233915e3949d02be71af0e521 ) + rom ( name "Medarot 4 - Kuwagata Version (Japan).gbc" size 4194304 crc 6a29b9d8 sha1 10b3e69d19897fd233915e3949d02be71af0e521 flags verified ) ) game ( name "Medarot 5 - Susutake Mura no Tenkousei - Kabuto (Japan)" description "Medarot 5 - Susutake Mura no Tenkousei - Kabuto (Japan)" - rom ( name "Medarot 5 - Susutake Mura no Tenkousei - Kabuto (Japan).gbc" size 4194304 crc a3c1756e sha1 7f52ecb2a057d99b0448d82e3b5263eb92c2396c ) + rom ( name "Medarot 5 - Susutake Mura no Tenkousei - Kabuto (Japan).gbc" size 4194304 crc a3c1756e sha1 7f52ecb2a057d99b0448d82e3b5263eb92c2396c flags verified ) ) game ( name "Medarot 5 - Susutake Mura no Tenkousei - Kuwagata (Japan)" description "Medarot 5 - Susutake Mura no Tenkousei - Kuwagata (Japan)" - rom ( name "Medarot 5 - Susutake Mura no Tenkousei - Kuwagata (Japan).gbc" size 4194304 crc 014083a8 sha1 3fcc292449da992f04c61d9117f3d5cc1bef446f ) + rom ( name "Medarot 5 - Susutake Mura no Tenkousei - Kuwagata (Japan).gbc" size 4194304 crc 014083a8 sha1 3fcc292449da992f04c61d9117f3d5cc1bef446f flags verified ) ) game ( @@ -42131,7 +42623,7 @@ game ( game ( name "Monsters, Inc. (Europe) (En,Fr,It)" description "Monsters, Inc. (Europe) (En,Fr,It)" - rom ( name "Monsters, Inc. (Europe) (En,Fr,It).gbc" size 1048576 crc 712d40a5 sha1 34ab1e38f34287f022c871b6ed2116ded6d4b60d ) + rom ( name "Monsters, Inc. (Europe) (En,Fr,It).gbc" size 1048576 crc 712d40a5 sha1 34ab1e38f34287f022c871b6ed2116ded6d4b60d flags verified ) ) game ( @@ -42668,18 +43160,18 @@ game ( rom ( name "O'Leary Manager 2000 (Europe) (En,Fr,De,Es,It,Nl,Ca).gbc" size 1048576 crc 3485761a sha1 724aa0f905d7a6e7b9b2b01a477f424ac95eadf9 ) ) -game ( - name "Oddworld Adventures 2 (USA) (En,Fr,De,Es,It) (GB Compatible)" - description "Oddworld Adventures 2 (USA) (En,Fr,De,Es,It) (GB Compatible)" - rom ( name "Oddworld Adventures 2 (USA) (En,Fr,De,Es,It) (GB Compatible).gbc" size 1048576 crc 5c260d5a sha1 a35ccdf0789ce84df3c12cfe7c4b9b98af08ca9a ) -) - game ( name "Oddworld Adventures 2 (Europe) (En,Fr,De,Es,It) (GB Compatible)" description "Oddworld Adventures 2 (Europe) (En,Fr,De,Es,It) (GB Compatible)" rom ( name "Oddworld Adventures 2 (Europe) (En,Fr,De,Es,It) (GB Compatible).gbc" size 1048576 crc 4b83b14f sha1 c9d4d1dd1c33a9fa9b54e9f2a7a5f6dd90069b91 flags verified ) ) +game ( + name "Oddworld Adventures 2 (USA) (En,Fr,De,Es,It) (GB Compatible)" + description "Oddworld Adventures 2 (USA) (En,Fr,De,Es,It) (GB Compatible)" + rom ( name "Oddworld Adventures 2 (USA) (En,Fr,De,Es,It) (GB Compatible).gbc" size 1048576 crc 5c260d5a sha1 a35ccdf0789ce84df3c12cfe7c4b9b98af08ca9a ) +) + game ( name "Ohasuta Dance Dance Revolution GB (Japan)" description "Ohasuta Dance Dance Revolution GB (Japan)" @@ -42938,6 +43430,12 @@ game ( rom ( name "Pocket Cooking (Japan).gbc" size 4194304 crc f5ab554d sha1 60a13fc904100655a52dfb61d9abe77cc126e58c flags verified ) ) +game ( + name "Pocket Cooking (Japan) (Beta)" + description "Pocket Cooking (Japan) (Beta)" + rom ( name "Pocket Cooking (Japan) (Beta).gbc" size 4194304 crc fb9594a1 sha1 13bbc290c706aebbd0e4abb518a716d65a5250ea ) +) + game ( name "Pocket Densha 2 (Japan) (SGB Enhanced) (GB Compatible)" description "Pocket Densha 2 (Japan) (SGB Enhanced) (GB Compatible)" @@ -43053,9 +43551,21 @@ game ( ) game ( - name "Pocket Music (USA) (En,Es) (Proto)" - description "Pocket Music (USA) (En,Es) (Proto)" - rom ( name "Pocket Music (USA) (En,Es) (Proto).gbc" size 1048576 crc c4387812 sha1 ad547a79af864eb56a18e1c2ad4346eb35df41ed ) + name "Pocket Music (USA) (En,Es) (Proto) (2003-03-07)" + description "Pocket Music (USA) (En,Es) (Proto) (2003-03-07)" + rom ( name "Pocket Music (USA) (En,Es) (Proto) (2003-03-07).gbc" size 1048576 crc e6dda84f sha1 5291a00ac57dcf47e485f33972000682b861bfe8 ) +) + +game ( + name "Pocket Music (World) (Proto) (Riff Sampler Test)" + description "Pocket Music (World) (Proto) (Riff Sampler Test)" + rom ( name "Pocket Music (World) (Proto) (Riff Sampler Test).gbc" size 32768 crc 419d4565 sha1 b7ecdd51358e2794a45b425ada778722f5f000c0 ) +) + +game ( + name "Pocket Music (USA) (En,Es) (Proto) (2002-03-04)" + description "Pocket Music (USA) (En,Es) (Proto) (2002-03-04)" + rom ( name "Pocket Music (USA) (En,Es) (Proto) (2002-03-04).gbc" size 1048576 crc c4387812 sha1 ad547a79af864eb56a18e1c2ad4346eb35df41ed ) ) game ( @@ -43280,18 +43790,18 @@ game ( rom ( name "Pokemon de Panepon (Japan).gbc" size 2097152 crc 6bf7e4a6 sha1 110ae6649b4264f88d82760ad6ae4ee7f07db9b2 ) ) -game ( - name "Pokemon Diamond (Taiwan) (En) (Unl)" - description "Pokemon Diamond (Taiwan) (En) (Unl)" - rom ( name "Pokemon Diamond (Taiwan) (En) (Unl).gbc" size 524288 crc 1b5bef4b sha1 433e7991b706baedf59af8b91bc142ba2f72112f ) -) - game ( name "Pokemon Diamond (Taiwan) (Zh) (Unl)" description "Pokemon Diamond (Taiwan) (Zh) (Unl)" rom ( name "Pokemon Diamond (Taiwan) (Zh) (Unl).gbc" size 524288 crc 7309551a sha1 3fb38a7f49e13f5bfce0cd1983d9006b30f68930 ) ) +game ( + name "Pokemon Diamond (Taiwan) (En) (Unl)" + description "Pokemon Diamond (Taiwan) (En) (Unl)" + rom ( name "Pokemon Diamond (Taiwan) (En) (Unl).gbc" size 524288 crc 1b5bef4b sha1 433e7991b706baedf59af8b91bc142ba2f72112f ) +) + game ( name "Pokemon Gold (Taiwan) (En) (Unl)" description "Pokemon Gold (Taiwan) (En) (Unl)" @@ -43700,6 +44210,12 @@ game ( rom ( name "Project S-11 (USA).gbc" size 524288 crc 20cee2e8 sha1 cbedd34a0c6a2f4e58d05f0bb6d54f7cbcda815c ) ) +game ( + name "Project S-11 (USA) (Beta) (2000-05-11)" + description "Project S-11 (USA) (Beta) (2000-05-11)" + rom ( name "Project S-11 (USA) (Beta) (2000-05-11).gbc" size 65536 crc 94cb3df7 sha1 d03395abc4649ed21c41a2c6f0a6cea5ea77d364 ) +) + game ( name "Puchi Carat (USA) (Proto 2) (SGB Enhanced) (GB Compatible)" description "Puchi Carat (USA) (Proto 2) (SGB Enhanced) (GB Compatible)" @@ -44558,12 +45074,6 @@ game ( rom ( name "Senkai Ibunroku Juntei Taisen - TV Animation Senkaiden Houshin Engi Yori (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 23fa5f53 sha1 c10249ef96a1989c3532a48917862b4dd8bad12a ) ) -game ( - name "Sesame Street - Elmo's 123s (Europe) (GB Compatible)" - description "Sesame Street - Elmo's 123s (Europe) (GB Compatible)" - rom ( name "Sesame Street - Elmo's 123s (Europe) (GB Compatible).gbc" size 1048576 crc 3bf7fcd4 sha1 841bda2e2c1542c44b45c98a390ba3320ef735cc ) -) - game ( name "Sesame Street - Elmo's 123s (USA) (GB Compatible)" description "Sesame Street - Elmo's 123s (USA) (GB Compatible)" @@ -44571,9 +45081,9 @@ game ( ) game ( - name "Sesame Street - Elmo's ABCs (USA) (GB Compatible)" - description "Sesame Street - Elmo's ABCs (USA) (GB Compatible)" - rom ( name "Sesame Street - Elmo's ABCs (USA) (GB Compatible).gbc" size 262144 crc cc1fb2a9 sha1 c63ea7ff94250f1f7514a01250bbd5f04a5d2037 ) + name "Sesame Street - Elmo's 123s (Europe) (GB Compatible)" + description "Sesame Street - Elmo's 123s (Europe) (GB Compatible)" + rom ( name "Sesame Street - Elmo's 123s (Europe) (GB Compatible).gbc" size 1048576 crc 3bf7fcd4 sha1 841bda2e2c1542c44b45c98a390ba3320ef735cc ) ) game ( @@ -44582,6 +45092,12 @@ game ( rom ( name "Sesame Street - Elmo's ABCs (Europe) (GB Compatible).gbc" size 1048576 crc 20158fbc sha1 e98d4d4179ca13c22d0205f118470ecc795928b1 ) ) +game ( + name "Sesame Street - Elmo's ABCs (USA) (GB Compatible)" + description "Sesame Street - Elmo's ABCs (USA) (GB Compatible)" + rom ( name "Sesame Street - Elmo's ABCs (USA) (GB Compatible).gbc" size 262144 crc cc1fb2a9 sha1 c63ea7ff94250f1f7514a01250bbd5f04a5d2037 ) +) + game ( name "Sesame Street - The Adventures of Elmo in Grouchland (Europe) (GB Compatible)" description "Sesame Street - The Adventures of Elmo in Grouchland (Europe) (GB Compatible)" @@ -45011,7 +45527,7 @@ game ( game ( name "Simpsons, The - Night of the Living Treehouse of Horror (USA, Europe)" description "Simpsons, The - Night of the Living Treehouse of Horror (USA, Europe)" - rom ( name "Simpsons, The - Night of the Living Treehouse of Horror (USA, Europe).gbc" size 1048576 crc ebaf4888 sha1 a5be079336e48552e53706f0380f35829d91b3c0 ) + rom ( name "Simpsons, The - Night of the Living Treehouse of Horror (USA, Europe).gbc" size 1048576 crc ebaf4888 sha1 a5be079336e48552e53706f0380f35829d91b3c0 flags verified ) ) game ( @@ -45203,7 +45719,7 @@ game ( game ( name "Speedy Gonzales - Aztec Adventure (USA, Europe) (GB Compatible)" description "Speedy Gonzales - Aztec Adventure (USA, Europe) (GB Compatible)" - rom ( name "Speedy Gonzales - Aztec Adventure (USA, Europe) (GB Compatible).gbc" size 1048576 crc ae82afa4 sha1 95f868358979c5bbdfa70920fb35b7d4e00bf8cc ) + rom ( name "Speedy Gonzales - Aztec Adventure (USA, Europe) (GB Compatible).gbc" size 1048576 crc ae82afa4 sha1 95f868358979c5bbdfa70920fb35b7d4e00bf8cc flags verified ) ) game ( @@ -46400,6 +46916,12 @@ game ( rom ( name "Triple Play 2001 (USA, Europe).gbc" size 1048576 crc 74e04c07 sha1 326841285c54476c1fc231886a8cbd9c00e0195c ) ) +game ( + name "Trouballs (USA) (Beta) (2000-04-02)" + description "Trouballs (USA) (Beta) (2000-04-02)" + rom ( name "Trouballs (USA) (Beta) (2000-04-02).gbc" size 131072 crc 89b2f7da sha1 8a6efcf30643969a82fbbe24b986ad8a79a99a55 ) +) + game ( name "Trouballs (USA)" description "Trouballs (USA)" @@ -46553,7 +47075,7 @@ game ( game ( name "Ultimate Surfing (Europe)" description "Ultimate Surfing (Europe)" - rom ( name "Ultimate Surfing (Europe).gbc" size 1048576 crc b3398a9b sha1 15426e79cf1c0cafbf8972501b8478951023c21a ) + rom ( name "Ultimate Surfing (Europe).gbc" size 1048576 crc b3398a9b sha1 15426e79cf1c0cafbf8972501b8478951023c21a flags verified ) ) game ( @@ -47207,7 +47729,7 @@ game ( game ( name "Yu-Gi-Oh! - Monster Capsule GB (Japan) (SGB Enhanced) (GB Compatible)" description "Yu-Gi-Oh! - Monster Capsule GB (Japan) (SGB Enhanced) (GB Compatible)" - rom ( name "Yu-Gi-Oh! - Monster Capsule GB (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 1371e872 sha1 4ed5607dd83ebe7975c492b08d36870f8dd6e302 ) + rom ( name "Yu-Gi-Oh! - Monster Capsule GB (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 1371e872 sha1 4ed5607dd83ebe7975c492b08d36870f8dd6e302 flags verified ) ) game ( @@ -47501,8 +48023,8 @@ game ( clrmamepro ( name "Nintendo - Game Boy Color (Aftermarket)" description "Nintendo - Game Boy Color (Aftermarket)" - version 20240810-021134 - author "akubi, Arctic Circle System, Aringon, baldjared, Bent, BigFred, bikerspade, BitLooter, C. V. Reynolds, chillerecke, coraz, darthcloud, DeadSkullzJr, Densetsu, DeriLoko3, Flashfire42, foxe, fuzzball, Gefflon, gordonj, Hiccup, hking0036, InternalLoss, Just001Kim, kazumi213, Lesserkuma, Madeline, Money_114, NESBrew12, NGEfreak, nnssxx, norkmetnoil577, NovaAurora, omonim2007, PPLToast, Psychofox11, psykopat, rarenight, relax, Rifu, sCZther, SonGoku, Tauwasser, togemet2, UnlockerPT, Whovian9369, xprism, xuom2, zg" + version 20250329-080448 + author "akubi, Arctic Circle System, Aringon, baldjared, Bent, BigFred, bikerspade, BitLooter, buckwheat, C. V. Reynolds, chillerecke, ChuckD, coraz, darthcloud, DeadSkullzJr, Densetsu, DeriLoko3, Flashfire42, foxe, fuzzball, Gefflon, gordonj, Hiccup, hking0036, InternalLoss, Just001Kim, kazumi213, Lesserkuma, Madeline, Money_114, NESBrew12, NGEfreak, nnssxx, norkmetnoil577, NovaAurora, number, omonim2007, PPLToast, Psychofox11, psykopat, rarenight, relax, Rifu, sCZther, SonGoku, Tauwasser, togemet2, UnlockerPT, Whovian9369, xprism, xuom2, zg" homepage No-Intro url "https://www.no-intro.org" forcenodump required @@ -47878,6 +48400,12 @@ game ( rom ( name "Bygone Choices (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 4194304 crc dbaec8ec sha1 699b752e8a4a21b7f814f59e35260e0b82fc6795 ) ) +game ( + name "Cake Keeper (World) (Demo) (Aftermarket) (Unl)" + description "Cake Keeper (World) (Demo) (Aftermarket) (Unl)" + rom ( name "Cake Keeper (World) (Demo) (Aftermarket) (Unl).gbc" size 524288 crc 43346690 sha1 68f654f6193d26e2e1bc2eb80e867cb91eed5a94 ) +) + game ( name "Cancer Culture VS The Illuminati (World) (2023-05-03) (Demo) (GB Compatible) (Aftermarket) (Unl)" description "Cancer Culture VS The Illuminati (World) (2023-05-03) (Demo) (GB Compatible) (Aftermarket) (Unl)" @@ -48262,6 +48790,12 @@ game ( rom ( name "Disco Elysium - Game Boy Edition (World) (No Music) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc 7c0f52cc sha1 8bc16b70838f9245b7e37c6dbe57159b294fbf81 ) ) +game ( + name "DiveBlob (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "DiveBlob (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "DiveBlob (World) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 131072 crc c6847d36 sha1 59c2513239222cc5dea9984640f854e929403a58 ) +) + game ( name "Diver 94 (World) (GB Compatible) (Aftermarket) (Unl)" description "Diver 94 (World) (GB Compatible) (Aftermarket) (Unl)" @@ -48317,9 +48851,9 @@ game ( ) game ( - name "Dragonyhm (World) (v1.0.0) (Demo) (SGB Enhanced) (GB Compatible) (Aftermarket) (Unl)" - description "Dragonyhm (World) (v1.0.0) (Demo) (SGB Enhanced) (GB Compatible) (Aftermarket) (Unl)" - rom ( name "Dragonyhm (World) (v1.0.0) (Demo) (SGB Enhanced) (GB Compatible) (Aftermarket) (Unl).gbc" size 4194304 crc 96f9b7c0 sha1 2b74d076cabff92e5935d922e8f37202a723aaf0 flags verified ) + name "Dragonyhm (World) (Demo) (SGB Enhanced) (GB Compatible) (Aftermarket) (Unl)" + description "Dragonyhm (World) (Demo) (SGB Enhanced) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Dragonyhm (World) (Demo) (SGB Enhanced) (GB Compatible) (Aftermarket) (Unl).gbc" size 4194304 crc 96f9b7c0 sha1 2b74d076cabff92e5935d922e8f37202a723aaf0 flags verified ) ) game ( @@ -48328,6 +48862,12 @@ game ( rom ( name "Dragonyhm (World) (v1.1.0) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 4194304 crc cad7369d sha1 0058096f8fcbb2774f908811aaa0b1ee54111493 ) ) +game ( + name "Dream Shark - Part 1 (World) (Beta 2) (GB Compatible) (Aftermarket) (Unl)" + description "Dream Shark - Part 1 (World) (Beta 2) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Dream Shark - Part 1 (World) (Beta 2) (GB Compatible) (Aftermarket) (Unl).gbc" size 4194304 crc ea6c4527 sha1 9d11b4c12622961b7aa03d0c2d9ab57de4b4c541 ) +) + game ( name "Dungeon Crystal (World) (v1.1) (Demo) (GB Compatible) (Aftermarket) (Unl)" description "Dungeon Crystal (World) (v1.1) (Demo) (GB Compatible) (Aftermarket) (Unl)" @@ -48400,6 +48940,12 @@ game ( rom ( name "Exploits of Fingers Malone, The (World) (Aftermarket) (Unl).gbc" size 524288 crc 20fe84af sha1 18056b5b032955099c606651f26164de131e54a6 ) ) +game ( + name "Fallen Crown, The (World) (v0.60) (Demo) (Aftermarket) (Unl)" + description "Fallen Crown, The (World) (v0.60) (Demo) (Aftermarket) (Unl)" + rom ( name "Fallen Crown, The (World) (v0.60) (Demo) (Aftermarket) (Unl).gbc" size 4194304 crc ab96806a sha1 7c86eefe37b82d422327651b8761c1f8b0ca327d ) +) + game ( name "Far After (World) (v1.1) (Demo) (GB Compatible) (Aftermarket) (Unl)" description "Far After (World) (v1.1) (Demo) (GB Compatible) (Aftermarket) (Unl)" @@ -48988,6 +49534,12 @@ game ( rom ( name "Inspector Waffles Early Days (World) (v1.0.1) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 524288 crc fea1cdb9 sha1 ac10ca63f15ca78af9f7d0eabb85eef8733aef50 ) ) +game ( + name "Inspector Waffles Early Days (World) (v1.1.0) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Inspector Waffles Early Days (World) (v1.1.0) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Inspector Waffles Early Days (World) (v1.1.0) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 524288 crc a4a7156f sha1 b6801267b58914ab7cba5e47da68792694c829d4 ) +) + game ( name "Iron Cor - Stainless (World) (v2.0) (GB Compatible) (Aftermarket) (Unl)" description "Iron Cor - Stainless (World) (v2.0) (GB Compatible) (Aftermarket) (Unl)" @@ -49180,6 +49732,12 @@ game ( rom ( name "Lunatic Tower (World) (Aftermarket) (Unl).gbc" size 524288 crc 6edacbb3 sha1 ee706a5f6272bda950a5d80ffc071453a9880fb6 ) ) +game ( + name "Machine, The (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Machine, The (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Machine, The (World) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc 6be797f6 sha1 85ea55c9833b120e29bada2c3d66c05d84757edc flags verified ) +) + game ( name "Machine, The (World) (v1.1) (Demo) (GB Compatible) (Aftermarket) (Unl)" description "Machine, The (World) (v1.1) (Demo) (GB Compatible) (Aftermarket) (Unl)" @@ -49187,9 +49745,9 @@ game ( ) game ( - name "Machine, The (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" - description "Machine, The (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" - rom ( name "Machine, The (World) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc 5662865e sha1 a5ac99a4087bd5ea45417e5b4a7c9af10433911a ) + name "Machine, The (World) (Demo) (2022-06-04) (GB Compatible) (Aftermarket) (Unl)" + description "Machine, The (World) (Demo) (2022-06-04) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Machine, The (World) (Demo) (2022-06-04) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc 5662865e sha1 a5ac99a4087bd5ea45417e5b4a7c9af10433911a ) ) game ( @@ -49210,6 +49768,12 @@ game ( rom ( name "Magic & Legend - Time Knights (World) (Demo) (The Retro Room) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc c276843d sha1 852697a2952d2e7375b73aba877ebc17aa4f4179 ) ) +game ( + name "Magical Tale, A - Revoke (World) (Demo) (Aftermarket) (Unl)" + description "Magical Tale, A - Revoke (World) (Demo) (Aftermarket) (Unl)" + rom ( name "Magical Tale, A - Revoke (World) (Demo) (Aftermarket) (Unl).gbc" size 524288 crc 7faff113 sha1 bb5a03e73fda0c6080d6e312f8320ce164355b53 ) +) + game ( name "Magician's Curse, The (World) (Aftermarket) (Unl)" description "Magician's Curse, The (World) (Aftermarket) (Unl)" @@ -49246,6 +49810,12 @@ game ( rom ( name "Memory Mania Challenge (World) (v1.3) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc 50618f4b sha1 c8d74af4746313800df3a23c1ad01f931e0e2eca flags verified ) ) +game ( + name "Metamorphosis Collection (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Metamorphosis Collection (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Metamorphosis Collection (World) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc 3b83f236 sha1 6ca5c69ff59f9bf8dce3cfc765c26a0077f0452c ) +) + game ( name "Meteorite (World) (Aftermarket) (Unl)" description "Meteorite (World) (Aftermarket) (Unl)" @@ -49552,6 +50122,12 @@ game ( rom ( name "Pancho (World) (Aftermarket) (Unl).gbc" size 524288 crc d32d46c5 sha1 2f46974e6d92f5deb277818d95426a4507b3701d ) ) +game ( + name "Pandora's Blocks (World) (v1.8) (GB Compatible) (Aftermarket) (Unl)" + description "Pandora's Blocks (World) (v1.8) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Pandora's Blocks (World) (v1.8) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc a4e1aac6 sha1 71649d4d34b039bf37081ce2c31346f699f6c3b5 ) +) + game ( name "Panik!16 (World) (Aftermarket) (Unl)" description "Panik!16 (World) (Aftermarket) (Unl)" @@ -49577,9 +50153,9 @@ game ( ) game ( - name "Pine Creek (World) (En-US,Es-MX,Pt-BR) (Beta) (GB Compatible) (Aftermarket) (Unl)" - description "Pine Creek (World) (En-US,Es-MX,Pt-BR) (Beta) (GB Compatible) (Aftermarket) (Unl)" - rom ( name "Pine Creek (World) (En-US,Es-MX,Pt-BR) (Beta) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc 189aa999 sha1 2cf46a36502eaf22ac9572fe1136d369fcbe9e46 ) + name "Pine Creek (World) (En-US,Es-MX,Pt-BR) (v2.2) (Beta) (GB Compatible) (Aftermarket) (Unl)" + description "Pine Creek (World) (En-US,Es-MX,Pt-BR) (v2.2) (Beta) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Pine Creek (World) (En-US,Es-MX,Pt-BR) (v2.2) (Beta) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc 189aa999 sha1 2cf46a36502eaf22ac9572fe1136d369fcbe9e46 ) ) game ( @@ -49624,6 +50200,12 @@ game ( rom ( name "Pomape Castle (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc 9350c71d sha1 a0f449e270b72de56bfea151b5ac271c0c7e3c93 ) ) +game ( + name "Porklike (World) (v1.0.13) (Aftermarket) (Unl)" + description "Porklike (World) (v1.0.13) (Aftermarket) (Unl)" + rom ( name "Porklike (World) (v1.0.13) (Aftermarket) (Unl).gbc" size 65536 crc 2556b00f sha1 397e5cda2a50fc87dea62bde3ee4e823390d8f10 ) +) + game ( name "Postie (World) (v1.1) (GB Compatible) (Aftermarket) (Unl)" description "Postie (World) (v1.1) (GB Compatible) (Aftermarket) (Unl)" @@ -49654,6 +50236,12 @@ game ( rom ( name "Potbound (World) (2022-09-26) (Proto) (GBJam 10) (GB Compatible) (Aftermarket) (Unl).gbc" size 524288 crc d0d8386d sha1 fbddf81a1663388ff0efeeb9051e68e0c323c839 ) ) +game ( + name "POWA! (World) (Eu) (GB Compatible) (Aftermarket) (Unl)" + description "POWA! (World) (Eu) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "POWA! (World) (Eu) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc b05ca61f sha1 c1602809cad54e6b3022bf1ca1d743c749b70d52 ) +) + game ( name "POWA! (World) (En) (GB Compatible) (Aftermarket) (Unl)" description "POWA! (World) (En) (GB Compatible) (Aftermarket) (Unl)" @@ -49696,6 +50284,12 @@ game ( rom ( name "Proof of Destruction (World) (Aftermarket) (Unl).gbc" size 262144 crc fa528f88 sha1 5062f16346c31cae1b13e469cb610ce84d4eecb2 ) ) +game ( + name "PSY - Psychic Love Damage (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "PSY - Psychic Love Damage (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "PSY - Psychic Love Damage (World) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc fb58ec55 sha1 d27334c846fa05623f4bfaadfed74774e37bb0dc ) +) + game ( name "Purple Turtles (World) (Aftermarket) (Unl)" description "Purple Turtles (World) (Aftermarket) (Unl)" @@ -50038,6 +50632,12 @@ game ( rom ( name "Snooze (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc b246095f sha1 b7fdcc006c9c06dd391ee26d44b278d9d3793f6d ) ) +game ( + name "Snout (World) (Demo) (Aftermarket) (Unl)" + description "Snout (World) (Demo) (Aftermarket) (Unl)" + rom ( name "Snout (World) (Demo) (Aftermarket) (Unl).gbc" size 262144 crc 2c851e4f sha1 3f7140d3a42b15515ebc378ed0fcd69944c5a69d ) +) + game ( name "Sofia the Witch - Curse of the Monster Moon (World) (Demo) (Aftermarket) (Unl)" description "Sofia the Witch - Curse of the Monster Moon (World) (Demo) (Aftermarket) (Unl)" @@ -50146,6 +50746,12 @@ game ( rom ( name "Super Bunny Mission (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc c47330e2 sha1 6151286e93b2e2fb92f583371e650e36ab91aaf7 ) ) +game ( + name "Super Dassalo Land (World) (v1.1) (Aftermarket) (Unl)" + description "Super Dassalo Land (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Super Dassalo Land (World) (v1.1) (Aftermarket) (Unl).gbc" size 1048576 crc 07354ec8 sha1 6a8a520186aa4c9d78e2a880073b6b1e122d885d ) +) + game ( name "Super Gran (World) (Aftermarket) (Unl)" description "Super Gran (World) (Aftermarket) (Unl)" @@ -50308,6 +50914,12 @@ game ( rom ( name "Tools of Nexaura (World) (v0.4) (Demo) (SGB Enhanced) (GB Compatible) (Aftermarket) (Unl).gbc" size 524288 crc 50707e43 sha1 4a50327514554f075178f4d6342a4497d3122f31 ) ) +game ( + name "Tools of Nexaura (World) (Beta 4) (SGB Enhanced) (GB Compatible) (Aftermarket) (Unl)" + description "Tools of Nexaura (World) (Beta 4) (SGB Enhanced) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Tools of Nexaura (World) (Beta 4) (SGB Enhanced) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc 232c0f9d sha1 2f11a5843e0704f48c0755c176b73026e7538a38 ) +) + game ( name "Tower of Evil (World) (Aftermarket) (Unl)" description "Tower of Evil (World) (Aftermarket) (Unl)" @@ -50380,6 +50992,12 @@ game ( rom ( name "Unearthed (World) (v1.3) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc 29bb9238 sha1 39fbfb220e9be0120090f770249049b3f3583f64 ) ) +game ( + name "Unearthed Zero (World) (2021 Jam Version) (GB Compatible) (Aftermarket) (Unl)" + description "Unearthed Zero (World) (2021 Jam Version) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Unearthed Zero (World) (2021 Jam Version) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc ff854161 sha1 000e59118c9a5a59cd1b4f36a5c847dc9a9b88a4 ) +) + game ( name "Ungrateful Son, The (World) (v1.1) (GB Compatible) (Aftermarket) (Unl)" description "Ungrateful Son, The (World) (v1.1) (GB Compatible) (Aftermarket) (Unl)" @@ -50549,15 +51167,39 @@ game ( ) game ( - name "Witches and Butchers (World) (Demo 2) (GB Compatible) (Aftermarket) (Unl)" - description "Witches and Butchers (World) (Demo 2) (GB Compatible) (Aftermarket) (Unl)" - rom ( name "Witches and Butchers (World) (Demo 2) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc 321aecca sha1 90541cd5b08f6030b5b2fe4f1e0ac7c0b616e01e ) + name "Wink & the Broken Robot (World) (Beta 6) (GB Compatible) (Aftermarket) (Unl)" + description "Wink & the Broken Robot (World) (Beta 6) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Wink & the Broken Robot (World) (Beta 6) (GB Compatible) (Aftermarket) (Unl).gbc" size 4194304 crc 6c1d4498 sha1 f580ec863b7a92edc94187bc54bf47f3d254b6b7 ) ) game ( - name "Witches and Butchers (World) (Demo 4) (GB Compatible) (Aftermarket) (Unl)" - description "Witches and Butchers (World) (Demo 4) (GB Compatible) (Aftermarket) (Unl)" - rom ( name "Witches and Butchers (World) (Demo 4) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc 9b53a90f sha1 77210d3c0825d913f4aebf09a10897a84ca23881 ) + name "Winsley's Guardian (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Winsley's Guardian (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Winsley's Guardian (World) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc 709f175e sha1 f70606b2bd29fb6a891a9db3cbf3a4d6c76acd72 ) +) + +game ( + name "Witches and Butchers (World) (2023-08-26) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Witches and Butchers (World) (2023-08-26) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Witches and Butchers (World) (2023-08-26) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc 321aecca sha1 90541cd5b08f6030b5b2fe4f1e0ac7c0b616e01e ) +) + +game ( + name "Witches and Butchers (World) (2024-03-23) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Witches and Butchers (World) (2024-03-23) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Witches and Butchers (World) (2024-03-23) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc 9b53a90f sha1 77210d3c0825d913f4aebf09a10897a84ca23881 ) +) + +game ( + name "Witches and Butchers (World) (En,Es) (2024-08-14-2) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Witches and Butchers (World) (En,Es) (2024-08-14-2) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Witches and Butchers (World) (En,Es) (2024-08-14-2) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc 8afd1868 sha1 23cd422252583528f134f96d9249d3ef55bcd622 ) +) + +game ( + name "Witches and Butchers (World) (En,Es) (2024-08-15) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Witches and Butchers (World) (En,Es) (2024-08-15) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Witches and Butchers (World) (En,Es) (2024-08-15) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc 1e4b433b sha1 5e5a33f4e2109b6092f8bbc7ac6f2a5dd5a86a6d ) ) game ( From eb781d290b82dc5eda0533eaa9af318191f9773a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 30 Mar 2025 16:44:33 -0700 Subject: [PATCH 26/74] Core: Add SHA1 hashing for ROMs --- CHANGES | 1 + include/mgba-util/sha1.h | 33 +++++ include/mgba/core/core.h | 5 - include/mgba/core/interface.h | 6 + src/core/scripting.c | 3 + src/gb/core.c | 10 ++ src/gba/core.c | 14 ++ src/platform/qt/ROMInfo.cpp | 6 + src/platform/qt/ROMInfo.ui | 19 ++- src/util/CMakeLists.txt | 1 + src/util/sha1.c | 258 ++++++++++++++++++++++++++++++++++ src/util/test/hash.c | 92 ++++++++++++ 12 files changed, 442 insertions(+), 6 deletions(-) create mode 100644 include/mgba-util/sha1.h create mode 100644 src/util/sha1.c diff --git a/CHANGES b/CHANGES index 25485133c..2c4e2f0e2 100644 --- a/CHANGES +++ b/CHANGES @@ -37,6 +37,7 @@ Misc: - Core: Improve rumble emulation by averaging state over entire frame (fixes mgba.io/i/3232) - Core: Add MD5 hashing for ROMs - Core: Add support for specifying an arbitrary portable directory + - Core: Add SHA1 hashing for ROMs - FFmpeg: Add Ut Video option - GB: Prevent incompatible BIOSes from being used on differing models - GB Serialize: Add missing savestate support for MBC6 and NT (newer) diff --git a/include/mgba-util/sha1.h b/include/mgba-util/sha1.h new file mode 100644 index 000000000..28c97e75e --- /dev/null +++ b/include/mgba-util/sha1.h @@ -0,0 +1,33 @@ +/* Copyright (c) 2013-2025 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/. + * + * Based on https://github.com/clibs/sha1 + */ +#ifndef SHA1_H +#define SHA1_H + +#include + +CXX_GUARD_START + +struct SHA1Context { + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +}; + +void sha1Init(struct SHA1Context* ctx); +void sha1Update(struct SHA1Context* ctx, const void* input, size_t len); +void sha1Finalize(uint8_t digest[20], struct SHA1Context* ctx); + +void sha1Buffer(const void* input, size_t len, uint8_t* result); + +struct VFile; +bool sha1File(struct VFile* vf, uint8_t* result); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/core/core.h b/include/mgba/core/core.h index 64eda6627..c2951c6c2 100644 --- a/include/mgba/core/core.h +++ b/include/mgba/core/core.h @@ -28,11 +28,6 @@ enum mPlatform { mPLATFORM_GB = 1, }; -enum mCoreChecksumType { - mCHECKSUM_CRC32, - mCHECKSUM_MD5, -}; - struct mAudioBuffer; struct mCoreConfig; struct mCoreSync; diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h index 033047cac..2ceaf0bbd 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -188,6 +188,12 @@ struct mCoreRegisterInfo { enum mCoreRegisterType type; }; +enum mCoreChecksumType { + mCHECKSUM_CRC32, + mCHECKSUM_MD5, + mCHECKSUM_SHA1, +}; + CXX_GUARD_END #endif diff --git a/src/core/scripting.c b/src/core/scripting.c index 20659f0bc..c87829f75 100644 --- a/src/core/scripting.c +++ b/src/core/scripting.c @@ -353,6 +353,9 @@ static struct mScriptValue* _mScriptCoreChecksum(const struct mCore* core, int t case mCHECKSUM_MD5: size = 16; break; + case mCHECKSUM_SHA1: + size = 20; + break; } if (!size) { return &mScriptValueNull; diff --git a/src/gb/core.c b/src/gb/core.c index a97dd175f..9b0d491ea 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -24,6 +24,7 @@ #include #include #include +#include #include static const struct mCoreChannelInfo _GBVideoLayers[] = { @@ -539,6 +540,15 @@ static void _GBCoreChecksum(const struct mCore* core, void* data, enum mCoreChec md5Buffer("", 0, data); } break; + case mCHECKSUM_SHA1: + if (gb->romVf) { + sha1File(gb->romVf, data); + } else if (gb->memory.rom && gb->isPristine) { + sha1Buffer(gb->memory.rom, gb->pristineRomSize, data); + } else { + sha1Buffer("", 0, data); + } + break; } return; } diff --git a/src/gba/core.c b/src/gba/core.c index 65ec999f2..6c2ffe1f5 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -31,6 +31,7 @@ #include #endif #include +#include #include #include #include @@ -701,6 +702,19 @@ static void _GBACoreChecksum(const struct mCore* core, void* data, enum mCoreChe md5Buffer("", 0, data); } break; + case mCHECKSUM_SHA1: + if (gba->romVf) { + sha1File(gba->romVf, data); + } else if (gba->mbVf) { + sha1File(gba->mbVf, data); + } else if (gba->memory.rom && gba->isPristine) { + sha1Buffer(gba->memory.rom, gba->pristineRomSize, data); + } else if (gba->memory.rom) { + sha1Buffer(gba->memory.rom, gba->memory.romSize, data); + } else { + sha1Buffer("", 0, data); + } + break; } return; } diff --git a/src/platform/qt/ROMInfo.cpp b/src/platform/qt/ROMInfo.cpp index 25aafeda8..40760dccf 100644 --- a/src/platform/qt/ROMInfo.cpp +++ b/src/platform/qt/ROMInfo.cpp @@ -25,6 +25,7 @@ ROMInfo::ROMInfo(std::shared_ptr controller, QWidget* parent) #endif uint32_t crc32 = 0; uint8_t md5[16]{}; + uint8_t sha1[20]{}; CoreController::Interrupter interrupter(controller); mCore* core = controller->thread()->core; @@ -41,6 +42,7 @@ ROMInfo::ROMInfo(std::shared_ptr controller, QWidget* parent) core->checksum(core, &crc32, mCHECKSUM_CRC32); core->checksum(core, &md5, mCHECKSUM_MD5); + core->checksum(core, &sha1, mCHECKSUM_SHA1); m_ui.size->setText(QString::number(core->romSize(core)) + tr(" bytes")); @@ -69,6 +71,10 @@ ROMInfo::ROMInfo(std::shared_ptr controller, QWidget* parent) md5[0x0], md5[0x1], md5[0x2], md5[0x3], md5[0x4], md5[0x5], md5[0x6], md5[0x7], md5[0x8], md5[0x9], md5[0xA], md5[0xB], md5[0xC], md5[0xD], md5[0xE], md5[0xF])); + m_ui.sha1->setText(QString::asprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + sha1[ 0], sha1[ 1], sha1[ 2], sha1[ 3], sha1[ 4], sha1[ 5], sha1[ 6], sha1[ 7], sha1[ 8], sha1[ 9], + sha1[10], sha1[11], sha1[12], sha1[13], sha1[14], sha1[15], sha1[16], sha1[17], sha1[18], sha1[19])); + QString savePath = controller->savePath(); if (!savePath.isEmpty()) { m_ui.savefile->setText(savePath); diff --git a/src/platform/qt/ROMInfo.ui b/src/platform/qt/ROMInfo.ui index a4a179ca8..f6ba67398 100644 --- a/src/platform/qt/ROMInfo.ui +++ b/src/platform/qt/ROMInfo.ui @@ -95,13 +95,30 @@ + + + SHA-1 + + + + + + + {SHA1} + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + Save file: - + {SAVEFILE} diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 2f4ba2dcd..8009f5ad2 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -7,6 +7,7 @@ set(BASE_SOURCE_FILES gbk-table.c hash.c md5.c + sha1.c string.c table.c vector.c diff --git a/src/util/sha1.c b/src/util/sha1.c new file mode 100644 index 000000000..7e238e8bc --- /dev/null +++ b/src/util/sha1.c @@ -0,0 +1,258 @@ +/* Copyright (c) 2013-2025 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/. + * + * Based on https://github.com/clibs/sha1 + * + * Test Vectors (from FIPS PUB 180-1) + * "abc" + * A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D + * "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + * 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 + * A million repetitions of "a" + * 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F + */ +#include + +#include + +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#define SHA1HANDSOFF + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#ifndef __BIG_ENDIAN__ +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#else +#define blk0(i) block->l[i] +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + +/* Hash a single 512-bit block. This is the core of the algorithm. */ +static void sha1Transform(uint32_t state[5], const uint8_t buffer[64]) { + uint32_t a, b, c, d, e; + + typedef union { + unsigned char c[64]; + uint32_t l[16]; + } CHAR64LONG16; + +#ifdef SHA1HANDSOFF + CHAR64LONG16 block[1]; /* use array to appear as a pointer */ + + memcpy(block, buffer, 64); +#else + /* The following had better never be used because it causes the + * pointer-to-const buffer to be cast into a pointer to non-const. + * And the result is written through. I threw a "const" in, hoping + * this will cause a diagnostic. + */ + CHAR64LONG16 *block = (const CHAR64LONG16 *) buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a, b, c, d, e, 0); + R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); + R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); + R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); + R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); + R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); + R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); + R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); + R0(a, b, c, d, e, 15); + R1(e, a, b, c, d, 16); + R1(d, e, a, b, c, 17); + R1(c, d, e, a, b, 18); + R1(b, c, d, e, a, 19); + R2(a, b, c, d, e, 20); + R2(e, a, b, c, d, 21); + R2(d, e, a, b, c, 22); + R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); + R2(a, b, c, d, e, 25); + R2(e, a, b, c, d, 26); + R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); + R2(b, c, d, e, a, 29); + R2(a, b, c, d, e, 30); + R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); + R2(c, d, e, a, b, 33); + R2(b, c, d, e, a, 34); + R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); + R2(d, e, a, b, c, 37); + R2(c, d, e, a, b, 38); + R2(b, c, d, e, a, 39); + R3(a, b, c, d, e, 40); + R3(e, a, b, c, d, 41); + R3(d, e, a, b, c, 42); + R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); + R3(a, b, c, d, e, 45); + R3(e, a, b, c, d, 46); + R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); + R3(b, c, d, e, a, 49); + R3(a, b, c, d, e, 50); + R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); + R3(c, d, e, a, b, 53); + R3(b, c, d, e, a, 54); + R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); + R3(d, e, a, b, c, 57); + R3(c, d, e, a, b, 58); + R3(b, c, d, e, a, 59); + R4(a, b, c, d, e, 60); + R4(e, a, b, c, d, 61); + R4(d, e, a, b, c, 62); + R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); + R4(a, b, c, d, e, 65); + R4(e, a, b, c, d, 66); + R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); + R4(b, c, d, e, a, 69); + R4(a, b, c, d, e, 70); + R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); + R4(c, d, e, a, b, 73); + R4(b, c, d, e, a, 74); + R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); + R4(d, e, a, b, c, 77); + R4(c, d, e, a, b, 78); + R4(b, c, d, e, a, 79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +#ifdef SHA1HANDSOFF + memset(block, '\0', sizeof(block)); +#endif +} + +/* shaInit - Initialize new context */ +void sha1Init(struct SHA1Context* context) { + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +/* Run your data through this. */ +void sha1Update(struct SHA1Context* context, const void* data, size_t len) { + size_t i; + size_t j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) { + ++context->count[1]; + } + context->count[1] += (len >> 29); + j = (j >> 3) & 63; + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64 - j)); + sha1Transform(context->state, context->buffer); + for (; i + 63 < len; i += 64) { + sha1Transform(context->state, &((uint8_t*) data)[i]); + } + j = 0; + } else { + i = 0; + } + memcpy(&context->buffer[j], &((uint8_t*) data)[i], len - i); +} + +/* Add padding and return the message digest. */ +void sha1Finalize(uint8_t digest[20], struct SHA1Context* context) { + unsigned i; + uint8_t finalcount[8]; + uint8_t c; + + for (i = 0; i < 8; ++i) { + finalcount[i] = (uint8_t) ((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ + } + c = 0200; + sha1Update(context, &c, 1); + while ((context->count[0] & 504) != 448) { + c = 0000; + sha1Update(context, &c, 1); + } + sha1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; ++i) { + digest[i] = (uint8_t) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); + } + /* Wipe variables */ + memset(context, '\0', sizeof(*context)); + memset(&finalcount, '\0', sizeof(finalcount)); +} + +void sha1Buffer(const void* input, size_t len, uint8_t* result) { + struct SHA1Context ctx; + size_t i; + + sha1Init(&ctx); + for (i = 0; i + 63 < len; i += 64) { + sha1Update(&ctx, &((const uint8_t*) input)[i], 64); + } + for (; i < len; ++i) { + sha1Update(&ctx, &((const uint8_t*) input)[i], 1); + } + sha1Finalize(result, &ctx); +} + +bool sha1File(struct VFile* vf, uint8_t* result) { + struct SHA1Context ctx; + uint8_t buffer[2048]; + sha1Init(&ctx); + + ssize_t read; + ssize_t position = vf->seek(vf, 0, SEEK_CUR); + if (vf->seek(vf, 0, SEEK_SET) < 0) { + return false; + } + while ((read = vf->read(vf, buffer, sizeof(buffer))) > 0) { + sha1Update(&ctx, buffer, read); + } + vf->seek(vf, position, SEEK_SET); + if (read < 0) { + return false; + } + sha1Finalize(result, &ctx); + return true; +} diff --git a/src/util/test/hash.c b/src/util/test/hash.c index 6d014435b..27bb8a0bd 100644 --- a/src/util/test/hash.c +++ b/src/util/test/hash.c @@ -7,6 +7,7 @@ #include #include +#include M_TEST_DEFINE(emptyCrc32) { uint8_t buffer[1] = {0}; @@ -115,6 +116,92 @@ M_TEST_DEFINE(twoBlockMd5) { }), 16); } +M_TEST_DEFINE(emptySha1) { + uint8_t buffer[1] = {0}; + uint8_t digest[20] = {0}; + sha1Buffer(buffer, 0, digest); + assert_memory_equal(digest, ((uint8_t[]) { + 0xDA, 0x39, 0xA3, 0xEE, 0x5E, 0x6B, 0x4B, 0x0D, 0x32, 0x55, + 0xBF, 0xEF, 0x95, 0x60, 0x18, 0x90, 0xAF, 0xD8, 0x07, 0x09 + }), 16); +} + +M_TEST_DEFINE(newlineSha1) { + uint8_t buffer[1] = { '\n' }; + uint8_t digest[20] = {0}; + sha1Buffer(buffer, 1, digest); + assert_memory_equal(digest, ((uint8_t[]) { + 0xAD, 0xC8, 0x3B, 0x19, 0xE7, 0x93, 0x49, 0x1B, 0x1C, 0x6E, + 0xA0, 0xFD, 0x8B, 0x46, 0xCD, 0x9F, 0x32, 0xE5, 0x92, 0xFC + }), 20); +} + +M_TEST_DEFINE(fullBlockSha1) { + uint8_t buffer[64] = { + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + }; + uint8_t digest[20] = {0}; + sha1Buffer(buffer, 64, digest); + assert_memory_equal(digest, ((uint8_t[]) { + 0xCB, 0x4D, 0xD3, 0xDA, 0xCA, 0x2D, 0x6F, 0x25, 0x44, 0xBC, + 0x0D, 0xAA, 0x6B, 0xEB, 0xB7, 0x8A, 0xED, 0x0B, 0xD0, 0x34 + }), 20); +} + +M_TEST_DEFINE(overflowBlockSha1) { + uint8_t buffer[65] = { + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x0a, + }; + uint8_t digest[20] = {0}; + sha1Buffer(buffer, 65, digest); + assert_memory_equal(digest, ((uint8_t[]) { + 0xA3, 0x96, 0x68, 0x5E, 0xF7, 0x73, 0x87, 0x13, 0x2C, 0x43, + 0x64, 0x42, 0x2D, 0x16, 0x65, 0x39, 0x65, 0x6F, 0xB8, 0x93 + }), 20); +} + +M_TEST_DEFINE(twoBlockSha1) { + uint8_t buffer[128] = { + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + }; + uint8_t digest[20] = {0}; + sha1Buffer(buffer, 128, digest); + assert_memory_equal(digest, ((uint8_t[]) { + 0xFF, 0xB5, 0xE5, 0xD9, 0x6E, 0x19, 0x71, 0x4F, 0xFE, 0xF6, + 0x0A, 0xC8, 0x74, 0x9E, 0xCA, 0xEF, 0xBE, 0xC9, 0xD2, 0x95 + }), 20); +} + M_TEST_SUITE_DEFINE(Hashes, cmocka_unit_test(emptyCrc32), cmocka_unit_test(newlineCrc32), @@ -127,4 +214,9 @@ M_TEST_SUITE_DEFINE(Hashes, cmocka_unit_test(fullBlockMd5), cmocka_unit_test(overflowBlockMd5), cmocka_unit_test(twoBlockMd5), + cmocka_unit_test(emptySha1), + cmocka_unit_test(newlineSha1), + cmocka_unit_test(fullBlockSha1), + cmocka_unit_test(overflowBlockSha1), + cmocka_unit_test(twoBlockSha1), ) From 0e42f9d5613dfe81d122c70c086e91c055215864 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 30 Mar 2025 20:58:05 -0700 Subject: [PATCH 27/74] Util: Bring up MD5 and SHA-1 library and No-Intro querying --- include/mgba/core/library.h | 2 + src/core/library.c | 44 ++++++++++++++++++-- src/feature/sqlite3/no-intro.c | 73 ++++++++++++++++++++++++++++++---- src/feature/sqlite3/no-intro.h | 2 + src/platform/qt/ROMInfo.cpp | 52 ++++++++++++++---------- 5 files changed, 141 insertions(+), 32 deletions(-) diff --git a/include/mgba/core/library.h b/include/mgba/core/library.h index ededd5463..cfdd4cc60 100644 --- a/include/mgba/core/library.h +++ b/include/mgba/core/library.h @@ -24,6 +24,8 @@ struct mLibraryEntry { enum mPlatform platform; size_t filesize; uint32_t crc32; + uint8_t md5[16]; + uint8_t sha1[20]; }; #ifdef USE_SQLITE3 diff --git a/src/core/library.c b/src/core/library.c index 1c92a0cbc..638f49880 100644 --- a/src/core/library.c +++ b/src/core/library.c @@ -34,6 +34,8 @@ struct mLibrary { "CASE WHEN :useSize THEN roms.size = :size ELSE 1 END AND " \ "CASE WHEN :usePlatform THEN roms.platform = :platform ELSE 1 END AND " \ "CASE WHEN :useCrc32 THEN roms.crc32 = :crc32 ELSE 1 END AND " \ + "CASE WHEN :useMd5 THEN roms.md5 = :md5 ELSE 1 END AND " \ + "CASE WHEN :useSha1 THEN roms.sha1 = :sha1 ELSE 1 END AND " \ "CASE WHEN :useInternalCode THEN roms.internalCode = :internalCode ELSE 1 END" #define CONSTRAINTS \ @@ -58,6 +60,20 @@ static void _bindConstraints(sqlite3_stmt* statement, const struct mLibraryEntry sqlite3_bind_int(statement, index, constraints->crc32); } + if (memcmp(constraints->md5, &(uint8_t[16]) {0}, 16) != 0) { + useIndex = sqlite3_bind_parameter_index(statement, ":useMd5"); + index = sqlite3_bind_parameter_index(statement, ":md5"); + sqlite3_bind_int(statement, useIndex, 1); + sqlite3_bind_blob(statement, index, constraints->md5, 16, NULL); + } + + if (memcmp(constraints->sha1, &(uint8_t[20]) {0}, 20) != 0) { + useIndex = sqlite3_bind_parameter_index(statement, ":useSha1"); + index = sqlite3_bind_parameter_index(statement, ":sha1"); + sqlite3_bind_int(statement, useIndex, 1); + sqlite3_bind_blob(statement, index, constraints->sha1, 20, NULL); + } + if (constraints->filesize) { useIndex = sqlite3_bind_parameter_index(statement, ":useSize"); index = sqlite3_bind_parameter_index(statement, ":size"); @@ -139,6 +155,8 @@ struct mLibrary* mLibraryLoad(const char* path) { "\n CONSTRAINT location UNIQUE (path, rootid)" "\n );" "\n CREATE INDEX IF NOT EXISTS crc32 ON roms (crc32);" + "\n CREATE INDEX IF NOT EXISTS md5 ON roms (md5);" + "\n CREATE INDEX IF NOT EXISTS sha1 ON roms (sha1);" "\n INSERT OR IGNORE INTO version (tname, version) VALUES ('version', 1);" "\n INSERT OR IGNORE INTO version (tname, version) VALUES ('roots', 1);" "\n INSERT OR IGNORE INTO version (tname, version) VALUES ('roms', 1);" @@ -152,7 +170,7 @@ struct mLibrary* mLibraryLoad(const char* path) { goto error; } - static const char insertRom[] = "INSERT INTO roms (crc32, size, internalCode, platform) VALUES (:crc32, :size, :internalCode, :platform);"; + static const char insertRom[] = "INSERT INTO roms (crc32, md5, sha1, size, internalCode, platform) VALUES (:crc32, :md5, :sha1, :size, :internalCode, :platform);"; if (sqlite3_prepare_v2(library->db, insertRom, -1, &library->insertRom, NULL)) { goto error; } @@ -297,6 +315,8 @@ bool _mLibraryAddEntry(struct mLibrary* library, const char* filename, const cha snprintf(entry.internalCode, sizeof(entry.internalCode), "%s-%s", info.system, info.code); strlcpy(entry.internalTitle, info.title, sizeof(entry.internalTitle)); core->checksum(core, &entry.crc32, mCHECKSUM_CRC32); + core->checksum(core, &entry.md5, mCHECKSUM_MD5); + core->checksum(core, &entry.sha1, mCHECKSUM_SHA1); entry.platform = core->platform(core); entry.title = NULL; entry.base = base; @@ -402,10 +422,28 @@ size_t mLibraryGetEntries(struct mLibrary* library, struct mLibraryListing* out, int i; for (i = 0; i < nCols; ++i) { const char* colName = sqlite3_column_name(library->select, i); - if (strcmp(colName, "crc32") == 0) { + if (strcmp(colName, "sha1") == 0) { + const void* buf = sqlite3_column_blob(library->select, i); + if (buf && sqlite3_column_bytes(library->select, i) == sizeof(entry->sha1)) { + memcpy(entry->sha1, buf, sizeof(entry->sha1)); + struct NoIntroGame game; + if (!entry->title && NoIntroDBLookupGameBySHA1(library->gameDB, entry->sha1, &game)) { + entry->title = strdup(game.name); + } + } + } else if (strcmp(colName, "md5") == 0) { + const void* buf = sqlite3_column_blob(library->select, i); + if (buf && sqlite3_column_bytes(library->select, i) == sizeof(entry->md5)) { + memcpy(entry->md5, buf, sizeof(entry->md5)); + struct NoIntroGame game; + if (!entry->title && NoIntroDBLookupGameByMD5(library->gameDB, entry->md5, &game)) { + entry->title = strdup(game.name); + } + } + } else if (strcmp(colName, "crc32") == 0) { entry->crc32 = sqlite3_column_int(library->select, i); struct NoIntroGame game; - if (NoIntroDBLookupGameByCRC(library->gameDB, entry->crc32, &game)) { + if (!entry->title && NoIntroDBLookupGameByCRC(library->gameDB, entry->crc32, &game)) { entry->title = strdup(game.name); } } else if (strcmp(colName, "platform") == 0) { diff --git a/src/feature/sqlite3/no-intro.c b/src/feature/sqlite3/no-intro.c index 463a2fe5e..8b0474c8c 100644 --- a/src/feature/sqlite3/no-intro.c +++ b/src/feature/sqlite3/no-intro.c @@ -14,6 +14,8 @@ struct NoIntroDB { sqlite3* db; sqlite3_stmt* crc32; + sqlite3_stmt* md5; + sqlite3_stmt* sha1; }; struct NoIntroDB* NoIntroDBLoad(const char* path) { @@ -54,8 +56,18 @@ struct NoIntroDB* NoIntroDBLoad(const char* path) { goto error; } - static const char selectRom[] = "SELECT * FROM games JOIN roms USING (gid) WHERE roms.crc32 = ?;"; - if (sqlite3_prepare_v2(db->db, selectRom, -1, &db->crc32, NULL)) { + static const char selectCrc32[] = "SELECT games.name, roms.name, size, crc32, md5, sha1, flags FROM games JOIN roms USING (gid) WHERE roms.crc32 = ?;"; + if (sqlite3_prepare_v2(db->db, selectCrc32, -1, &db->crc32, NULL)) { + goto error; + } + + static const char selectMd5[] = "SELECT games.name, roms.name, size, crc32, md5, sha1, flags FROM games JOIN roms USING (gid) WHERE roms.md5 = ?;"; + if (sqlite3_prepare_v2(db->db, selectMd5, -1, &db->md5, NULL)) { + goto error; + } + + static const char selectSha1[] = "SELECT games.name, roms.name, size, crc32, md5, sha1, flags FROM games JOIN roms USING (gid) WHERE roms.sha1 = ?;"; + if (sqlite3_prepare_v2(db->db, selectSha1, -1, &db->sha1, NULL)) { goto error; } @@ -300,12 +312,34 @@ void NoIntroDBDestroy(struct NoIntroDB* db) { if (db->crc32) { sqlite3_finalize(db->crc32); } + if (db->md5) { + sqlite3_finalize(db->md5); + } + if (db->sha1) { + sqlite3_finalize(db->sha1); + } if (db->db) { sqlite3_close(db->db); } free(db); } +void _extractGame(sqlite3_stmt* stmt, struct NoIntroGame* game) { + game->name = (const char*) sqlite3_column_text(stmt, 0); + game->romName = (const char*) sqlite3_column_text(stmt, 1); + game->size = sqlite3_column_int(stmt, 2); + game->crc32 = sqlite3_column_int(stmt, 3); + const void* buf = sqlite3_column_blob(stmt, 4); + if (buf && sqlite3_column_bytes(stmt, 4) == sizeof(game->md5)) { + memcpy(game->md5, buf, sizeof(game->md5)); + } + buf = sqlite3_column_blob(stmt, 5); + if (buf && sqlite3_column_bytes(stmt, 5) == sizeof(game->sha1)) { + memcpy(game->sha1, buf, sizeof(game->sha1)); + } + game->verified = sqlite3_column_int(stmt, 6); +} + bool NoIntroDBLookupGameByCRC(const struct NoIntroDB* db, uint32_t crc32, struct NoIntroGame* game) { if (!db) { return false; @@ -316,11 +350,34 @@ bool NoIntroDBLookupGameByCRC(const struct NoIntroDB* db, uint32_t crc32, struct if (sqlite3_step(db->crc32) != SQLITE_ROW) { return false; } - game->name = (const char*) sqlite3_column_text(db->crc32, 1); - game->romName = (const char*) sqlite3_column_text(db->crc32, 3); - game->size = sqlite3_column_int(db->crc32, 4); - game->crc32 = sqlite3_column_int(db->crc32, 5); - // TODO: md5/sha1 - game->verified = sqlite3_column_int(db->crc32, 8); + _extractGame(db->crc32, game); + return true; +} + +bool NoIntroDBLookupGameByMD5(const struct NoIntroDB* db, const uint8_t* md5, struct NoIntroGame* game) { + if (!db) { + return false; + } + sqlite3_clear_bindings(db->md5); + sqlite3_reset(db->md5); + sqlite3_bind_blob(db->md5, 1, md5, 16, NULL); + if (sqlite3_step(db->md5) != SQLITE_ROW) { + return false; + } + _extractGame(db->md5, game); + return true; +} + +bool NoIntroDBLookupGameBySHA1(const struct NoIntroDB* db, const uint8_t* sha1, struct NoIntroGame* game) { + if (!db) { + return false; + } + sqlite3_clear_bindings(db->sha1); + sqlite3_reset(db->sha1); + sqlite3_bind_blob(db->sha1, 1, sha1, 20, NULL); + if (sqlite3_step(db->sha1) != SQLITE_ROW) { + return false; + } + _extractGame(db->sha1, game); return true; } diff --git a/src/feature/sqlite3/no-intro.h b/src/feature/sqlite3/no-intro.h index 648da8042..ba3bc128f 100644 --- a/src/feature/sqlite3/no-intro.h +++ b/src/feature/sqlite3/no-intro.h @@ -27,6 +27,8 @@ struct NoIntroDB* NoIntroDBLoad(const char* path); bool NoIntroDBLoadClrMamePro(struct NoIntroDB* db, struct VFile* vf); void NoIntroDBDestroy(struct NoIntroDB* db); bool NoIntroDBLookupGameByCRC(const struct NoIntroDB* db, uint32_t crc32, struct NoIntroGame* game); +bool NoIntroDBLookupGameByMD5(const struct NoIntroDB* db, const uint8_t* md5, struct NoIntroGame* game); +bool NoIntroDBLookupGameBySHA1(const struct NoIntroDB* db, const uint8_t* sha1, struct NoIntroGame* game); CXX_GUARD_END diff --git a/src/platform/qt/ROMInfo.cpp b/src/platform/qt/ROMInfo.cpp index 40760dccf..43be10fe1 100644 --- a/src/platform/qt/ROMInfo.cpp +++ b/src/platform/qt/ROMInfo.cpp @@ -48,32 +48,42 @@ ROMInfo::ROMInfo(std::shared_ptr controller, QWidget* parent) if (crc32) { m_ui.crc->setText(QString::number(crc32, 16)); -#ifdef USE_SQLITE3 - if (db) { - NoIntroGame game{}; - if (NoIntroDBLookupGameByCRC(db, crc32, &game)) { - m_ui.name->setText(game.name); - } else { - m_ui.name->setText(tr("(unknown)")); - } - } else { - m_ui.name->setText(tr("(no database present)")); - } -#else - m_ui.name->hide(); -#endif } else { m_ui.crc->setText(tr("(unknown)")); - m_ui.name->setText(tr("(unknown)")); } - m_ui.md5->setText(QString::asprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - md5[0x0], md5[0x1], md5[0x2], md5[0x3], md5[0x4], md5[0x5], md5[0x6], md5[0x7], - md5[0x8], md5[0x9], md5[0xA], md5[0xB], md5[0xC], md5[0xD], md5[0xE], md5[0xF])); + if (memcmp(md5, &(const uint8_t[16]) {}, 16) != 0) { + m_ui.md5->setText(QString::asprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + md5[0x0], md5[0x1], md5[0x2], md5[0x3], md5[0x4], md5[0x5], md5[0x6], md5[0x7], + md5[0x8], md5[0x9], md5[0xA], md5[0xB], md5[0xC], md5[0xD], md5[0xE], md5[0xF])); + } else { + m_ui.md5->setText(tr("(unknown)")); + } - m_ui.sha1->setText(QString::asprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - sha1[ 0], sha1[ 1], sha1[ 2], sha1[ 3], sha1[ 4], sha1[ 5], sha1[ 6], sha1[ 7], sha1[ 8], sha1[ 9], - sha1[10], sha1[11], sha1[12], sha1[13], sha1[14], sha1[15], sha1[16], sha1[17], sha1[18], sha1[19])); + if (memcmp(sha1, &(const uint8_t[20]) {}, 20) != 0) { + m_ui.sha1->setText(QString::asprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + sha1[ 0], sha1[ 1], sha1[ 2], sha1[ 3], sha1[ 4], sha1[ 5], sha1[ 6], sha1[ 7], sha1[ 8], sha1[ 9], + sha1[10], sha1[11], sha1[12], sha1[13], sha1[14], sha1[15], sha1[16], sha1[17], sha1[18], sha1[19])); + } else { + m_ui.sha1->setText(tr("(unknown)")); + } + +#ifdef USE_SQLITE3 + if (db) { + NoIntroGame game{}; + if (memcmp(sha1, &(const uint8_t[20]) {}, 20) != 0 && NoIntroDBLookupGameBySHA1(db, sha1, &game)) { + m_ui.name->setText(game.name); + } else if (crc32 && NoIntroDBLookupGameByCRC(db, crc32, &game)) { + m_ui.name->setText(game.name); + } else { + m_ui.name->setText(tr("(unknown)")); + } + } else { + m_ui.name->setText(tr("(no database present)")); + } +#else + m_ui.name->hide(); +#endif QString savePath = controller->savePath(); if (!savePath.isEmpty()) { From 5d7b8756296a632dcc03ae7077698693fac3716e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 30 Mar 2025 21:12:35 -0700 Subject: [PATCH 28/74] Scripting: Add SHA1 checksum constant --- src/script/stdlib.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/script/stdlib.c b/src/script/stdlib.c index 8fc1b1404..caa1e8a31 100644 --- a/src/script/stdlib.c +++ b/src/script/stdlib.c @@ -131,6 +131,7 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) { mScriptContextExportConstants(context, "CHECKSUM", (struct mScriptKVPair[]) { mSCRIPT_CONSTANT_PAIR(mCHECKSUM, CRC32), mSCRIPT_CONSTANT_PAIR(mCHECKSUM, MD5), + mSCRIPT_CONSTANT_PAIR(mCHECKSUM, SHA1), mSCRIPT_KV_SENTINEL }); #ifdef M_CORE_GBA From a9262868fc689dfda1d3c161553b0bc5eac553ee Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 30 Mar 2025 21:26:14 -0700 Subject: [PATCH 29/74] Qt: Use less questionable way of checking for zeroing --- src/platform/qt/ROMInfo.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/ROMInfo.cpp b/src/platform/qt/ROMInfo.cpp index 43be10fe1..e2bd037e5 100644 --- a/src/platform/qt/ROMInfo.cpp +++ b/src/platform/qt/ROMInfo.cpp @@ -15,6 +15,15 @@ using namespace QGBA; +template bool isZeroed(const uint8_t* mem) { + for (size_t i = 0; i < N; ++i) { + if (mem[i]) { + return false; + } + } + return true; +} + ROMInfo::ROMInfo(std::shared_ptr controller, QWidget* parent) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) { @@ -52,7 +61,7 @@ ROMInfo::ROMInfo(std::shared_ptr controller, QWidget* parent) m_ui.crc->setText(tr("(unknown)")); } - if (memcmp(md5, &(const uint8_t[16]) {}, 16) != 0) { + if (!isZeroed<16>(md5)) { m_ui.md5->setText(QString::asprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", md5[0x0], md5[0x1], md5[0x2], md5[0x3], md5[0x4], md5[0x5], md5[0x6], md5[0x7], md5[0x8], md5[0x9], md5[0xA], md5[0xB], md5[0xC], md5[0xD], md5[0xE], md5[0xF])); @@ -60,7 +69,7 @@ ROMInfo::ROMInfo(std::shared_ptr controller, QWidget* parent) m_ui.md5->setText(tr("(unknown)")); } - if (memcmp(sha1, &(const uint8_t[20]) {}, 20) != 0) { + if (!isZeroed<20>(sha1)) { m_ui.sha1->setText(QString::asprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", sha1[ 0], sha1[ 1], sha1[ 2], sha1[ 3], sha1[ 4], sha1[ 5], sha1[ 6], sha1[ 7], sha1[ 8], sha1[ 9], sha1[10], sha1[11], sha1[12], sha1[13], sha1[14], sha1[15], sha1[16], sha1[17], sha1[18], sha1[19])); From 93d248859f3dd1f656d4efe4805be46788b89fbf Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 30 Mar 2025 21:33:16 -0700 Subject: [PATCH 30/74] Qt: Add missing use of isZeroed --- src/platform/qt/ROMInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/ROMInfo.cpp b/src/platform/qt/ROMInfo.cpp index e2bd037e5..3cd8c3f01 100644 --- a/src/platform/qt/ROMInfo.cpp +++ b/src/platform/qt/ROMInfo.cpp @@ -80,7 +80,7 @@ ROMInfo::ROMInfo(std::shared_ptr controller, QWidget* parent) #ifdef USE_SQLITE3 if (db) { NoIntroGame game{}; - if (memcmp(sha1, &(const uint8_t[20]) {}, 20) != 0 && NoIntroDBLookupGameBySHA1(db, sha1, &game)) { + if (!isZeroed<20>(sha1) && NoIntroDBLookupGameBySHA1(db, sha1, &game)) { m_ui.name->setText(game.name); } else if (crc32 && NoIntroDBLookupGameByCRC(db, crc32, &game)) { m_ui.name->setText(game.name); From ba8671d1d6a55c122186382625b2f4ecc6a0fe57 Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Tue, 28 Jun 2022 14:10:47 -0500 Subject: [PATCH 31/74] Library: rewrite Qt library frontend --- src/platform/qt/CMakeLists.txt | 37 +- src/platform/qt/library/LibraryController.cpp | 231 ++++++---- src/platform/qt/library/LibraryController.h | 76 ++-- src/platform/qt/library/LibraryEntry.cpp | 51 +++ src/platform/qt/library/LibraryEntry.h | 47 ++ src/platform/qt/library/LibraryModel.cpp | 417 ++++++++++++++++++ src/platform/qt/library/LibraryModel.h | 81 ++++ src/platform/qt/library/LibraryTree.cpp | 212 --------- src/platform/qt/library/LibraryTree.h | 61 --- src/platform/qt/resources.qrc | 18 + src/platform/qt/test/library.cpp | 200 +++++++++ src/platform/qt/test/spanset.cpp | 61 +++ src/platform/qt/utils.cpp | 42 ++ src/platform/qt/utils.h | 17 + 14 files changed, 1143 insertions(+), 408 deletions(-) create mode 100644 src/platform/qt/library/LibraryEntry.cpp create mode 100644 src/platform/qt/library/LibraryEntry.h create mode 100644 src/platform/qt/library/LibraryModel.cpp create mode 100644 src/platform/qt/library/LibraryModel.h delete mode 100644 src/platform/qt/library/LibraryTree.cpp delete mode 100644 src/platform/qt/library/LibraryTree.h create mode 100644 src/platform/qt/test/library.cpp create mode 100644 src/platform/qt/test/spanset.cpp diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 9858fddb0..560a6fa44 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -220,6 +220,11 @@ set(GB_SRC GBOverride.cpp PrinterView.cpp) +set(TEST_QT_spanset_SRC + test/spanset.cpp + utils.cpp + VFileDevice.cpp) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt${QT_V}widgets${QT_V}") set(AUDIO_SRC) @@ -274,8 +279,15 @@ if(USE_SQLITE3) list(APPEND SOURCE_FILES ArchiveInspector.cpp library/LibraryController.cpp - library/LibraryGrid.cpp - library/LibraryTree.cpp) + library/LibraryEntry.cpp + library/LibraryModel.cpp) + + set(TEST_QT_library_SRC + library/LibraryEntry.cpp + library/LibraryModel.cpp + test/library.cpp + utils.cpp + VFileDevice.cpp) endif() if(USE_DISCORD_RPC) @@ -496,7 +508,7 @@ if(APPLE) set_source_files_properties("${COREAUDIO}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) set_source_files_properties("${QTAVFSERVICE}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) endif() - + install(CODE " include(BundleUtilities) set(BU_CHMOD_BUNDLE_ITEMS ON) @@ -539,6 +551,25 @@ elseif(WIN32) endif() debug_strip(${BINARY_NAME}-qt) + +if(BUILD_SUITE) + enable_testing() + find_package(${QT}Test) + if(${QT}Test_FOUND) + get_property(ALL_TESTS DIRECTORY PROPERTY VARIABLES) + list(FILTER ALL_TESTS INCLUDE REGEX "^TEST_QT_.*_SRC$") + foreach(TEST_SRC ${ALL_TESTS}) + string(REGEX REPLACE "^TEST_QT_(.*)_SRC$" "\\1" TEST_NAME ${TEST_SRC}) + add_executable(test-qt-${TEST_NAME} WIN32 ${${TEST_SRC}}) + target_link_libraries(test-qt-${TEST_NAME} ${PLATFORM_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES} ${QT}::Test) + set_target_properties(test-qt-${TEST_NAME} PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES};${OS_DEFINES};${QT_DEFINES}" COMPILE_OPTIONS "${FEATURE_FLAGS}") + add_test(platform-qt-${TEST_NAME} test-qt-${TEST_NAME}) + endforeach() + else() + message("${QT}Test not found") + endif() +endif() + install(TARGETS ${BINARY_NAME}-qt RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-qt BUNDLE DESTINATION ${APPDIR} COMPONENT ${BINARY_NAME}-qt) diff --git a/src/platform/qt/library/LibraryController.cpp b/src/platform/qt/library/LibraryController.cpp index 2a1ba1546..ed78ce410 100644 --- a/src/platform/qt/library/LibraryController.cpp +++ b/src/platform/qt/library/LibraryController.cpp @@ -8,39 +8,16 @@ #include "ConfigController.h" #include "GBAApp.h" -#include "LibraryGrid.h" -#include "LibraryTree.h" +#include "LibraryModel.h" + +#include +#include +#include +#include +#include using namespace QGBA; -LibraryEntry::LibraryEntry(const mLibraryEntry* entry) - : base(entry->base) - , filename(entry->filename) - , fullpath(QString("%1/%2").arg(entry->base, entry->filename)) - , title(entry->title) - , internalTitle(entry->internalTitle) - , internalCode(entry->internalCode) - , platform(entry->platform) - , filesize(entry->filesize) - , crc32(entry->crc32) -{ -} - -void AbstractGameList::addEntry(const LibraryEntry& item) { - addEntries({item}); -} - -void AbstractGameList::updateEntry(const LibraryEntry& item) { - updateEntries({item}); -} - -void AbstractGameList::removeEntry(const QString& item) { - removeEntries({item}); -} -void AbstractGameList::setShowFilename(bool showFilename) { - m_showFilename = showFilename; -} - LibraryController::LibraryController(QWidget* parent, const QString& path, ConfigController* config) : QStackedWidget(parent) , m_config(config) @@ -55,14 +32,53 @@ LibraryController::LibraryController(QWidget* parent, const QString& path, Confi mLibraryAttachGameDB(m_library.get(), GBAApp::app()->gameDB()); - m_libraryTree = std::make_unique(this); - addWidget(m_libraryTree->widget()); + m_libraryModel = new LibraryModel(this); - m_libraryGrid = std::make_unique(this); - addWidget(m_libraryGrid->widget()); + m_treeView = new QTreeView(this); + addWidget(m_treeView); + m_treeModel = new QSortFilterProxyModel(this); + m_treeModel->setSourceModel(m_libraryModel); + m_treeModel->setSortRole(Qt::EditRole); + m_treeView->setModel(m_treeModel); + m_treeView->setSortingEnabled(true); + m_treeView->setAlternatingRowColors(true); - m_currentStyle = LibraryStyle::STYLE_TREE; // Make sure setViewStyle does something - setViewStyle(LibraryStyle::STYLE_LIST); + m_listView = new QListView(this); + addWidget(m_listView); + m_listModel = new QSortFilterProxyModel(this); + m_listModel->setSourceModel(m_libraryModel); + m_listModel->setSortRole(Qt::EditRole); + m_listView->setModel(m_listModel); + + QObject::connect(m_treeView, &QAbstractItemView::activated, this, &LibraryController::startGame); + QObject::connect(m_listView, &QAbstractItemView::activated, this, &LibraryController::startGame); + QObject::connect(m_treeView->header(), &QHeaderView::sortIndicatorChanged, this, &LibraryController::sortChanged); + + m_expandThrottle.setInterval(100); + m_expandThrottle.setSingleShot(true); + QObject::connect(&m_expandThrottle, &QTimer::timeout, this, qOverload<>(&LibraryController::resizeTreeView)); + QObject::connect(m_libraryModel, &QAbstractItemModel::modelReset, &m_expandThrottle, qOverload<>(&QTimer::start)); + QObject::connect(m_libraryModel, &QAbstractItemModel::rowsInserted, &m_expandThrottle, qOverload<>(&QTimer::start)); + + LibraryStyle libraryStyle = LibraryStyle(m_config->getOption("libraryStyle", int(LibraryStyle::STYLE_LIST)).toInt()); + // Make sure setViewStyle does something + if (libraryStyle == LibraryStyle::STYLE_TREE) { + m_currentStyle = LibraryStyle::STYLE_LIST; + } else { + m_currentStyle = LibraryStyle::STYLE_TREE; + } + setViewStyle(libraryStyle); + + QVariant librarySort = m_config->getQtOption("librarySort"); + QVariant librarySortOrder = m_config->getQtOption("librarySortOrder"); + if (librarySort.isNull() || !librarySort.canConvert()) { + librarySort = 0; + } + if (librarySortOrder.isNull() || !librarySortOrder.canConvert()) { + librarySortOrder = Qt::AscendingOrder; + } + m_treeModel->sort(librarySort.toInt(), librarySortOrder.value()); + m_listModel->sort(0, Qt::AscendingOrder); refresh(); } @@ -73,32 +89,59 @@ void LibraryController::setViewStyle(LibraryStyle newStyle) { if (m_currentStyle == newStyle) { return; } - m_currentStyle = newStyle; - - AbstractGameList* newCurrentList = nullptr; - if (newStyle == LibraryStyle::STYLE_LIST || newStyle == LibraryStyle::STYLE_TREE) { - newCurrentList = m_libraryTree.get(); - } else { - newCurrentList = m_libraryGrid.get(); + QString selected; + if (m_currentView) { + QModelIndex selectedIndex = m_currentView->selectionModel()->currentIndex(); + if (selectedIndex.isValid()) { + selected = selectedIndex.data(LibraryModel::FullPathRole).toString(); + } } - newCurrentList->selectEntry(selectedEntry().fullpath); - newCurrentList->setViewStyle(newStyle); - setCurrentWidget(newCurrentList->widget()); - m_currentList = newCurrentList; + + m_currentStyle = newStyle; + m_libraryModel->setTreeMode(newStyle == LibraryStyle::STYLE_TREE); + + QAbstractItemView* newView = m_listView; + if (newStyle == LibraryStyle::STYLE_LIST || newStyle == LibraryStyle::STYLE_TREE) { + newView = m_treeView; + } + + setCurrentWidget(newView); + m_currentView = newView; + selectEntry(selected); +} + +void LibraryController::sortChanged(int column, Qt::SortOrder order) { + m_config->setQtOption("librarySort", column); + m_config->setQtOption("librarySortOrder", order); } void LibraryController::selectEntry(const QString& fullpath) { - if (!m_currentList) { + if (!m_currentView) { return; } - m_currentList->selectEntry(fullpath); + QModelIndex index = m_libraryModel->index(fullpath); + + // If the model is proxied in the current view, map the index to the proxy + QAbstractProxyModel* proxy = qobject_cast(m_currentView->model()); + if (proxy) { + index = proxy->mapFromSource(index); + } + + if (index.isValid()) { + m_currentView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current); + } } LibraryEntry LibraryController::selectedEntry() { - if (!m_currentList) { + if (!m_currentView) { return {}; } - return m_entries.value(m_currentList->selectedEntry()); + QModelIndex index = m_currentView->selectionModel()->currentIndex(); + if (!index.isValid()) { + return {}; + } + QString fullpath = index.data(LibraryModel::FullPathRole).toString(); + return m_libraryModel->entry(fullpath); } VFile* LibraryController::selectedVFile() { @@ -149,42 +192,34 @@ void LibraryController::refresh() { setDisabled(true); - QHash removedEntries = m_entries; - QHash updatedEntries; + QSet removedEntries = QSet::fromList(m_knownGames.keys()); + QList updatedEntries; QList newEntries; mLibraryListing listing; mLibraryListingInit(&listing, 0); mLibraryGetEntries(m_library.get(), &listing, 0, 0, nullptr); for (size_t i = 0; i < mLibraryListingSize(&listing); i++) { - LibraryEntry entry = mLibraryListingGetConstPointer(&listing, i); - if (!m_entries.contains(entry.fullpath)) { + const mLibraryEntry* entry = mLibraryListingGetConstPointer(&listing, i); + uint64_t checkHash = LibraryEntry::checkHash(entry); + QString fullpath = QStringLiteral("%1/%2").arg(entry->base, entry->filename); + if (!m_knownGames.contains(fullpath)) { newEntries.append(entry); - } else { - updatedEntries[entry.fullpath] = entry; + } else if (checkHash != m_knownGames[fullpath]) { + updatedEntries.append(entry); } - m_entries[entry.fullpath] = entry; - removedEntries.remove(entry.fullpath); + removedEntries.remove(fullpath); + m_knownGames[fullpath] = checkHash; } // Check for entries that were removed - for (QString& path : removedEntries.keys()) { - m_entries.remove(path); + for (const QString& path : removedEntries) { + m_knownGames.remove(path); } - if (!removedEntries.size() && !newEntries.size()) { - m_libraryTree->updateEntries(updatedEntries.values()); - m_libraryGrid->updateEntries(updatedEntries.values()); - } else if (!updatedEntries.size()) { - m_libraryTree->removeEntries(removedEntries.keys()); - m_libraryGrid->removeEntries(removedEntries.keys()); - - m_libraryTree->addEntries(newEntries); - m_libraryGrid->addEntries(newEntries); - } else { - m_libraryTree->resetEntries(m_entries.values()); - m_libraryGrid->resetEntries(m_entries.values()); - } + m_libraryModel->removeEntries(removedEntries.toList()); + m_libraryModel->updateEntries(updatedEntries); + m_libraryModel->addEntries(newEntries); for (size_t i = 0; i < mLibraryListingSize(&listing); ++i) { mLibraryEntryFree(mLibraryListingGetPointer(&listing, i)); @@ -201,7 +236,7 @@ void LibraryController::selectLastBootedGame() { return; } const QString lastfile = m_config->getMRU().first(); - if (m_entries.contains(lastfile)) { + if (m_knownGames.contains(lastfile)) { selectEntry(lastfile); } } @@ -213,16 +248,50 @@ void LibraryController::loadDirectory(const QString& dir, bool recursive) { mLibraryLoadDirectory(library.get(), dir.toUtf8().constData(), recursive); m_libraryJob.testAndSetOrdered(libraryJob, -1); } + void LibraryController::setShowFilename(bool showFilename) { if (showFilename == m_showFilename) { return; } m_showFilename = showFilename; - if (m_libraryGrid) { - m_libraryGrid->setShowFilename(m_showFilename); - } - if (m_libraryTree) { - m_libraryTree->setShowFilename(m_showFilename); - } + m_libraryModel->setShowFilename(m_showFilename); refresh(); } + +void LibraryController::showEvent(QShowEvent*) { + resizeTreeView(false); +} + +void LibraryController::resizeEvent(QResizeEvent*) { + resizeTreeView(false); +} + +void LibraryController::resizeTreeView(bool expand) { + if (expand) { + m_treeView->expandAll(); + } + + int viewportWidth = m_treeView->viewport()->width(); + int totalWidth = m_treeView->header()->sectionSizeHint(LibraryModel::MAX_COLUMN); + for (int column = 0; column < LibraryModel::MAX_COLUMN; column++) { + totalWidth += m_treeView->columnWidth(column); + } + if (totalWidth < viewportWidth) { + totalWidth = 0; + for (int column = 0; column <= LibraryModel::MAX_COLUMN; column++) { + m_treeView->resizeColumnToContents(column); + totalWidth += m_treeView->columnWidth(column); + } + } + if (totalWidth > viewportWidth) { + int locationWidth = m_treeView->columnWidth(LibraryModel::COL_LOCATION); + if (locationWidth > 100) { + int newLocationWidth = m_treeView->viewport()->width() - (totalWidth - locationWidth); + if (newLocationWidth < 100) { + newLocationWidth = 100; + } + m_treeView->setColumnWidth(LibraryModel::COL_LOCATION, newLocationWidth); + totalWidth = totalWidth - locationWidth + newLocationWidth; + } + } +} diff --git a/src/platform/qt/library/LibraryController.h b/src/platform/qt/library/LibraryController.h index 1b5d06572..8a14f3a4b 100644 --- a/src/platform/qt/library/LibraryController.h +++ b/src/platform/qt/library/LibraryController.h @@ -12,15 +12,21 @@ #include #include #include +#include #include +#include "LibraryEntry.h" + +class QAbstractItemView; +class QListView; +class QSortFilterProxyModel; +class QTreeView; + namespace QGBA { -// Predefinitions -class LibraryGrid; -class LibraryTree; class ConfigController; +class LibraryModel; enum class LibraryStyle { STYLE_LIST = 0, @@ -29,50 +35,6 @@ enum class LibraryStyle { STYLE_ICON }; -struct LibraryEntry { - LibraryEntry() {} - LibraryEntry(const mLibraryEntry* entry); - - bool isNull() const { return fullpath.isNull(); } - - QString displayTitle() const { return title.isNull() ? filename : title; } - - QString base; - QString filename; - QString fullpath; - QString title; - QByteArray internalTitle; - QByteArray internalCode; - mPlatform platform; - size_t filesize; - uint32_t crc32; - - bool operator==(const LibraryEntry& other) const { return other.fullpath == fullpath; } -}; - -class AbstractGameList { -public: - virtual QString selectedEntry() = 0; - virtual void selectEntry(const QString& fullpath) = 0; - - virtual void setViewStyle(LibraryStyle newStyle) = 0; - - virtual void resetEntries(const QList&) = 0; - virtual void addEntries(const QList&) = 0; - virtual void updateEntries(const QList&) = 0; - virtual void removeEntries(const QList&) = 0; - - virtual void addEntry(const LibraryEntry&); - virtual void updateEntry(const LibraryEntry&); - virtual void removeEntry(const QString&); - virtual void setShowFilename(bool showFilename); - - virtual QWidget* widget() = 0; - -protected: - bool m_showFilename = false; -}; - class LibraryController final : public QStackedWidget { Q_OBJECT @@ -103,6 +65,13 @@ signals: private slots: void refresh(); + void sortChanged(int column, Qt::SortOrder order); + inline void resizeTreeView() { resizeTreeView(true); } + void resizeTreeView(bool expand); + +protected: + void showEvent(QShowEvent*) override; + void resizeEvent(QResizeEvent*) override; private: void loadDirectory(const QString&, bool recursive = true); // Called on separate thread @@ -110,14 +79,19 @@ private: ConfigController* m_config = nullptr; std::shared_ptr m_library; QAtomicInteger m_libraryJob = -1; - QHash m_entries; LibraryStyle m_currentStyle; - AbstractGameList* m_currentList = nullptr; - std::unique_ptr m_libraryGrid; - std::unique_ptr m_libraryTree; + QHash m_knownGames; + LibraryModel* m_libraryModel; + QSortFilterProxyModel* m_listModel; + QSortFilterProxyModel* m_treeModel; + QListView* m_listView; + QTreeView* m_treeView; + QAbstractItemView* m_currentView = nullptr; bool m_showFilename = false; + + QTimer m_expandThrottle; }; } diff --git a/src/platform/qt/library/LibraryEntry.cpp b/src/platform/qt/library/LibraryEntry.cpp new file mode 100644 index 000000000..27feb64e8 --- /dev/null +++ b/src/platform/qt/library/LibraryEntry.cpp @@ -0,0 +1,51 @@ +/* Copyright (c) 2014-2017 waddlesplash + * Copyright (c) 2013-2021 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 "LibraryEntry.h" + +#include + +using namespace QGBA; + +static inline uint64_t checkHash(size_t filesize, uint32_t crc32) { + return (uint64_t(filesize) << 32) ^ ((crc32 + 1ULL) * (uint32_t(filesize) + 1ULL)); +} + +LibraryEntry::LibraryEntry(const mLibraryEntry* entry) + : base(entry->base) + , filename(entry->filename) + , fullpath(QString("%1/%2").arg(entry->base, entry->filename)) + , title(entry->title) + , internalTitle(entry->internalTitle) + , internalCode(entry->internalCode) + , platform(entry->platform) + , filesize(entry->filesize) + , crc32(entry->crc32) +{ +} + +bool LibraryEntry::isNull() const { + return fullpath.isNull(); +} + +QString LibraryEntry::displayTitle(bool showFilename) const { + if (showFilename || title.isNull()) { + return filename; + } + return title; +} + +bool LibraryEntry::operator==(const LibraryEntry& other) const { + return other.fullpath == fullpath; +} + +uint64_t LibraryEntry::checkHash() const { + return ::checkHash(filesize, crc32); +} + +uint64_t LibraryEntry::checkHash(const mLibraryEntry* entry) { + return ::checkHash(entry->filesize, entry->crc32); +} diff --git a/src/platform/qt/library/LibraryEntry.h b/src/platform/qt/library/LibraryEntry.h new file mode 100644 index 000000000..2a733db4a --- /dev/null +++ b/src/platform/qt/library/LibraryEntry.h @@ -0,0 +1,47 @@ +/* Copyright (c) 2014-2017 waddlesplash + * Copyright (c) 2013-2021 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/. */ +#pragma once + +#include +#include +#include + +#include + +struct mLibraryEntry; + +namespace QGBA { + +struct LibraryEntry { + LibraryEntry() = default; + LibraryEntry(const LibraryEntry&) = default; + LibraryEntry(LibraryEntry&&) = default; + LibraryEntry(const mLibraryEntry* entry); + + bool isNull() const; + + QString displayTitle(bool showFilename = false) const; + + QString base; + QString filename; + QString fullpath; + QString title; + QByteArray internalTitle; + QByteArray internalCode; + mPlatform platform; + size_t filesize; + uint32_t crc32; + + LibraryEntry& operator=(const LibraryEntry&) = default; + LibraryEntry& operator=(LibraryEntry&&) = default; + bool operator==(const LibraryEntry& other) const; + + uint64_t checkHash() const; + static uint64_t checkHash(const mLibraryEntry* entry); +}; + +}; diff --git a/src/platform/qt/library/LibraryModel.cpp b/src/platform/qt/library/LibraryModel.cpp new file mode 100644 index 000000000..bc08228c1 --- /dev/null +++ b/src/platform/qt/library/LibraryModel.cpp @@ -0,0 +1,417 @@ +/* Copyright (c) 2013-2022 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 "LibraryModel.h" + +#include "../utils.h" + +#include +#include +#include +#include +#include + +#include + +using namespace QGBA; + +static const QStringList iconSets{ + "GBA", + "GBC", + "GB", + // "DS", +}; + +LibraryModel::LibraryModel(QObject* parent) + : QAbstractItemModel(parent) + , m_treeMode(false) + , m_showFilename(false) +{ + for (const QString& platform : iconSets) { + QString pathTemplate = QStringLiteral(":/res/%1-icon%2").arg(platform); + QIcon icon; + icon.addFile(pathTemplate.arg("-256.png"), QSize(256, 256)); + icon.addFile(pathTemplate.arg("-128.png"), QSize(128, 128)); + icon.addFile(pathTemplate.arg("-32.png"), QSize(32, 32)); + icon.addFile(pathTemplate.arg("-24.png"), QSize(24, 24)); + icon.addFile(pathTemplate.arg("-16.png"), QSize(16, 16)); + // This will silently and harmlessly fail if QSvgIconEngine isn't compiled in. + icon.addFile(pathTemplate.arg(".svg")); + m_icons[platform.toLower()] = icon; + } +} + +bool LibraryModel::treeMode() const { + return m_treeMode; +} + +void LibraryModel::setTreeMode(bool tree) { + if (m_treeMode == tree) { + return; + } + beginResetModel(); + m_treeMode = tree; + endResetModel(); +} + +bool LibraryModel::showFilename() const { + return m_showFilename; +} + +void LibraryModel::setShowFilename(bool show) { + if (m_showFilename == show) { + return; + } + m_showFilename = show; + if (m_treeMode) { + int numPaths = m_pathOrder.size(); + for (int i = 0; i < numPaths; i++) { + QModelIndex parent = index(i, 0); + emit dataChanged(index(0, 0, parent), index(m_pathIndex[m_pathOrder[i]].size() - 1, 0)); + } + } else { + emit dataChanged(index(0, 0), index(rowCount() - 1, 0)); + } +} + +void LibraryModel::resetEntries(const QList& items) { + beginResetModel(); + blockSignals(true); + + m_games.clear(); + m_pathOrder.clear(); + m_pathIndex.clear(); + addEntriesList(items); + + blockSignals(false); + endResetModel(); +} + +void LibraryModel::addEntries(const QList& items) { + if (items.isEmpty()) { + return; + } else if (m_treeMode) { + addEntriesTree(items); + } else { + addEntriesList(items); + } +} + +void LibraryModel::addEntryInternal(const LibraryEntry& item) { + m_gameIndex[item.fullpath] = m_games.size(); + m_games << item; + if (!m_pathIndex.contains(item.base)) { + m_pathOrder << item.base; + } + m_pathIndex[item.base] << &m_games.back(); +} + +void LibraryModel::addEntriesList(const QList& items) { + beginInsertRows(QModelIndex(), m_games.size(), m_games.size() + items.size() - 1); + for (const LibraryEntry& item : items) { + addEntryInternal(item); + } + endInsertRows(); +} + +void LibraryModel::addEntriesTree(const QList& items) { + QHash> byPath; + QHash> newPaths; + for (const LibraryEntry& item : items) { + if (m_pathIndex.contains(item.base)) { + byPath[item.base] << &item; + } else { + newPaths[item.base] << &item; + } + } + + if (newPaths.size() > 0) { + beginInsertRows(QModelIndex(), m_pathIndex.size(), m_pathIndex.size() + newPaths.size() - 1); + for (const QString& base : newPaths.keys()) { + for (const LibraryEntry* item : newPaths[base]) { + addEntryInternal(*item); + } + } + endInsertRows(); + } + + for (const QString& base : byPath.keys()) { + QList& pathItems = m_pathIndex[base]; + QList& newItems = byPath[base]; + + QModelIndex parent = indexForPath(base); + beginInsertRows(parent, pathItems.size(), pathItems.size() + newItems.size() - 1); + for (const LibraryEntry* item : newItems) { + addEntryInternal(*item); + } + endInsertRows(); + } +} + +void LibraryModel::updateEntries(const QList& items) { + QHash updatedSpans; + for (const LibraryEntry& item : items) { + QModelIndex idx = index(item.fullpath); + Q_ASSERT(idx.isValid()); + int pos = m_gameIndex.value(item.fullpath, -1); + Q_ASSERT(pos >= 0); + m_games[pos] = item; + updatedSpans[idx.parent()].add(pos); + } + for (auto iter = updatedSpans.begin(); iter != updatedSpans.end(); iter++) { + QModelIndex parent = iter.key(); + SpanSet spans = iter.value(); + spans.merge(); + for (const SpanSet::Span& span : spans.spans) { + QModelIndex topLeft = index(span.left, 0, parent); + QModelIndex bottomRight = index(span.right, MAX_COLUMN, parent); + emit dataChanged(topLeft, bottomRight); + } + } +} + +void LibraryModel::removeEntries(const QList& items) { + SpanSet removedRootSpans; + QHash removedTreeSpans; + int firstModifiedIndex = m_games.size(); + for (const QString& item : items) { + int pos = m_gameIndex.value(item, -1); + Q_ASSERT(pos >= 0); + if (pos < firstModifiedIndex) { + firstModifiedIndex = pos; + } + LibraryEntry* entry = &m_games[pos]; + QModelIndex parent = indexForPath(entry->base); + Q_ASSERT(!m_treeMode || parent.isValid()); + QList& pathItems = m_pathIndex[entry->base]; + removedTreeSpans[entry->base].add(pathItems.indexOf(entry)); + if (!m_treeMode) { + removedRootSpans.add(pos); + } + m_gameIndex.remove(item); + } + for (const QString& base : removedTreeSpans.keys()) { + SpanSet& spanSet = removedTreeSpans[base]; + spanSet.merge(); + QList& pathIndex = m_pathIndex[base]; + if (spanSet.spans.size() == 1) { + SpanSet::Span span = spanSet.spans[0]; + if (span.left == 0 && span.right == pathIndex.size() - 1) { + if (m_treeMode) { + removedRootSpans.add(m_pathOrder.indexOf(base)); + } else { + m_pathIndex.remove(base); + m_pathOrder.removeAll(base); + } + continue; + } + } + QModelIndex parent = indexForPath(base); + spanSet.sort(true); + for (const SpanSet::Span& span : spanSet.spans) { + if (m_treeMode) { + beginRemoveRows(parent, span.left, span.right); + for (int i = span.left; i <= span.right; i++) { + + } + } + pathIndex.erase(pathIndex.begin() + span.left, pathIndex.begin() + span.right + 1); + if (m_treeMode) { + endRemoveRows(); + } + } + } + removedRootSpans.merge(); + removedRootSpans.sort(true); + for (const SpanSet::Span& span : removedRootSpans.spans) { + beginRemoveRows(QModelIndex(), span.left, span.right); + if (m_treeMode) { + for (int i = span.right; i >= span.left; i--) { + QString base = m_pathOrder.takeAt(i); + m_pathIndex.remove(base); + } + } else { + m_games.erase(m_games.begin() + span.left, m_games.begin() + span.right + 1); + } + endRemoveRows(); + } + for (int i = m_games.count() - 1; i >= firstModifiedIndex; i--) { + m_gameIndex[m_games[i].fullpath] = i; + } +} + +QModelIndex LibraryModel::index(const QString& game) const { + int pos = m_gameIndex.value(game, -1); + if (pos < 0) { + return QModelIndex(); + } + if (m_treeMode) { + const LibraryEntry& entry = m_games[pos]; + return createIndex(m_pathIndex[entry.base].indexOf(&entry), 0, m_pathOrder.indexOf(entry.base)); + } + return createIndex(pos, 0); +} + +QModelIndex LibraryModel::index(int row, int column, const QModelIndex& parent) const { + if (!parent.isValid()) { + return createIndex(row, column, quintptr(0)); + } + if (!m_treeMode || parent.internalId() || parent.column() != 0) { + return QModelIndex(); + } + return createIndex(row, column, parent.row() + 1); +} + +QModelIndex LibraryModel::parent(const QModelIndex& child) const { + if (!child.isValid() || child.internalId() == 0) { + return QModelIndex(); + } + return createIndex(child.internalId() - 1, 0, quintptr(0)); +} + +int LibraryModel::columnCount(const QModelIndex& parent) const { + if (!parent.isValid() || (parent.column() == 0 && !parent.parent().isValid())) { + return MAX_COLUMN + 1; + } + return 0; +} + +int LibraryModel::rowCount(const QModelIndex& parent) const { + if (parent.isValid()) { + if (m_treeMode) { + if (parent.row() < 0 || parent.row() >= m_pathOrder.size() || parent.column() != 0) { + return 0; + } + return m_pathIndex[m_pathOrder[parent.row()]].size(); + } + return 0; + } + if (m_treeMode) { + return m_pathOrder.size(); + } + return m_games.size(); +} + +QVariant LibraryModel::folderData(const QModelIndex& index, int role) const +{ + // Precondition: index and role must have already been validated + if (role == Qt::DecorationRole) { + return qApp->style()->standardIcon(QStyle::SP_DirOpenIcon); + } + if (role == FullPathRole || (index.column() == COL_LOCATION && role != Qt::DisplayRole)) { + return m_pathOrder[index.row()]; + } + if (index.column() == COL_NAME) { + QString path = m_pathOrder[index.row()]; + return path.section('/', -1); + } + return QVariant(); +} + +QVariant LibraryModel::data(const QModelIndex& index, int role) const { + if (role != Qt::DisplayRole && + role != Qt::EditRole && + role != Qt::ToolTipRole && + role != Qt::DecorationRole && + role != Qt::TextAlignmentRole && + role != FullPathRole) { + return QVariant(); + } + if (!checkIndex(index)) { + return QVariant(); + } + if (role == Qt::ToolTipRole && index.column() > COL_LOCATION) { + return QVariant(); + } + if (role == Qt::DecorationRole && index.column() != COL_NAME) { + return QVariant(); + } + if (role == Qt::TextAlignmentRole) { + return index.column() == COL_SIZE ? (int)(Qt::AlignTrailing | Qt::AlignVCenter) : (int)(Qt::AlignLeading | Qt::AlignVCenter); + } + const LibraryEntry* entry = nullptr; + if (m_treeMode) { + if (!index.parent().isValid()) { + return folderData(index, role); + } + QString path = m_pathOrder[index.parent().row()]; + entry = m_pathIndex[path][index.row()]; + } else if (!index.parent().isValid() && index.row() < m_games.size()) { + entry = &m_games[index.row()]; + } + if (entry) { + if (role == FullPathRole) { + return entry->fullpath; + } + switch (index.column()) { + case COL_NAME: + if (role == Qt::DecorationRole) { + return m_icons.value(nicePlatformFormat(entry->platform), qApp->style()->standardIcon(QStyle::SP_FileIcon)); + } + return entry->displayTitle(m_showFilename); + case COL_LOCATION: + return QDir::toNativeSeparators(entry->base); + case COL_PLATFORM: + return nicePlatformFormat(entry->platform); + case COL_SIZE: + return (role == Qt::DisplayRole) ? QVariant(niceSizeFormat(entry->filesize)) : QVariant(int(entry->filesize)); + case COL_CRC32: + return (role == Qt::DisplayRole) ? QVariant(QStringLiteral("%0").arg(entry->crc32, 8, 16, QChar('0'))) : QVariant(entry->crc32); + } + } + return QVariant(); +} + +QVariant LibraryModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + switch (section) { + case COL_NAME: + return QApplication::translate("LibraryTree", "Name", nullptr); + case COL_LOCATION: + return QApplication::translate("LibraryTree", "Location", nullptr); + case COL_PLATFORM: + return QApplication::translate("LibraryTree", "Platform", nullptr); + case COL_SIZE: + return QApplication::translate("LibraryTree", "Size", nullptr); + case COL_CRC32: + return QApplication::translate("LibraryTree", "CRC32", nullptr); + }; + } + return QVariant(); +} + +QModelIndex LibraryModel::indexForPath(const QString& path) { + int pos = m_pathOrder.indexOf(path); + if (pos < 0) { + pos = m_pathOrder.size(); + beginInsertRows(QModelIndex(), pos, pos); + m_pathOrder << path; + m_pathIndex[path] = QList(); + endInsertRows(); + } + if (!m_treeMode) { + return QModelIndex(); + } + return index(pos, 0, QModelIndex()); +} + +QModelIndex LibraryModel::indexForPath(const QString& path) const { + if (!m_treeMode) { + return QModelIndex(); + } + int pos = m_pathOrder.indexOf(path); + if (pos < 0) { + return QModelIndex(); + } + return index(pos, 0, QModelIndex()); +} + +LibraryEntry LibraryModel::entry(const QString& game) const { + int pos = m_gameIndex.value(game, -1); + if (pos < 0) { + return {}; + } + return m_games[pos]; +} diff --git a/src/platform/qt/library/LibraryModel.h b/src/platform/qt/library/LibraryModel.h new file mode 100644 index 000000000..db220392f --- /dev/null +++ b/src/platform/qt/library/LibraryModel.h @@ -0,0 +1,81 @@ +/* Copyright (c) 2013-2022 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/. */ +#pragma once + +#include +#include +#include + +#include + +#include "LibraryEntry.h" + +class QTreeView; + +namespace QGBA { + +class LibraryModel final : public QAbstractItemModel { +Q_OBJECT + +public: + enum Columns { + COL_NAME = 0, + COL_LOCATION = 1, + COL_PLATFORM = 2, + COL_SIZE = 3, + COL_CRC32 = 4, + MAX_COLUMN = 4, + }; + + enum ItemDataRole { + FullPathRole = Qt::UserRole + 1, + }; + + explicit LibraryModel(QObject* parent = nullptr); + + bool treeMode() const; + void setTreeMode(bool tree); + + bool showFilename() const; + void setShowFilename(bool show); + + void resetEntries(const QList& items); + void addEntries(const QList& items); + void updateEntries(const QList& items); + void removeEntries(const QList& items); + + QModelIndex index(const QString& game) const; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& child) const override; + + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + LibraryEntry entry(const QString& game) const; + +private: + QModelIndex indexForPath(const QString& path); + QModelIndex indexForPath(const QString& path) const; + + QVariant folderData(const QModelIndex& index, int role = Qt::DisplayRole) const; + + void addEntriesList(const QList& items); + void addEntriesTree(const QList& items); + void addEntryInternal(const LibraryEntry& item); + + bool m_treeMode; + bool m_showFilename; + + QList m_games; + QStringList m_pathOrder; + QHash> m_pathIndex; + QHash m_gameIndex; + QHash m_icons; +}; + +} diff --git a/src/platform/qt/library/LibraryTree.cpp b/src/platform/qt/library/LibraryTree.cpp deleted file mode 100644 index c9029e963..000000000 --- a/src/platform/qt/library/LibraryTree.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/* Copyright (c) 2014-2017 waddlesplash - * Copyright (c) 2013-2022 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 "LibraryTree.h" - -#include "utils.h" - -#include -#include - -using namespace QGBA; - -namespace QGBA { - -class LibraryTreeItem : public QTreeWidgetItem { -public: - LibraryTreeItem(QTreeWidget* parent = nullptr) : QTreeWidgetItem(parent) {} - void setFilesize(size_t size); - - virtual bool operator<(const QTreeWidgetItem& other) const override; -protected: - size_t m_size = 0; -}; - -} - -void LibraryTreeItem::setFilesize(size_t size) { - m_size = size; - setText(LibraryTree::COL_SIZE, niceSizeFormat(size)); -} - -bool LibraryTreeItem::operator<(const QTreeWidgetItem& other) const { - const int column = treeWidget()->sortColumn(); - return ((column == LibraryTree::COL_SIZE) ? - m_size < dynamic_cast(&other)->m_size : - QTreeWidgetItem::operator<(other)); -} - -LibraryTree::LibraryTree(LibraryController* parent) - : m_widget(new QTreeWidget(parent)) - , m_controller(parent) -{ - m_widget->setObjectName("LibraryTree"); - m_widget->setSortingEnabled(true); - m_widget->setAlternatingRowColors(true); - - QTreeWidgetItem* header = new QTreeWidgetItem({ - QApplication::translate("QGBA::LibraryTree", "Name", nullptr), - QApplication::translate("QGBA::LibraryTree", "Location", nullptr), - QApplication::translate("QGBA::LibraryTree", "Platform", nullptr), - QApplication::translate("QGBA::LibraryTree", "Size", nullptr), - QApplication::translate("QGBA::LibraryTree", "CRC32", nullptr), - }); - header->setTextAlignment(3, Qt::AlignTrailing | Qt::AlignVCenter); - m_widget->setHeaderItem(header); - - setViewStyle(LibraryStyle::STYLE_TREE); - m_widget->sortByColumn(COL_NAME, Qt::AscendingOrder); - - QObject::connect(m_widget, &QTreeWidget::itemActivated, [this](QTreeWidgetItem* item, int) -> void { - if (m_items.values().contains(item)) { - emit m_controller->startGame(); - } - }); -} - -LibraryTree::~LibraryTree() { - m_widget->clear(); -} - -void LibraryTree::resizeAllCols() { - for (int i = 0; i < m_widget->columnCount(); i++) { - m_widget->resizeColumnToContents(i); - } -} - -QString LibraryTree::selectedEntry() { - if (!m_widget->selectedItems().empty()) { - return m_items.key(m_widget->selectedItems().at(0)); - } else { - return {}; - } -} - -void LibraryTree::selectEntry(const QString& game) { - if (game.isNull()) { - return; - } - m_widget->setCurrentItem(m_items.value(game)); -} - -void LibraryTree::setViewStyle(LibraryStyle newStyle) { - if (newStyle == LibraryStyle::STYLE_LIST) { - m_widget->setIndentation(0); - } else { - m_widget->setIndentation(20); - } - m_currentStyle = newStyle; - rebuildTree(); -} - -void LibraryTree::resetEntries(const QList& items) { - m_deferredTreeRebuild = true; - m_entries.clear(); - m_pathNodes.clear(); - addEntries(items); -} - -void LibraryTree::addEntries(const QList& items) { - m_deferredTreeRebuild = true; - for (const auto& item : items) { - addEntry(item); - } - m_deferredTreeRebuild = false; - rebuildTree(); -} - -void LibraryTree::addEntry(const LibraryEntry& item) { - m_entries[item.fullpath] = item; - - QString folder = item.base; - if (!m_pathNodes.contains(folder)) { - m_pathNodes.insert(folder, 1); - } else { - ++m_pathNodes[folder]; - } - - rebuildTree(); -} - -void LibraryTree::updateEntries(const QList& items) { - for (const auto& item : items) { - updateEntry(item); - } -} - -void LibraryTree::updateEntry(const LibraryEntry& item) { - m_entries[item.fullpath] = item; - - LibraryTreeItem* i = static_cast(m_items.value(item.fullpath)); - i->setText(COL_NAME, m_showFilename ? item.filename : item.displayTitle()); - i->setText(COL_PLATFORM, nicePlatformFormat(item.platform)); - i->setFilesize(item.filesize); - i->setText(COL_CRC32, QString("%0").arg(item.crc32, 8, 16, QChar('0'))); -} - -void LibraryTree::removeEntries(const QList& items) { - m_deferredTreeRebuild = true; - for (const auto& item : items) { - removeEntry(item); - } - m_deferredTreeRebuild = false; - rebuildTree(); -} - -void LibraryTree::removeEntry(const QString& item) { - if (!m_entries.contains(item)) { - return; - } - QString folder = m_entries.value(item).base; - --m_pathNodes[folder]; - if (m_pathNodes[folder] <= 0) { - m_pathNodes.remove(folder); - } - - m_entries.remove(item); - rebuildTree(); -} - -void LibraryTree::rebuildTree() { - if (m_deferredTreeRebuild) { - return; - } - - QString currentGame = selectedEntry(); - m_widget->clear(); - m_items.clear(); - - QHash pathNodes; - if (m_currentStyle == LibraryStyle::STYLE_TREE) { - for (const QString& folder : m_pathNodes.keys()) { - QTreeWidgetItem* i = new LibraryTreeItem; - pathNodes.insert(folder, i); - i->setText(0, folder.section("/", -1)); - m_widget->addTopLevelItem(i); - } - } - - for (const auto& item : m_entries.values()) { - LibraryTreeItem* i = new LibraryTreeItem; - i->setText(COL_NAME, item.displayTitle()); - i->setText(COL_LOCATION, QDir::toNativeSeparators(item.base)); - i->setText(COL_PLATFORM, nicePlatformFormat(item.platform)); - i->setFilesize(item.filesize); - i->setTextAlignment(COL_SIZE, Qt::AlignTrailing | Qt::AlignVCenter); - i->setText(COL_CRC32, QString("%0").arg(item.crc32, 8, 16, QChar('0'))); - m_items.insert(item.fullpath, i); - - if (m_currentStyle == LibraryStyle::STYLE_TREE) { - pathNodes.value(item.base)->addChild(i); - } else { - m_widget->addTopLevelItem(i); - } - } - - m_widget->expandAll(); - resizeAllCols(); - selectEntry(currentGame); -} diff --git a/src/platform/qt/library/LibraryTree.h b/src/platform/qt/library/LibraryTree.h deleted file mode 100644 index 28354f3ec..000000000 --- a/src/platform/qt/library/LibraryTree.h +++ /dev/null @@ -1,61 +0,0 @@ -/* Copyright (c) 2014-2017 waddlesplash - * - * 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/. */ -#pragma once - -#include - -#include "LibraryController.h" - -namespace QGBA { - -class LibraryTreeItem; - -class LibraryTree final : public AbstractGameList { - -public: - enum Columns { - COL_NAME = 0, - COL_LOCATION = 1, - COL_PLATFORM = 2, - COL_SIZE = 3, - COL_CRC32 = 4, - }; - - explicit LibraryTree(LibraryController* parent = nullptr); - ~LibraryTree(); - - QString selectedEntry() override; - void selectEntry(const QString& fullpath) override; - - void setViewStyle(LibraryStyle newStyle) override; - - void resetEntries(const QList& items) override; - void addEntries(const QList& items) override; - void updateEntries(const QList& items) override; - void removeEntries(const QList& items) override; - - void addEntry(const LibraryEntry& items) override; - void updateEntry(const LibraryEntry& items) override; - void removeEntry(const QString& items) override; - - QWidget* widget() override { return m_widget; } - -private: - QTreeWidget* m_widget; - LibraryStyle m_currentStyle; - - LibraryController* m_controller; - - bool m_deferredTreeRebuild = false; - QHash m_entries; - QHash m_items; - QHash m_pathNodes; - - void rebuildTree(); - void resizeAllCols(); -}; - -} diff --git a/src/platform/qt/resources.qrc b/src/platform/qt/resources.qrc index e4b8fab5f..271440f67 100644 --- a/src/platform/qt/resources.qrc +++ b/src/platform/qt/resources.qrc @@ -7,6 +7,24 @@ ../../../res/keymap.qpic ../../../res/patrons.txt ../../../res/no-cam.png + ../../../res/gb-icon-256.png + ../../../res/gb-icon-128.png + ../../../res/gb-icon-32.png + ../../../res/gb-icon-24.png + ../../../res/gb-icon-16.png + ../../../res/gb-icon.svg + ../../../res/gbc-icon-256.png + ../../../res/gbc-icon-128.png + ../../../res/gbc-icon-32.png + ../../../res/gbc-icon-24.png + ../../../res/gbc-icon-16.png + ../../../res/gbc-icon.svg + ../../../res/gba-icon-256.png + ../../../res/gba-icon-128.png + ../../../res/gba-icon-32.png + ../../../res/gba-icon-24.png + ../../../res/gba-icon-16.png + ../../../res/gba-icon.svg ../../../res/exe4/chip-names.txt diff --git a/src/platform/qt/test/library.cpp b/src/platform/qt/test/library.cpp new file mode 100644 index 000000000..2cda06694 --- /dev/null +++ b/src/platform/qt/test/library.cpp @@ -0,0 +1,200 @@ +/* Copyright (c) 2013-2022 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 "platform/qt/library/LibraryModel.h" + +#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) +#include +#endif + +#include +#include + +#define FIND_GBA_ROW(gba, gb) \ + int gba = findGBARow(); \ + if (gba < 0) QFAIL("Could not find gba row"); \ + int gb = 1 - gba; + +using namespace QGBA; + +class LibraryModelTest : public QObject { +Q_OBJECT + +private: + LibraryModel* model = nullptr; + + int findGBARow() { + for (int i = 0; i < model->rowCount(); i++) { + if (model->index(i, 0).data() == "gba") { + return i; + } + } + return -1; + } + + LibraryEntry makeGBA(const QString& name, uint32_t crc) { + LibraryEntry entry; + entry.base = "/gba"; + entry.filename = name + ".gba"; + entry.fullpath = entry.base + "/" + entry.filename; + entry.title = name; + entry.internalTitle = name.toUpper().toUtf8(); + entry.internalCode = entry.internalTitle.replace(" ", "").left(4); + entry.platform = mPLATFORM_GBA; + entry.filesize = entry.fullpath.size() * 4; + entry.crc32 = crc; + return entry; + } + + LibraryEntry makeGB(const QString& name, uint32_t crc) { + LibraryEntry entry = makeGBA(name, crc); + entry.base = "/gb"; + entry.filename = entry.filename.replace("gba", "gb"); + entry.fullpath = entry.fullpath.replace("gba", "gb"); + entry.platform = mPLATFORM_GB; + entry.filesize /= 4; + return entry; + } + + void addTestGames1() { + model->addEntries({ + makeGBA("Test Game", 0x12345678), + makeGBA("Another", 0x23456789), + makeGB("Old Game", 0x87654321), + }); + } + + void addTestGames2() { + model->addEntries({ + makeGBA("Game 3", 0x12345679), + makeGBA("Game 4", 0x2345678A), + makeGBA("Game 5", 0x2345678B), + makeGB("Game 6", 0x87654322), + makeGB("Game 7", 0x87654323), + }); + } + + void updateGame() { + LibraryEntry game = makeGBA("Another", 0x88888888); + model->updateEntries({ game }); + QModelIndex idx = find("Another"); + QVERIFY2(idx.isValid(), "game not found"); + QCOMPARE(idx.siblingAtColumn(LibraryModel::COL_CRC32).data(Qt::EditRole).toInt(), 0x88888888); + } + + void removeGames1() { + model->removeEntries({ "/gba/Another.gba", "/gb/Game 6.gb" }); + QVERIFY2(!find("Another").isValid(), "game not removed"); + QVERIFY2(!find("Game 6").isValid(), "game not removed"); + } + + void removeGames2() { + model->removeEntries({ "/gb/Old Game.gb", "/gb/Game 7.gb" }); + QVERIFY2(!find("Old Game").isValid(), "game not removed"); + QVERIFY2(!find("Game 7").isValid(), "game not removed"); + } + + QModelIndex find(const QString& name) { + for (int i = 0; i < model->rowCount(); i++) { + QModelIndex idx = model->index(i, 0); + if (idx.data().toString() == name) { + return idx; + } + for (int j = 0; j < model->rowCount(idx); j++) { + QModelIndex child = model->index(j, 0, idx); + if (child.data().toString() == name) { + return child; + } + } + } + return QModelIndex(); + } + +private slots: + void init() { + model = new LibraryModel(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) + new QAbstractItemModelTester(model, QAbstractItemModelTester::FailureReportingMode::QtTest, model); +#endif + } + + void cleanup() { + delete model; + model = nullptr; + } + + void testList() { + addTestGames1(); + QCOMPARE(model->rowCount(), 3); + addTestGames2(); + QCOMPARE(model->rowCount(), 8); + updateGame(); + model->removeEntries({ "/gba/Another.gba", "/gb/Game 6.gb" }); + QCOMPARE(model->rowCount(), 6); + model->removeEntries({ "/gb/Old Game.gb", "/gb/Game 7.gb" }); + QCOMPARE(model->rowCount(), 4); + } + + void testTree() { + model->setTreeMode(true); + addTestGames1(); + FIND_GBA_ROW(gbaRow, gbRow); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->rowCount(model->index(gbRow, 0)), 1); + QCOMPARE(model->rowCount(model->index(gbaRow, 0)), 2); + addTestGames2(); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->rowCount(model->index(gbRow, 0)), 3); + QCOMPARE(model->rowCount(model->index(gbaRow, 0)), 5); + updateGame(); + removeGames1(); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->rowCount(model->index(gbRow, 0)), 2); + QCOMPARE(model->rowCount(model->index(gbaRow, 0)), 4); + removeGames2(); + QVERIFY2(!find("gb").isValid(), "did not remove gb folder"); + QCOMPARE(model->rowCount(), 1); + QCOMPARE(model->rowCount(model->index(0, 0)), 4); + } + + void modeSwitchTest1() { + addTestGames1(); + { + QSignalSpy resetSpy(model, SIGNAL(modelReset())); + model->setTreeMode(true); + QVERIFY(resetSpy.count()); + } + FIND_GBA_ROW(gbaRow, gbRow); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->rowCount(model->index(gbRow, 0)), 1); + QCOMPARE(model->rowCount(model->index(gbaRow, 0)), 2); + { + QSignalSpy resetSpy(model, SIGNAL(modelReset())); + model->setTreeMode(false); + QVERIFY(resetSpy.count()); + } + addTestGames2(); + QCOMPARE(model->rowCount(), 8); + } + + void modeSwitchTest2() { + model->setTreeMode(false); + addTestGames1(); + model->setTreeMode(true); + FIND_GBA_ROW(gbaRow, gbRow); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->rowCount(model->index(gbRow, 0)), 1); + QCOMPARE(model->rowCount(model->index(gbaRow, 0)), 2); + addTestGames2(); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->rowCount(model->index(gbRow, 0)), 3); + QCOMPARE(model->rowCount(model->index(gbaRow, 0)), 5); + model->setTreeMode(false); + QCOMPARE(model->rowCount(), 8); + } +}; + +QTEST_MAIN(LibraryModelTest) +#include "library.moc" diff --git a/src/platform/qt/test/spanset.cpp b/src/platform/qt/test/spanset.cpp new file mode 100644 index 000000000..45a2a1e25 --- /dev/null +++ b/src/platform/qt/test/spanset.cpp @@ -0,0 +1,61 @@ +/* Copyright (c) 2013-2022 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 "platform/qt/utils.h" + +#include + +using namespace QGBA; + +class SpanSetTest : public QObject { +Q_OBJECT + +private: + void debugSpans(const SpanSet& spanSet) { + QStringList debug; + for (auto span : spanSet.spans) { + debug << QStringLiteral("[%1, %2]").arg(span.left).arg(span.right); + } + qDebug() << QStringLiteral("SpanSet{%1}").arg(debug.join(", ")); + } + +private slots: + void oneSpan() { + SpanSet spanSet; + spanSet.add(1); + spanSet.add(2); + spanSet.add(3); + QCOMPARE(spanSet.spans.size(), 1); + spanSet.merge(); + QCOMPARE(spanSet.spans.size(), 1); + } + + void twoSpans() { + SpanSet spanSet; + spanSet.add(1); + spanSet.add(2); + spanSet.add(4); + QCOMPARE(spanSet.spans.size(), 2); + spanSet.merge(); + QCOMPARE(spanSet.spans.size(), 2); + } + + void mergeSpans() { + SpanSet spanSet; + spanSet.add(1); + spanSet.add(3); + spanSet.add(2); + spanSet.add(5); + spanSet.add(4); + spanSet.add(7); + spanSet.add(8); + QCOMPARE(spanSet.spans.size(), 4); + spanSet.merge(); + QCOMPARE(spanSet.spans.size(), 2); + } +}; + +QTEST_APPLESS_MAIN(SpanSetTest) +#include "spanset.moc" diff --git a/src/platform/qt/utils.cpp b/src/platform/qt/utils.cpp index c445c41b8..e6d3bbe99 100644 --- a/src/platform/qt/utils.cpp +++ b/src/platform/qt/utils.cpp @@ -6,6 +6,7 @@ #include "utils.h" #include +#include #include #include @@ -174,6 +175,47 @@ QString keyName(int key) { return QObject::tr("Menu"); default: return QKeySequence(key).toString(QKeySequence::NativeText); + } +} + +void SpanSet::add(int pos) { + for (Span& span : spans) { + if (pos == span.left - 1) { + span.left = pos; + return; + } else if (pos == span.right + 1) { + span.right = pos; + return; + } + } + spans << Span{ pos, pos }; +} + +void SpanSet::merge() { + int numSpans = spans.size(); + if (!numSpans) { + return; + } + sort(); + QVector merged({ spans[0] }); + int lastRight = merged[0].right; + for (int i = 1; i < numSpans; i++) { + int right = spans[i].right; + if (spans[i].left - 1 <= lastRight) { + merged.back().right = right; + } else { + merged << spans[i]; + } + lastRight = right; + } + spans = merged; +} + +void SpanSet::sort(bool reverse) { + if (reverse) { + std::sort(spans.begin(), spans.end(), std::greater()); + } else { + std::sort(spans.begin(), spans.end()); } } diff --git a/src/platform/qt/utils.h b/src/platform/qt/utils.h index cad2595b5..e6cb81929 100644 --- a/src/platform/qt/utils.h +++ b/src/platform/qt/utils.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -117,4 +118,20 @@ bool extractMatchingFile(VDir* dir, std::function filter); QString keyName(int key); +struct SpanSet { + struct Span { + int left; + int right; + + inline bool operator<(const Span& other) const { return left < other.left; } + inline bool operator>(const Span& other) const { return left > other.left; } + }; + + void add(int pos); + void merge(); + void sort(bool reverse = false); + + QVector spans; +}; + } From 447054674d0368f942d85347fff4173b98ff36b9 Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Sat, 2 Jul 2022 14:10:48 -0500 Subject: [PATCH 32/74] Library: store platform models in database, render GBC/SGB icons --- include/mgba/core/library.h | 3 ++ src/core/library.c | 64 +++++++++++++++++++++++- src/platform/qt/library/LibraryEntry.cpp | 7 +++ src/platform/qt/library/LibraryEntry.h | 2 + src/platform/qt/library/LibraryModel.cpp | 3 +- src/platform/qt/resources.qrc | 6 +++ src/platform/qt/utils.cpp | 12 ++++- src/platform/qt/utils.h | 2 +- 8 files changed, 94 insertions(+), 5 deletions(-) diff --git a/include/mgba/core/library.h b/include/mgba/core/library.h index cfdd4cc60..eb819361e 100644 --- a/include/mgba/core/library.h +++ b/include/mgba/core/library.h @@ -15,6 +15,8 @@ CXX_GUARD_START #include #include +#define M_LIBRARY_MODEL_UNKNOWN -1 + struct mLibraryEntry { const char* base; const char* filename; @@ -26,6 +28,7 @@ struct mLibraryEntry { uint32_t crc32; uint8_t md5[16]; uint8_t sha1[20]; + int platformModels; }; #ifdef USE_SQLITE3 diff --git a/src/core/library.c b/src/core/library.c index 638f49880..8112ffc1a 100644 --- a/src/core/library.c +++ b/src/core/library.c @@ -9,6 +9,11 @@ #include #include +#ifdef M_CORE_GB +#include +#include +#endif + #ifdef USE_SQLITE3 #include @@ -108,12 +113,38 @@ static void _bindConstraints(sqlite3_stmt* statement, const struct mLibraryEntry sqlite3_bind_int(statement, useIndex, 1); sqlite3_bind_int(statement, index, constraints->platform); } + + if (constraints->platformModels != M_LIBRARY_MODEL_UNKNOWN) { + index = sqlite3_bind_parameter_index(statement, ":models"); + sqlite3_bind_int(statement, index, constraints->platformModels); + } } struct mLibrary* mLibraryCreateEmpty(void) { return mLibraryLoad(":memory:"); } +static int _mLibraryTableVersion(struct mLibrary* library, const char* tableName) { + int version = -1; + + static const char getVersion[] = "SELECT version FROM version WHERE tname=?"; + sqlite3_stmt* getVersionStmt; + if (sqlite3_prepare_v2(library->db, getVersion, -1, &getVersionStmt, NULL)) { + goto error; + } + + sqlite3_clear_bindings(getVersionStmt); + sqlite3_reset(getVersionStmt); + sqlite3_bind_text(getVersionStmt, 1, tableName, -1, SQLITE_TRANSIENT); + if (sqlite3_step(getVersionStmt) != SQLITE_DONE) { + version = sqlite3_column_int(getVersionStmt, 0); + } + +error: + sqlite3_finalize(getVersionStmt); + return version; +} + struct mLibrary* mLibraryLoad(const char* path) { struct mLibrary* library = malloc(sizeof(*library)); memset(library, 0, sizeof(*library)); @@ -140,6 +171,7 @@ struct mLibrary* mLibraryLoad(const char* path) { "\n internalTitle TEXT," "\n internalCode TEXT," "\n platform INTEGER NOT NULL DEFAULT -1," + "\n models INTEGER NULL," "\n size INTEGER," "\n crc32 INTEGER," "\n md5 BLOB," @@ -159,18 +191,35 @@ struct mLibrary* mLibraryLoad(const char* path) { "\n CREATE INDEX IF NOT EXISTS sha1 ON roms (sha1);" "\n INSERT OR IGNORE INTO version (tname, version) VALUES ('version', 1);" "\n INSERT OR IGNORE INTO version (tname, version) VALUES ('roots', 1);" - "\n INSERT OR IGNORE INTO version (tname, version) VALUES ('roms', 1);" + "\n INSERT OR IGNORE INTO version (tname, version) VALUES ('roms', 2);" "\n INSERT OR IGNORE INTO version (tname, version) VALUES ('paths', 1);"; if (sqlite3_exec(library->db, createTables, NULL, NULL, NULL)) { goto error; } + int romsTableVersion = _mLibraryTableVersion(library, "roms"); + if (romsTableVersion < 0) { + goto error; + } else if (romsTableVersion < 2) { + static const char upgradeRomsTable[] = + " ALTER TABLE roms" + "\nADD COLUMN models INTEGER NULL"; + if (sqlite3_exec(library->db, upgradeRomsTable, NULL, NULL, NULL)) { + goto error; + } + + static const char updateRomsTableVersion[] = "UPDATE version SET version=2 WHERE tname='roms'"; + if (sqlite3_exec(library->db, updateRomsTableVersion, NULL, NULL, NULL)) { + goto error; + } + } + static const char insertPath[] = "INSERT INTO paths (romid, path, customTitle, rootid) VALUES (?, ?, ?, ?);"; if (sqlite3_prepare_v2(library->db, insertPath, -1, &library->insertPath, NULL)) { goto error; } - static const char insertRom[] = "INSERT INTO roms (crc32, md5, sha1, size, internalCode, platform) VALUES (:crc32, :md5, :sha1, :size, :internalCode, :platform);"; + static const char insertRom[] = "INSERT INTO roms (crc32, md5, sha1, size, internalCode, platform, :models) VALUES (:crc32, :md5, :sha1, :size, :internalCode, :platform, :models);"; if (sqlite3_prepare_v2(library->db, insertRom, -1, &library->insertRom, NULL)) { goto error; } @@ -318,6 +367,15 @@ bool _mLibraryAddEntry(struct mLibrary* library, const char* filename, const cha core->checksum(core, &entry.md5, mCHECKSUM_MD5); core->checksum(core, &entry.sha1, mCHECKSUM_SHA1); entry.platform = core->platform(core); + entry.platformModels = M_LIBRARY_MODEL_UNKNOWN; +#ifdef M_CORE_GB + if (entry.platform == mPLATFORM_GB) { + struct GB* gb = (struct GB*) core->board; + if (gb->memory.rom) { + entry.platformModels = GBValidModels(gb->memory.rom); + } + } +#endif entry.title = NULL; entry.base = base; entry.filename = filename; @@ -448,6 +506,8 @@ size_t mLibraryGetEntries(struct mLibrary* library, struct mLibraryListing* out, } } else if (strcmp(colName, "platform") == 0) { entry->platform = sqlite3_column_int(library->select, i); + } else if (strcmp(colName, "models") == 0) { + entry->platformModels = sqlite3_column_int(library->select, i); } else if (strcmp(colName, "size") == 0) { entry->filesize = sqlite3_column_int64(library->select, i); } else if (strcmp(colName, "internalCode") == 0 && sqlite3_column_type(library->select, i) == SQLITE_TEXT) { diff --git a/src/platform/qt/library/LibraryEntry.cpp b/src/platform/qt/library/LibraryEntry.cpp index 27feb64e8..25a5976c9 100644 --- a/src/platform/qt/library/LibraryEntry.cpp +++ b/src/platform/qt/library/LibraryEntry.cpp @@ -6,6 +6,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "LibraryEntry.h" +#include "utils.h" + #include using namespace QGBA; @@ -22,6 +24,7 @@ LibraryEntry::LibraryEntry(const mLibraryEntry* entry) , internalTitle(entry->internalTitle) , internalCode(entry->internalCode) , platform(entry->platform) + , platformModels(entry->platformModels) , filesize(entry->filesize) , crc32(entry->crc32) { @@ -38,6 +41,10 @@ QString LibraryEntry::displayTitle(bool showFilename) const { return title; } +QString LibraryEntry::displayPlatform() const { + return nicePlatformFormat(platform, platformModels); +} + bool LibraryEntry::operator==(const LibraryEntry& other) const { return other.fullpath == fullpath; } diff --git a/src/platform/qt/library/LibraryEntry.h b/src/platform/qt/library/LibraryEntry.h index 2a733db4a..3d8e6c8a8 100644 --- a/src/platform/qt/library/LibraryEntry.h +++ b/src/platform/qt/library/LibraryEntry.h @@ -25,6 +25,7 @@ struct LibraryEntry { bool isNull() const; QString displayTitle(bool showFilename = false) const; + QString displayPlatform() const; QString base; QString filename; @@ -33,6 +34,7 @@ struct LibraryEntry { QByteArray internalTitle; QByteArray internalCode; mPlatform platform; + int platformModels; size_t filesize; uint32_t crc32; diff --git a/src/platform/qt/library/LibraryModel.cpp b/src/platform/qt/library/LibraryModel.cpp index bc08228c1..ebb8f97e6 100644 --- a/src/platform/qt/library/LibraryModel.cpp +++ b/src/platform/qt/library/LibraryModel.cpp @@ -21,6 +21,7 @@ static const QStringList iconSets{ "GBA", "GBC", "GB", + "SGB", // "DS", }; @@ -348,7 +349,7 @@ QVariant LibraryModel::data(const QModelIndex& index, int role) const { switch (index.column()) { case COL_NAME: if (role == Qt::DecorationRole) { - return m_icons.value(nicePlatformFormat(entry->platform), qApp->style()->standardIcon(QStyle::SP_FileIcon)); + return m_icons.value(entry->displayPlatform(), qApp->style()->standardIcon(QStyle::SP_FileIcon)); } return entry->displayTitle(m_showFilename); case COL_LOCATION: diff --git a/src/platform/qt/resources.qrc b/src/platform/qt/resources.qrc index 271440f67..4c9fe9098 100644 --- a/src/platform/qt/resources.qrc +++ b/src/platform/qt/resources.qrc @@ -19,6 +19,12 @@ ../../../res/gbc-icon-24.png ../../../res/gbc-icon-16.png ../../../res/gbc-icon.svg + ../../../res/sgb-icon-256.png + ../../../res/sgb-icon-128.png + ../../../res/sgb-icon-32.png + ../../../res/sgb-icon-24.png + ../../../res/sgb-icon-16.png + ../../../res/sgb-icon.svg ../../../res/gba-icon-256.png ../../../res/gba-icon-128.png ../../../res/gba-icon-32.png diff --git a/src/platform/qt/utils.cpp b/src/platform/qt/utils.cpp index e6d3bbe99..0f013e296 100644 --- a/src/platform/qt/utils.cpp +++ b/src/platform/qt/utils.cpp @@ -5,6 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "utils.h" +#include +#include + #include #include #include @@ -30,7 +33,7 @@ QString niceSizeFormat(size_t filesize) { return unit.arg(size, 0, 'f', int(size * 10) % 10 ? 1 : 0); } -QString nicePlatformFormat(mPlatform platform) { +QString nicePlatformFormat(mPlatform platform, int validModels) { switch (platform) { #ifdef M_CORE_GBA case mPLATFORM_GBA: @@ -38,6 +41,13 @@ QString nicePlatformFormat(mPlatform platform) { #endif #ifdef M_CORE_GB case mPLATFORM_GB: + if (validModels != M_LIBRARY_MODEL_UNKNOWN) { + if (validModels & GB_MODEL_CGB) { + return QObject::tr("GBC"); + } else if (validModels & GB_MODEL_SGB) { + return QObject::tr("SGB"); + } + } return QObject::tr("GB"); #endif default: diff --git a/src/platform/qt/utils.h b/src/platform/qt/utils.h index e6cb81929..d1e1a7063 100644 --- a/src/platform/qt/utils.h +++ b/src/platform/qt/utils.h @@ -31,7 +31,7 @@ enum class Endian { }; QString niceSizeFormat(size_t filesize); -QString nicePlatformFormat(mPlatform platform); +QString nicePlatformFormat(mPlatform platform, int validModels = 0); bool convertAddress(const QHostAddress* input, Address* output); From 578709254f555058b5126450d7a5a2b589257c24 Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Wed, 26 Apr 2023 21:45:18 -0500 Subject: [PATCH 33/74] clean up according to PR comments --- src/core/library.c | 7 +++++-- src/platform/qt/CMakeLists.txt | 2 +- src/platform/qt/library/LibraryModel.cpp | 14 +++++++------- src/platform/qt/utils.cpp | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/core/library.c b/src/core/library.c index 8112ffc1a..24f3ddd42 100644 --- a/src/core/library.c +++ b/src/core/library.c @@ -38,6 +38,7 @@ struct mLibrary { #define CONSTRAINTS_ROMONLY \ "CASE WHEN :useSize THEN roms.size = :size ELSE 1 END AND " \ "CASE WHEN :usePlatform THEN roms.platform = :platform ELSE 1 END AND " \ + "CASE WHEN :useModels THEN roms.models & :models ELSE 1 END AND " \ "CASE WHEN :useCrc32 THEN roms.crc32 = :crc32 ELSE 1 END AND " \ "CASE WHEN :useMd5 THEN roms.md5 = :md5 ELSE 1 END AND " \ "CASE WHEN :useSha1 THEN roms.sha1 = :sha1 ELSE 1 END AND " \ @@ -115,7 +116,9 @@ static void _bindConstraints(sqlite3_stmt* statement, const struct mLibraryEntry } if (constraints->platformModels != M_LIBRARY_MODEL_UNKNOWN) { + useIndex = sqlite3_bind_parameter_index(statement, ":useModels"); index = sqlite3_bind_parameter_index(statement, ":models"); + sqlite3_bind_int(statement, useIndex, 1); sqlite3_bind_int(statement, index, constraints->platformModels); } } @@ -202,8 +205,8 @@ struct mLibrary* mLibraryLoad(const char* path) { goto error; } else if (romsTableVersion < 2) { static const char upgradeRomsTable[] = - " ALTER TABLE roms" - "\nADD COLUMN models INTEGER NULL"; + " ALTER TABLE roms" + "\n ADD COLUMN models INTEGER NULL"; if (sqlite3_exec(library->db, upgradeRomsTable, NULL, NULL, NULL)) { goto error; } diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 560a6fa44..433e8c061 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -566,7 +566,7 @@ if(BUILD_SUITE) add_test(platform-qt-${TEST_NAME} test-qt-${TEST_NAME}) endforeach() else() - message("${QT}Test not found") + message(WARNING "${QT}Test not found") endif() endif() diff --git a/src/platform/qt/library/LibraryModel.cpp b/src/platform/qt/library/LibraryModel.cpp index ebb8f97e6..0a4379bbb 100644 --- a/src/platform/qt/library/LibraryModel.cpp +++ b/src/platform/qt/library/LibraryModel.cpp @@ -340,13 +340,13 @@ QVariant LibraryModel::data(const QModelIndex& index, int role) const { QString path = m_pathOrder[index.parent().row()]; entry = m_pathIndex[path][index.row()]; } else if (!index.parent().isValid() && index.row() < m_games.size()) { - entry = &m_games[index.row()]; + entry = &m_games[index.row()]; + } + if (entry) { + if (role == FullPathRole) { + return entry->fullpath; } - if (entry) { - if (role == FullPathRole) { - return entry->fullpath; - } - switch (index.column()) { + switch (index.column()) { case COL_NAME: if (role == Qt::DecorationRole) { return m_icons.value(entry->displayPlatform(), qApp->style()->standardIcon(QStyle::SP_FileIcon)); @@ -360,8 +360,8 @@ QVariant LibraryModel::data(const QModelIndex& index, int role) const { return (role == Qt::DisplayRole) ? QVariant(niceSizeFormat(entry->filesize)) : QVariant(int(entry->filesize)); case COL_CRC32: return (role == Qt::DisplayRole) ? QVariant(QStringLiteral("%0").arg(entry->crc32, 8, 16, QChar('0'))) : QVariant(entry->crc32); - } } + } return QVariant(); } diff --git a/src/platform/qt/utils.cpp b/src/platform/qt/utils.cpp index 0f013e296..6c7d2d52b 100644 --- a/src/platform/qt/utils.cpp +++ b/src/platform/qt/utils.cpp @@ -185,7 +185,7 @@ QString keyName(int key) { return QObject::tr("Menu"); default: return QKeySequence(key).toString(QKeySequence::NativeText); - } + } } void SpanSet::add(int pos) { From 130319494a1631e9de3cf5df306820a2db02d6b7 Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Sun, 30 Mar 2025 17:21:01 -0500 Subject: [PATCH 34/74] switch away from APIs removed / compatibility-broken by upstream --- src/platform/qt/library/LibraryController.cpp | 4 +-- src/platform/qt/library/LibraryModel.cpp | 27 +++++++++---------- src/platform/qt/library/LibraryModel.h | 5 +++- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/platform/qt/library/LibraryController.cpp b/src/platform/qt/library/LibraryController.cpp index ed78ce410..198d14080 100644 --- a/src/platform/qt/library/LibraryController.cpp +++ b/src/platform/qt/library/LibraryController.cpp @@ -192,7 +192,7 @@ void LibraryController::refresh() { setDisabled(true); - QSet removedEntries = QSet::fromList(m_knownGames.keys()); + QSet removedEntries(m_knownGames.keyBegin(), m_knownGames.keyEnd()); QList updatedEntries; QList newEntries; @@ -217,7 +217,7 @@ void LibraryController::refresh() { m_knownGames.remove(path); } - m_libraryModel->removeEntries(removedEntries.toList()); + m_libraryModel->removeEntries(QList(removedEntries.begin(), removedEntries.end())); m_libraryModel->updateEntries(updatedEntries); m_libraryModel->addEntries(newEntries); diff --git a/src/platform/qt/library/LibraryModel.cpp b/src/platform/qt/library/LibraryModel.cpp index 0a4379bbb..dcd72a33b 100644 --- a/src/platform/qt/library/LibraryModel.cpp +++ b/src/platform/qt/library/LibraryModel.cpp @@ -102,11 +102,11 @@ void LibraryModel::addEntries(const QList& items) { void LibraryModel::addEntryInternal(const LibraryEntry& item) { m_gameIndex[item.fullpath] = m_games.size(); - m_games << item; + m_games.emplace_back(new LibraryEntry(item)); if (!m_pathIndex.contains(item.base)) { m_pathOrder << item.base; } - m_pathIndex[item.base] << &m_games.back(); + m_pathIndex[item.base] << m_games.back().get(); } void LibraryModel::addEntriesList(const QList& items) { @@ -158,7 +158,7 @@ void LibraryModel::updateEntries(const QList& items) { Q_ASSERT(idx.isValid()); int pos = m_gameIndex.value(item.fullpath, -1); Q_ASSERT(pos >= 0); - m_games[pos] = item; + *m_games[pos] = item; updatedSpans[idx.parent()].add(pos); } for (auto iter = updatedSpans.begin(); iter != updatedSpans.end(); iter++) { @@ -183,11 +183,13 @@ void LibraryModel::removeEntries(const QList& items) { if (pos < firstModifiedIndex) { firstModifiedIndex = pos; } - LibraryEntry* entry = &m_games[pos]; + LibraryEntry* entry = m_games[pos].get(); QModelIndex parent = indexForPath(entry->base); Q_ASSERT(!m_treeMode || parent.isValid()); QList& pathItems = m_pathIndex[entry->base]; - removedTreeSpans[entry->base].add(pathItems.indexOf(entry)); + int pathPos = pathItems.indexOf(entry); + Q_ASSERT(pathPos >= 0); + removedTreeSpans[entry->base].add(pathPos); if (!m_treeMode) { removedRootSpans.add(pos); } @@ -214,9 +216,6 @@ void LibraryModel::removeEntries(const QList& items) { for (const SpanSet::Span& span : spanSet.spans) { if (m_treeMode) { beginRemoveRows(parent, span.left, span.right); - for (int i = span.left; i <= span.right; i++) { - - } } pathIndex.erase(pathIndex.begin() + span.left, pathIndex.begin() + span.right + 1); if (m_treeMode) { @@ -238,8 +237,8 @@ void LibraryModel::removeEntries(const QList& items) { } endRemoveRows(); } - for (int i = m_games.count() - 1; i >= firstModifiedIndex; i--) { - m_gameIndex[m_games[i].fullpath] = i; + for (int i = m_games.size() - 1; i >= firstModifiedIndex; i--) { + m_gameIndex[m_games[i]->fullpath] = i; } } @@ -249,7 +248,7 @@ QModelIndex LibraryModel::index(const QString& game) const { return QModelIndex(); } if (m_treeMode) { - const LibraryEntry& entry = m_games[pos]; + const LibraryEntry& entry = *m_games[pos]; return createIndex(m_pathIndex[entry.base].indexOf(&entry), 0, m_pathOrder.indexOf(entry.base)); } return createIndex(pos, 0); @@ -339,8 +338,8 @@ QVariant LibraryModel::data(const QModelIndex& index, int role) const { } QString path = m_pathOrder[index.parent().row()]; entry = m_pathIndex[path][index.row()]; - } else if (!index.parent().isValid() && index.row() < m_games.size()) { - entry = &m_games[index.row()]; + } else if (!index.parent().isValid() && index.row() < (int)m_games.size()) { + entry = m_games[index.row()].get(); } if (entry) { if (role == FullPathRole) { @@ -414,5 +413,5 @@ LibraryEntry LibraryModel::entry(const QString& game) const { if (pos < 0) { return {}; } - return m_games[pos]; + return *m_games[pos]; } diff --git a/src/platform/qt/library/LibraryModel.h b/src/platform/qt/library/LibraryModel.h index db220392f..1d57c1c41 100644 --- a/src/platform/qt/library/LibraryModel.h +++ b/src/platform/qt/library/LibraryModel.h @@ -11,6 +11,9 @@ #include +#include +#include + #include "LibraryEntry.h" class QTreeView; @@ -71,7 +74,7 @@ private: bool m_treeMode; bool m_showFilename; - QList m_games; + std::vector> m_games; QStringList m_pathOrder; QHash> m_pathIndex; QHash m_gameIndex; From 165cce1a6ccca3bd3b54de053e9131b9388efd43 Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Sun, 30 Mar 2025 18:25:07 -0500 Subject: [PATCH 35/74] fix library icons --- src/platform/qt/library/LibraryModel.cpp | 4 ++-- src/platform/qt/utils.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/platform/qt/library/LibraryModel.cpp b/src/platform/qt/library/LibraryModel.cpp index dcd72a33b..db530c177 100644 --- a/src/platform/qt/library/LibraryModel.cpp +++ b/src/platform/qt/library/LibraryModel.cpp @@ -31,7 +31,7 @@ LibraryModel::LibraryModel(QObject* parent) , m_showFilename(false) { for (const QString& platform : iconSets) { - QString pathTemplate = QStringLiteral(":/res/%1-icon%2").arg(platform); + QString pathTemplate = QStringLiteral(":/res/%1-icon%2").arg(platform.toLower()); QIcon icon; icon.addFile(pathTemplate.arg("-256.png"), QSize(256, 256)); icon.addFile(pathTemplate.arg("-128.png"), QSize(128, 128)); @@ -40,7 +40,7 @@ LibraryModel::LibraryModel(QObject* parent) icon.addFile(pathTemplate.arg("-16.png"), QSize(16, 16)); // This will silently and harmlessly fail if QSvgIconEngine isn't compiled in. icon.addFile(pathTemplate.arg(".svg")); - m_icons[platform.toLower()] = icon; + m_icons[platform] = icon; } } diff --git a/src/platform/qt/utils.cpp b/src/platform/qt/utils.cpp index 6c7d2d52b..1d12310ed 100644 --- a/src/platform/qt/utils.cpp +++ b/src/platform/qt/utils.cpp @@ -37,21 +37,21 @@ QString nicePlatformFormat(mPlatform platform, int validModels) { switch (platform) { #ifdef M_CORE_GBA case mPLATFORM_GBA: - return QObject::tr("GBA"); + return "GBA"; #endif #ifdef M_CORE_GB case mPLATFORM_GB: if (validModels != M_LIBRARY_MODEL_UNKNOWN) { if (validModels & GB_MODEL_CGB) { - return QObject::tr("GBC"); + return "GBC"; } else if (validModels & GB_MODEL_SGB) { - return QObject::tr("SGB"); + return "SGB"; } } - return QObject::tr("GB"); + return "GB"; #endif default: - return QObject::tr("?"); + return "?"; } } From 4bca59daa5123a01df43f6216e6dd0b3d9005c81 Mon Sep 17 00:00:00 2001 From: CasualPokePlayer <50538166+CasualPokePlayer@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:41:03 -0700 Subject: [PATCH 36/74] Mask away unused GPIO bits Unused GPIO bits are not writable and always return 0. This is documented on gbatek and I've confirmed this is the correct behavior on my own Emerald cartridge. --- src/gba/cart/gpio.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/gba/cart/gpio.c b/src/gba/cart/gpio.c index a44bab294..5798a636e 100644 --- a/src/gba/cart/gpio.c +++ b/src/gba/cart/gpio.c @@ -78,15 +78,15 @@ void GBAHardwareGPIOWrite(struct GBACartridgeHardware* hw, uint32_t address, uin hw->pinState &= ~hw->direction; hw->pinState |= value & hw->direction; } else { - hw->pinState = value; + hw->pinState = value & 0xF; } _readPins(hw); break; case GPIO_REG_DIRECTION: - hw->direction = value; + hw->direction = value & 0xF; break; case GPIO_REG_CONTROL: - hw->readWrite = value; + hw->readWrite = value & 0x1; break; default: mLOG(GBA_HW, WARN, "Invalid GPIO address"); @@ -514,6 +514,8 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer hw->readWrite = GBASerializedHWFlags1GetReadWrite(flags1); LOAD_16(hw->pinState, 0, &state->hw.pinState); LOAD_16(hw->direction, 0, &state->hw.pinDirection); + hw->pinState &= 0xF; + hw->direction &= 0xF; hw->devices = state->hw.devices; if ((hw->devices & HW_GPIO) && hw->gpioBase) { From 86df2543e63b28a08906cefcce0bef3e7c1d2f80 Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Mon, 31 Mar 2025 12:08:32 -0500 Subject: [PATCH 37/74] PR review updates --- src/core/library.c | 2 +- src/platform/qt/library/LibraryController.cpp | 25 ++-- src/platform/qt/library/LibraryController.h | 1 + src/platform/qt/library/LibraryEntry.cpp | 21 ++- src/platform/qt/library/LibraryEntry.h | 1 + src/platform/qt/library/LibraryModel.cpp | 124 +++++++++++------- src/platform/qt/library/LibraryModel.h | 4 +- src/platform/qt/test/library.cpp | 10 ++ src/platform/qt/utils.cpp | 2 + 9 files changed, 133 insertions(+), 57 deletions(-) diff --git a/src/core/library.c b/src/core/library.c index 24f3ddd42..9a7f1b636 100644 --- a/src/core/library.c +++ b/src/core/library.c @@ -222,7 +222,7 @@ struct mLibrary* mLibraryLoad(const char* path) { goto error; } - static const char insertRom[] = "INSERT INTO roms (crc32, md5, sha1, size, internalCode, platform, :models) VALUES (:crc32, :md5, :sha1, :size, :internalCode, :platform, :models);"; + static const char insertRom[] = "INSERT INTO roms (crc32, md5, sha1, size, internalCode, platform, models) VALUES (:crc32, :md5, :sha1, :size, :internalCode, :platform, :models);"; if (sqlite3_prepare_v2(library->db, insertRom, -1, &library->insertRom, NULL)) { goto error; } diff --git a/src/platform/qt/library/LibraryController.cpp b/src/platform/qt/library/LibraryController.cpp index 198d14080..87d9ab5e2 100644 --- a/src/platform/qt/library/LibraryController.cpp +++ b/src/platform/qt/library/LibraryController.cpp @@ -61,13 +61,7 @@ LibraryController::LibraryController(QWidget* parent, const QString& path, Confi QObject::connect(m_libraryModel, &QAbstractItemModel::rowsInserted, &m_expandThrottle, qOverload<>(&QTimer::start)); LibraryStyle libraryStyle = LibraryStyle(m_config->getOption("libraryStyle", int(LibraryStyle::STYLE_LIST)).toInt()); - // Make sure setViewStyle does something - if (libraryStyle == LibraryStyle::STYLE_TREE) { - m_currentStyle = LibraryStyle::STYLE_LIST; - } else { - m_currentStyle = LibraryStyle::STYLE_TREE; - } - setViewStyle(libraryStyle); + updateViewStyle(libraryStyle); QVariant librarySort = m_config->getQtOption("librarySort"); QVariant librarySortOrder = m_config->getQtOption("librarySortOrder"); @@ -89,6 +83,10 @@ void LibraryController::setViewStyle(LibraryStyle newStyle) { if (m_currentStyle == newStyle) { return; } + updateViewStyle(newStyle); +} + +void LibraryController::updateViewStyle(LibraryStyle newStyle) { QString selected; if (m_currentView) { QModelIndex selectedIndex = m_currentView->selectionModel()->currentIndex(); @@ -266,16 +264,24 @@ void LibraryController::resizeEvent(QResizeEvent*) { resizeTreeView(false); } +// This function automatically reallocates the horizontal space between the +// columns in the view in a useful way when the window is resized. void LibraryController::resizeTreeView(bool expand) { + // When new items are added to the model, make sure they are revealed. if (expand) { m_treeView->expandAll(); } + // Start off by asking the view how wide it thinks each column should be. int viewportWidth = m_treeView->viewport()->width(); int totalWidth = m_treeView->header()->sectionSizeHint(LibraryModel::MAX_COLUMN); for (int column = 0; column < LibraryModel::MAX_COLUMN; column++) { totalWidth += m_treeView->columnWidth(column); } + + // If there would be empty space, ask the view to redistribute it. + // The final column is set to fill any remaining width, so this + // should (at least) fill the window. if (totalWidth < viewportWidth) { totalWidth = 0; for (int column = 0; column <= LibraryModel::MAX_COLUMN; column++) { @@ -283,6 +289,10 @@ void LibraryController::resizeTreeView(bool expand) { totalWidth += m_treeView->columnWidth(column); } } + + // If the columns would be too wide for the view now, try shrinking the + // "Location" column down to reduce horizontal scrolling, with a fixed + // minimum width of 100px. if (totalWidth > viewportWidth) { int locationWidth = m_treeView->columnWidth(LibraryModel::COL_LOCATION); if (locationWidth > 100) { @@ -291,7 +301,6 @@ void LibraryController::resizeTreeView(bool expand) { newLocationWidth = 100; } m_treeView->setColumnWidth(LibraryModel::COL_LOCATION, newLocationWidth); - totalWidth = totalWidth - locationWidth + newLocationWidth; } } } diff --git a/src/platform/qt/library/LibraryController.h b/src/platform/qt/library/LibraryController.h index 8a14f3a4b..483010c79 100644 --- a/src/platform/qt/library/LibraryController.h +++ b/src/platform/qt/library/LibraryController.h @@ -75,6 +75,7 @@ protected: private: void loadDirectory(const QString&, bool recursive = true); // Called on separate thread + void updateViewStyle(LibraryStyle newStyle); ConfigController* m_config = nullptr; std::shared_ptr m_library; diff --git a/src/platform/qt/library/LibraryEntry.cpp b/src/platform/qt/library/LibraryEntry.cpp index 25a5976c9..c2aeae1ab 100644 --- a/src/platform/qt/library/LibraryEntry.cpp +++ b/src/platform/qt/library/LibraryEntry.cpp @@ -12,7 +12,21 @@ using namespace QGBA; -static inline uint64_t checkHash(size_t filesize, uint32_t crc32) { +static inline uint64_t getSha1Prefix(const uint8_t* sha1) { + return *reinterpret_cast(sha1); +} + +static inline uint64_t getSha1Prefix(const QByteArray& sha1) { + if (sha1.size() < 8) { + return 0; + } + return getSha1Prefix((const uint8_t*)sha1.constData()); +} + +static inline uint64_t checkHash(size_t filesize, uint32_t crc32, uint64_t sha1Prefix) { + if (sha1Prefix) { + return sha1Prefix; + } return (uint64_t(filesize) << 32) ^ ((crc32 + 1ULL) * (uint32_t(filesize) + 1ULL)); } @@ -27,6 +41,7 @@ LibraryEntry::LibraryEntry(const mLibraryEntry* entry) , platformModels(entry->platformModels) , filesize(entry->filesize) , crc32(entry->crc32) + , sha1(reinterpret_cast(entry->sha1), sizeof(entry->sha1)) { } @@ -50,9 +65,9 @@ bool LibraryEntry::operator==(const LibraryEntry& other) const { } uint64_t LibraryEntry::checkHash() const { - return ::checkHash(filesize, crc32); + return ::checkHash(filesize, crc32, getSha1Prefix(sha1)); } uint64_t LibraryEntry::checkHash(const mLibraryEntry* entry) { - return ::checkHash(entry->filesize, entry->crc32); + return ::checkHash(entry->filesize, entry->crc32, getSha1Prefix(entry->sha1)); } diff --git a/src/platform/qt/library/LibraryEntry.h b/src/platform/qt/library/LibraryEntry.h index 3d8e6c8a8..cdd132840 100644 --- a/src/platform/qt/library/LibraryEntry.h +++ b/src/platform/qt/library/LibraryEntry.h @@ -37,6 +37,7 @@ struct LibraryEntry { int platformModels; size_t filesize; uint32_t crc32; + QByteArray sha1; LibraryEntry& operator=(const LibraryEntry&) = default; LibraryEntry& operator=(LibraryEntry&&) = default; diff --git a/src/platform/qt/library/LibraryModel.cpp b/src/platform/qt/library/LibraryModel.cpp index db530c177..f700a18eb 100644 --- a/src/platform/qt/library/LibraryModel.cpp +++ b/src/platform/qt/library/LibraryModel.cpp @@ -22,25 +22,28 @@ static const QStringList iconSets{ "GBC", "GB", "SGB", - // "DS", }; +static QHash platformIcons; + LibraryModel::LibraryModel(QObject* parent) : QAbstractItemModel(parent) , m_treeMode(false) , m_showFilename(false) { - for (const QString& platform : iconSets) { - QString pathTemplate = QStringLiteral(":/res/%1-icon%2").arg(platform.toLower()); - QIcon icon; - icon.addFile(pathTemplate.arg("-256.png"), QSize(256, 256)); - icon.addFile(pathTemplate.arg("-128.png"), QSize(128, 128)); - icon.addFile(pathTemplate.arg("-32.png"), QSize(32, 32)); - icon.addFile(pathTemplate.arg("-24.png"), QSize(24, 24)); - icon.addFile(pathTemplate.arg("-16.png"), QSize(16, 16)); - // This will silently and harmlessly fail if QSvgIconEngine isn't compiled in. - icon.addFile(pathTemplate.arg(".svg")); - m_icons[platform] = icon; + if (platformIcons.isEmpty()) { + for (const QString& platform : iconSets) { + QString pathTemplate = QStringLiteral(":/res/%1-icon%2").arg(platform.toLower()); + QIcon icon; + icon.addFile(pathTemplate.arg("-256.png"), QSize(256, 256)); + icon.addFile(pathTemplate.arg("-128.png"), QSize(128, 128)); + icon.addFile(pathTemplate.arg("-32.png"), QSize(32, 32)); + icon.addFile(pathTemplate.arg("-24.png"), QSize(24, 24)); + icon.addFile(pathTemplate.arg("-16.png"), QSize(16, 16)); + // This will silently and harmlessly fail if QSvgIconEngine isn't compiled in. + icon.addFile(pathTemplate.arg(".svg")); + platformIcons[platform] = icon; + } } } @@ -174,9 +177,13 @@ void LibraryModel::updateEntries(const QList& items) { } void LibraryModel::removeEntries(const QList& items) { - SpanSet removedRootSpans; + SpanSet removedRootSpans, removedGameSpans; QHash removedTreeSpans; int firstModifiedIndex = m_games.size(); + + // Remove the items from the game index and assemble a span + // set so that we can later inform the view of which rows + // were removed in an optimized way. for (const QString& item : items) { int pos = m_gameIndex.value(item, -1); Q_ASSERT(pos >= 0); @@ -189,12 +196,18 @@ void LibraryModel::removeEntries(const QList& items) { QList& pathItems = m_pathIndex[entry->base]; int pathPos = pathItems.indexOf(entry); Q_ASSERT(pathPos >= 0); + removedGameSpans.add(pos); removedTreeSpans[entry->base].add(pathPos); - if (!m_treeMode) { - removedRootSpans.add(pos); - } m_gameIndex.remove(item); } + + if (!m_treeMode) { + // If not using a tree view, all entries are root entries. + removedRootSpans = removedGameSpans; + } + + // Remove the paths from the path indexes. + // If it's a tree view, inform the view. for (const QString& base : removedTreeSpans.keys()) { SpanSet& spanSet = removedTreeSpans[base]; spanSet.merge(); @@ -223,6 +236,9 @@ void LibraryModel::removeEntries(const QList& items) { } } } + + // Remove the games from the backing store and path indexes, + // and tell the view to remove the root items. removedRootSpans.merge(); removedRootSpans.sort(true); for (const SpanSet::Span& span : removedRootSpans.spans) { @@ -233,10 +249,21 @@ void LibraryModel::removeEntries(const QList& items) { m_pathIndex.remove(base); } } else { + // In list view, remove games from the backing store immediately m_games.erase(m_games.begin() + span.left, m_games.begin() + span.right + 1); } endRemoveRows(); } + if (m_treeMode) { + // In tree view, remove them after cleaning up the path indexes. + removedGameSpans.merge(); + removedGameSpans.sort(true); + for (const SpanSet::Span& span : removedGameSpans.spans) { + m_games.erase(m_games.begin() + span.left, m_games.begin() + span.right + 1); + } + } + + // Finally, update the game index for the remaining items. for (int i = m_games.size() - 1; i >= firstModifiedIndex; i--) { m_gameIndex[m_games[i]->fullpath] = i; } @@ -294,8 +321,7 @@ int LibraryModel::rowCount(const QModelIndex& parent) const { return m_games.size(); } -QVariant LibraryModel::folderData(const QModelIndex& index, int role) const -{ +QVariant LibraryModel::folderData(const QModelIndex& index, int role) const { // Precondition: index and role must have already been validated if (role == Qt::DecorationRole) { return qApp->style()->standardIcon(QStyle::SP_DirOpenIcon); @@ -311,26 +337,34 @@ QVariant LibraryModel::folderData(const QModelIndex& index, int role) const } QVariant LibraryModel::data(const QModelIndex& index, int role) const { - if (role != Qt::DisplayRole && - role != Qt::EditRole && - role != Qt::ToolTipRole && - role != Qt::DecorationRole && - role != Qt::TextAlignmentRole && - role != FullPathRole) { - return QVariant(); + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: + case Qt::TextAlignmentRole: + case FullPathRole: + break; + case Qt::ToolTipRole: + if (index.column() > COL_LOCATION) { + return QVariant(); + } + break; + case Qt::DecorationRole: + if (index.column() != COL_NAME) { + return QVariant(); + } + break; + default: + return QVariant(); } + if (!checkIndex(index)) { return QVariant(); } - if (role == Qt::ToolTipRole && index.column() > COL_LOCATION) { - return QVariant(); - } - if (role == Qt::DecorationRole && index.column() != COL_NAME) { - return QVariant(); - } + if (role == Qt::TextAlignmentRole) { return index.column() == COL_SIZE ? (int)(Qt::AlignTrailing | Qt::AlignVCenter) : (int)(Qt::AlignLeading | Qt::AlignVCenter); } + const LibraryEntry* entry = nullptr; if (m_treeMode) { if (!index.parent().isValid()) { @@ -341,26 +375,28 @@ QVariant LibraryModel::data(const QModelIndex& index, int role) const { } else if (!index.parent().isValid() && index.row() < (int)m_games.size()) { entry = m_games[index.row()].get(); } + if (entry) { if (role == FullPathRole) { return entry->fullpath; } switch (index.column()) { - case COL_NAME: - if (role == Qt::DecorationRole) { - return m_icons.value(entry->displayPlatform(), qApp->style()->standardIcon(QStyle::SP_FileIcon)); - } - return entry->displayTitle(m_showFilename); - case COL_LOCATION: - return QDir::toNativeSeparators(entry->base); - case COL_PLATFORM: - return nicePlatformFormat(entry->platform); - case COL_SIZE: - return (role == Qt::DisplayRole) ? QVariant(niceSizeFormat(entry->filesize)) : QVariant(int(entry->filesize)); - case COL_CRC32: - return (role == Qt::DisplayRole) ? QVariant(QStringLiteral("%0").arg(entry->crc32, 8, 16, QChar('0'))) : QVariant(entry->crc32); + case COL_NAME: + if (role == Qt::DecorationRole) { + return platformIcons.value(entry->displayPlatform(), qApp->style()->standardIcon(QStyle::SP_FileIcon)); + } + return entry->displayTitle(m_showFilename); + case COL_LOCATION: + return QDir::toNativeSeparators(entry->base); + case COL_PLATFORM: + return nicePlatformFormat(entry->platform); + case COL_SIZE: + return (role == Qt::DisplayRole) ? QVariant(niceSizeFormat(entry->filesize)) : QVariant(int(entry->filesize)); + case COL_CRC32: + return (role == Qt::DisplayRole) ? QVariant(QStringLiteral("%0").arg(entry->crc32, 8, 16, QChar('0'))) : QVariant(entry->crc32); } } + return QVariant(); } diff --git a/src/platform/qt/library/LibraryModel.h b/src/platform/qt/library/LibraryModel.h index 1d57c1c41..dd78026c8 100644 --- a/src/platform/qt/library/LibraryModel.h +++ b/src/platform/qt/library/LibraryModel.h @@ -17,6 +17,7 @@ #include "LibraryEntry.h" class QTreeView; +class LibraryModelTest; namespace QGBA { @@ -62,6 +63,8 @@ public: LibraryEntry entry(const QString& game) const; private: + friend class ::LibraryModelTest; + QModelIndex indexForPath(const QString& path); QModelIndex indexForPath(const QString& path) const; @@ -78,7 +81,6 @@ private: QStringList m_pathOrder; QHash> m_pathIndex; QHash m_gameIndex; - QHash m_icons; }; } diff --git a/src/platform/qt/test/library.cpp b/src/platform/qt/test/library.cpp index 2cda06694..c189f8259 100644 --- a/src/platform/qt/test/library.cpp +++ b/src/platform/qt/test/library.cpp @@ -128,13 +128,18 @@ private slots: void testList() { addTestGames1(); QCOMPARE(model->rowCount(), 3); + QCOMPARE(model->m_games.size(), 3); addTestGames2(); QCOMPARE(model->rowCount(), 8); + QCOMPARE(model->m_games.size(), 8); updateGame(); + QCOMPARE(model->m_games.size(), 8); model->removeEntries({ "/gba/Another.gba", "/gb/Game 6.gb" }); QCOMPARE(model->rowCount(), 6); + QCOMPARE(model->m_games.size(), 6); model->removeEntries({ "/gb/Old Game.gb", "/gb/Game 7.gb" }); QCOMPARE(model->rowCount(), 4); + QCOMPARE(model->m_games.size(), 4); } void testTree() { @@ -144,19 +149,24 @@ private slots: QCOMPARE(model->rowCount(), 2); QCOMPARE(model->rowCount(model->index(gbRow, 0)), 1); QCOMPARE(model->rowCount(model->index(gbaRow, 0)), 2); + QCOMPARE(model->m_games.size(), 3); addTestGames2(); QCOMPARE(model->rowCount(), 2); QCOMPARE(model->rowCount(model->index(gbRow, 0)), 3); QCOMPARE(model->rowCount(model->index(gbaRow, 0)), 5); + QCOMPARE(model->m_games.size(), 8); updateGame(); + QCOMPARE(model->m_games.size(), 8); removeGames1(); QCOMPARE(model->rowCount(), 2); QCOMPARE(model->rowCount(model->index(gbRow, 0)), 2); QCOMPARE(model->rowCount(model->index(gbaRow, 0)), 4); + QCOMPARE(model->m_games.size(), 6); removeGames2(); QVERIFY2(!find("gb").isValid(), "did not remove gb folder"); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->rowCount(model->index(0, 0)), 4); + QCOMPARE(model->m_games.size(), 4); } void modeSwitchTest1() { diff --git a/src/platform/qt/utils.cpp b/src/platform/qt/utils.cpp index 1d12310ed..1df387e24 100644 --- a/src/platform/qt/utils.cpp +++ b/src/platform/qt/utils.cpp @@ -6,7 +6,9 @@ #include "utils.h" #include +#ifdef M_CORE_GB #include +#endif #include #include From bbc61e0f38080674867f6da8de7911edf84627fe Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 2 Apr 2025 14:50:12 -0700 Subject: [PATCH 38/74] Qt: Update copyright year --- src/platform/qt/AboutScreen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/AboutScreen.cpp b/src/platform/qt/AboutScreen.cpp index dae9e8dc9..0276477c4 100644 --- a/src/platform/qt/AboutScreen.cpp +++ b/src/platform/qt/AboutScreen.cpp @@ -74,7 +74,7 @@ AboutScreen::AboutScreen(QWidget* parent) { QString copyright = m_ui.copyright->text(); - copyright.replace("{year}", QLatin1String("2023")); + copyright.replace("{year}", QLatin1String("2025")); m_ui.copyright->setText(copyright); } } From e95b81f1f7b95161fbda81fa5e931e3bcb193ccf Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 3 Apr 2025 00:41:05 -0700 Subject: [PATCH 39/74] CMake: Bump to 3.10 minimum This really is only needed to get CMake to stop yelling at us... --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c7413c1f..f7e84cf0d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.3) +cmake_minimum_required(VERSION 3.10) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/src/platform/cmake/") if(POLICY CMP0025) From 3ae429fd91b06e551cd8b752ca642ddb9e385699 Mon Sep 17 00:00:00 2001 From: CasualPokePlayer <50538166+CasualPokePlayer@users.noreply.github.com> Date: Sat, 5 Apr 2025 17:14:20 -0700 Subject: [PATCH 40/74] Implement GPIO internal write latch Regardless of direction, a write to GPIO data actually succeeds fully with all 4 bits. However, this does not directly touch the pin state. It instead places it into an internal write only latch. This latch asserts bits onto the pin state if direction allows for such, otherwise the other side (e.g. RTC) will be the one asserting bits (but this does not end up touching the internal write latch). The implementation here is likely not entirely accurate for direction changes from out to in (as that depends on each external device implementation), but it should be correct for in to out changes. --- include/mgba/internal/gba/cart/gpio.h | 1 + include/mgba/internal/gba/serialize.h | 3 ++- src/gba/cart/gpio.c | 19 ++++++++++++++----- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/include/mgba/internal/gba/cart/gpio.h b/include/mgba/internal/gba/cart/gpio.h index be3cd9173..9aebfa11b 100644 --- a/include/mgba/internal/gba/cart/gpio.h +++ b/include/mgba/internal/gba/cart/gpio.h @@ -68,6 +68,7 @@ struct GBACartridgeHardware { enum GPIODirection readWrite; uint16_t* gpioBase; + uint8_t writeLatch; uint16_t pinState; uint16_t direction; diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index 6e9a3124a..afa4569b0 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -376,7 +376,8 @@ struct GBASerializedState { } dma[4]; struct { - uint16_t pinState; + uint8_t pinState; + uint8_t writeLatch; uint16_t pinDirection; int32_t rtcBytesRemaining; int32_t rtcTransferStep; diff --git a/src/gba/cart/gpio.c b/src/gba/cart/gpio.c index 5798a636e..61b4e389c 100644 --- a/src/gba/cart/gpio.c +++ b/src/gba/cart/gpio.c @@ -49,6 +49,7 @@ void GBAHardwareInit(struct GBACartridgeHardware* hw, uint16_t* base) { void GBAHardwareReset(struct GBACartridgeHardware* hw) { hw->readWrite = GPIO_WRITE_ONLY; + hw->writeLatch = 0; hw->pinState = 0; hw->direction = 0; hw->lightCounter = 0; @@ -64,6 +65,7 @@ void GBAHardwareReset(struct GBACartridgeHardware* hw) { void GBAHardwareClear(struct GBACartridgeHardware* hw) { hw->devices = HW_NONE | (hw->devices & HW_GB_PLAYER_DETECTION); hw->readWrite = GPIO_WRITE_ONLY; + hw->writeLatch = 0; hw->pinState = 0; hw->direction = 0; } @@ -74,16 +76,22 @@ void GBAHardwareGPIOWrite(struct GBACartridgeHardware* hw, uint32_t address, uin } switch (address) { case GPIO_REG_DATA: + hw->writeLatch = value & 0xF; if (!hw->p->vbaBugCompat) { hw->pinState &= ~hw->direction; - hw->pinState |= value & hw->direction; + hw->pinState |= hw->writeLatch & hw->direction; } else { - hw->pinState = value & 0xF; + hw->pinState = hw->writeLatch; } _readPins(hw); break; case GPIO_REG_DIRECTION: hw->direction = value & 0xF; + if (!hw->p->vbaBugCompat) { + hw->pinState &= ~hw->direction; + hw->pinState |= hw->writeLatch & hw->direction; + _readPins(hw); + } break; case GPIO_REG_CONTROL: hw->readWrite = value & 0x1; @@ -475,7 +483,8 @@ uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* hw, uint32_t address) { void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASerializedState* state) { GBASerializedHWFlags1 flags1 = 0; flags1 = GBASerializedHWFlags1SetReadWrite(flags1, hw->readWrite); - STORE_16(hw->pinState, 0, &state->hw.pinState); + state->hw.writeLatch = hw->writeLatch; + state->hw.pinState = hw->pinState; STORE_16(hw->direction, 0, &state->hw.pinDirection); state->hw.devices = hw->devices; @@ -512,9 +521,9 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer GBASerializedHWFlags1 flags1; LOAD_16(flags1, 0, &state->hw.flags1); hw->readWrite = GBASerializedHWFlags1GetReadWrite(flags1); - LOAD_16(hw->pinState, 0, &state->hw.pinState); + hw->writeLatch = state->hw.writeLatch; + hw->pinState = state->hw.pinState; LOAD_16(hw->direction, 0, &state->hw.pinDirection); - hw->pinState &= 0xF; hw->direction &= 0xF; hw->devices = state->hw.devices; From 57d1552582cdf9f37ca788da5562338f7e0c74b7 Mon Sep 17 00:00:00 2001 From: CasualPokePlayer <50538166+CasualPokePlayer@users.noreply.github.com> Date: Sun, 6 Apr 2025 21:08:50 -0700 Subject: [PATCH 41/74] Increment state version, narrow direction in state too --- include/mgba/internal/gba/serialize.h | 11 +++++++---- src/gba/cart/gpio.c | 9 ++++----- src/gba/serialize.c | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index afa4569b0..43a015827 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -165,8 +165,10 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | 0x00288 - 0x0028B: DMA next count * | 0x0028C - 0x0028F: DMA next event * 0x00290 - 0x002C3: GPIO state - * | 0x00290 - 0x00291: Pin state - * | 0x00292 - 0x00293: Direction state + * | 0x00290: Pin state + * | 0x00291: Write latch + * | 0x00292: Direction state + * | 0x00293: Reserved * | 0x00294 - 0x002B6: RTC state (see hardware.h for format) * | 0x002B7 - 0x002B7: GPIO devices * | bit 0: Has RTC values @@ -378,7 +380,8 @@ struct GBASerializedState { struct { uint8_t pinState; uint8_t writeLatch; - uint16_t pinDirection; + uint8_t pinDirection; + uint8_t reserved0; int32_t rtcBytesRemaining; int32_t rtcTransferStep; int32_t rtcBitsRead; @@ -386,7 +389,7 @@ struct GBASerializedState { int32_t rtcCommandActive; RTCCommandData rtcCommand; uint8_t rtcControl; - uint8_t reserved[3]; + uint8_t reserved1[3]; uint8_t time[7]; uint8_t devices; uint16_t gyroSample; diff --git a/src/gba/cart/gpio.c b/src/gba/cart/gpio.c index 61b4e389c..83ba4fa56 100644 --- a/src/gba/cart/gpio.c +++ b/src/gba/cart/gpio.c @@ -485,7 +485,7 @@ void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASeria flags1 = GBASerializedHWFlags1SetReadWrite(flags1, hw->readWrite); state->hw.writeLatch = hw->writeLatch; state->hw.pinState = hw->pinState; - STORE_16(hw->direction, 0, &state->hw.pinDirection); + state->hw.pinDirection = hw->direction; state->hw.devices = hw->devices; STORE_32(hw->rtc.bytesRemaining, 0, &state->hw.rtcBytesRemaining); @@ -521,10 +521,9 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer GBASerializedHWFlags1 flags1; LOAD_16(flags1, 0, &state->hw.flags1); hw->readWrite = GBASerializedHWFlags1GetReadWrite(flags1); - hw->writeLatch = state->hw.writeLatch; - hw->pinState = state->hw.pinState; - LOAD_16(hw->direction, 0, &state->hw.pinDirection); - hw->direction &= 0xF; + hw->writeLatch = state->hw.writeLatch & 0xF; + hw->pinState = state->hw.pinState & 0xF; + hw->direction = state->hw.pinDirection & 0xF; hw->devices = state->hw.devices; if ((hw->devices & HW_GPIO) && hw->gpioBase) { diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 3866c0c80..d4908f539 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -15,7 +15,7 @@ #include MGBA_EXPORT const uint32_t GBASavestateMagic = 0x01000000; -MGBA_EXPORT const uint32_t GBASavestateVersion = 0x00000007; +MGBA_EXPORT const uint32_t GBASavestateVersion = 0x00000008; mLOG_DEFINE_CATEGORY(GBA_STATE, "GBA Savestate", "gba.serialize"); From 8740f3dde13d027ae41ecbe1ff108ba32fc0a32f Mon Sep 17 00:00:00 2001 From: CasualPokePlayer <50538166+CasualPokePlayer@users.noreply.github.com> Date: Sun, 6 Apr 2025 21:17:51 -0700 Subject: [PATCH 42/74] Narrow these in runtime struct too --- include/mgba/internal/gba/cart/gpio.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/mgba/internal/gba/cart/gpio.h b/include/mgba/internal/gba/cart/gpio.h index 9aebfa11b..28c5fa0ea 100644 --- a/include/mgba/internal/gba/cart/gpio.h +++ b/include/mgba/internal/gba/cart/gpio.h @@ -69,8 +69,8 @@ struct GBACartridgeHardware { uint16_t* gpioBase; uint8_t writeLatch; - uint16_t pinState; - uint16_t direction; + uint8_t pinState; + uint8_t direction; struct GBARTC rtc; From a75c6c41e04efca3ad9d81b28a216350d0973c62 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 9 Apr 2025 19:00:20 -0700 Subject: [PATCH 43/74] Third-Party: Fix discord-rpc CMakeLists issues --- src/third-party/discord-rpc/CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/third-party/discord-rpc/CMakeLists.txt b/src/third-party/discord-rpc/CMakeLists.txt index 09e05c3f3..2f272c40b 100644 --- a/src/third-party/discord-rpc/CMakeLists.txt +++ b/src/third-party/discord-rpc/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION 3.1.0) +cmake_minimum_required (VERSION 3.10) project (DiscordRPC) include(GNUInstallDirs) @@ -9,6 +9,10 @@ file(GLOB_RECURSE ALL_SOURCE_FILES src/*.cpp src/*.h src/*.c ) +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-class-memaccess") +endif() + # add subdirs add_subdirectory(src) From 40cae51e2871f95a9721d9e34bd8fa783ef94bea Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Thu, 10 Apr 2025 18:08:20 -0500 Subject: [PATCH 44/74] Qt: replace deprecated QSet APIs --- src/platform/qt/library/LibraryController.cpp | 5 +++-- src/platform/qt/utils.h | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/library/LibraryController.cpp b/src/platform/qt/library/LibraryController.cpp index 87d9ab5e2..8ae4055ba 100644 --- a/src/platform/qt/library/LibraryController.cpp +++ b/src/platform/qt/library/LibraryController.cpp @@ -9,6 +9,7 @@ #include "ConfigController.h" #include "GBAApp.h" #include "LibraryModel.h" +#include "utils.h" #include #include @@ -190,7 +191,7 @@ void LibraryController::refresh() { setDisabled(true); - QSet removedEntries(m_knownGames.keyBegin(), m_knownGames.keyEnd()); + QSet removedEntries(qListToSet(m_knownGames.keys())); QList updatedEntries; QList newEntries; @@ -215,7 +216,7 @@ void LibraryController::refresh() { m_knownGames.remove(path); } - m_libraryModel->removeEntries(QList(removedEntries.begin(), removedEntries.end())); + m_libraryModel->removeEntries(removedEntries.values()); m_libraryModel->updateEntries(updatedEntries); m_libraryModel->addEntries(newEntries); diff --git a/src/platform/qt/utils.h b/src/platform/qt/utils.h index d1e1a7063..3cc7d28f4 100644 --- a/src/platform/qt/utils.h +++ b/src/platform/qt/utils.h @@ -134,4 +134,13 @@ struct SpanSet { QVector spans; }; +template +QSet qListToSet(const QList& list) { +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + return QSet::fromList(list); +#else + return QSet(list.begin(), list.end()); +#endif +} + } From 86453b8107ebd9137b66d61f28e830bd9d9151fb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 9 Apr 2025 19:09:27 -0700 Subject: [PATCH 45/74] SDL: Check for _mSDLOpenJoystick null return --- src/platform/sdl/sdl-events.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index ca8345d22..df5218407 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -396,6 +396,10 @@ void mSDLUpdateJoysticks(struct mSDLEvents* events, const struct Configuration* events->players[i]->joystick = NULL; } struct SDL_JoystickCombo* joystick = _mSDLOpenJoystick(events, event.jdevice.which); + if (!joystick) { + mLOG(SDL_EVENTS, ERROR, "SDL joystick hotplug attach failed: %s", SDL_GetError()); + continue; + } for (i = 0; i < events->playersAttached && i < MAX_PLAYERS; ++i) { if (joysticks[i] != -1) { From 932062c1a46f8685d291e1035663b966cf1ec9e1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 9 Apr 2025 20:30:49 -0700 Subject: [PATCH 46/74] Qt: Fix selecting high tiles in tile and map views (fixes #3461) --- CHANGES | 1 + src/platform/qt/MapView.cpp | 2 +- src/platform/qt/TileView.cpp | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 2c4e2f0e2..8861f8a2d 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,7 @@ Other fixes: - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) - Qt: Fix potential crash when configuring shortcuts - Qt: Fix regression where loading BIOS creates a save file (fixes mgba.io/i/3359) + - Qt: Fix selecting high tiles in tile and map views (fixes mgba.io/i/3461) Misc: - 3DS: Change title ID to avoid conflict with commercial title (fixes mgba.io/i/3023) - Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826) diff --git a/src/platform/qt/MapView.cpp b/src/platform/qt/MapView.cpp index aef8c58b7..d8e215be9 100644 --- a/src/platform/qt/MapView.cpp +++ b/src/platform/qt/MapView.cpp @@ -56,7 +56,7 @@ MapView::MapView(std::shared_ptr controller, QWidget* parent) #ifdef M_CORE_GB case mPLATFORM_GB: m_boundary = 1024; - m_ui.tile->setMaxTile(512); + m_ui.tile->setMaxTile(1024); m_addressBase = GB_BASE_VRAM; m_addressWidth = 4; m_ui.bgInfo->addCustomProperty("screenBase", tr("Map base")); diff --git a/src/platform/qt/TileView.cpp b/src/platform/qt/TileView.cpp index 6ba409a0a..ca75e4f31 100644 --- a/src/platform/qt/TileView.cpp +++ b/src/platform/qt/TileView.cpp @@ -61,7 +61,7 @@ TileView::TileView(std::shared_ptr controller, QWidget* parent) m_ui.tilesBoth->setEnabled(false); m_ui.palette256->setEnabled(false); m_ui.tile->setBoundary(1024, 0, 0); - m_ui.tile->setMaxTile(512); + m_ui.tile->setMaxTile(896); break; #endif default: @@ -201,7 +201,8 @@ void TileView::updateTilesGBA(bool force) { #ifdef M_CORE_GB void TileView::updateTilesGB(bool force) { const GB* gb = static_cast(m_controller->thread()->core->board); - int count = gb->model >= GB_MODEL_CGB ? 1024 : 512; + // TODO: Strip out tiles 384-511, as they aren't valid + int count = gb->model >= GB_MODEL_CGB ? 896 : 384; m_ui.tiles->setTileCount(count); mTileCache* cache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0); for (int i = 0; i < count; ++i) { From 5bf240ac32bd0ceff844221cf60aee6757f7ec3b Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Thu, 10 Apr 2025 18:18:11 -0500 Subject: [PATCH 47/74] Qt: replace QAbstractItemModel::checkIndex() that was introduced in Qt 5.11 --- src/platform/qt/library/LibraryModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/library/LibraryModel.cpp b/src/platform/qt/library/LibraryModel.cpp index f700a18eb..cf4cb58ca 100644 --- a/src/platform/qt/library/LibraryModel.cpp +++ b/src/platform/qt/library/LibraryModel.cpp @@ -357,7 +357,7 @@ QVariant LibraryModel::data(const QModelIndex& index, int role) const { return QVariant(); } - if (!checkIndex(index)) { + if (index.model() != this || index.row() < 0 || index.row() > rowCount() || index.column() < 0 || index.column() > columnCount()) { return QVariant(); } From 35b6003a7d6421ea0eb0ae7d44831888e0a9cc1e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 13 Apr 2025 00:06:20 -0700 Subject: [PATCH 48/74] Qt: Make sure resizeContext doesn't call doneCurrent at end of start() --- 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 ec69acdea..00af3cb8d 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -813,11 +813,11 @@ void PainterGL::start() { mGLES2ShaderAttach(reinterpret_cast(m_backend), static_cast(m_shader.passes), m_shader.nPasses); } #endif - resizeContext(); m_buffer = nullptr; m_active = true; m_started = true; + resizeContext(); swapInterval(1); emit started(); } From c3f3d00f14d202ff4cc6749ea5272218b6d38c83 Mon Sep 17 00:00:00 2001 From: Maximilian Mader Date: Sat, 12 Apr 2025 04:29:12 +0200 Subject: [PATCH 49/74] Crop frame view exports to current video size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The base video size of the Game Boy core is the Super Game Boy’s resolution of 256×224 pixels. Previously frames were exported at that size, leading to a 160×144 frame surrounded by uninitialized memory. This assumes that the base size is always greater or equal to the current video size. --- src/platform/qt/FrameView.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/FrameView.cpp b/src/platform/qt/FrameView.cpp index 245ebf4bb..bec285f7f 100644 --- a/src/platform/qt/FrameView.cpp +++ b/src/platform/qt/FrameView.cpp @@ -594,7 +594,16 @@ void FrameView::exportFrame() { return; } CoreController::Interrupter interrupter(m_controller); - m_framebuffer.save(filename, "PNG"); + + unsigned width, height; + m_vl->currentVideoSize(m_vl, &width, &height); + + if ((int)width != m_framebuffer.width() || (int)height != m_framebuffer.height()) { + QImage crop = m_framebuffer.copy(0, 0, width, height); + crop.save(filename, "PNG"); + } else { + m_framebuffer.save(filename, "PNG"); + } } void FrameView::reset() { From d79579d1caedb5cc3de664893c7e453dbe66a1c0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 13 Apr 2025 17:58:01 -0700 Subject: [PATCH 50/74] Qt: Throttle fatal error dialogs --- CHANGES | 1 + src/platform/qt/CoreController.cpp | 7 ++++++- src/platform/qt/CoreController.h | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 8861f8a2d..a8c0ca04b 100644 --- a/CHANGES +++ b/CHANGES @@ -61,6 +61,7 @@ Misc: - Qt: Support building against Qt 6 - Qt: Add shortcuts to increment fast forward speed (mgba.io/i/2903) - Qt: Enable ROM preloading by default + - Qt: Throttle fatal error dialogs - Res: Port hq2x and OmniScale shaders from SameBoy - Res: Port NSO-gba-colors shader (closes mgba.io/i/2834) - Res: Update gba-colors shader (closes mgba.io/i/2976) diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 7cd6c58d5..55e743be1 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -205,7 +205,8 @@ CoreController::CoreController(mCore* core, QObject* parent) } message = QString::vasprintf(format, args); QMetaObject::invokeMethod(controller, "logPosted", Q_ARG(int, level), Q_ARG(int, category), Q_ARG(const QString&, message)); - if (level == mLOG_FATAL) { + if (level == mLOG_FATAL && !controller->m_crashSeen) { + controller->m_crashSeen = true; QMetaObject::invokeMethod(controller, "crashed", Q_ARG(const QString&, message)); } }; @@ -500,6 +501,7 @@ void CoreController::stop() { } void CoreController::reset() { + m_crashSeen = false; mCoreThreadReset(&m_threadContext); } @@ -651,6 +653,7 @@ void CoreController::loadState(int slot) { m_stateSlot = slot; m_backupSaveState.clear(); } + m_crashSeen = false; mCoreThreadClearCrashed(&m_threadContext); mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { CoreController* controller = static_cast(context->userData); @@ -671,6 +674,7 @@ void CoreController::loadState(const QString& path, int flags) { if (flags != -1) { m_loadStateFlags = flags; } + m_crashSeen = false; mCoreThreadClearCrashed(&m_threadContext); mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { CoreController* controller = static_cast(context->userData); @@ -700,6 +704,7 @@ void CoreController::loadState(QIODevice* iodev, int flags) { if (flags != -1) { m_loadStateFlags = flags; } + m_crashSeen = false; mCoreThreadClearCrashed(&m_threadContext); mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { CoreController* controller = static_cast(context->userData); diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index a6b6a03ca..5e0245e16 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -257,6 +257,7 @@ private: struct CoreLogger : public mLogger { CoreController* self; } m_logger{}; + bool m_crashSeen = false; QString m_path; QString m_baseDirectory; From 44349b0a37cf83d2c1ffd78a51825a9ffebf63db Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Mon, 14 Apr 2025 10:03:06 -0500 Subject: [PATCH 51/74] Qt: fix blank rows in library rendering --- src/platform/qt/library/LibraryModel.cpp | 22 +++++++++++++++++++++- src/platform/qt/library/LibraryModel.h | 2 ++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/library/LibraryModel.cpp b/src/platform/qt/library/LibraryModel.cpp index cf4cb58ca..6c09025b7 100644 --- a/src/platform/qt/library/LibraryModel.cpp +++ b/src/platform/qt/library/LibraryModel.cpp @@ -336,6 +336,26 @@ QVariant LibraryModel::folderData(const QModelIndex& index, int role) const { return QVariant(); } +bool LibraryModel::validateIndex(const QModelIndex& index) const +{ + if (index.model() != this || index.row() < 0 || index.column() < 0 || index.column() > MAX_COLUMN) { + // Obviously invalid index + return false; + } + + if (index.parent().isValid() && !validateIndex(index.parent())) { + // Parent index is invalid + return false; + } + + if (index.row() >= rowCount(index.parent())) { + // Row is out of bounds for this level of hierarchy + return false; + } + + return true; +} + QVariant LibraryModel::data(const QModelIndex& index, int role) const { switch (role) { case Qt::DisplayRole: @@ -357,7 +377,7 @@ QVariant LibraryModel::data(const QModelIndex& index, int role) const { return QVariant(); } - if (index.model() != this || index.row() < 0 || index.row() > rowCount() || index.column() < 0 || index.column() > columnCount()) { + if (!validateIndex(index)) { return QVariant(); } diff --git a/src/platform/qt/library/LibraryModel.h b/src/platform/qt/library/LibraryModel.h index dd78026c8..651bbe7b3 100644 --- a/src/platform/qt/library/LibraryModel.h +++ b/src/platform/qt/library/LibraryModel.h @@ -70,6 +70,8 @@ private: QVariant folderData(const QModelIndex& index, int role = Qt::DisplayRole) const; + bool validateIndex(const QModelIndex& index) const; + void addEntriesList(const QList& items); void addEntriesTree(const QList& items); void addEntryInternal(const LibraryEntry& item); From 3565c12d8ce3bddf380c4b41e86e3ac25b722968 Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Mon, 14 Apr 2025 20:00:30 -0500 Subject: [PATCH 52/74] Qt: don't restrict by model when loading from the library --- src/platform/qt/library/LibraryController.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/qt/library/LibraryController.cpp b/src/platform/qt/library/LibraryController.cpp index 8ae4055ba..1ed5411b5 100644 --- a/src/platform/qt/library/LibraryController.cpp +++ b/src/platform/qt/library/LibraryController.cpp @@ -152,6 +152,7 @@ VFile* LibraryController::selectedVFile() { libentry.base = baseUtf8.constData(); libentry.filename = filenameUtf8.constData(); libentry.platform = mPLATFORM_NONE; + libentry.platformModels = M_LIBRARY_MODEL_UNKNOWN; return mLibraryOpenVFile(m_library.get(), &libentry); } else { return nullptr; From 939c8f0487377384f0c7ce86cde83eba63a48014 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 14 Apr 2025 02:23:26 -0700 Subject: [PATCH 53/74] Qt: Clean up some FrameView technical debt --- src/platform/qt/Window.cpp | 9 +-------- src/platform/qt/Window.h | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 97d77ed84..ad18ab934 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1754,14 +1754,7 @@ void Window::setupMenu(QMenuBar* menubar) { addGameAction(tr("&Frame inspector..."), "frameWindow", [this]() { if (!m_frameView) { m_frameView = new FrameView(m_controller); - connect(this, &Window::shutdown, this, [this]() { - if (m_frameView) { - m_frameView->close(); - } - }); - connect(m_frameView, &QObject::destroyed, this, [this]() { - m_frameView = nullptr; - }); + connect(this, &Window::shutdown, m_frameView, &QWidget::close); m_frameView->setAttribute(Qt::WA_DeleteOnClose); } m_frameView->show(); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index cb467f89d..5c73032b5 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -244,7 +244,7 @@ private: std::unique_ptr m_overrideView; std::unique_ptr m_sensorView; std::unique_ptr m_dolphinView; - FrameView* m_frameView = nullptr; + QPointer m_frameView; #ifdef USE_FFMPEG std::unique_ptr m_videoView; From 9bbf6b317378fac97d975191479de301d50c13f9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 15 Apr 2025 00:03:05 -0700 Subject: [PATCH 54/74] Qt: Clean up named view initialization and fix raising --- src/platform/qt/GIFView.cpp | 4 ++- src/platform/qt/GIFView.h | 2 +- src/platform/qt/VideoView.cpp | 4 ++- src/platform/qt/VideoView.h | 2 +- src/platform/qt/Window.cpp | 59 ++++++++++++++++++++--------------- src/platform/qt/Window.h | 14 ++++----- 6 files changed, 49 insertions(+), 36 deletions(-) diff --git a/src/platform/qt/GIFView.cpp b/src/platform/qt/GIFView.cpp index c0b2d3994..c3bf496da 100644 --- a/src/platform/qt/GIFView.cpp +++ b/src/platform/qt/GIFView.cpp @@ -15,7 +15,7 @@ using namespace QGBA; -GIFView::GIFView(QWidget* parent) +GIFView::GIFView(std::shared_ptr controller, QWidget* parent) : QWidget(parent) { m_ui.setupUi(this); @@ -31,6 +31,8 @@ GIFView::GIFView(QWidget* parent) FFmpegEncoderInit(&m_encoder); FFmpegEncoderSetAudio(&m_encoder, nullptr, 0); + + setController(controller); } GIFView::~GIFView() { diff --git a/src/platform/qt/GIFView.h b/src/platform/qt/GIFView.h index 37f71fd93..c6f1230a9 100644 --- a/src/platform/qt/GIFView.h +++ b/src/platform/qt/GIFView.h @@ -23,7 +23,7 @@ class GIFView : public QWidget { Q_OBJECT public: - GIFView(QWidget* parent = nullptr); + GIFView(std::shared_ptr controller, QWidget* parent = nullptr); virtual ~GIFView(); mAVStream* getStream() { return &m_encoder.d; } diff --git a/src/platform/qt/VideoView.cpp b/src/platform/qt/VideoView.cpp index 21e50f396..356133bbb 100644 --- a/src/platform/qt/VideoView.cpp +++ b/src/platform/qt/VideoView.cpp @@ -47,7 +47,7 @@ bool VideoView::Preset::compatible(const Preset& other) const { return true; } -VideoView::VideoView(QWidget* parent) +VideoView::VideoView(std::shared_ptr controller, QWidget* parent) : QWidget(parent) { m_ui.setupUi(this); @@ -133,6 +133,8 @@ VideoView::VideoView(QWidget* parent) m_ui.presetYoutube->setChecked(true); // Use the Youtube preset by default showAdvanced(false); + + setController(controller); } void VideoView::updatePresets() { diff --git a/src/platform/qt/VideoView.h b/src/platform/qt/VideoView.h index 9e3ab5ca2..4c61800d4 100644 --- a/src/platform/qt/VideoView.h +++ b/src/platform/qt/VideoView.h @@ -26,7 +26,7 @@ class VideoView : public QWidget { Q_OBJECT public: - VideoView(QWidget* parent = nullptr); + VideoView(std::shared_ptr controller, QWidget* parent = nullptr); virtual ~VideoView(); mAVStream* getStream() { return &m_encoder.d; } diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index ad18ab934..da1c80847 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -580,29 +580,35 @@ std::function Window::openControllerTView(A... arg) { } template -std::function Window::openNamedTView(std::unique_ptr* name, A... arg) { +std::function Window::openNamedTView(QPointer* name, bool keepalive, A... arg) { return [=]() { if (!*name) { - *name = std::make_unique(arg...); + *name = new T(arg...); connect(this, &Window::shutdown, name->get(), &QWidget::close); + if (!keepalive) { + (*name)->setAttribute(Qt::WA_DeleteOnClose); + } } (*name)->show(); - (*name)->setFocus(Qt::PopupFocusReason); + (*name)->activateWindow(); + (*name)->raise(); }; } template -std::function Window::openNamedControllerTView(std::unique_ptr* name, A... arg) { +std::function Window::openNamedControllerTView(QPointer* name, bool keepalive, A... arg) { return [=]() { if (!*name) { - *name = std::make_unique(arg...); - if (m_controller) { - (*name)->setController(m_controller); - } + *name = new T(m_controller, arg...); + connect(m_controller.get(), &CoreController::stopping, name->get(), &QWidget::close); connect(this, &Window::shutdown, name->get(), &QWidget::close); + if (!keepalive) { + (*name)->setAttribute(Qt::WA_DeleteOnClose); + } } (*name)->show(); - (*name)->setFocus(Qt::PopupFocusReason); + (*name)->activateWindow(); + (*name)->raise(); }; } @@ -1414,7 +1420,7 @@ void Window::setupMenu(QMenuBar* menubar) { m_multiWindow = m_actions.addAction(tr("New multiplayer window"), "multiWindow", GBAApp::app(), &GBAApp::newWindow, "file"); #ifdef M_CORE_GBA - auto dolphin = m_actions.addAction(tr("Connect to Dolphin..."), "connectDolphin", openNamedTView(&m_dolphinView, this), "file"); + auto dolphin = m_actions.addAction(tr("Connect to Dolphin..."), "connectDolphin", openNamedTView(&m_dolphinView, true, this), "file"); m_platformActions.insert(mPLATFORM_GBA, dolphin); #endif @@ -1695,8 +1701,8 @@ void Window::setupMenu(QMenuBar* menubar) { #endif #ifdef USE_FFMPEG - addGameAction(tr("Record A/V..."), "recordOutput", openNamedControllerTView(&m_videoView), "av"); - addGameAction(tr("Record GIF/WebP/APNG..."), "recordGIF", openNamedControllerTView(&m_gifView), "av"); + addGameAction(tr("Record A/V..."), "recordOutput", openNamedControllerTView(&m_videoView, true), "av"); + addGameAction(tr("Record GIF/WebP/APNG..."), "recordGIF", openNamedControllerTView(&m_gifView, true), "av"); #endif m_actions.addSeparator("av"); @@ -1710,17 +1716,29 @@ void Window::setupMenu(QMenuBar* menubar) { m_actions.addAction(tr("Game &overrides..."), "overrideWindow", [this]() { if (!m_overrideView) { - m_overrideView = std::make_unique(m_config); + m_overrideView = new OverrideView(m_config); if (m_controller) { m_overrideView->setController(m_controller); } connect(this, &Window::shutdown, m_overrideView.get(), &QWidget::close); } m_overrideView->show(); - m_overrideView->recheck(); + m_overrideView->activateWindow(); + m_overrideView->raise(); }, "tools"); - m_actions.addAction(tr("Game Pak sensors..."), "sensorWindow", openNamedControllerTView(&m_sensorView, &m_inputController), "tools"); + m_actions.addAction(tr("Game Pak sensors..."), "sensorWindow", [this]() { + if (!m_sensorView) { + m_sensorView = new SensorView(&m_inputController); + if (m_controller) { + m_sensorView->setController(m_controller); + } + connect(this, &Window::shutdown, m_sensorView.get(), &QWidget::close); + } + m_sensorView->show(); + m_sensorView->activateWindow(); + m_sensorView->raise(); + }, "tools"); addGameAction(tr("&Cheats..."), "cheatsWindow", openControllerTView(), "tools"); #ifdef ENABLE_SCRIPTING @@ -1750,16 +1768,7 @@ void Window::setupMenu(QMenuBar* menubar) { addGameAction(tr("View &sprites..."), "spriteWindow", openControllerTView(), "stateViews"); addGameAction(tr("View &tiles..."), "tileWindow", openControllerTView(), "stateViews"); addGameAction(tr("View &map..."), "mapWindow", openControllerTView(), "stateViews"); - - addGameAction(tr("&Frame inspector..."), "frameWindow", [this]() { - if (!m_frameView) { - m_frameView = new FrameView(m_controller); - connect(this, &Window::shutdown, m_frameView, &QWidget::close); - m_frameView->setAttribute(Qt::WA_DeleteOnClose); - } - m_frameView->show(); - }, "stateViews"); - + addGameAction(tr("&Frame inspector..."), "frameWindow", openNamedControllerTView(&m_frameView, false), "stateViews"); addGameAction(tr("View memory..."), "memoryView", openControllerTView(), "stateViews"); addGameAction(tr("Search memory..."), "memorySearch", openControllerTView(), "stateViews"); addGameAction(tr("View &I/O registers..."), "ioViewer", openControllerTView(), "stateViews"); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 5c73032b5..0ae94397d 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -177,8 +177,8 @@ private: template std::function openTView(A... arg); template std::function openControllerTView(A... arg); - template std::function openNamedTView(std::unique_ptr*, A... arg); - template std::function openNamedControllerTView(std::unique_ptr*, A... arg); + template std::function openNamedTView(QPointer*, bool keepalive, A... arg); + template std::function openNamedControllerTView(QPointer*, bool keepalive, A... arg); std::shared_ptr addGameAction(const QString& visibleName, const QString& name, Action::Function action, const QString& menu = {}, const QKeySequence& = {}); template std::shared_ptr addGameAction(const QString& visibleName, const QString& name, T* obj, V (T::*action)(), const QString& menu = {}, const QKeySequence& = {}); @@ -241,14 +241,14 @@ private: bool m_multiActive = true; int m_playerId; - std::unique_ptr m_overrideView; - std::unique_ptr m_sensorView; - std::unique_ptr m_dolphinView; + QPointer m_overrideView; + QPointer m_sensorView; + QPointer m_dolphinView; QPointer m_frameView; #ifdef USE_FFMPEG - std::unique_ptr m_videoView; - std::unique_ptr m_gifView; + QPointer m_videoView; + QPointer m_gifView; #endif #ifdef ENABLE_GDB_STUB From bacd3545e8b6a638ecb3da321c60a4236b68cba6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 15 Apr 2025 00:11:57 -0700 Subject: [PATCH 55/74] Qt: Fix Qt 5 build --- src/platform/qt/Window.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index da1c80847..3fafb1d7d 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -584,7 +584,7 @@ std::function Window::openNamedTView(QPointer* name, bool keepalive, return [=]() { if (!*name) { *name = new T(arg...); - connect(this, &Window::shutdown, name->get(), &QWidget::close); + connect(this, &Window::shutdown, name->data(), &QWidget::close); if (!keepalive) { (*name)->setAttribute(Qt::WA_DeleteOnClose); } @@ -600,8 +600,8 @@ std::function Window::openNamedControllerTView(QPointer* name, bool k return [=]() { if (!*name) { *name = new T(m_controller, arg...); - connect(m_controller.get(), &CoreController::stopping, name->get(), &QWidget::close); - connect(this, &Window::shutdown, name->get(), &QWidget::close); + connect(m_controller.get(), &CoreController::stopping, name->data(), &QWidget::close); + connect(this, &Window::shutdown, name->data(), &QWidget::close); if (!keepalive) { (*name)->setAttribute(Qt::WA_DeleteOnClose); } From 46c0464da3d7efcfcbb4733f76d6ca9236e54ffd Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 15 Apr 2025 00:14:35 -0700 Subject: [PATCH 56/74] Qt: Fix Qt 5 build harder --- src/platform/qt/Window.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 3fafb1d7d..9611f0dda 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1720,7 +1720,7 @@ void Window::setupMenu(QMenuBar* menubar) { if (m_controller) { m_overrideView->setController(m_controller); } - connect(this, &Window::shutdown, m_overrideView.get(), &QWidget::close); + connect(this, &Window::shutdown, m_overrideView.data(), &QWidget::close); } m_overrideView->show(); m_overrideView->activateWindow(); @@ -1733,7 +1733,7 @@ void Window::setupMenu(QMenuBar* menubar) { if (m_controller) { m_sensorView->setController(m_controller); } - connect(this, &Window::shutdown, m_sensorView.get(), &QWidget::close); + connect(this, &Window::shutdown, m_sensorView.data(), &QWidget::close); } m_sensorView->show(); m_sensorView->activateWindow(); From 28ef99e056a02c0f48b45bb88d5ce1534709120c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 19 Apr 2025 20:33:35 -0700 Subject: [PATCH 57/74] Res: Fix Pokemon script game detection --- res/scripts/pokemon.lua | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/res/scripts/pokemon.lua b/res/scripts/pokemon.lua index c89fb8e75..31c3bdcf8 100644 --- a/res/scripts/pokemon.lua +++ b/res/scripts/pokemon.lua @@ -466,20 +466,24 @@ local gameLeafGreenEnR1 = gameLeafGreenEn:new{ _speciesNameTable=0x245f2c, } -gameCodes = { - ["DMG-AAUE"]=gameGSEn, -- Gold - ["DMG-AAXE"]=gameGSEn, -- Silver - ["CGB-BYTE"]=gameCrystalEn, - ["AGB-AXVE"]=gameRubyEn, - ["AGB-AXPE"]=gameSapphireEn, - ["AGB-BPEE"]=gameEmeraldEn, - ["AGB-BPRE"]=gameFireRedEn, - ["AGB-BPGE"]=gameLeafGreenEn, +local gameCodes = { + [C.PLATFORM.GB] = { + ["AAUE"] = gameGSEn, -- Gold + ["AAXE"] = gameGSEn, -- Silver + ["BYTE"] = gameCrystalEn, + }, + [C.PLATFORM.GBA] = { + ["AXVE"] = gameRubyEn, + ["AXPE"] = gameSapphireEn, + ["BPEE"] = gameEmeraldEn, + ["BPRE"] = gameFireRedEn, + ["BPGE"] = gameLeafGreenEn, + } } -- These versions have slight differences and/or cannot be uniquely -- identified by their in-header game codes, so fall back on a CRC32 -gameCrc32 = { +local gameCrc32 = { [0x9f7fdd53] = gameRBEn, -- Red [0xd6da8a1a] = gameRBEn, -- Blue [0x7d527d62] = gameYellowEn, @@ -510,7 +514,7 @@ function detectGame() end game = gameCrc32[checksum] if not game then - game = gameCodes[emu:getGameCode()] + game = gameCodes[emu:platform()][emu:getGameCode()] end if not game then From 355c3796364c75492e6ff31d7aa94738b43f748b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 26 Apr 2025 20:04:49 -0700 Subject: [PATCH 58/74] Qt: Add another known-bad driver version for the ig4icd64 crash --- src/platform/qt/OpenGLBug.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/platform/qt/OpenGLBug.cpp b/src/platform/qt/OpenGLBug.cpp index 089d7d754..2beff2a6e 100644 --- a/src/platform/qt/OpenGLBug.cpp +++ b/src/platform/qt/OpenGLBug.cpp @@ -39,6 +39,10 @@ bool glContextHasBug(OpenGLBug bug) { if (renderer == "Intel Pineview Platform") { return true; } + + if (version == "2.1.0 - Build 8.15.10.2900") { + return true; + } #endif return false; From c33a0d65344984294ed8666e98d1735a29f0a2d8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 26 Apr 2025 20:29:40 -0700 Subject: [PATCH 59/74] Qt: Ensure document gets reparented along with text buffer --- src/platform/qt/scripting/ScriptingTextBuffer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/qt/scripting/ScriptingTextBuffer.cpp b/src/platform/qt/scripting/ScriptingTextBuffer.cpp index c209adcd5..0461c1b33 100644 --- a/src/platform/qt/scripting/ScriptingTextBuffer.cpp +++ b/src/platform/qt/scripting/ScriptingTextBuffer.cpp @@ -16,6 +16,7 @@ using namespace QGBA; ScriptingTextBuffer::ScriptingTextBuffer(QObject* parent) : QObject(parent) + , m_document(this) { m_shim.init = &ScriptingTextBuffer::init; m_shim.deinit = &ScriptingTextBuffer::deinit; From 90057703b53f40aa355d5e331f771bf7914107ba Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 27 Apr 2025 22:02:19 -0700 Subject: [PATCH 60/74] Qt: Add support for running scripts at startup (closes #3465) --- CHANGES | 1 + src/platform/qt/CMakeLists.txt | 3 + src/platform/qt/ConfigController.cpp | 33 ++++ src/platform/qt/ConfigController.h | 4 +- src/platform/qt/SettingsView.cpp | 2 + src/platform/qt/SettingsView.h | 1 + src/platform/qt/SettingsView.ui | 37 +++-- src/platform/qt/Window.cpp | 39 +++-- src/platform/qt/Window.h | 4 +- .../qt/scripting/AutorunScriptModel.cpp | 131 +++++++++++++++ .../qt/scripting/AutorunScriptModel.h | 57 +++++++ .../qt/scripting/AutorunScriptView.cpp | 52 ++++++ src/platform/qt/scripting/AutorunScriptView.h | 33 ++++ .../qt/scripting/AutorunScriptView.ui | 157 ++++++++++++++++++ .../qt/scripting/ScriptingController.cpp | 31 +++- .../qt/scripting/ScriptingController.h | 10 +- src/platform/qt/scripting/ScriptingView.cpp | 14 +- src/platform/qt/scripting/ScriptingView.h | 2 - src/platform/qt/scripting/ScriptingView.ui | 10 +- 19 files changed, 573 insertions(+), 48 deletions(-) create mode 100644 src/platform/qt/scripting/AutorunScriptModel.cpp create mode 100644 src/platform/qt/scripting/AutorunScriptModel.h create mode 100644 src/platform/qt/scripting/AutorunScriptView.cpp create mode 100644 src/platform/qt/scripting/AutorunScriptView.h create mode 100644 src/platform/qt/scripting/AutorunScriptView.ui diff --git a/CHANGES b/CHANGES index a8c0ca04b..09f0f22cb 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,7 @@ Features: - Scripting: New `storage` API for saving data for a script, e.g. settings - Scripting: New `image` and `canvas` APIs for drawing images and displaying on-screen - Scripting: Debugger integration to allow for breakpoints and watchpoints + - Scripting: Add support for running scripts at startup - New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81, Sintax - Initial support for bootleg GBA multicarts - Debugger: Add range watchpoints diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 433e8c061..0dcfbaefb 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -297,12 +297,15 @@ endif() if(ENABLE_SCRIPTING) list(APPEND SOURCE_FILES + scripting/AutorunScriptModel.cpp + scripting/AutorunScriptView.cpp scripting/ScriptingController.cpp scripting/ScriptingTextBuffer.cpp scripting/ScriptingTextBufferModel.cpp scripting/ScriptingView.cpp) list(APPEND UI_FILES + scripting/AutorunScriptView.ui scripting/ScriptingView.ui) endif() diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp index eae75fea4..5b65ac2ea 100644 --- a/src/platform/qt/ConfigController.cpp +++ b/src/platform/qt/ConfigController.cpp @@ -7,6 +7,7 @@ #include "ActionMapper.h" #include "CoreController.h" +#include "scripting/AutorunScriptModel.h" #include #include @@ -122,6 +123,8 @@ QString ConfigController::s_configDir; ConfigController::ConfigController(QObject* parent) : QObject(parent) { + qRegisterMetaType(); + QString fileName = configDir(); fileName.append(QDir::separator()); fileName.append("qt.ini"); @@ -379,6 +382,36 @@ void ConfigController::setMRU(const QStringList& mru, ConfigController::MRU mruT m_settings->endGroup(); } +QList ConfigController::getList(const QString& group) const { + QList list; + m_settings->beginGroup(group); + for (int i = 0; ; ++i) { + QVariant item = m_settings->value(QString::number(i)); + if (item.isNull() || !item.isValid()) { + break; + } + list.append(item); + } + m_settings->endGroup(); + return list; +} + +void ConfigController::setList(const QString& group, const QList& list) { + int i = 0; + m_settings->beginGroup(group); + QStringList keys = m_settings->childKeys(); + for (const QVariant& item : list) { + QString key = QString::number(i); + keys.removeAll(key); + m_settings->setValue(key, item); + ++i; + } + for (const auto& key: keys) { + m_settings->remove(key); + } + m_settings->endGroup(); +} + constexpr const char* ConfigController::mruName(ConfigController::MRU mru) { switch (mru) { case MRU::ROM: diff --git a/src/platform/qt/ConfigController.h b/src/platform/qt/ConfigController.h index 5aa9b7ac2..25c307b47 100644 --- a/src/platform/qt/ConfigController.h +++ b/src/platform/qt/ConfigController.h @@ -90,7 +90,7 @@ public: QVariant takeArgvOption(const QString& key); QStringList getMRU(MRU = MRU::ROM) const; - void setMRU(const QStringList& mru, MRU = MRU::ROM); + QList getList(const QString& group) const; Configuration* overrides() { return mCoreConfigGetOverrides(&m_config); } void saveOverride(const Override&); @@ -116,6 +116,8 @@ public slots: void setOption(const char* key, const char* value); void setOption(const char* key, const QVariant& value); void setQtOption(const QString& key, const QVariant& value, const QString& group = QString()); + void setMRU(const QStringList& mru, MRU = MRU::ROM); + void setList(const QString& group, const QList& list); void makePortable(); void write(); diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 967cf1a4d..2099ce7f4 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -69,6 +69,8 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC reloadConfig(); + connect(m_ui.autorunScripts, &QAbstractButton::pressed, this, &SettingsView::openAutorunScripts); + connect(m_ui.volume, static_cast(&QSlider::valueChanged), [this](int v) { if (v < m_ui.volumeFf->value()) { m_ui.volumeFf->setValue(v); diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h index 710456958..4a697151d 100644 --- a/src/platform/qt/SettingsView.h +++ b/src/platform/qt/SettingsView.h @@ -63,6 +63,7 @@ signals: void languageChanged(); void libraryCleared(); void saveSettingsRequested(); + void openAutorunScripts(); public slots: void selectPage(Page); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index bce56f730..e66059d41 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -569,14 +569,21 @@ - + + + + Edit autorun scripts + + + + Qt::Horizontal - + Periodically autosave state @@ -586,7 +593,7 @@ - + Save entered cheats @@ -596,21 +603,21 @@ - + Qt::Horizontal - + Save state extra data: - + Screenshot @@ -620,7 +627,7 @@ - + Save game @@ -630,7 +637,7 @@ - + Cheat codes @@ -640,21 +647,21 @@ - + Qt::Horizontal - + Load state extra data: - + Screenshot @@ -664,28 +671,28 @@ - + Save game - + Cheat codes - + Qt::Horizontal - + Enable Discord Rich Presence diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 9611f0dda..8f09d439f 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -305,6 +305,10 @@ void Window::loadConfig() { updateMRU(); m_inputController.setConfiguration(m_config); + + if (!m_config->getList("autorunSettings").isEmpty()) { + ensureScripting(); + } } void Window::reloadConfig() { @@ -550,6 +554,10 @@ void Window::openSettingsWindow(SettingsView::Page page) { #ifdef USE_SQLITE3 connect(settingsWindow, &SettingsView::libraryCleared, m_libraryView, &LibraryController::clear); #endif + connect(settingsWindow, &SettingsView::openAutorunScripts, this, [this]() { + ensureScripting(); + m_scripting->openAutorunEdit(); + }); connect(this, &Window::shaderSelectorAdded, settingsWindow, &SettingsView::setShaderSelector); openView(settingsWindow); settingsWindow->selectPage(page); @@ -639,17 +647,7 @@ void Window::consoleOpen() { #ifdef ENABLE_SCRIPTING void Window::scriptingOpen() { - if (!m_scripting) { - m_scripting = std::make_unique(); - m_scripting->setInputController(&m_inputController); - m_shortcutController->setScriptingController(m_scripting.get()); - if (m_controller) { - m_scripting->setController(m_controller); - m_display->installEventFilter(m_scripting.get()); - } - - m_scripting->setVideoBackend(m_display->videoBackend()); - } + ensureScripting(); ScriptingView* view = new ScriptingView(m_scripting.get(), m_config); openView(view); } @@ -2057,6 +2055,25 @@ void Window::updateMRU() { m_actions.rebuildMenu(menuBar(), this, *m_shortcutController); } +void Window::ensureScripting() { + if (m_scripting) { + return; + } + m_scripting = std::make_unique(m_config); + m_scripting->setInputController(&m_inputController); + m_shortcutController->setScriptingController(m_scripting.get()); + if (m_controller) { + m_scripting->setController(m_controller); + m_display->installEventFilter(m_scripting.get()); + } + + if (m_display) { + m_scripting->setVideoBackend(m_display->videoBackend()); + } + + connect(m_scripting.get(), &ScriptingController::autorunScriptsOpened, this, &Window::openView); +} + std::shared_ptr Window::addGameAction(const QString& visibleName, const QString& name, Action::Function function, const QString& menu, const QKeySequence& shortcut) { auto action = m_actions.addAction(visibleName, name, [this, function = std::move(function)]() { if (m_controller) { diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 0ae94397d..d4126100b 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -109,6 +109,8 @@ public slots: void startVideoLog(); + void openView(QWidget* widget); + #ifdef ENABLE_DEBUGGERS void consoleOpen(); #endif @@ -173,7 +175,7 @@ private: void clearMRU(); void updateMRU(); - void openView(QWidget* widget); + void ensureScripting(); template std::function openTView(A... arg); template std::function openControllerTView(A... arg); diff --git a/src/platform/qt/scripting/AutorunScriptModel.cpp b/src/platform/qt/scripting/AutorunScriptModel.cpp new file mode 100644 index 000000000..35431b855 --- /dev/null +++ b/src/platform/qt/scripting/AutorunScriptModel.cpp @@ -0,0 +1,131 @@ +/* Copyright (c) 2013-2025 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 "scripting/AutorunScriptModel.h" + +#include "ConfigController.h" + +using namespace QGBA; + +AutorunScriptModel::AutorunScriptModel(ConfigController* config, QObject* parent) + : QAbstractListModel(parent) + , m_config(config) +{ + QList autorun = m_config->getList("autorunSettings"); + for (const auto& item: autorun) { + if (!item.canConvert()) { + continue; + } + m_scripts.append(qvariant_cast(item)); + } +} + +int AutorunScriptModel::rowCount(const QModelIndex& parent) const { + if (parent.isValid()) { + return 0; + } + return m_scripts.count(); +} + +bool AutorunScriptModel::setData(const QModelIndex& index, const QVariant& data, int role) { + if (!index.isValid() || index.parent().isValid() || index.row() >= m_scripts.count()) { + return {}; + } + + switch (role) { + case Qt::CheckStateRole: + m_scripts[index.row()].active = data.value() == Qt::Checked; + save(); + return true; + } + return false; + +} + +QVariant AutorunScriptModel::data(const QModelIndex& index, int role) const { + if (!index.isValid() || index.parent().isValid() || index.row() >= m_scripts.count()) { + return {}; + } + + switch (role) { + case Qt::DisplayRole: + return m_scripts.at(index.row()).filename; + case Qt::CheckStateRole: + return m_scripts.at(index.row()).active ? Qt::Checked : Qt::Unchecked; + } + return {}; +} + +Qt::ItemFlags AutorunScriptModel::flags(const QModelIndex& index) const { + if (!index.isValid() || index.parent().isValid()) { + return Qt::NoItemFlags; + } + return Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; +} + +bool AutorunScriptModel::removeRows(int row, int count, const QModelIndex& parent) { + if (parent.isValid()) { + return false; + } + if (m_scripts.size() < row) { + return false; + } + if (m_scripts.size() < row + count) { + count = m_scripts.size() - row; + } + m_scripts.erase(m_scripts.begin() + row, m_scripts.begin() + row + count); + save(); + return true; +} + +bool AutorunScriptModel::moveRows(const QModelIndex& sourceParent, int sourceRow, int count, const QModelIndex& destinationParent, int destinationChild) { + if (sourceParent.isValid() || destinationParent.isValid()) { + return false; + } + + if (sourceRow < 0 || destinationChild < 0) { + return false; + } + + if (sourceRow >= m_scripts.size() || destinationChild >= m_scripts.size()) { + return false; + } + + if (count > 1) { + qWarning() << tr("Moving more than one row at once is not yet supported"); + return false; + } + + auto item = m_scripts.takeAt(sourceRow); + m_scripts.insert(destinationChild, item); + save(); + return true; +} + +void AutorunScriptModel::addScript(const QString& filename) { + beginInsertRows({}, m_scripts.count(), m_scripts.count()); + m_scripts.append(ScriptInfo { filename, true }); + endInsertRows(); + save(); +} + +QList AutorunScriptModel::activeScripts() const { + QList scripts; + for (const auto& pair: m_scripts) { + if (!pair.active) { + continue; + } + scripts.append(pair.filename); + } + return scripts; +} + +void AutorunScriptModel::save() { + QList list; + for (const auto& script : m_scripts) { + list.append(QVariant::fromValue(script)); + } + m_config->setList("autorunSettings", list); +} diff --git a/src/platform/qt/scripting/AutorunScriptModel.h b/src/platform/qt/scripting/AutorunScriptModel.h new file mode 100644 index 000000000..c41402b90 --- /dev/null +++ b/src/platform/qt/scripting/AutorunScriptModel.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2013-2025 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/. */ +#pragma once + +#include + +namespace QGBA { + +class ConfigController; + +class AutorunScriptModel : public QAbstractListModel { +Q_OBJECT + +public: + struct ScriptInfo { + QString filename; + bool active; + + friend QDataStream& operator<<(QDataStream& stream, const ScriptInfo& object) { + stream << object.filename; + stream << object.active; + return stream; + } + + friend QDataStream& operator>>(QDataStream& stream, ScriptInfo& object) { + stream >> object.filename; + stream >> object.active; + return stream; + } + + }; + + AutorunScriptModel(ConfigController* config, QObject* parent = nullptr); + + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + virtual bool setData(const QModelIndex& index, const QVariant& data, int role = Qt::DisplayRole) override; + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + virtual Qt::ItemFlags flags(const QModelIndex& index) const override; + virtual bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override; + virtual bool moveRows(const QModelIndex& sourceParent, int sourceRow, int count, const QModelIndex& destinationParent, int destinationChild) override; + + void addScript(const QString& filename); + QList activeScripts() const; + +private: + ConfigController* m_config; + QList m_scripts; + + void save(); +}; + +} + +Q_DECLARE_METATYPE(QGBA::AutorunScriptModel::ScriptInfo); diff --git a/src/platform/qt/scripting/AutorunScriptView.cpp b/src/platform/qt/scripting/AutorunScriptView.cpp new file mode 100644 index 000000000..10d3cfb25 --- /dev/null +++ b/src/platform/qt/scripting/AutorunScriptView.cpp @@ -0,0 +1,52 @@ +/* Copyright (c) 2013-2025 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 "scripting/AutorunScriptView.h" + +#include "GBAApp.h" +#include "scripting/AutorunScriptModel.h" +#include "scripting/ScriptingController.h" + +using namespace QGBA; + +AutorunScriptView::AutorunScriptView(AutorunScriptModel* model, ScriptingController* controller, QWidget* parent) + : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) + , m_controller(controller) +{ + m_ui.setupUi(this); + + m_ui.autorunList->setModel(model); +} + +void AutorunScriptView::addScript() { + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select a script"), m_controller->getFilenameFilters()); + if (filename.isEmpty()) { + return; + } + + AutorunScriptModel* model = static_cast(m_ui.autorunList->model()); + model->addScript(filename); +} + +void AutorunScriptView::removeScript(const QModelIndex& index) { + QAbstractItemModel* model = m_ui.autorunList->model(); + model->removeRow(index.row(), index.parent()); +} + +void AutorunScriptView::removeScript() { + removeScript(m_ui.autorunList->currentIndex()); +} + +void AutorunScriptView::moveUp() { + QModelIndex index = m_ui.autorunList->currentIndex(); + QAbstractItemModel* model = m_ui.autorunList->model(); + model->moveRows(index.parent(), index.row(), 1, index.parent(), index.row() - 1); +} + +void AutorunScriptView::moveDown() { + QModelIndex index = m_ui.autorunList->currentIndex(); + QAbstractItemModel* model = m_ui.autorunList->model(); + model->moveRows(index.parent(), index.row(), 1, index.parent(), index.row() + 1); +} diff --git a/src/platform/qt/scripting/AutorunScriptView.h b/src/platform/qt/scripting/AutorunScriptView.h new file mode 100644 index 000000000..3657f7248 --- /dev/null +++ b/src/platform/qt/scripting/AutorunScriptView.h @@ -0,0 +1,33 @@ +/* Copyright (c) 2013-2025 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/. */ +#pragma once + +#include "ui_AutorunScriptView.h" + +namespace QGBA { + +class AutorunScriptModel; +class ScriptingController; + +class AutorunScriptView : public QDialog { +Q_OBJECT + +public: + AutorunScriptView(AutorunScriptModel* model, ScriptingController* controller, QWidget* parent = nullptr); + void removeScript(const QModelIndex&); + +private slots: + void addScript(); + void removeScript(); + void moveUp(); + void moveDown(); + +private: + Ui::AutorunScriptView m_ui; + ScriptingController* m_controller; +}; + +} diff --git a/src/platform/qt/scripting/AutorunScriptView.ui b/src/platform/qt/scripting/AutorunScriptView.ui new file mode 100644 index 000000000..219e40d33 --- /dev/null +++ b/src/platform/qt/scripting/AutorunScriptView.ui @@ -0,0 +1,157 @@ + + + QGBA::AutorunScriptView + + + + 0 + 0 + 400 + 300 + + + + Autorun scripts + + + + + + Add + + + + + + + + + + Remove + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + Move up + + + + + + + + + + Move down + + + + + + + + + + Run scripts when starting a game + + + + + + + + + + + + + + add + clicked() + QGBA::AutorunScriptView + addScript() + + + 47 + 276 + + + 199 + 149 + + + + + remove + clicked() + QGBA::AutorunScriptView + removeScript() + + + 138 + 276 + + + 199 + 149 + + + + + up + clicked() + QGBA::AutorunScriptView + moveUp() + + + 238 + 276 + + + 199 + 149 + + + + + down + clicked() + QGBA::AutorunScriptView + moveDown() + + + 341 + 276 + + + 199 + 149 + + + + + + addScript() + removeScript() + moveUp() + moveDown() + + diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index b1d68e22a..3c2d8fa4d 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -10,14 +10,17 @@ #include #include +#include "ConfigController.h" #include "CoreController.h" #include "Display.h" #include "input/Gamepad.h" #include "input/GamepadButtonEvent.h" #include "input/GamepadHatEvent.h" #include "InputController.h" +#include "scripting/AutorunScriptView.h" #include "scripting/ScriptingTextBuffer.h" #include "scripting/ScriptingTextBufferModel.h" +#include "Window.h" #include #include @@ -25,8 +28,9 @@ using namespace QGBA; -ScriptingController::ScriptingController(QObject* parent) +ScriptingController::ScriptingController(ConfigController* config, QObject* parent) : QObject(parent) + , m_model(config) { m_logger.p = this; m_logger.log = [](mLogger* log, int, enum mLogLevel level, const char* format, va_list args) { @@ -154,9 +158,6 @@ void ScriptingController::reset() { m_engines.clear(); m_activeEngine = nullptr; init(); - if (m_controller && m_controller->hasStarted()) { - attach(); - } } void ScriptingController::runCode(const QString& code) { @@ -164,6 +165,11 @@ void ScriptingController::runCode(const QString& code) { load(vf, "*prompt"); } +void ScriptingController::openAutorunEdit() { + AutorunScriptView* view = new AutorunScriptView(&m_model, this); + emit autorunScriptsOpened(view); +} + void ScriptingController::flushStorage() { #ifdef USE_JSON_C mScriptStorageFlushAll(&m_scriptContext); @@ -259,6 +265,15 @@ void ScriptingController::scriptingEvent(QObject* obj, QEvent* event) { } } +QString ScriptingController::getFilenameFilters() const { + QStringList filters; +#ifdef USE_LUA + filters.append(tr("Lua scripts (*.lua)")); +#endif + filters.append(tr("All files (*.*)")); + return filters.join(";;"); +} + void ScriptingController::updateGamepad() { InputDriver* driver = m_inputController->gamepadDriver(); if (!driver) { @@ -354,6 +369,14 @@ void ScriptingController::init() { #ifdef USE_JSON_C m_storageFlush.start(); #endif + + if (m_controller && m_controller->hasStarted()) { + attach(); + } + + for (const auto& script: m_model.activeScripts()) { + loadFile(script); + } } uint32_t ScriptingController::qtToScriptingKey(const QKeyEvent* event) { diff --git a/src/platform/qt/scripting/ScriptingController.h b/src/platform/qt/scripting/ScriptingController.h index 5c579a768..8e2b111f9 100644 --- a/src/platform/qt/scripting/ScriptingController.h +++ b/src/platform/qt/scripting/ScriptingController.h @@ -13,6 +13,7 @@ #include #include +#include "scripting/AutorunScriptModel.h" #include "VFileDevice.h" #include @@ -24,6 +25,7 @@ struct VideoBackend; namespace QGBA { +class ConfigController; class CoreController; class InputController; class ScriptingTextBuffer; @@ -33,7 +35,7 @@ class ScriptingController : public QObject { Q_OBJECT public: - ScriptingController(QObject* parent = nullptr); + ScriptingController(ConfigController* config, QObject* parent = nullptr); ~ScriptingController(); void setController(std::shared_ptr controller); @@ -48,17 +50,22 @@ public: mScriptContext* context() { return &m_scriptContext; } ScriptingTextBufferModel* textBufferModel() const { return m_bufferModel; } + QString getFilenameFilters() const; + signals: void log(const QString&); void warn(const QString&); void error(const QString&); void textBufferCreated(ScriptingTextBuffer*); + void autorunScriptsOpened(QWidget* view); + public slots: void clearController(); void updateVideoScale(); void reset(); void runCode(const QString& code); + void openAutorunEdit(); void flushStorage(); @@ -91,6 +98,7 @@ private: mScriptGamepad m_gamepad; + AutorunScriptModel m_model; std::shared_ptr m_controller; InputController* m_inputController = nullptr; diff --git a/src/platform/qt/scripting/ScriptingView.cpp b/src/platform/qt/scripting/ScriptingView.cpp index d31a9c738..ff1f98577 100644 --- a/src/platform/qt/scripting/ScriptingView.cpp +++ b/src/platform/qt/scripting/ScriptingView.cpp @@ -40,6 +40,7 @@ ScriptingView::ScriptingView(ScriptingController* controller, ConfigController* connect(m_ui.buffers->selectionModel(), &QItemSelectionModel::currentChanged, this, &ScriptingView::selectBuffer); connect(m_ui.load, &QAction::triggered, this, &ScriptingView::load); connect(m_ui.loadMostRecent, &QAction::triggered, this, &ScriptingView::loadMostRecent); + connect(m_ui.editAutorunScripts, &QAction::triggered, controller, &ScriptingController::openAutorunEdit); connect(m_ui.reset, &QAction::triggered, controller, &ScriptingController::reset); m_mruFiles = m_config->getMRU(ConfigController::MRU::Script); @@ -58,7 +59,7 @@ void ScriptingView::submitRepl() { } void ScriptingView::load() { - QString filename = GBAApp::app()->getOpenFileName(this, tr("Select script to load"), getFilters()); + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select script to load"), m_controller->getFilenameFilters()); if (!filename.isEmpty()) { if (!m_controller->loadFile(filename)) { return; @@ -84,15 +85,6 @@ void ScriptingView::selectBuffer(const QModelIndex& current, const QModelIndex&) } } -QString ScriptingView::getFilters() const { - QStringList filters; -#ifdef USE_LUA - filters.append(tr("Lua scripts (*.lua)")); -#endif - filters.append(tr("All files (*.*)")); - return filters.join(";;"); -} - void ScriptingView::appendMRU(const QString& fname) { int index = m_mruFiles.indexOf(fname); if (index >= 0) { @@ -121,4 +113,4 @@ void ScriptingView::updateMRU() { void ScriptingView::checkEmptyMRU() { m_ui.loadMostRecent->setEnabled(!m_mruFiles.isEmpty()); -} \ No newline at end of file +} diff --git a/src/platform/qt/scripting/ScriptingView.h b/src/platform/qt/scripting/ScriptingView.h index a62bd2314..d7b49955c 100644 --- a/src/platform/qt/scripting/ScriptingView.h +++ b/src/platform/qt/scripting/ScriptingView.h @@ -28,8 +28,6 @@ private slots: void selectBuffer(const QModelIndex& current, const QModelIndex& = QModelIndex()); private: - QString getFilters() const; - void appendMRU(const QString&); void updateMRU(); void checkEmptyMRU(); diff --git a/src/platform/qt/scripting/ScriptingView.ui b/src/platform/qt/scripting/ScriptingView.ui index 4d55a8707..6de7ec8f9 100644 --- a/src/platform/qt/scripting/ScriptingView.ui +++ b/src/platform/qt/scripting/ScriptingView.ui @@ -98,9 +98,10 @@ - + + @@ -110,7 +111,7 @@ Load script... - + &Load most recent @@ -125,6 +126,11 @@ 0 + + + Edit autorun scripts... + + From 11ebe11c8e497690157eb53d0e227b2228e4bb99 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 27 Apr 2025 22:03:06 -0700 Subject: [PATCH 61/74] Scripting: Allow display input script to work even if overlay does not work initially --- res/scripts/input-display.lua | 53 ++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/res/scripts/input-display.lua b/res/scripts/input-display.lua index e3db0340c..6cd82b97e 100644 --- a/res/scripts/input-display.lua +++ b/res/scripts/input-display.lua @@ -44,27 +44,25 @@ local state = { [C.PLATFORM.GB] = 7, } } -state.overlay = canvas:newLayer(32, 16) -state.painter = image.newPainter(state.overlay.image) -state.painter:setBlend(false) -state.painter:setFill(true) -function state.update() - local keys = util.expandBitmask(emu:getKeys()) - local maxKey = state.maxKey[emu:platform()] - - for key = 0, maxKey do - if emu:getKey(key) ~= 0 then - state.painter:setFillColor(0x80FFFFFF) - else - state.painter:setFillColor(0x40404040) - end - state.drawButton[key](state) +function state.create() + if state.overlay ~= nil then + return true end - state.overlay:update() + if canvas == nil then + return false + end + state.overlay = canvas:newLayer(32, 16) + if state.overlay == nil then + return false + end + state.painter = image.newPainter(state.overlay.image) + state.painter:setBlend(false) + state.painter:setFill(true) + return true end -function state.reset() +function state.update() local endX = canvas:screenWidth() - 32 local endY = canvas:screenHeight() - 16 @@ -112,11 +110,32 @@ function state.reset() pos.y = pos.y + input_display.offset.y; state.overlay:setPosition(pos.x, pos.y); + + local keys = util.expandBitmask(emu:getKeys()) + local maxKey = state.maxKey[emu:platform()] + + for key = 0, maxKey do + if emu:getKey(key) ~= 0 then + state.painter:setFillColor(0x80FFFFFF) + else + state.painter:setFillColor(0x40404040) + end + state.drawButton[key](state) + end + state.overlay:update() +end + +function state.reset() + if not state.create() then + return + end state.painter:setFillColor(0x40808080) state.painter:drawRectangle(0, 0, 32, 16) state.overlay:update() end +input_display.state = state + state.reset() callbacks:add("frame", state.update) callbacks:add("start", state.reset) From de3ab3889de3897b1ed64ec379f13819633d6720 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 27 Apr 2025 22:12:50 -0700 Subject: [PATCH 62/74] Qt: Fix build --- src/platform/qt/scripting/AutorunScriptModel.cpp | 3 ++- src/platform/qt/scripting/AutorunScriptModel.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/scripting/AutorunScriptModel.cpp b/src/platform/qt/scripting/AutorunScriptModel.cpp index 35431b855..de8a7a06b 100644 --- a/src/platform/qt/scripting/AutorunScriptModel.cpp +++ b/src/platform/qt/scripting/AutorunScriptModel.cpp @@ -6,6 +6,7 @@ #include "scripting/AutorunScriptModel.h" #include "ConfigController.h" +#include "LogController.h" using namespace QGBA; @@ -94,7 +95,7 @@ bool AutorunScriptModel::moveRows(const QModelIndex& sourceParent, int sourceRow } if (count > 1) { - qWarning() << tr("Moving more than one row at once is not yet supported"); + LOG(QT, WARN) << tr("Moving more than one row at once is not yet supported"); return false; } diff --git a/src/platform/qt/scripting/AutorunScriptModel.h b/src/platform/qt/scripting/AutorunScriptModel.h index c41402b90..f269d2798 100644 --- a/src/platform/qt/scripting/AutorunScriptModel.h +++ b/src/platform/qt/scripting/AutorunScriptModel.h @@ -6,6 +6,7 @@ #pragma once #include +#include namespace QGBA { From 4f9ad3a162d789318d55f9426ec13ddc201acf77 Mon Sep 17 00:00:00 2001 From: CasualPokePlayer <50538166+CasualPokePlayer@users.noreply.github.com> Date: Tue, 29 Apr 2025 22:47:35 -0700 Subject: [PATCH 63/74] Allow truncation to work within VFileFromMemory (#3455) * Allow truncation to work within VFileFromMemory/VFileFromConstMemory Truncation here is limited to buffer size (allowing for shrinking but not growing the buffer size) * Don't change VFileFromConstMemory; have VFileFromMemory write "expand" size until the buffer size * 0 out "expanded" space --- src/util/vfs/vfs-mem.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/util/vfs/vfs-mem.c b/src/util/vfs/vfs-mem.c index 06189b886..05580f544 100644 --- a/src/util/vfs/vfs-mem.c +++ b/src/util/vfs/vfs-mem.c @@ -26,6 +26,7 @@ static ssize_t _vfmWriteNoop(struct VFile* vf, const void* buffer, size_t size); static void* _vfmMap(struct VFile* vf, size_t size, int flags); static void _vfmUnmap(struct VFile* vf, void* memory, size_t size); static void _vfmTruncate(struct VFile* vf, size_t size); +static void _vfmTruncateNoExpand(struct VFile* vf, size_t size); static void _vfmTruncateNoop(struct VFile* vf, size_t size); static ssize_t _vfmSize(struct VFile* vf); static bool _vfmSync(struct VFile* vf, void* buffer, size_t size); @@ -51,7 +52,7 @@ struct VFile* VFileFromMemory(void* mem, size_t size) { vfm->d.write = _vfmWrite; vfm->d.map = _vfmMap; vfm->d.unmap = _vfmUnmap; - vfm->d.truncate = _vfmTruncateNoop; + vfm->d.truncate = _vfmTruncateNoExpand; vfm->d.size = _vfmSize; vfm->d.sync = _vfmSync; @@ -236,8 +237,12 @@ ssize_t _vfmRead(struct VFile* vf, void* buffer, size_t size) { ssize_t _vfmWrite(struct VFile* vf, const void* buffer, size_t size) { struct VFileMem* vfm = (struct VFileMem*) vf; - if (size + vfm->offset >= vfm->size) { - size = vfm->size - vfm->offset; + if (size + vfm->offset > vfm->size) { + vfm->size = size + vfm->offset; + if (vfm->size > vfm->bufferSize) { + vfm->size = vfm->bufferSize; + size = vfm->size - vfm->offset; + } } memcpy((void*) ((uintptr_t) vfm->mem + vfm->offset), buffer, size); @@ -287,6 +292,20 @@ void _vfmTruncate(struct VFile* vf, size_t size) { _vfmExpand(vfm, size); } +void _vfmTruncateNoExpand(struct VFile* vf, size_t size) { + struct VFileMem* vfm = (struct VFileMem*) vf; + + if (size > vfm->bufferSize) { + size = vfm->bufferSize; + } + + if (size > vfm->size) { + memset((void*) ((uintptr_t) vfm->mem + vfm->size), 0, size - vfm->size); + } + + vfm->size = size; +} + void _vfmTruncateNoop(struct VFile* vf, size_t size) { // TODO: Return value? UNUSED(vf); From c5cddc040757f3d2f382d79e359a1ce5e5cc73d5 Mon Sep 17 00:00:00 2001 From: CasualPokePlayer <50538166+CasualPokePlayer@users.noreply.github.com> Date: Tue, 29 Apr 2025 22:48:23 -0700 Subject: [PATCH 64/74] RTC state machine improvements (#3459) * Try to correct the RTC state machine to only operate on edges Unsure if all of this is entirely right (haven't ran any tests to confirm behavior here). Fixes RTC within Pokemon games (and maybe other games) due to the added write latch code adding a _readPins call. * More correct behavior based on testing * Move this init to align to struct * Correctly handle rtc output on falling edges rather than raising edges also correctly handle rtc output in general, even in cases outside of an rtc read cmd _outputPins needed to be corrected here, it shouldn't be reading gpioBase here... * Simplify * More RTC state machine fixes Separate out command start and command write data processing Command start processing happens again if the command magic is invalid (note: doesn't apply to the unmapped command 5) Ensure command data processing loops Output 1s for commands with no actual output * Put SIO output in states * Try to correct light sensor too * inc state version * fix reserved names --- include/mgba/internal/gba/cart/gpio.h | 3 +- include/mgba/internal/gba/serialize.h | 14 +- src/gba/cart/gpio.c | 220 ++++++++++++++------------ src/gba/serialize.c | 2 +- 4 files changed, 129 insertions(+), 110 deletions(-) diff --git a/include/mgba/internal/gba/cart/gpio.h b/include/mgba/internal/gba/cart/gpio.h index 28c5fa0ea..1f870cfd5 100644 --- a/include/mgba/internal/gba/cart/gpio.h +++ b/include/mgba/internal/gba/cart/gpio.h @@ -49,10 +49,11 @@ DECL_BIT(RTCCommandData, Reading, 7); struct GBARTC { int32_t bytesRemaining; - int32_t transferStep; int32_t bitsRead; int32_t bits; int32_t commandActive; + bool sckEdge; + bool sioOutput; RTCCommandData command; RTCControl control; uint8_t time[7]; diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index 43a015827..5f42cee42 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -168,7 +168,9 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | 0x00290: Pin state * | 0x00291: Write latch * | 0x00292: Direction state - * | 0x00293: Reserved + * | 0x00293: Flags + * | bit 0: RTC SIO output + * | bit 1 - 7: Reserved * | 0x00294 - 0x002B6: RTC state (see hardware.h for format) * | 0x002B7 - 0x002B7: GPIO devices * | bit 0: Has RTC values @@ -185,7 +187,7 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | bit 0: Is read enabled * | bit 1: Gyroscope sample is edge * | bit 2: Light sample is edge - * | bit 3: Reserved + * | bit 3: RTC SCK is edge * | bits 4 - 15: Light counter * | 0x002C0 - 0x002C0: Light sample * | 0x002C1: Flags @@ -284,6 +286,7 @@ DECL_BITFIELD(GBASerializedHWFlags1, uint16_t); DECL_BIT(GBASerializedHWFlags1, ReadWrite, 0); DECL_BIT(GBASerializedHWFlags1, GyroEdge, 1); DECL_BIT(GBASerializedHWFlags1, LightEdge, 2); +DECL_BIT(GBASerializedHWFlags1, RtcSckEdge, 3); DECL_BITS(GBASerializedHWFlags1, LightCounter, 4, 12); DECL_BITFIELD(GBASerializedHWFlags2, uint8_t); @@ -291,6 +294,9 @@ DECL_BITS(GBASerializedHWFlags2, TiltState, 0, 2); DECL_BITS(GBASerializedHWFlags2, GbpInputsPosted, 2, 2); DECL_BITS(GBASerializedHWFlags2, GbpTxPosition, 4, 4); +DECL_BITFIELD(GBASerializedHWFlags3, uint8_t); +DECL_BITS(GBASerializedHWFlags3, RtcSioOutput, 0, 1); + DECL_BITFIELD(GBASerializedUnlCartFlags, uint16_t); DECL_BITS(GBASerializedUnlCartFlags, Type, 0, 5); DECL_BITS(GBASerializedUnlCartFlags, Subtype, 5, 3); @@ -381,9 +387,9 @@ struct GBASerializedState { uint8_t pinState; uint8_t writeLatch; uint8_t pinDirection; - uint8_t reserved0; + GBASerializedHWFlags3 flags3; int32_t rtcBytesRemaining; - int32_t rtcTransferStep; + int32_t reserved0; int32_t rtcBitsRead; int32_t rtcBits; int32_t rtcCommandActive; diff --git a/src/gba/cart/gpio.c b/src/gba/cart/gpio.c index 83ba4fa56..2371fc87d 100644 --- a/src/gba/cart/gpio.c +++ b/src/gba/cart/gpio.c @@ -21,6 +21,7 @@ static void _outputPins(struct GBACartridgeHardware* hw, unsigned pins); static void _rtcReadPins(struct GBACartridgeHardware* hw); static unsigned _rtcOutput(struct GBACartridgeHardware* hw); +static void _rtcBeginCommand(struct GBACartridgeHardware* hw); static void _rtcProcessByte(struct GBACartridgeHardware* hw); static void _rtcUpdateClock(struct GBACartridgeHardware* hw); static unsigned _rtcBCD(unsigned value); @@ -114,11 +115,11 @@ void GBAHardwareInitRTC(struct GBACartridgeHardware* hw) { hw->devices |= HW_RTC; hw->rtc.bytesRemaining = 0; - hw->rtc.transferStep = 0; - hw->rtc.bitsRead = 0; hw->rtc.bits = 0; - hw->rtc.commandActive = 0; + hw->rtc.commandActive = false; + hw->rtc.sckEdge = true; + hw->rtc.sioOutput = true; hw->rtc.command = 0; hw->rtc.control = 0x40; memset(hw->rtc.time, 0, sizeof(hw->rtc.time)); @@ -146,11 +147,9 @@ void _readPins(struct GBACartridgeHardware* hw) { } void _outputPins(struct GBACartridgeHardware* hw, unsigned pins) { + hw->pinState &= hw->direction; + hw->pinState |= (pins & ~hw->direction & 0xF); if (hw->readWrite) { - uint16_t old; - LOAD_16(old, 0, hw->gpioBase); - old &= hw->direction; - hw->pinState = old | (pins & ~hw->direction & 0xF); STORE_16(hw->pinState, 0, hw->gpioBase); } } @@ -158,121 +157,127 @@ void _outputPins(struct GBACartridgeHardware* hw, unsigned pins) { // == RTC void _rtcReadPins(struct GBACartridgeHardware* hw) { - // Transfer sequence: - // P: 0 | 1 | 2 | 3 - // == Initiate - // > HI | - | LO | - - // > HI | - | HI | - - // == Transfer bit (x8) - // > LO | x | HI | - - // > HI | - | HI | - - // < ?? | x | ?? | - - // == Terminate - // > - | - | LO | - - switch (hw->rtc.transferStep) { - case 0: - if ((hw->pinState & 5) == 1) { - hw->rtc.transferStep = 1; - } - break; - case 1: - if ((hw->pinState & 5) == 5) { - hw->rtc.transferStep = 2; - } else if ((hw->pinState & 5) != 1) { - hw->rtc.transferStep = 0; - } - break; - case 2: + // P: 0 - SCK | 1 - SIO | 2 - CS | 3 - Unused + // CS rising edge starts RTC transfer + // Conversely, CS falling edge aborts RTC transfer + // SCK rising edge shifts a bit from SIO into the transfer + // However, there appears to be a race condition if SIO changes at SCK rising edge + // For writing the command, the old SIO data is used in this race + // For writing command data, 0 is used in this race + // Note while CS is low, SCK is internally considered high by the RTC + // SCK falling edge shifts a bit from the transfer into SIO + // (Assuming a read command, outside of read commands SIO is held high) + + // RTC keeps SCK/CS/Unused to low + _outputPins(hw, hw->pinState & 2); + + if (!(hw->pinState & 4)) { + hw->rtc.bitsRead = 0; + hw->rtc.bytesRemaining = 0; + hw->rtc.commandActive = false; + hw->rtc.command = 0; + hw->rtc.sckEdge = true; + hw->rtc.sioOutput = true; + _outputPins(hw, 2); + return; + } + + if (!hw->rtc.commandActive) { + _outputPins(hw, 2); if (!(hw->pinState & 1)) { hw->rtc.bits &= ~(1 << hw->rtc.bitsRead); hw->rtc.bits |= ((hw->pinState & 2) >> 1) << hw->rtc.bitsRead; - } else { - if (hw->pinState & 4) { - if (!RTCCommandDataIsReading(hw->rtc.command)) { - ++hw->rtc.bitsRead; - if (hw->rtc.bitsRead == 8) { - _rtcProcessByte(hw); - } - } else { - _outputPins(hw, 5 | (_rtcOutput(hw) << 1)); - ++hw->rtc.bitsRead; - if (hw->rtc.bitsRead == 8) { - --hw->rtc.bytesRemaining; - if (hw->rtc.bytesRemaining <= 0) { - hw->rtc.commandActive = 0; - hw->rtc.command = 0; - } - hw->rtc.bitsRead = 0; - } - } - } else { - hw->rtc.bitsRead = 0; - hw->rtc.bytesRemaining = 0; - hw->rtc.commandActive = 0; - hw->rtc.command = 0; - hw->rtc.transferStep = hw->pinState & 1; - _outputPins(hw, 1); + } + if (!hw->rtc.sckEdge && (hw->pinState & 1)) { + ++hw->rtc.bitsRead; + if (hw->rtc.bitsRead == 8) { + _rtcBeginCommand(hw); } } - break; - } -} - -void _rtcProcessByte(struct GBACartridgeHardware* hw) { - --hw->rtc.bytesRemaining; - if (!hw->rtc.commandActive) { - RTCCommandData command; - command = hw->rtc.bits; - if (RTCCommandDataGetMagic(command) == 0x06) { - hw->rtc.command = command; - - hw->rtc.bytesRemaining = RTC_BYTES[RTCCommandDataGetCommand(command)]; - hw->rtc.commandActive = hw->rtc.bytesRemaining > 0; - mLOG(GBA_HW, DEBUG, "Got RTC command %x", RTCCommandDataGetCommand(command)); - switch (RTCCommandDataGetCommand(command)) { - case RTC_RESET: - hw->rtc.control = 0; - break; - case RTC_DATETIME: - case RTC_TIME: - _rtcUpdateClock(hw); - break; - case RTC_FORCE_IRQ: - case RTC_CONTROL: - break; + } else if (!RTCCommandDataIsReading(hw->rtc.command)) { + _outputPins(hw, 2); + if (!(hw->pinState & 1)) { + hw->rtc.bits &= ~(1 << hw->rtc.bitsRead); + hw->rtc.bits |= ((hw->pinState & 2) >> 1) << hw->rtc.bitsRead; + } + if (!hw->rtc.sckEdge && (hw->pinState & 1)) { + if ((((hw->rtc.bits >> hw->rtc.bitsRead) & 1) ^ ((hw->pinState & 2) >> 1))) { + hw->rtc.bits &= ~(1 << hw->rtc.bitsRead); + } + ++hw->rtc.bitsRead; + if (hw->rtc.bitsRead == 8) { + _rtcProcessByte(hw); } - } else { - mLOG(GBA_HW, WARN, "Invalid RTC command byte: %02X", hw->rtc.bits); } } else { - switch (RTCCommandDataGetCommand(hw->rtc.command)) { - case RTC_CONTROL: - hw->rtc.control = hw->rtc.bits; - break; - case RTC_FORCE_IRQ: - mLOG(GBA_HW, STUB, "Unimplemented RTC command %u", RTCCommandDataGetCommand(hw->rtc.command)); - break; + if (hw->rtc.sckEdge && !(hw->pinState & 1)) { + hw->rtc.sioOutput = _rtcOutput(hw); + ++hw->rtc.bitsRead; + if (hw->rtc.bitsRead == 8) { + --hw->rtc.bytesRemaining; + if (hw->rtc.bytesRemaining <= 0) { + hw->rtc.bytesRemaining = RTC_BYTES[RTCCommandDataGetCommand(hw->rtc.command)]; + } + hw->rtc.bitsRead = 0; + } + } + _outputPins(hw, hw->rtc.sioOutput << 1); + } + + hw->rtc.sckEdge = !!(hw->pinState & 1); +} + +void _rtcBeginCommand(struct GBACartridgeHardware* hw) { + RTCCommandData command = hw->rtc.bits; + if (RTCCommandDataGetMagic(command) == 0x06) { + hw->rtc.command = command; + hw->rtc.bytesRemaining = RTC_BYTES[RTCCommandDataGetCommand(command)]; + hw->rtc.commandActive = true; + mLOG(GBA_HW, DEBUG, "Got RTC command %x", RTCCommandDataGetCommand(command)); + switch (RTCCommandDataGetCommand(command)) { case RTC_RESET: + hw->rtc.control = 0; + break; case RTC_DATETIME: case RTC_TIME: + _rtcUpdateClock(hw); + break; + case RTC_FORCE_IRQ: + case RTC_CONTROL: break; } + } else { + mLOG(GBA_HW, WARN, "Invalid RTC command byte: %02X", hw->rtc.bits); } hw->rtc.bits = 0; hw->rtc.bitsRead = 0; - if (!hw->rtc.bytesRemaining) { - hw->rtc.commandActive = 0; - hw->rtc.command = 0; +} + +void _rtcProcessByte(struct GBACartridgeHardware* hw) { + switch (RTCCommandDataGetCommand(hw->rtc.command)) { + case RTC_CONTROL: + hw->rtc.control = hw->rtc.bits; + break; + case RTC_FORCE_IRQ: + mLOG(GBA_HW, STUB, "Unimplemented RTC command %u", RTCCommandDataGetCommand(hw->rtc.command)); + break; + case RTC_RESET: + case RTC_DATETIME: + case RTC_TIME: + break; + } + + hw->rtc.bits = 0; + hw->rtc.bitsRead = 0; + --hw->rtc.bytesRemaining; + if (hw->rtc.bytesRemaining <= 0) { + hw->rtc.bytesRemaining = RTC_BYTES[RTCCommandDataGetCommand(hw->rtc.command)]; } } unsigned _rtcOutput(struct GBACartridgeHardware* hw) { - uint8_t outputByte = 0; - if (!hw->rtc.commandActive) { - mLOG(GBA_HW, GAME_ERROR, "Attempting to use RTC without an active command"); - return 0; - } + uint8_t outputByte = 0xFF; switch (RTCCommandDataGetCommand(hw->rtc.command)) { case RTC_CONTROL: outputByte = hw->rtc.control; @@ -399,6 +404,7 @@ void _lightReadPins(struct GBACartridgeHardware* hw) { struct GBALuminanceSource* lux = hw->p->luminanceSource; mLOG(GBA_HW, DEBUG, "[SOLAR] Got reset"); hw->lightCounter = 0; + hw->lightEdge = true; // unverified (perhaps reset only happens on bit 1 rising edge?) if (lux) { if (lux->sample) { lux->sample(lux); @@ -414,7 +420,7 @@ void _lightReadPins(struct GBACartridgeHardware* hw) { hw->lightEdge = !(hw->pinState & 1); bool sendBit = hw->lightCounter >= hw->lightSample; - _outputPins(hw, sendBit << 3); + _outputPins(hw, (sendBit << 3) | (hw->pinState & 0x7)); mLOG(GBA_HW, DEBUG, "[SOLAR] Output %u with pins %u", hw->lightCounter, hw->pinState); } @@ -488,11 +494,15 @@ void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASeria state->hw.pinDirection = hw->direction; state->hw.devices = hw->devices; + GBASerializedHWFlags3 flags3 = 0; + flags3 = GBASerializedHWFlags3SetRtcSioOutput(flags3, hw->rtc.sioOutput); + state->hw.flags3 = flags3; + STORE_32(hw->rtc.bytesRemaining, 0, &state->hw.rtcBytesRemaining); - STORE_32(hw->rtc.transferStep, 0, &state->hw.rtcTransferStep); STORE_32(hw->rtc.bitsRead, 0, &state->hw.rtcBitsRead); STORE_32(hw->rtc.bits, 0, &state->hw.rtcBits); STORE_32(hw->rtc.commandActive, 0, &state->hw.rtcCommandActive); + flags1 = GBASerializedHWFlags1SetRtcSckEdge(flags1, hw->rtc.sckEdge); STORE_32(hw->rtc.command, 0, &state->hw.rtcCommand); STORE_32(hw->rtc.control, 0, &state->hw.rtcControl); memcpy(state->hw.time, hw->rtc.time, sizeof(state->hw.time)); @@ -539,11 +549,13 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer } } + hw->rtc.sioOutput = GBASerializedHWFlags3GetRtcSioOutput(state->hw.flags3); + LOAD_32(hw->rtc.bytesRemaining, 0, &state->hw.rtcBytesRemaining); - LOAD_32(hw->rtc.transferStep, 0, &state->hw.rtcTransferStep); LOAD_32(hw->rtc.bitsRead, 0, &state->hw.rtcBitsRead); LOAD_32(hw->rtc.bits, 0, &state->hw.rtcBits); LOAD_32(hw->rtc.commandActive, 0, &state->hw.rtcCommandActive); + hw->rtc.sckEdge = GBASerializedHWFlags1GetRtcSckEdge(flags1); LOAD_32(hw->rtc.command, 0, &state->hw.rtcCommand); LOAD_32(hw->rtc.control, 0, &state->hw.rtcControl); memcpy(hw->rtc.time, state->hw.time, sizeof(hw->rtc.time)); diff --git a/src/gba/serialize.c b/src/gba/serialize.c index d4908f539..89ec0fb94 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -15,7 +15,7 @@ #include MGBA_EXPORT const uint32_t GBASavestateMagic = 0x01000000; -MGBA_EXPORT const uint32_t GBASavestateVersion = 0x00000008; +MGBA_EXPORT const uint32_t GBASavestateVersion = 0x00000009; mLOG_DEFINE_CATEGORY(GBA_STATE, "GBA Savestate", "gba.serialize"); From 95206944697f9c2422b12c446741057ae04bdd02 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 29 Apr 2025 23:03:35 -0700 Subject: [PATCH 65/74] Util: Fix VFSMem truncation not affecting offsets --- src/util/test/vfs.c | 31 ++++++++++++++++++++++++++++++- src/util/vfs/vfs-mem.c | 6 ++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/util/test/vfs.c b/src/util/test/vfs.c index 2d1cfd56a..d0a4ef1b4 100644 --- a/src/util/test/vfs.c +++ b/src/util/test/vfs.c @@ -72,14 +72,33 @@ M_TEST_DEFINE(openNullMemChunkNonzero) { } M_TEST_DEFINE(resizeMem) { - uint8_t bytes[32]; + uint8_t bytes[32] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + }; struct VFile* vf = VFileFromMemory(bytes, 32); assert_non_null(vf); + assert_int_equal(vf->seek(vf, 0, SEEK_END), 32); assert_int_equal(vf->size(vf), 32); + vf->truncate(vf, 64); assert_int_equal(vf->size(vf), 32); + assert_int_equal(bytes[15], 0x0F); + assert_int_equal(bytes[16], 0x10); + assert_int_equal(vf->seek(vf, 0, SEEK_CUR), 32); + vf->truncate(vf, 16); + assert_int_equal(vf->size(vf), 16); + assert_int_equal(vf->seek(vf, 0, SEEK_CUR), 16); + + vf->truncate(vf, 64); assert_int_equal(vf->size(vf), 32); + assert_int_equal(bytes[15], 0x0F); + assert_int_equal(bytes[16], 0x00); + assert_int_equal(vf->seek(vf, 0, SEEK_CUR), 16); + vf->close(vf); } @@ -87,11 +106,16 @@ M_TEST_DEFINE(resizeConstMem) { uint8_t bytes[32] = {0}; struct VFile* vf = VFileFromConstMemory(bytes, 32); assert_non_null(vf); + assert_int_equal(vf->seek(vf, 0, SEEK_END), 32); assert_int_equal(vf->size(vf), 32); + vf->truncate(vf, 64); assert_int_equal(vf->size(vf), 32); + assert_int_equal(vf->seek(vf, 0, SEEK_CUR), 32); + vf->truncate(vf, 16); assert_int_equal(vf->size(vf), 32); + assert_int_equal(vf->seek(vf, 0, SEEK_CUR), 32); vf->close(vf); } @@ -99,11 +123,16 @@ M_TEST_DEFINE(resizeMemChunk) { uint8_t bytes[32] = {0}; struct VFile* vf = VFileMemChunk(bytes, 32); assert_non_null(vf); + assert_int_equal(vf->seek(vf, 0, SEEK_END), 32); assert_int_equal(vf->size(vf), 32); + vf->truncate(vf, 64); assert_int_equal(vf->size(vf), 64); + assert_int_equal(vf->seek(vf, 0, SEEK_CUR), 32); + vf->truncate(vf, 16); assert_int_equal(vf->size(vf), 16); + assert_int_equal(vf->seek(vf, 0, SEEK_CUR), 16); vf->close(vf); } diff --git a/src/util/vfs/vfs-mem.c b/src/util/vfs/vfs-mem.c index 05580f544..048194e2d 100644 --- a/src/util/vfs/vfs-mem.c +++ b/src/util/vfs/vfs-mem.c @@ -290,6 +290,9 @@ void _vfmUnmap(struct VFile* vf, void* memory, size_t size) { void _vfmTruncate(struct VFile* vf, size_t size) { struct VFileMem* vfm = (struct VFileMem*) vf; _vfmExpand(vfm, size); + if (vfm->offset > vfm->size) { + vfm->offset = vfm->size; + } } void _vfmTruncateNoExpand(struct VFile* vf, size_t size) { @@ -304,6 +307,9 @@ void _vfmTruncateNoExpand(struct VFile* vf, size_t size) { } vfm->size = size; + if (vfm->offset > vfm->size) { + vfm->offset = vfm->size; + } } void _vfmTruncateNoop(struct VFile* vf, size_t size) { From 2bc93330384d639c325f886af1b3144639533a91 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 2 May 2025 04:13:23 -0700 Subject: [PATCH 66/74] Qt: Reformat MBC names for better handling of translations --- src/platform/qt/GameBoy.cpp | 50 ++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/platform/qt/GameBoy.cpp b/src/platform/qt/GameBoy.cpp index 9f64ae3b1..8aec1ea13 100644 --- a/src/platform/qt/GameBoy.cpp +++ b/src/platform/qt/GameBoy.cpp @@ -79,31 +79,31 @@ QString GameBoy::mbcName(GBMemoryBankControllerType mbc) { if (s_mbcNames.isEmpty()) { s_mbcNames[GB_MBC_AUTODETECT] = tr("Autodetect"); s_mbcNames[GB_MBC_NONE] = tr("ROM Only"); - s_mbcNames[GB_MBC1] = tr("MBC1"); - s_mbcNames[GB_MBC2] = tr("MBC2"); - s_mbcNames[GB_MBC3] = tr("MBC3"); - s_mbcNames[GB_MBC3_RTC] = tr("MBC3 + RTC"); - s_mbcNames[GB_MBC5] = tr("MBC5"); - s_mbcNames[GB_MBC5_RUMBLE] = tr("MBC5 + Rumble"); - s_mbcNames[GB_MBC6] = tr("MBC6"); - s_mbcNames[GB_MBC7] = tr("MBC7 (Tilt)"); - s_mbcNames[GB_MMM01] = tr("MMM01"); - s_mbcNames[GB_HuC1] = tr("HuC-1"); - s_mbcNames[GB_HuC3] = tr("HuC-3"); - s_mbcNames[GB_POCKETCAM] = tr("Pocket Cam"); - s_mbcNames[GB_TAMA5] = tr("TAMA5"); - s_mbcNames[GB_UNL_WISDOM_TREE] = tr("Wisdom Tree"); - s_mbcNames[GB_UNL_NT_OLD_1] = tr("NT (old 1)"); - s_mbcNames[GB_UNL_NT_OLD_2] = tr("NT (old 2)"); - s_mbcNames[GB_UNL_NT_NEW] = tr("NT (new)"); - s_mbcNames[GB_UNL_PKJD] = tr("Pokémon Jade/Diamond"); - s_mbcNames[GB_UNL_BBD] = tr("BBD"); - s_mbcNames[GB_UNL_HITEK] = tr("Hitek"); - s_mbcNames[GB_UNL_GGB81] = tr("GGB-81"); - s_mbcNames[GB_UNL_LI_CHENG] = tr("Li Cheng"); - s_mbcNames[GB_UNL_SACHEN_MMC1] = tr("Sachen (MMC1)"); - s_mbcNames[GB_UNL_SACHEN_MMC2] = tr("Sachen (MMC2)"); - s_mbcNames[GB_UNL_SINTAX] = tr("Sintax"); + s_mbcNames[GB_MBC1] = "MBC1"; + s_mbcNames[GB_MBC2] = "MBC2"; + s_mbcNames[GB_MBC3] = "MBC3"; + s_mbcNames[GB_MBC3_RTC] = tr("%1 + RTC").arg("MBC3"); + s_mbcNames[GB_MBC5] = "MBC5"; + s_mbcNames[GB_MBC5_RUMBLE] = tr("%1 + Rumble").arg("MBC5"); + s_mbcNames[GB_MBC6] = "MBC6"; + s_mbcNames[GB_MBC7] = tr("%1 (Tilt)").arg("MBC7"); + s_mbcNames[GB_MMM01] = "MMM01"; + s_mbcNames[GB_HuC1] = "HuC-1"; + s_mbcNames[GB_HuC3] = "HuC-3"; + s_mbcNames[GB_POCKETCAM] = "Pocket Cam"; + s_mbcNames[GB_TAMA5] = "TAMA5"; + s_mbcNames[GB_UNL_WISDOM_TREE] = "Wisdom Tree"; + s_mbcNames[GB_UNL_NT_OLD_1] = tr("%1 (old 1)").arg("NT"); + s_mbcNames[GB_UNL_NT_OLD_2] = tr("%1 (old 2)").arg("NT"); + s_mbcNames[GB_UNL_NT_NEW] = tr("%1 (new)").arg("NT"); + s_mbcNames[GB_UNL_PKJD] = "Pokémon Jade/Diamond"; + s_mbcNames[GB_UNL_BBD] = "BBD"; + s_mbcNames[GB_UNL_HITEK] = "Hitek"; + s_mbcNames[GB_UNL_GGB81] = "GGB-81"; + s_mbcNames[GB_UNL_LI_CHENG] = "Li Cheng"; + s_mbcNames[GB_UNL_SACHEN_MMC1] = "Sachen (MMC1)"; + s_mbcNames[GB_UNL_SACHEN_MMC2] = "Sachen (MMC2)"; + s_mbcNames[GB_UNL_SINTAX] = "Sintax"; } return s_mbcNames[mbc]; From 33d8c859c3aa720f19bd15059f7cd62b81cbfa21 Mon Sep 17 00:00:00 2001 From: Felipe Date: Sat, 15 Feb 2025 12:38:02 +0000 Subject: [PATCH 67/74] 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 | 92 ++++++++++++++++---------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/src/platform/qt/ts/mgba-pt_BR.ts b/src/platform/qt/ts/mgba-pt_BR.ts index 2ff13fc8d..73ba6df06 100644 --- a/src/platform/qt/ts/mgba-pt_BR.ts +++ b/src/platform/qt/ts/mgba-pt_BR.ts @@ -1143,7 +1143,7 @@ Tamanho do download: %3 Sintax - + Sintaxe @@ -3267,7 +3267,7 @@ Tamanho do download: %3 Info - Info + Informações @@ -3422,147 +3422,147 @@ Tamanho do download: %3 Data read - + Leitura dos Dados Data written - + Dados gravados Code executed - + Código executado Code aborted - + Código abortado 8-bit access - + Acesso de 8 bits 16-bit access - + Acesso de 16 bits 32-bit access - + Acesso de 32 bits 64-bit access - + Acesso de 64 bits Accessed by instruction - + Acessado pela instrução Accessed by DMA - + Acessado pelo DMA Accessed by BIOS - + Acessado pela BIOS Compressed data - + Dados comprimidos Accessed by memory copy - + Acessado pela cópia da memória (Unknown extra bit 5) - + (Bit extra desconhecido 5) (Unknown extra bit 6) - + (Bit extra desconhecido 6) (Unknown extra bit 7) - + (Bit extra desconhecido 7) Invalid instruction - + Instrução inválida Invalid read - + Leitura inválida Invalid write - + Gravação inválida Invalid executable address - + Endereço do executável inválido (Private bit 0) - + (Bit privado 0) (Private bit 1) - + (Bit privado 1) ARM code - + Código ARM Instruction opcode - + Opcode da instrução (Private bit 2) - + (Bit privado 2) Thumb code - + Código do thumb Instruction operand - + Operando da instrução (Private bit 3) - + (Bit privado 3) (Unknown) - (Desconhecido) + (Desconhecido) @@ -3595,12 +3595,12 @@ Tamanho do download: %3 Load - Carregar + Carregar Unload - + Descarregar @@ -3935,37 +3935,37 @@ Tamanho do download: %3 Address: - + Endereço: Alignment: - + Alinhamento: 1 Byte - + 1 Byte 2 Bytes - + 2 Bytes 4 Bytes - + 4 Bytes Signed: - + Assinado: Unsigned: - + Não assinado: @@ -3980,12 +3980,12 @@ Tamanho do download: %3 Selected address accesses - + Acessos do endereço selecionado Logging configuration - + Configuração do registro @@ -4006,12 +4006,12 @@ Tamanho do download: %3 Clearing invalid save ID - Limpando a ID inválida do save + Limpando a ID do salvamento inválido Clearing invalid preferred ID - Limpando a ID inválida preferida + Limpando a ID preferida inválida @@ -5034,9 +5034,9 @@ Tamanho do download: %3 Shaders are not supported when the display driver is not OpenGL. If it is set to OpenGL and you still see this, your graphics card or drivers may be too old. - Os shaders não são suportados quando o driver de exibição não é OpenGL. + Shaders não são suportados quando o driver de exibição não é OpenGL. -Se ele está definido como OpenGL e você ainda ver isto sua placa gráfica ou drivers podem ser muito antigos. +Se estiver definido como OpenGL e você ainda ver isto, sua placa gráfica ou drivers podem ser muito antigos. From 057c360befb774466c70f3b55a149855a581268a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=99=A8=E6=97=AD?= Date: Tue, 11 Mar 2025 02:56:59 +0100 Subject: [PATCH 68/74] Qt: Update translation (Chinese (Simplified Han script)) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/zh_Hans/ --- src/platform/qt/ts/mgba-zh_CN.ts | 94 ++++++++++++++++---------------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/src/platform/qt/ts/mgba-zh_CN.ts b/src/platform/qt/ts/mgba-zh_CN.ts index d391fd3d6..30b7b2320 100644 --- a/src/platform/qt/ts/mgba-zh_CN.ts +++ b/src/platform/qt/ts/mgba-zh_CN.ts @@ -1143,7 +1143,7 @@ Download size: %3 Sintax - + 语法 @@ -3422,147 +3422,147 @@ Download size: %3 Data read - + 数据读取 Data written - + 数据写入 Code executed - + 代码执行 Code aborted - + 代码已中止 8-bit access - + 8 位访问 16-bit access - + 16 位访问 32-bit access - + 32 位访问 64-bit access - + 64 位访问 Accessed by instruction - + 通过指令访问 Accessed by DMA - + 通过 DMA 访问 Accessed by BIOS - + 通过 BIOS 访问 Compressed data - + 压缩数据 Accessed by memory copy - + 通过内存复制访问 (Unknown extra bit 5) - + (未知附加位 5) (Unknown extra bit 6) - + (未知附加位 6) (Unknown extra bit 7) - + (未知附加位 7) Invalid instruction - + 指令无效 Invalid read - + 读取无效 Invalid write - + 写入无效 Invalid executable address - + 无效的可执行地址 (Private bit 0) - + (私人位 0) (Private bit 1) - + (私人位 1) ARM code - + ARM 代码 Instruction opcode - + 指令操作码 (Private bit 2) - + (私人位 2) Thumb code - + 拇指代码 Instruction operand - + 指令操作数 (Private bit 3) - + (私人位 3) (Unknown) - (未知) + (未知) @@ -3595,12 +3595,12 @@ Download size: %3 Load - 载入 + 加载 Unload - + 取消加载 @@ -3935,37 +3935,37 @@ Download size: %3 Address: - + 地址: Alignment: - + 对齐: 1 Byte - + 1 字节 2 Bytes - + 2 字节 4 Bytes - + 4 字节 Signed: - + 签名: Unsigned: - + 未签名: @@ -3975,12 +3975,12 @@ Download size: %3 Selected address accesses - + 所选地址访问 Logging configuration - + 日志配置 @@ -4559,7 +4559,7 @@ Download size: %3 ROM header - + ROM 头 @@ -5034,7 +5034,9 @@ Download size: %3 Shaders are not supported when the display driver is not OpenGL. If it is set to OpenGL and you still see this, your graphics card or drivers may be too old. - + 当显示驱动程序不是 OpenGL 时,不支持着色器。 + +如果设置为 OpenGL 并且您仍然看到此信息,则您的显卡或驱动程序可能太旧了。 @@ -5841,17 +5843,17 @@ If it is set to OpenGL and you still see this, your graphics card or drivers may mGBA Shaders - + mGBA 着色器 Error loading shader - + 加载着色器时出错 The shader "%1" could not be loaded successfully. - + 着色器“%1”无法成功加载。 From 85ec3e2dd5b58f75166f0d234090624d1ddf51af Mon Sep 17 00:00:00 2001 From: Champ0999 Date: Sun, 16 Mar 2025 12:00:45 +0100 Subject: [PATCH 69/74] Qt: Update translation (Italian) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/it/ --- src/platform/qt/ts/mgba-it.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/platform/qt/ts/mgba-it.ts b/src/platform/qt/ts/mgba-it.ts index 159c691bd..9a1833f77 100644 --- a/src/platform/qt/ts/mgba-it.ts +++ b/src/platform/qt/ts/mgba-it.ts @@ -3562,7 +3562,7 @@ Dimensione del download: %3 (Unknown) - (Sconosciuto) + (Sconosciuto) @@ -3595,7 +3595,7 @@ Dimensione del download: %3 Load - Carica + Carica @@ -3945,17 +3945,17 @@ Dimensione del download: %3 1 Byte - + 1 Byte 2 Bytes - + 2 Byte 4 Bytes - + 4 Byte @@ -4554,7 +4554,7 @@ Dimensione del download: %3 MD5 - + MD5 From 160f42e7201908a4cacd22cb348328418cc921d2 Mon Sep 17 00:00:00 2001 From: ssantos Date: Fri, 4 Apr 2025 20:09:26 +0200 Subject: [PATCH 70/74] Qt: Update translation (Portuguese (Portugal)) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/pt_PT/ --- src/platform/qt/ts/mgba-pt_PT.ts | 138 ++++++++++++++++--------------- 1 file changed, 70 insertions(+), 68 deletions(-) diff --git a/src/platform/qt/ts/mgba-pt_PT.ts b/src/platform/qt/ts/mgba-pt_PT.ts index f045b6a50..7f87e2510 100644 --- a/src/platform/qt/ts/mgba-pt_PT.ts +++ b/src/platform/qt/ts/mgba-pt_PT.ts @@ -482,7 +482,7 @@ Tamanho da descarga: %3 Failed to create an OpenGL 3 context, trying old-style... - + Falhou ao criar um contexto do OpenGL 3, tentando o estilo antigo... @@ -1143,7 +1143,7 @@ Tamanho da descarga: %3 Sintax - + Sintaxe @@ -3414,7 +3414,7 @@ Tamanho da descarga: %3 Failed to open memory log file - + Falhou em abrir o ficheiro do registo de memória @@ -3422,147 +3422,147 @@ Tamanho da descarga: %3 Data read - + Leitura dos Dados Data written - + Dados gravados Code executed - + Código executado Code aborted - + Código abortado 8-bit access - + Acesso de 8 bits 16-bit access - + Acesso de 16 bits 32-bit access - + Acesso de 32 bits 64-bit access - + Acesso de 64 bits Accessed by instruction - + Acedido pela instrução Accessed by DMA - + Acedido pelo DMA Accessed by BIOS - + Acedido pela BIOS Compressed data - + Dados comprimidos Accessed by memory copy - + Acedido pela cópia da memória (Unknown extra bit 5) - + (Bit extra desconhecido 5) (Unknown extra bit 6) - + (Bit extra desconhecido 6) (Unknown extra bit 7) - + (Bit extra desconhecido 7) Invalid instruction - + Instrução inválida Invalid read - + Leitura inválida Invalid write - + Gravação inválida Invalid executable address - + Endereço do executável inválido (Private bit 0) - + (Bit privado 0) (Private bit 1) - + (Bit privado 1) ARM code - + Código ARM Instruction opcode - + Opcode da instrução (Private bit 2) - + (Bit privado 2) Thumb code - + Código do thumb Instruction operand - + Operando da instrução (Private bit 3) - + (Bit privado 3) (Unknown) - (Desconhecido) + (Desconhecido) @@ -3570,68 +3570,68 @@ Tamanho da descarga: %3 Memory access logging - + Registo de acesso a memória Log file - + Ficheiro do registo Browse - Explorar + Explorar Log additional information (uses 3× space) - + Informação adicional do registo (usa 3x mais espaço) Load existing file if present - + Carregar o ficheiro existente se estiver presente Load - Carregar + Carregar Unload - + Descarregar Regions - + Regiões Export ROM snapshot - + Exportar a snapshot da ROM Start - + Iniciar Stop - Parar + Parar Select access log file - + Selecionar o ficheiro de registo do acesso Memory access logs (*.mal) - + Registos de acesso a memória (*.mal) @@ -3935,37 +3935,37 @@ Tamanho da descarga: %3 Address: - + Endereço: Alignment: - + Alinhamento: 1 Byte - + 1 Byte 2 Bytes - + 2 Bytes 4 Bytes - + 4 Bytes Signed: - + Assinado: Unsigned: - + Não assinado: @@ -3980,12 +3980,12 @@ Tamanho da descarga: %3 Selected address accesses - + Acessos do endereço selecionado Logging configuration - + Configuração do registo @@ -4006,12 +4006,12 @@ Tamanho da descarga: %3 Clearing invalid save ID - + A limpar a ID do gravamento inválido Clearing invalid preferred ID - + A limpar a ID preferida inválida @@ -4544,7 +4544,7 @@ Tamanho da descarga: %3 File information - + Informação do ficheiro @@ -4554,12 +4554,12 @@ Tamanho da descarga: %3 MD5 - + MD5 ROM header - + Cabeçalho da ROM @@ -4574,12 +4574,12 @@ Tamanho da descarga: %3 Maker Code: - + Código do Criador: Revision: - + Revisão: @@ -4862,7 +4862,7 @@ Tamanho da descarga: %3 &Load most recent - + &Carregar o mais recente @@ -5034,7 +5034,9 @@ Tamanho da descarga: %3 Shaders are not supported when the display driver is not OpenGL. If it is set to OpenGL and you still see this, your graphics card or drivers may be too old. - + Shaders não são suportados quando o driver de exibição não é OpenGL. + +Se estiver definido como OpenGL e ainda ver isto, a sua placa gráfica ou drivers podem ser muito antigos. @@ -5843,17 +5845,17 @@ If it is set to OpenGL and you still see this, your graphics card or drivers may mGBA Shaders - + Shaders do mGBA Error loading shader - + Erro ao carregar o shader The shader "%1" could not be loaded successfully. - + O shader "%1" não pôde ser carregado com sucesso. @@ -6708,7 +6710,7 @@ If it is set to OpenGL and you still see this, your graphics card or drivers may &Lock frame size - + &Travar o tamanho do frame @@ -6883,7 +6885,7 @@ If it is set to OpenGL and you still see this, your graphics card or drivers may Log memory &accesses... - + Registar os acessos a &memória... From a669d704589e62dc8eef0bb852073d67f621dec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20=C3=87etinkal?= Date: Tue, 8 Apr 2025 19:09:10 +0200 Subject: [PATCH 71/74] Qt: Update translation (Turkish) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/tr/ --- src/platform/qt/ts/mgba-tr.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/ts/mgba-tr.ts b/src/platform/qt/ts/mgba-tr.ts index bbe8c524b..7b4c8ddfb 100644 --- a/src/platform/qt/ts/mgba-tr.ts +++ b/src/platform/qt/ts/mgba-tr.ts @@ -466,7 +466,7 @@ Yeni sürüm: %2 Break - Ara ver + Kır From 310a8a3634c3f20c93c9f956c0840637b21e9175 Mon Sep 17 00:00:00 2001 From: Daniel Nylander Date: Sun, 13 Apr 2025 17:33:25 +0200 Subject: [PATCH 72/74] Qt: Update translation (Swedish) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/sv/ --- src/platform/qt/ts/mgba-sv.ts | 78 ++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/src/platform/qt/ts/mgba-sv.ts b/src/platform/qt/ts/mgba-sv.ts index 6a680c144..d666f4b49 100644 --- a/src/platform/qt/ts/mgba-sv.ts +++ b/src/platform/qt/ts/mgba-sv.ts @@ -395,7 +395,7 @@ Hämtningsstorlek: %3 Rewinding not currently enabled - + Tillbakaspolning inte aktiverad för närvarande @@ -420,7 +420,7 @@ Hämtningsstorlek: %3 Can't yank pack in unexpected platform! - + Det går inte att dra ut paketet på en oväntad plattform! @@ -582,7 +582,7 @@ Hämtningsstorlek: %3 Create forwarder - + Skapa forwarder @@ -609,7 +609,7 @@ Hämtningsstorlek: %3 Forwarder base: - + Forwarder-bas: @@ -629,7 +629,7 @@ Hämtningsstorlek: %3 Base file: - + Basfil: @@ -689,7 +689,7 @@ Hämtningsstorlek: %3 Select base file - + Välj basfil @@ -699,7 +699,7 @@ Hämtningsstorlek: %3 Forwarder finished building - + Forwarder färdigbyggd @@ -709,7 +709,7 @@ Hämtningsstorlek: %3 Failed to build forwarder - + Misslyckades med att bygga forwarder @@ -1308,7 +1308,7 @@ Hämtningsstorlek: %3 Current scanline - + Aktuell skanlinje @@ -3414,7 +3414,7 @@ Hämtningsstorlek: %3 Failed to open memory log file - Misslyckades med att öppna minnesloggfil + Misslyckades med att öppna minnesloggfil @@ -3422,22 +3422,22 @@ Hämtningsstorlek: %3 Data read - + Data läst Data written - + Data skriven Code executed - + Kod exekverad Code aborted - + Kod avbruten @@ -3477,7 +3477,7 @@ Hämtningsstorlek: %3 Compressed data - + Komprimerad data @@ -3502,17 +3502,17 @@ Hämtningsstorlek: %3 Invalid instruction - + Ogiltig instruktion Invalid read - + Ogiltig läsning Invalid write - + Ogiltig skrivning @@ -3532,7 +3532,7 @@ Hämtningsstorlek: %3 ARM code - + ARM-kod @@ -3562,7 +3562,7 @@ Hämtningsstorlek: %3 (Unknown) - (Okänd) + (Okänd) @@ -3595,12 +3595,12 @@ Hämtningsstorlek: %3 Load - Läs in + Läs in Unload - + Läs ur @@ -3935,7 +3935,7 @@ Hämtningsstorlek: %3 Address: - + Adress: @@ -3945,17 +3945,17 @@ Hämtningsstorlek: %3 1 Byte - + 1 Byte 2 Bytes - + 2 Byte 4 Bytes - + 4 Byte @@ -3985,7 +3985,7 @@ Hämtningsstorlek: %3 Logging configuration - + Loggningskonfiguration @@ -4001,27 +4001,27 @@ Hämtningsstorlek: %3 Trying to detach a multiplayer player that's not attached - + Försöker koppla bort en flerspelar-spelare som inte är ansluten Clearing invalid save ID - + Tömmer ogiltigt spar-ID Clearing invalid preferred ID - + Tömmer ogiltigt föredraget ID Trying to get player ID for a multiplayer player that's not attached - + Försöker få spelar-ID för en flerspelar-spelare som inte är ansluten Trying to get save ID for a multiplayer player that's not attached - + Försöker få spar-ID för en flerspelar-spelare som inte är ansluten @@ -5034,7 +5034,9 @@ Hämtningsstorlek: %3 Shaders are not supported when the display driver is not OpenGL. If it is set to OpenGL and you still see this, your graphics card or drivers may be too old. - + Shaders stöds inte när bildskärmsdrivrutinen inte är OpenGL. + +Om den är inställd på OpenGL och du fortfarande ser detta, kan ditt grafikkort eller dina drivrutiner vara för gamla. @@ -5497,7 +5499,7 @@ If it is set to OpenGL and you still see this, your graphics card or drivers may Allow opposing input directions - + Tillåt motsatta inmatningsriktningar @@ -5686,7 +5688,7 @@ If it is set to OpenGL and you still see this, your graphics card or drivers may Enable VBA bug compatibility in ROM hacks - + Aktivera VBA-buggkompatibilitet i ROM-hack @@ -5814,12 +5816,12 @@ If it is set to OpenGL and you still see this, your graphics card or drivers may Default sprite colors 1: - + Spritefärger som standard 1: Default sprite colors 2: - + Spritefärger som standard 2: @@ -6538,7 +6540,7 @@ If it is set to OpenGL and you still see this, your graphics card or drivers may Use player %0 save game - + Använd sparat spel för spelare %0 From 5fe256f90e5bd232a6a3952b56329885e1a86aa6 Mon Sep 17 00:00:00 2001 From: "Michelangelo Chaume (MickMick Washes Things)" Date: Mon, 14 Apr 2025 16:58:21 +0200 Subject: [PATCH 73/74] Qt: Update translation (French) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/fr/ --- src/platform/qt/ts/mgba-fr.ts | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/platform/qt/ts/mgba-fr.ts b/src/platform/qt/ts/mgba-fr.ts index af564a1db..1ec1e78d6 100644 --- a/src/platform/qt/ts/mgba-fr.ts +++ b/src/platform/qt/ts/mgba-fr.ts @@ -6,22 +6,22 @@ Game Boy Advance ROMs (%1) - ROMs de Game Boy Advance (%1) + ROMs de Game Boy Advance (%1) Game Boy ROMs (%1) - ROMs de Game Boy (%1) + ROMs de Game Boy (%1) All ROMs (%1) - Toutes les ROM (%1) + Toutes les ROMs (%1) %1 Video Logs (*.mvl) - %1 Journaux vidéo (*.mvl) + %1 Journaux vidéo (*.mvl) @@ -570,7 +570,7 @@ Taille du téléchargement : %3 Background - Arrière plan + Arrière plan @@ -600,7 +600,7 @@ Taille du téléchargement : %3 Browse - Parcourir + Parcourir @@ -2740,7 +2740,7 @@ Taille du téléchargement : %3 1/16 - 4K {1/16?} + 1/16 @@ -3598,7 +3598,7 @@ Taille du téléchargement : %3 Browse - Parcourir + Parcourir @@ -3613,7 +3613,7 @@ Taille du téléchargement : %3 Load - Charger + Charger @@ -3638,7 +3638,7 @@ Taille du téléchargement : %3 Stop - Arrêter + Arrêter @@ -4530,7 +4530,7 @@ Taille du téléchargement : %3 Portable Network Graphics (*.png) - Portable Network Graphics (*.png) + Portable Network Graphics (*.png) @@ -4739,7 +4739,7 @@ Taille du téléchargement : %3 Browse - Parcourir + Parcourir @@ -4764,7 +4764,7 @@ Taille du téléchargement : %3 SRAM - SRAM + SRAM @@ -4885,12 +4885,12 @@ Taille du téléchargement : %3 &Reset - &Réinitialiser + &Réinitialiser 0 - 0 + 0 @@ -5026,7 +5026,7 @@ Taille du téléchargement : %3 None - Aucun + Aucun @@ -5076,7 +5076,7 @@ If it is set to OpenGL and you still see this, your graphics card or drivers may Select image - Choisir une image + Choisir une image From a657159fffa721b931210b550a37cebfea86e86a Mon Sep 17 00:00:00 2001 From: Not-underscore Date: Thu, 17 Apr 2025 22:13:31 +0200 Subject: [PATCH 74/74] 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 | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/platform/qt/ts/mgba-es.ts b/src/platform/qt/ts/mgba-es.ts index 1b849d3d4..0baf53735 100644 --- a/src/platform/qt/ts/mgba-es.ts +++ b/src/platform/qt/ts/mgba-es.ts @@ -1143,7 +1143,7 @@ Tamaño de descarga: %3 Sintax - + Sintaxis @@ -3422,72 +3422,72 @@ Tamaño de descarga: %3 Data read - + Lectura de datos Data written - + Datos escritos Code executed - + Código ejecutado Code aborted - + Código abortado 8-bit access - + Acceso de 8 bits 16-bit access - + Acceso de 16 bits 32-bit access - + Acceso de 32 bits 64-bit access - + Acceso de 64 bits Accessed by instruction - + Accedido por instrucción Accessed by DMA - + Accedido mediante DMA Accessed by BIOS - + Accedido por la BIOS Compressed data - + Datos comprimidos Accessed by memory copy - + Accedido mediante copia de memoria (Unknown extra bit 5) - + (Bit desconocido 5)