diff --git a/CHANGES b/CHANGES index d7b13c1aa..09f0f22cb 100644 --- a/CHANGES +++ b/CHANGES @@ -5,10 +5,12 @@ 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 - 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 @@ -23,23 +25,22 @@ 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 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) + - 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) - 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) - GBA: Improve detection of valid ELF ROMs @@ -61,11 +62,24 @@ 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) - 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: diff --git a/CMakeLists.txt b/CMakeLists.txt index ad464bab0..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) @@ -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/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-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..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; @@ -47,7 +42,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 +183,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/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/include/mgba/core/library.h b/include/mgba/core/library.h index ededd5463..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; @@ -24,6 +26,9 @@ struct mLibraryEntry { enum mPlatform platform; size_t filesize; uint32_t crc32; + uint8_t md5[16]; + uint8_t sha1[20]; + int platformModels; }; #ifdef USE_SQLITE3 diff --git a/include/mgba/internal/gba/cart/gpio.h b/include/mgba/internal/gba/cart/gpio.h index be3cd9173..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]; @@ -68,8 +69,9 @@ struct GBACartridgeHardware { enum GPIODirection readWrite; uint16_t* gpioBase; - uint16_t pinState; - uint16_t direction; + uint8_t writeLatch; + uint8_t pinState; + uint8_t direction; struct GBARTC rtc; diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index 6e9a3124a..5f42cee42 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -165,8 +165,12 @@ 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: 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 @@ -183,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 @@ -282,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); @@ -289,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); @@ -376,16 +384,18 @@ struct GBASerializedState { } dma[4]; struct { - uint16_t pinState; - uint16_t pinDirection; + uint8_t pinState; + uint8_t writeLatch; + uint8_t pinDirection; + GBASerializedHWFlags3 flags3; int32_t rtcBytesRemaining; - int32_t rtcTransferStep; + int32_t reserved0; int32_t rtcBitsRead; int32_t rtcBits; 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/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 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 ( 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) 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 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;) 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/core/library.c b/src/core/library.c index 1c92a0cbc..9a7f1b636 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 @@ -33,7 +38,10 @@ 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 " \ "CASE WHEN :useInternalCode THEN roms.internalCode = :internalCode ELSE 1 END" #define CONSTRAINTS \ @@ -58,6 +66,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"); @@ -92,12 +114,40 @@ 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) { + 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); + } } 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)); @@ -124,6 +174,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," @@ -139,20 +190,39 @@ 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);" + "\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" + "\n ADD 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, size, internalCode, platform) VALUES (:crc32, :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; } @@ -297,7 +367,18 @@ 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.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; @@ -402,14 +483,34 @@ 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) { 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/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/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/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) { diff --git a/src/gb/core.c b/src/gb/core.c index f71b570e0..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[] = { @@ -150,7 +151,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 +163,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 @@ -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; } @@ -651,6 +661,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 +690,7 @@ static void _GBCoreReset(struct mCore* core) { bios = NULL; } } +#endif if (found && bios) { GBLoadBIOS(gb, bios); } @@ -1128,7 +1140,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/gb/gb.c b/src/gb/gb.c index 875cbf86e..102e4cfa2 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"); @@ -85,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)); @@ -230,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) { @@ -339,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; } @@ -435,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; @@ -444,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) { @@ -550,7 +557,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 +576,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 +873,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: 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); 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; } } diff --git a/src/gba/cart/gpio.c b/src/gba/cart/gpio.c index a44bab294..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); @@ -49,6 +50,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 +66,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,19 +77,25 @@ 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; + hw->pinState = hw->writeLatch; } _readPins(hw); break; case GPIO_REG_DIRECTION: - hw->direction = value; + 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; + hw->readWrite = value & 0x1; break; default: mLOG(GBA_HW, WARN, "Invalid GPIO address"); @@ -106,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)); @@ -138,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); } } @@ -150,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; @@ -391,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); @@ -406,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); } @@ -475,15 +489,20 @@ 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); - STORE_16(hw->direction, 0, &state->hw.pinDirection); + state->hw.writeLatch = hw->writeLatch; + state->hw.pinState = hw->pinState; + 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)); @@ -512,8 +531,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); - LOAD_16(hw->direction, 0, &state->hw.pinDirection); + 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) { @@ -529,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/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; } diff --git a/src/gba/core.c b/src/gba/core.c index b369c92df..6c2ffe1f5 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -31,6 +31,7 @@ #include #endif #include +#include #include #include #include @@ -296,7 +297,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 +309,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 @@ -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; } @@ -1352,7 +1366,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/gba/serialize.c b/src/gba/serialize.c index 3866c0c80..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 = 0x00000007; +MGBA_EXPORT const uint32_t GBASavestateVersion = 0x00000009; mLOG_DEFINE_CATEGORY(GBA_STATE, "GBA Savestate", "gba.serialize"); 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 diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index d2a871546..ea05cac05 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: @@ -1385,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; 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]); 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 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); } } diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 9858fddb0..0dcfbaefb 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) @@ -285,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() @@ -496,7 +511,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 +554,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(WARNING "${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/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/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; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index b3a7d62c8..00af3cb8d 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; } @@ -807,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(); } 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() { 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/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]; 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/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 - - 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; diff --git a/src/platform/qt/ROMInfo.cpp b/src/platform/qt/ROMInfo.cpp index 25aafeda8..3cd8c3f01 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) { @@ -25,6 +34,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,33 +51,48 @@ 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")); 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 (!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])); + } else { + m_ui.md5->setText(tr("(unknown)")); + } + + 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])); + } else { + m_ui.sha1->setText(tr("(unknown)")); + } + +#ifdef USE_SQLITE3 + if (db) { + NoIntroGame 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); + } 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()) { 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/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/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/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) { diff --git a/src/platform/qt/VideoView.cpp b/src/platform/qt/VideoView.cpp index b03949c2f..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); @@ -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"; @@ -132,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/VideoView.ui b/src/platform/qt/VideoView.ui index c56c5e1b5..604d63aca 100644 --- a/src/platform/qt/VideoView.ui +++ b/src/platform/qt/VideoView.ui @@ -302,6 +302,11 @@ VP9 + + + Ut Video + + FFV1 diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index a224c3ba4..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); @@ -580,29 +588,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...); - connect(this, &Window::shutdown, name->get(), &QWidget::close); + *name = new T(arg...); + connect(this, &Window::shutdown, name->data(), &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->data(), &QWidget::close); + connect(this, &Window::shutdown, name->data(), &QWidget::close); + if (!keepalive) { + (*name)->setAttribute(Qt::WA_DeleteOnClose); } - connect(this, &Window::shutdown, name->get(), &QWidget::close); } (*name)->show(); - (*name)->setFocus(Qt::PopupFocusReason); + (*name)->activateWindow(); + (*name)->raise(); }; } @@ -633,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); } @@ -1101,7 +1105,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()); @@ -1414,7 +1418,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 +1699,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 +1714,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); + connect(this, &Window::shutdown, m_overrideView.data(), &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.data(), &QWidget::close); + } + m_sensorView->show(); + m_sensorView->activateWindow(); + m_sensorView->raise(); + }, "tools"); addGameAction(tr("&Cheats..."), "cheatsWindow", openControllerTView(), "tools"); #ifdef ENABLE_SCRIPTING @@ -1750,23 +1766,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, this, [this]() { - if (m_frameView) { - m_frameView->close(); - } - }); - connect(m_frameView, &QObject::destroyed, this, [this]() { - m_frameView = nullptr; - }); - 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"); @@ -2055,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 cb467f89d..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,12 +175,12 @@ private: void clearMRU(); void updateMRU(); - void openView(QWidget* widget); + void ensureScripting(); 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 +243,14 @@ private: bool m_multiActive = true; int m_playerId; - std::unique_ptr m_overrideView; - std::unique_ptr m_sensorView; - std::unique_ptr m_dolphinView; - FrameView* m_frameView = nullptr; + 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 diff --git a/src/platform/qt/library/LibraryController.cpp b/src/platform/qt/library/LibraryController.cpp index 2a1ba1546..1ed5411b5 100644 --- a/src/platform/qt/library/LibraryController.cpp +++ b/src/platform/qt/library/LibraryController.cpp @@ -8,39 +8,17 @@ #include "ConfigController.h" #include "GBAApp.h" -#include "LibraryGrid.h" -#include "LibraryTree.h" +#include "LibraryModel.h" +#include "utils.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 +33,47 @@ 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()); + updateViewStyle(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 +84,63 @@ void LibraryController::setViewStyle(LibraryStyle newStyle) { if (m_currentStyle == newStyle) { return; } - m_currentStyle = newStyle; + updateViewStyle(newStyle); +} - AbstractGameList* newCurrentList = nullptr; - if (newStyle == LibraryStyle::STYLE_LIST || newStyle == LibraryStyle::STYLE_TREE) { - newCurrentList = m_libraryTree.get(); - } else { - newCurrentList = m_libraryGrid.get(); +void LibraryController::updateViewStyle(LibraryStyle newStyle) { + 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() { @@ -110,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; @@ -149,42 +192,34 @@ void LibraryController::refresh() { setDisabled(true); - QHash removedEntries = m_entries; - QHash updatedEntries; + QSet removedEntries(qListToSet(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.values()); + 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,61 @@ 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); +} + +// 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++) { + m_treeView->resizeColumnToContents(column); + 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) { + int newLocationWidth = m_treeView->viewport()->width() - (totalWidth - locationWidth); + if (newLocationWidth < 100) { + newLocationWidth = 100; + } + m_treeView->setColumnWidth(LibraryModel::COL_LOCATION, newLocationWidth); + } + } +} diff --git a/src/platform/qt/library/LibraryController.h b/src/platform/qt/library/LibraryController.h index 1b5d06572..483010c79 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,21 +65,34 @@ 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 + void updateViewStyle(LibraryStyle newStyle); 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..c2aeae1ab --- /dev/null +++ b/src/platform/qt/library/LibraryEntry.cpp @@ -0,0 +1,73 @@ +/* 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 "utils.h" + +#include + +using namespace QGBA; + +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)); +} + +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) + , platformModels(entry->platformModels) + , filesize(entry->filesize) + , crc32(entry->crc32) + , sha1(reinterpret_cast(entry->sha1), sizeof(entry->sha1)) +{ +} + +bool LibraryEntry::isNull() const { + return fullpath.isNull(); +} + +QString LibraryEntry::displayTitle(bool showFilename) const { + if (showFilename || title.isNull()) { + return filename; + } + return title; +} + +QString LibraryEntry::displayPlatform() const { + return nicePlatformFormat(platform, platformModels); +} + +bool LibraryEntry::operator==(const LibraryEntry& other) const { + return other.fullpath == fullpath; +} + +uint64_t LibraryEntry::checkHash() const { + return ::checkHash(filesize, crc32, getSha1Prefix(sha1)); +} + +uint64_t LibraryEntry::checkHash(const mLibraryEntry* entry) { + 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 new file mode 100644 index 000000000..cdd132840 --- /dev/null +++ b/src/platform/qt/library/LibraryEntry.h @@ -0,0 +1,50 @@ +/* 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 displayPlatform() const; + + QString base; + QString filename; + QString fullpath; + QString title; + QByteArray internalTitle; + QByteArray internalCode; + mPlatform platform; + int platformModels; + size_t filesize; + uint32_t crc32; + QByteArray sha1; + + 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..6c09025b7 --- /dev/null +++ b/src/platform/qt/library/LibraryModel.cpp @@ -0,0 +1,473 @@ +/* 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", + "SGB", +}; + +static QHash platformIcons; + +LibraryModel::LibraryModel(QObject* parent) + : QAbstractItemModel(parent) + , m_treeMode(false) + , m_showFilename(false) +{ + 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; + } + } +} + +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.emplace_back(new LibraryEntry(item)); + if (!m_pathIndex.contains(item.base)) { + m_pathOrder << item.base; + } + m_pathIndex[item.base] << m_games.back().get(); +} + +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, 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); + if (pos < firstModifiedIndex) { + firstModifiedIndex = pos; + } + LibraryEntry* entry = m_games[pos].get(); + QModelIndex parent = indexForPath(entry->base); + Q_ASSERT(!m_treeMode || parent.isValid()); + QList& pathItems = m_pathIndex[entry->base]; + int pathPos = pathItems.indexOf(entry); + Q_ASSERT(pathPos >= 0); + removedGameSpans.add(pos); + removedTreeSpans[entry->base].add(pathPos); + 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(); + 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); + } + pathIndex.erase(pathIndex.begin() + span.left, pathIndex.begin() + span.right + 1); + if (m_treeMode) { + endRemoveRows(); + } + } + } + + // 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) { + 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 { + // 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; + } +} + +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(); +} + +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: + 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 (!validateIndex(index)) { + 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() < (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 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(); +} + +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..651bbe7b3 --- /dev/null +++ b/src/platform/qt/library/LibraryModel.h @@ -0,0 +1,88 @@ +/* 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 +#include + +#include "LibraryEntry.h" + +class QTreeView; +class LibraryModelTest; + +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: + friend class ::LibraryModelTest; + + QModelIndex indexForPath(const QString& path); + QModelIndex indexForPath(const QString& path) const; + + 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); + + bool m_treeMode; + bool m_showFilename; + + std::vector> m_games; + QStringList m_pathOrder; + QHash> m_pathIndex; + QHash m_gameIndex; +}; + +} 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/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 diff --git a/src/platform/qt/resources.qrc b/src/platform/qt/resources.qrc index e4b8fab5f..4c9fe9098 100644 --- a/src/platform/qt/resources.qrc +++ b/src/platform/qt/resources.qrc @@ -7,6 +7,30 @@ ../../../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/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 + ../../../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/scripting/AutorunScriptModel.cpp b/src/platform/qt/scripting/AutorunScriptModel.cpp new file mode 100644 index 000000000..de8a7a06b --- /dev/null +++ b/src/platform/qt/scripting/AutorunScriptModel.cpp @@ -0,0 +1,132 @@ +/* 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" +#include "LogController.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) { + LOG(QT, WARN) << 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..f269d2798 --- /dev/null +++ b/src/platform/qt/scripting/AutorunScriptModel.h @@ -0,0 +1,58 @@ +/* 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 +#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/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; 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... + + diff --git a/src/platform/qt/test/library.cpp b/src/platform/qt/test/library.cpp new file mode 100644 index 000000000..c189f8259 --- /dev/null +++ b/src/platform/qt/test/library.cpp @@ -0,0 +1,210 @@ +/* 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); + 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() { + 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); + 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() { + 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..1df387e24 100644 --- a/src/platform/qt/utils.cpp +++ b/src/platform/qt/utils.cpp @@ -5,7 +5,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "utils.h" +#include +#ifdef M_CORE_GB +#include +#endif + #include +#include #include #include @@ -29,18 +35,25 @@ 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: - return QObject::tr("GBA"); + return "GBA"; #endif #ifdef M_CORE_GB case mPLATFORM_GB: - return QObject::tr("GB"); + if (validModels != M_LIBRARY_MODEL_UNKNOWN) { + if (validModels & GB_MODEL_CGB) { + return "GBC"; + } else if (validModels & GB_MODEL_SGB) { + return "SGB"; + } + } + return "GB"; #endif default: - return QObject::tr("?"); + return "?"; } } @@ -177,4 +190,45 @@ QString keyName(int key) { } } +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..3cc7d28f4 100644 --- a/src/platform/qt/utils.h +++ b/src/platform/qt/utils.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -30,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); @@ -117,4 +118,29 @@ 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; +}; + +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 +} + } 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); 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) { 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 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) 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/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: 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), ) 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.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); diff --git a/src/util/vfs/vfs-mem.c b/src/util/vfs/vfs-mem.c index 06189b886..048194e2d 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); @@ -285,6 +290,26 @@ 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) { + 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; + if (vfm->offset > vfm->size) { + vfm->offset = vfm->size; + } } void _vfmTruncateNoop(struct VFile* vf, size_t size) { 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;