diff --git a/.gitignore b/.gitignore index 0e5d801be..999fdd673 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,15 @@ +# Generic files *.user* *~ *.swp *.pyc +# Build directories /build /build-* /.vs +# Build files *.a *.dylib *.dll @@ -18,4 +21,9 @@ CMakeCache.txt CMakeFiles CMakeSettings.json cmake_install.cmake +hle-bios.bin version.c + +# Runtime generated cruft +*.sav +*.ss0 diff --git a/CHANGES b/CHANGES index 369036477..c552ba13f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,20 +1,76 @@ 0.11.0: (Future) Features: + - Scripting: New `input` API for getting raw keyboard/mouse/controller state + - Scripting: New `storage` API for saving data for a script, e.g. settings + - Scripting: Debugger integration to allow for breakpoints and watchpoints - New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81 - Debugger: Add range watchpoints Emulation fixes: - - GBA Memory: Make VRAM access stalls only apply to BG RAM + - ARM: Remove obsolete force-alignment in `bx pc` (fixes mgba.io/i/2964) + - ARM: Fake bpkt instruction should take no cycles (fixes mgba.io/i/2551) + - GB Audio: Fix channels 1/2 staying muted if restarted after long silence + - GB Audio: Fix channel 1 restarting if sweep applies after stop (fixes mgba.io/i/2965) + - GB I/O: Read back proper SVBK value after writing 0 (fixes mgba.io/i/2921) + - GB Serialize: Add missing Pocket Cam state to savestates + - GB SIO: Disabling SIO should cancel pending transfers (fixes mgba.io/i/2537) + - GB Video: Implement DMG-style sprite ordering + - GBA: Unhandled bkpt should be treated as an undefined exception + - GBA Audio: Fix sample timing drifting when changing sample interval + - GBA Audio: Fix initial channel 3 wave RAM (fixes mgba.io/i/2947) + - GBA GPIO: Fix tilt scale and orientation (fixes mgba.io/i/2703) + - GBA BIOS: Fix clobbering registers with word-sized CpuSet + - GBA SIO: Fix normal mode SI/SO semantics (fixes mgba.io/i/2925) - GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) Other fixes: - - Core: Allow sending thread requests to a crashed core (fixes mgba.io/i/2784) - - Qt: Fix crash when attempting to use OpenGL 2.1 to 3.1 (fixes mgba.io/i/2794) - - Qt: Disable sync while running scripts from main thread (fixes mgba.io/i/2738) + - Core: Fix inconsistencies with setting game-specific overrides (fixes mgba.io/i/2963) + - Debugger: Fix writing to specific segment in command-line debugger + - mGUI: Fix cases where an older save state screenshot would be shown. (fixes mgba.io/i/2183) - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) Misc: + - Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826) + - GB: Prevent incompatible BIOSes from being used on differing models - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs + - mGUI: Enable auto-softpatching (closes mgba.io/i/2899) + - Qt: Add exporting of SAV + RTC saves from Save Converter to strip RTC data + - Qt: Handle multiple save game files for disparate games separately (fixes mgba.io/i/2887) + - Scripting: Add `callbacks:oneshot` for single-call callbacks + +0.10.2: (2023-04-23) +Emulation fixes: + - GBA Audio: Fix improperly deserializing GB audio registers (fixes mgba.io/i/2793) + - GBA Audio: Clear GB audio state when disabled + - GBA Memory: Make VRAM access stalls only apply to BG RAM + - GBA Overrides: Fix saving in PMD:RRT (JP) (fixes mgba.io/i/2862) + - GBA SIO: Fix SIOCNT SI pin value after attaching player 2 (fixes mgba.io/i/2805) + - GBA SIO: Fix unconnected normal mode SIOCNT SI bit (fixes mgba.io/i/2810) + - GBA SIO: Normal mode transfers with no clock should not finish (fixes mgba.io/i/2811) + - GBA Timers: Cascading timers don't tick when disabled (fixes mgba.io/i/2812) + - GBA Video: Fix interpolation issues with OpenGL renderer +Other fixes: + - Core: Allow sending thread requests to a crashed core (fixes mgba.io/i/2784) + - FFmpeg: Force lower sample rate for codecs not supporting high rates (fixes mgba.io/i/2869) + - Qt: Fix crash when attempting to use OpenGL 2.1 to 3.1 (fixes mgba.io/i/2794) + - Qt: Disable sync while running scripts from main thread (fixes mgba.io/i/2738) + - Qt: Properly cap number of attached players by platform (fixes mgba.io/i/2807) + - Qt: Disable attempted linking betwen incompatible platforms (fixes mgba.io/i/2702) + - Qt: Fix modifier key names in shortcut editor (fixes mgba.io/i/2817) + - Qt: Fix a handful of edge cases with graphics viewers (fixes mgba.io/i/2827) + - Qt: Fix full-buffer rewind + - Qt: Fix crash if loading a shader fails + - Qt: Fix black screen when starting with a game (fixes mgba.io/i/2781) + - Qt: Fix OSD on modern macOS (fixes mgba.io/i/2736) + - Qt: Fix checked state of mute menu option at load (fixes mgba.io/i/2701) + - Qt: Remove OpenGL proxy thread and override SwapInterval directly instead + - Scripting: Fix receiving packets for client sockets + - Scripting: Fix empty receive calls returning unknown error on Windows + - Scripting: Return proper callback ID from socket.add + - Vita: Work around broken mktime implementation in Vita SDK (fixes mgba.io/i/2876) +Misc: - Qt: Include wayland QPA in AppImage (fixes mgba.io/i/2796) - Qt: Stop eating boolean action key events (fixes mgba.io/i/2636) + - Qt: Automatically change video file extension as appropriate + - Qt: Swap P1 and other player's save if P1 loaded it first (closes mgba.io/i/2750) 0.10.1: (2023-01-10) Emulation fixes: diff --git a/CMakeLists.txt b/CMakeLists.txt index 42cff1ebc..6407f7a81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.3) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/src/platform/cmake/") if(POLICY CMP0025) @@ -34,8 +34,12 @@ if(NOT MSVC) # mingw32 likes to complain about using the "wrong" format strings despite them actually working set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-format") endif() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration -Werror=implicit-int") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration -Werror=implicit-int -fwrapv") + if(CMAKE_C_COMPILER_ID STREQUAL "GNU") + # TODO: Remove this once mScript KV pairs support const correctness + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=incompatible-pointer-types") + endif() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS} -Woverloaded-virtual") else() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146 /wd4267 /Zc:preprocessor-") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146 /wd4267 /Zc:preprocessor-") @@ -55,6 +59,7 @@ if(NOT LIBMGBA_ONLY) set(USE_SQLITE3 ON CACHE BOOL "Whether or not to enable SQLite3 support") set(USE_ELF ON CACHE BOOL "Whether or not to enable ELF support") set(USE_LUA ON CACHE BOOL "Whether or not to enable Lua scripting support") + set(USE_JSON_C ON CACHE BOOL "Whether or not to enable JSON-C support") set(M_CORE_GBA ON CACHE BOOL "Build Game Boy Advance core") set(M_CORE_GB ON CACHE BOOL "Build Game Boy core") set(USE_LZMA ON CACHE BOOL "Whether or not to enable 7-Zip support") @@ -337,6 +342,8 @@ find_function(popcount32) find_function(futimens) find_function(futimes) +find_function(realpath) + if(ANDROID AND ANDROID_NDK_MAJOR GREATER 13) find_function(localtime_r) set(HAVE_STRTOF_L ON) @@ -419,6 +426,13 @@ if(BUILD_GL) elseif(UNIX AND NOT APPLE AND TARGET OpenGL::GL) set(OPENGL_LIBRARY OpenGL::GL) endif() + if(OpenGL_GLX_FOUND) + list(APPEND FEATURES GLX) + endif() + if(OpenGL_EGL_FOUND) + list(APPEND FEATURES EGL) + list(APPEND OPENGL_LIBRARY ${OPENGL_egl_LIBRARY}) + endif() endif() if(BUILD_GL) list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gl.c) @@ -477,6 +491,7 @@ endif() if(DISABLE_DEPS) set(USE_GDB_STUB OFF) set(USE_DISCORD_RPC OFF) + set(USE_JSON_C OFF) set(USE_SQLITE3 OFF) set(USE_PNG OFF) set(USE_ZLIB OFF) @@ -765,11 +780,36 @@ endif() if(ENABLE_SCRIPTING) list(APPEND ENABLES SCRIPTING) + find_feature(USE_JSON_C "json-c") if(NOT USE_LUA VERSION_LESS 5.1) find_feature(USE_LUA "Lua" ${USE_LUA}) else() find_feature(USE_LUA "Lua") endif() + if(USE_JSON_C) + list(APPEND FEATURES JSON_C) + if(TARGET json-c::json-c) + list(APPEND DEPENDENCY_LIB json-c::json-c) + get_target_property(JSON_C_SONAME json-c::json-c IMPORTED_SONAME_NONE) + string(SUBSTRING "${JSON_C_SONAME}" 13 -1 JSON_C_SOVER) + + # This is only needed on 0.15, but the target unhelpfully does not contain version info + get_target_property(JSON_C_INCLUDE_DIR json-c::json-c INTERFACE_INCLUDE_DIRECTORIES) + if(NOT JSON_C_INCLUDE_DIR MATCHES json-c$) + include_directories(AFTER "${JSON_C_INCLUDE_DIR}/json-c") + endif() + else() + if(${json-c_VERSION} VERSION_LESS 0.13.0) + set(JSON_C_SOVER 3) + elseif(${json-c_VERSION} VERSION_LESS 0.15.0) + set(JSON_C_SOVER 4) + endif() + list(APPEND DEPENDENCY_LIB ${json-c_LIBRARIES}) + include_directories(AFTER ${json-c_INCLUDE_DIRS}) + link_directories(${json-c_LIBDIRS}) + endif() + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libjson-c${JSON_C_SOVER}") + endif() if(USE_LUA) list(APPEND FEATURE_DEFINES USE_LUA) include_directories(AFTER ${LUA_INCLUDE_DIR}) @@ -1289,6 +1329,7 @@ if(NOT QUIET AND NOT LIBMGBA_ONLY) else() message(STATUS " Lua: ${USE_LUA}") endif() + message(STATUS " storage API: ${USE_JSON_C}") endif() message(STATUS "Frontends:") message(STATUS " Qt: ${BUILD_QT}") diff --git a/README.md b/README.md index 20507604e..78b69f255 100644 --- a/README.md +++ b/README.md @@ -125,9 +125,9 @@ Compiling requires using CMake 3.1 or newer. GCC, Clang, and Visual Studio 2019 #### Docker building -The recommended way to build for most platforms is to use Docker. Several Docker images are provided that contain the requisite toolchain and dependencies for building mGBA across several platforms. +The recommended way to build for most platforms is to use Docker. Several Docker images are provided that contain the requisite toolchain and dependencies for building mGBA across several platforms. -Note: If you are on an older Windows system before Windows 10, you may need to configure your Docker to use VirtualBox shared folders to correctly map your current `mgba` checkout directory to the Docker image's working directory. (See issue [#1985](https://mgba.io/i/1985) for details.) +Note: If you are on an older Windows system before Windows 10, you may need to configure your Docker to use VirtualBox shared folders to correctly map your current `mgba` checkout directory to the Docker image's working directory. (See issue [#1985](https://mgba.io/i/1985) for details.) To use a Docker image to build mGBA, simply run the following command while in the root of an mGBA checkout: @@ -234,6 +234,7 @@ mGBA has no hard dependencies, however, the following optional dependencies are - SQLite3: for game databases. - libelf: for ELF loading. - Lua: for scripting. +- json-c: for the scripting `storage` API. SQLite3, libpng, and zlib are included with the emulator, so they do not need to be externally compiled first. @@ -254,7 +255,7 @@ Footnotes Copyright --------- -mGBA is Copyright © 2013 – 2022 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file. +mGBA is Copyright © 2013 – 2023 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file. mGBA contains the following third-party libraries: diff --git a/cinema/gb/acid/cgb-acid2/baseline_0000.png b/cinema/gb/acid/cgb-acid2/baseline_0000.png new file mode 100644 index 000000000..4baff3425 Binary files /dev/null and b/cinema/gb/acid/cgb-acid2/baseline_0000.png differ diff --git a/cinema/gb/acid/cgb-acid2/test.gbc b/cinema/gb/acid/cgb-acid2/test.gbc new file mode 100644 index 000000000..5f71bd360 Binary files /dev/null and b/cinema/gb/acid/cgb-acid2/test.gbc differ diff --git a/cinema/gb/acid/config.ini b/cinema/gb/acid/config.ini new file mode 100644 index 000000000..e6b9f2af6 --- /dev/null +++ b/cinema/gb/acid/config.ini @@ -0,0 +1,6 @@ +[testinfo] +skip=15 +frames=1 + +[ports.cinema] +sgb.borders=0 diff --git a/cinema/gb/acid/dmg-acid2/baseline_0000.png b/cinema/gb/acid/dmg-acid2/baseline_0000.png new file mode 100644 index 000000000..fde56ebc3 Binary files /dev/null and b/cinema/gb/acid/dmg-acid2/baseline_0000.png differ diff --git a/cinema/gb/acid/dmg-acid2/test.gb b/cinema/gb/acid/dmg-acid2/test.gb new file mode 100644 index 000000000..a25ef9485 Binary files /dev/null and b/cinema/gb/acid/dmg-acid2/test.gb differ diff --git a/cinema/gb/mooneye-gb/manual-only/sprite_priority/baseline_0000.png b/cinema/gb/mooneye-gb/manual-only/sprite_priority/baseline_0000.png index 64aac5965..cb3569baf 100644 Binary files a/cinema/gb/mooneye-gb/manual-only/sprite_priority/baseline_0000.png and b/cinema/gb/mooneye-gb/manual-only/sprite_priority/baseline_0000.png differ diff --git a/cinema/gb/mooneye-gb/manual-only/sprite_priority/xbaseline_0000.png b/cinema/gb/mooneye-gb/manual-only/sprite_priority/xbaseline_0000.png deleted file mode 100644 index 2201a6ac5..000000000 Binary files a/cinema/gb/mooneye-gb/manual-only/sprite_priority/xbaseline_0000.png and /dev/null differ diff --git a/include/mgba-util/common.h b/include/mgba-util/common.h index 4583ac0ed..0fc10be58 100644 --- a/include/mgba-util/common.h +++ b/include/mgba-util/common.h @@ -44,6 +44,10 @@ CXX_GUARD_START #define restrict __restrict #endif +#ifndef containerof +#define containerof(PTR, TYPE, MEMBER) ((TYPE*) ((uintptr_t) (PTR) - offsetof(TYPE, MEMBER))) +#endif + #ifdef _MSC_VER #include #include @@ -110,13 +114,13 @@ typedef intptr_t ssize_t; #define ATOMIC_LOAD_PTR(DST, SRC) DST = InterlockedCompareExchangePointer(&SRC, 0, 0) #else // TODO -#define ATOMIC_STORE(DST, SRC) DST = SRC -#define ATOMIC_LOAD(DST, SRC) DST = SRC -#define ATOMIC_ADD(DST, OP) DST += OP -#define ATOMIC_SUB(DST, OP) DST -= OP -#define ATOMIC_OR(DST, OP) DST |= OP -#define ATOMIC_AND(DST, OP) DST &= OP -#define ATOMIC_CMPXCHG(DST, EXPECTED, OP) ((DST == EXPECTED) ? ((DST = OP), true) : false) +#define ATOMIC_STORE(DST, SRC) ((DST) = (SRC)) +#define ATOMIC_LOAD(DST, SRC) ((DST) = (SRC)) +#define ATOMIC_ADD(DST, OP) ((DST) += (OP)) +#define ATOMIC_SUB(DST, OP) ((DST) -= (OP)) +#define ATOMIC_OR(DST, OP) ((DST) |= (OP)) +#define ATOMIC_AND(DST, OP) ((DST) &= (OP)) +#define ATOMIC_CMPXCHG(DST, EXPECTED, OP) (((DST) == (EXPECTED)) ? (((DST) = (OP)), true) : false) #define ATOMIC_STORE_PTR(DST, SRC) ATOMIC_STORE(DST, SRC) #define ATOMIC_LOAD_PTR(DST, SRC) ATOMIC_LOAD(DST, SRC) #endif diff --git a/include/mgba-util/geometry.h b/include/mgba-util/geometry.h new file mode 100644 index 000000000..aa7d65e94 --- /dev/null +++ b/include/mgba-util/geometry.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GEOMETRY_H +#define GEOMETRY_H + +#include + +CXX_GUARD_START + +struct mSize { + int width; + int height; +}; + +struct mRectangle { + int x; + int y; + int width; + int height; +}; + +void mRectangleUnion(struct mRectangle* dst, const struct mRectangle* add); +bool mRectangleIntersection(struct mRectangle* dst, const struct mRectangle* add); +void mRectangleCenter(const struct mRectangle* ref, struct mRectangle* rect); + +CXX_GUARD_END + +#endif diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h new file mode 100644 index 000000000..31514687d --- /dev/null +++ b/include/mgba-util/image.h @@ -0,0 +1,397 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef M_IMAGE_H +#define M_IMAGE_H + +#include + +CXX_GUARD_START + +#ifdef COLOR_16_BIT +typedef uint16_t color_t; +#define BYTES_PER_PIXEL 2 +#else +typedef uint32_t color_t; +#define BYTES_PER_PIXEL 4 +#endif + +#define M_R5(X) ((X) & 0x1F) +#define M_G5(X) (((X) >> 5) & 0x1F) +#define M_B5(X) (((X) >> 10) & 0x1F) + +#define M_R8(X) ((M_R5(X) * 0x21) >> 2) +#define M_G8(X) ((M_G5(X) * 0x21) >> 2) +#define M_B8(X) ((M_B5(X) * 0x21) >> 2) + +#define M_RGB5_TO_BGR8(X) ((M_R5(X) << 3) | (M_G5(X) << 11) | (M_B5(X) << 19)) +#define M_RGB5_TO_RGB8(X) ((M_R5(X) << 19) | (M_G5(X) << 11) | (M_B5(X) << 3)) +#define M_RGB8_TO_BGR5(X) ((((X) & 0xF8) >> 3) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 9)) +#define M_RGB8_TO_RGB5(X) ((((X) & 0xF8) << 7) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 19)) + +#ifndef COLOR_16_BIT +#define M_COLOR_RED 0x000000FF +#define M_COLOR_GREEN 0x0000FF00 +#define M_COLOR_BLUE 0x00FF0000 +#define M_COLOR_ALPHA 0xFF000000 +#define M_COLOR_WHITE 0x00FFFFFF + +#define M_RGB8_TO_NATIVE(X) (((X) & 0x00FF00) | (((X) & 0x0000FF) << 16) | (((X) & 0xFF0000) >> 16)) +#elif defined(COLOR_5_6_5) +#define M_COLOR_RED 0x001F +#define M_COLOR_GREEN 0x07E0 +#define M_COLOR_BLUE 0xF800 +#define M_COLOR_ALPHA 0x0000 +#define M_COLOR_WHITE 0xFFDF + +#define M_RGB8_TO_NATIVE(X) ((((X) & 0xF8) << 8) | (((X) & 0xFC00) >> 5) | (((X) & 0xF80000) >> 19)) +#else +#define M_COLOR_RED 0x001F +#define M_COLOR_GREEN 0x03E0 +#define M_COLOR_BLUE 0x7C00 +#define M_COLOR_ALPHA 0x1000 +#define M_COLOR_WHITE 0x7FFF + +#define M_RGB8_TO_NATIVE(X) M_RGB8_TO_BGR5(X) +#endif + +enum mColorFormat { + mCOLOR_XBGR8 = 0x00001, + mCOLOR_XRGB8 = 0x00002, + mCOLOR_BGRX8 = 0x00004, + mCOLOR_RGBX8 = 0x00008, + mCOLOR_ABGR8 = 0x00010, + mCOLOR_ARGB8 = 0x00020, + mCOLOR_BGRA8 = 0x00040, + mCOLOR_RGBA8 = 0x00080, + mCOLOR_RGB5 = 0x00100, + mCOLOR_BGR5 = 0x00200, + mCOLOR_RGB565 = 0x00400, + mCOLOR_BGR565 = 0x00800, + mCOLOR_ARGB5 = 0x01000, + mCOLOR_ABGR5 = 0x02000, + mCOLOR_RGBA5 = 0x04000, + mCOLOR_BGRA5 = 0x08000, + mCOLOR_RGB8 = 0x10000, + mCOLOR_BGR8 = 0x20000, + mCOLOR_L8 = 0x40000, + mCOLOR_PAL8 = 0x80000, + + mCOLOR_ANY = -1 +}; + +#ifndef COLOR_16_BIT +#define mCOLOR_NATIVE mCOLOR_XBGR8 +#elif !defined(COLOR_5_6_5) +#define mCOLOR_NATIVE mCOLOR_BGR5 +#else +#define mCOLOR_NATIVE mCOLOR_RGB565 +#endif + +struct mImage { + void* data; + uint32_t* palette; + unsigned width; + unsigned height; + unsigned stride; + unsigned depth; + unsigned palSize; + enum mColorFormat format; +}; + +struct mPainter { + struct mImage* backing; + bool blend; + bool fill; + unsigned strokeWidth; + uint32_t strokeColor; + uint32_t fillColor; +}; + +struct VFile; +struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format); +struct mImage* mImageCreateWithStride(unsigned width, unsigned height, unsigned stride, enum mColorFormat format); +struct mImage* mImageCreateFromConstBuffer(unsigned width, unsigned height, unsigned stride, enum mColorFormat format, const void* pixels); +struct mImage* mImageLoad(const char* path); +struct mImage* mImageLoadVF(struct VFile* vf); +struct mImage* mImageConvertToFormat(const struct mImage*, enum mColorFormat format); +void mImageDestroy(struct mImage*); + +bool mImageSave(const struct mImage*, const char* path, const char* format); +bool mImageSaveVF(const struct mImage*, struct VFile* vf, const char* format); + +uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y); +uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y); +void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color); +void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color); + +void mImageSetPaletteSize(struct mImage* image, unsigned count); +void mImageSetPaletteEntry(struct mImage* image, unsigned index, uint32_t color); + +void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y); +void mImageComposite(struct mImage* image, const struct mImage* source, int x, int y); +void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source, int x, int y, float alpha); + +void mPainterInit(struct mPainter*, struct mImage* backing); +void mPainterDrawRectangle(struct mPainter*, int x, int y, int width, int height); +void mPainterDrawLine(struct mPainter*, int x1, int y1, int x2, int y2); +void mPainterDrawCircle(struct mPainter*, int x, int y, int diameter); + +uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to); +uint32_t mImageColorConvert(uint32_t color, const struct mImage* from, enum mColorFormat to); + +#ifndef PYCPARSE +static inline unsigned mColorFormatBytes(enum mColorFormat format) { + switch (format) { + case mCOLOR_XBGR8: + case mCOLOR_XRGB8: + case mCOLOR_BGRX8: + case mCOLOR_RGBX8: + case mCOLOR_ABGR8: + case mCOLOR_ARGB8: + case mCOLOR_BGRA8: + case mCOLOR_RGBA8: + return 4; + case mCOLOR_RGB5: + case mCOLOR_BGR5: + case mCOLOR_RGB565: + case mCOLOR_BGR565: + case mCOLOR_ARGB5: + case mCOLOR_ABGR5: + case mCOLOR_RGBA5: + case mCOLOR_BGRA5: + return 2; + case mCOLOR_RGB8: + case mCOLOR_BGR8: + return 3; + case mCOLOR_L8: + case mCOLOR_PAL8: + return 1; + case mCOLOR_ANY: + break; + } + return 0; +} + +static inline bool mColorFormatHasAlpha(enum mColorFormat format) { + switch (format) { + case mCOLOR_XBGR8: + case mCOLOR_XRGB8: + case mCOLOR_BGRX8: + case mCOLOR_RGBX8: + case mCOLOR_RGB5: + case mCOLOR_BGR5: + case mCOLOR_RGB565: + case mCOLOR_BGR565: + case mCOLOR_RGB8: + case mCOLOR_BGR8: + case mCOLOR_L8: + return false; + case mCOLOR_ABGR8: + case mCOLOR_ARGB8: + case mCOLOR_BGRA8: + case mCOLOR_RGBA8: + case mCOLOR_ARGB5: + case mCOLOR_ABGR5: + case mCOLOR_RGBA5: + case mCOLOR_BGRA5: + case mCOLOR_PAL8: + return true; + case mCOLOR_ANY: + break; + } + return false; +} + +static inline color_t mColorFrom555(uint16_t value) { +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + color_t color = 0; + color |= (value & 0x001F) << 11; + color |= (value & 0x03E0) << 1; + color |= (value & 0x7C00) >> 10; +#else + color_t color = value; +#endif +#else + color_t color = M_RGB5_TO_BGR8(value); + color |= (color >> 5) & 0x070707; +#endif + return color; +} + +ATTRIBUTE_UNUSED static unsigned mColorMix5Bit(int weightA, unsigned colorA, int weightB, unsigned colorB) { + unsigned c = 0; + unsigned a, b; +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + a = colorA & 0xF81F; + b = colorB & 0xF81F; + a |= (colorA & 0x7C0) << 16; + b |= (colorB & 0x7C0) << 16; + c = ((a * weightA + b * weightB) / 16); + if (c & 0x08000000) { + c = (c & ~0x0FC00000) | 0x07C00000; + } + if (c & 0x0020) { + c = (c & ~0x003F) | 0x001F; + } + if (c & 0x10000) { + c = (c & ~0x1F800) | 0xF800; + } + c = (c & 0xF81F) | ((c >> 16) & 0x07C0); +#else + a = colorA & 0x7C1F; + b = colorB & 0x7C1F; + a |= (colorA & 0x3E0) << 16; + b |= (colorB & 0x3E0) << 16; + c = ((a * weightA + b * weightB) / 16); + if (c & 0x04000000) { + c = (c & ~0x07E00000) | 0x03E00000; + } + if (c & 0x0020) { + c = (c & ~0x003F) | 0x001F; + } + if (c & 0x8000) { + c = (c & ~0xF800) | 0x7C00; + } + c = (c & 0x7C1F) | ((c >> 16) & 0x03E0); +#endif +#else + a = colorA & 0xFF; + b = colorB & 0xFF; + c |= ((a * weightA + b * weightB) / 16) & 0x1FF; + if (c & 0x00000100) { + c = 0x000000FF; + } + + a = colorA & 0xFF00; + b = colorB & 0xFF00; + c |= ((a * weightA + b * weightB) / 16) & 0x1FF00; + if (c & 0x00010000) { + c = (c & 0x000000FF) | 0x0000FF00; + } + + a = colorA & 0xFF0000; + b = colorB & 0xFF0000; + c |= ((a * weightA + b * weightB) / 16) & 0x1FF0000; + if (c & 0x01000000) { + c = (c & 0x0000FFFF) | 0x00FF0000; + } +#endif + return c; +} + +ATTRIBUTE_UNUSED static uint32_t mColorMixARGB8(uint32_t colorA, uint32_t colorB) { + uint32_t alphaA = colorA >> 24; + if (!alphaA) { + return colorB; + } + uint32_t alphaB = colorB >> 24; + uint32_t color = 0; + +#if 1 + // TODO: Benchmark integer and float versions + uint32_t a, b; + uint32_t alpha = (alphaA * 0xFF) + alphaB * (0xFF - alphaA); + + a = colorA & 0xFF; + a *= alphaA * 0xFF; + b = a; + + a = colorB & 0xFF; + a *= alphaB * (0xFF - alphaA); + b += a; + + b /= alpha; + if (b > 0xFF) { + color |= 0xFF; + } else { + color |= b; + } + + a = (colorA >> 8) & 0xFF; + a *= alphaA * 0xFF; + b = a; + + a = (colorB >> 8) & 0xFF; + a *= alphaB * (0xFF - alphaA); + b += a; + + b /= alpha; + if (b > 0xFF) { + color |= 0xFF00; + } else { + color |= b << 8; + } + + a = (colorA >> 16) & 0xFF; + a *= alphaA * 0xFF; + b = a; + + a = (colorB >> 16) & 0xFF; + a *= alphaB * (0xFF - alphaA); + b += a; + + b /= alpha; + if (b > 0xFF) { + color |= 0xFF0000; + } else { + color |= b << 16; + } + + alpha /= 0xFF; + if (alpha > 0xFF) { + color |= 0xFF000000; + } else { + color |= alpha << 24; + } +#else + float ca, aa; + float cb, ab; + + static const float r255 = 1 / 255.f; + aa = alphaA * r255; + ab = alphaB * r255; + float alpha = aa + ab * (1.f - aa); + float ralpha = 1.f / alpha; + alpha = alpha * 255.f; + color = ((int) alpha) << 24; + + ca = ((colorA >> 16) & 0xFF) * r255; + cb = ((colorB >> 16) & 0xFF) * r255; + ca = ca * aa + cb * ab * (1.f - aa); + ca = ca * ralpha * 255.f; + if (ca > 255.f) { + ca = 255.f; + } + color |= ((int) ca) << 16; + + ca = ((colorA >> 8) & 0xFF) * r255; + cb = ((colorB >> 8) & 0xFF) * r255; + ca = ca * aa + cb * ab * (1.f - aa); + ca = ca * ralpha * 255.f; + if (ca > 255.f) { + ca = 255.f; + } + color |= ((int) ca) << 8; + + ca = (colorA & 0xFF) * r255; + cb = (colorB & 0xFF) * r255; + ca = ca * aa + cb * ab * (1.f - aa); + ca = ca * ralpha * 255.f; + if (ca > 255.f) { + ca = 255.f; + } + color |= (int) ca; +#endif + + return color; +} +#endif + +CXX_GUARD_END + +#endif diff --git a/include/mgba-util/export.h b/include/mgba-util/image/export.h similarity index 56% rename from include/mgba-util/export.h rename to include/mgba-util/image/export.h index 92047b93b..bc657cb87 100644 --- a/include/mgba-util/export.h +++ b/include/mgba-util/image/export.h @@ -1,10 +1,10 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau +/* Copyright (c) 2013-2023 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef EXPORT_H -#define EXPORT_H +#ifndef M_IMAGE_EXPORT_H +#define M_IMAGE_EXPORT_H #include @@ -12,8 +12,8 @@ CXX_GUARD_START struct VFile; -bool exportPaletteRIFF(struct VFile* vf, size_t entries, const uint16_t* colors); -bool exportPaletteACT(struct VFile* vf, size_t entries, const uint16_t* colors); +bool mPaletteExportRIFF(struct VFile* vf, size_t entries, const uint16_t* colors); +bool mPaletteExportACT(struct VFile* vf, size_t entries, const uint16_t* colors); CXX_GUARD_END diff --git a/include/mgba-util/png-io.h b/include/mgba-util/image/png-io.h similarity index 76% rename from include/mgba-util/png-io.h rename to include/mgba-util/image/png-io.h index 4156b5d20..b9b82c0ac 100644 --- a/include/mgba-util/png-io.h +++ b/include/mgba-util/image/png-io.h @@ -12,6 +12,8 @@ CXX_GUARD_START #ifdef USE_PNG +#include + // png.h defines its own version of restrict which conflicts with mGBA's. #ifdef restrict #undef restrict @@ -25,13 +27,10 @@ enum { }; png_structp PNGWriteOpen(struct VFile* source); -png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height); -png_infop PNGWriteHeaderA(png_structp png, unsigned width, unsigned height); -png_infop PNGWriteHeader8(png_structp png, unsigned width, unsigned height); -bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, unsigned entries); -bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels); -bool PNGWritePixelsA(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels); -bool PNGWritePixels8(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels); +png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height, enum mColorFormat); +png_infop PNGWriteHeaderPalette(png_structp png, unsigned width, unsigned height, const uint32_t* palette, unsigned entries); +bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels, enum mColorFormat); +bool PNGWritePixelsPalette(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels); bool PNGWriteCustomChunk(png_structp png, const char* name, size_t size, void* data); void PNGWriteClose(png_structp png, png_infop info); diff --git a/include/mgba-util/macros.h b/include/mgba-util/macros.h index f994def47..b2844c8c3 100644 --- a/include/mgba-util/macros.h +++ b/include/mgba-util/macros.h @@ -16,13 +16,13 @@ #define _mCALL_0(FN, ...) #define _mCALL_1(FN, A) FN(A) #define _mCALL_2(FN, A, B) FN(A), FN(B) -#define _mCALL_3(FN, A, ...) FN(A), _mCALL_2(FN, __VA_ARGS__) -#define _mCALL_4(FN, A, ...) FN(A), _mCALL_3(FN, __VA_ARGS__) -#define _mCALL_5(FN, A, ...) FN(A), _mCALL_4(FN, __VA_ARGS__) -#define _mCALL_6(FN, A, ...) FN(A), _mCALL_5(FN, __VA_ARGS__) -#define _mCALL_7(FN, A, ...) FN(A), _mCALL_6(FN, __VA_ARGS__) -#define _mCALL_8(FN, A, ...) FN(A), _mCALL_7(FN, __VA_ARGS__) -#define _mCALL_9(FN, A, ...) FN(A), _mCALL_8(FN, __VA_ARGS__) +#define _mCALL_3(FN, A, B, C) FN(A), FN(B), FN(C) +#define _mCALL_4(FN, A, B, C, D) FN(A), FN(B), FN(C), FN(D) +#define _mCALL_5(FN, A, B, C, D, E) FN(A), FN(B), FN(C), FN(D), FN(E) +#define _mCALL_6(FN, A, B, C, D, E, F) FN(A), FN(B), FN(C), FN(D), FN(E), FN(F) +#define _mCALL_7(FN, A, B, C, D, E, F, G) FN(A), FN(B), FN(C), FN(D), FN(E), FN(F), FN(G) +#define _mCALL_8(FN, A, B, C, D, E, F, G, H) FN(A), FN(B), FN(C), FN(D), FN(E), FN(F), FN(G), FN(H) +#define _mCALL_9(FN, A, B, C, D, E, F, G, H, I) FN(A), FN(B), FN(C), FN(D), FN(E), FN(F), FN(G), FN(H), FN(I) #define _mCOMMA_0(N, ...) N #define _mCOMMA_1(N, ...) N, __VA_ARGS__ @@ -37,25 +37,25 @@ #define _mEVEN_0(...) #define _mEVEN_1(A, B, ...) A -#define _mEVEN_2(A, B, ...) A, _mIDENT(_mEVEN_1(__VA_ARGS__)) -#define _mEVEN_3(A, B, ...) A, _mIDENT(_mEVEN_2(__VA_ARGS__)) -#define _mEVEN_4(A, B, ...) A, _mIDENT(_mEVEN_3(__VA_ARGS__)) -#define _mEVEN_5(A, B, ...) A, _mIDENT(_mEVEN_4(__VA_ARGS__)) +#define _mEVEN_2(A, B, C, D, ...) A, C +#define _mEVEN_3(A, B, C, D, E, F, ...) A, C, E +#define _mEVEN_4(A, B, C, D, E, F, G, H, ...) A, C, E, G +#define _mEVEN_5(A, B, C, D, E, F, G, H, I, J, ...) A, C, E, G, I #define _mEVEN_6(A, B, ...) A, _mIDENT(_mEVEN_5(__VA_ARGS__)) -#define _mEVEN_7(A, B, ...) A, _mIDENT(_mEVEN_6(__VA_ARGS__)) -#define _mEVEN_8(A, B, ...) A, _mIDENT(_mEVEN_7(__VA_ARGS__)) -#define _mEVEN_9(A, B, ...) A, _mIDENT(_mEVEN_7(__VA_ARGS__)) +#define _mEVEN_7(A, B, C, D, ...) A, C, _mIDENT(_mEVEN_5(__VA_ARGS__)) +#define _mEVEN_8(A, B, C, D, E, F, ...) A, C, E, _mIDENT(_mEVEN_5(__VA_ARGS__)) +#define _mEVEN_9(A, B, C, D, E, F, G, H, ...) A, C, E, G, _mIDENT(_mEVEN_5(__VA_ARGS__)) #define _mODD_0(...) #define _mODD_1(A, B, ...) B -#define _mODD_2(A, B, ...) B, _mIDENT(_mODD_1(__VA_ARGS__)) -#define _mODD_3(A, B, ...) B, _mIDENT(_mODD_2(__VA_ARGS__)) -#define _mODD_4(A, B, ...) B, _mIDENT(_mODD_3(__VA_ARGS__)) -#define _mODD_5(A, B, ...) B, _mIDENT(_mODD_4(__VA_ARGS__)) +#define _mODD_2(A, B, C, D, ...) B, D +#define _mODD_3(A, B, C, D, E, F, ...) B, D, F +#define _mODD_4(A, B, C, D, E, F, G, H, ...) B, D, F, H +#define _mODD_5(A, B, C, D, E, F, G, H, I, J, ...) B, D, F, H, J #define _mODD_6(A, B, ...) B, _mIDENT(_mODD_5(__VA_ARGS__)) -#define _mODD_7(A, B, ...) B, _mIDENT(_mODD_6(__VA_ARGS__)) -#define _mODD_8(A, B, ...) B, _mIDENT(_mODD_7(__VA_ARGS__)) -#define _mODD_9(A, B, ...) B, _mIDENT(_mODD_7(__VA_ARGS__)) +#define _mODD_7(A, B, C, D, ...) B, D, _mIDENT(_mODD_5(__VA_ARGS__)) +#define _mODD_8(A, B, C, D, E, F, ...) B, D, F, _mIDENT(_mODD_5(__VA_ARGS__)) +#define _mODD_9(A, B, C, D, E, F, G, H, ...) B, D, F, H, _mIDENT(_mODD_5(__VA_ARGS__)) #define _mIF0_0(...) __VA_ARGS__ #define _mIF0_1(...) diff --git a/include/mgba-util/math.h b/include/mgba-util/math.h index 8402da3db..f7b3269c9 100644 --- a/include/mgba-util/math.h +++ b/include/mgba-util/math.h @@ -25,7 +25,7 @@ static inline unsigned clz32(uint32_t bits) { } return __builtin_clz(bits); #else - static const int table[256] = { + static const int8_t table[256] = { 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, @@ -55,6 +55,43 @@ static inline unsigned clz32(uint32_t bits) { #endif } +static inline unsigned ctz32(uint32_t bits) { +#if defined(__GNUC__) || __clang__ + if (!bits) { + return 32; + } + return __builtin_ctz(bits); +#else + static const int8_t table[256] = { + 8, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + }; + + if (bits & 0x000000FF) { + return table[bits & 0xFF]; + } else if (bits & 0x0000FF00) { + return table[(bits >> 8) & 0xFF] + 8; + } else if (bits & 0x00FF0000) { + return table[(bits >> 16) & 0xFF] + 16; + } + return table[bits >> 24] + 24; +#endif +} + static inline uint32_t toPow2(uint32_t bits) { if (!bits) { return 0; diff --git a/include/mgba-util/platform/3ds/threading.h b/include/mgba-util/platform/3ds/threading.h index dfc03342d..8d5555317 100644 --- a/include/mgba-util/platform/3ds/threading.h +++ b/include/mgba-util/platform/3ds/threading.h @@ -12,6 +12,7 @@ #include #define THREAD_ENTRY void +#define THREAD_EXIT(RES) return typedef ThreadFunc ThreadEntry; typedef LightLock Mutex; diff --git a/include/mgba-util/platform/posix/threading.h b/include/mgba-util/platform/posix/threading.h index c8d42ed73..ad6e99690 100644 --- a/include/mgba-util/platform/posix/threading.h +++ b/include/mgba-util/platform/posix/threading.h @@ -20,6 +20,7 @@ CXX_GUARD_START #define THREAD_ENTRY void* typedef THREAD_ENTRY (*ThreadEntry)(void*); +#define THREAD_EXIT(RES) return RES typedef pthread_t Thread; typedef pthread_mutex_t Mutex; diff --git a/include/mgba-util/platform/psp2/threading.h b/include/mgba-util/platform/psp2/threading.h index ba4827229..4c084804c 100644 --- a/include/mgba-util/platform/psp2/threading.h +++ b/include/mgba-util/platform/psp2/threading.h @@ -17,6 +17,7 @@ typedef struct { } Condition; #define THREAD_ENTRY int typedef THREAD_ENTRY (*ThreadEntry)(void*); +#define THREAD_EXIT(RES) return RES static inline int MutexInit(Mutex* mutex) { Mutex id = sceKernelCreateMutex("mutex", 0, 0, 0); diff --git a/include/mgba-util/platform/switch/threading.h b/include/mgba-util/platform/switch/threading.h index 76f1ae8b5..52419fa28 100644 --- a/include/mgba-util/platform/switch/threading.h +++ b/include/mgba-util/platform/switch/threading.h @@ -11,6 +11,7 @@ #include #define THREAD_ENTRY void +#define THREAD_EXIT(RES) return typedef ThreadFunc ThreadEntry; typedef CondVar Condition; diff --git a/include/mgba-util/platform/windows/getopt.h b/include/mgba-util/platform/windows/getopt.h index 5ea2dbe6f..22a196ef6 100644 --- a/include/mgba-util/platform/windows/getopt.h +++ b/include/mgba-util/platform/windows/getopt.h @@ -27,6 +27,8 @@ extern "C" { #endif +struct option; + #define REPLACE_GETOPT /* use this getopt as the system getopt(3) */ #ifdef REPLACE_GETOPT diff --git a/include/mgba-util/platform/windows/threading.h b/include/mgba-util/platform/windows/threading.h index 9be6fe53a..3f5de5a77 100644 --- a/include/mgba-util/platform/windows/threading.h +++ b/include/mgba-util/platform/windows/threading.h @@ -12,6 +12,7 @@ #include #define THREAD_ENTRY DWORD WINAPI typedef THREAD_ENTRY ThreadEntry(LPVOID); +#define THREAD_EXIT(RES) return RES typedef HANDLE Thread; typedef CRITICAL_SECTION Mutex; diff --git a/include/mgba-util/socket.h b/include/mgba-util/socket.h index 9f07491bd..ff9d89626 100644 --- a/include/mgba-util/socket.h +++ b/include/mgba-util/socket.h @@ -204,6 +204,7 @@ static inline Socket SocketOpenTCP(int port, const struct Address* bindAddress) err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); #endif if (err) { + SocketCloseQuiet(sock); return INVALID_SOCKET; } @@ -433,9 +434,9 @@ static inline int SocketPoll(size_t nSockets, Socket* reads, Socket* writes, Soc #else int result = select(maxFd, &rset, &wset, &eset, timeoutMillis < 0 ? 0 : &tv); #endif - int r = 0; - int w = 0; - int e = 0; + size_t r = 0; + size_t w = 0; + size_t e = 0; Socket j; for (j = 0; j < maxFd; ++j) { if (reads && FD_ISSET(j, &rset)) { @@ -451,6 +452,21 @@ static inline int SocketPoll(size_t nSockets, Socket* reads, Socket* writes, Soc ++e; } } + if (reads) { + for (; r < nSockets; ++r) { + reads[r] = INVALID_SOCKET; + } + } + if (writes) { + for (; w < nSockets; ++w) { + writes[w] = INVALID_SOCKET; + } + } + if (errors) { + for (; e < nSockets; ++e) { + errors[e] = INVALID_SOCKET; + } + } return result; } diff --git a/include/mgba-util/vfs.h b/include/mgba-util/vfs.h index 334f84419..eee68b99e 100644 --- a/include/mgba-util/vfs.h +++ b/include/mgba-util/vfs.h @@ -100,6 +100,9 @@ struct VFile* VFileFromFILE(FILE* file); void separatePath(const char* path, char* dirname, char* basename, char* extension); +bool isAbsolute(const char* path); +void makeAbsolute(const char* path, const char* base, char* out); + struct VFile* VDirFindFirst(struct VDir* dir, bool (*filter)(struct VFile*)); struct VFile* VDirFindNextAvailable(struct VDir*, const char* basename, const char* infix, const char* suffix, int mode); diff --git a/include/mgba/core/config.h b/include/mgba/core/config.h index 80184876b..ef7437487 100644 --- a/include/mgba/core/config.h +++ b/include/mgba/core/config.h @@ -33,6 +33,7 @@ struct mCoreOptions { int frameskip; bool rewindEnable; int rewindBufferCapacity; + int rewindBufferInterval; float fpsTarget; size_t audioBuffers; unsigned sampleRate; diff --git a/include/mgba/core/core.h b/include/mgba/core/core.h index d3f2b4e04..052e36795 100644 --- a/include/mgba/core/core.h +++ b/include/mgba/core/core.h @@ -65,8 +65,13 @@ struct mCore { void (*setSync)(struct mCore*, struct mCoreSync*); void (*loadConfig)(struct mCore*, const struct mCoreConfig*); void (*reloadConfigOption)(struct mCore*, const char* option, const struct mCoreConfig*); + void (*setOverride)(struct mCore*, const void* override); + + void (*baseVideoSize)(const struct mCore*, unsigned* width, unsigned* height); + void (*currentVideoSize)(const struct mCore*, unsigned* width, unsigned* height); + unsigned (*videoScale)(const struct mCore*); + size_t (*screenRegions)(const struct mCore*, const struct mCoreScreenRegion**); - void (*desiredVideoDimensions)(const struct mCore*, unsigned* width, unsigned* height); void (*setVideoBuffer)(struct mCore*, color_t* buffer, size_t stride); void (*setVideoGLTex)(struct mCore*, unsigned texid); @@ -116,6 +121,7 @@ struct mCore { void (*getGameCode)(const struct mCore*, char* title); void (*setPeripheral)(struct mCore*, int type, void*); + void* (*getPeripheral)(struct mCore*, int type); uint32_t (*busRead8)(struct mCore*, uint32_t address); uint32_t (*busRead16)(struct mCore*, uint32_t address); diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h index 9046d1a4f..f1af5e50d 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -10,165 +10,14 @@ CXX_GUARD_START +#include #include struct mCore; struct mStateExtdataItem; -#ifdef COLOR_16_BIT -typedef uint16_t color_t; -#define BYTES_PER_PIXEL 2 -#else -typedef uint32_t color_t; -#define BYTES_PER_PIXEL 4 -#endif - -#define M_R5(X) ((X) & 0x1F) -#define M_G5(X) (((X) >> 5) & 0x1F) -#define M_B5(X) (((X) >> 10) & 0x1F) - -#define M_R8(X) (((((X) << 3) & 0xF8) * 0x21) >> 5) -#define M_G8(X) (((((X) >> 2) & 0xF8) * 0x21) >> 5) -#define M_B8(X) (((((X) >> 7) & 0xF8) * 0x21) >> 5) - -#define M_RGB5_TO_BGR8(X) ((M_R5(X) << 3) | (M_G5(X) << 11) | (M_B5(X) << 19)) -#define M_RGB5_TO_RGB8(X) ((M_R5(X) << 19) | (M_G5(X) << 11) | (M_B5(X) << 3)) -#define M_RGB8_TO_BGR5(X) ((((X) & 0xF8) >> 3) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 9)) -#define M_RGB8_TO_RGB5(X) ((((X) & 0xF8) << 7) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 19)) - -#ifndef COLOR_16_BIT -#define M_COLOR_RED 0x000000FF -#define M_COLOR_GREEN 0x0000FF00 -#define M_COLOR_BLUE 0x00FF0000 -#define M_COLOR_ALPHA 0xFF000000 -#define M_COLOR_WHITE 0x00FFFFFF - -#define M_RGB8_TO_NATIVE(X) (((X) & 0x00FF00) | (((X) & 0x0000FF) << 16) | (((X) & 0xFF0000) >> 16)) -#elif defined(COLOR_5_6_5) -#define M_COLOR_RED 0x001F -#define M_COLOR_GREEN 0x07E0 -#define M_COLOR_BLUE 0xF800 -#define M_COLOR_ALPHA 0x0000 -#define M_COLOR_WHITE 0xFFDF - -#define M_RGB8_TO_NATIVE(X) ((((X) & 0xF8) << 8) | (((X) & 0xFC00) >> 5) | (((X) & 0xF80000) >> 19)) -#else -#define M_COLOR_RED 0x001F -#define M_COLOR_GREEN 0x03E0 -#define M_COLOR_BLUE 0x7C00 -#define M_COLOR_ALPHA 0x1000 -#define M_COLOR_WHITE 0x7FFF - -#define M_RGB8_TO_NATIVE(X) M_RGB8_TO_BGR5(X) -#endif - -#ifndef PYCPARSE -static inline color_t mColorFrom555(uint16_t value) { -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - color_t color = 0; - color |= (value & 0x001F) << 11; - color |= (value & 0x03E0) << 1; - color |= (value & 0x7C00) >> 10; -#else - color_t color = value; -#endif -#else - color_t color = M_RGB5_TO_BGR8(value); - color |= (color >> 5) & 0x070707; -#endif - return color; -} - -ATTRIBUTE_UNUSED static unsigned mColorMix5Bit(int weightA, unsigned colorA, int weightB, unsigned colorB) { - unsigned c = 0; - unsigned a, b; -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - a = colorA & 0xF81F; - b = colorB & 0xF81F; - a |= (colorA & 0x7C0) << 16; - b |= (colorB & 0x7C0) << 16; - c = ((a * weightA + b * weightB) / 16); - if (c & 0x08000000) { - c = (c & ~0x0FC00000) | 0x07C00000; - } - if (c & 0x0020) { - c = (c & ~0x003F) | 0x001F; - } - if (c & 0x10000) { - c = (c & ~0x1F800) | 0xF800; - } - c = (c & 0xF81F) | ((c >> 16) & 0x07C0); -#else - a = colorA & 0x7C1F; - b = colorB & 0x7C1F; - a |= (colorA & 0x3E0) << 16; - b |= (colorB & 0x3E0) << 16; - c = ((a * weightA + b * weightB) / 16); - if (c & 0x04000000) { - c = (c & ~0x07E00000) | 0x03E00000; - } - if (c & 0x0020) { - c = (c & ~0x003F) | 0x001F; - } - if (c & 0x8000) { - c = (c & ~0xF800) | 0x7C00; - } - c = (c & 0x7C1F) | ((c >> 16) & 0x03E0); -#endif -#else - a = colorA & 0xFF; - b = colorB & 0xFF; - c |= ((a * weightA + b * weightB) / 16) & 0x1FF; - if (c & 0x00000100) { - c = 0x000000FF; - } - - a = colorA & 0xFF00; - b = colorB & 0xFF00; - c |= ((a * weightA + b * weightB) / 16) & 0x1FF00; - if (c & 0x00010000) { - c = (c & 0x000000FF) | 0x0000FF00; - } - - a = colorA & 0xFF0000; - b = colorB & 0xFF0000; - c |= ((a * weightA + b * weightB) / 16) & 0x1FF0000; - if (c & 0x01000000) { - c = (c & 0x0000FFFF) | 0x00FF0000; - } -#endif - return c; -} -#endif - struct blip_t; -enum mColorFormat { - mCOLOR_XBGR8 = 0x00001, - mCOLOR_XRGB8 = 0x00002, - mCOLOR_BGRX8 = 0x00004, - mCOLOR_RGBX8 = 0x00008, - mCOLOR_ABGR8 = 0x00010, - mCOLOR_ARGB8 = 0x00020, - mCOLOR_BGRA8 = 0x00040, - mCOLOR_RGBA8 = 0x00080, - mCOLOR_RGB5 = 0x00100, - mCOLOR_BGR5 = 0x00200, - mCOLOR_RGB565 = 0x00400, - mCOLOR_BGR565 = 0x00800, - mCOLOR_ARGB5 = 0x01000, - mCOLOR_ABGR5 = 0x02000, - mCOLOR_RGBA5 = 0x04000, - mCOLOR_BGRA5 = 0x08000, - mCOLOR_RGB8 = 0x10000, - mCOLOR_BGR8 = 0x20000, - mCOLOR_L8 = 0x40000, - - mCOLOR_ANY = -1 -}; - enum mCoreFeature { mCORE_FEATURE_OPENGL = 1, }; @@ -293,6 +142,15 @@ struct mCoreMemoryBlock { uint32_t segmentStart; }; +struct mCoreScreenRegion { + size_t id; + const char* description; + int16_t x; + int16_t y; + int16_t w; + int16_t h; +}; + enum mCoreRegisterType { mCORE_REGISTER_GPR = 0, mCORE_REGISTER_FPR, diff --git a/include/mgba/core/rewind.h b/include/mgba/core/rewind.h index 04549761d..9d2595557 100644 --- a/include/mgba/core/rewind.h +++ b/include/mgba/core/rewind.h @@ -24,6 +24,7 @@ struct mCoreRewindContext { size_t size; struct VFile* previousState; struct VFile* currentState; + int rewindFrameCounter; #ifndef DISABLE_THREADING bool onThread; diff --git a/include/mgba/debugger/debugger.h b/include/mgba/debugger/debugger.h index b460623e3..4ce7c3cf9 100644 --- a/include/mgba/debugger/debugger.h +++ b/include/mgba/debugger/debugger.h @@ -12,6 +12,7 @@ CXX_GUARD_START #include #include +#include #include #include @@ -28,6 +29,7 @@ enum mDebuggerType { }; enum mDebuggerState { + DEBUGGER_CREATED = 0, DEBUGGER_PAUSED, DEBUGGER_RUNNING, DEBUGGER_CALLBACK, @@ -56,8 +58,10 @@ enum mDebuggerEntryReason { DEBUGGER_ENTER_STACK }; +struct mDebuggerModule; struct mDebuggerEntryInfo { uint32_t address; + int segment; union { struct { uint32_t oldValue; @@ -76,6 +80,7 @@ struct mDebuggerEntryInfo { } st; } type; ssize_t pointId; + struct mDebuggerModule* target; }; struct mBreakpoint { @@ -97,6 +102,7 @@ struct mWatchpoint { DECLARE_VECTOR(mBreakpointList, struct mBreakpoint); DECLARE_VECTOR(mWatchpointList, struct mWatchpoint); +DECLARE_VECTOR(mDebuggerModuleList, struct mDebuggerModule*); struct mDebugger; struct ParseTree; @@ -111,11 +117,11 @@ struct mDebuggerPlatform { void (*checkBreakpoints)(struct mDebuggerPlatform*); bool (*clearBreakpoint)(struct mDebuggerPlatform*, ssize_t id); - ssize_t (*setBreakpoint)(struct mDebuggerPlatform*, const struct mBreakpoint*); - void (*listBreakpoints)(struct mDebuggerPlatform*, struct mBreakpointList*); + ssize_t (*setBreakpoint)(struct mDebuggerPlatform*, struct mDebuggerModule*, const struct mBreakpoint*); + void (*listBreakpoints)(struct mDebuggerPlatform*, struct mDebuggerModule*, struct mBreakpointList*); - ssize_t (*setWatchpoint)(struct mDebuggerPlatform*, const struct mWatchpoint*); - void (*listWatchpoints)(struct mDebuggerPlatform*, struct mWatchpointList*); + ssize_t (*setWatchpoint)(struct mDebuggerPlatform*, struct mDebuggerModule*, const struct mWatchpoint*); + void (*listWatchpoints)(struct mDebuggerPlatform*, struct mDebuggerModule*, struct mWatchpointList*); void (*trace)(struct mDebuggerPlatform*, char* out, size_t* length); @@ -130,28 +136,52 @@ struct mDebugger { struct mCPUComponent d; struct mDebuggerPlatform* platform; enum mDebuggerState state; - enum mDebuggerType type; struct mCore* core; struct mScriptBridge* bridge; struct mStackTrace stackTrace; - void (*init)(struct mDebugger*); - void (*deinit)(struct mDebugger*); - - void (*paused)(struct mDebugger*); - void (*update)(struct mDebugger*); - void (*entered)(struct mDebugger*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*); - void (*custom)(struct mDebugger*); - - void (*interrupt)(struct mDebugger*); + struct mDebuggerModuleList modules; + struct Table pointOwner; }; -struct mDebugger* mDebuggerCreate(enum mDebuggerType type, struct mCore*); +struct mDebuggerModule { + struct mDebugger* p; + enum mDebuggerType type; + bool isPaused; + bool needsCallback; + + void (*init)(struct mDebuggerModule*); + void (*deinit)(struct mDebuggerModule*); + + void (*paused)(struct mDebuggerModule*, int32_t timeoutMs); + void (*update)(struct mDebuggerModule*); + void (*entered)(struct mDebuggerModule*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*); + void (*custom)(struct mDebuggerModule*); + + void (*interrupt)(struct mDebuggerModule*); +}; + +void mDebuggerInit(struct mDebugger*); +void mDebuggerDeinit(struct mDebugger*); + void mDebuggerAttach(struct mDebugger*, struct mCore*); +void mDebuggerAttachModule(struct mDebugger*, struct mDebuggerModule*); +void mDebuggerDetachModule(struct mDebugger*, struct mDebuggerModule*); +void mDebuggerRunTimeout(struct mDebugger* debugger, int32_t timeoutMs); void mDebuggerRun(struct mDebugger*); void mDebuggerRunFrame(struct mDebugger*); void mDebuggerEnter(struct mDebugger*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*); +void mDebuggerInterrupt(struct mDebugger*); +void mDebuggerUpdatePaused(struct mDebugger*); +void mDebuggerShutdown(struct mDebugger*); +void mDebuggerUpdate(struct mDebugger*); + +bool mDebuggerIsShutdown(const struct mDebugger*); + +struct mDebuggerModule* mDebuggerCreateModule(enum mDebuggerType type, struct mCore*); +void mDebuggerModuleSetNeedsCallback(struct mDebuggerModule*); + bool mDebuggerLookupIdentifier(struct mDebugger* debugger, const char* name, int32_t* value, int* segment); CXX_GUARD_END diff --git a/include/mgba/feature/commandline.h b/include/mgba/feature/commandline.h index 0f9c543d1..964d9bdf9 100644 --- a/include/mgba/feature/commandline.h +++ b/include/mgba/feature/commandline.h @@ -25,8 +25,9 @@ struct mArguments { struct Table configOverrides; - enum mDebuggerType debuggerType; bool debugAtStart; + bool debugCli; + bool debugGdb; bool showHelp; bool showVersion; }; diff --git a/include/mgba/feature/proxy-backend.h b/include/mgba/feature/proxy-backend.h new file mode 100644 index 000000000..8c0b9e427 --- /dev/null +++ b/include/mgba/feature/proxy-backend.h @@ -0,0 +1,81 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef PROXY_BACKEND_H +#define PROXY_BACKEND_H + +#include + +CXX_GUARD_START + +#include +#include +#include + +enum mVideoBackendCommandType { + mVB_CMD_DUMMY = 0, + mVB_CMD_INIT, + mVB_CMD_DEINIT, + mVB_CMD_SET_LAYER_DIMENSIONS, + mVB_CMD_LAYER_DIMENSIONS, + mVB_CMD_SWAP, + mVB_CMD_CLEAR, + mVB_CMD_CONTEXT_RESIZED, + mVB_CMD_SET_IMAGE_SIZE, + mVB_CMD_IMAGE_SIZE, + mVB_CMD_SET_IMAGE, + mVB_CMD_DRAW_FRAME, +}; + +union mVideoBackendCommandData { + struct mRectangle dims; + struct { + int width; + int height; + } s; + struct { + unsigned width; + unsigned height; + } u; + const void* image; +}; + +struct mVideoBackendCommand { + enum mVideoBackendCommandType cmd; + + union { + WHandle handle; + enum VideoLayer layer; + }; + union mVideoBackendCommandData data; +}; + +struct mVideoProxyBackend { + struct VideoBackend d; + struct VideoBackend* backend; + + struct RingFIFO in; + struct RingFIFO out; + + Mutex inLock; + Mutex outLock; + Condition inWait; + Condition outWait; + + void (*wakeupCb)(struct mVideoProxyBackend*, void* context); + void* context; +}; + +void mVideoProxyBackendInit(struct mVideoProxyBackend* proxy, struct VideoBackend* backend); +void mVideoProxyBackendDeinit(struct mVideoProxyBackend* proxy); + +void mVideoProxyBackendSubmit(struct mVideoProxyBackend* proxy, const struct mVideoBackendCommand* cmd, union mVideoBackendCommandData* out); +bool mVideoProxyBackendRun(struct mVideoProxyBackend* proxy, bool block); + +bool mVideoProxyBackendCommandIsBlocking(enum mVideoBackendCommandType); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/feature/video-backend.h b/include/mgba/feature/video-backend.h new file mode 100644 index 000000000..292dc9c82 --- /dev/null +++ b/include/mgba/feature/video-backend.h @@ -0,0 +1,80 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef VIDEO_BACKEND_H +#define VIDEO_BACKEND_H + +#include + +CXX_GUARD_START + +#include +#include + +#ifdef _WIN32 +#include +typedef HWND WHandle; +#else +typedef void* WHandle; +#endif + +mLOG_DECLARE_CATEGORY(VIDEO); + +enum VideoLayer { + VIDEO_LAYER_BACKGROUND = 0, + VIDEO_LAYER_BEZEL, + VIDEO_LAYER_IMAGE, + VIDEO_LAYER_OVERLAY0, + VIDEO_LAYER_OVERLAY1, + VIDEO_LAYER_OVERLAY2, + VIDEO_LAYER_OVERLAY3, + VIDEO_LAYER_OVERLAY4, + VIDEO_LAYER_OVERLAY5, + VIDEO_LAYER_OVERLAY6, + VIDEO_LAYER_OVERLAY7, + VIDEO_LAYER_OVERLAY8, + VIDEO_LAYER_OVERLAY9, + VIDEO_LAYER_MAX +}; + +#define VIDEO_LAYER_OVERLAY_COUNT VIDEO_LAYER_MAX - VIDEO_LAYER_OVERLAY0 + +struct VideoBackend { + void (*init)(struct VideoBackend*, WHandle handle); + void (*deinit)(struct VideoBackend*); + void (*setLayerDimensions)(struct VideoBackend*, enum VideoLayer, const struct mRectangle*); + void (*layerDimensions)(const struct VideoBackend*, enum VideoLayer, struct mRectangle*); + void (*swap)(struct VideoBackend*); + void (*clear)(struct VideoBackend*); + void (*contextResized)(struct VideoBackend*, unsigned w, unsigned h); + void (*setImageSize)(struct VideoBackend*, enum VideoLayer, int w, int h); + void (*imageSize)(struct VideoBackend*, enum VideoLayer, int* w, int* h); + void (*setImage)(struct VideoBackend*, enum VideoLayer, const void* frame); + void (*drawFrame)(struct VideoBackend*); + + void* user; + + bool filter; + bool lockAspectRatio; + bool lockIntegerScaling; + bool interframeBlending; + enum VideoLayer cropToLayer; +}; + +struct VideoShader { + const char* name; + const char* author; + const char* description; + void* preprocessShader; + void* passes; + size_t nPasses; +}; + +void VideoBackendGetFrame(const struct VideoBackend*, struct mRectangle* frame); +void VideoBackendGetFrameSize(const struct VideoBackend*, unsigned* width, unsigned* height); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/gb/interface.h b/include/mgba/gb/interface.h index 0ec54559f..ecd99e2f9 100644 --- a/include/mgba/gb/interface.h +++ b/include/mgba/gb/interface.h @@ -71,6 +71,7 @@ struct VFile; bool GBIsROM(struct VFile* vf); bool GBIsBIOS(struct VFile* vf); +bool GBIsCompatibleBIOS(struct VFile* vf, enum GBModel model); enum GBModel GBNameToModel(const char*); const char* GBModelToName(enum GBModel); diff --git a/include/mgba/internal/arm/debugger/debugger.h b/include/mgba/internal/arm/debugger/debugger.h index cbce4f120..e2f8e3858 100644 --- a/include/mgba/internal/arm/debugger/debugger.h +++ b/include/mgba/internal/arm/debugger/debugger.h @@ -45,7 +45,7 @@ struct ARMDebugger { }; struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void); -ssize_t ARMDebuggerSetSoftwareBreakpoint(struct mDebuggerPlatform* debugger, uint32_t address, enum ExecutionMode mode); +ssize_t ARMDebuggerSetSoftwareBreakpoint(struct mDebuggerPlatform* debugger, struct mDebuggerModule* owner, uint32_t address, enum ExecutionMode mode); CXX_GUARD_END diff --git a/include/mgba/internal/debugger/cli-debugger.h b/include/mgba/internal/debugger/cli-debugger.h index 1fc5df369..87ba9db88 100644 --- a/include/mgba/internal/debugger/cli-debugger.h +++ b/include/mgba/internal/debugger/cli-debugger.h @@ -73,6 +73,7 @@ struct CLIDebuggerBackend { ATTRIBUTE_FORMAT(printf, 2, 3) void (*printf)(struct CLIDebuggerBackend*, const char* fmt, ...); + int (*poll)(struct CLIDebuggerBackend*, int32_t timeoutMs); const char* (*readline)(struct CLIDebuggerBackend*, size_t* len); void (*lineAppend)(struct CLIDebuggerBackend*, const char* line); const char* (*historyLast)(struct CLIDebuggerBackend*, size_t* len); @@ -81,7 +82,7 @@ struct CLIDebuggerBackend { }; struct CLIDebugger { - struct mDebugger d; + struct mDebuggerModule d; struct CLIDebuggerSystem* system; struct CLIDebuggerBackend* backend; diff --git a/include/mgba/internal/debugger/gdb-stub.h b/include/mgba/internal/debugger/gdb-stub.h index b76b52452..6110ec499 100644 --- a/include/mgba/internal/debugger/gdb-stub.h +++ b/include/mgba/internal/debugger/gdb-stub.h @@ -14,7 +14,7 @@ CXX_GUARD_START #include -#define GDB_STUB_MAX_LINE 1200 +#define GDB_STUB_MAX_LINE 1400 #define GDB_STUB_INTERVAL 32 enum GDBStubAckState { @@ -31,7 +31,7 @@ enum GDBWatchpointsBehvaior { }; struct GDBStub { - struct mDebugger d; + struct mDebuggerModule d; char line[GDB_STUB_MAX_LINE]; char outgoing[GDB_STUB_MAX_LINE]; @@ -41,7 +41,6 @@ struct GDBStub { Socket socket; Socket connection; - bool shouldBlock; int untilPoll; bool supportsSwbreak; @@ -56,7 +55,7 @@ bool GDBStubListen(struct GDBStub*, int port, const struct Address* bindAddress, void GDBStubHangup(struct GDBStub*); void GDBStubShutdown(struct GDBStub*); -void GDBStubUpdate(struct GDBStub*); +bool GDBStubUpdate(struct GDBStub*, int timeoutMs); CXX_GUARD_END diff --git a/include/mgba/internal/gb/memory.h b/include/mgba/internal/gb/memory.h index d3a42a229..052a9f77e 100644 --- a/include/mgba/internal/gb/memory.h +++ b/include/mgba/internal/gb/memory.h @@ -292,7 +292,7 @@ struct GBMemory { int currentSramBank1; uint8_t* sramBank1; - unsigned cartBusDecay; + int cartBusDecay; uint16_t cartBusPc; uint8_t cartBus; diff --git a/include/mgba/internal/gb/serialize.h b/include/mgba/internal/gb/serialize.h index f4433c495..e76d0accb 100644 --- a/include/mgba/internal/gb/serialize.h +++ b/include/mgba/internal/gb/serialize.h @@ -419,6 +419,9 @@ struct GBSerializedState { uint8_t locked; uint8_t bank0; } mmm01; + struct { + uint8_t registersActive; + } pocketCam; struct { uint64_t lastLatch; uint8_t reg; @@ -484,6 +487,7 @@ struct GBSerializedState { union { uint8_t huc3Registers[0x80]; + uint8_t pocketCamRegisters[0x36]; struct { uint8_t registers[4]; uint8_t reserved[4]; diff --git a/include/mgba/internal/gb/video.h b/include/mgba/internal/gb/video.h index 31c54b089..c9b179739 100644 --- a/include/mgba/internal/gb/video.h +++ b/include/mgba/internal/gb/video.h @@ -22,6 +22,9 @@ enum { GB_VIDEO_VBLANK_PIXELS = 10, GB_VIDEO_VERTICAL_TOTAL_PIXELS = 154, + SGB_VIDEO_HORIZONTAL_PIXELS = 256, + SGB_VIDEO_VERTICAL_PIXELS = 224, + // TODO: Figure out exact lengths GB_VIDEO_MODE_2_LENGTH = 80, GB_VIDEO_MODE_3_LENGTH_BASE = 172, diff --git a/include/mgba/script.h b/include/mgba/script.h new file mode 100644 index 000000000..747dfedc1 --- /dev/null +++ b/include/mgba/script.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef M_SCRIPT_H +#define M_SCRIPT_H + +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/include/mgba/script/base.h b/include/mgba/script/base.h new file mode 100644 index 000000000..4e8bbf168 --- /dev/null +++ b/include/mgba/script/base.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef M_SCRIPT_BASE_H +#define M_SCRIPT_BASE_H + +#include + +CXX_GUARD_START + +#include +#include + +mSCRIPT_DECLARE_STRUCT(mImage) + +struct mScriptContext; +void mScriptContextAttachImage(struct mScriptContext* context); +void mScriptContextAttachStdlib(struct mScriptContext* context); +void mScriptContextAttachSocket(struct mScriptContext* context); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/script/canvas.h b/include/mgba/script/canvas.h new file mode 100644 index 000000000..e48cd4e5d --- /dev/null +++ b/include/mgba/script/canvas.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef M_SCRIPT_CANVAS_H +#define M_SCRIPT_CANVAS_H + +#include + +CXX_GUARD_START + +#include +#include + +struct VideoBackend; +void mScriptContextAttachCanvas(struct mScriptContext* context); +void mScriptCanvasUpdate(struct mScriptContext* context); +void mScriptCanvasUpdateBackend(struct mScriptContext* context, struct VideoBackend*); +void mScriptCanvasSetInternalScale(struct mScriptContext* context, unsigned scale); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/script/context.h b/include/mgba/script/context.h index dcec138e1..435277323 100644 --- a/include/mgba/script/context.h +++ b/include/mgba/script/context.h @@ -91,13 +91,12 @@ struct mScriptValue* mScriptContextAccessWeakref(struct mScriptContext*, struct void mScriptContextClearWeakref(struct mScriptContext*, uint32_t weakref); void mScriptContextDisownWeakref(struct mScriptContext*, uint32_t weakref); -void mScriptContextAttachStdlib(struct mScriptContext* context); -void mScriptContextAttachSocket(struct mScriptContext* context); void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants); void mScriptContextExportNamespace(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* value); void mScriptContextTriggerCallback(struct mScriptContext*, const char* callback, struct mScriptList* args); uint32_t mScriptContextAddCallback(struct mScriptContext*, const char* callback, struct mScriptValue* value); +uint32_t mScriptContextAddOneshot(struct mScriptContext*, const char* callback, struct mScriptValue* value); void mScriptContextRemoveCallback(struct mScriptContext*, uint32_t cbid); void mScriptContextSetDocstring(struct mScriptContext*, const char* key, const char* docstring); diff --git a/include/mgba/script/input.h b/include/mgba/script/input.h index ae1260ad1..fea9f3320 100644 --- a/include/mgba/script/input.h +++ b/include/mgba/script/input.h @@ -249,6 +249,7 @@ mSCRIPT_DECLARE_STRUCT(mScriptGamepad); void mScriptContextAttachInput(struct mScriptContext* context); void mScriptContextFireEvent(struct mScriptContext*, struct mScriptEvent*); +void mScriptContextClearKeys(struct mScriptContext*); int mScriptContextGamepadAttach(struct mScriptContext*, struct mScriptGamepad*); bool mScriptContextGamepadDetach(struct mScriptContext*, int pad); diff --git a/include/mgba/script/macros.h b/include/mgba/script/macros.h index 42710f9e8..9ce97f067 100644 --- a/include/mgba/script/macros.h +++ b/include/mgba/script/macros.h @@ -212,20 +212,30 @@ CXX_GUARD_START } \ }, -#define mSCRIPT_DEFINE_STRUCT_MEMBER_NAMED(STRUCT, TYPE, EXPORTED_NAME, NAME) { \ +#define _mSCRIPT_DEFINE_STRUCT_MEMBER(STRUCT, TYPE, EXPORTED_NAME, NAME, RO) { \ .type = mSCRIPT_CLASS_INIT_INSTANCE_MEMBER, \ .info = { \ .member = { \ .name = #EXPORTED_NAME, \ .type = mSCRIPT_TYPE_MS_ ## TYPE, \ - .offset = offsetof(struct STRUCT, NAME) \ + .offset = offsetof(struct STRUCT, NAME), \ + .readonly = RO \ } \ } \ }, +#define mSCRIPT_DEFINE_STRUCT_MEMBER_NAMED(STRUCT, TYPE, EXPORTED_NAME, NAME) \ + _mSCRIPT_DEFINE_STRUCT_MEMBER(STRUCT, TYPE, EXPORTED_NAME, NAME, false) + +#define mSCRIPT_DEFINE_STRUCT_CONST_MEMBER_NAMED(STRUCT, TYPE, EXPORTED_NAME, NAME) \ + _mSCRIPT_DEFINE_STRUCT_MEMBER(STRUCT, TYPE, EXPORTED_NAME, NAME, true) + #define mSCRIPT_DEFINE_STRUCT_MEMBER(STRUCT, TYPE, NAME) \ mSCRIPT_DEFINE_STRUCT_MEMBER_NAMED(STRUCT, TYPE, NAME, NAME) +#define mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(STRUCT, TYPE, NAME) \ + mSCRIPT_DEFINE_STRUCT_CONST_MEMBER_NAMED(STRUCT, TYPE, NAME, NAME) + #define mSCRIPT_DEFINE_INHERIT(PARENT) { \ .type = mSCRIPT_CLASS_INIT_INHERIT, \ .info = { \ @@ -393,6 +403,9 @@ CXX_GUARD_START static const struct mScriptValue _mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX]; \ _mSCRIPT_DECLARE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME, S, 0, 0, NPARAMS, _mIDENT(_mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME), __VA_ARGS__) \ +#define mSCRIPT_DEFINE_FUNCTION_BINDING_DEFAULTS(NAME) \ + static const struct mScriptValue _bindingDefaults_ ## NAME[mSCRIPT_PARAMS_MAX] = { + #define mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(TYPE, NAME) \ static const struct mScriptValue _mSTStructBindingDefaults_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX] = { \ mSCRIPT_NO_DEFAULT, @@ -423,7 +436,7 @@ CXX_GUARD_START #define mSCRIPT_DEFINE_STRUCT_DEINIT(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(DEINIT, TYPE, _deinit, _deinit) #define mSCRIPT_DEFINE_STRUCT_DEINIT_NAMED(TYPE, NAME) _mSCRIPT_DEFINE_STRUCT_BINDING(DEINIT, TYPE, _deinit, NAME) #define mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(GET, TYPE, _get, _get) -#define mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(SET, TYPE, _set, _set) +#define mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TYPE, SETTER) _mSCRIPT_DEFINE_STRUCT_BINDING(SET, TYPE, SETTER, SETTER) #define mSCRIPT_DEFINE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME) mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(doc_ ## TYPE, NAME, NAME) @@ -439,7 +452,7 @@ CXX_GUARD_START #define mSCRIPT_DEFINE_END { .type = mSCRIPT_CLASS_INIT_END } } } -#define _mSCRIPT_BIND_FUNCTION(NAME, NRET, RETURN, NPARAMS, ...) \ +#define _mSCRIPT_BIND_FUNCTION(NAME, NRET, RETURN, DEFAULTS, NPARAMS, ...) \ static struct mScriptFunction _function_ ## NAME = { \ .call = _binding_ ## NAME \ }; \ @@ -456,6 +469,7 @@ CXX_GUARD_START .count = NPARAMS, \ .entries = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(mSCRIPT_PREFIX_ ## NPARAMS, mSCRIPT_TYPE_MS_, _mEVEN_ ## NPARAMS(__VA_ARGS__)) }, \ .names = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(_mCALL_ ## NPARAMS, _mSTRINGIFY, _mODD_ ## NPARAMS(__VA_ARGS__)) }, \ + .defaults = DEFAULTS, \ }, \ .returnType = { \ .count = NRET, \ @@ -472,7 +486,7 @@ CXX_GUARD_START } \ } -#define mSCRIPT_BIND_FUNCTION(NAME, RETURN, FUNCTION, NPARAMS, ...) \ +#define _mSCRIPT_BIND_N_FUNCTION(NAME, RETURN, FUNCTION, DEFAULTS, NPARAMS, ...) \ static bool _binding_ ## NAME(struct mScriptFrame* frame, void* ctx) { \ UNUSED(ctx); \ _mCALL(mSCRIPT_POP_ ## NPARAMS, &frame->arguments, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \ @@ -482,9 +496,9 @@ CXX_GUARD_START _mSCRIPT_CALL(RETURN, FUNCTION, NPARAMS); \ return true; \ } \ - _mSCRIPT_BIND_FUNCTION(NAME, 1, mSCRIPT_TYPE_MS_ ## RETURN, NPARAMS, __VA_ARGS__) + _mSCRIPT_BIND_FUNCTION(NAME, 1, mSCRIPT_TYPE_MS_ ## RETURN, DEFAULTS, NPARAMS, __VA_ARGS__) -#define mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, NPARAMS, ...) \ +#define _mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, DEFAULTS, NPARAMS, ...) \ static bool _binding_ ## NAME(struct mScriptFrame* frame, void* ctx) { \ UNUSED(ctx); \ _mCALL(mSCRIPT_POP_ ## NPARAMS, &frame->arguments, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \ @@ -494,7 +508,21 @@ CXX_GUARD_START _mSCRIPT_CALL_VOID(FUNCTION, NPARAMS); \ return true; \ } \ - _mSCRIPT_BIND_FUNCTION(NAME, 0, 0, NPARAMS, __VA_ARGS__) + _mSCRIPT_BIND_FUNCTION(NAME, 0, 0, NULL, NPARAMS, __VA_ARGS__) + +#define mSCRIPT_BIND_FUNCTION(NAME, RETURN, FUNCTION, NPARAMS, ...) \ + _mSCRIPT_BIND_N_FUNCTION(NAME, RETURN, FUNCTION, NULL, NPARAMS, __VA_ARGS__) + +#define mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, NPARAMS, ...) \ + _mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, NULL, NPARAMS, __VA_ARGS__) + +#define mSCRIPT_BIND_FUNCTION_WITH_DEFAULTS(NAME, RETURN, FUNCTION, NPARAMS, ...) \ + static const struct mScriptValue _bindingDefaults_ ## NAME[mSCRIPT_PARAMS_MAX]; \ + _mSCRIPT_BIND_N_FUNCTION(NAME, RETURN, FUNCTION, _mIDENT(_bindingDefaults_ ## NAME), NPARAMS, __VA_ARGS__) + +#define mSCRIPT_BIND_VOID_FUNCTION_WITH_DEFAULTS(NAME, FUNCTION, NPARAMS, ...) \ + static const struct mScriptValue _bindingDefaults_ ## _ ## NAME[mSCRIPT_PARAMS_MAX]; \ + _mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, _mIDENT(_bindingDefaults_ ## NAME), NPARAMS, __VA_ARGS__) #define _mSCRIPT_DEFINE_DOC_FUNCTION(SCOPE, NAME, NRET, RETURN, NPARAMS, ...) \ static const struct mScriptType _mScriptDocType_ ## NAME = { \ diff --git a/include/mgba/script/storage.h b/include/mgba/script/storage.h new file mode 100644 index 000000000..629158e83 --- /dev/null +++ b/include/mgba/script/storage.h @@ -0,0 +1,28 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef M_SCRIPT_STORAGE_H +#define M_SCRIPT_STORAGE_H + +#include + +CXX_GUARD_START + +#include +#include + +struct VFile; +void mScriptContextAttachStorage(struct mScriptContext* context); +void mScriptStorageFlushAll(struct mScriptContext* context); + +bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucket); +bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucket, struct VFile* vf); +bool mScriptStorageLoadBucket(struct mScriptContext* context, const char* bucket); +bool mScriptStorageLoadBucketVF(struct mScriptContext* context, const char* bucket, struct VFile* vf); +void mScriptStorageGetBucketPath(const char* bucket, char* out); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index ed8ae02e3..d5a6d9947 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -35,16 +35,17 @@ CXX_GUARD_START #define mSCRIPT_TYPE_C_PTR void* #define mSCRIPT_TYPE_C_CPTR const void* #define mSCRIPT_TYPE_C_LIST struct mScriptList* -#define mSCRIPT_TYPE_C_TABLE Table* +#define mSCRIPT_TYPE_C_TABLE struct Table* #define mSCRIPT_TYPE_C_WRAPPER struct mScriptValue* #define mSCRIPT_TYPE_C_WEAKREF uint32_t +#define mSCRIPT_TYPE_C_NUL void* #define mSCRIPT_TYPE_C_S(STRUCT) struct STRUCT* #define mSCRIPT_TYPE_C_CS(STRUCT) const struct STRUCT* -#define mSCRIPT_TYPE_C_S_METHOD(STRUCT, NAME) _mSTStructFunctionType_ ## STRUCT ## _ ## NAME #define mSCRIPT_TYPE_C_PS(X) void #define mSCRIPT_TYPE_C_PCS(X) void #define mSCRIPT_TYPE_C_WSTR struct mScriptValue* #define mSCRIPT_TYPE_C_WLIST struct mScriptValue* +#define mSCRIPT_TYPE_C_WTABLE struct mScriptValue* #define mSCRIPT_TYPE_C_W(X) struct mScriptValue* #define mSCRIPT_TYPE_C_CW(X) const struct mScriptValue* @@ -66,13 +67,14 @@ CXX_GUARD_START #define mSCRIPT_TYPE_FIELD_TABLE table #define mSCRIPT_TYPE_FIELD_WRAPPER opaque #define mSCRIPT_TYPE_FIELD_WEAKREF u32 +#define mSCRIPT_TYPE_FIELD_NUL opaque #define mSCRIPT_TYPE_FIELD_S(STRUCT) opaque #define mSCRIPT_TYPE_FIELD_CS(STRUCT) copaque -#define mSCRIPT_TYPE_FIELD_S_METHOD(STRUCT, NAME) copaque #define mSCRIPT_TYPE_FIELD_PS(STRUCT) opaque #define mSCRIPT_TYPE_FIELD_PCS(STRUCT) copaque #define mSCRIPT_TYPE_FIELD_WSTR opaque #define mSCRIPT_TYPE_FIELD_WLIST opaque +#define mSCRIPT_TYPE_FIELD_WTABLE opaque #define mSCRIPT_TYPE_FIELD_W(TYPE) opaque #define mSCRIPT_TYPE_FIELD_CW(TYPE) opaque @@ -94,13 +96,14 @@ CXX_GUARD_START #define mSCRIPT_TYPE_MS_TABLE (&mSTTable) #define mSCRIPT_TYPE_MS_WRAPPER (&mSTWrapper) #define mSCRIPT_TYPE_MS_WEAKREF (&mSTWeakref) +#define mSCRIPT_TYPE_MS_NUL mSCRIPT_TYPE_MS_VOID #define mSCRIPT_TYPE_MS_S(STRUCT) (&mSTStruct_ ## STRUCT) #define mSCRIPT_TYPE_MS_CS(STRUCT) (&mSTStructConst_ ## STRUCT) -#define mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME) (&_mSTStructBindingType_ ## STRUCT ## _ ## NAME) #define mSCRIPT_TYPE_MS_PS(STRUCT) (&mSTStructPtr_ ## STRUCT) #define mSCRIPT_TYPE_MS_PCS(STRUCT) (&mSTStructPtrConst_ ## STRUCT) #define mSCRIPT_TYPE_MS_WSTR (&mSTStringWrapper) #define mSCRIPT_TYPE_MS_WLIST (&mSTListWrapper) +#define mSCRIPT_TYPE_MS_WTABLE (&mSTTableWrapper) #define mSCRIPT_TYPE_MS_W(TYPE) (&mSTWrapper_ ## TYPE) #define mSCRIPT_TYPE_MS_CW(TYPE) (&mSTWrapperConst_ ## TYPE) #define mSCRIPT_TYPE_MS_DS(STRUCT) (&mSTStruct_doc_ ## STRUCT) @@ -120,14 +123,16 @@ CXX_GUARD_START #define mSCRIPT_TYPE_CMP_STR(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) #define mSCRIPT_TYPE_CMP_CHARP(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE) #define mSCRIPT_TYPE_CMP_LIST(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE) +#define mSCRIPT_TYPE_CMP_TABLE(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_TABLE, TYPE) #define mSCRIPT_TYPE_CMP_PTR(TYPE) ((TYPE)->base >= mSCRIPT_TYPE_OPAQUE) #define mSCRIPT_TYPE_CMP_WRAPPER(TYPE) (true) +#define mSCRIPT_TYPE_CMP_NUL(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_VOID, TYPE) #define mSCRIPT_TYPE_CMP_S(STRUCT) mSCRIPT_TYPE_MS_S(STRUCT)->name == _mSCRIPT_FIELD_NAME #define mSCRIPT_TYPE_CMP_CS(STRUCT) mSCRIPT_TYPE_MS_CS(STRUCT)->name == _mSCRIPT_FIELD_NAME -#define mSCRIPT_TYPE_CMP_S_METHOD(STRUCT, NAME) mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME)->name == _mSCRIPT_FIELD_NAME +#define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_WSTR, TYPE)) +#define mSCRIPT_TYPE_CMP_WLIST(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_WLIST, TYPE)) +#define mSCRIPT_TYPE_CMP_WTABLE(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_WTABLE, TYPE)) #define mSCRIPT_TYPE_CMP(TYPE0, TYPE1) mSCRIPT_TYPE_CMP_ ## TYPE0(TYPE1) -#define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) || mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE)) -#define mSCRIPT_TYPE_CMP_WLIST(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE)) enum mScriptTypeBase { mSCRIPT_TYPE_VOID = 0, @@ -159,7 +164,8 @@ enum mScriptClassInitType { }; enum { - mSCRIPT_VALUE_FLAG_FREE_BUFFER = 1 + mSCRIPT_VALUE_FLAG_FREE_BUFFER = 1, + mSCRIPT_VALUE_FLAG_DEINIT = 2, }; struct mScriptType; @@ -183,6 +189,7 @@ extern const struct mScriptType mSTWrapper; extern const struct mScriptType mSTWeakref; extern const struct mScriptType mSTStringWrapper; extern const struct mScriptType mSTListWrapper; +extern const struct mScriptType mSTTableWrapper; extern struct mScriptValue mScriptValueNull; @@ -225,6 +232,7 @@ struct mScriptClassMember { const char* docstring; const struct mScriptType* type; size_t offset; + bool readonly; }; struct mScriptClassCastMember { @@ -250,10 +258,10 @@ struct mScriptTypeClass { bool internal; struct Table instanceMembers; struct Table castToMembers; + struct Table setters; struct mScriptClassMember* alloc; // TODO struct mScriptClassMember* free; struct mScriptClassMember* get; - struct mScriptClassMember* set; // TODO }; struct mScriptType { @@ -327,6 +335,8 @@ bool mScriptTableIteratorLookup(struct mScriptValue* table, struct TableIterator void mScriptFrameInit(struct mScriptFrame* frame); void mScriptFrameDeinit(struct mScriptFrame* frame); +struct mScriptValue* mScriptLambdaCreate0(struct mScriptValue* fn, struct mScriptList* args); + void mScriptClassInit(struct mScriptTypeClass* cls); void mScriptClassDeinit(struct mScriptTypeClass* cls); @@ -335,6 +345,7 @@ bool mScriptObjectGetConst(const struct mScriptValue* obj, const char* member, s bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScriptValue*); bool mScriptObjectCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) ; void mScriptObjectFree(struct mScriptValue* obj); +struct mScriptValue* mScriptObjectBindLambda(struct mScriptValue* obj, const char* member, struct mScriptList* args); bool mScriptPopS32(struct mScriptList* list, int32_t* out); bool mScriptPopU32(struct mScriptList* list, uint32_t* out); diff --git a/res/info.plist.in b/res/info.plist.in index eb625299c..f4b9bba80 100644 --- a/res/info.plist.in +++ b/res/info.plist.in @@ -32,6 +32,8 @@ ${MACOSX_BUNDLE_COPYRIGHT} NSSupportsAutomaticGraphicsSwitching + NSCameraUsageDescription + Only used when Game Boy Camera is selected and a physical camera is set CFBundleDocumentTypes diff --git a/res/nointro.dat b/res/nointro.dat index ac844f883..cb73775b6 100644 --- a/res/nointro.dat +++ b/res/nointro.dat @@ -1,8 +1,8 @@ clrmamepro ( name "Nintendo - Game Boy Advance" description "Nintendo - Game Boy Advance" - version 20220828-205130 - author "aci68, Arctic Circle System, Aringon, Bent, BigFred, bikerspade, C. V. Reynolds, chillerecke, DeadSkullzJr, Densetsu, DeriLoko3, einstein95, ElBarto, Enker, Flashfire42, fuzzball, Gefflon, Hiccup, hking0036, hydr0x, InternalLoss, Jack, jimmsu, kazumi213, Larsenv, Lesserkuma, Madeline, MeguCocoa, Money_114, NESBrew12, niemand, nnssxx, norkmetnoil577, omonim2007, Powerpuff, PPLToast, relax, RetroGamer, Rifu, sCZther, SonGoku, Tauwasser, togemet2, ufocrossing, Vallaine01, Whovian9369, xuom2, zg" + version 20230422-084331 + author "aci68, Arctic Circle System, Aringon, Bent, BigFred, bikerspade, C. V. Reynolds, chillerecke, DeadSkullzJr, Densetsu, DeriLoko3, einstein95, ElBarto, Enker, Flashfire42, fuzzball, Gefflon, Hiccup, hking0036, hydr0x, InternalLoss, Jack, jimmsu, kazumi213, Larsenv, Lesserkuma, Madeline, MeguCocoa, Money_114, NESBrew12, niemand, nnssxx, norkmetnoil577, NovaAurora, omonim2007, Powerpuff, PPLToast, rarenight, relax, RetroGamer, Rifu, sCZther, SonGoku, Tauwasser, Tescu, togemet2, ufocrossing, Vallaine01, Whovian9369, xprism, xuom2, zg" homepage No-Intro url "https://www.no-intro.org" forcenodump required @@ -45,7 +45,7 @@ game ( game ( name "007 - NightFire (USA, Europe) (En,Fr,De)" description "007 - NightFire (USA, Europe) (En,Fr,De)" - rom ( name "007 - NightFire (USA, Europe) (En,Fr,De).gba" size 8388608 crc 56c83c16 sha1 f4363923181b71448ddd6e28ac72d30b3ecfc019 ) + rom ( name "007 - NightFire (USA, Europe) (En,Fr,De).gba" size 8388608 crc 56c83c16 sha1 f4363923181b71448ddd6e28ac72d30b3ecfc019 flags verified ) ) game ( @@ -651,7 +651,7 @@ game ( game ( name "3 Game Pack! - Candy Land + Chutes and Ladders + Original Memory Game (USA)" description "3 Game Pack! - Candy Land + Chutes and Ladders + Original Memory Game (USA)" - rom ( name "3 Game Pack! - Candy Land + Chutes and Ladders + Original Memory Game (USA).gba" size 4194304 crc 628a8e32 sha1 946ccbf8d21adc7a5587ef85a9759a2ded23875d ) + rom ( name "3 Game Pack! - Candy Land + Chutes and Ladders + Original Memory Game (USA).gba" size 4194304 crc 628a8e32 sha1 946ccbf8d21adc7a5587ef85a9759a2ded23875d flags verified ) ) game ( @@ -765,7 +765,7 @@ game ( game ( name "Ace Combat Advance (USA, Europe)" description "Ace Combat Advance (USA, Europe)" - rom ( name "Ace Combat Advance (USA, Europe).gba" size 4194304 crc 43f5e157 sha1 856a08e8f60f817b96add5bf2f6db186bea832ef ) + rom ( name "Ace Combat Advance (USA, Europe).gba" size 4194304 crc 43f5e157 sha1 856a08e8f60f817b96add5bf2f6db186bea832ef flags verified ) ) game ( @@ -843,7 +843,7 @@ game ( game ( name "Activision Anthology (USA)" description "Activision Anthology (USA)" - rom ( name "Activision Anthology (USA).gba" size 8388608 crc 14a28d68 sha1 5125bbbbf1df7782590d99273735826636a2f9ba ) + rom ( name "Activision Anthology (USA).gba" size 8388608 crc 14a28d68 sha1 5125bbbbf1df7782590d99273735826636a2f9ba flags verified ) ) game ( @@ -903,7 +903,7 @@ game ( game ( name "Advance Wars (USA) (Rev 1)" description "Advance Wars (USA) (Rev 1)" - rom ( name "Advance Wars (USA) (Rev 1).gba" size 4194304 crc 26fd0fc9 sha1 15053499d5b3f49128a941d7f2d84876f5424d0c ) + rom ( name "Advance Wars (USA) (Rev 1).gba" size 4194304 crc 26fd0fc9 sha1 15053499d5b3f49128a941d7f2d84876f5424d0c flags verified ) ) game ( @@ -985,9 +985,15 @@ game ( ) game ( - name "Aero the Acro-Bat (USA) (Beta)" - description "Aero the Acro-Bat (USA) (Beta)" - rom ( name "Aero the Acro-Bat (USA) (Beta).gba" size 1030244 crc beed50f0 sha1 68c0a00275c7135ca1a116ccced1ecede75819b8 ) + name "Adventures of Mr. Bean (Europe) (Demo)" + description "Adventures of Mr. Bean (Europe) (Demo)" + rom ( name "Adventures of Mr. Bean (Europe) (Demo).gba" size 2097152 crc 5e3b686d sha1 158856b0b438217ddf8b900ba9c5dca3d6457c7f ) +) + +game ( + name "Aero the Acro-Bat (USA) (Beta 1)" + description "Aero the Acro-Bat (USA) (Beta 1)" + rom ( name "Aero the Acro-Bat (USA) (Beta 1).gba" size 1030244 crc beed50f0 sha1 68c0a00275c7135ca1a116ccced1ecede75819b8 ) ) game ( @@ -997,9 +1003,9 @@ game ( ) game ( - name "Aero the Acro-Bat - Rascal Rival Revenge (Europe) (Beta)" - description "Aero the Acro-Bat - Rascal Rival Revenge (Europe) (Beta)" - rom ( name "Aero the Acro-Bat - Rascal Rival Revenge (Europe) (Beta).gba" size 4194304 crc 6f3ea564 sha1 3f88084c501fb15820f6ad9a9c87ac21af3b59eb ) + name "Aero the Acro-Bat - Rascal Rival Revenge (USA) (Beta 2) (2002-03-27)" + description "Aero the Acro-Bat - Rascal Rival Revenge (USA) (Beta 2) (2002-03-27)" + rom ( name "Aero the Acro-Bat - Rascal Rival Revenge (USA) (Beta 2) (2002-03-27).gba" size 4194304 crc 6f3ea564 sha1 3f88084c501fb15820f6ad9a9c87ac21af3b59eb ) ) game ( @@ -1020,6 +1026,12 @@ game ( rom ( name "Agassi Tennis Generation (USA).gba" size 4194304 crc 8ba179b8 sha1 1797251886d165e136ce6ba564ad8c8ec865d829 ) ) +game ( + name "AGB Aging Cartridge (World) (v1.0) (Test Program)" + description "AGB Aging Cartridge (World) (v1.0) (Test Program)" + rom ( name "AGB Aging Cartridge (World) (v1.0) (Test Program).gba" size 2097152 crc bf553530 sha1 2445bf11a5f905695b00011d77d18c99e754b633 ) +) + game ( name "AGB-Parallel Interface Cartridge (Japan) (En) (Program)" description "AGB-Parallel Interface Cartridge (Japan) (En) (Program)" @@ -1045,9 +1057,9 @@ game ( ) game ( - name "AGS Aging Cartridge (World) (v7.0) (Test Program)" - description "AGS Aging Cartridge (World) (v7.0) (Test Program)" - rom ( name "AGS Aging Cartridge (World) (v7.0) (Test Program).gba" size 2097152 crc bbb6a960 sha1 c67e0a5e26ea5eba2bc11c99d003027a96e44060 flags verified ) + name "AGS Aging Cartridge (World) (Rev 1, v7.0) (Test Program)" + description "AGS Aging Cartridge (World) (Rev 1, v7.0) (Test Program)" + rom ( name "AGS Aging Cartridge (World) (Rev 1, v7.0) (Test Program).gba" size 2097152 crc bbb6a960 sha1 c67e0a5e26ea5eba2bc11c99d003027a96e44060 flags verified ) ) game ( @@ -1063,9 +1075,9 @@ game ( ) game ( - name "AGS Aging Cartridge (World) (v9.0) (Test Program)" - description "AGS Aging Cartridge (World) (v9.0) (Test Program)" - rom ( name "AGS Aging Cartridge (World) (v9.0) (Test Program).gba" size 2097152 crc 2e69686d sha1 03d3a486482b61128872519fd755d0c072c12d93 ) + name "AGS Aging Cartridge (World) (Rev 3, v9.0) (Test Program)" + description "AGS Aging Cartridge (World) (Rev 3, v9.0) (Test Program)" + rom ( name "AGS Aging Cartridge (World) (Rev 3, v9.0) (Test Program).gba" size 2097152 crc 2e69686d sha1 03d3a486482b61128872519fd755d0c072c12d93 flags verified ) ) game ( @@ -1083,7 +1095,7 @@ game ( game ( name "AirForce Delta Storm (USA) (En,Ja,Fr,De)" description "AirForce Delta Storm (USA) (En,Ja,Fr,De)" - rom ( name "AirForce Delta Storm (USA) (En,Ja,Fr,De).gba" size 4194304 crc ebf757b8 sha1 7bc53480a43ada2aad32d798ab00b6e761726728 ) + rom ( name "AirForce Delta Storm (USA) (En,Ja,Fr,De).gba" size 4194304 crc ebf757b8 sha1 7bc53480a43ada2aad32d798ab00b6e761726728 flags verified ) ) game ( @@ -1146,12 +1158,6 @@ game ( rom ( name "Aleck Bordon Adventure - Tower & Shaft Advance (Japan).gba" size 4194304 crc e068728f sha1 2def9d7ea29a0ed5845b354e433e0a55e979f636 ) ) -game ( - name "Alex Ferguson's Player Manager 2002 ~ Total Soccer Manager (Europe) (En,Fr,De,Es,It,Nl)" - description "Alex Ferguson's Player Manager 2002 ~ Total Soccer Manager (Europe) (En,Fr,De,Es,It,Nl)" - rom ( name "Alex Ferguson's Player Manager 2002 ~ Total Soccer Manager (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc 92f99295 sha1 a870e1321a8ad3317cd695fcc0c713441c25919f ) -) - game ( name "Alex Rider - Stormbreaker (USA)" description "Alex Rider - Stormbreaker (USA)" @@ -1173,7 +1179,7 @@ game ( game ( name "Alienators - Evolution Continues (USA, Europe)" description "Alienators - Evolution Continues (USA, Europe)" - rom ( name "Alienators - Evolution Continues (USA, Europe).gba" size 4194304 crc 0d694ca4 sha1 fb691c5e21fa388d75497e9090db82dec881e422 ) + rom ( name "Alienators - Evolution Continues (USA, Europe).gba" size 4194304 crc 0d694ca4 sha1 fb691c5e21fa388d75497e9090db82dec881e422 flags verified ) ) game ( @@ -1209,7 +1215,7 @@ game ( game ( name "Altered Beast - Guardian of the Realms (USA)" description "Altered Beast - Guardian of the Realms (USA)" - rom ( name "Altered Beast - Guardian of the Realms (USA).gba" size 8388608 crc c4955f69 sha1 194dc1fff5578dcd8a2a6914647f1b67334f2a8a ) + rom ( name "Altered Beast - Guardian of the Realms (USA).gba" size 8388608 crc c4955f69 sha1 194dc1fff5578dcd8a2a6914647f1b67334f2a8a flags verified ) ) game ( @@ -1273,9 +1279,69 @@ game ( ) game ( - name "Anguna - Warriors of Virtue (World) (Aftermarket) (Homebrew)" - description "Anguna - Warriors of Virtue (World) (Aftermarket) (Homebrew)" - rom ( name "Anguna - Warriors of Virtue (World) (Aftermarket) (Homebrew).gba" size 1710888 crc 3346891f sha1 270c426705df767a4ad2dc69d039842442f779b2 ) + name "Anguna - Warriors of Virtue (World) (v0.95) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (v0.95) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (v0.95) (Aftermarket) (Unl).gba" size 1710888 crc 3346891f sha1 270c426705df767a4ad2dc69d039842442f779b2 flags verified ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (2021-02-28) (Patreon) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (2021-02-28) (Patreon) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (2021-02-28) (Patreon) (Aftermarket) (Unl).gba" size 1729120 crc a354d555 sha1 d7cd0ab9d622187d4ce55bf0c7f14a24ee781710 ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (v0.95) (Itch.io) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (v0.95) (Itch.io) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (v0.95) (Itch.io) (Aftermarket) (Unl).gba" size 1775856 crc 41ea5b0b sha1 e351bc6a9046ec002fc2dfdee061047908bd4350 ) +) + +game ( + name "Anguna - Warriors of Virtue (Unknown) (Retro-Bit Generations) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (Unknown) (Retro-Bit Generations) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (Unknown) (Retro-Bit Generations) (Aftermarket) (Unl).gba" size 1775660 crc a234813c sha1 ad904624bf198a164ba580eea326cd30fffebac8 ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (Demo) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (Demo) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (Demo) (Aftermarket) (Unl).gba" size 597104 crc 0a9098b4 sha1 0992fb6f38e49426adc834fb3831748a1caa1bc0 ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (v2.0) (Demo) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (v2.0) (Demo) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (v2.0) (Demo) (Aftermarket) (Unl).gba" size 811840 crc 63c1cc2c sha1 faca8225ee20b8e5edb14e005af77b63c6f51446 ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (v0.91) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (v0.91) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (v0.91) (Aftermarket) (Unl).gba" size 1714000 crc eebb016f sha1 008a9d06f78bccc540ca4ece4d1982de76f95901 ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (v0.92) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (v0.92) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (v0.92) (Aftermarket) (Unl).gba" size 1710624 crc ba271987 sha1 04f1e79e79e894d8f45ed1ea2ff5aa67053f0ab3 ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (v0.93) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (v0.93) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (v0.93) (Aftermarket) (Unl).gba" size 1710624 crc df7108e3 sha1 bd267fd7e762acb8cd595e54bfd51a139864759e ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (v0.93) (Aftermarket) (Unl) (Alt)" + description "Anguna - Warriors of Virtue (World) (v0.93) (Aftermarket) (Unl) (Alt)" + rom ( name "Anguna - Warriors of Virtue (World) (v0.93) (Aftermarket) (Unl) (Alt).gba" size 1714000 crc 54ff2e56 sha1 0386315fc140db2a43a6f927c04de658bd800da9 ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (v0.94) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (v0.94) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (v0.94) (Aftermarket) (Unl).gba" size 1710952 crc cb4e2768 sha1 a7297cf4ecffc1f93f86edde68e1ff42e8099f3b ) ) game ( @@ -1293,7 +1359,7 @@ game ( game ( name "Animal Snap - Rescue Them 2 by 2 (USA)" description "Animal Snap - Rescue Them 2 by 2 (USA)" - rom ( name "Animal Snap - Rescue Them 2 by 2 (USA).gba" size 4194304 crc 7304dca4 sha1 899b9158f622ab145c6787b2af65abda902e6a95 ) + rom ( name "Animal Snap - Rescue Them 2 by 2 (USA).gba" size 4194304 crc 7304dca4 sha1 899b9158f622ab145c6787b2af65abda902e6a95 flags verified ) ) game ( @@ -1315,9 +1381,15 @@ game ( ) game ( - name "Another World (World) (En,Fr) (Unl)" - description "Another World (World) (En,Fr) (Unl)" - rom ( name "Another World (World) (En,Fr) (Unl).gba" size 2010358 crc 86c4f772 sha1 41d39a0c34f72469dd3fbcc90190605b8ada93e6 ) + name "Another World (Europe) (En,Fr) (v2.1) (Unl)" + description "Another World (Europe) (En,Fr) (v2.1) (Unl)" + rom ( name "Another World (Europe) (En,Fr) (v2.1) (Unl).gba" size 2010358 crc 86c4f772 sha1 41d39a0c34f72469dd3fbcc90190605b8ada93e6 ) +) + +game ( + name "Another World (Europe) (Fr) (v1.2) (Unl)" + description "Another World (Europe) (Fr) (v1.2) (Unl)" + rom ( name "Another World (Europe) (Fr) (v1.2) (Unl).gba" size 1823864 crc 1a1397de sha1 5bcc5c9a633e2226411dd41f1a191b9fcc793d92 ) ) game ( @@ -1350,6 +1422,12 @@ game ( rom ( name "Ao-Zora to Nakama-tachi - Yume no Bouken (Japan).gba" size 4194304 crc ad9af125 sha1 0e6c92477793ce495caa400899effb4f87384f3c ) ) +game ( + name "Apotris (World) (v3.4.5) (Aftermarket) (Unl)" + description "Apotris (World) (v3.4.5) (Aftermarket) (Unl)" + rom ( name "Apotris (World) (v3.4.5) (Aftermarket) (Unl).gba" size 4194304 crc 55ae4312 sha1 fb7142bcc30f71f187cc51b7fcbc5a3958374c6c ) +) + game ( name "Archer Maclean's 3D Pool (USA)" description "Archer Maclean's 3D Pool (USA)" @@ -1365,25 +1443,25 @@ game ( game ( name "Army Men - Operation Green (USA) (En,Fr,De,Es,It)" description "Army Men - Operation Green (USA) (En,Fr,De,Es,It)" - rom ( name "Army Men - Operation Green (USA) (En,Fr,De,Es,It).gba" size 4194304 crc 6d174e28 sha1 b0fa4769cddbc66bc333ad45658524a4aeeec755 ) + rom ( name "Army Men - Operation Green (USA) (En,Fr,De,Es,It).gba" size 4194304 crc 6d174e28 sha1 b0fa4769cddbc66bc333ad45658524a4aeeec755 flags verified ) ) game ( name "Army Men - Turf Wars (USA)" description "Army Men - Turf Wars (USA)" - rom ( name "Army Men - Turf Wars (USA).gba" size 8388608 crc 65ac74cc sha1 43bfc86185a06d9d9c27686ad8cb650288b716ae ) + rom ( name "Army Men - Turf Wars (USA).gba" size 8388608 crc 65ac74cc sha1 43bfc86185a06d9d9c27686ad8cb650288b716ae flags verified ) ) game ( name "Army Men Advance (USA, Europe) (En,Fr,De,Es,It)" description "Army Men Advance (USA, Europe) (En,Fr,De,Es,It)" - rom ( name "Army Men Advance (USA, Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 9a4a509f sha1 43cd22c8e5832790b0cdbdcf0e11859021747342 ) + rom ( name "Army Men Advance (USA, Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 9a4a509f sha1 43cd22c8e5832790b0cdbdcf0e11859021747342 flags verified ) ) game ( name "Around the World in 80 Days (USA)" description "Around the World in 80 Days (USA)" - rom ( name "Around the World in 80 Days (USA).gba" size 4194304 crc c2c22af2 sha1 a3649682ef8a2378767154b37ca854d615305646 ) + rom ( name "Around the World in 80 Days (USA).gba" size 4194304 crc c2c22af2 sha1 a3649682ef8a2378767154b37ca854d615305646 flags verified ) ) game ( @@ -1395,7 +1473,7 @@ game ( game ( name "Arthur and the Invisibles (USA) (En,Fr,Es)" description "Arthur and the Invisibles (USA) (En,Fr,Es)" - rom ( name "Arthur and the Invisibles (USA) (En,Fr,Es).gba" size 16777216 crc 87c25055 sha1 6173ea7e00497e7a908a97e64cb4bc86396e8459 ) + rom ( name "Arthur and the Invisibles (USA) (En,Fr,Es).gba" size 16777216 crc 87c25055 sha1 6173ea7e00497e7a908a97e64cb4bc86396e8459 flags verified ) ) game ( @@ -1449,7 +1527,7 @@ game ( game ( name "Astro Boy - Tetsuwan Atom - Atom Heart no Himitsu (Japan)" description "Astro Boy - Tetsuwan Atom - Atom Heart no Himitsu (Japan)" - rom ( name "Astro Boy - Tetsuwan Atom - Atom Heart no Himitsu (Japan).gba" size 8388608 crc e2d94b0d sha1 ca16dc4044b9ae98a26c826bb3cc19a7e8315315 ) + rom ( name "Astro Boy - Tetsuwan Atom - Atom Heart no Himitsu (Japan).gba" size 8388608 crc e2d94b0d sha1 ca16dc4044b9ae98a26c826bb3cc19a7e8315315 flags verified ) ) game ( @@ -1476,6 +1554,12 @@ game ( rom ( name "Atlantis - The Lost Empire (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc b3948dbc sha1 93624eaa9a80cdb791bd14955b55dd7c58c4abbd ) ) +game ( + name "Atomic Betty (USA, Europe)" + description "Atomic Betty (USA, Europe)" + rom ( name "Atomic Betty (USA, Europe).gba" size 8388608 crc 8919d82c sha1 59e7400802ab634065b9674de3f437ddf8309d6e flags verified ) +) + game ( name "Atomic Betty (USA, Europe) (Beta)" description "Atomic Betty (USA, Europe) (Beta)" @@ -1483,9 +1567,9 @@ game ( ) game ( - name "Atomic Betty (USA, Europe)" - description "Atomic Betty (USA, Europe)" - rom ( name "Atomic Betty (USA, Europe).gba" size 8388608 crc 8919d82c sha1 59e7400802ab634065b9674de3f437ddf8309d6e ) + name "ATV - Quad Power Racing (USA, Europe)" + description "ATV - Quad Power Racing (USA, Europe)" + rom ( name "ATV - Quad Power Racing (USA, Europe).gba" size 4194304 crc 9c1a7dcb sha1 43a2c71b1f3b4085adee648e5a409be4517cd7bb flags verified ) ) game ( @@ -1494,12 +1578,6 @@ game ( rom ( name "ATV - Quad Power Racing (Europe) (En,Fr,De,Es,It) (Rev 1).gba" size 4194304 crc 4b4e8bc7 sha1 2234f23928c871b00bd48bdcdd5273362d4e0cdc ) ) -game ( - name "ATV - Quad Power Racing (USA, Europe)" - description "ATV - Quad Power Racing (USA, Europe)" - rom ( name "ATV - Quad Power Racing (USA, Europe).gba" size 4194304 crc 9c1a7dcb sha1 43a2c71b1f3b4085adee648e5a409be4517cd7bb ) -) - game ( name "ATV - Thunder Ridge Riders (USA)" description "ATV - Thunder Ridge Riders (USA)" @@ -1515,7 +1593,7 @@ game ( game ( name "Avatar - The Last Airbender (USA)" description "Avatar - The Last Airbender (USA)" - rom ( name "Avatar - The Last Airbender (USA).gba" size 8388608 crc 946787c0 sha1 9d864e9b3ccce4e5e1b1c566afa0d06088e88dfd ) + rom ( name "Avatar - The Last Airbender (USA).gba" size 8388608 crc 946787c0 sha1 9d864e9b3ccce4e5e1b1c566afa0d06088e88dfd flags verified ) ) game ( @@ -1575,7 +1653,7 @@ game ( game ( name "Back to Stone (USA) (En,Fr)" description "Back to Stone (USA) (En,Fr)" - rom ( name "Back to Stone (USA) (En,Fr).gba" size 4194304 crc 4c29c9c8 sha1 db1fe7581858053b58412a97df262b46838a7be7 ) + rom ( name "Back to Stone (USA) (En,Fr).gba" size 4194304 crc 4c29c9c8 sha1 db1fe7581858053b58412a97df262b46838a7be7 flags verified ) ) game ( @@ -1611,7 +1689,7 @@ game ( game ( name "Backyard Baseball (USA)" description "Backyard Baseball (USA)" - rom ( name "Backyard Baseball (USA).gba" size 4194304 crc 9f36b4e5 sha1 657a639ab3ffbfc85c7a0da51d640ac2e60ec430 ) + rom ( name "Backyard Baseball (USA).gba" size 4194304 crc 9f36b4e5 sha1 657a639ab3ffbfc85c7a0da51d640ac2e60ec430 flags verified ) ) game ( @@ -1653,7 +1731,7 @@ game ( game ( name "Backyard Sports - Baseball 2007 (USA)" description "Backyard Sports - Baseball 2007 (USA)" - rom ( name "Backyard Sports - Baseball 2007 (USA).gba" size 4194304 crc 0ee82569 sha1 cde84171b11a767338158c61ee2ad608d4eb8889 ) + rom ( name "Backyard Sports - Baseball 2007 (USA).gba" size 4194304 crc 0ee82569 sha1 cde84171b11a767338158c61ee2ad608d4eb8889 flags verified ) ) game ( @@ -1764,18 +1842,18 @@ game ( rom ( name "Banjo-Pilot (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc b98de3a4 sha1 5203f49c3b372b49dcb5c23f0dcbbb8432336081 flags verified ) ) -game ( - name "Banjo-Pilot (Europe) (En,Fr,De,Es,It) (Beta 2) (2004-09-29)" - description "Banjo-Pilot (Europe) (En,Fr,De,Es,It) (Beta 2) (2004-09-29)" - rom ( name "Banjo-Pilot (Europe) (En,Fr,De,Es,It) (Beta 2) (2004-09-29).gba" size 16777216 crc 3168a9a4 sha1 d0d26e986f73300b52f2a91b0bd57f92d05dda31 ) -) - game ( name "Banjo-Pilot (Europe) (Beta 1)" description "Banjo-Pilot (Europe) (Beta 1)" rom ( name "Banjo-Pilot (Europe) (Beta 1).gba" size 15545696 crc 817fd31d sha1 51b7fe0af316f4c25e08b2c8348b579e86697e3f ) ) +game ( + name "Banjo-Pilot (Europe) (En,Fr,De,Es,It) (Beta 2) (2004-09-29)" + description "Banjo-Pilot (Europe) (En,Fr,De,Es,It) (Beta 2) (2004-09-29)" + rom ( name "Banjo-Pilot (Europe) (En,Fr,De,Es,It) (Beta 2) (2004-09-29).gba" size 16777216 crc 3168a9a4 sha1 d0d26e986f73300b52f2a91b0bd57f92d05dda31 ) +) + game ( name "Barbie - The Princess and the Pauper (USA)" description "Barbie - The Princess and the Pauper (USA)" @@ -1801,9 +1879,9 @@ game ( ) game ( - name "Barbie as the Island Princess (USA)" - description "Barbie as the Island Princess (USA)" - rom ( name "Barbie as the Island Princess (USA).gba" size 8388608 crc db6160eb sha1 4ac1502b9b292584beecb8c7baf1abbcdb3a2d75 ) + name "Barbie as The Island Princess (USA)" + description "Barbie as The Island Princess (USA)" + rom ( name "Barbie as The Island Princess (USA).gba" size 8388608 crc db6160eb sha1 4ac1502b9b292584beecb8c7baf1abbcdb3a2d75 ) ) game ( @@ -1818,18 +1896,6 @@ game ( rom ( name "Barbie Diaries, The - High School Mystery (Europe).gba" size 8388608 crc 4e8a650c sha1 1a89dc89d8cffc119643b99228d4cc567d5421b3 ) ) -game ( - name "Barbie Groovy Games (USA)" - description "Barbie Groovy Games (USA)" - rom ( name "Barbie Groovy Games (USA).gba" size 4194304 crc aa017567 sha1 055db9f8f3ba18358162a1411cf24300a6de6046 ) -) - -game ( - name "Barbie Groovy Games (Europe) (En,Fr,De,Es,It)" - description "Barbie Groovy Games (Europe) (En,Fr,De,Es,It)" - rom ( name "Barbie Groovy Games (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 749339b2 sha1 e41751c52d5cd84b204ac4d73eefc5b360f56f1d ) -) - game ( name "Barbie Horse Adventures (Europe) (En,Fr,De,Es,It,Nl)" description "Barbie Horse Adventures (Europe) (En,Fr,De,Es,It,Nl)" @@ -1851,7 +1917,19 @@ game ( game ( name "Barbie in the 12 Dancing Princesses (Europe) (En,Fr,De,Es,It)" description "Barbie in the 12 Dancing Princesses (Europe) (En,Fr,De,Es,It)" - rom ( name "Barbie in the 12 Dancing Princesses (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 7ca7aa12 sha1 3feb77f60ad71d4dbb83c7ea0712759e50ccc088 ) + rom ( name "Barbie in the 12 Dancing Princesses (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 7ca7aa12 sha1 3feb77f60ad71d4dbb83c7ea0712759e50ccc088 flags verified ) +) + +game ( + name "Barbie Software - Groovy Games (USA)" + description "Barbie Software - Groovy Games (USA)" + rom ( name "Barbie Software - Groovy Games (USA).gba" size 4194304 crc aa017567 sha1 055db9f8f3ba18358162a1411cf24300a6de6046 ) +) + +game ( + name "Barbie Software - Groovy Games (Europe) (En,Fr,De,Es,It)" + description "Barbie Software - Groovy Games (Europe) (En,Fr,De,Es,It)" + rom ( name "Barbie Software - Groovy Games (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 749339b2 sha1 e41751c52d5cd84b204ac4d73eefc5b360f56f1d ) ) game ( @@ -1927,69 +2005,69 @@ game ( ) game ( - name "Battle Network Rockman EXE (Japan)" - description "Battle Network Rockman EXE (Japan)" - rom ( name "Battle Network Rockman EXE (Japan).gba" size 8388608 crc d9516e50 sha1 6e42dbd5cdee25851fb55dba060b28e28e4f4e5f ) + name "Battle Network - Rockman EXE (Japan) (Virtual Console)" + description "Battle Network - Rockman EXE (Japan) (Virtual Console)" + rom ( name "Battle Network - Rockman EXE (Japan) (Virtual Console).gba" size 8388608 crc 109f133b sha1 7872a276d0059ff0bcff86fd3f843cace3cc8840 ) ) game ( - name "Battle Network Rockman EXE (Japan) (Virtual Console)" - description "Battle Network Rockman EXE (Japan) (Virtual Console)" - rom ( name "Battle Network Rockman EXE (Japan) (Virtual Console).gba" size 8388608 crc 109f133b sha1 7872a276d0059ff0bcff86fd3f843cace3cc8840 ) + name "Battle Network - Rockman EXE (Japan)" + description "Battle Network - Rockman EXE (Japan)" + rom ( name "Battle Network - Rockman EXE (Japan).gba" size 8388608 crc d9516e50 sha1 6e42dbd5cdee25851fb55dba060b28e28e4f4e5f ) ) game ( - name "Battle Network Rockman EXE 2 (Japan) (Rev 1) (Virtual Console)" - description "Battle Network Rockman EXE 2 (Japan) (Rev 1) (Virtual Console)" - rom ( name "Battle Network Rockman EXE 2 (Japan) (Rev 1) (Virtual Console).gba" size 8388608 crc 44d44721 sha1 1c2416dfb86936752c5f68861f6339feac21458f ) + name "Battle Network - Rockman EXE 2 (Japan)" + description "Battle Network - Rockman EXE 2 (Japan)" + rom ( name "Battle Network - Rockman EXE 2 (Japan).gba" size 8388608 crc 98e4f096 sha1 6ed31ea56328673ba9d87186a7d506c701508e28 ) ) game ( - name "Battle Network Rockman EXE 2 (Japan) (Rev 1)" - description "Battle Network Rockman EXE 2 (Japan) (Rev 1)" - rom ( name "Battle Network Rockman EXE 2 (Japan) (Rev 1).gba" size 8388608 crc 41576087 sha1 9cb4d57bdedee5a760e98a3068c0e39a293b447c ) + name "Battle Network - Rockman EXE 2 (Japan) (Rev 1)" + description "Battle Network - Rockman EXE 2 (Japan) (Rev 1)" + rom ( name "Battle Network - Rockman EXE 2 (Japan) (Rev 1).gba" size 8388608 crc 41576087 sha1 9cb4d57bdedee5a760e98a3068c0e39a293b447c ) ) game ( - name "Battle Network Rockman EXE 2 (Japan)" - description "Battle Network Rockman EXE 2 (Japan)" - rom ( name "Battle Network Rockman EXE 2 (Japan).gba" size 8388608 crc 98e4f096 sha1 6ed31ea56328673ba9d87186a7d506c701508e28 ) + name "Battle Network - Rockman EXE 2 (Japan) (Rev 1) (Virtual Console)" + description "Battle Network - Rockman EXE 2 (Japan) (Rev 1) (Virtual Console)" + rom ( name "Battle Network - Rockman EXE 2 (Japan) (Rev 1) (Virtual Console).gba" size 8388608 crc 44d44721 sha1 1c2416dfb86936752c5f68861f6339feac21458f ) ) game ( - name "Battle Network Rockman EXE 3 (Japan)" - description "Battle Network Rockman EXE 3 (Japan)" - rom ( name "Battle Network Rockman EXE 3 (Japan).gba" size 8388608 crc 1c57724e sha1 2a381543f84dacc0f8310d4516fde0c33b5feca0 ) + name "Battle Network - Rockman EXE 3 (Japan) (Rev 1) (Virtual Console)" + description "Battle Network - Rockman EXE 3 (Japan) (Rev 1) (Virtual Console)" + rom ( name "Battle Network - Rockman EXE 3 (Japan) (Rev 1) (Virtual Console).gba" size 8388608 crc 47ccb9b8 sha1 e2a7c4abba66d17e073281e9ea44b6a5760b53c6 ) ) game ( - name "Battle Network Rockman EXE 3 (Japan) (Rev 1) (Virtual Console)" - description "Battle Network Rockman EXE 3 (Japan) (Rev 1) (Virtual Console)" - rom ( name "Battle Network Rockman EXE 3 (Japan) (Rev 1) (Virtual Console).gba" size 8388608 crc 47ccb9b8 sha1 e2a7c4abba66d17e073281e9ea44b6a5760b53c6 ) + name "Battle Network - Rockman EXE 3 (Japan)" + description "Battle Network - Rockman EXE 3 (Japan)" + rom ( name "Battle Network - Rockman EXE 3 (Japan).gba" size 8388608 crc 1c57724e sha1 2a381543f84dacc0f8310d4516fde0c33b5feca0 ) ) game ( - name "Battle Network Rockman EXE 3 (Japan) (Rev 1)" - description "Battle Network Rockman EXE 3 (Japan) (Rev 1)" - rom ( name "Battle Network Rockman EXE 3 (Japan) (Rev 1).gba" size 8388608 crc e48e6bc9 sha1 87e0ab10541eaaa5e9c01f7fad822a3e1bf52278 ) + name "Battle Network - Rockman EXE 3 (Japan) (Rev 1)" + description "Battle Network - Rockman EXE 3 (Japan) (Rev 1)" + rom ( name "Battle Network - Rockman EXE 3 (Japan) (Rev 1).gba" size 8388608 crc e48e6bc9 sha1 87e0ab10541eaaa5e9c01f7fad822a3e1bf52278 ) ) game ( - name "Battle Network Rockman EXE 3 - Black (Japan) (Promo)" - description "Battle Network Rockman EXE 3 - Black (Japan) (Promo)" - rom ( name "Battle Network Rockman EXE 3 - Black (Japan) (Promo).gba" size 8388608 crc 1f13c41f sha1 ff65af8fea15ecf5a556595efe414d1211a9ab4e ) + name "Battle Network - Rockman EXE 3 - Black (Japan) (Promo)" + description "Battle Network - Rockman EXE 3 - Black (Japan) (Promo)" + rom ( name "Battle Network - Rockman EXE 3 - Black (Japan) (Promo).gba" size 8388608 crc 1f13c41f sha1 ff65af8fea15ecf5a556595efe414d1211a9ab4e ) ) game ( - name "Battle Network Rockman EXE 3 - Black (Japan) (Rev 1)" - description "Battle Network Rockman EXE 3 - Black (Japan) (Rev 1)" - rom ( name "Battle Network Rockman EXE 3 - Black (Japan) (Rev 1).gba" size 8388608 crc fd57493b sha1 e089a2254496a4791666c8122585cb785e3012fc ) + name "Battle Network - Rockman EXE 3 - Black (Japan) (Rev 1) (Virtual Console)" + description "Battle Network - Rockman EXE 3 - Black (Japan) (Rev 1) (Virtual Console)" + rom ( name "Battle Network - Rockman EXE 3 - Black (Japan) (Rev 1) (Virtual Console).gba" size 8388608 crc 93e89735 sha1 42ed01e9c8fdc0ea7c0703c821322bd196c66be4 ) ) game ( - name "Battle Network Rockman EXE 3 - Black (Japan) (Rev 1) (Virtual Console)" - description "Battle Network Rockman EXE 3 - Black (Japan) (Rev 1) (Virtual Console)" - rom ( name "Battle Network Rockman EXE 3 - Black (Japan) (Rev 1) (Virtual Console).gba" size 8388608 crc 93e89735 sha1 42ed01e9c8fdc0ea7c0703c821322bd196c66be4 ) + name "Battle Network - Rockman EXE 3 - Black (Japan) (Rev 1)" + description "Battle Network - Rockman EXE 3 - Black (Japan) (Rev 1)" + rom ( name "Battle Network - Rockman EXE 3 - Black (Japan) (Rev 1).gba" size 8388608 crc fd57493b sha1 e089a2254496a4791666c8122585cb785e3012fc ) ) game ( @@ -2148,6 +2226,12 @@ game ( rom ( name "Bionicle - Matoran Adventures (USA, Europe) (En,Fr,De,Es,It,Nl,Sv,Da).gba" size 4194304 crc daec2264 sha1 a478f5880c484a70a5fdefc42f73aae2eb948168 flags verified ) ) +game ( + name "Bionicle - Maze of Shadows (Europe) (En,De)" + description "Bionicle - Maze of Shadows (Europe) (En,De)" + rom ( name "Bionicle - Maze of Shadows (Europe) (En,De).gba" size 8388608 crc bce2d68e sha1 7ff9811e2bd40b24da02be194213d41a0885aa34 ) +) + game ( name "Bionicle - Maze of Shadows (USA)" description "Bionicle - Maze of Shadows (USA)" @@ -2160,12 +2244,6 @@ game ( rom ( name "Bionicle - Maze of Shadows (Europe) (En,De) (Rev 1).gba" size 8388608 crc 9d66ec5e sha1 430c7dac6f7dd989294a8ac1cfdabd9e74b3e682 ) ) -game ( - name "Bionicle - Maze of Shadows (Europe) (En,De)" - description "Bionicle - Maze of Shadows (Europe) (En,De)" - rom ( name "Bionicle - Maze of Shadows (Europe) (En,De).gba" size 8388608 crc bce2d68e sha1 7ff9811e2bd40b24da02be194213d41a0885aa34 ) -) - game ( name "Bionicle Heroes (USA) (En,Fr,De,Es,It,Da)" description "Bionicle Heroes (USA) (En,Fr,De,Es,It,Da)" @@ -2262,6 +2340,12 @@ game ( rom ( name "Bleach Advance - Kurenai ni Somaru Soul Society (Japan).gba" size 33554432 crc 9de5cd08 sha1 29d24c38d3ec8bbe9d81df2f5ff61c4a2dadcee4 ) ) +game ( + name "Blender Bros. (World) (Aftermarket) (Unl)" + description "Blender Bros. (World) (Aftermarket) (Unl)" + rom ( name "Blender Bros. (World) (Aftermarket) (Unl).gba" size 8388608 crc 440f2f06 sha1 8f9ca62306b7ab56d8da45673d7b7d0eb4c349c5 ) +) + game ( name "Blender Bros. (USA)" description "Blender Bros. (USA)" @@ -2283,7 +2367,7 @@ game ( game ( name "BMX Trick Racer (USA)" description "BMX Trick Racer (USA)" - rom ( name "BMX Trick Racer (USA).gba" size 16777216 crc b6d79476 sha1 3a42d3331e81e92d49e285b36ae1a6ed5db6a2ea ) + rom ( name "BMX Trick Racer (USA).gba" size 16777216 crc b6d79476 sha1 3a42d3331e81e92d49e285b36ae1a6ed5db6a2ea flags verified ) ) game ( @@ -2322,12 +2406,6 @@ game ( rom ( name "Boboboubo Boubobo - Ougi 87.5 Bakuretsu Hanage Shinken (Japan).gba" size 8388608 crc 58105f89 sha1 c93fd22bb10e26cb986161ce1cdc4c2acb38add9 ) ) -game ( - name "Boktai - The Sun Is in Your Hand (USA) (Sample)" - description "Possibly a version given out to press at E3 or an E3 demo" - rom ( name "Boktai - The Sun Is in Your Hand (USA) (Sample).gba" size 16777216 crc cf692572 sha1 f91126cd3a1bf7bf5f770d3a70229171d0d5a6ee ) -) - game ( name "Boktai - The Sun Is in Your Hand (USA)" description "Boktai - The Sun Is in Your Hand (USA)" @@ -2340,6 +2418,12 @@ game ( rom ( name "Boktai - The Sun Is in Your Hand (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc 9686c36b sha1 64f7bf0f0560f6e94da33b549d3206678b29f557 flags verified ) ) +game ( + name "Boktai - The Sun Is in Your Hand (USA) (Sample)" + description "Possibly a version given out to press at E3 or an E3 demo" + rom ( name "Boktai - The Sun Is in Your Hand (USA) (Sample).gba" size 16777216 crc cf692572 sha1 f91126cd3a1bf7bf5f770d3a70229171d0d5a6ee ) +) + game ( name "Boktai 2 - Solar Boy Django (USA)" description "Boktai 2 - Solar Boy Django (USA)" @@ -2445,7 +2529,7 @@ game ( game ( name "Bouken Yuuki Pluster World - Densetsu no Plust Gate (Japan)" description "Bouken Yuuki Pluster World - Densetsu no Plust Gate (Japan)" - rom ( name "Bouken Yuuki Pluster World - Densetsu no Plust Gate (Japan).gba" size 8388608 crc 57438e39 sha1 bb779de3aa9c08b82ffcb3cb2eb70f151a8a3b13 ) + rom ( name "Bouken Yuuki Pluster World - Densetsu no Plust Gate (Japan).gba" size 8388608 crc 57438e39 sha1 bb779de3aa9c08b82ffcb3cb2eb70f151a8a3b13 flags verified ) ) game ( @@ -2595,7 +2679,7 @@ game ( game ( name "Breath of Fire (Europe)" description "Breath of Fire (Europe)" - rom ( name "Breath of Fire (Europe).gba" size 4194304 crc a1c3165d sha1 f47870d25665588d19b75e90d0bd32a759e64918 ) + rom ( name "Breath of Fire (Europe).gba" size 4194304 crc a1c3165d sha1 f47870d25665588d19b75e90d0bd32a759e64918 flags verified ) ) game ( @@ -2790,18 +2874,18 @@ game ( rom ( name "Cabela's Big Game Hunter - 2005 Adventures (USA, Europe).gba" size 4194304 crc bd054567 sha1 aff3c5bc948c2c868be7da8a327aee25beaa027c flags verified ) ) -game ( - name "Caesars Palace Advance - Millennium Gold Edition (USA) (Beta)" - description "Caesars Palace Advance - Millennium Gold Edition (USA) (Beta)" - rom ( name "Caesars Palace Advance - Millennium Gold Edition (USA) (Beta).gba" size 4194304 crc 13dc0731 sha1 1a89cecd9bf89df42ae4ef35d17d437b17e53c80 ) -) - game ( name "Caesars Palace Advance - Millennium Gold Edition (USA, Europe)" description "Caesars Palace Advance - Millennium Gold Edition (USA, Europe)" rom ( name "Caesars Palace Advance - Millennium Gold Edition (USA, Europe).gba" size 8388608 crc 5d54ece5 sha1 cd5dbb8b8361ca5d003ec496a99351020dabc329 ) ) +game ( + name "Caesars Palace Advance - Millennium Gold Edition (USA, Europe) (Beta)" + description "Caesars Palace Advance - Millennium Gold Edition (USA, Europe) (Beta)" + rom ( name "Caesars Palace Advance - Millennium Gold Edition (USA, Europe) (Beta).gba" size 4194304 crc 13dc0731 sha1 1a89cecd9bf89df42ae4ef35d17d437b17e53c80 ) +) + game ( name "Calciobit (Japan)" description "Calciobit (Japan)" @@ -2875,15 +2959,15 @@ game ( ) game ( - name "Care Bears - The Care Quests (Europe) (En,Fr,De,Es,It,Nl,Pt,Da)" - description "Care Bears - The Care Quests (Europe) (En,Fr,De,Es,It,Nl,Pt,Da)" - rom ( name "Care Bears - The Care Quests (Europe) (En,Fr,De,Es,It,Nl,Pt,Da).gba" size 4194304 crc 6111ed1e sha1 606ad547286fdf14cc0fe60e5c34f9db83a059dc ) + name "Care Bears - The Care Quest (Europe) (En,Fr,De,Es,It,Nl,Pt,Da)" + description "Care Bears - The Care Quest (Europe) (En,Fr,De,Es,It,Nl,Pt,Da)" + rom ( name "Care Bears - The Care Quest (Europe) (En,Fr,De,Es,It,Nl,Pt,Da).gba" size 4194304 crc 6111ed1e sha1 606ad547286fdf14cc0fe60e5c34f9db83a059dc ) ) game ( - name "Care Bears - The Care Quests (USA) (En,Fr,Es)" - description "Care Bears - The Care Quests (USA) (En,Fr,Es)" - rom ( name "Care Bears - The Care Quests (USA) (En,Fr,Es).gba" size 4194304 crc f848c327 sha1 750eb05960ace3bd38c6e362a0f6970ac3371964 ) + name "Care Bears - The Care Quest (USA) (En,Fr,Es)" + description "Care Bears - The Care Quest (USA) (En,Fr,Es)" + rom ( name "Care Bears - The Care Quest (USA) (En,Fr,Es).gba" size 4194304 crc f848c327 sha1 750eb05960ace3bd38c6e362a0f6970ac3371964 ) ) game ( @@ -3145,21 +3229,21 @@ game ( ) game ( - name "Celeste Classic (World) (v1.0) (Aftermarket) (Homebrew)" - description "Celeste Classic (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Celeste Classic (World) (v1.0) (Aftermarket) (Homebrew).gba" size 5416932 crc f79b0d53 sha1 756f02396a150698e695ad4afd24445e2af70576 ) + name "Celeste Classic (World) (v1.0) (Aftermarket) (Unl)" + description "Celeste Classic (World) (v1.0) (Aftermarket) (Unl)" + rom ( name "Celeste Classic (World) (v1.0) (Aftermarket) (Unl).gba" size 5416932 crc f79b0d53 sha1 756f02396a150698e695ad4afd24445e2af70576 ) ) game ( - name "Celeste Classic (World) (v1.1) (Aftermarket) (Homebrew)" - description "Celeste Classic (World) (v1.1) (Aftermarket) (Homebrew)" - rom ( name "Celeste Classic (World) (v1.1) (Aftermarket) (Homebrew).gba" size 5418248 crc f5d36ad2 sha1 95a86dff641b11b4c2ed0d0fe8152c33b462d927 ) + name "Celeste Classic (World) (v1.1) (Aftermarket) (Unl)" + description "Celeste Classic (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Celeste Classic (World) (v1.1) (Aftermarket) (Unl).gba" size 5418248 crc f5d36ad2 sha1 95a86dff641b11b4c2ed0d0fe8152c33b462d927 ) ) game ( - name "Celeste Classic (World) (v1.2) (Aftermarket) (Homebrew)" - description "Celeste Classic (World) (v1.2) (Aftermarket) (Homebrew)" - rom ( name "Celeste Classic (World) (v1.2) (Aftermarket) (Homebrew).gba" size 5418424 crc ff0e8ada sha1 08512605e5c02e81a72e24c2c8d4959d8e84e1f4 ) + name "Celeste Classic (World) (v1.2) (Aftermarket) (Unl)" + description "Celeste Classic (World) (v1.2) (Aftermarket) (Unl)" + rom ( name "Celeste Classic (World) (v1.2) (Aftermarket) (Unl).gba" size 5418424 crc ff0e8ada sha1 08512605e5c02e81a72e24c2c8d4959d8e84e1f4 ) ) game ( @@ -3375,7 +3459,7 @@ game ( game ( name "Cinderella - Magical Dreams (USA) (En,Fr,De,Es,It)" description "Cinderella - Magical Dreams (USA) (En,Fr,De,Es,It)" - rom ( name "Cinderella - Magical Dreams (USA) (En,Fr,De,Es,It).gba" size 8388608 crc 22f19169 sha1 1682d023933bf758c78ce2c18b503efd4f0803b9 ) + rom ( name "Cinderella - Magical Dreams (USA) (En,Fr,De,Es,It).gba" size 8388608 crc 22f19169 sha1 1682d023933bf758c78ce2c18b503efd4f0803b9 flags verified ) ) game ( @@ -3423,7 +3507,7 @@ game ( game ( name "Classic NES Series - Dr. Mario (USA, Europe)" description "Classic NES Series - Dr. Mario (USA, Europe)" - rom ( name "Classic NES Series - Dr. Mario (USA, Europe).gba" size 1048576 crc 934e1f1d sha1 fc396f0eae55cf19e573aa322f525427e03d3854 flags verified ) + rom ( name "Classic NES Series - Dr. Mario (USA, Europe).gba" size 1048576 crc 934e1f1d sha1 fc396f0eae55cf19e573aa322f525427e03d3854 ) ) game ( @@ -3645,7 +3729,7 @@ game ( game ( name "Crash Bandicoot - The Huge Adventure (USA)" description "Crash Bandicoot - The Huge Adventure (USA)" - rom ( name "Crash Bandicoot - The Huge Adventure (USA).gba" size 8388608 crc 034d2d4b sha1 5a2651c78bb8a1d707e3c3af1f46fb64e2104198 ) + rom ( name "Crash Bandicoot - The Huge Adventure (USA).gba" size 8388608 crc 034d2d4b sha1 5a2651c78bb8a1d707e3c3af1f46fb64e2104198 flags verified ) ) game ( @@ -3717,7 +3801,7 @@ game ( game ( name "Crash Nitro Kart (Europe) (En,Fr,De,Es,It,Nl)" description "Crash Nitro Kart (Europe) (En,Fr,De,Es,It,Nl)" - rom ( name "Crash Nitro Kart (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 03925772 sha1 91904f5ba5c501b761fc01a48e4efc41ee5b8e41 ) + rom ( name "Crash Nitro Kart (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 03925772 sha1 91904f5ba5c501b761fc01a48e4efc41ee5b8e41 flags verified ) ) game ( @@ -3859,9 +3943,9 @@ game ( ) game ( - name "Cruis'n Velocity (USA) (Beta)" - description "Cruis'n Velocity (USA) (Beta)" - rom ( name "Cruis'n Velocity (USA) (Beta).gba" size 8388608 crc 5436d5da sha1 d1716d4603dd8db7ae8715d5142a60230d17e1d2 ) + name "Cruis'n Velocity (USA, Europe) (Beta)" + description "Cruis'n Velocity (USA, Europe) (Beta)" + rom ( name "Cruis'n Velocity (USA, Europe) (Beta).gba" size 8388608 crc 5436d5da sha1 d1716d4603dd8db7ae8715d5142a60230d17e1d2 ) ) game ( @@ -3900,18 +3984,18 @@ game ( rom ( name "CT Special Forces - Back to Hell (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 863cbf31 sha1 b782dac774932d93c313b122ca2c7c072ca84840 ) ) -game ( - name "CT Special Forces - Bioterror (Europe) (En,Fr,De,Es,It,Nl)" - description "CT Special Forces - Bioterror (Europe) (En,Fr,De,Es,It,Nl)" - rom ( name "CT Special Forces - Bioterror (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 7dc40eda sha1 0ac478e4568f5886fce546b903fd50424a059438 ) -) - game ( name "CT Special Forces 2 - Back in the Trenches (USA) (En,Fr,De,Es,It,Nl)" description "CT Special Forces 2 - Back in the Trenches (USA) (En,Fr,De,Es,It,Nl)" rom ( name "CT Special Forces 2 - Back in the Trenches (USA) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc d2b58a33 sha1 cedca3aac39309dc122909d11457e306c81e16be ) ) +game ( + name "CT Special Forces 3 - Bioterror (Europe) (En,Fr,De,Es,It,Nl)" + description "CT Special Forces 3 - Bioterror (Europe) (En,Fr,De,Es,It,Nl)" + rom ( name "CT Special Forces 3 - Bioterror (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 7dc40eda sha1 0ac478e4568f5886fce546b903fd50424a059438 ) +) + game ( name "Cubix - Robots for Everyone - Clash 'N Bash (USA)" description "Cubix - Robots for Everyone - Clash 'N Bash (USA)" @@ -3948,6 +4032,12 @@ game ( rom ( name "Dai-mahjong. (Japan).gba" size 4194304 crc c6ccca05 sha1 35e4c69a6a30362d873e3383942f86932a216ecf ) ) +game ( + name "Daigassou! Band-Brothers - Request Selection (Japan) (DS Expansion Cartridge)" + description "Daigassou! Band-Brothers - Request Selection (Japan) (DS Expansion Cartridge)" + rom ( name "Daigassou! Band-Brothers - Request Selection (Japan) (DS Expansion Cartridge).gba" size 1048576 crc 374de320 sha1 5cb5af28b3b6a0fd7a0bff4a800f28c9e3727194 ) +) + game ( name "Daisenryaku for Game Boy Advance (Japan)" description "Daisenryaku for Game Boy Advance (Japan)" @@ -4011,7 +4101,7 @@ game ( game ( name "Daredevil (USA, Europe)" description "Daredevil (USA, Europe)" - rom ( name "Daredevil (USA, Europe).gba" size 4194304 crc d438347e sha1 db2bb397b47f1ff247dad5cfa6e35866b1048a7b ) + rom ( name "Daredevil (USA, Europe).gba" size 4194304 crc d438347e sha1 db2bb397b47f1ff247dad5cfa6e35866b1048a7b flags verified ) ) game ( @@ -4065,7 +4155,7 @@ game ( game ( name "Dave Mirra Freestyle BMX 3 (USA, Europe)" description "Dave Mirra Freestyle BMX 3 (USA, Europe)" - rom ( name "Dave Mirra Freestyle BMX 3 (USA, Europe).gba" size 8388608 crc 8d8f26a5 sha1 dcce47b536ace90a918d94d06698c92538d7e4d1 ) + rom ( name "Dave Mirra Freestyle BMX 3 (USA, Europe).gba" size 8388608 crc 8d8f26a5 sha1 dcce47b536ace90a918d94d06698c92538d7e4d1 flags verified ) ) game ( @@ -4149,7 +4239,7 @@ game ( game ( name "DemiKids - Light Version (USA)" description "DemiKids - Light Version (USA)" - rom ( name "DemiKids - Light Version (USA).gba" size 8388608 crc dc4357c4 sha1 8bc80eb5f08bfa83597483390b18092d9a12700d ) + rom ( name "DemiKids - Light Version (USA).gba" size 8388608 crc dc4357c4 sha1 8bc80eb5f08bfa83597483390b18092d9a12700d flags verified ) ) game ( @@ -4269,7 +4359,7 @@ game ( game ( name "Di Gi Charat - DigiCommunication (Japan)" description "Di Gi Charat - DigiCommunication (Japan)" - rom ( name "Di Gi Charat - DigiCommunication (Japan).gba" size 8388608 crc a1288427 sha1 35f9fa0305de3382aa6fb01f14baa9419ffa5349 ) + rom ( name "Di Gi Charat - DigiCommunication (Japan).gba" size 8388608 crc a1288427 sha1 35f9fa0305de3382aa6fb01f14baa9419ffa5349 flags verified ) ) game ( @@ -4548,6 +4638,18 @@ game ( rom ( name "DK - King of Swing (USA) (Demo) (Kiosk).gba" size 8388608 crc 049626d1 sha1 d704ea9311c6ac9773bb70181de86ba0843583fd ) ) +game ( + name "Dog Trainer (Europe) (DS Cheat Cartridge) (Unl)" + description "Dog Trainer (Europe) (DS Cheat Cartridge) (Unl)" + rom ( name "Dog Trainer (Europe) (DS Cheat Cartridge) (Unl).gba" size 1048576 crc 6b3961fb sha1 6994b43605a14f64b7b1ad2f4ba93f216342b9e1 ) +) + +game ( + name "Dog Trainer 2 (Europe) (DS Cheat Cartridge) (Unl)" + description "Dog Trainer 2 (Europe) (DS Cheat Cartridge) (Unl)" + rom ( name "Dog Trainer 2 (Europe) (DS Cheat Cartridge) (Unl).gba" size 1048576 crc b2100fed sha1 812475fcb025a7a216f4544605e9110759d8b02e ) +) + game ( name "Dogz (USA)" description "Dogz (USA)" @@ -4656,6 +4758,12 @@ game ( rom ( name "Domo-kun no Fushigi Television (Japan).gba" size 8388608 crc e743c611 sha1 c5c70840c6222c52a2e814d106ab1f2478cd5cab ) ) +game ( + name "Don-chan Puzzle - Hanabi de Doon! Advance (Japan)" + description "Don-chan Puzzle - Hanabi de Doon! Advance (Japan)" + rom ( name "Don-chan Puzzle - Hanabi de Doon! Advance (Japan).gba" size 8388608 crc 500922e8 sha1 5598e6143dd0dab1793507317624973557f35568 ) +) + game ( name "Donald Duck Advance (Europe) (En,Fr,De,Es,It)" description "Donald Duck Advance (Europe) (En,Fr,De,Es,It)" @@ -4674,30 +4782,24 @@ game ( rom ( name "Donald Duck Advance (USA).gba" size 8388608 crc 936daab2 sha1 020081ad9dda023adf604b790ddfc95fcf2a7835 ) ) -game ( - name "Donchan Puzzle - Hanabi de Dohn! Advance (Japan)" - description "Donchan Puzzle - Hanabi de Dohn! Advance (Japan)" - rom ( name "Donchan Puzzle - Hanabi de Dohn! Advance (Japan).gba" size 8388608 crc 500922e8 sha1 5598e6143dd0dab1793507317624973557f35568 ) -) - game ( name "Donkey Kong 2 (USA) (Unl)" description "Donkey Kong 2 (USA) (Unl)" rom ( name "Donkey Kong 2 (USA) (Unl).gba" size 33554432 crc 006a6afa sha1 b62e1625416f67981cb834c8428603f7800fee5b ) ) -game ( - name "Donkey Kong Country (Europe) (En,Fr,De,Es,It)" - description "Donkey Kong Country (Europe) (En,Fr,De,Es,It)" - rom ( name "Donkey Kong Country (Europe) (En,Fr,De,Es,It).gba" size 8388608 crc 41d277fe sha1 8995f0be99a9cff66474a8975b8499bd69fb4c45 flags verified ) -) - game ( name "Donkey Kong Country (USA)" description "Donkey Kong Country (USA)" rom ( name "Donkey Kong Country (USA).gba" size 8388608 crc 12f7a968 sha1 fcc62356a3b7157ca7dda1398c9bf1af1dd31265 flags verified ) ) +game ( + name "Donkey Kong Country (Europe) (En,Fr,De,Es,It)" + description "Donkey Kong Country (Europe) (En,Fr,De,Es,It)" + rom ( name "Donkey Kong Country (Europe) (En,Fr,De,Es,It).gba" size 8388608 crc 41d277fe sha1 8995f0be99a9cff66474a8975b8499bd69fb4c45 flags verified ) +) + game ( name "Donkey Kong Country 2 (Europe) (En,Fr,De,Es,It)" description "Donkey Kong Country 2 (Europe) (En,Fr,De,Es,It)" @@ -4707,7 +4809,7 @@ game ( game ( name "Donkey Kong Country 2 (USA)" description "Donkey Kong Country 2 (USA)" - rom ( name "Donkey Kong Country 2 (USA).gba" size 8388608 crc 11417fc1 sha1 b0a4d59447c8d7c321bea4dc7253b0f581129ede ) + rom ( name "Donkey Kong Country 2 (USA).gba" size 8388608 crc 11417fc1 sha1 b0a4d59447c8d7c321bea4dc7253b0f581129ede flags verified ) ) game ( @@ -4728,6 +4830,12 @@ game ( rom ( name "Donkey Kong Country 3 (USA).gba" size 16777216 crc fe03e5af sha1 c50982b4c26e25ba3538be97b585d95737d7ade7 flags verified ) ) +game ( + name "Donsol (World) (Aftermarket) (Unl)" + description "Donsol (World) (Aftermarket) (Unl)" + rom ( name "Donsol (World) (Aftermarket) (Unl).gba" size 52520 crc aec144b3 sha1 751675b0d77c341f2265b0edd38858b0dbdb7b50 ) +) + game ( name "Doom (USA, Europe)" description "Doom (USA, Europe)" @@ -4737,7 +4845,7 @@ game ( game ( name "Doom II (USA)" description "Doom II (USA)" - rom ( name "Doom II (USA).gba" size 16777216 crc c885d9e9 sha1 2feeffc96386cf2cc0b2076928b010f18e9e9748 ) + rom ( name "Doom II (USA).gba" size 16777216 crc c885d9e9 sha1 2feeffc96386cf2cc0b2076928b010f18e9e9748 flags verified ) ) game ( @@ -4920,18 +5028,18 @@ game ( rom ( name "Dragon Ball - Advanced Adventure (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc 6c135820 sha1 e49ae836b14f84dc8cd817bf912fcdce82d8a587 ) ) -game ( - name "Dragon Ball - Advanced Adventure (Europe) (En,Fr,De,Es,It) (Beta) (2005-02-25)" - description "Dragon Ball - Advanced Adventure (Europe) (En,Fr,De,Es,It) (Beta) (2005-02-25)" - rom ( name "Dragon Ball - Advanced Adventure (Europe) (En,Fr,De,Es,It) (Beta) (2005-02-25).gba" size 16777216 crc 2b136906 sha1 fe190755f05994f1c796286ad767d7da49c36385 ) -) - game ( name "Dragon Ball - Advanced Adventure (USA)" description "Dragon Ball - Advanced Adventure (USA)" rom ( name "Dragon Ball - Advanced Adventure (USA).gba" size 16777216 crc 7d7306df sha1 1338584d8cfa57402603197e65d2e2ff0184d24f ) ) +game ( + name "Dragon Ball - Advanced Adventure (Europe) (En,Fr,De,Es,It) (Beta) (2005-02-25)" + description "Dragon Ball - Advanced Adventure (Europe) (En,Fr,De,Es,It) (Beta) (2005-02-25)" + rom ( name "Dragon Ball - Advanced Adventure (Europe) (En,Fr,De,Es,It) (Beta) (2005-02-25).gba" size 16777216 crc 2b136906 sha1 fe190755f05994f1c796286ad767d7da49c36385 ) +) + game ( name "Dragon Ball GT - Transformation (USA)" description "Dragon Ball GT - Transformation (USA)" @@ -4995,7 +5103,7 @@ game ( game ( name "Dragon Ball Z - Taiketsu (USA)" description "Dragon Ball Z - Taiketsu (USA)" - rom ( name "Dragon Ball Z - Taiketsu (USA).gba" size 8388608 crc 64ec07e7 sha1 1fac4e187b8cf0aeb218ca307f10955f432d258d ) + rom ( name "Dragon Ball Z - Taiketsu (USA).gba" size 8388608 crc 64ec07e7 sha1 1fac4e187b8cf0aeb218ca307f10955f432d258d flags verified ) ) game ( @@ -6067,9 +6175,9 @@ game ( ) game ( - name "Fantastic Maerchen - Cake-ya-san Monogatari (Japan)" - description "Fantastic Maerchen - Cake-ya-san Monogatari (Japan)" - rom ( name "Fantastic Maerchen - Cake-ya-san Monogatari (Japan).gba" size 8388608 crc ec60d573 sha1 826c247ef4e5b831aa0d75e97cfdfe06c48ad7af ) + name "Fantastic Maerchen - Cake-ya-san Monogatari + Doubutsu Chara Navi Uranai - Kosei Shinri Gaku (Japan)" + description "Fantastic Maerchen - Cake-ya-san Monogatari + Doubutsu Chara Navi Uranai - Kosei Shinri Gaku (Japan)" + rom ( name "Fantastic Maerchen - Cake-ya-san Monogatari + Doubutsu Chara Navi Uranai - Kosei Shinri Gaku (Japan).gba" size 8388608 crc ec60d573 sha1 826c247ef4e5b831aa0d75e97cfdfe06c48ad7af ) ) game ( @@ -6085,9 +6193,9 @@ game ( ) game ( - name "Fear Factor Unleashed (USA)" - description "Fear Factor Unleashed (USA)" - rom ( name "Fear Factor Unleashed (USA).gba" size 8388608 crc 1289639c sha1 bf933c51bdcb52ae54d518e80129c687b751f836 ) + name "Fear Factor - Unleashed (USA)" + description "Fear Factor - Unleashed (USA)" + rom ( name "Fear Factor - Unleashed (USA).gba" size 8388608 crc 1289639c sha1 bf933c51bdcb52ae54d518e80129c687b751f836 ) ) game ( @@ -6441,7 +6549,7 @@ game ( game ( name "Fire Emblem - Rekka no Ken (Japan)" description "Fire Emblem - Rekka no Ken (Japan)" - rom ( name "Fire Emblem - Rekka no Ken (Japan).gba" size 16777216 crc f0c10e72 sha1 037702b1febd5c9535262165bf030551d153de81 ) + rom ( name "Fire Emblem - Rekka no Ken (Japan).gba" size 16777216 crc f0c10e72 sha1 037702b1febd5c9535262165bf030551d153de81 flags verified ) ) game ( @@ -7110,6 +7218,12 @@ game ( rom ( name "Ghost Trap (Japan).gba" size 8388608 crc 81ea54e2 sha1 00efb5ed50127f91e2a2827926cf2d4491e4b1b3 ) ) +game ( + name "Glacia Dungeon (World) (En,Es,Ru,Ro) (v1.5.2) (Aftermarket) (Unl)" + description "Glacia Dungeon (World) (En,Es,Ru,Ro) (v1.5.2) (Aftermarket) (Unl)" + rom ( name "Glacia Dungeon (World) (En,Es,Ru,Ro) (v1.5.2) (Aftermarket) (Unl).gba" size 1210608 crc ab5ae65b sha1 e35037ff9cc24f8a7043d9a9d378e86d2ef80f9b ) +) + game ( name "Global Star - Sudoku Fever (USA)" description "Global Star - Sudoku Fever (USA)" @@ -7167,7 +7281,7 @@ game ( game ( name "Golden Sun (France)" description "Golden Sun (France)" - rom ( name "Golden Sun (France).gba" size 8388608 crc f6521161 sha1 42f3b262c16cfc5bde1fa2b25016fb74046de1b3 ) + rom ( name "Golden Sun (France).gba" size 8388608 crc f6521161 sha1 42f3b262c16cfc5bde1fa2b25016fb74046de1b3 flags verified ) ) game ( @@ -7197,7 +7311,7 @@ game ( game ( name "Golden Sun - L'Age Perdu (France)" description "Golden Sun - L'Age Perdu (France)" - rom ( name "Golden Sun - L'Age Perdu (France).gba" size 16777216 crc 1090bd33 sha1 f14855c0f8c87a95cc52189e8e3b2bd9df186322 ) + rom ( name "Golden Sun - L'Age Perdu (France).gba" size 16777216 crc 1090bd33 sha1 f14855c0f8c87a95cc52189e8e3b2bd9df186322 flags verified ) ) game ( @@ -7326,18 +7440,18 @@ game ( rom ( name "Gremlins - Stripe vs Gizmo (Europe) (En,Fr,De,Es,It,Pt) (Beta).gba" size 4194304 crc 68ae6bbb sha1 6aa43d0624af03214473f329fda6334dc9c63009 ) ) -game ( - name "Gremlins - Stripe vs Gizmo (USA)" - description "Gremlins - Stripe vs Gizmo (USA)" - rom ( name "Gremlins - Stripe vs Gizmo (USA).gba" size 4194304 crc 5e72899a sha1 ee32e704598d2b6a21b3db271f9cf94578b94744 ) -) - game ( name "Gremlins - Stripe vs Gizmo (Europe) (En,Fr,De,Es,It,Pt)" description "Gremlins - Stripe vs Gizmo (Europe) (En,Fr,De,Es,It,Pt)" rom ( name "Gremlins - Stripe vs Gizmo (Europe) (En,Fr,De,Es,It,Pt).gba" size 4194304 crc b6225186 sha1 a2c4bf97785e717ef45e4b73a6c66a2d4ae18192 ) ) +game ( + name "Gremlins - Stripe vs Gizmo (USA)" + description "Gremlins - Stripe vs Gizmo (USA)" + rom ( name "Gremlins - Stripe vs Gizmo (USA).gba" size 4194304 crc 5e72899a sha1 ee32e704598d2b6a21b3db271f9cf94578b94744 ) +) + game ( name "Grim Adventures of Billy & Mandy, The (USA)" description "Grim Adventures of Billy & Mandy, The (USA)" @@ -7417,15 +7531,9 @@ game ( ) game ( - name "Guilty Gear X - Advance Edition (Japan)" - description "Guilty Gear X - Advance Edition (Japan)" - rom ( name "Guilty Gear X - Advance Edition (Japan).gba" size 8388608 crc 160903ee sha1 0801b7e39462527d5b650e57fcce908711258a0c ) -) - -game ( - name "Guilty Gear X - Advance Edition (Europe)" - description "Guilty Gear X - Advance Edition (Europe)" - rom ( name "Guilty Gear X - Advance Edition (Europe).gba" size 8388608 crc ba95861d sha1 8236a650a18dfedc22da7c00b5affd3e752ec5de ) + name "Guilty Gear X - Advance Edition (Japan) (Beta)" + description "Guilty Gear X - Advance Edition (Japan) (Beta)" + rom ( name "Guilty Gear X - Advance Edition (Japan) (Beta).gba" size 8388608 crc 4506ada8 sha1 85915ecf10ce73afe9fffbbc8cd7a449dfe802c4 ) ) game ( @@ -7435,9 +7543,15 @@ game ( ) game ( - name "Guilty Gear X - Advance Edition (Japan) (Beta)" - description "Guilty Gear X - Advance Edition (Japan) (Beta)" - rom ( name "Guilty Gear X - Advance Edition (Japan) (Beta).gba" size 8388608 crc 4506ada8 sha1 85915ecf10ce73afe9fffbbc8cd7a449dfe802c4 ) + name "Guilty Gear X - Advance Edition (Japan)" + description "Guilty Gear X - Advance Edition (Japan)" + rom ( name "Guilty Gear X - Advance Edition (Japan).gba" size 8388608 crc 160903ee sha1 0801b7e39462527d5b650e57fcce908711258a0c ) +) + +game ( + name "Guilty Gear X - Advance Edition (Europe)" + description "Guilty Gear X - Advance Edition (Europe)" + rom ( name "Guilty Gear X - Advance Edition (Europe).gba" size 8388608 crc ba95861d sha1 8236a650a18dfedc22da7c00b5affd3e752ec5de ) ) game ( @@ -7530,12 +7644,6 @@ game ( rom ( name "Gyakuten Saiban 3 (Japan).gba" size 8388608 crc 51b6cf22 sha1 70944b396da3f9ce039cc96bc1661826c37b0aa2 flags verified ) ) -game ( - name "Ha Li Bo Te IV (Taiwan) (Unl)" - description "Ha Li Bo Te IV (Taiwan) (Unl)" - rom ( name "Ha Li Bo Te IV (Taiwan) (Unl).gba" size 33554432 crc 892347d4 sha1 80c0a695e23fa5d867da0e39203a52adaab2601d ) -) - game ( name "Hachiemon (Japan)" description "Hachiemon (Japan)" @@ -7560,6 +7668,12 @@ game ( rom ( name "Hajime no Ippo - The Fighting! (Japan).gba" size 8388608 crc 782dc7eb sha1 4b39dde92aa2e26433a9f0c90ca6235a508ba87c ) ) +game ( + name "Hali Bote IV (Taiwan) (Unl)" + description "Hali Bote IV (Taiwan) (Unl)" + rom ( name "Hali Bote IV (Taiwan) (Unl).gba" size 33554432 crc 892347d4 sha1 80c0a695e23fa5d867da0e39203a52adaab2601d ) +) + game ( name "Hamepane - Tokyo Mew Mew (Japan)" description "Hamepane - Tokyo Mew Mew (Japan)" @@ -7629,7 +7743,7 @@ game ( game ( name "Hamtaro - Ham-Ham Heartbreak (Europe) (En,Fr,De,Es,It)" description "Hamtaro - Ham-Ham Heartbreak (Europe) (En,Fr,De,Es,It)" - rom ( name "Hamtaro - Ham-Ham Heartbreak (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc ecbd80ea sha1 b016328e4880f0413b9335c95758ba9c09e53710 ) + rom ( name "Hamtaro - Ham-Ham Heartbreak (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc ecbd80ea sha1 b016328e4880f0413b9335c95758ba9c09e53710 flags verified ) ) game ( @@ -7944,18 +8058,18 @@ game ( rom ( name "Hikaru no Go (Japan) (Rev 1).gba" size 8388608 crc 53116fd7 sha1 c61227f6ccfdc196ef810c1986322b0b1988e508 ) ) -game ( - name "Hikaru no Go (Japan) (Demo) (Promo)" - description "Hikaru no Go (Japan) (Demo) (Promo)" - rom ( name "Hikaru no Go (Japan) (Demo) (Promo).gba" size 4194304 crc 4955fa1b sha1 636c3247f455263c41fc0dee85fd4532246b60c2 ) -) - game ( name "Hikaru no Go (Japan)" description "Hikaru no Go (Japan)" rom ( name "Hikaru no Go (Japan).gba" size 8388608 crc 5a6c7537 sha1 a96299f55026b959ed806bff0fb4a440d33e65af ) ) +game ( + name "Hikaru no Go - Taikenban (Japan) (Demo)" + description "Hikaru no Go - Taikenban (Japan) (Demo)" + rom ( name "Hikaru no Go - Taikenban (Japan) (Demo).gba" size 4194304 crc 4955fa1b sha1 636c3247f455263c41fc0dee85fd4532246b60c2 ) +) + game ( name "Hikaru no Go 2 (Japan)" description "Hikaru no Go 2 (Japan)" @@ -8035,9 +8149,9 @@ game ( ) game ( - name "Hoshi no Kirby - Kagami no Daimeikyuu (Japan) (Virtual Console)" - description "Hoshi no Kirby - Kagami no Daimeikyuu (Japan) (Virtual Console)" - rom ( name "Hoshi no Kirby - Kagami no Daimeikyuu (Japan) (Virtual Console).gba" size 16777216 crc 3da3603b sha1 700006a8b919d7ee4b7dd972dbf6c429d1adca71 ) + name "Hoshi no Kirby - Kagami no Daimeikyuu (Japan) (Rev 1) (Virtual Console)" + description "Hoshi no Kirby - Kagami no Daimeikyuu (Japan) (Rev 1) (Virtual Console)" + rom ( name "Hoshi no Kirby - Kagami no Daimeikyuu (Japan) (Rev 1) (Virtual Console).gba" size 16777216 crc 3da3603b sha1 700006a8b919d7ee4b7dd972dbf6c429d1adca71 ) ) game ( @@ -8055,7 +8169,7 @@ game ( game ( name "Hot Potato! (USA)" description "Hot Potato! (USA)" - rom ( name "Hot Potato! (USA).gba" size 4194304 crc 5acb7a95 sha1 6d07c27f7d7858f177cf3c1466ea5a8858df4f92 ) + rom ( name "Hot Potato! (USA).gba" size 4194304 crc 5acb7a95 sha1 6d07c27f7d7858f177cf3c1466ea5a8858df4f92 flags verified ) ) game ( @@ -8262,6 +8376,18 @@ game ( rom ( name "Ignition Collection - Volume 1 (Europe).gba" size 16777216 crc 164a75ac sha1 62e598f9c6ea47626954619295107955ad7c371a ) ) +game ( + name "IK+ (USA)" + description "IK+ (USA)" + rom ( name "IK+ (USA).gba" size 4194304 crc 31c4f03b sha1 99075504699400430e9817feb4192a6e153fbc9a ) +) + +game ( + name "IK+ (Europe)" + description "IK+ (Europe)" + rom ( name "IK+ (Europe).gba" size 4194304 crc 1052a0ea sha1 844395914641af431c58bbe82ab2513c5f8a60ce ) +) + game ( name "Incredibili, Gli - Una 'Normale' Famiglia di Supereroi (Italy)" description "Incredibili, Gli - Una 'Normale' Famiglia di Supereroi (Italy)" @@ -8340,18 +8466,6 @@ game ( rom ( name "International Karate Advanced (Europe).gba" size 4194304 crc d33a14af sha1 bb8b12d0a446ed097fb74a9a4496980fb65164ef flags verified ) ) -game ( - name "International Karate Plus (Europe)" - description "International Karate Plus (Europe)" - rom ( name "International Karate Plus (Europe).gba" size 4194304 crc 1052a0ea sha1 844395914641af431c58bbe82ab2513c5f8a60ce ) -) - -game ( - name "International Karate Plus (USA)" - description "International Karate Plus (USA)" - rom ( name "International Karate Plus (USA).gba" size 4194304 crc 31c4f03b sha1 99075504699400430e9817feb4192a6e153fbc9a ) -) - game ( name "International Superstar Soccer (Europe)" description "International Superstar Soccer (Europe)" @@ -8401,9 +8515,9 @@ game ( ) game ( - name "Iridion 3D (World) (Aftermarket) (Unl)" - description "Iridion 3D (World) (Aftermarket) (Unl)" - rom ( name "Iridion 3D (World) (Aftermarket) (Unl).gba" size 4194304 crc 4c7ebe42 sha1 6f1c77ab88351d2d50da412e4788fc7ca8a6714d ) + name "Iridion 3D (USA) (Aftermarket) (Unl)" + description "Iridion 3D (USA) (Aftermarket) (Unl)" + rom ( name "Iridion 3D (USA) (Aftermarket) (Unl).gba" size 4194304 crc 4c7ebe42 sha1 6f1c77ab88351d2d50da412e4788fc7ca8a6714d ) ) game ( @@ -8431,9 +8545,9 @@ game ( ) game ( - name "Iridion II (World) (Aftermarket) (Unl)" - description "Iridion II (World) (Aftermarket) (Unl)" - rom ( name "Iridion II (World) (Aftermarket) (Unl).gba" size 8388608 crc b371f070 sha1 cc788b38a047ff5ec8c445ce11efcd1852ad7c4d ) + name "Iridion II (USA) (Aftermarket) (Unl)" + description "Iridion II (USA) (Aftermarket) (Unl)" + rom ( name "Iridion II (USA) (Aftermarket) (Unl).gba" size 8388608 crc b371f070 sha1 cc788b38a047ff5ec8c445ce11efcd1852ad7c4d ) ) game ( @@ -8544,12 +8658,6 @@ game ( rom ( name "JGTO Kounin Golf Master Mobile - Japan Golf Tour Game (Japan).gba" size 8388608 crc b863dae1 sha1 975cff59783cd8f5ec30caaa9e6bef130a6e3529 ) ) -game ( - name "Ji Xie Ren Da Zhan - Zhong Jie Ban (Taiwan) (Unl)" - description "Ji Xie Ren Da Zhan - Zhong Jie Ban (Taiwan) (Unl)" - rom ( name "Ji Xie Ren Da Zhan - Zhong Jie Ban (Taiwan) (Unl).gba" size 8388608 crc 81462aba sha1 75e94c4b91638dcb82a6a6778394ec8dc7940091 ) -) - game ( name "Jikkyou World Soccer Pocket (Japan)" description "Jikkyou World Soccer Pocket (Japan)" @@ -8563,15 +8671,15 @@ game ( ) game ( - name "Jimmy Neutron Boy Genius (USA)" - description "Jimmy Neutron Boy Genius (USA)" - rom ( name "Jimmy Neutron Boy Genius (USA).gba" size 4194304 crc d3ee0c51 sha1 31ed7659e2e072d1c00baf95c0ee9c770e949900 ) + name "Jimmy Neutron - Boy Genius (USA)" + description "Jimmy Neutron - Boy Genius (USA)" + rom ( name "Jimmy Neutron - Boy Genius (USA).gba" size 4194304 crc d3ee0c51 sha1 31ed7659e2e072d1c00baf95c0ee9c770e949900 ) ) game ( - name "Jimmy Neutron Boy Genius (Europe) (En,Fr,De,Es)" - description "Jimmy Neutron Boy Genius (Europe) (En,Fr,De,Es)" - rom ( name "Jimmy Neutron Boy Genius (Europe) (En,Fr,De,Es).gba" size 4194304 crc a9423234 sha1 0923b6186739ba697627822ebce04edaa721a70f ) + name "Jimmy Neutron - Boy Genius (Europe) (En,Fr,De,Es)" + description "Jimmy Neutron - Boy Genius (Europe) (En,Fr,De,Es)" + rom ( name "Jimmy Neutron - Boy Genius (Europe) (En,Fr,De,Es).gba" size 4194304 crc a9423234 sha1 0923b6186739ba697627822ebce04edaa721a70f ) ) game ( @@ -8604,6 +8712,12 @@ game ( rom ( name "Jisu F-Zero Weilai Saiche (China).gba" size 4194304 crc 0e5c38b7 sha1 2bf6622398655a16a5d6b3d94241b72e63a71de9 ) ) +game ( + name "Jixie Ren Dazhan - Zhongjie Ban (Taiwan) (Unl)" + description "Jixie Ren Dazhan - Zhongjie Ban (Taiwan) (Unl)" + rom ( name "Jixie Ren Dazhan - Zhongjie Ban (Taiwan) (Unl).gba" size 8388608 crc 81462aba sha1 75e94c4b91638dcb82a6a6778394ec8dc7940091 ) +) + game ( name "Jonny Moseley Mad Trix (USA, Europe) (En,Fr,De,Es,It)" description "Jonny Moseley Mad Trix (USA, Europe) (En,Fr,De,Es,It)" @@ -8631,7 +8745,7 @@ game ( game ( name "Jungle Book 2, The (Europe) (En,Fr,De,Es,It,Nl)" description "Jungle Book 2, The (Europe) (En,Fr,De,Es,It,Nl)" - rom ( name "Jungle Book 2, The (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 9898258b sha1 4dd4fbf9ecd755c37dfb5f6eb0094873c72aabc2 ) + rom ( name "Jungle Book 2, The (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 9898258b sha1 4dd4fbf9ecd755c37dfb5f6eb0094873c72aabc2 flags verified ) ) game ( @@ -8779,15 +8893,15 @@ game ( ) game ( - name "Kao the Kangaroo (USA) (En,Fr,De,Es,It,Nl)" - description "Kao the Kangaroo (USA) (En,Fr,De,Es,It,Nl)" - rom ( name "Kao the Kangaroo (USA) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc f6667b3e sha1 772d323145cb7707cb8792bdfd5c7ef5e1e27c46 ) + name "KAO the Kangaroo (USA) (En,Fr,De,Es,It,Nl)" + description "KAO the Kangaroo (USA) (En,Fr,De,Es,It,Nl)" + rom ( name "KAO the Kangaroo (USA) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc f6667b3e sha1 772d323145cb7707cb8792bdfd5c7ef5e1e27c46 ) ) game ( - name "Kao the Kangaroo (Europe) (En,Fr,De,Es,It,Nl)" - description "Kao the Kangaroo (Europe) (En,Fr,De,Es,It,Nl)" - rom ( name "Kao the Kangaroo (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc 51e7522c sha1 5a337fcc321eaa0c350644c026767824add338f3 ) + name "KAO the Kangaroo (Europe) (En,Fr,De,Es,It,Nl)" + description "KAO the Kangaroo (Europe) (En,Fr,De,Es,It,Nl)" + rom ( name "KAO the Kangaroo (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc 51e7522c sha1 5a337fcc321eaa0c350644c026767824add338f3 ) ) game ( @@ -8799,7 +8913,7 @@ game ( game ( name "Karnaaj Rally (USA, Europe)" description "Karnaaj Rally (USA, Europe)" - rom ( name "Karnaaj Rally (USA, Europe).gba" size 8388608 crc 63fe3dfe sha1 31bb73186bb98f3d2b80089490f2eeca2861fe5e ) + rom ( name "Karnaaj Rally (USA, Europe).gba" size 8388608 crc 63fe3dfe sha1 31bb73186bb98f3d2b80089490f2eeca2861fe5e flags verified ) ) game ( @@ -8877,7 +8991,7 @@ game ( game ( name "Kelly Slater's Pro Surfer (USA, Europe)" description "Kelly Slater's Pro Surfer (USA, Europe)" - rom ( name "Kelly Slater's Pro Surfer (USA, Europe).gba" size 8388608 crc 31f85dbe sha1 73328bfd274d1ec77c9a67351c026e22f09eeff5 ) + rom ( name "Kelly Slater's Pro Surfer (USA, Europe).gba" size 8388608 crc 31f85dbe sha1 73328bfd274d1ec77c9a67351c026e22f09eeff5 flags verified ) ) game ( @@ -9201,7 +9315,7 @@ game ( game ( name "Klonoa - Empire of Dreams (USA)" description "Klonoa - Empire of Dreams (USA)" - rom ( name "Klonoa - Empire of Dreams (USA).gba" size 4194304 crc f74e1036 sha1 a0a298d9dba1ba15d04a42fc2eb35893d1a9569b ) + rom ( name "Klonoa - Empire of Dreams (USA).gba" size 4194304 crc f74e1036 sha1 a0a298d9dba1ba15d04a42fc2eb35893d1a9569b flags verified ) ) game ( @@ -9396,6 +9510,12 @@ game ( rom ( name "Konjiki no Gashbell!! - Unare! Yuujou no Zakeru 2 (Japan).gba" size 16777216 crc b7827f20 sha1 854a76c49f6e83d8ee9dd42098ce1fc7236af855 flags verified ) ) +game ( + name "Konjiki no Gashbell!! - Unare! Yuujou no Zakeru 2 (Japan) (Rev 1)" + description "Konjiki no Gashbell!! - Unare! Yuujou no Zakeru 2 (Japan) (Rev 1)" + rom ( name "Konjiki no Gashbell!! - Unare! Yuujou no Zakeru 2 (Japan) (Rev 1).gba" size 16777216 crc f40e19a9 sha1 a8a68c714497c8ba68ba47636d8156182a5c3b7c ) +) + game ( name "Konjiki no Gashbell!! The Card Battle for GBA (Japan)" description "Konjiki no Gashbell!! The Card Battle for GBA (Japan)" @@ -9409,9 +9529,9 @@ game ( ) game ( - name "Koro Koro Puzzle - Happy Panechu! (Japan)" - description "Koro Koro Puzzle - Happy Panechu! (Japan)" - rom ( name "Koro Koro Puzzle - Happy Panechu! (Japan).gba" size 4194304 crc 0bfe46e9 sha1 40cb751d119a49be0cd44cf0491c93ebc8795ef0 ) + name "Korokoro Puzzle - Happy Panecchu! (Japan)" + description "Korokoro Puzzle - Happy Panecchu! (Japan)" + rom ( name "Korokoro Puzzle - Happy Panecchu! (Japan).gba" size 4194304 crc 0bfe46e9 sha1 40cb751d119a49be0cd44cf0491c93ebc8795ef0 ) ) game ( @@ -9451,9 +9571,9 @@ game ( ) game ( - name "Koutetsu Teikoku from HOT-B (Japan)" - description "Koutetsu Teikoku from HOT-B (Japan)" - rom ( name "Koutetsu Teikoku from HOT-B (Japan).gba" size 4194304 crc cdfac4ee sha1 7253d2d036429b478e4132de4901417dd840ae1a ) + name "Koutetsu Teikoku (Japan)" + description "Koutetsu Teikoku (Japan)" + rom ( name "Koutetsu Teikoku (Japan).gba" size 4194304 crc cdfac4ee sha1 7253d2d036429b478e4132de4901417dd840ae1a ) ) game ( @@ -9577,9 +9697,9 @@ game ( ) game ( - name "Legend of Dynamic Goushouden - Houkai no Rondo (Japan)" - description "Legend of Dynamic Goushouden - Houkai no Rondo (Japan)" - rom ( name "Legend of Dynamic Goushouden - Houkai no Rondo (Japan).gba" size 8388608 crc 67f18f8e sha1 8a037eff3a44b1667ec5e81ca8c1ff03537366b2 ) + name "Legend of Dynamic - Goushouden - Houkai no Rondo (Japan)" + description "Legend of Dynamic - Goushouden - Houkai no Rondo (Japan)" + rom ( name "Legend of Dynamic - Goushouden - Houkai no Rondo (Japan).gba" size 8388608 crc 67f18f8e sha1 8a037eff3a44b1667ec5e81ca8c1ff03537366b2 ) ) game ( @@ -9702,12 +9822,6 @@ game ( rom ( name "LEGO Racers 2 (Europe) (En,Fr,De,Es,It,Nl,Sv,Da).gba" size 8388608 crc 7a1dc458 sha1 a4da0237646a4f56296465b92c659f3e7438ad98 ) ) -game ( - name "LEGO Soccer Mania (USA, Europe) (En,Fr,De,Es,It,Nl,Sv,Da)" - description "LEGO Soccer Mania (USA, Europe) (En,Fr,De,Es,It,Nl,Sv,Da)" - rom ( name "LEGO Soccer Mania (USA, Europe) (En,Fr,De,Es,It,Nl,Sv,Da).gba" size 8388608 crc 73ef3a35 sha1 05799b99395aba04dfe5ae8af019e4d70cb8e61b ) -) - game ( name "LEGO Star Wars - The Video Game (USA, Europe) (En,Fr,De,Es,It,Nl,Da)" description "LEGO Star Wars - The Video Game (USA, Europe) (En,Fr,De,Es,It,Nl,Da)" @@ -9801,13 +9915,13 @@ game ( game ( name "Lilo & Stitch (Europe) (En,Fr,De,Es,It,Nl) (Rev 1)" description "Lilo & Stitch (Europe) (En,Fr,De,Es,It,Nl) (Rev 1)" - rom ( name "Lilo & Stitch (Europe) (En,Fr,De,Es,It,Nl) (Rev 1).gba" size 8388608 crc e7bc4ef1 sha1 a4e86401593c1763fdd70c6e1f7b4ccec7101db7 flags verified ) + rom ( name "Lilo & Stitch (Europe) (En,Fr,De,Es,It,Nl) (Rev 1).gba" size 8388608 crc e7bc4ef1 sha1 a4e86401593c1763fdd70c6e1f7b4ccec7101db7 ) ) game ( name "Lilo & Stitch (USA)" description "Lilo & Stitch (USA)" - rom ( name "Lilo & Stitch (USA).gba" size 8388608 crc 542aa4ac sha1 b86e22f7009ae20c0d8efc51ab7c61c0cee21a0c ) + rom ( name "Lilo & Stitch (USA).gba" size 8388608 crc 542aa4ac sha1 b86e22f7009ae20c0d8efc51ab7c61c0cee21a0c flags verified ) ) game ( @@ -10047,7 +10161,7 @@ game ( game ( name "Madagascar (Europe)" description "Madagascar (Europe)" - rom ( name "Madagascar (Europe).gba" size 8388608 crc 394f2126 sha1 30e85193a92ae6fa4dd1c54ea76d629276789657 ) + rom ( name "Madagascar (Europe).gba" size 8388608 crc 394f2126 sha1 30e85193a92ae6fa4dd1c54ea76d629276789657 flags verified ) ) game ( @@ -10191,7 +10305,7 @@ game ( game ( name "Magical Quest 2 Starring Mickey & Minnie (Europe) (En,Fr,De)" description "Magical Quest 2 Starring Mickey & Minnie (Europe) (En,Fr,De)" - rom ( name "Magical Quest 2 Starring Mickey & Minnie (Europe) (En,Fr,De).gba" size 4194304 crc f9498038 sha1 1cac78eb9f6a9079f1a02d48dcd1ce4c7a81350b ) + rom ( name "Magical Quest 2 Starring Mickey & Minnie (Europe) (En,Fr,De).gba" size 4194304 crc f9498038 sha1 1cac78eb9f6a9079f1a02d48dcd1ce4c7a81350b flags verified ) ) game ( @@ -10309,15 +10423,15 @@ game ( ) game ( - name "Manga-ka Debut Monogatari (Japan) (Rev 1)" - description "Manga-ka Debut Monogatari (Japan) (Rev 1)" - rom ( name "Manga-ka Debut Monogatari (Japan) (Rev 1).gba" size 4194304 crc f0c22c62 sha1 78c57d1062830074166da3192b01d0d0ae9afaeb ) + name "Manga-ka Debut Monogatari - Akogare! Manga Ka Ikusei Game! (Japan) (Rev 1)" + description "Manga-ka Debut Monogatari - Akogare! Manga Ka Ikusei Game! (Japan) (Rev 1)" + rom ( name "Manga-ka Debut Monogatari - Akogare! Manga Ka Ikusei Game! (Japan) (Rev 1).gba" size 4194304 crc f0c22c62 sha1 78c57d1062830074166da3192b01d0d0ae9afaeb ) ) game ( - name "Manga-ka Debut Monogatari (Japan)" - description "Manga-ka Debut Monogatari (Japan)" - rom ( name "Manga-ka Debut Monogatari (Japan).gba" size 4194304 crc 61a602d7 sha1 5f7c1b88b8c7caa57a0af81cd3ae74171abe50d1 ) + name "Manga-ka Debut Monogatari - Akogare! Manga Ka Ikusei Game! (Japan)" + description "Manga-ka Debut Monogatari - Akogare! Manga Ka Ikusei Game! (Japan)" + rom ( name "Manga-ka Debut Monogatari - Akogare! Manga Ka Ikusei Game! (Japan).gba" size 4194304 crc 61a602d7 sha1 5f7c1b88b8c7caa57a0af81cd3ae74171abe50d1 ) ) game ( @@ -10512,6 +10626,18 @@ game ( rom ( name "Mario Kart Advance (Japan).gba" size 4194304 crc 30e99fcd sha1 88ffde363b05264a99a4d5ada0c80a00196a94d7 flags verified ) ) +game ( + name "Mario Kart XXL (Europe) (Demo)" + description "Mario Kart XXL (Europe) (Demo)" + rom ( name "Mario Kart XXL (Europe) (Demo).gba" size 4194304 crc e3075601 sha1 ba1e75f780c41e737922f090dcb17526f1fd7a01 ) +) + +game ( + name "Mario Party Advance (Europe) (En,Fr,De,Es,It) (Virtual Console)" + description "Mario Party Advance (Europe) (En,Fr,De,Es,It) (Virtual Console)" + rom ( name "Mario Party Advance (Europe) (En,Fr,De,Es,It) (Virtual Console).gba" size 16777216 crc 46c66f40 sha1 34d13fd8cca31a77eeebf9c864a4388969f975c4 ) +) + game ( name "Mario Party Advance (USA) (Virtual Console)" description "Mario Party Advance (USA) (Virtual Console)" @@ -10543,9 +10669,9 @@ game ( ) game ( - name "Mario Party Advance (Europe) (En,Fr,De,Es,It) (Virtual Console)" - description "Mario Party Advance (Europe) (En,Fr,De,Es,It) (Virtual Console)" - rom ( name "Mario Party Advance (Europe) (En,Fr,De,Es,It) (Virtual Console).gba" size 16777216 crc 46c66f40 sha1 34d13fd8cca31a77eeebf9c864a4388969f975c4 ) + name "Mario Pinball Land (USA, Australia)" + description "Mario Pinball Land (USA, Australia)" + rom ( name "Mario Pinball Land (USA, Australia).gba" size 8388608 crc 70a6d2c1 sha1 3fdcd3bb30d61b4dd6829dbdc1a0ac116618b87d flags verified ) ) game ( @@ -10561,9 +10687,9 @@ game ( ) game ( - name "Mario Pinball Land (USA, Australia)" - description "Mario Pinball Land (USA, Australia)" - rom ( name "Mario Pinball Land (USA, Australia).gba" size 8388608 crc 70a6d2c1 sha1 3fdcd3bb30d61b4dd6829dbdc1a0ac116618b87d flags verified ) + name "Mario Power Tennis (Europe) (En,Fr,De,Es,It) (Virtual Console)" + description "Mario Power Tennis (Europe) (En,Fr,De,Es,It) (Virtual Console)" + rom ( name "Mario Power Tennis (Europe) (En,Fr,De,Es,It) (Virtual Console).gba" size 16777216 crc 851a44cb sha1 24087080476ce04853455fd82f09c4d8c65604d1 ) ) game ( @@ -10572,12 +10698,6 @@ game ( rom ( name "Mario Power Tennis (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc c8db4f60 sha1 d61990974040d405b5bf8436ac8e1e0beb0f7964 flags verified ) ) -game ( - name "Mario Power Tennis (Europe) (En,Fr,De,Es,It) (Virtual Console)" - description "Mario Power Tennis (Europe) (En,Fr,De,Es,It) (Virtual Console)" - rom ( name "Mario Power Tennis (Europe) (En,Fr,De,Es,It) (Virtual Console).gba" size 16777216 crc 851a44cb sha1 24087080476ce04853455fd82f09c4d8c65604d1 ) -) - game ( name "Mario Tennis - Power Tour (USA, Australia) (En,Fr,De,Es,It)" description "Mario Tennis - Power Tour (USA, Australia) (En,Fr,De,Es,It)" @@ -10608,6 +10728,12 @@ game ( rom ( name "Mario vs. Donkey Kong (Japan) (Demo) (Kiosk, GameCube).gba" size 16777216 crc b3642431 sha1 e7389b36573f7e32d53ae9acfdba744389c2f6c2 flags verified ) ) +game ( + name "Mario vs. Donkey Kong (USA) (Demo) (Kiosk, GameCube)" + description "Mario vs. Donkey Kong (USA) (Demo) (Kiosk, GameCube)" + rom ( name "Mario vs. Donkey Kong (USA) (Demo) (Kiosk, GameCube).gba" size 16777216 crc 25505164 sha1 68eebe6dbcf8973ed7426a793185db202a01288f flags verified ) +) + game ( name "Mario vs. Donkey Kong (USA, Australia)" description "Mario vs. Donkey Kong (USA, Australia)" @@ -10626,12 +10752,6 @@ game ( rom ( name "Mario vs. Donkey Kong (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc ca030d61 sha1 c645798ea9cf94e2fd8826ea96a5971a7aeb52b7 flags verified ) ) -game ( - name "Mario vs. Donkey Kong (USA) (Demo) (Kiosk, GameCube)" - description "Mario vs. Donkey Kong (USA) (Demo) (Kiosk, GameCube)" - rom ( name "Mario vs. Donkey Kong (USA) (Demo) (Kiosk, GameCube).gba" size 16777216 crc 25505164 sha1 68eebe6dbcf8973ed7426a793185db202a01288f flags verified ) -) - game ( name "Marvel - Ultimate Alliance (USA)" description "Marvel - Ultimate Alliance (USA)" @@ -10665,7 +10785,7 @@ game ( game ( name "Mat Hoffman's Pro BMX (USA, Europe)" description "Mat Hoffman's Pro BMX (USA, Europe)" - rom ( name "Mat Hoffman's Pro BMX (USA, Europe).gba" size 4194304 crc a333fa51 sha1 df0345c31ca3cad51cef7adae8c6fdadf81b3a97 ) + rom ( name "Mat Hoffman's Pro BMX (USA, Europe).gba" size 4194304 crc a333fa51 sha1 df0345c31ca3cad51cef7adae8c6fdadf81b3a97 flags verified ) ) game ( @@ -10680,18 +10800,18 @@ game ( rom ( name "Mat Hoffman's Pro BMX 2 (USA, Europe).gba" size 8388608 crc e7fa71c6 sha1 d12bc188404bfb6d012b9ce1cf7302762d934832 ) ) -game ( - name "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan) (Rev 1)" - description "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan) (Rev 1)" - rom ( name "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan) (Rev 1).gba" size 8388608 crc d647ab18 sha1 3489569fa8a3c6a750c5cb0048915da78c5f40ab ) -) - game ( name "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan)" description "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan)" rom ( name "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan).gba" size 8388608 crc 4ebdad86 sha1 9e1d0d5d5454d544ab85b34ad0e50fad7b54487c flags verified ) ) +game ( + name "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan) (Rev 1)" + description "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan) (Rev 1)" + rom ( name "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan) (Rev 1).gba" size 8388608 crc d647ab18 sha1 3489569fa8a3c6a750c5cb0048915da78c5f40ab ) +) + game ( name "Matchbox Cross Town Heroes (USA)" description "Matchbox Cross Town Heroes (USA)" @@ -10770,6 +10890,12 @@ game ( rom ( name "Medabots - Metabee (Europe) (Virtual Console).gba" size 8388608 crc 3524f206 sha1 5a4d269998829d7e41fdb014ffa11584ea1f8aa6 ) ) +game ( + name "Medabots - Metabee (Europe)" + description "Medabots - Metabee (Europe)" + rom ( name "Medabots - Metabee (Europe).gba" size 8388608 crc 50927f3e sha1 cd3d674e88f40a0707b150c4293588a659001d29 ) +) + game ( name "Medabots - Metabee (Spain)" description "Medabots - Metabee (Spain)" @@ -10789,9 +10915,9 @@ game ( ) game ( - name "Medabots - Metabee (Europe)" - description "Medabots - Metabee (Europe)" - rom ( name "Medabots - Metabee (Europe).gba" size 8388608 crc 50927f3e sha1 cd3d674e88f40a0707b150c4293588a659001d29 ) + name "Medabots - Rokusho (USA)" + description "Medabots - Rokusho (USA)" + rom ( name "Medabots - Rokusho (USA).gba" size 8388608 crc e144ded2 sha1 c4572428ea97b302f699a3b4eba2a1f0e87c1c9c ) ) game ( @@ -10800,12 +10926,6 @@ game ( rom ( name "Medabots - Rokusho (Europe).gba" size 8388608 crc a519feb5 sha1 fc44b80f3e71effc33d8e05a74888cb885c32eb0 flags verified ) ) -game ( - name "Medabots - Rokusho (Spain)" - description "Medabots - Rokusho (Spain)" - rom ( name "Medabots - Rokusho (Spain).gba" size 8388608 crc 046d86c4 sha1 90fe7f2927c592aabc9b33d0df00d92046c5cb92 ) -) - game ( name "Medabots - Rokusho (USA) (Virtual Console)" description "Medabots - Rokusho (USA) (Virtual Console)" @@ -10819,15 +10939,9 @@ game ( ) game ( - name "Medabots - Rokusho (USA)" - description "Medabots - Rokusho (USA)" - rom ( name "Medabots - Rokusho (USA).gba" size 8388608 crc e144ded2 sha1 c4572428ea97b302f699a3b4eba2a1f0e87c1c9c ) -) - -game ( - name "Medabots AX - Metabee Ver. (Europe) (En,Fr,De,Es,It)" - description "Medabots AX - Metabee Ver. (Europe) (En,Fr,De,Es,It)" - rom ( name "Medabots AX - Metabee Ver. (Europe) (En,Fr,De,Es,It).gba" size 8388608 crc 5f1e5a48 sha1 130c908d24ba3e422fd8db84e683bacc87235e78 flags verified ) + name "Medabots - Rokusho (Spain)" + description "Medabots - Rokusho (Spain)" + rom ( name "Medabots - Rokusho (Spain).gba" size 8388608 crc 046d86c4 sha1 90fe7f2927c592aabc9b33d0df00d92046c5cb92 ) ) game ( @@ -10836,6 +10950,12 @@ game ( rom ( name "Medabots AX - Metabee Ver. (USA).gba" size 8388608 crc 03294511 sha1 80c024df6d40e499776665d7f0c494a252973048 ) ) +game ( + name "Medabots AX - Metabee Ver. (Europe) (En,Fr,De,Es,It)" + description "Medabots AX - Metabee Ver. (Europe) (En,Fr,De,Es,It)" + rom ( name "Medabots AX - Metabee Ver. (Europe) (En,Fr,De,Es,It).gba" size 8388608 crc 5f1e5a48 sha1 130c908d24ba3e422fd8db84e683bacc87235e78 flags verified ) +) + game ( name "Medabots AX - Metabee Ver. (USA) (Virtual Console)" description "Medabots AX - Metabee Ver. (USA) (Virtual Console)" @@ -10878,6 +10998,12 @@ game ( rom ( name "Medal of Honor - Infiltrator (USA, Europe) (En,Fr,De).gba" size 16777216 crc f23150a4 sha1 47761911475e9548c81fed78e3d3336ddae89a58 flags verified ) ) +game ( + name "Medal of Honor - Underground (Europe) (En,Fr,Es,It) (Zoo Digital)" + description "Medal of Honor - Underground (Europe) (En,Fr,Es,It) (Zoo Digital)" + rom ( name "Medal of Honor - Underground (Europe) (En,Fr,Es,It) (Zoo Digital).gba" size 8388608 crc 72b4cf20 sha1 abb26d759ef729d21e41a913fc6c1ce4ab77149f ) +) + game ( name "Medal of Honor - Underground (USA)" description "Medal of Honor - Underground (USA)" @@ -10890,12 +11016,6 @@ game ( rom ( name "Medal of Honor - Underground (Europe) (En,Fr,Es,It) (Ubi Soft).gba" size 8388608 crc 9db145b8 sha1 e43aaba3f925443cfccf9294b870024a9c71a9a9 ) ) -game ( - name "Medal of Honor - Underground (Europe) (En,Fr,Es,It) (Zoo Digital)" - description "Medal of Honor - Underground (Europe) (En,Fr,Es,It) (Zoo Digital)" - rom ( name "Medal of Honor - Underground (Europe) (En,Fr,Es,It) (Zoo Digital).gba" size 8388608 crc 72b4cf20 sha1 abb26d759ef729d21e41a913fc6c1ce4ab77149f ) -) - game ( name "Medal of Honor Advance (Japan)" description "Medal of Honor Advance (Japan)" @@ -10993,375 +11113,375 @@ game ( ) game ( - name "Mega Man & Bass (USA) (Virtual Console)" - description "Mega Man & Bass (USA) (Virtual Console)" - rom ( name "Mega Man & Bass (USA) (Virtual Console).gba" size 8388608 crc b61f99d4 sha1 37db963a52aecec8018057cef3811860c3e889ed ) + name "Megaman - Battle Chip Challenge (USA) (Virtual Console)" + description "Megaman - Battle Chip Challenge (USA) (Virtual Console)" + rom ( name "Megaman - Battle Chip Challenge (USA) (Virtual Console).gba" size 8388608 crc 59fc1ef6 sha1 e936db20e9cf923dc10c6c1dc14bc7ed6b220080 ) ) game ( - name "Mega Man & Bass (USA)" - description "Mega Man & Bass (USA)" - rom ( name "Mega Man & Bass (USA).gba" size 8388608 crc eea68c2e sha1 7610847b331870d4338e5ac894b36e55e2bec5a0 ) + name "Megaman - Battle Chip Challenge (Europe) (Virtual Console)" + description "Megaman - Battle Chip Challenge (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Chip Challenge (Europe) (Virtual Console).gba" size 8388608 crc 24046bf2 sha1 f0776ad33eb01a47207a076d95b534145223371d ) ) game ( - name "Mega Man & Bass (Europe)" - description "Mega Man & Bass (Europe)" - rom ( name "Mega Man & Bass (Europe).gba" size 8388608 crc 01b4d95e sha1 5d6f8fb1f52803a54e9857e53d0b88173cf8f48a flags verified ) + name "Megaman - Battle Chip Challenge (USA)" + description "Megaman - Battle Chip Challenge (USA)" + rom ( name "Megaman - Battle Chip Challenge (USA).gba" size 8388608 crc 26be44fd sha1 72309736f3820470c6f372d6a05ad1f16bc5a946 ) ) game ( - name "Mega Man & Bass (Europe) (Virtual Console)" - description "Mega Man & Bass (Europe) (Virtual Console)" - rom ( name "Mega Man & Bass (Europe) (Virtual Console).gba" size 8388608 crc 6e140bfa sha1 37a188a250ff553a71144f8bbe92098c85c8006d ) + name "Megaman - Battle Chip Challenge (Europe)" + description "Megaman - Battle Chip Challenge (Europe)" + rom ( name "Megaman - Battle Chip Challenge (Europe).gba" size 8388608 crc 5b4631f9 sha1 f54486c8a0bb22cf0ece4297fb052d0a0f11e56d ) ) game ( - name "Mega Man Battle Chip Challenge (USA) (Virtual Console)" - description "Mega Man Battle Chip Challenge (USA) (Virtual Console)" - rom ( name "Mega Man Battle Chip Challenge (USA) (Virtual Console).gba" size 8388608 crc 59fc1ef6 sha1 e936db20e9cf923dc10c6c1dc14bc7ed6b220080 ) + name "Megaman - Battle Network (USA) (Virtual Console)" + description "Megaman - Battle Network (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network (USA) (Virtual Console).gba" size 8388608 crc 1d5d0cb6 sha1 a371765e107c30dc4ac37c2f114a785305f47a4c ) ) game ( - name "Mega Man Battle Chip Challenge (Europe) (Virtual Console)" - description "Mega Man Battle Chip Challenge (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Chip Challenge (Europe) (Virtual Console).gba" size 8388608 crc 24046bf2 sha1 f0776ad33eb01a47207a076d95b534145223371d ) + name "Megaman - Battle Network (Europe) (Virtual Console)" + description "Megaman - Battle Network (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network (Europe) (Virtual Console).gba" size 8388608 crc 1a16c13d sha1 0dcbc43d5d50401c0eab8bb5989055523ce90b10 ) ) game ( - name "Mega Man Battle Chip Challenge (USA)" - description "Mega Man Battle Chip Challenge (USA)" - rom ( name "Mega Man Battle Chip Challenge (USA).gba" size 8388608 crc 26be44fd sha1 72309736f3820470c6f372d6a05ad1f16bc5a946 ) + name "Megaman - Battle Network (USA)" + description "Megaman - Battle Network (USA)" + rom ( name "Megaman - Battle Network (USA).gba" size 8388608 crc 1d347971 sha1 a4fbae389654a6611d0597b1e9109cbbd32a132f ) ) game ( - name "Mega Man Battle Chip Challenge (Europe)" - description "Mega Man Battle Chip Challenge (Europe)" - rom ( name "Mega Man Battle Chip Challenge (Europe).gba" size 8388608 crc 5b4631f9 sha1 f54486c8a0bb22cf0ece4297fb052d0a0f11e56d ) + name "Megaman - Battle Network (Europe)" + description "Megaman - Battle Network (Europe)" + rom ( name "Megaman - Battle Network (Europe).gba" size 8388608 crc 1a7fb4fa sha1 b017b6054ffafc012be9adee785819b17706cabc flags verified ) ) game ( - name "Mega Man Battle Network (USA)" - description "Mega Man Battle Network (USA)" - rom ( name "Mega Man Battle Network (USA).gba" size 8388608 crc 1d347971 sha1 a4fbae389654a6611d0597b1e9109cbbd32a132f ) + name "Megaman - Battle Network 2 (USA)" + description "Megaman - Battle Network 2 (USA)" + rom ( name "Megaman - Battle Network 2 (USA).gba" size 8388608 crc 6d961f82 sha1 601b5012f77001d2c5c11b31304afafc45a70d0b ) ) game ( - name "Mega Man Battle Network (Europe)" - description "Mega Man Battle Network (Europe)" - rom ( name "Mega Man Battle Network (Europe).gba" size 8388608 crc 1a7fb4fa sha1 b017b6054ffafc012be9adee785819b17706cabc ) + name "Megaman - Battle Network 2 (Europe)" + description "Megaman - Battle Network 2 (Europe)" + rom ( name "Megaman - Battle Network 2 (Europe).gba" size 8388608 crc 66341f3b sha1 13d8c1978cbcd9ca2a127168544fda176e0a4d6c ) ) game ( - name "Mega Man Battle Network (USA) (Virtual Console)" - description "Mega Man Battle Network (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network (USA) (Virtual Console).gba" size 8388608 crc 1d5d0cb6 sha1 a371765e107c30dc4ac37c2f114a785305f47a4c ) + name "Megaman - Battle Network 2 (USA) (Debug Version)" + description "Megaman - Battle Network 2 (USA) (Debug Version)" + rom ( name "Megaman - Battle Network 2 (USA) (Debug Version).gba" size 8388608 crc c3aabd70 sha1 d8968ac6c33f376d8f6ce6205b53756434a8d2ac ) ) game ( - name "Mega Man Battle Network (Europe) (Virtual Console)" - description "Mega Man Battle Network (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network (Europe) (Virtual Console).gba" size 8388608 crc 1a16c13d sha1 0dcbc43d5d50401c0eab8bb5989055523ce90b10 ) + name "Megaman - Battle Network 2 (USA, Europe) (Virtual Console)" + description "Megaman - Battle Network 2 (USA, Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 2 (USA, Europe) (Virtual Console).gba" size 8388608 crc 5e88c34b sha1 d6b574203cc393f292a9825fcec64e72b3b2a11b flags verified ) ) game ( - name "Mega Man Battle Network 2 (USA) (Debug Version)" - description "Mega Man Battle Network 2 (USA) (Debug Version)" - rom ( name "Mega Man Battle Network 2 (USA) (Debug Version).gba" size 33554432 crc fbdadaa7 sha1 3bc2a627a859431db0f40f755fbc2857936e23b2 ) + name "Megaman - Battle Network 3 - Blue Version (USA) (Virtual Console)" + description "Megaman - Battle Network 3 - Blue Version (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network 3 - Blue Version (USA) (Virtual Console).gba" size 8388608 crc edd7106e sha1 6e30310f3994803e56170f6d67c5f154a5d91ccb flags verified ) ) game ( - name "Mega Man Battle Network 2 (USA, Europe) (Virtual Console)" - description "Mega Man Battle Network 2 (USA, Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 2 (USA, Europe) (Virtual Console).gba" size 8388608 crc 5e88c34b sha1 d6b574203cc393f292a9825fcec64e72b3b2a11b flags verified ) + name "Megaman - Battle Network 3 - Blue Version (Europe) (Virtual Console)" + description "Megaman - Battle Network 3 - Blue Version (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 3 - Blue Version (Europe) (Virtual Console).gba" size 8388608 crc 3213fc2d sha1 a9fe51d3ffd7488739821b895b45cf91c10a3108 ) ) game ( - name "Mega Man Battle Network 2 (USA)" - description "Mega Man Battle Network 2 (USA)" - rom ( name "Mega Man Battle Network 2 (USA).gba" size 8388608 crc 6d961f82 sha1 601b5012f77001d2c5c11b31304afafc45a70d0b ) + name "Megaman - Battle Network 3 - Blue Version (USA)" + description "Megaman - Battle Network 3 - Blue Version (USA)" + rom ( name "Megaman - Battle Network 3 - Blue Version (USA).gba" size 8388608 crc c0c780f9 sha1 3d21905b6e860d39a00ba643779776de4c73c411 flags verified ) ) game ( - name "Mega Man Battle Network 2 (Europe)" - description "Mega Man Battle Network 2 (Europe)" - rom ( name "Mega Man Battle Network 2 (Europe).gba" size 8388608 crc 66341f3b sha1 13d8c1978cbcd9ca2a127168544fda176e0a4d6c ) + name "Megaman - Battle Network 3 - Blue Version (Europe)" + description "Megaman - Battle Network 3 - Blue Version (Europe)" + rom ( name "Megaman - Battle Network 3 - Blue Version (Europe).gba" size 8388608 crc 1f036cba sha1 9cb052728cae18864a012e7598119ca7b93eea67 ) ) game ( - name "Mega Man Battle Network 3 - Blue (Europe)" - description "Mega Man Battle Network 3 - Blue (Europe)" - rom ( name "Mega Man Battle Network 3 - Blue (Europe).gba" size 8388608 crc 1f036cba sha1 9cb052728cae18864a012e7598119ca7b93eea67 ) + name "Megaman - Battle Network 3 - White Version (USA)" + description "Megaman - Battle Network 3 - White Version (USA)" + rom ( name "Megaman - Battle Network 3 - White Version (USA).gba" size 8388608 crc 0be4410a sha1 ff45038ae6d01cde4eae25a02dcb8bed29e07a6f flags verified ) ) game ( - name "Mega Man Battle Network 3 - Blue (Europe) (Virtual Console)" - description "Mega Man Battle Network 3 - Blue (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 3 - Blue (Europe) (Virtual Console).gba" size 8388608 crc 3213fc2d sha1 a9fe51d3ffd7488739821b895b45cf91c10a3108 ) + name "Megaman - Battle Network 3 - White Version (Europe)" + description "Megaman - Battle Network 3 - White Version (Europe)" + rom ( name "Megaman - Battle Network 3 - White Version (Europe).gba" size 8388608 crc 23d0a981 sha1 2942a890c369569e163a60f831150305ca0828fc ) ) game ( - name "Mega Man Battle Network 3 - Blue Version (USA) (Virtual Console)" - description "Mega Man Battle Network 3 - Blue Version (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network 3 - Blue Version (USA) (Virtual Console).gba" size 8388608 crc edd7106e sha1 6e30310f3994803e56170f6d67c5f154a5d91ccb flags verified ) + name "Megaman - Battle Network 3 - White Version (USA) (Virtual Console)" + description "Megaman - Battle Network 3 - White Version (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network 3 - White Version (USA) (Virtual Console).gba" size 8388608 crc f1c5ac80 sha1 1f0762b7c817211e855f4f50c2b11585d0893be9 ) ) game ( - name "Mega Man Battle Network 3 - Blue Version (USA)" - description "Mega Man Battle Network 3 - Blue Version (USA)" - rom ( name "Mega Man Battle Network 3 - Blue Version (USA).gba" size 8388608 crc c0c780f9 sha1 3d21905b6e860d39a00ba643779776de4c73c411 flags verified ) + name "Megaman - Battle Network 3 - White Version (Europe) (Virtual Console)" + description "Megaman - Battle Network 3 - White Version (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 3 - White Version (Europe) (Virtual Console).gba" size 8388608 crc d9f1440b sha1 e0bd967c2296ca5a515e8b6231d4959336c1278d ) ) game ( - name "Mega Man Battle Network 3 - White (Europe)" - description "Mega Man Battle Network 3 - White (Europe)" - rom ( name "Mega Man Battle Network 3 - White (Europe).gba" size 8388608 crc 23d0a981 sha1 2942a890c369569e163a60f831150305ca0828fc ) + name "Megaman - Battle Network 4 - Blue Moon (USA) (Virtual Console)" + description "Megaman - Battle Network 4 - Blue Moon (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network 4 - Blue Moon (USA) (Virtual Console).gba" size 8388608 crc 739b7ec4 sha1 62a0169f0fe00e5f8184475a20a36877d1063f63 flags verified ) ) game ( - name "Mega Man Battle Network 3 - White (Europe) (Virtual Console)" - description "Mega Man Battle Network 3 - White (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 3 - White (Europe) (Virtual Console).gba" size 8388608 crc d9f1440b sha1 e0bd967c2296ca5a515e8b6231d4959336c1278d ) + name "Megaman - Battle Network 4 - Blue Moon (Europe) (Virtual Console)" + description "Megaman - Battle Network 4 - Blue Moon (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 4 - Blue Moon (Europe) (Virtual Console).gba" size 8388608 crc ad89f27d sha1 0fbf2b2f53f0a32e893e2593e2d7c542b5fcac99 ) ) game ( - name "Mega Man Battle Network 3 - White Version (USA) (Virtual Console)" - description "Mega Man Battle Network 3 - White Version (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network 3 - White Version (USA) (Virtual Console).gba" size 8388608 crc f1c5ac80 sha1 1f0762b7c817211e855f4f50c2b11585d0893be9 ) + name "Megaman - Battle Network 4 - Blue Moon (USA)" + description "Megaman - Battle Network 4 - Blue Moon (USA)" + rom ( name "Megaman - Battle Network 4 - Blue Moon (USA).gba" size 8388608 crc 758a46e9 sha1 5e017c803ddc768efb8010314b46bc17e9757f71 flags verified ) ) game ( - name "Mega Man Battle Network 3 - White Version (USA)" - description "Mega Man Battle Network 3 - White Version (USA)" - rom ( name "Mega Man Battle Network 3 - White Version (USA).gba" size 8388608 crc 0be4410a sha1 ff45038ae6d01cde4eae25a02dcb8bed29e07a6f flags verified ) + name "Megaman - Battle Network 4 - Blue Moon (Europe)" + description "Megaman - Battle Network 4 - Blue Moon (Europe)" + rom ( name "Megaman - Battle Network 4 - Blue Moon (Europe).gba" size 8388608 crc 48758316 sha1 a05e8dce26b5134001337e193d49958be2081598 ) ) game ( - name "Mega Man Battle Network 4 - Blue Moon (USA)" - description "Mega Man Battle Network 4 - Blue Moon (USA)" - rom ( name "Mega Man Battle Network 4 - Blue Moon (USA).gba" size 8388608 crc 758a46e9 sha1 5e017c803ddc768efb8010314b46bc17e9757f71 flags verified ) + name "Megaman - Battle Network 4 - Red Sun (USA)" + description "Megaman - Battle Network 4 - Red Sun (USA)" + rom ( name "Megaman - Battle Network 4 - Red Sun (USA).gba" size 8388608 crc 2120695c sha1 a97e96a7da03abd70f7953328e511b9fa29179f1 flags verified ) ) game ( - name "Mega Man Battle Network 4 - Blue Moon (Europe)" - description "Mega Man Battle Network 4 - Blue Moon (Europe)" - rom ( name "Mega Man Battle Network 4 - Blue Moon (Europe).gba" size 8388608 crc 48758316 sha1 a05e8dce26b5134001337e193d49958be2081598 ) + name "Megaman - Battle Network 4 - Red Sun (Europe)" + description "Megaman - Battle Network 4 - Red Sun (Europe)" + rom ( name "Megaman - Battle Network 4 - Red Sun (Europe).gba" size 8388608 crc 0cb136c2 sha1 21af9f7805a27729e770928f939acedd6ae27c1c ) ) game ( - name "Mega Man Battle Network 4 - Blue Moon (USA) (Virtual Console)" - description "Mega Man Battle Network 4 - Blue Moon (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network 4 - Blue Moon (USA) (Virtual Console).gba" size 8388608 crc 739b7ec4 sha1 62a0169f0fe00e5f8184475a20a36877d1063f63 flags verified ) + name "Megaman - Battle Network 4 - Red Sun (USA) (Virtual Console)" + description "Megaman - Battle Network 4 - Red Sun (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network 4 - Red Sun (USA) (Virtual Console).gba" size 8388608 crc 12e7ab52 sha1 07426328dbb4a3aea763e75c8ffe6de482ff69d0 flags verified ) ) game ( - name "Mega Man Battle Network 4 - Blue Moon (Europe) (Virtual Console)" - description "Mega Man Battle Network 4 - Blue Moon (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 4 - Blue Moon (Europe) (Virtual Console).gba" size 8388608 crc ad89f27d sha1 0fbf2b2f53f0a32e893e2593e2d7c542b5fcac99 ) + name "Megaman - Battle Network 4 - Red Sun (Europe) (Virtual Console)" + description "Megaman - Battle Network 4 - Red Sun (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 4 - Red Sun (Europe) (Virtual Console).gba" size 8388608 crc cb3bbf74 sha1 36d83a1509d28b0c09365466cc1481c919d84cb0 ) ) game ( - name "Mega Man Battle Network 4 - Red Sun (USA) (Virtual Console)" - description "Mega Man Battle Network 4 - Red Sun (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network 4 - Red Sun (USA) (Virtual Console).gba" size 8388608 crc 12e7ab52 sha1 07426328dbb4a3aea763e75c8ffe6de482ff69d0 flags verified ) + name "Megaman - Battle Network 5 - Team Colonel (USA) (Virtual Console)" + description "Megaman - Battle Network 5 - Team Colonel (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network 5 - Team Colonel (USA) (Virtual Console).gba" size 8388608 crc 78dd4edc sha1 c715f1d01e016ad6890ebc8ec90120bc3b146a7e ) ) game ( - name "Mega Man Battle Network 4 - Red Sun (Europe) (Virtual Console)" - description "Mega Man Battle Network 4 - Red Sun (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 4 - Red Sun (Europe) (Virtual Console).gba" size 8388608 crc cb3bbf74 sha1 36d83a1509d28b0c09365466cc1481c919d84cb0 ) + name "Megaman - Battle Network 5 - Team Colonel (Europe)" + description "Megaman - Battle Network 5 - Team Colonel (Europe)" + rom ( name "Megaman - Battle Network 5 - Team Colonel (Europe).gba" size 8388608 crc 8fc8cf73 sha1 d15bcb7c351252a8890c29a2eaac25ff621635c9 ) ) game ( - name "Mega Man Battle Network 4 - Red Sun (USA)" - description "Mega Man Battle Network 4 - Red Sun (USA)" - rom ( name "Mega Man Battle Network 4 - Red Sun (USA).gba" size 8388608 crc 2120695c sha1 a97e96a7da03abd70f7953328e511b9fa29179f1 ) + name "Megaman - Battle Network 5 - Team Colonel (USA)" + description "Megaman - Battle Network 5 - Team Colonel (USA)" + rom ( name "Megaman - Battle Network 5 - Team Colonel (USA).gba" size 8388608 crc a552f683 sha1 5f472f78d8de2df01d5039e045c043cb40969a39 ) ) game ( - name "Mega Man Battle Network 4 - Red Sun (Europe)" - description "Mega Man Battle Network 4 - Red Sun (Europe)" - rom ( name "Mega Man Battle Network 4 - Red Sun (Europe).gba" size 8388608 crc 0cb136c2 sha1 21af9f7805a27729e770928f939acedd6ae27c1c ) + name "Megaman - Battle Network 5 - Team Colonel (Europe) (Virtual Console)" + description "Megaman - Battle Network 5 - Team Colonel (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 5 - Team Colonel (Europe) (Virtual Console).gba" size 8388608 crc 5247772c sha1 061c291b29629d5ce44011d2d83931f2e35044ed ) ) game ( - name "Mega Man Battle Network 5 - Team Colonel (Europe)" - description "Mega Man Battle Network 5 - Team Colonel (Europe)" - rom ( name "Mega Man Battle Network 5 - Team Colonel (Europe).gba" size 8388608 crc 8fc8cf73 sha1 d15bcb7c351252a8890c29a2eaac25ff621635c9 ) + name "Megaman - Battle Network 5 - Team Protoman (Europe)" + description "Megaman - Battle Network 5 - Team Protoman (Europe)" + rom ( name "Megaman - Battle Network 5 - Team Protoman (Europe).gba" size 8388608 crc 79f45ed8 sha1 3d017ed23535e42174299ff89fff44678eb553c3 ) ) game ( - name "Mega Man Battle Network 5 - Team Colonel (USA)" - description "Mega Man Battle Network 5 - Team Colonel (USA)" - rom ( name "Mega Man Battle Network 5 - Team Colonel (USA).gba" size 8388608 crc a552f683 sha1 5f472f78d8de2df01d5039e045c043cb40969a39 ) + name "Megaman - Battle Network 5 - Team Protoman (USA)" + description "Megaman - Battle Network 5 - Team Protoman (USA)" + rom ( name "Megaman - Battle Network 5 - Team Protoman (USA).gba" size 8388608 crc a73e83a4 sha1 b3774e96b1f107bb8b1db79b216be41b9bc5bac0 ) ) game ( - name "Mega Man Battle Network 5 - Team Colonel (Europe) (Virtual Console)" - description "Mega Man Battle Network 5 - Team Colonel (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 5 - Team Colonel (Europe) (Virtual Console).gba" size 8388608 crc 5247772c sha1 061c291b29629d5ce44011d2d83931f2e35044ed ) + name "Megaman - Battle Network 5 - Team Protoman (USA) (Virtual Console)" + description "Megaman - Battle Network 5 - Team Protoman (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network 5 - Team Protoman (USA) (Virtual Console).gba" size 8388608 crc a0911541 sha1 70ee16615f7d9d6bb13382d70dd2f213b918063b ) ) game ( - name "Mega Man Battle Network 5 - Team Colonel (USA) (Virtual Console)" - description "Mega Man Battle Network 5 - Team Colonel (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network 5 - Team Colonel (USA) (Virtual Console).gba" size 8388608 crc 78dd4edc sha1 c715f1d01e016ad6890ebc8ec90120bc3b146a7e ) + name "Megaman - Battle Network 5 - Team Protoman (Europe) (Virtual Console)" + description "Megaman - Battle Network 5 - Team Protoman (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 5 - Team Protoman (Europe) (Virtual Console).gba" size 8388608 crc 7e5bc83d sha1 b68b310e3106e7fa6a47d4155239be2e43959da4 ) ) game ( - name "Mega Man Battle Network 5 - Team Proto Man (USA) (Virtual Console)" - description "Mega Man Battle Network 5 - Team Proto Man (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network 5 - Team Proto Man (USA) (Virtual Console).gba" size 8388608 crc a0911541 sha1 70ee16615f7d9d6bb13382d70dd2f213b918063b ) + name "Megaman - Battle Network 6 - Cybeast Falzar (Europe)" + description "Megaman - Battle Network 6 - Cybeast Falzar (Europe)" + rom ( name "Megaman - Battle Network 6 - Cybeast Falzar (Europe).gba" size 8388608 crc 13183967 sha1 1f3037b33878fc66b79b4e2dcf1bb83202fa1b90 ) ) game ( - name "Mega Man Battle Network 5 - Team Proto Man (Europe) (Virtual Console)" - description "Mega Man Battle Network 5 - Team Proto Man (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 5 - Team Proto Man (Europe) (Virtual Console).gba" size 8388608 crc 7e5bc83d sha1 b68b310e3106e7fa6a47d4155239be2e43959da4 ) + name "Megaman - Battle Network 6 - Cybeast Falzar (USA)" + description "Megaman - Battle Network 6 - Cybeast Falzar (USA)" + rom ( name "Megaman - Battle Network 6 - Cybeast Falzar (USA).gba" size 8388608 crc dee6f2a9 sha1 0676ecd4d58a976af3346caebb44b9b6489ad099 flags verified ) ) game ( - name "Mega Man Battle Network 5 - Team Protoman (Europe)" - description "Mega Man Battle Network 5 - Team Protoman (Europe)" - rom ( name "Mega Man Battle Network 5 - Team Protoman (Europe).gba" size 8388608 crc 79f45ed8 sha1 3d017ed23535e42174299ff89fff44678eb553c3 ) + name "Megaman - Battle Network 6 - Cybeast Falzar (USA) (Virtual Console)" + description "Megaman - Battle Network 6 - Cybeast Falzar (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network 6 - Cybeast Falzar (USA) (Virtual Console).gba" size 16777216 crc c2a21be3 sha1 c477367c5c9b81cbca2f3ba0cf6a820d489a3b7e ) ) game ( - name "Mega Man Battle Network 5 - Team Protoman (USA)" - description "Mega Man Battle Network 5 - Team Protoman (USA)" - rom ( name "Mega Man Battle Network 5 - Team Protoman (USA).gba" size 8388608 crc a73e83a4 sha1 b3774e96b1f107bb8b1db79b216be41b9bc5bac0 ) + name "Megaman - Battle Network 6 - Cybeast Falzar (Europe) (Virtual Console)" + description "Megaman - Battle Network 6 - Cybeast Falzar (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 6 - Cybeast Falzar (Europe) (Virtual Console).gba" size 16777216 crc 8104e85a sha1 e2dbec1cb65065ed716fa9851237677efe27ae1c ) ) game ( - name "Mega Man Battle Network 6 - Cybeast Falzar (Europe)" - description "Mega Man Battle Network 6 - Cybeast Falzar (Europe)" - rom ( name "Mega Man Battle Network 6 - Cybeast Falzar (Europe).gba" size 8388608 crc 13183967 sha1 1f3037b33878fc66b79b4e2dcf1bb83202fa1b90 ) + name "Megaman - Battle Network 6 - Cybeast Gregar (USA)" + description "Megaman - Battle Network 6 - Cybeast Gregar (USA)" + rom ( name "Megaman - Battle Network 6 - Cybeast Gregar (USA).gba" size 8388608 crc 79452182 sha1 89fe0bac4fd3d2ab1d2ca35e87ef8b1294a84cd6 ) ) game ( - name "Mega Man Battle Network 6 - Cybeast Falzar (USA)" - description "Mega Man Battle Network 6 - Cybeast Falzar (USA)" - rom ( name "Mega Man Battle Network 6 - Cybeast Falzar (USA).gba" size 8388608 crc dee6f2a9 sha1 0676ecd4d58a976af3346caebb44b9b6489ad099 flags verified ) + name "Megaman - Battle Network 6 - Cybeast Gregar (Europe)" + description "Megaman - Battle Network 6 - Cybeast Gregar (Europe)" + rom ( name "Megaman - Battle Network 6 - Cybeast Gregar (Europe).gba" size 8388608 crc 25c29efb sha1 4d2e441b1bcb8438c0bef2ae61d937de7d04af02 ) ) game ( - name "Mega Man Battle Network 6 - Cybeast Falzar (USA) (Virtual Console)" - description "Mega Man Battle Network 6 - Cybeast Falzar (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network 6 - Cybeast Falzar (USA) (Virtual Console).gba" size 16777216 crc c2a21be3 sha1 c477367c5c9b81cbca2f3ba0cf6a820d489a3b7e ) + name "Megaman - Battle Network 6 - Cybeast Gregar (USA) (Virtual Console)" + description "Megaman - Battle Network 6 - Cybeast Gregar (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network 6 - Cybeast Gregar (USA) (Virtual Console).gba" size 8388608 crc e7e546fe sha1 0a8de8417b9939573daa04f95cdecdeaaf58a79e ) ) game ( - name "Mega Man Battle Network 6 - Cybeast Falzar (Europe) (Virtual Console)" - description "Mega Man Battle Network 6 - Cybeast Falzar (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 6 - Cybeast Falzar (Europe) (Virtual Console).gba" size 16777216 crc 8104e85a sha1 e2dbec1cb65065ed716fa9851237677efe27ae1c ) + name "Megaman - Battle Network 6 - Cybeast Gregar (Europe) (Virtual Console)" + description "Megaman - Battle Network 6 - Cybeast Gregar (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 6 - Cybeast Gregar (Europe) (Virtual Console).gba" size 8388608 crc bb62f987 sha1 7106dc0469898cb5768ec76c99029b24039d7605 ) ) game ( - name "Mega Man Battle Network 6 - Cybeast Gregar (Europe) (Virtual Console)" - description "Mega Man Battle Network 6 - Cybeast Gregar (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 6 - Cybeast Gregar (Europe) (Virtual Console).gba" size 8388608 crc bb62f987 sha1 7106dc0469898cb5768ec76c99029b24039d7605 ) + name "Megaman & Bass (USA) (Virtual Console)" + description "Megaman & Bass (USA) (Virtual Console)" + rom ( name "Megaman & Bass (USA) (Virtual Console).gba" size 8388608 crc b61f99d4 sha1 37db963a52aecec8018057cef3811860c3e889ed ) ) game ( - name "Mega Man Battle Network 6 - Cybeast Gregar (USA)" - description "Mega Man Battle Network 6 - Cybeast Gregar (USA)" - rom ( name "Mega Man Battle Network 6 - Cybeast Gregar (USA).gba" size 8388608 crc 79452182 sha1 89fe0bac4fd3d2ab1d2ca35e87ef8b1294a84cd6 ) + name "Megaman & Bass (Europe)" + description "Megaman & Bass (Europe)" + rom ( name "Megaman & Bass (Europe).gba" size 8388608 crc 01b4d95e sha1 5d6f8fb1f52803a54e9857e53d0b88173cf8f48a flags verified ) ) game ( - name "Mega Man Battle Network 6 - Cybeast Gregar (Europe)" - description "Mega Man Battle Network 6 - Cybeast Gregar (Europe)" - rom ( name "Mega Man Battle Network 6 - Cybeast Gregar (Europe).gba" size 8388608 crc 25c29efb sha1 4d2e441b1bcb8438c0bef2ae61d937de7d04af02 ) + name "Megaman & Bass (Europe) (Virtual Console)" + description "Megaman & Bass (Europe) (Virtual Console)" + rom ( name "Megaman & Bass (Europe) (Virtual Console).gba" size 8388608 crc 6e140bfa sha1 37a188a250ff553a71144f8bbe92098c85c8006d ) ) game ( - name "Mega Man Battle Network 6 - Cybeast Gregar (USA) (Virtual Console)" - description "Mega Man Battle Network 6 - Cybeast Gregar (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network 6 - Cybeast Gregar (USA) (Virtual Console).gba" size 8388608 crc e7e546fe sha1 0a8de8417b9939573daa04f95cdecdeaaf58a79e ) + name "Megaman & Bass (USA)" + description "Megaman & Bass (USA)" + rom ( name "Megaman & Bass (USA).gba" size 8388608 crc eea68c2e sha1 7610847b331870d4338e5ac894b36e55e2bec5a0 flags verified ) ) game ( - name "Mega Man Zero (USA) (Virtual Console)" - description "Mega Man Zero (USA) (Virtual Console)" - rom ( name "Mega Man Zero (USA) (Virtual Console).gba" size 8388608 crc 9c77209c sha1 7279705e24d32895dada7f5476f68bbc33dd643b ) + name "Megaman Zero (USA, Europe)" + description "Megaman Zero (USA, Europe)" + rom ( name "Megaman Zero (USA, Europe).gba" size 8388608 crc 9707d2a1 sha1 193b14120119162518a73c70876f0b8bffdbd96e flags verified ) ) game ( - name "Mega Man Zero (USA, Europe)" - description "Mega Man Zero (USA, Europe)" - rom ( name "Mega Man Zero (USA, Europe).gba" size 8388608 crc 9707d2a1 sha1 193b14120119162518a73c70876f0b8bffdbd96e flags verified ) + name "Megaman Zero (USA) (Virtual Console)" + description "Megaman Zero (USA) (Virtual Console)" + rom ( name "Megaman Zero (USA) (Virtual Console).gba" size 8388608 crc 9c77209c sha1 7279705e24d32895dada7f5476f68bbc33dd643b ) ) game ( - name "Mega Man Zero 2 (USA) (Virtual Console)" - description "Mega Man Zero 2 (USA) (Virtual Console)" - rom ( name "Mega Man Zero 2 (USA) (Virtual Console).gba" size 8388608 crc 30d051fe sha1 d7a1edd912f8e01bc442809b922bceaf5a1f0176 ) + name "Megaman Zero 2 (USA) (Virtual Console)" + description "Megaman Zero 2 (USA) (Virtual Console)" + rom ( name "Megaman Zero 2 (USA) (Virtual Console).gba" size 8388608 crc 30d051fe sha1 d7a1edd912f8e01bc442809b922bceaf5a1f0176 ) ) game ( - name "Mega Man Zero 2 (USA)" - description "Mega Man Zero 2 (USA)" - rom ( name "Mega Man Zero 2 (USA).gba" size 8388608 crc ce1e37bb sha1 c4d93a58f0f82c526dec8a3fdbda170336303689 ) + name "Megaman Zero 2 (USA)" + description "Megaman Zero 2 (USA)" + rom ( name "Megaman Zero 2 (USA).gba" size 8388608 crc ce1e37bb sha1 c4d93a58f0f82c526dec8a3fdbda170336303689 ) ) game ( - name "Mega Man Zero 2 (Europe)" - description "Mega Man Zero 2 (Europe)" - rom ( name "Mega Man Zero 2 (Europe).gba" size 8388608 crc 29a14b59 sha1 c55eca8f2c31fdf772f2605181d0b29815ea37a0 ) + name "Megaman Zero 2 (Europe)" + description "Megaman Zero 2 (Europe)" + rom ( name "Megaman Zero 2 (Europe).gba" size 8388608 crc 29a14b59 sha1 c55eca8f2c31fdf772f2605181d0b29815ea37a0 ) ) game ( - name "Mega Man Zero 2 (Europe) (Virtual Console)" - description "Mega Man Zero 2 (Europe) (Virtual Console)" - rom ( name "Mega Man Zero 2 (Europe) (Virtual Console).gba" size 8388608 crc d76f2d1c sha1 4ae9cd23f80d190f21002d2fdf4b8847c9452592 ) + name "Megaman Zero 2 (Europe) (Virtual Console)" + description "Megaman Zero 2 (Europe) (Virtual Console)" + rom ( name "Megaman Zero 2 (Europe) (Virtual Console).gba" size 8388608 crc d76f2d1c sha1 4ae9cd23f80d190f21002d2fdf4b8847c9452592 ) ) game ( - name "Mega Man Zero 3 (Europe) (Virtual Console)" - description "Mega Man Zero 3 (Europe) (Virtual Console)" - rom ( name "Mega Man Zero 3 (Europe) (Virtual Console).gba" size 8388608 crc 87e8656e sha1 8245ecb895caf0e0a2914f46bb79e4c9c5ba8c4a ) + name "Megaman Zero 3 (Europe) (Virtual Console)" + description "Megaman Zero 3 (Europe) (Virtual Console)" + rom ( name "Megaman Zero 3 (Europe) (Virtual Console).gba" size 8388608 crc 87e8656e sha1 8245ecb895caf0e0a2914f46bb79e4c9c5ba8c4a ) ) game ( - name "Mega Man Zero 3 (USA) (Virtual Console)" - description "Mega Man Zero 3 (USA) (Virtual Console)" - rom ( name "Mega Man Zero 3 (USA) (Virtual Console).gba" size 16777216 crc cbd24ac4 sha1 de5413481d823c53973cb29507567ef3dc6af399 ) + name "Megaman Zero 3 (USA) (Virtual Console)" + description "Megaman Zero 3 (USA) (Virtual Console)" + rom ( name "Megaman Zero 3 (USA) (Virtual Console).gba" size 16777216 crc cbd24ac4 sha1 de5413481d823c53973cb29507567ef3dc6af399 ) ) game ( - name "Mega Man Zero 3 (Europe)" - description "Mega Man Zero 3 (Europe)" - rom ( name "Mega Man Zero 3 (Europe).gba" size 8388608 crc b099577f sha1 edfcb606136951374f24aa8fd7e5b4e710300301 ) + name "Megaman Zero 3 (Europe)" + description "Megaman Zero 3 (Europe)" + rom ( name "Megaman Zero 3 (Europe).gba" size 8388608 crc b099577f sha1 edfcb606136951374f24aa8fd7e5b4e710300301 ) ) game ( - name "Mega Man Zero 3 (USA)" - description "Mega Man Zero 3 (USA)" - rom ( name "Mega Man Zero 3 (USA).gba" size 8388608 crc 2784f3f2 sha1 403a78f2cad93d41e4b0f2e520ce08026531664b ) + name "Megaman Zero 3 (USA)" + description "Megaman Zero 3 (USA)" + rom ( name "Megaman Zero 3 (USA).gba" size 8388608 crc 2784f3f2 sha1 403a78f2cad93d41e4b0f2e520ce08026531664b ) ) game ( - name "Mega Man Zero 4 (Europe) (Virtual Console)" - description "Mega Man Zero 4 (Europe) (Virtual Console)" - rom ( name "Mega Man Zero 4 (Europe) (Virtual Console).gba" size 16777216 crc ffda95be sha1 4424f9e08381a447192d87bbad20dab3c77740db ) + name "Megaman Zero 4 (Europe) (Virtual Console)" + description "Megaman Zero 4 (Europe) (Virtual Console)" + rom ( name "Megaman Zero 4 (Europe) (Virtual Console).gba" size 16777216 crc ffda95be sha1 4424f9e08381a447192d87bbad20dab3c77740db ) ) game ( - name "Mega Man Zero 4 (Europe)" - description "Mega Man Zero 4 (Europe)" - rom ( name "Mega Man Zero 4 (Europe).gba" size 16777216 crc b7f022b9 sha1 8e13b9ee89a2ed665212daa401ba9331ad11bda9 ) + name "Megaman Zero 4 (Europe)" + description "Megaman Zero 4 (Europe)" + rom ( name "Megaman Zero 4 (Europe).gba" size 16777216 crc b7f022b9 sha1 8e13b9ee89a2ed665212daa401ba9331ad11bda9 ) ) game ( - name "Mega Man Zero 4 (USA)" - description "Mega Man Zero 4 (USA)" - rom ( name "Mega Man Zero 4 (USA).gba" size 16777216 crc 7ee24793 sha1 596993205a1895a6f51e80749407fb069b907628 ) + name "Megaman Zero 4 (USA)" + description "Megaman Zero 4 (USA)" + rom ( name "Megaman Zero 4 (USA).gba" size 16777216 crc 7ee24793 sha1 596993205a1895a6f51e80749407fb069b907628 ) ) game ( - name "Mega Man Zero 4 (USA) (Virtual Console)" - description "Mega Man Zero 4 (USA) (Virtual Console)" - rom ( name "Mega Man Zero 4 (USA) (Virtual Console).gba" size 16777216 crc c4838cfa sha1 24a774446c8f652c78bb4bd938fc57c612db3d6c flags verified ) + name "Megaman Zero 4 (USA) (Virtual Console)" + description "Megaman Zero 4 (USA) (Virtual Console)" + rom ( name "Megaman Zero 4 (USA) (Virtual Console).gba" size 16777216 crc c4838cfa sha1 24a774446c8f652c78bb4bd938fc57c612db3d6c flags verified ) ) game ( @@ -11664,6 +11784,12 @@ game ( rom ( name "Mini Moni. - Onegai Ohoshi-sama! (Japan).gba" size 8388608 crc f11c35cc sha1 d34795cf3679c6f259177db52a138c0d6e9fcdfd ) ) +game ( + name "Minicraft (World) (Aftermarket) (Unl)" + description "Minicraft (World) (Aftermarket) (Unl)" + rom ( name "Minicraft (World) (Aftermarket) (Unl).gba" size 131072 crc e852c9e9 sha1 06faa5be11978666db6995d8fce40ce2c3641ce8 ) +) + game ( name "Minna de Puyo Puyo (Japan) (En,Ja)" description "Minna de Puyo Puyo (Japan) (En,Ja)" @@ -11773,9 +11899,9 @@ game ( ) game ( - name "Misfortune Advance (World) (Aftermarket) (Homebrew)" - description "Misfortune Advance (World) (Aftermarket) (Homebrew)" - rom ( name "Misfortune Advance (World) (Aftermarket) (Homebrew).gba" size 5125100 crc 18f2166d sha1 68e215025ac963220f16ea3c100e51698f97c4eb ) + name "Misfortune Advance (World) (Aftermarket) (Unl)" + description "Misfortune Advance (World) (Aftermarket) (Unl)" + rom ( name "Misfortune Advance (World) (Aftermarket) (Unl).gba" size 5125100 crc 18f2166d sha1 68e215025ac963220f16ea3c100e51698f97c4eb ) ) game ( @@ -11803,15 +11929,9 @@ game ( ) game ( - name "MLB SlugFest 20-04 (USA)" - description "MLB SlugFest 20-04 (USA)" - rom ( name "MLB SlugFest 20-04 (USA).gba" size 4194304 crc a4e12d4b sha1 cd60f0aacdada987f7935d7a5fa2cd3242abc145 ) -) - -game ( - name "Mo Jie Qibing (Taiwan) (Unl)" - description "Mo Jie Qibing (Taiwan) (Unl)" - rom ( name "Mo Jie Qibing (Taiwan) (Unl).gba" size 4194304 crc 8ee0ed6f sha1 e9b68e1f7584892b366a6a4b3a94feeb8729e4c5 ) + name "MLB SlugFest 2004 (USA)" + description "MLB SlugFest 2004 (USA)" + rom ( name "MLB SlugFest 2004 (USA).gba" size 4194304 crc a4e12d4b sha1 cd60f0aacdada987f7935d7a5fa2cd3242abc145 ) ) game ( @@ -11832,6 +11952,12 @@ game ( rom ( name "Moero!! Jaleco Collection (Japan).gba" size 4194304 crc 6da36e82 sha1 52d1cbeed52de62147d13a667a9631f6d8a24865 ) ) +game ( + name "Mojie Qibing (Taiwan) (Unl)" + description "Mojie Qibing (Taiwan) (Unl)" + rom ( name "Mojie Qibing (Taiwan) (Unl).gba" size 4194304 crc 8ee0ed6f sha1 e9b68e1f7584892b366a6a4b3a94feeb8729e4c5 ) +) + game ( name "Momotarou Dentetsu G - Gold Deck o Tsukure! (Japan)" description "Momotarou Dentetsu G - Gold Deck o Tsukure! (Japan)" @@ -12030,12 +12156,30 @@ game ( rom ( name "Monsters, Inc. (Europe) (En,Es,Nl).gba" size 4194304 crc d13177e0 sha1 36645b8de074f0b27dbe61528e70ae3af28fb3b9 ) ) +game ( + name "Mooncat's Trio (World) (Aftermarket) (Unl)" + description "Mooncat's Trio (World) (Aftermarket) (Unl)" + rom ( name "Mooncat's Trio (World) (Aftermarket) (Unl).gba" size 1175680 crc b05d5339 sha1 890de7d73723d235d9762e4866d569d3554d1480 flags verified ) +) + +game ( + name "Mooncat's Trio (World) (Beta) (Aftermarket) (Unl)" + description "Mooncat's Trio (World) (Beta) (Aftermarket) (Unl)" + rom ( name "Mooncat's Trio (World) (Beta) (Aftermarket) (Unl).gba" size 504220 crc cd3328ee sha1 c3ea5247f32c428bbcfd5c087218fc56779e3106 ) +) + game ( name "Moorhen 3 - The Chicken Chase! (Europe) (En,Fr,De,Es,It)" description "Moorhen 3 - The Chicken Chase! (Europe) (En,Fr,De,Es,It)" rom ( name "Moorhen 3 - The Chicken Chase! (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc a16c10a8 sha1 b743dd4b3f1bfa169fed64f86766d5a616c5ee60 ) ) +game ( + name "Moorhuhn Jagd (Europe) (Proto)" + description "Moorhuhn Jagd (Europe) (Proto)" + rom ( name "Moorhuhn Jagd (Europe) (Proto).gba" size 8388608 crc d3f841f3 sha1 a2a698345251b9487c5387a665b8c49c3af7ccd5 ) +) + game ( name "Morita Shougi Advance (Japan)" description "Morita Shougi Advance (Japan)" @@ -12252,18 +12396,18 @@ game ( rom ( name "Mummy, The (USA) (En,Fr,De,Es,It).gba" size 4194304 crc 55bf350b sha1 cc833b1398a6ea87d31712806757c2fe52fadd31 ) ) -game ( - name "Muppet Pinball Mayhem (USA)" - description "Muppet Pinball Mayhem (USA)" - rom ( name "Muppet Pinball Mayhem (USA).gba" size 4194304 crc 58575f65 sha1 cbf381536ab8d8ea02814a77f46db13cb625c72f ) -) - game ( name "Muppet Pinball Mayhem (Europe)" description "Muppet Pinball Mayhem (Europe)" rom ( name "Muppet Pinball Mayhem (Europe).gba" size 4194304 crc 8d5034d7 sha1 1c45790873605defc999b45ee145013e7e406a5d ) ) +game ( + name "Muppet Pinball Mayhem (USA)" + description "Muppet Pinball Mayhem (USA)" + rom ( name "Muppet Pinball Mayhem (USA).gba" size 4194304 crc 58575f65 sha1 cbf381536ab8d8ea02814a77f46db13cb625c72f ) +) + game ( name "Muppets, The - On with the Show! (USA, Europe) (En,Fr,De,Es,It,Nl)" description "Muppets, The - On with the Show! (USA, Europe) (En,Fr,De,Es,It,Nl)" @@ -12342,6 +12486,12 @@ game ( rom ( name "Nakayoshi Pet Advance Series 4 - Kawaii Koinu Mini - Wanko to Asobou!! Kogata-ken (Japan).gba" size 4194304 crc ae2a69f3 sha1 0c78a24d5a8a296d05a8b47cf5623de0f031748e ) ) +game ( + name "Nakayoshi Youchien - Sukoyaka Enji Ikusei Game (Japan)" + description "Nakayoshi Youchien - Sukoyaka Enji Ikusei Game (Japan)" + rom ( name "Nakayoshi Youchien - Sukoyaka Enji Ikusei Game (Japan).gba" size 4194304 crc 1276a95c sha1 77743de7541e0a9d09e3aac864c4e3f9f1a51d2b ) +) + game ( name "Nakayoshi Youchien - Sukoyaka Enji Ikusei Game (Japan) (Rev 1)" description "Nakayoshi Youchien - Sukoyaka Enji Ikusei Game (Japan) (Rev 1)" @@ -12349,9 +12499,9 @@ game ( ) game ( - name "Nakayoshi Youchien - Sukoyaka Enji Ikusei Game (Japan)" - description "Nakayoshi Youchien - Sukoyaka Enji Ikusei Game (Japan)" - rom ( name "Nakayoshi Youchien - Sukoyaka Enji Ikusei Game (Japan).gba" size 4194304 crc 1276a95c sha1 77743de7541e0a9d09e3aac864c4e3f9f1a51d2b ) + name "Namco Museum (Europe)" + description "Namco Museum (Europe)" + rom ( name "Namco Museum (Europe).gba" size 4194304 crc bb82460a sha1 3e84508a0d2362a0abf31dede1d55b865566213c ) ) game ( @@ -12366,12 +12516,6 @@ game ( rom ( name "Namco Museum (Japan) (En).gba" size 4194304 crc 818f4e4b sha1 54d01cf5ac57f5fb454c0e188de9e60f63854a97 ) ) -game ( - name "Namco Museum (Europe)" - description "Namco Museum (Europe)" - rom ( name "Namco Museum (Europe).gba" size 4194304 crc bb82460a sha1 3e84508a0d2362a0abf31dede1d55b865566213c ) -) - game ( name "Namco Museum - 50th Anniversary (USA)" description "Namco Museum - 50th Anniversary (USA)" @@ -12492,6 +12636,12 @@ game ( rom ( name "NBA Jam 2002 (USA, Europe).gba" size 4194304 crc ca428a7a sha1 38de98758669b120b895661bec3882c3474cf47e ) ) +game ( + name "Need for Speed - Carbon - Own the City (USA, Europe) (En,Fr,De,Es,It)" + description "Need for Speed - Carbon - Own the City (USA, Europe) (En,Fr,De,Es,It)" + rom ( name "Need for Speed - Carbon - Own the City (USA, Europe) (En,Fr,De,Es,It).gba" size 8388608 crc f4c0d140 sha1 e5298b2482a769aa6458955cd6b18db3ac3f3a20 flags verified ) +) + game ( name "Need for Speed - Most Wanted (USA, Europe) (En,Fr,De,It)" description "Need for Speed - Most Wanted (USA, Europe) (En,Fr,De,It)" @@ -12513,7 +12663,7 @@ game ( game ( name "Need for Speed - Underground (USA, Europe) (En,Fr,De,It)" description "Need for Speed - Underground (USA, Europe) (En,Fr,De,It)" - rom ( name "Need for Speed - Underground (USA, Europe) (En,Fr,De,It).gba" size 8388608 crc 828020e9 sha1 ddd304481617a748aa9f37535908a37452dd2f03 ) + rom ( name "Need for Speed - Underground (USA, Europe) (En,Fr,De,It).gba" size 8388608 crc 828020e9 sha1 ddd304481617a748aa9f37535908a37452dd2f03 flags verified ) ) game ( @@ -12522,12 +12672,6 @@ game ( rom ( name "Need for Speed - Underground 2 (USA, Europe) (En,Fr,De,It).gba" size 8388608 crc 9a0c5090 sha1 f772000fbdfb84d8d5de9f69bd9c0de551389929 flags verified ) ) -game ( - name "Need for Speed Carbon - Own the City (USA, Europe) (En,Fr,De,Es,It)" - description "Need for Speed Carbon - Own the City (USA, Europe) (En,Fr,De,Es,It)" - rom ( name "Need for Speed Carbon - Own the City (USA, Europe) (En,Fr,De,Es,It).gba" size 8388608 crc f4c0d140 sha1 e5298b2482a769aa6458955cd6b18db3ac3f3a20 flags verified ) -) - game ( name "Nekketsu Monogatari Advance (Japan) (Demo) (Aftermarket) (Unl)" description "Nekketsu Monogatari Advance (Japan) (Demo) (Aftermarket) (Unl)" @@ -12559,15 +12703,15 @@ game ( ) game ( - name "NFL Blitz 20-02 (USA)" - description "NFL Blitz 20-02 (USA)" - rom ( name "NFL Blitz 20-02 (USA).gba" size 4194304 crc 8c748dcb sha1 590196c49eb4b33d933db60d58a1d0efd94b3f7b ) + name "NFL Blitz 2002 (USA)" + description "NFL Blitz 2002 (USA)" + rom ( name "NFL Blitz 2002 (USA).gba" size 4194304 crc 8c748dcb sha1 590196c49eb4b33d933db60d58a1d0efd94b3f7b ) ) game ( - name "NFL Blitz 20-03 (USA)" - description "NFL Blitz 20-03 (USA)" - rom ( name "NFL Blitz 20-03 (USA).gba" size 4194304 crc e0b137e7 sha1 6d63871abf8016248ff7f8777d762e7c75877d40 ) + name "NFL Blitz 2003 (USA)" + description "NFL Blitz 2003 (USA)" + rom ( name "NFL Blitz 2003 (USA).gba" size 4194304 crc e0b137e7 sha1 6d63871abf8016248ff7f8777d762e7c75877d40 ) ) game ( @@ -12577,9 +12721,9 @@ game ( ) game ( - name "NHL Hitz 20-03 (USA)" - description "NHL Hitz 20-03 (USA)" - rom ( name "NHL Hitz 20-03 (USA).gba" size 4194304 crc 4f1adf75 sha1 4a88ca2962fdfb7ea1b91b9e2bffe8c69ff96519 ) + name "NHL Hitz 2003 (USA)" + description "NHL Hitz 2003 (USA)" + rom ( name "NHL Hitz 2003 (USA).gba" size 4194304 crc 4f1adf75 sha1 4a88ca2962fdfb7ea1b91b9e2bffe8c69ff96519 ) ) game ( @@ -12645,7 +12789,7 @@ game ( game ( name "Nintendo MP3 Player (Europe) (En,Fr,De,Es,It)" description "Nintendo MP3 Player (Europe) (En,Fr,De,Es,It)" - rom ( name "Nintendo MP3 Player (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 867bec76 sha1 0c1eb4af684f3b428766b10cdbbe62fd726e5e27 ) + rom ( name "Nintendo MP3 Player (Europe) (En,Fr,De,Es,It).gba" size 1048576 crc 16e5d0f2 sha1 b7c517f1cb5860fec8beaa4e819bcffa8ce95a5b flags verified ) ) game ( @@ -12723,7 +12867,7 @@ game ( game ( name "Oddworld - Munch's Oddysee (USA, Europe)" description "Oddworld - Munch's Oddysee (USA, Europe)" - rom ( name "Oddworld - Munch's Oddysee (USA, Europe).gba" size 8388608 crc 97367887 sha1 cc6bf19e5d6a35c2a745bea4e3b2a210a9ebd296 flags verified ) + rom ( name "Oddworld - Munch's Oddysee (USA, Europe).gba" size 8388608 crc 97367887 sha1 cc6bf19e5d6a35c2a745bea4e3b2a210a9ebd296 ) ) game ( @@ -12853,9 +12997,9 @@ game ( ) game ( - name "Oshare Princess 2 (Japan)" - description "Oshare Princess 2 (Japan)" - rom ( name "Oshare Princess 2 (Japan).gba" size 8388608 crc 8383fcda sha1 649001f85cdcd2515183fc1364d08de52c918feb ) + name "Oshare Princess 2 + Doubutsu Kyaranabi Uranai (Japan)" + description "Oshare Princess 2 + Doubutsu Kyaranabi Uranai (Japan)" + rom ( name "Oshare Princess 2 + Doubutsu Kyaranabi Uranai (Japan).gba" size 8388608 crc 8383fcda sha1 649001f85cdcd2515183fc1364d08de52c918feb ) ) game ( @@ -12931,9 +13075,9 @@ game ( ) game ( - name "Overstorm (Unknown) (Proto)" - description "Overstorm (Unknown) (Proto)" - rom ( name "Overstorm (Unknown) (Proto).gba" size 3470916 crc ee764cf6 sha1 88129a07e9dfb556c38a5c61c6e96fbfb4d23fea ) + name "Overstorm (Europe) (Proto)" + description "Overstorm (Europe) (Proto)" + rom ( name "Overstorm (Europe) (Proto).gba" size 3470916 crc ee764cf6 sha1 88129a07e9dfb556c38a5c61c6e96fbfb4d23fea ) ) game ( @@ -13033,9 +13177,9 @@ game ( ) game ( - name "Pac-Man World 2 (USA) (Beta)" - description "Pac-Man World 2 (USA) (Beta)" - rom ( name "Pac-Man World 2 (USA) (Beta).gba" size 4194304 crc b645b5ef sha1 82fe986f205f733c29bc00cd96ddf3ec46098fc6 ) + name "Pac-Man World 2 (USA) (Beta) (2005-04-15)" + description "Pac-Man World 2 (USA) (Beta) (2005-04-15)" + rom ( name "Pac-Man World 2 (USA) (Beta) (2005-04-15).gba" size 4194304 crc b645b5ef sha1 82fe986f205f733c29bc00cd96ddf3ec46098fc6 ) ) game ( @@ -13153,9 +13297,9 @@ game ( ) game ( - name "Pferd & Pony 2 in 1 (Germany)" - description "Pferd & Pony 2 in 1 (Germany)" - rom ( name "Pferd & Pony 2 in 1 (Germany).gba" size 8388608 crc 965391ea sha1 ec0307c819b41ff0f3f5aa9212a6e333b2242675 ) + name "Pferd & Pony - Mein Pferdehof & Pferd and Pony - Lass Uns Reiten 2 (Germany)" + description "Pferd & Pony - Mein Pferdehof & Pferd and Pony - Lass Uns Reiten 2 (Germany)" + rom ( name "Pferd & Pony - Mein Pferdehof & Pferd and Pony - Lass Uns Reiten 2 (Germany).gba" size 8388608 crc 965391ea sha1 ec0307c819b41ff0f3f5aa9212a6e333b2242675 ) ) game ( @@ -13179,7 +13323,7 @@ game ( game ( name "Phantasy Star Collection (USA)" description "Phantasy Star Collection (USA)" - rom ( name "Phantasy Star Collection (USA).gba" size 8388608 crc e5a7fe17 sha1 9f2dc591c9b1526f9f965b1c375fb4ea7101fd16 ) + rom ( name "Phantasy Star Collection (USA).gba" size 8388608 crc e5a7fe17 sha1 9f2dc591c9b1526f9f965b1c375fb4ea7101fd16 flags verified ) ) game ( @@ -13323,13 +13467,13 @@ game ( game ( name "Pirates of the Caribbean - Dead Man's Chest (USA, Europe) (En,Fr,De,Es,It)" description "Pirates of the Caribbean - Dead Man's Chest (USA, Europe) (En,Fr,De,Es,It)" - rom ( name "Pirates of the Caribbean - Dead Man's Chest (USA, Europe) (En,Fr,De,Es,It).gba" size 16777216 crc d1a67be8 sha1 3a11c76edaad8fa0845b7da55e8ed26e26e09883 ) + rom ( name "Pirates of the Caribbean - Dead Man's Chest (USA, Europe) (En,Fr,De,Es,It).gba" size 16777216 crc d1a67be8 sha1 3a11c76edaad8fa0845b7da55e8ed26e26e09883 flags verified ) ) game ( name "Pirates of the Caribbean - The Curse of the Black Pearl (USA) (En,Fr,De,Es,It)" description "Pirates of the Caribbean - The Curse of the Black Pearl (USA) (En,Fr,De,Es,It)" - rom ( name "Pirates of the Caribbean - The Curse of the Black Pearl (USA) (En,Fr,De,Es,It).gba" size 8388608 crc 8f15bf4c sha1 034e5206c3ed8275e4bbb249edf305d14948bf8f ) + rom ( name "Pirates of the Caribbean - The Curse of the Black Pearl (USA) (En,Fr,De,Es,It).gba" size 8388608 crc 8f15bf4c sha1 034e5206c3ed8275e4bbb249edf305d14948bf8f flags verified ) ) game ( @@ -13416,18 +13560,6 @@ game ( rom ( name "Pocket Dogs (USA).gba" size 8388608 crc beddc67f sha1 79a90b119ea84c5812575d20ba6107be7edbfee7 ) ) -game ( - name "Pocket Monster Diamond Tomodachi to Issho Ni! Present Campaign Senyou Cartridge (Japan) [b]" - description "Pocket Monster Diamond Tomodachi to Issho Ni! Present Campaign Senyou Cartridge (Japan) [b]" - rom ( name "Pocket Monster Diamond Tomodachi to Issho Ni! Present Campaign Senyou Cartridge (Japan) [b].gba" size 2097152 crc 393a77e8 sha1 147d115e4d5d758ec386dce7b0063321a1dfd583 flags baddump ) -) - -game ( - name "Pocket Monster Pearl Tomodachi to Issho ni! Present Campaign Senyou Cartridge (Japan) [b]" - description "Pocket Monster Pearl Tomodachi to Issho ni! Present Campaign Senyou Cartridge (Japan) [b]" - rom ( name "Pocket Monster Pearl Tomodachi to Issho ni! Present Campaign Senyou Cartridge (Japan) [b].gba" size 2097152 crc 21e120be sha1 ebc831ef1e00e204975104cfbd0fd526e0f6e435 flags baddump ) -) - game ( name "Pocket Monsters - Emerald (Japan)" description "Pocket Monsters - Emerald (Japan)" @@ -13494,6 +13626,18 @@ game ( rom ( name "Pocket Monsters - Sapphire (Japan) (Rev 1).gba" size 8388608 crc 01bd60e3 sha1 01f509671445965236ac4c6b5a354fe2f1e69f13 flags verified ) ) +game ( + name "Pocket Monsters Diamond - Tomodachi to Issho ni! - Present Campaign Senyou Cartridge (Japan) [b]" + description "Pocket Monsters Diamond - Tomodachi to Issho ni! - Present Campaign Senyou Cartridge (Japan) [b]" + rom ( name "Pocket Monsters Diamond - Tomodachi to Issho ni! - Present Campaign Senyou Cartridge (Japan) [b].gba" size 2097152 crc 393a77e8 sha1 147d115e4d5d758ec386dce7b0063321a1dfd583 flags baddump ) +) + +game ( + name "Pocket Monsters Pearl - Tomodachi to Issho ni! - Present Campaign Senyou Cartridge (Japan) [b]" + description "Pocket Monsters Pearl - Tomodachi to Issho ni! - Present Campaign Senyou Cartridge (Japan) [b]" + rom ( name "Pocket Monsters Pearl - Tomodachi to Issho ni! - Present Campaign Senyou Cartridge (Japan) [b].gba" size 2097152 crc 21e120be sha1 ebc831ef1e00e204975104cfbd0fd526e0f6e435 flags baddump ) +) + game ( name "Pocket Music (Europe) (En,Fr,De,Es,It)" description "Pocket Music (Europe) (En,Fr,De,Es,It)" @@ -13611,7 +13755,7 @@ game ( game ( name "Pokemon - Edicion Rubi (Spain)" description "Pokemon - Edicion Rubi (Spain)" - rom ( name "Pokemon - Edicion Rubi (Spain).gba" size 16777216 crc eb0729cf sha1 1f49f7289253dcbfecbc4c5ba3e67aa0652ec83c ) + rom ( name "Pokemon - Edicion Rubi (Spain).gba" size 16777216 crc eb0729cf sha1 1f49f7289253dcbfecbc4c5ba3e67aa0652ec83c flags verified ) ) game ( @@ -13651,9 +13795,9 @@ game ( ) game ( - name "Pokemon - FireRed Version (USA)" - description "Pokemon - FireRed Version (USA)" - rom ( name "Pokemon - FireRed Version (USA).gba" size 16777216 crc dd88761c sha1 41cb23d8dccc8ebd7c649cd8fbb58eeace6e2fdc flags verified ) + name "Pokemon - FireRed Version (USA, Europe)" + description "Pokemon - FireRed Version (USA, Europe)" + rom ( name "Pokemon - FireRed Version (USA, Europe).gba" size 16777216 crc dd88761c sha1 41cb23d8dccc8ebd7c649cd8fbb58eeace6e2fdc flags verified ) ) game ( @@ -13669,9 +13813,9 @@ game ( ) game ( - name "Pokemon - LeafGreen Version (USA)" - description "Pokemon - LeafGreen Version (USA)" - rom ( name "Pokemon - LeafGreen Version (USA).gba" size 16777216 crc d69c96cc sha1 574fa542ffebb14be69902d1d36f1ec0a4afd71e flags verified ) + name "Pokemon - LeafGreen Version (USA, Europe)" + description "Pokemon - LeafGreen Version (USA, Europe)" + rom ( name "Pokemon - LeafGreen Version (USA, Europe).gba" size 16777216 crc d69c96cc sha1 574fa542ffebb14be69902d1d36f1ec0a4afd71e flags verified ) ) game ( @@ -13717,9 +13861,9 @@ game ( ) game ( - name "Pokemon - Ruby Version (USA)" - description "Pokemon - Ruby Version (USA)" - rom ( name "Pokemon - Ruby Version (USA).gba" size 16777216 crc f0815ee7 sha1 f28b6ffc97847e94a6c21a63cacf633ee5c8df1e flags verified ) + name "Pokemon - Ruby Version (USA, Europe)" + description "Pokemon - Ruby Version (USA, Europe)" + rom ( name "Pokemon - Ruby Version (USA, Europe).gba" size 16777216 crc f0815ee7 sha1 f28b6ffc97847e94a6c21a63cacf633ee5c8df1e flags verified ) ) game ( @@ -13747,9 +13891,9 @@ game ( ) game ( - name "Pokemon - Sapphire Version (USA)" - description "Pokemon - Sapphire Version (USA)" - rom ( name "Pokemon - Sapphire Version (USA).gba" size 16777216 crc 554dedc4 sha1 3ccbbd45f8553c36463f13b938e833f652b793e4 flags verified ) + name "Pokemon - Sapphire Version (USA, Europe)" + description "Pokemon - Sapphire Version (USA, Europe)" + rom ( name "Pokemon - Sapphire Version (USA, Europe).gba" size 16777216 crc 554dedc4 sha1 3ccbbd45f8553c36463f13b938e833f652b793e4 flags verified ) ) game ( @@ -13986,12 +14130,6 @@ game ( rom ( name "Power Poke Dash (Japan).gba" size 8388608 crc 59e7ab4c sha1 598db3dd414ec3acb8bea47192761816e401df15 ) ) -game ( - name "Power Pro Kun Pocket 1, 2 (Japan) (Promo)" - description "Power Pro Kun Pocket 1, 2 (Japan) (Promo)" - rom ( name "Power Pro Kun Pocket 1, 2 (Japan) (Promo).gba" size 8388608 crc 46493fa1 sha1 43badcefe8cad127f8f8655ee89262517399a20b ) -) - game ( name "Power Pro Kun Pocket 1, 2 (Japan)" description "Power Pro Kun Pocket 1, 2 (Japan)" @@ -14023,9 +14161,9 @@ game ( ) game ( - name "Power Pro Kun Pocket 5 (Japan) (Promo)" - description "Power Pro Kun Pocket 5 (Japan) (Promo)" - rom ( name "Power Pro Kun Pocket 5 (Japan) (Promo).gba" size 8388608 crc 969ac691 sha1 f9fd4c8ee4f274c751551f056dc868abd0355390 ) + name "Power Pro Kun Pocket 5 (Japan) (Demo) (Kiosk)" + description "Power Pro Kun Pocket 5 (Japan) (Demo) (Kiosk)" + rom ( name "Power Pro Kun Pocket 5 (Japan) (Demo) (Kiosk).gba" size 8388608 crc 969ac691 sha1 f9fd4c8ee4f274c751551f056dc868abd0355390 ) ) game ( @@ -14040,12 +14178,6 @@ game ( rom ( name "Power Pro Kun Pocket 5 (Japan).gba" size 8388608 crc 06f0f8d4 sha1 15bc24f0f47e0317c849c1c33a36357a43def74c ) ) -game ( - name "Power Pro Kun Pocket 6 (Japan) (Promo)" - description "Power Pro Kun Pocket 6 (Japan) (Promo)" - rom ( name "Power Pro Kun Pocket 6 (Japan) (Promo).gba" size 8388608 crc 023a9a75 sha1 066d2086d776c66aa566eac211a0e9b45f1942f6 ) -) - game ( name "Power Pro Kun Pocket 6 (Japan)" description "Power Pro Kun Pocket 6 (Japan)" @@ -14155,9 +14287,9 @@ game ( ) game ( - name "Prehistorik Man (USA) (En,Fr,De,Es,It,Nl) (Beta)" - description "Prehistorik Man (USA) (En,Fr,De,Es,It,Nl) (Beta)" - rom ( name "Prehistorik Man (USA) (En,Fr,De,Es,It,Nl) (Beta).gba" size 4194304 crc abb50d93 sha1 dc789663025c8e5122622ce4fc6d0953a5656bd6 ) + name "Prehistorik Man (USA, Europe) (En,Fr,De,Es,It,Nl) (Beta)" + description "Prehistorik Man (USA, Europe) (En,Fr,De,Es,It,Nl) (Beta)" + rom ( name "Prehistorik Man (USA, Europe) (En,Fr,De,Es,It,Nl) (Beta).gba" size 4194304 crc abb50d93 sha1 dc789663025c8e5122622ce4fc6d0953a5656bd6 ) ) game ( @@ -14383,9 +14515,9 @@ game ( ) game ( - name "Racing Fever (Europe) (En,De,Es,It)" - description "Racing Fever (Europe) (En,De,Es,It)" - rom ( name "Racing Fever (Europe) (En,De,Es,It).gba" size 4194304 crc e40ec737 sha1 9bb5c036bca8f0d2e06013cac7ea6148a4f7f512 ) + name "R3D Demo (Europe) (Demo)" + description "R3D Demo (Europe) (Demo)" + rom ( name "R3D Demo (Europe) (Demo).gba" size 8388608 crc 3e7f9100 sha1 48261a7984808f573a1b07be647c02b941af6247 ) ) game ( @@ -14394,6 +14526,18 @@ game ( rom ( name "Racing Fever (France).gba" size 4194304 crc 4f61dedb sha1 67eb3051c7527692cfd7515ea367796fab3a1362 ) ) +game ( + name "Racing Fever (Europe) (En,De,Es,It)" + description "Racing Fever (Europe) (En,De,Es,It)" + rom ( name "Racing Fever (Europe) (En,De,Es,It).gba" size 4194304 crc e40ec737 sha1 9bb5c036bca8f0d2e06013cac7ea6148a4f7f512 ) +) + +game ( + name "Racing Gears Advance (USA) (Beta)" + description "Racing Gears Advance (USA) (Beta)" + rom ( name "Racing Gears Advance (USA) (Beta).gba" size 16777216 crc 86a05f93 sha1 d258526ce1dcfbed51b745a762f148c9bea01154 ) +) + game ( name "Racing Gears Advance (Europe) (En,Fr,De,Es,It)" description "Racing Gears Advance (Europe) (En,Fr,De,Es,It)" @@ -14409,7 +14553,7 @@ game ( game ( name "Rampage - Puzzle Attack (USA, Europe)" description "Rampage - Puzzle Attack (USA, Europe)" - rom ( name "Rampage - Puzzle Attack (USA, Europe).gba" size 4194304 crc 8eca2b0f sha1 35c4ace4044f2171dc90a1195ec83ae57324fc83 ) + rom ( name "Rampage - Puzzle Attack (USA, Europe).gba" size 4194304 crc 8eca2b0f sha1 35c4ace4044f2171dc90a1195ec83ae57324fc83 flags verified ) ) game ( @@ -14418,12 +14562,6 @@ game ( rom ( name "Rapala Pro Fishing (USA, Europe).gba" size 4194304 crc 964d39a7 sha1 e3df6fe7a447ab30faf6fd7d4c57e88f987a5639 ) ) -game ( - name "Ratatouille (Greece) (En)" - description "Ratatouille (Greece) (En)" - rom ( name "Ratatouille (Greece) (En).gba" size 8388608 crc 3e1711c7 sha1 58f2fa1d7e531c7345b11db268e1bbc10372bebc ) -) - game ( name "Ratatouille (USA)" description "Ratatouille (USA)" @@ -14448,6 +14586,12 @@ game ( rom ( name "Ratatouille (Europe) (En,It,Sv,No,Da).gba" size 8388608 crc 82f9c596 sha1 a5c4a636979c06670a81428c778ff76ed2c65eee ) ) +game ( + name "Ratatouille (Greece) (En)" + description "Ratatouille (Greece) (En)" + rom ( name "Ratatouille (Greece) (En).gba" size 8388608 crc 3e1711c7 sha1 58f2fa1d7e531c7345b11db268e1bbc10372bebc ) +) + game ( name "Rave Master - Special Attack Force! (USA)" description "Rave Master - Special Attack Force! (USA)" @@ -14496,18 +14640,6 @@ game ( rom ( name "Rayman 3 (Europe) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi) (Beta).gba" size 8388608 crc 8f34b14f sha1 064f6b09732f4225f5964d0e8bd386affb83e3d7 ) ) -game ( - name "Rayman 3 (Europe) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi)" - description "Rayman 3 (Europe) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi)" - rom ( name "Rayman 3 (Europe) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi).gba" size 8388608 crc 29f83314 sha1 21b7296d29486ccab68bdfa45ab24b9d370d052c ) -) - -game ( - name "Rayman 3 (USA) (En,Fr,Es)" - description "Rayman 3 (USA) (En,Fr,Es)" - rom ( name "Rayman 3 (USA) (En,Fr,Es).gba" size 8388608 crc d1613266 sha1 1a8fb488aac9af4d3d42046750bdf429f48ac391 ) -) - game ( name "Rayman 3 (USA) (Beta)" description "Rayman 3 (USA) (Beta)" @@ -14515,9 +14647,15 @@ game ( ) game ( - name "Rayman Advance (Europe) (En,Fr,De,Es,It) (Beta)" - description "Rayman Advance (Europe) (En,Fr,De,Es,It) (Beta)" - rom ( name "Rayman Advance (Europe) (En,Fr,De,Es,It) (Beta).gba" size 8388608 crc 4b1b4e02 sha1 2a473e6a0664412ead33aa9889912dc25db93714 ) + name "Rayman 3 (Europe) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi)" + description "Rayman 3 (Europe) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi)" + rom ( name "Rayman 3 (Europe) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi).gba" size 8388608 crc 29f83314 sha1 21b7296d29486ccab68bdfa45ab24b9d370d052c flags verified ) +) + +game ( + name "Rayman 3 (USA) (En,Fr,Es)" + description "Rayman 3 (USA) (En,Fr,Es)" + rom ( name "Rayman 3 (USA) (En,Fr,Es).gba" size 8388608 crc d1613266 sha1 1a8fb488aac9af4d3d42046750bdf429f48ac391 ) ) game ( @@ -14532,6 +14670,12 @@ game ( rom ( name "Rayman Advance (Europe) (En,Fr,De,Es,It).gba" size 8388608 crc b43783b4 sha1 6a32d270848ae302a3e4fb873e53b663c2db4811 ) ) +game ( + name "Rayman Advance (Europe) (En,Fr,De,Es,It) (Beta)" + description "Rayman Advance (Europe) (En,Fr,De,Es,It) (Beta)" + rom ( name "Rayman Advance (Europe) (En,Fr,De,Es,It) (Beta).gba" size 8388608 crc 4b1b4e02 sha1 2a473e6a0664412ead33aa9889912dc25db93714 ) +) + game ( name "Rayman IV (USA) (Unl)" description "Rayman IV (USA) (Unl)" @@ -14583,7 +14727,13 @@ game ( game ( name "Ready 2 Rumble Boxing - Round 2 (Europe) (En,Fr,De)" description "Ready 2 Rumble Boxing - Round 2 (Europe) (En,Fr,De)" - rom ( name "Ready 2 Rumble Boxing - Round 2 (Europe) (En,Fr,De).gba" size 4194304 crc e418e962 sha1 57d06e405b281f68800b986bd9ff1257e4466939 ) + rom ( name "Ready 2 Rumble Boxing - Round 2 (Europe) (En,Fr,De).gba" size 4194304 crc e418e962 sha1 57d06e405b281f68800b986bd9ff1257e4466939 flags verified ) +) + +game ( + name "Rebelstar - Tactical Command (Europe) (En,Fr,De,Es,It) (Beta)" + description "Rebelstar - Tactical Command (Europe) (En,Fr,De,Es,It) (Beta)" + rom ( name "Rebelstar - Tactical Command (Europe) (En,Fr,De,Es,It) (Beta).gba" size 4194304 crc 2189021c sha1 b6e2167272a68bd4e3833cccd68929d50a27a426 ) ) game ( @@ -14598,12 +14748,6 @@ game ( rom ( name "Rebelstar - Tactical Command (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc e448c5d4 sha1 669c47e62f2388ce0ba676f3dc641b5ad2ddb5c5 flags verified ) ) -game ( - name "Rebelstar - Tactical Command (Europe) (En,Fr,De,Es,It) (Beta)" - description "Rebelstar - Tactical Command (Europe) (En,Fr,De,Es,It) (Beta)" - rom ( name "Rebelstar - Tactical Command (Europe) (En,Fr,De,Es,It) (Beta).gba" size 4194304 crc 2189021c sha1 b6e2167272a68bd4e3833cccd68929d50a27a426 ) -) - game ( name "Recca no Honoo - The Game (Japan)" description "Recca no Honoo - The Game (Japan)" @@ -14635,15 +14779,15 @@ game ( ) game ( - name "Ren Zhe Shen Gui 2 (Taiwan) (Unl)" - description "Ren Zhe Shen Gui 2 (Taiwan) (Unl)" - rom ( name "Ren Zhe Shen Gui 2 (Taiwan) (Unl).gba" size 33554432 crc f4641711 sha1 efe55f22035576ebe0be6012c1b9d32238bb0ca2 ) + name "Renzhe Shen Gui 2 (Taiwan) (Unl)" + description "Renzhe Shen Gui 2 (Taiwan) (Unl)" + rom ( name "Renzhe Shen Gui 2 (Taiwan) (Unl).gba" size 33554432 crc f4641711 sha1 efe55f22035576ebe0be6012c1b9d32238bb0ca2 ) ) game ( name "Rescue Heroes - Billy Blazes! (USA)" description "Rescue Heroes - Billy Blazes! (USA)" - rom ( name "Rescue Heroes - Billy Blazes! (USA).gba" size 4194304 crc 8111261c sha1 481fd8ab9e8e980039e244ba31a6dff057235210 ) + rom ( name "Rescue Heroes - Billy Blazes! (USA).gba" size 4194304 crc 8111261c sha1 481fd8ab9e8e980039e244ba31a6dff057235210 flags verified ) ) game ( @@ -14805,7 +14949,7 @@ game ( game ( name "Robots (Europe) (En,Fr,De,Es,It)" description "Robots (Europe) (En,Fr,De,Es,It)" - rom ( name "Robots (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc 2acbdb64 sha1 26be826817f35433b3609d13ee75a3872e94d616 ) + rom ( name "Robots (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc 2acbdb64 sha1 26be826817f35433b3609d13ee75a3872e94d616 flags verified ) ) game ( @@ -14886,6 +15030,18 @@ game ( rom ( name "Rockman & Forte (Japan).gba" size 8388608 crc ce2b48c4 sha1 f8eb056745e8b58c1ef4bfef995c77a27aadc57a flags verified ) ) +game ( + name "Rockman EXE - Battle Chip GP (Japan)" + description "Rockman EXE - Battle Chip GP (Japan)" + rom ( name "Rockman EXE - Battle Chip GP (Japan).gba" size 8388608 crc 9217fb18 sha1 f39992851257a1567f3bfca81dd269f37469bb67 ) +) + +game ( + name "Rockman EXE - Battle Chip GP (Japan) (Virtual Console)" + description "Rockman EXE - Battle Chip GP (Japan) (Virtual Console)" + rom ( name "Rockman EXE - Battle Chip GP (Japan) (Virtual Console).gba" size 8388608 crc f37bf038 sha1 9816fdb5f076c601287f5dfb3c7218150a75f2f6 ) +) + game ( name "Rockman EXE 4 - Tournament Blue Moon (Japan) (Rev 1) (Virtual Console)" description "Rockman EXE 4 - Tournament Blue Moon (Japan) (Rev 1) (Virtual Console)" @@ -14976,18 +15132,6 @@ game ( rom ( name "Rockman EXE 6 - Dennoujuu Gregar (Japan) (Virtual Console).gba" size 8388608 crc d0fdbefb sha1 678787c7d7cbee119eb524211487fb5dcd01426b ) ) -game ( - name "Rockman EXE Battle Chip GP (Japan) (Virtual Console)" - description "Rockman EXE Battle Chip GP (Japan) (Virtual Console)" - rom ( name "Rockman EXE Battle Chip GP (Japan) (Virtual Console).gba" size 8388608 crc f37bf038 sha1 9816fdb5f076c601287f5dfb3c7218150a75f2f6 ) -) - -game ( - name "Rockman EXE Battle Chip GP (Japan)" - description "Rockman EXE Battle Chip GP (Japan)" - rom ( name "Rockman EXE Battle Chip GP (Japan).gba" size 8388608 crc 9217fb18 sha1 f39992851257a1567f3bfca81dd269f37469bb67 ) -) - game ( name "Rockman Zero (Japan) (Virtual Console)" description "Rockman Zero (Japan) (Virtual Console)" @@ -15165,7 +15309,7 @@ game ( game ( name "Samurai Jack - The Amulet of Time (USA, Europe)" description "Samurai Jack - The Amulet of Time (USA, Europe)" - rom ( name "Samurai Jack - The Amulet of Time (USA, Europe).gba" size 8388608 crc b250366c sha1 27a5604b4aa89d2fec216aeac2b99e76165f531f ) + rom ( name "Samurai Jack - The Amulet of Time (USA, Europe).gba" size 8388608 crc b250366c sha1 27a5604b4aa89d2fec216aeac2b99e76165f531f flags verified ) ) game ( @@ -15195,7 +15339,7 @@ game ( game ( name "Santa Claus Jr. Advance (Europe)" description "Santa Claus Jr. Advance (Europe)" - rom ( name "Santa Claus Jr. Advance (Europe).gba" size 4194304 crc fe3e6769 sha1 60d6b482c4be9fb61a172f3864c9b35afc275980 ) + rom ( name "Santa Claus Jr. Advance (Europe).gba" size 4194304 crc fe3e6769 sha1 60d6b482c4be9fb61a172f3864c9b35afc275980 flags verified ) ) game ( @@ -15594,12 +15738,6 @@ game ( rom ( name "Sheep (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 75a92925 sha1 cb200350b4dc01dcdb0a52af11286aaca15cd786 ) ) -game ( - name "Shi Rui Ke II - Yu Mei Ren (Taiwan) (Unl)" - description "Shi Rui Ke II - Yu Mei Ren (Taiwan) (Unl)" - rom ( name "Shi Rui Ke II - Yu Mei Ren (Taiwan) (Unl).gba" size 33554432 crc 16d294d1 sha1 8033cc6c2c7f929844c39304b7faaf90115f1abc ) -) - game ( name "Shifting Gears - Road Trip (USA)" description "Shifting Gears - Road Trip (USA)" @@ -15618,18 +15756,18 @@ game ( rom ( name "Shikakui Atama o Maruku Suru. Advance - Kokugo, Sansuu, Shakai, Rika (Japan).gba" size 4194304 crc 07695a6c sha1 c1cf1478d79daf6c2717df6f19cad806bd7f6429 ) ) -game ( - name "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan) (Rev 1)" - description "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan) (Rev 1)" - rom ( name "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan) (Rev 1).gba" size 4194304 crc 5546b016 sha1 c7c94199bac6a1f69356413031b5a49cb30c93d1 ) -) - game ( name "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan)" description "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan)" rom ( name "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan).gba" size 4194304 crc f29e6e2d sha1 b2f100650af2c8d6ec02c0a71774eeba22d6be2f ) ) +game ( + name "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan) (Rev 1)" + description "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan) (Rev 1)" + rom ( name "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan) (Rev 1).gba" size 4194304 crc 5546b016 sha1 c7c94199bac6a1f69356413031b5a49cb30c93d1 ) +) + game ( name "Shin Bokura no Taiyou - Gyakushuu no Sabata (Japan)" description "Shin Bokura no Taiyou - Gyakushuu no Sabata (Japan)" @@ -15660,6 +15798,30 @@ game ( rom ( name "Shin Megami Tensei (Japan).gba" size 8388608 crc b857c3c5 sha1 7851f7f061693d664672f47f2d2d714829cf52ac ) ) +game ( + name "Shin Megami Tensei - Devil Children - Honoo no Sho (Japan)" + description "Shin Megami Tensei - Devil Children - Honoo no Sho (Japan)" + rom ( name "Shin Megami Tensei - Devil Children - Honoo no Sho (Japan).gba" size 8388608 crc 9a0903bc sha1 8c29d6921e7db8b0e71094d32e32066ac13c500d ) +) + +game ( + name "Shin Megami Tensei - Devil Children - Koori no Sho (Japan)" + description "Shin Megami Tensei - Devil Children - Koori no Sho (Japan)" + rom ( name "Shin Megami Tensei - Devil Children - Koori no Sho (Japan).gba" size 8388608 crc ad80d5f9 sha1 8470a93bded96270d1403bb485f32c42edf1462a flags verified ) +) + +game ( + name "Shin Megami Tensei - Devil Children - Messiah Riser (Japan)" + description "Shin Megami Tensei - Devil Children - Messiah Riser (Japan)" + rom ( name "Shin Megami Tensei - Devil Children - Messiah Riser (Japan).gba" size 8388608 crc 0ec98c51 sha1 9387947baa410c748ec5fab1ce9a07ae8047e398 ) +) + +game ( + name "Shin Megami Tensei - Devil Children - Puzzle de Call! (Japan)" + description "Shin Megami Tensei - Devil Children - Puzzle de Call! (Japan)" + rom ( name "Shin Megami Tensei - Devil Children - Puzzle de Call! (Japan).gba" size 4194304 crc ea8a185a sha1 e70fc457b56c8e27ab63e491706b88232f598452 ) +) + game ( name "Shin Megami Tensei Devil Children - Hikari no Sho (Japan)" description "Shin Megami Tensei Devil Children - Hikari no Sho (Japan)" @@ -15667,27 +15829,9 @@ game ( ) game ( - name "Shin Megami Tensei Devil Children - Honoo no Sho (Japan)" - description "Shin Megami Tensei Devil Children - Honoo no Sho (Japan)" - rom ( name "Shin Megami Tensei Devil Children - Honoo no Sho (Japan).gba" size 8388608 crc 9a0903bc sha1 8c29d6921e7db8b0e71094d32e32066ac13c500d ) -) - -game ( - name "Shin Megami Tensei Devil Children - Koori no Sho (Japan)" - description "Shin Megami Tensei Devil Children - Koori no Sho (Japan)" - rom ( name "Shin Megami Tensei Devil Children - Koori no Sho (Japan).gba" size 8388608 crc ad80d5f9 sha1 8470a93bded96270d1403bb485f32c42edf1462a flags verified ) -) - -game ( - name "Shin Megami Tensei Devil Children - Messiah Riser (Japan)" - description "Shin Megami Tensei Devil Children - Messiah Riser (Japan)" - rom ( name "Shin Megami Tensei Devil Children - Messiah Riser (Japan).gba" size 8388608 crc 0ec98c51 sha1 9387947baa410c748ec5fab1ce9a07ae8047e398 ) -) - -game ( - name "Shin Megami Tensei Devil Children - Puzzle de Call! (Japan)" - description "Shin Megami Tensei Devil Children - Puzzle de Call! (Japan)" - rom ( name "Shin Megami Tensei Devil Children - Puzzle de Call! (Japan).gba" size 4194304 crc ea8a185a sha1 e70fc457b56c8e27ab63e491706b88232f598452 ) + name "Shin Megami Tensei Devil Children - Yami no Sho (Japan)" + description "Shin Megami Tensei Devil Children - Yami no Sho (Japan)" + rom ( name "Shin Megami Tensei Devil Children - Yami no Sho (Japan).gba" size 8388608 crc e0e153b7 sha1 236fd0e7c14f5d6db7fa2083766324341b9e5b39 ) ) game ( @@ -15696,12 +15840,6 @@ game ( rom ( name "Shin Megami Tensei Devil Children - Yami no Sho (Japan) (Beta).gba" size 8388608 crc 81f30ebc sha1 c2db9b977758b4f350340f981baffcc0bba0c6c2 ) ) -game ( - name "Shin Megami Tensei Devil Children - Yami no Sho (Japan)" - description "Shin Megami Tensei Devil Children - Yami no Sho (Japan)" - rom ( name "Shin Megami Tensei Devil Children - Yami no Sho (Japan).gba" size 8388608 crc e0e153b7 sha1 236fd0e7c14f5d6db7fa2083766324341b9e5b39 ) -) - game ( name "Shin Megami Tensei II (Japan)" description "Shin Megami Tensei II (Japan)" @@ -15714,18 +15852,18 @@ game ( rom ( name "Shin Nihon Pro Wrestling - Toukon Retsuden Advance (Japan).gba" size 8388608 crc 9f0b8b79 sha1 8c7d119f4e9ac6dab2c308d1dfad5eabb0837014 ) ) -game ( - name "Shin Sangoku Musou Advance (Japan)" - description "Shin Sangoku Musou Advance (Japan)" - rom ( name "Shin Sangoku Musou Advance (Japan).gba" size 16777216 crc fe1be6c1 sha1 677cd51c1ecdde73a6f40ec2cd30d35c77db459a ) -) - game ( name "Shin Sangoku Musou Advance (Japan) (Rev 1)" description "Shin Sangoku Musou Advance (Japan) (Rev 1)" rom ( name "Shin Sangoku Musou Advance (Japan) (Rev 1).gba" size 16777216 crc 7ceded10 sha1 be2bfa709478dc482e80fb476c1ee846b703bcbb ) ) +game ( + name "Shin Sangoku Musou Advance (Japan)" + description "Shin Sangoku Musou Advance (Japan)" + rom ( name "Shin Sangoku Musou Advance (Japan).gba" size 16777216 crc fe1be6c1 sha1 677cd51c1ecdde73a6f40ec2cd30d35c77db459a ) +) + game ( name "Shingata Medarot - Kabuto Version (Japan)" description "Shingata Medarot - Kabuto Version (Japan)" @@ -15828,6 +15966,12 @@ game ( rom ( name "Shiren Monsters - Netsal (Japan) (Demo) (Kiosk, GameCube).gba" size 16777216 crc 25b0b122 sha1 2ebd243709a9b0af16c6c0b0404fb02999eefffd flags verified ) ) +game ( + name "Shiruike II - Yu Meiren (Taiwan) (Unl)" + description "Shiruike II - Yu Meiren (Taiwan) (Unl)" + rom ( name "Shiruike II - Yu Meiren (Taiwan) (Unl).gba" size 33554432 crc 16d294d1 sha1 8033cc6c2c7f929844c39304b7faaf90115f1abc ) +) + game ( name "Shrek - Hassle at the Castle (USA) (En,Fr,De,Es,It,Nl)" description "Shrek - Hassle at the Castle (USA) (En,Fr,De,Es,It,Nl)" @@ -15843,7 +15987,7 @@ game ( game ( name "Shrek - Reekin' Havoc (USA) (En,Fr,De,Es,It,Nl)" description "Shrek - Reekin' Havoc (USA) (En,Fr,De,Es,It,Nl)" - rom ( name "Shrek - Reekin' Havoc (USA) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 63f49ba9 sha1 af0bc8338e49be78ce6e85b59d1ddfca8a10f290 ) + rom ( name "Shrek - Reekin' Havoc (USA) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 63f49ba9 sha1 af0bc8338e49be78ce6e85b59d1ddfca8a10f290 flags verified ) ) game ( @@ -16206,6 +16350,12 @@ game ( rom ( name "Soccer Kid (USA, Europe).gba" size 4194304 crc d3485f5a sha1 4e7dc4d47ef064131990fe3ebb340edeaed321ef flags verified ) ) +game ( + name "Soccer Mania (USA, Europe) (En,Fr,De,Es,It,Nl,Sv,Da)" + description "Soccer Mania (USA, Europe) (En,Fr,De,Es,It,Nl,Sv,Da)" + rom ( name "Soccer Mania (USA, Europe) (En,Fr,De,Es,It,Nl,Sv,Da).gba" size 8388608 crc 73ef3a35 sha1 05799b99395aba04dfe5ae8af019e4d70cb8e61b ) +) + game ( name "Sonic 3 - Fighter Sonic (USA) (Unl)" description "Sonic 3 - Fighter Sonic (USA) (Unl)" @@ -16273,9 +16423,9 @@ game ( ) game ( - name "Sonic Advance 2 (USA) (Beta)" - description "Sonic Advance 2 (USA) (Beta)" - rom ( name "Sonic Advance 2 (USA) (Beta).gba" size 16777216 crc 95ab3867 sha1 3368642fc4157824af63367e2a685b7d6ee9b09d ) + name "Sonic Advance 2 (USA) (Beta) (2002-10-25)" + description "Sonic Advance 2 (USA) (Beta) (2002-10-25)" + rom ( name "Sonic Advance 2 (USA) (Beta) (2002-10-25).gba" size 16777216 crc 95ab3867 sha1 3368642fc4157824af63367e2a685b7d6ee9b09d ) ) game ( @@ -16615,15 +16765,15 @@ game ( ) game ( - name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08) (05-16)" - description "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08) (05-16)" - rom ( name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08) (05-16).gba" size 315572 crc 49754ee4 sha1 d14787d2af88af2907f71744d4b4a933b38ee31a ) + name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08T051624)" + description "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08T051624)" + rom ( name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08T051624).gba" size 315572 crc 49754ee4 sha1 d14787d2af88af2907f71744d4b4a933b38ee31a ) ) game ( - name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08) (07-47)" - description "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08) (07-47)" - rom ( name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08) (07-47).gba" size 342160 crc b83d7e0d sha1 bc0833743e90b6cff8f51906e12f15748fed4ed6 ) + name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08T074756)" + description "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08T074756)" + rom ( name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08T074756).gba" size 342160 crc b83d7e0d sha1 bc0833743e90b6cff8f51906e12f15748fed4ed6 ) ) game ( @@ -16669,15 +16819,15 @@ game ( ) game ( - name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06) (11-34)" - description "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06) (11-34)" - rom ( name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06) (11-34).gba" size 2470464 crc 40cc2e8a sha1 071e9a6c09ccd6e52d1d6cc40a0ebea3e8dd80c3 ) + name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06T113400)" + description "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06T113400)" + rom ( name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06T113400).gba" size 2470464 crc 40cc2e8a sha1 071e9a6c09ccd6e52d1d6cc40a0ebea3e8dd80c3 ) ) game ( - name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06) (11-39)" - description "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06) (11-39)" - rom ( name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06) (11-39).gba" size 3011936 crc ff4f1ee7 sha1 909a6cc0930818de20489cfcdd967280b403272b ) + name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06T113900)" + description "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06T113900)" + rom ( name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06T113900).gba" size 3011936 crc ff4f1ee7 sha1 909a6cc0930818de20489cfcdd967280b403272b ) ) game ( @@ -16947,7 +17097,7 @@ game ( game ( name "Star Wars - The New Droid Army (Europe) (En,Fr,De,Es)" description "Star Wars - The New Droid Army (Europe) (En,Fr,De,Es)" - rom ( name "Star Wars - The New Droid Army (Europe) (En,Fr,De,Es).gba" size 8388608 crc 677854af sha1 4966ce40938df2b0ffd262c6aa6c4461b9fa8922 ) + rom ( name "Star Wars - The New Droid Army (Europe) (En,Fr,De,Es).gba" size 8388608 crc 677854af sha1 4966ce40938df2b0ffd262c6aa6c4461b9fa8922 flags verified ) ) game ( @@ -16993,9 +17143,9 @@ game ( ) game ( - name "Starsky & Hutch (USA) (Beta)" - description "Starsky & Hutch (USA) (Beta)" - rom ( name "Starsky & Hutch (USA) (Beta).gba" size 4231928 crc ab142d2e sha1 861066ddf8b52be194b30cbb70e959989f0a2f76 ) + name "Starsky & Hutch (USA) (Beta) (2003-02-11)" + description "Starsky & Hutch (USA) (Beta) (2003-02-11)" + rom ( name "Starsky & Hutch (USA) (Beta) (2003-02-11).gba" size 4231928 crc ab142d2e sha1 861066ddf8b52be194b30cbb70e959989f0a2f76 ) ) game ( @@ -17004,12 +17154,6 @@ game ( rom ( name "Steel Empire (Europe).gba" size 4194304 crc cb91922e sha1 b280937636a533020bf42591cc246b221a16f94e ) ) -game ( - name "Steven Gerrard's Total Soccer 2002 (Europe) (En,Fr,De,Es,It,Nl)" - description "Steven Gerrard's Total Soccer 2002 (Europe) (En,Fr,De,Es,It,Nl)" - rom ( name "Steven Gerrard's Total Soccer 2002 (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc b94ddc93 sha1 1f37abe2a70ff0027abe714cb4cd93f168925753 flags verified ) -) - game ( name "Strawberry Shortcake - Ice Cream Island - Riding Camp (Europe) (En,Fr,De,Es,It,Nl,Pt,Da)" description "Strawberry Shortcake - Ice Cream Island - Riding Camp (Europe) (En,Fr,De,Es,It,Nl,Pt,Da)" @@ -17205,7 +17349,7 @@ game ( game ( name "Super Bust-A-Move (Europe) (En,Fr,De,Es,It)" description "Super Bust-A-Move (Europe) (En,Fr,De,Es,It)" - rom ( name "Super Bust-A-Move (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 6bfd2915 sha1 e4dd4bb3691444fff595a926b2c06313f2e69751 ) + rom ( name "Super Bust-A-Move (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 6bfd2915 sha1 e4dd4bb3691444fff595a926b2c06313f2e69751 flags verified ) ) game ( @@ -17388,6 +17532,12 @@ game ( rom ( name "Super Mario Advance 3 - Yoshi's Island + Mario Brothers (Japan) (Virtual Console).gba" size 4194304 crc 10f9eda4 sha1 fa7486b4721925074ba1fde9b800ef85a5b5954d ) ) +game ( + name "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan) (Rev 2) (Switch Online)" + description "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan) (Rev 2) (Switch Online)" + rom ( name "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan) (Rev 2) (Switch Online).gba" size 8388608 crc aa312980 sha1 b65217bb411bf1e9aff50bf0940c4a0021789f59 ) +) + game ( name "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan) (Rev 2)" description "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan) (Rev 2)" @@ -17409,7 +17559,7 @@ game ( game ( name "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan)" description "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan)" - rom ( name "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan).gba" size 4194304 crc f3c87306 sha1 19f7928bc4ffd733d71884dae9ad9b7f4007d38d ) + rom ( name "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan).gba" size 4194304 crc f3c87306 sha1 19f7928bc4ffd733d71884dae9ad9b7f4007d38d flags verified ) ) game ( @@ -17448,6 +17598,30 @@ game ( rom ( name "Super Mario Advance 4 - Super Mario Bros. 3 (Europe) (En,Fr,De,Es,It) (Rev 1) (Virtual Console).gba" size 8388608 crc d4f45b01 sha1 00667ce3da4bfee3182c4445ac2f5483870be97c flags verified ) ) +game ( + name "Super Mario Advance 4 - Super Mario Bros. 3 (USA) (Rev 1) (Switch Online)" + description "Super Mario Advance 4 - Super Mario Bros. 3 (USA) (Rev 1) (Switch Online)" + rom ( name "Super Mario Advance 4 - Super Mario Bros. 3 (USA) (Rev 1) (Switch Online).gba" size 8388608 crc 22e12d0e sha1 82fa5a6cf09415c2e262931488841b78a524e2c3 flags verified ) +) + +game ( + name "Super Mario Advance 4 - Super Mario Bros. 3 (Europe) (En,Fr,De,Es,It) (Rev 1) (Switch Online)" + description "Super Mario Advance 4 - Super Mario Bros. 3 (Europe) (En,Fr,De,Es,It) (Rev 1) (Switch Online)" + rom ( name "Super Mario Advance 4 - Super Mario Bros. 3 (Europe) (En,Fr,De,Es,It) (Rev 1) (Switch Online).gba" size 8388608 crc e3847e32 sha1 cdb79f7926fb61ee7f13ee4cbd61ebe3fb01ba69 flags verified ) +) + +game ( + name "Super Mario Ball (Europe) (Virtual Console)" + description "Super Mario Ball (Europe) (Virtual Console)" + rom ( name "Super Mario Ball (Europe) (Virtual Console).gba" size 8388608 crc 3b15e886 sha1 e3ee63bf92340a50085cb905e167c03f3c60b72a ) +) + +game ( + name "Super Mario Ball (Japan) (Demo) (Kiosk, GameCube)" + description "Super Mario Ball (Japan) (Demo) (Kiosk, GameCube)" + rom ( name "Super Mario Ball (Japan) (Demo) (Kiosk, GameCube).gba" size 8388608 crc 65b1d9cb sha1 f8a356c0ba5e216c8ba408474fd74553d284b9b8 flags verified ) +) + game ( name "Super Mario Ball (Japan) (Virtual Console)" description "Super Mario Ball (Japan) (Virtual Console)" @@ -17466,18 +17640,6 @@ game ( rom ( name "Super Mario Ball (Europe).gba" size 8388608 crc 3b035681 sha1 d53fbc63e08c15bfdc045b5664fe24e8e2718469 flags verified ) ) -game ( - name "Super Mario Ball (Europe) (Virtual Console)" - description "Super Mario Ball (Europe) (Virtual Console)" - rom ( name "Super Mario Ball (Europe) (Virtual Console).gba" size 8388608 crc 3b15e886 sha1 e3ee63bf92340a50085cb905e167c03f3c60b72a ) -) - -game ( - name "Super Mario Ball (Japan) (Demo) (Kiosk, GameCube)" - description "Super Mario Ball (Japan) (Demo) (Kiosk, GameCube)" - rom ( name "Super Mario Ball (Japan) (Demo) (Kiosk, GameCube).gba" size 8388608 crc 65b1d9cb sha1 f8a356c0ba5e216c8ba408474fd74553d284b9b8 flags verified ) -) - game ( name "Super Mario Bros. (Japan) (Hot Mario Campaign)" description "Super Mario Bros. (Japan) (Hot Mario Campaign)" @@ -17805,7 +17967,7 @@ game ( game ( name "Tales of the World - Narikiri Dungeon 2 (Japan)" description "Tales of the World - Narikiri Dungeon 2 (Japan)" - rom ( name "Tales of the World - Narikiri Dungeon 2 (Japan).gba" size 8388608 crc 231b9fca sha1 2feb4cff9485c68758c1fac847c6eb907e747a01 ) + rom ( name "Tales of the World - Narikiri Dungeon 2 (Japan).gba" size 8388608 crc 231b9fca sha1 2feb4cff9485c68758c1fac847c6eb907e747a01 flags verified ) ) game ( @@ -18049,9 +18211,9 @@ game ( ) game ( - name "Texas Hold 'em Poker (USA, Europe)" - description "Texas Hold 'em Poker (USA, Europe)" - rom ( name "Texas Hold 'em Poker (USA, Europe).gba" size 4194304 crc 78b59aac sha1 5fcc9958210d8bde94a429516b15ca669427fe6d ) + name "Texas Hold 'em Poker (USA)" + description "Texas Hold 'em Poker (USA)" + rom ( name "Texas Hold 'em Poker (USA).gba" size 4194304 crc 78b59aac sha1 5fcc9958210d8bde94a429516b15ca669427fe6d ) ) game ( @@ -18099,7 +18261,7 @@ game ( game ( name "Three-in-One Pack - Sorry! + Aggravation + Scrabble Junior (USA)" description "Three-in-One Pack - Sorry! + Aggravation + Scrabble Junior (USA)" - rom ( name "Three-in-One Pack - Sorry! + Aggravation + Scrabble Junior (USA).gba" size 4194304 crc 111eb61a sha1 cbc5bb55d4951e884e65ea2cab36957ee51aaadb ) + rom ( name "Three-in-One Pack - Sorry! + Aggravation + Scrabble Junior (USA).gba" size 4194304 crc 111eb61a sha1 cbc5bb55d4951e884e65ea2cab36957ee51aaadb flags verified ) ) game ( @@ -18150,12 +18312,6 @@ game ( rom ( name "Tim Burton's The Nightmare Before Christmas - The Pumpkin King (USA, Europe) (En,Fr,De,Es,It).gba" size 8388608 crc 3c247c14 sha1 8e713fa3e3cc5aa8c1836fba9d81bbf80de20638 ) ) -game ( - name "Tiny Chao Garden (USA) (Ja) (Phantasy Star Online Episode I & II)" - description "Tiny Chao Garden (USA) (Ja) (Phantasy Star Online Episode I & II)" - rom ( name "Tiny Chao Garden (USA) (Ja) (Phantasy Star Online Episode I & II).gba" size 182840 crc c523504f sha1 55c9812e2164a618575c6dcd669fd993b60585f5 flags verified ) -) - game ( name "Tiny Toon Adventures - Buster's Bad Dream (Europe) (En,Fr,De,Es,It)" description "Tiny Toon Adventures - Buster's Bad Dream (Europe) (En,Fr,De,Es,It)" @@ -18168,36 +18324,36 @@ game ( rom ( name "Tiny Toon Adventures - Scary Dreams (USA).gba" size 4194304 crc fcd7a7c0 sha1 305c6cf66e47508deee4b9fc12c8917302f2ed39 ) ) -game ( - name "Tiny Toon Adventures - Wacky Stackers (USA)" - description "Tiny Toon Adventures - Wacky Stackers (USA)" - rom ( name "Tiny Toon Adventures - Wacky Stackers (USA).gba" size 4194304 crc e03c633e sha1 fd9d10006bd29945ac16c6f88608a27e15f8f7da ) -) - game ( name "Tiny Toon Adventures - Wacky Stackers (Europe) (En,Fr,De,Es,It)" description "Tiny Toon Adventures - Wacky Stackers (Europe) (En,Fr,De,Es,It)" rom ( name "Tiny Toon Adventures - Wacky Stackers (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 4ac770b6 sha1 cd9d810b29070d9f37df219bfd7e52ce9d52d2b0 ) ) +game ( + name "Tiny Toon Adventures - Wacky Stackers (USA)" + description "Tiny Toon Adventures - Wacky Stackers (USA)" + rom ( name "Tiny Toon Adventures - Wacky Stackers (USA).gba" size 4194304 crc e03c633e sha1 fd9d10006bd29945ac16c6f88608a27e15f8f7da ) +) + game ( name "Tir et But - Edition Champions du Monde (France)" description "Tir et But - Edition Champions du Monde (France)" rom ( name "Tir et But - Edition Champions du Monde (France).gba" size 4194304 crc 7782a204 sha1 e097aa37ab67146d544ae1438a19f8cd9e3a8f7c ) ) -game ( - name "Titeuf - Mega Compet (France) (Beta)" - description "Titeuf - Mega Compet (France) (Beta)" - rom ( name "Titeuf - Mega Compet (France) (Beta).gba" size 4194304 crc c9be7fcf sha1 1ed43c7419b22b1147bd7e3198de52f4c8d3003d ) -) - game ( name "Titeuf - Mega Compet (France)" description "Titeuf - Mega Compet (France)" rom ( name "Titeuf - Mega Compet (France).gba" size 4194304 crc ffd0a045 sha1 ff59a9810adc3270295763282c9d86d549f58a6f ) ) +game ( + name "Titeuf - Mega Compet (France) (Beta)" + description "Titeuf - Mega Compet (France) (Beta)" + rom ( name "Titeuf - Mega Compet (France) (Beta).gba" size 4194304 crc c9be7fcf sha1 1ed43c7419b22b1147bd7e3198de52f4c8d3003d ) +) + game ( name "Titeuf - Ze Gag Machine (France)" description "Titeuf - Ze Gag Machine (France)" @@ -18249,7 +18405,7 @@ game ( game ( name "Tokyo Xtreme Racer Advance (Europe) (En,Fr,De,Es,It)" description "Tokyo Xtreme Racer Advance (Europe) (En,Fr,De,Es,It)" - rom ( name "Tokyo Xtreme Racer Advance (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc bd5f5f4c sha1 15a306794efdd64651387c1447ae4713892e4b87 ) + rom ( name "Tokyo Xtreme Racer Advance (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc bd5f5f4c sha1 15a306794efdd64651387c1447ae4713892e4b87 flags verified ) ) game ( @@ -18306,18 +18462,18 @@ game ( rom ( name "Tom Clancy's Splinter Cell (USA) (En,Fr,Es).gba" size 8388608 crc 308ba69c sha1 dbd089cbdd79c26b2f0b651f74df59b6cbfafbcb ) ) -game ( - name "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl) (Beta)" - description "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl) (Beta)" - rom ( name "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl) (Beta).gba" size 16777216 crc e07ba799 sha1 3a55f6ed66a3ec7c019221fa2660e2f85954ebae ) -) - game ( name "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl)" description "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl)" rom ( name "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 7ecd29ce sha1 d623cbd1019665eee78846a2a60d17adaf8ec0fe flags verified ) ) +game ( + name "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl) (Beta)" + description "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl) (Beta)" + rom ( name "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl) (Beta).gba" size 16777216 crc e07ba799 sha1 3a55f6ed66a3ec7c019221fa2660e2f85954ebae ) +) + game ( name "Tom Clancy's Splinter Cell - Pandora Tomorrow (Europe) (En,Fr,De,Es,It,Nl)" description "Tom Clancy's Splinter Cell - Pandora Tomorrow (Europe) (En,Fr,De,Es,It,Nl)" @@ -18435,7 +18591,7 @@ game ( game ( name "Tony Hawk's Underground 2 (USA, Europe)" description "Tony Hawk's Underground 2 (USA, Europe)" - rom ( name "Tony Hawk's Underground 2 (USA, Europe).gba" size 8388608 crc bf864083 sha1 c684d839c32515a99e62b183b607e2259120cfc9 ) + rom ( name "Tony Hawk's Underground 2 (USA, Europe).gba" size 8388608 crc bf864083 sha1 c684d839c32515a99e62b183b607e2259120cfc9 flags verified ) ) game ( @@ -18498,12 +18654,24 @@ game ( rom ( name "Top Spin 2 (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc a8e8618b sha1 192bb233840382e410fd242f6b6bfbd33ab24d38 ) ) +game ( + name "Total Soccer 2002 (Europe) (En,Fr,De,Es,It,Nl)" + description "Total Soccer 2002 (Europe) (En,Fr,De,Es,It,Nl)" + rom ( name "Total Soccer 2002 (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc b94ddc93 sha1 1f37abe2a70ff0027abe714cb4cd93f168925753 flags verified ) +) + game ( name "Total Soccer Advance (Japan)" description "Total Soccer Advance (Japan)" rom ( name "Total Soccer Advance (Japan).gba" size 4194304 crc 28c3e211 sha1 3a45d03ac6b098b70641c222f684abd13ef02500 ) ) +game ( + name "Total Soccer Manager (Europe) (En,Fr,De,Es,It,Nl)" + description "Total Soccer Manager (Europe) (En,Fr,De,Es,It,Nl)" + rom ( name "Total Soccer Manager (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc 92f99295 sha1 a870e1321a8ad3317cd695fcc0c713441c25919f ) +) + game ( name "Totally Spies! (Europe) (En,Fr,De,Es,It,Nl)" description "Totally Spies! (Europe) (En,Fr,De,Es,It,Nl)" @@ -18577,21 +18745,15 @@ game ( ) game ( - name "Toyrobo Force (Japan)" - description "Toyrobo Force (Japan)" - rom ( name "Toyrobo Force (Japan).gba" size 4194304 crc 227fc16b sha1 6ef30145099d09cf1a1f2b3bc6618a985dabdaae ) + name "Toy Robo Force (Japan)" + description "Toy Robo Force (Japan)" + rom ( name "Toy Robo Force (Japan).gba" size 4194304 crc 227fc16b sha1 6ef30145099d09cf1a1f2b3bc6618a985dabdaae ) ) game ( - name "Travis Pastrana's Pro MotoX (USA, Europe) (Proto) (2002-12-12)" - description "Travis Pastrana's Pro MotoX (USA, Europe) (Proto) (2002-12-12)" - rom ( name "Travis Pastrana's Pro MotoX (USA, Europe) (Proto) (2002-12-12).gba" size 4194304 crc ecfe5f96 sha1 64e356f495fd58a862f272124798ecf61e20cfb7 ) -) - -game ( - name "Treasure Planet (Europe) (En,Fr,De,Es,It,Nl) (Rev 1)" - description "Treasure Planet (Europe) (En,Fr,De,Es,It,Nl) (Rev 1)" - rom ( name "Treasure Planet (Europe) (En,Fr,De,Es,It,Nl) (Rev 1).gba" size 8388608 crc 794b2602 sha1 c703edaee8a2bad09c2298e2c92a14d459fe664a ) + name "Travis Pastrana's Pro MotoX (USA) (Proto) (2002-12-12)" + description "Travis Pastrana's Pro MotoX (USA) (Proto) (2002-12-12)" + rom ( name "Travis Pastrana's Pro MotoX (USA) (Proto) (2002-12-12).gba" size 4194304 crc ecfe5f96 sha1 64e356f495fd58a862f272124798ecf61e20cfb7 ) ) game ( @@ -18607,27 +18769,33 @@ game ( ) game ( - name "Tremblay Island (World) (v.1.10) (Aftermarket) (Homebrew)" - description "Tremblay Island (World) (v.1.10) (Aftermarket) (Homebrew)" - rom ( name "Tremblay Island (World) (v.1.10) (Aftermarket) (Homebrew).gba" size 16239516 crc ce85ba07 sha1 b09b600bfc222c2d92829d8883feab700b013953 ) + name "Treasure Planet (Europe) (En,Fr,De,Es,It,Nl) (Rev 1)" + description "Treasure Planet (Europe) (En,Fr,De,Es,It,Nl) (Rev 1)" + rom ( name "Treasure Planet (Europe) (En,Fr,De,Es,It,Nl) (Rev 1).gba" size 8388608 crc 794b2602 sha1 c703edaee8a2bad09c2298e2c92a14d459fe664a ) ) game ( - name "Tremblay Island (World) (v.1.10) (Solid State Version) (Aftermarket) (Homebrew)" - description "Tremblay Island (World) (v.1.10) (Solid State Version) (Aftermarket) (Homebrew)" - rom ( name "Tremblay Island (World) (v.1.10) (Solid State Version) (Aftermarket) (Homebrew).gba" size 16229600 crc bd57344d sha1 a2fcfe94c9b8b0feff31c1245c8b033222c9f668 ) + name "Tremblay Island (World) (En) (v.1.10) (Aftermarket) (Unl)" + description "Tremblay Island (World) (En) (v.1.10) (Aftermarket) (Unl)" + rom ( name "Tremblay Island (World) (En) (v.1.10) (Aftermarket) (Unl).gba" size 16239516 crc ce85ba07 sha1 b09b600bfc222c2d92829d8883feab700b013953 ) ) game ( - name "Tremblay Island (World) (Fr) (v.1.10) (Solid State Version) (Aftermarket) (Homebrew)" - description "Tremblay Island (World) (Fr) (v.1.10) (Solid State Version) (Aftermarket) (Homebrew)" - rom ( name "Tremblay Island (World) (Fr) (v.1.10) (Solid State Version) (Aftermarket) (Homebrew).gba" size 16204024 crc fa43669e sha1 a57392fa7cee61913b20a840fe8b2ee27f11d808 ) + name "Tremblay Island (World) (En) (v.1.10) (Solid State Version) (Aftermarket) (Unl)" + description "Tremblay Island (World) (En) (v.1.10) (Solid State Version) (Aftermarket) (Unl)" + rom ( name "Tremblay Island (World) (En) (v.1.10) (Solid State Version) (Aftermarket) (Unl).gba" size 16229600 crc bd57344d sha1 a2fcfe94c9b8b0feff31c1245c8b033222c9f668 ) ) game ( - name "Tremblay Island (World) (Kickstarter Edition) (Aftermarket) (Homebrew)" - description "Tremblay Island (World) (Kickstarter Edition) (Aftermarket) (Homebrew)" - rom ( name "Tremblay Island (World) (Kickstarter Edition) (Aftermarket) (Homebrew).gba" size 16777216 crc ff5d64ab sha1 645ab8d4dacb6666d8eb549d491f77d8cd63ce97 ) + name "Tremblay Island (World) (Fr) (v.1.10) (Solid State Version) (Aftermarket) (Unl)" + description "Tremblay Island (World) (Fr) (v.1.10) (Solid State Version) (Aftermarket) (Unl)" + rom ( name "Tremblay Island (World) (Fr) (v.1.10) (Solid State Version) (Aftermarket) (Unl).gba" size 16204024 crc fa43669e sha1 a57392fa7cee61913b20a840fe8b2ee27f11d808 ) +) + +game ( + name "Tremblay Island (World) (En) (Kickstarter Edition) (Aftermarket) (Unl)" + description "Tremblay Island (World) (En) (Kickstarter Edition) (Aftermarket) (Unl)" + rom ( name "Tremblay Island (World) (En) (Kickstarter Edition) (Aftermarket) (Unl).gba" size 16777216 crc ff5d64ab sha1 645ab8d4dacb6666d8eb549d491f77d8cd63ce97 ) ) game ( @@ -18696,6 +18864,12 @@ game ( rom ( name "Tsuukin Hitofude (Japan) (Virtual Console).gba" size 4194304 crc f4c7a95b sha1 906dbf246cb40954fc27942763048bc09783b1f8 ) ) +game ( + name "Tsuukin Hitofude (Japan) (Beta)" + description "Tsuukin Hitofude (Japan) (Beta)" + rom ( name "Tsuukin Hitofude (Japan) (Beta).gba" size 1048576 crc 9542e247 sha1 24b4be7f296fce5eae93d969eb02831951f00b7b ) +) + game ( name "Turbo Turtle Adventure (USA)" description "Turbo Turtle Adventure (USA)" @@ -18942,6 +19116,18 @@ game ( rom ( name "Unglaublichen, Die (Germany).gba" size 8388608 crc bdc06e57 sha1 6bc89e6aa6cd251d56fe86e520b08cadf20ca4f4 ) ) +game ( + name "Uno - Free Fall (USA)" + description "Uno - Free Fall (USA)" + rom ( name "Uno - Free Fall (USA).gba" size 4194304 crc 19fcc78e sha1 2dacf03965ee248d414d385594cc919f896f032e ) +) + +game ( + name "Uno - Free Fall (Europe) (En,Fr,De,Es,It)" + description "Uno - Free Fall (Europe) (En,Fr,De,Es,It)" + rom ( name "Uno - Free Fall (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 414ea9f4 sha1 2ad8bdfc425f9426296c03acd433b296e40eb72e ) +) + game ( name "Uno 52 (USA)" description "Uno 52 (USA)" @@ -18954,18 +19140,6 @@ game ( rom ( name "Uno 52 (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 315360d0 sha1 f85709ec7a97c8b40504f2097497bd961531272d ) ) -game ( - name "Uno Free Fall (USA)" - description "Uno Free Fall (USA)" - rom ( name "Uno Free Fall (USA).gba" size 4194304 crc 19fcc78e sha1 2dacf03965ee248d414d385594cc919f896f032e ) -) - -game ( - name "Uno Free Fall (Europe) (En,Fr,De,Es,It)" - description "Uno Free Fall (Europe) (En,Fr,De,Es,It)" - rom ( name "Uno Free Fall (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 414ea9f4 sha1 2ad8bdfc425f9426296c03acd433b296e40eb72e ) -) - game ( name "Urban Yeti! (USA, Europe)" description "Urban Yeti! (USA, Europe)" @@ -19051,9 +19225,9 @@ game ( ) game ( - name "Viewpoint (World) (Tech Demo)" - description "Viewpoint (World) (Tech Demo)" - rom ( name "Viewpoint (World) (Tech Demo).gba" size 2097152 crc 7ad65e4b sha1 8fdcc5c207593cb196cea8416bb14bb45db37434 ) + name "Viewpoint (Europe) (Demo)" + description "Viewpoint (Europe) (Demo)" + rom ( name "Viewpoint (Europe) (Demo).gba" size 2097152 crc 7ad65e4b sha1 8fdcc5c207593cb196cea8416bb14bb45db37434 ) ) game ( @@ -19207,9 +19381,9 @@ game ( ) game ( - name "WarioWare - Twisted! (USA)" - description "WarioWare - Twisted! (USA)" - rom ( name "WarioWare - Twisted! (USA).gba" size 16777216 crc cb4e844b sha1 f0102d0d6f7596fe853d5d0a94682718278e083a flags verified ) + name "WarioWare - Twisted! (USA, Australia)" + description "WarioWare - Twisted! (USA, Australia)" + rom ( name "WarioWare - Twisted! (USA, Australia).gba" size 16777216 crc cb4e844b sha1 f0102d0d6f7596fe853d5d0a94682718278e083a flags verified ) ) game ( @@ -19495,9 +19669,9 @@ game ( ) game ( - name "World Reborn (World) (Aftermarket) (Unl)" - description "World Reborn (World) (Aftermarket) (Unl)" - rom ( name "World Reborn (World) (Aftermarket) (Unl).gba" size 4194304 crc eefb32ff sha1 c7ec2f8d7d3dec40a893cdfe2a41a8ed43ed71c4 ) + name "World Reborn (USA) (Aftermarket) (Unl)" + description "World Reborn (USA) (Aftermarket) (Unl)" + rom ( name "World Reborn (USA) (Aftermarket) (Unl).gba" size 4194304 crc eefb32ff sha1 c7ec2f8d7d3dec40a893cdfe2a41a8ed43ed71c4 ) ) game ( @@ -19512,24 +19686,24 @@ game ( rom ( name "World Tennis Stars (USA).gba" size 4194304 crc 036e30b0 sha1 c668815a2ccf0bc6c3816d0947218661437449bc ) ) +game ( + name "Worms - World Party (USA) (En,Fr,De,Es,It)" + description "Worms - World Party (USA) (En,Fr,De,Es,It)" + rom ( name "Worms - World Party (USA) (En,Fr,De,Es,It).gba" size 4194304 crc e8789c18 sha1 7b078976d9056aba7f81bb1ff33e5a36357f5f0b ) +) + +game ( + name "Worms - World Party (Europe) (En,Fr,De,Es,It)" + description "Worms - World Party (Europe) (En,Fr,De,Es,It)" + rom ( name "Worms - World Party (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 8ee32cbe sha1 5e0abe3d94d1489fadebe897df1ed5a5c0212901 ) +) + game ( name "Worms Blast (Europe) (En,Fr,De,Es,It)" description "Worms Blast (Europe) (En,Fr,De,Es,It)" rom ( name "Worms Blast (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 5223f5aa sha1 0da4ff24b61ae8b6d67ef59377770fd958517f2f ) ) -game ( - name "Worms World Party (Europe) (En,Fr,De,Es,It)" - description "Worms World Party (Europe) (En,Fr,De,Es,It)" - rom ( name "Worms World Party (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 8ee32cbe sha1 5e0abe3d94d1489fadebe897df1ed5a5c0212901 ) -) - -game ( - name "Worms World Party (USA) (En,Fr,De,Es,It)" - description "Worms World Party (USA) (En,Fr,De,Es,It)" - rom ( name "Worms World Party (USA) (En,Fr,De,Es,It).gba" size 4194304 crc e8789c18 sha1 7b078976d9056aba7f81bb1ff33e5a36357f5f0b ) -) - game ( name "WTA Tour Tennis (USA)" description "WTA Tour Tennis (USA)" @@ -19551,7 +19725,7 @@ game ( game ( name "WWE - Survivor Series (USA, Europe)" description "WWE - Survivor Series (USA, Europe)" - rom ( name "WWE - Survivor Series (USA, Europe).gba" size 4194304 crc 6694f94d sha1 bb61e289f2a09e7ce0e193267430dbc6f5583a49 ) + rom ( name "WWE - Survivor Series (USA, Europe).gba" size 4194304 crc 6694f94d sha1 bb61e289f2a09e7ce0e193267430dbc6f5583a49 flags verified ) ) game ( @@ -19887,7 +20061,7 @@ game ( game ( name "Yu-Gi-Oh! Duel Monsters 6 Expert 2 (Japan)" description "Yu-Gi-Oh! Duel Monsters 6 Expert 2 (Japan)" - rom ( name "Yu-Gi-Oh! Duel Monsters 6 Expert 2 (Japan).gba" size 16777216 crc a7054d60 sha1 e5ee6fb7a6a4eb0e33197549fd30f8fe402b595f ) + rom ( name "Yu-Gi-Oh! Duel Monsters 6 Expert 2 (Japan).gba" size 16777216 crc a7054d60 sha1 e5ee6fb7a6a4eb0e33197549fd30f8fe402b595f flags verified ) ) game ( @@ -19995,7 +20169,7 @@ game ( game ( name "Zelda no Densetsu - Fushigi no Boushi (Japan)" description "Zelda no Densetsu - Fushigi no Boushi (Japan)" - rom ( name "Zelda no Densetsu - Fushigi no Boushi (Japan).gba" size 16777216 crc 6ce771a5 sha1 6c5404a1effb17f481f352181d0f1c61a2765c5d ) + rom ( name "Zelda no Densetsu - Fushigi no Boushi (Japan).gba" size 16777216 crc 6ce771a5 sha1 6c5404a1effb17f481f352181d0f1c61a2765c5d flags verified ) ) game ( @@ -20077,9 +20251,9 @@ game ( ) game ( - name "Zidane Football Generation 2002 (Europe) (En,Fr,De,Es,It)" - description "Zidane Football Generation 2002 (Europe) (En,Fr,De,Es,It)" - rom ( name "Zidane Football Generation 2002 (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 59e2635f sha1 e18e9e8aabb8844de688ddff8843329f84e470aa ) + name "Zidane - Football Generation 2002 (Europe) (En,Fr,De,Es,It)" + description "Zidane - Football Generation 2002 (Europe) (En,Fr,De,Es,It)" + rom ( name "Zidane - Football Generation 2002 (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 59e2635f sha1 e18e9e8aabb8844de688ddff8843329f84e470aa ) ) game ( @@ -20089,9 +20263,9 @@ game ( ) game ( - name "Zoids Legacy (USA)" - description "Zoids Legacy (USA)" - rom ( name "Zoids Legacy (USA).gba" size 8388608 crc 302e75f3 sha1 460fa2158606097f6e6f63ce966d2d6ecdd58d70 ) + name "Zoids - Legacy (USA)" + description "Zoids - Legacy (USA)" + rom ( name "Zoids - Legacy (USA).gba" size 8388608 crc 302e75f3 sha1 460fa2158606097f6e6f63ce966d2d6ecdd58d70 ) ) game ( @@ -20763,10 +20937,10 @@ game ( ) clrmamepro ( - name "Nintendo - Game Boy" - description "Nintendo - Game Boy" - version 20220829-183056 - author "aci68, akubi, Arctic Circle System, Aringon, baldjared, Bent, BigFred, BitLooter, C. V. Reynolds, chillerecke, darthcloud, DeadSkullzJr, Densetsu, DeriLoko3, ElBarto, foxe, fuzzball, Gefflon, Hiccup, hking0036, InternalLoss, jimmsu, kazumi213, leekindo, Lesserkuma, Madeline, Money_114, NESBrew12, NGEfreak, nnssxx, norkmetnoil577, NovaAurora, omonim2007, Powerpuff, PPLToast, relax, RetroUprising, rpg2813, sCZther, SonGoku, Tauwasser, togemet2, UnlockerPT, xNo, xuom2" + name "Nintendo - Game Boy Advance (Video)" + description "Nintendo - Game Boy Advance (Video)" + version 20230301-094141 + author "BigFred, C. V. Reynolds, DeadSkullzJr, Hiccup, kazumi213, omonim2007, relax, SonGoku, xuom2" homepage No-Intro url "https://www.no-intro.org" forcenodump required @@ -20777,9 +20951,281 @@ emulator ( ) game ( - name "[BIOS] Nintendo Game Boy Boot ROM (Japan) (En)" - description "[BIOS] Nintendo Game Boy Boot ROM (Japan) (En)" - rom ( name "[BIOS] Nintendo Game Boy Boot ROM (Japan) (En).gb" size 256 crc c2f5cc97 sha1 8bd501e31921e9601788316dbd3ce9833a97bcbc flags verified ) + name "Game Boy Advance Video - All Grown Up! - Volume 1 (USA)" + description "Game Boy Advance Video - All Grown Up! - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - All Grown Up! - Volume 1 (USA).gba" size 33554432 crc ffbd4da9 sha1 02158627fd5a526f848077440182fa92ad87ea3f ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Edition Platinum (France)" + description "Game Boy Advance Video - Cartoon Network Collection - Edition Platinum (France)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Edition Platinum (France).gba" size 33554432 crc fc042f18 sha1 01564b86644cf43f49320e0c05854896f8c60c2f ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Edition Premium (France)" + description "Game Boy Advance Video - Cartoon Network Collection - Edition Premium (France)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Edition Premium (France).gba" size 33554432 crc 58f1cde8 sha1 f63052e2b53c42601f53148bee69f89d9591fbf9 ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Edition Speciale (France)" + description "Game Boy Advance Video - Cartoon Network Collection - Edition Speciale (France)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Edition Speciale (France).gba" size 33554432 crc 71154d42 sha1 73cbdd82640f166737173b0e8197d771d7906a91 ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Limited Edition (USA)" + description "Game Boy Advance Video - Cartoon Network Collection - Limited Edition (USA)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Limited Edition (USA).gba" size 33554432 crc 5d918b2d sha1 ac63a7691774bde6eaad4c2c5c4d305e7cc0535a ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Platinum Edition (USA, Europe)" + description "Game Boy Advance Video - Cartoon Network Collection - Platinum Edition (USA, Europe)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Platinum Edition (USA, Europe).gba" size 33554432 crc 6443554b sha1 0cf9b536f87cb21f738bd7552e95bd114b0f0b2e ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Premium Edition (USA, Europe)" + description "Game Boy Advance Video - Cartoon Network Collection - Premium Edition (USA, Europe)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Premium Edition (USA, Europe).gba" size 33554432 crc f2825729 sha1 d0fe380fcdf5b1bb99188ed95c8c3272cbb65ce8 flags verified ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Special Edition (USA, Europe)" + description "Game Boy Advance Video - Cartoon Network Collection - Special Edition (USA, Europe)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Special Edition (USA, Europe).gba" size 33554432 crc e9b7b8a4 sha1 5f17d2ec1a3ba1d605d486b6f6abcde6d82df767 ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Volume 1 (USA, Europe)" + description "Game Boy Advance Video - Cartoon Network Collection - Volume 1 (USA, Europe)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Volume 1 (USA, Europe).gba" size 33554432 crc 91f39447 sha1 3227d82e64024a5dc0e5b7f3fd768b0fec19e9b0 ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Volume 2 (USA, Europe)" + description "Game Boy Advance Video - Cartoon Network Collection - Volume 2 (USA, Europe)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Volume 2 (USA, Europe).gba" size 33554432 crc 4bfaa8de sha1 692ffa1856780edd28b3aa55526ad06eeddc292e ) +) + +game ( + name "Game Boy Advance Video - Codename - Kids Next Door - Volume 1 (USA, Europe)" + description "Game Boy Advance Video - Codename - Kids Next Door - Volume 1 (USA, Europe)" + rom ( name "Game Boy Advance Video - Codename - Kids Next Door - Volume 1 (USA, Europe).gba" size 33554432 crc 4463f345 sha1 5e78808676213d6e5b55a78560fc47112f3008d4 ) +) + +game ( + name "Game Boy Advance Video - Disney Channel Collection - Volume 1 (USA)" + description "Game Boy Advance Video - Disney Channel Collection - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - Disney Channel Collection - Volume 1 (USA).gba" size 33554432 crc 7cc985cb sha1 53069a5a8b564495ad800f0685b4d1970859b0f8 ) +) + +game ( + name "Game Boy Advance Video - Disney Channel Collection - Volume 2 (USA) (Rev 5)" + description "Game Boy Advance Video - Disney Channel Collection - Volume 2 (USA) (Rev 5)" + rom ( name "Game Boy Advance Video - Disney Channel Collection - Volume 2 (USA) (Rev 5).gba" size 33554432 crc da3b64f3 sha1 6bb825bd00a8daae329ea40cdbfd3db48547585e ) +) + +game ( + name "Game Boy Advance Video - Dora the Explorer - Volume 1 (USA)" + description "Game Boy Advance Video - Dora the Explorer - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - Dora the Explorer - Volume 1 (USA).gba" size 33554432 crc dd6a7fcc sha1 67fe664bdadc7927ba888014ea923f11e3334b6c ) +) + +game ( + name "Game Boy Advance Video - Dragon Ball GT - Volume 1 (USA)" + description "Game Boy Advance Video - Dragon Ball GT - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - Dragon Ball GT - Volume 1 (USA).gba" size 33554432 crc da559199 sha1 dc85cccc0cede3fc11d257995cd7d3cc64ca00e0 ) +) + +game ( + name "Game Boy Advance Video - Nicktoons - Volume 3 (USA)" + description "Game Boy Advance Video - Nicktoons - Volume 3 (USA)" + rom ( name "Game Boy Advance Video - Nicktoons - Volume 3 (USA).gba" size 33554432 crc 6fbf5ceb sha1 2732103fda15d8fb017f21023d2fda22ae7a548a ) +) + +game ( + name "Game Boy Advance Video - Nicktoons Collection - Volume 1 (USA)" + description "Game Boy Advance Video - Nicktoons Collection - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - Nicktoons Collection - Volume 1 (USA).gba" size 33554432 crc 5d47676f sha1 b3bf6d95b548a82808a109cefaaf8b0c22ca66f2 ) +) + +game ( + name "Game Boy Advance Video - Nicktoons Collection - Volume 2 (USA)" + description "Game Boy Advance Video - Nicktoons Collection - Volume 2 (USA)" + rom ( name "Game Boy Advance Video - Nicktoons Collection - Volume 2 (USA).gba" size 33554432 crc 65deeeb6 sha1 03a767124f034b1eea51ab84ba6e80ec76f7f50c ) +) + +game ( + name "Game Boy Advance Video - Pokemon - Volume 1 (USA)" + description "Game Boy Advance Video - Pokemon - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - Pokemon - Volume 1 (USA).gba" size 33554432 crc a59ef954 sha1 ad0164df397860b0967590ee46ca1f41d6cecdcd ) +) + +game ( + name "Game Boy Advance Video - Pokemon - Volume 2 (USA)" + description "Game Boy Advance Video - Pokemon - Volume 2 (USA)" + rom ( name "Game Boy Advance Video - Pokemon - Volume 2 (USA).gba" size 33554432 crc 0da1a383 sha1 e7b2e00d6a60e8c149cd07b0ee8d98bf2b4efd15 ) +) + +game ( + name "Game Boy Advance Video - Pokemon - Volume 3 (USA)" + description "Game Boy Advance Video - Pokemon - Volume 3 (USA)" + rom ( name "Game Boy Advance Video - Pokemon - Volume 3 (USA).gba" size 33554432 crc a36aa9c5 sha1 0f0020ef4278e1777baaf74b6787355a5cb57d50 ) +) + +game ( + name "Game Boy Advance Video - Pokemon - Volume 4 (USA)" + description "Game Boy Advance Video - Pokemon - Volume 4 (USA)" + rom ( name "Game Boy Advance Video - Pokemon - Volume 4 (USA).gba" size 33554432 crc be468496 sha1 fd79b821ff601e0091f19ab9c86b08f1c39ea2f3 ) +) + +game ( + name "Game Boy Advance Video - Shark Tale (USA) (Rev 6)" + description "Game Boy Advance Video - Shark Tale (USA) (Rev 6)" + rom ( name "Game Boy Advance Video - Shark Tale (USA) (Rev 6).gba" size 67108864 crc d2cf417a sha1 9bc5b60793b8a6de3b80eb2c213cd125e1fa468e flags verified ) +) + +game ( + name "Game Boy Advance Video - Shark Tale (USA) (Rev 5)" + description "Game Boy Advance Video - Shark Tale (USA) (Rev 5)" + rom ( name "Game Boy Advance Video - Shark Tale (USA) (Rev 5).gba" size 67108864 crc 01468820 sha1 6128a476edb10e6839ac5bd2697e83b9b7a9b234 ) +) + +game ( + name "Game Boy Advance Video - Shrek (USA) (Rev 5)" + description "Game Boy Advance Video - Shrek (USA) (Rev 5)" + rom ( name "Game Boy Advance Video - Shrek (USA) (Rev 5).gba" size 67108864 crc 690176e5 sha1 2dedea86ec289a0e31089299613d9f74cca03609 flags verified ) +) + +game ( + name "Game Boy Advance Video - Shrek (USA) (Rev 6)" + description "Game Boy Advance Video - Shrek (USA) (Rev 6)" + rom ( name "Game Boy Advance Video - Shrek (USA) (Rev 6).gba" size 67108864 crc 4010e9fa sha1 95d612057682b7b886441d387b167ba2493e49ef ) +) + +game ( + name "Game Boy Advance Video - Shrek + Shark Tale (USA) (Rev 5)" + description "Game Boy Advance Video - Shrek + Shark Tale (USA) (Rev 5)" + rom ( name "Game Boy Advance Video - Shrek + Shark Tale (USA) (Rev 5).gba" size 67108864 crc aadb3e3d sha1 f23390b7a62c605fbce6730addc29d381488e076 flags verified ) +) + +game ( + name "Game Boy Advance Video - Shrek 2 (USA) (Rev 5)" + description "Game Boy Advance Video - Shrek 2 (USA) (Rev 5)" + rom ( name "Game Boy Advance Video - Shrek 2 (USA) (Rev 5).gba" size 67108864 crc 0d353654 sha1 5cd627e205020297b25d707131883be5850515fe flags verified ) +) + +game ( + name "Game Boy Advance Video - Shrek 2 (USA) (Rev 6)" + description "Game Boy Advance Video - Shrek 2 (USA) (Rev 6)" + rom ( name "Game Boy Advance Video - Shrek 2 (USA) (Rev 6).gba" size 67108864 crc 925b6c02 sha1 3bcb4acc5da539bc1b91c9537bf7ffa081ded1d4 ) +) + +game ( + name "Game Boy Advance Video - Sonic X - Volume 1 (USA)" + description "Game Boy Advance Video - Sonic X - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - Sonic X - Volume 1 (USA).gba" size 33554432 crc 363c3eb8 sha1 dc4c959a7740315d892633869f7fc6382aadbb0e ) +) + +game ( + name "Game Boy Advance Video - SpongeBob SquarePants - Volume 1 (USA) (Rev 1)" + description "Game Boy Advance Video - SpongeBob SquarePants - Volume 1 (USA) (Rev 1)" + rom ( name "Game Boy Advance Video - SpongeBob SquarePants - Volume 1 (USA) (Rev 1).gba" size 33554432 crc d47bf1d4 sha1 65f6b06c5397b9406960050025361550e0ff2e92 ) +) + +game ( + name "Game Boy Advance Video - SpongeBob SquarePants - Volume 2 (USA) (Rev 1)" + description "Game Boy Advance Video - SpongeBob SquarePants - Volume 2 (USA) (Rev 1)" + rom ( name "Game Boy Advance Video - SpongeBob SquarePants - Volume 2 (USA) (Rev 1).gba" size 33554432 crc 7074364a sha1 c4caa875f93657626bf73541ddd61eb5c8b4957e ) +) + +game ( + name "Game Boy Advance Video - SpongeBob SquarePants - Volume 3 (USA)" + description "Game Boy Advance Video - SpongeBob SquarePants - Volume 3 (USA)" + rom ( name "Game Boy Advance Video - SpongeBob SquarePants - Volume 3 (USA).gba" size 33554432 crc 9772ca45 sha1 fa11aeef21cdd18fcfea281990030acb65c7aa96 ) +) + +game ( + name "Game Boy Advance Video - Strawberry Shortcake - Volume 1 (USA)" + description "Game Boy Advance Video - Strawberry Shortcake - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - Strawberry Shortcake - Volume 1 (USA).gba" size 33554432 crc ff7582ef sha1 f84cc8a33e3a75e42f4e8023a5cc851c92ea409a ) +) + +game ( + name "Game Boy Advance Video - Super Robot Monkey Team - Hyper Force Go! - Volume 1 (USA)" + description "Game Boy Advance Video - Super Robot Monkey Team - Hyper Force Go! - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - Super Robot Monkey Team - Hyper Force Go! - Volume 1 (USA).gba" size 33554432 crc d743a070 sha1 e3bce6ddf9437bded89fc662e992c1ab0aa1dbba ) +) + +game ( + name "Game Boy Advance Video - Teenage Mutant Ninja Turtles - Le Demenagement (France)" + description "Game Boy Advance Video - Teenage Mutant Ninja Turtles - Le Demenagement (France)" + rom ( name "Game Boy Advance Video - Teenage Mutant Ninja Turtles - Le Demenagement (France).gba" size 33554432 crc 1ee78166 sha1 1c7444a45c9bd15b1643c195832497881955acee ) +) + +game ( + name "Game Boy Advance Video - Teenage Mutant Ninja Turtles - Things Change (USA, Europe)" + description "Game Boy Advance Video - Teenage Mutant Ninja Turtles - Things Change (USA, Europe)" + rom ( name "Game Boy Advance Video - Teenage Mutant Ninja Turtles - Things Change (USA, Europe).gba" size 33554432 crc 046589c8 sha1 8b9665ce2cefd663f7722f38b64f9271f7c00daa ) +) + +game ( + name "Game Boy Advance Video - The Adventures of Jimmy Neutron Boy Genius - Volume 1 (USA)" + description "Game Boy Advance Video - The Adventures of Jimmy Neutron Boy Genius - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - The Adventures of Jimmy Neutron Boy Genius - Volume 1 (USA).gba" size 33554432 crc 0e556edf sha1 1414c55bd3d252fdc05c0a94085f12dddf86ea31 ) +) + +game ( + name "Game Boy Advance Video - The Fairly OddParents! - Volume 1 (USA)" + description "Game Boy Advance Video - The Fairly OddParents! - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - The Fairly OddParents! - Volume 1 (USA).gba" size 33554432 crc 7958df20 sha1 42e343e5048f99f05f7cde5aad6bfcc3d2410399 ) +) + +game ( + name "Game Boy Advance Video - The Fairly OddParents! - Volume 2 (USA) (Rev 1)" + description "Game Boy Advance Video - The Fairly OddParents! - Volume 2 (USA) (Rev 1)" + rom ( name "Game Boy Advance Video - The Fairly OddParents! - Volume 2 (USA) (Rev 1).gba" size 33554432 crc bbcbc6fd sha1 5b8a83410d8f9de12113e2974d65b5790aaf62fb ) +) + +game ( + name "Game Boy Advance Video - The Proud Family - Volume 1 (USA)" + description "Game Boy Advance Video - The Proud Family - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - The Proud Family - Volume 1 (USA).gba" size 33554432 crc c6a91365 sha1 31a2cbb9b1b5adf7de399eecf2a9cec4f5412767 ) +) + +game ( + name "Game Boy Advance Video - Yu-Gi-Oh! - Yugi vs. Joey (France)" + description "Game Boy Advance Video - Yu-Gi-Oh! - Yugi vs. Joey (France)" + rom ( name "Game Boy Advance Video - Yu-Gi-Oh! - Yugi vs. Joey (France).gba" size 33554432 crc c9b6ccf1 sha1 8fb43e5018cf2d4b1a2aa2d1693862ce248ab6b0 ) +) + +game ( + name "Game Boy Advance Video - Yu-Gi-Oh! - Yugi vs. Joey (USA, Europe)" + description "Game Boy Advance Video - Yu-Gi-Oh! - Yugi vs. Joey (USA, Europe)" + rom ( name "Game Boy Advance Video - Yu-Gi-Oh! - Yugi vs. Joey (USA, Europe).gba" size 33554432 crc 8d631f4e sha1 6daf15bb61a10d1bbf94009886d6a76f9c937f8b flags verified ) +) + +clrmamepro ( + name "Nintendo - Game Boy" + description "Nintendo - Game Boy" + version 20230422-225330 + author "aci68, akubi, Arctic Circle System, Aringon, baldjared, Bent, BigFred, BitLooter, buckwheat, C. V. Reynolds, chillerecke, darthcloud, DeadSkullzJr, Densetsu, DeriLoko3, ElBarto, foxe, fuzzball, Gefflon, Hiccup, hking0036, InternalLoss, jimmsu, kazumi213, leekindo, Lesserkuma, Madeline, NESBrew12, NGEfreak, nnssxx, norkmetnoil577, NovaAurora, omonim2007, Powerpuff, PPLToast, rarenight, relax, RetroUprising, rpg2813, sCZther, SonGoku, Tauwasser, togemet2, UnlockerPT, xNo, xuom2" + homepage No-Intro + url "https://www.no-intro.org" + forcenodump required +) + +emulator ( + name "datafile" +) + +game ( + name "[BIOS] Maxstation Boot ROM (China) (En) (Unl)" + description "[BIOS] Maxstation Boot ROM (China) (En) (Unl)" + rom ( name "[BIOS] Maxstation Boot ROM (China) (En) (Unl).gb" size 256 crc 783e69c2 sha1 1776bd61b8db71fc4c4d4b5feab4a21b3c1fd95b ) ) game ( @@ -20788,6 +21234,12 @@ game ( rom ( name "[BIOS] Nintendo Game Boy Boot ROM (World) (Rev 1).gb" size 256 crc 59c8598e sha1 4ed31ec6b0b175bb109c0eb5fd3d193da823339f flags verified ) ) +game ( + name "[BIOS] Nintendo Game Boy Boot ROM (Japan) (En)" + description "[BIOS] Nintendo Game Boy Boot ROM (Japan) (En)" + rom ( name "[BIOS] Nintendo Game Boy Boot ROM (Japan) (En).gb" size 256 crc c2f5cc97 sha1 8bd501e31921e9601788316dbd3ce9833a97bcbc flags verified ) +) + game ( name "[BIOS] Nintendo Game Boy Pocket Boot ROM (World)" description "[BIOS] Nintendo Game Boy Pocket Boot ROM (World)" @@ -20795,15 +21247,9 @@ game ( ) game ( - name "10-Pin Bowling (USA)" - description "10-Pin Bowling (USA)" - rom ( name "10-Pin Bowling (USA).gb" size 131072 crc 9a024415 sha1 952d154dd2c6189ef4b786ae37bd7887c8ca9037 ) -) - -game ( - name "1bit Game Collection (World) (Aftermarket) (Homebrew)" - description "1bit Game Collection (World) (Aftermarket) (Homebrew)" - rom ( name "1bit Game Collection (World) (Aftermarket) (Homebrew).gbc" size 131072 crc 7230319a sha1 8ab9b6d16e374172f9a88a3eded19f4888179143 flags verified ) + name "10-Pin Bowling (USA) (Proto)" + description "10-Pin Bowling (USA) (Proto)" + rom ( name "10-Pin Bowling (USA) (Proto).gb" size 131072 crc 9a024415 sha1 952d154dd2c6189ef4b786ae37bd7887c8ca9037 ) ) game ( @@ -20909,9 +21355,9 @@ game ( ) game ( - name "8-in-1 (Unknown) (Unl)" - description "8-in-1 (Unknown) (Unl)" - rom ( name "8-in-1 (Unknown) (Unl).gb" size 524288 crc fbc77225 sha1 f57efd4d5063cb87519fc3b574377cc9a2a841ee ) + name "8-in-1 (Taiwan) (En) (Unl)" + description "8-in-1 (Taiwan) (En) (Unl)" + rom ( name "8-in-1 (Taiwan) (En) (Unl).gb" size 524288 crc fbc77225 sha1 f57efd4d5063cb87519fc3b574377cc9a2a841ee ) ) game ( @@ -20926,6 +21372,12 @@ game ( rom ( name "Aa Harimanada (Japan).gb" size 131072 crc 5bffcc28 sha1 ff3565fcac22b44bf542732d1b0b20ba96a6c961 ) ) +game ( + name "Action Replay Pro (World)" + description "Action Replay Pro (World)" + rom ( name "Action Replay Pro (World).gb" size 16384 crc 2ea05daa sha1 e947b9264092168950ad1ce23bbe3d8ccfed765e ) +) + game ( name "Addams Family, The (Europe) (En,Fr,De)" description "Addams Family, The (Europe) (En,Fr,De)" @@ -20950,6 +21402,18 @@ game ( rom ( name "Addams Family, The - Pugsley's Scavenger Hunt (USA, Europe).gb" size 131072 crc 7e054a88 sha1 f9020e3d104cb5c5347e28f45ed9e24e6c0ebddd flags verified ) ) +game ( + name "Adulting! (World) (v2.0) (Aftermarket) (Unl)" + description "Adulting! (World) (v2.0) (Aftermarket) (Unl)" + rom ( name "Adulting! (World) (v2.0) (Aftermarket) (Unl).gb" size 524288 crc e56d1244 sha1 d107bd8bf32d0d94a988466885fe1a44aae32c9a ) +) + +game ( + name "Adulting! Soundtrack (World) (Aftermarket) (Unl)" + description "Adulting! Soundtrack (World) (Aftermarket) (Unl)" + rom ( name "Adulting! Soundtrack (World) (Aftermarket) (Unl).gb" size 131072 crc 1b5b9e64 sha1 c5dad32be800c36814a0f9b940e99b5eada34150 ) +) + game ( name "Adventure Island (USA, Europe)" description "Adventure Island (USA, Europe)" @@ -21112,6 +21576,12 @@ game ( rom ( name "Alien vs Predator - The Last of His Clan (USA).gb" size 131072 crc 4bbcf652 sha1 c0d39ee87b908cdbd68c59f73e5dc2a7a6ccbedc ) ) +game ( + name "All Humans Must Die! (World) (Aftermarket) (Unl)" + description "All Humans Must Die! (World) (Aftermarket) (Unl)" + rom ( name "All Humans Must Die! (World) (Aftermarket) (Unl).gb" size 524288 crc b4d50eed sha1 f4513fc525cbcfa4a2804b55ce20c809e01d1c87 ) +) + game ( name "All-Star Baseball 99 (USA)" description "All-Star Baseball 99 (USA)" @@ -21125,9 +21595,9 @@ game ( ) game ( - name "Alphamax (World) (Aftermarket) (Homebrew)" - description "Alphamax (World) (Aftermarket) (Homebrew)" - rom ( name "Alphamax (World) (Aftermarket) (Homebrew).gb" size 131072 crc 8b493b41 sha1 798dda34d04a06dcee32f44f8a4a045caf734927 ) + name "Alphamax (World) (Aftermarket) (Unl)" + description "Alphamax (World) (Aftermarket) (Unl)" + rom ( name "Alphamax (World) (Aftermarket) (Unl).gb" size 131072 crc 8b493b41 sha1 798dda34d04a06dcee32f44f8a4a045caf734927 ) ) game ( @@ -21221,9 +21691,9 @@ game ( ) game ( - name "Another Adventure (World) (Aftermarket) (Homebrew)" - description "Another Adventure (World) (Aftermarket) (Homebrew)" - rom ( name "Another Adventure (World) (Aftermarket) (Homebrew).gb" size 1048576 crc dfcd02ef sha1 041473b2381dd9e11b3cbda5858f9841324327af ) + name "Another Adventure (World) (En,Es) (Aftermarket) (Unl)" + description "Another Adventure (World) (En,Es) (Aftermarket) (Unl)" + rom ( name "Another Adventure (World) (En,Es) (Aftermarket) (Unl).gb" size 1048576 crc dfcd02ef sha1 041473b2381dd9e11b3cbda5858f9841324327af ) ) game ( @@ -21239,9 +21709,9 @@ game ( ) game ( - name "[MIA] Aprilia - DiTech Interface (Unknown) (Unl) [b]" - description "[MIA] Aprilia - DiTech Interface (Unknown) (Unl) [b]" - rom ( name "Aprilia - DiTech Interface (Unknown) (Unl) [b].gb" size 32768 crc 07d0f62c sha1 134625e507bc230efa236908034449fb1afcfdfc flags baddump ) + name "Aprilia - DiTech Interface (Unknown) (Unl) [b]" + description "Aprilia - DiTech Interface (Unknown) (Unl) [b]" + rom ( name "Aprilia - DiTech Interface (Unknown) (Unl) [b].gb" size 262144 crc 2e47d2d3 sha1 a921679b79191991eb75de3b6b21443f322a3699 flags baddump ) ) game ( @@ -21299,27 +21769,27 @@ game ( ) game ( - name "Art School Pocket (World) (Aftermarket) (Homebrew)" - description "Art School Pocket (World) (Aftermarket) (Homebrew)" - rom ( name "Art School Pocket (World) (Aftermarket) (Homebrew).gb" size 1048576 crc b4eab528 sha1 c482cfc6ec40b1f33c4ba48ecdc45fef4730b653 ) + name "Art School Pocket (World) (Aftermarket) (Unl)" + description "Art School Pocket (World) (Aftermarket) (Unl)" + rom ( name "Art School Pocket (World) (Aftermarket) (Unl).gb" size 1048576 crc b4eab528 sha1 c482cfc6ec40b1f33c4ba48ecdc45fef4730b653 ) ) game ( - name "Art School Pocket (Spain) (Aftermarket) (Homebrew)" - description "Art School Pocket (Spain) (Aftermarket) (Homebrew)" - rom ( name "Art School Pocket (Spain) (Aftermarket) (Homebrew).gb" size 1048576 crc 240067df sha1 c8a75895c87b11f9493f629c19c54d0c607505bd ) + name "Art School Pocket (Spain) (Aftermarket) (Unl)" + description "Art School Pocket (Spain) (Aftermarket) (Unl)" + rom ( name "Art School Pocket (Spain) (Aftermarket) (Unl).gb" size 1048576 crc 240067df sha1 c8a75895c87b11f9493f629c19c54d0c607505bd ) ) game ( - name "Art School Pocket (France) (Aftermarket) (Homebrew)" - description "Art School Pocket (France) (Aftermarket) (Homebrew)" - rom ( name "Art School Pocket (France) (Aftermarket) (Homebrew).gb" size 1048576 crc 49a5b74d sha1 73ecffaee185eb2caf1db385d04b863676c777fb ) + name "Art School Pocket (France) (Aftermarket) (Unl)" + description "Art School Pocket (France) (Aftermarket) (Unl)" + rom ( name "Art School Pocket (France) (Aftermarket) (Unl).gb" size 1048576 crc 49a5b74d sha1 73ecffaee185eb2caf1db385d04b863676c777fb ) ) game ( - name "Art School Pocket (Germany) (Aftermarket) (Homebrew)" - description "Art School Pocket (Germany) (Aftermarket) (Homebrew)" - rom ( name "Art School Pocket (Germany) (Aftermarket) (Homebrew).gb" size 1048576 crc 82b73e5b sha1 7772e2b9d5e722e54d19fc6283ccb1fa5d19b641 ) + name "Art School Pocket (Germany) (Aftermarket) (Unl)" + description "Art School Pocket (Germany) (Aftermarket) (Unl)" + rom ( name "Art School Pocket (Germany) (Aftermarket) (Unl).gb" size 1048576 crc 82b73e5b sha1 7772e2b9d5e722e54d19fc6283ccb1fa5d19b641 ) ) game ( @@ -21389,15 +21859,15 @@ game ( ) game ( - name "Astro-Jump (World) (Aftermarket) (Homebrew)" - description "Astro-Jump (World) (Aftermarket) (Homebrew)" - rom ( name "Astro-Jump (World) (Aftermarket) (Homebrew).gb" size 262144 crc c35a3b39 sha1 1bcb4be684626ce061aad105701548fa3a77e254 ) + name "Astro-Jump (World) (Aftermarket) (Unl)" + description "Astro-Jump (World) (Aftermarket) (Unl)" + rom ( name "Astro-Jump (World) (Aftermarket) (Unl).gb" size 262144 crc c35a3b39 sha1 1bcb4be684626ce061aad105701548fa3a77e254 ) ) game ( - name "Astro-Jump - The Sequel (World) (Aftermarket) (Homebrew)" - description "Astro-Jump - The Sequel (World) (Aftermarket) (Homebrew)" - rom ( name "Astro-Jump - The Sequel (World) (Aftermarket) (Homebrew).gb" size 131072 crc e6a96130 sha1 2cd0ef086c4b89497d9497a17d871a6568a9e2d3 ) + name "Astro-Jump - The Sequel (World) (Aftermarket) (Unl)" + description "Astro-Jump - The Sequel (World) (Aftermarket) (Unl)" + rom ( name "Astro-Jump - The Sequel (World) (Aftermarket) (Unl).gb" size 131072 crc e6a96130 sha1 2cd0ef086c4b89497d9497a17d871a6568a9e2d3 ) ) game ( @@ -21419,15 +21889,15 @@ game ( ) game ( - name "Auto Zone (World) (Aftermarket) (Homebrew)" - description "Auto Zone (World) (Aftermarket) (Homebrew)" - rom ( name "Auto Zone (World) (Aftermarket) (Homebrew).gb" size 524288 crc cee73c14 sha1 3070ec215014633dac5dbbb487aade2e2993c049 ) + name "Auto Zone (World) (Aftermarket) (Unl)" + description "Auto Zone (World) (Aftermarket) (Unl)" + rom ( name "Auto Zone (World) (Aftermarket) (Unl).gb" size 524288 crc cee73c14 sha1 3070ec215014633dac5dbbb487aade2e2993c049 ) ) game ( - name "Autumn With You, An (World) (Aftermarket) (Homebrew)" - description "Autumn With You, An (World) (Aftermarket) (Homebrew)" - rom ( name "Autumn With You, An (World) (Aftermarket) (Homebrew).gb" size 262144 crc eac459fa sha1 68a5520be76c52a4936ad1756ea274667f2ac0e3 ) + name "Autumn With You, An (World) (Aftermarket) (Unl)" + description "Autumn With You, An (World) (Aftermarket) (Unl)" + rom ( name "Autumn With You, An (World) (Aftermarket) (Unl).gb" size 262144 crc eac459fa sha1 68a5520be76c52a4936ad1756ea274667f2ac0e3 ) ) game ( @@ -21742,6 +22212,12 @@ game ( rom ( name "Battletoads-Double Dragon (USA).gb" size 262144 crc a727f9cd sha1 ce68df4dc2d604625164430266017b237b72303d ) ) +game ( + name "Batty Zabella (World) (Aftermarket) (Unl)" + description "Batty Zabella (World) (Aftermarket) (Unl)" + rom ( name "Batty Zabella (World) (Aftermarket) (Unl).gb" size 1048576 crc dfa3c64c sha1 2361d5f87bd525964e61bd39765a6fa843fa4c43 ) +) + game ( name "Beavis and Butt-head (USA, Europe)" description "Beavis and Butt-head (USA, Europe)" @@ -21790,6 +22266,12 @@ game ( rom ( name "Bill & Ted's Excellent Game Boy Adventure - A Bogus Journey! (USA, Europe).gb" size 131072 crc 5e8f656a sha1 e19a7e5a5bd8fde0f31773c22dbebc9b4cf3824e flags verified ) ) +game ( + name "Bill & Ted's Excellent Portable Adventure - A Bogus Journey! (World) (Aftermarket) (Unl)" + description "Bill & Ted's Excellent Portable Adventure - A Bogus Journey! (World) (Aftermarket) (Unl)" + rom ( name "Bill & Ted's Excellent Portable Adventure - A Bogus Journey! (World) (Aftermarket) (Unl).gb" size 131072 crc 7b5e19ca sha1 c6e606cb5e17f7530a529387ad2fb703a94b205a ) +) + game ( name "Bill Elliott's NASCAR Fast Tracks (USA)" description "Bill Elliott's NASCAR Fast Tracks (USA)" @@ -21839,15 +22321,15 @@ game ( ) game ( - name "Black Castle GB (World) (Aftermarket) (Homebrew)" - description "Black Castle GB (World) (Aftermarket) (Homebrew)" - rom ( name "Black Castle GB (World) (Aftermarket) (Homebrew).gb" size 65536 crc 10f577c7 sha1 45d979be572bb820835d2ecd4e990cd1eadbf5a6 ) + name "Black Castle GB (World) (Aftermarket) (Unl)" + description "Black Castle GB (World) (Aftermarket) (Unl)" + rom ( name "Black Castle GB (World) (Aftermarket) (Unl).gb" size 65536 crc 10f577c7 sha1 45d979be572bb820835d2ecd4e990cd1eadbf5a6 ) ) game ( - name "Black Tape (World) (Aftermarket) (Homebrew)" - description "Black Tape (World) (Aftermarket) (Homebrew)" - rom ( name "Black Tape (World) (Aftermarket) (Homebrew).gb" size 524288 crc 45c5f990 sha1 ec8c467e955d69b2084805f11a954e0d90855461 ) + name "Black Tape (World) (Aftermarket) (Unl)" + description "Black Tape (World) (Aftermarket) (Unl)" + rom ( name "Black Tape (World) (Aftermarket) (Unl).gb" size 524288 crc 45c5f990 sha1 ec8c467e955d69b2084805f11a954e0d90855461 ) ) game ( @@ -21887,9 +22369,9 @@ game ( ) game ( - name "Blitz Bomber (World) (Aftermarket) (Homebrew)" - description "Blitz Bomber (World) (Aftermarket) (Homebrew)" - rom ( name "Blitz Bomber (World) (Aftermarket) (Homebrew).gb" size 262144 crc 5e9956de sha1 b72cbc6bfa6ceef940f49c9c82024071c6f82b90 ) + name "Blitz Bomber (World) (Aftermarket) (Unl)" + description "Blitz Bomber (World) (Aftermarket) (Unl)" + rom ( name "Blitz Bomber (World) (Aftermarket) (Unl).gb" size 262144 crc 5e9956de sha1 b72cbc6bfa6ceef940f49c9c82024071c6f82b90 ) ) game ( @@ -21899,9 +22381,9 @@ game ( ) game ( - name "Blockade (World) (Aftermarket) (Homebrew)" - description "Blockade (World) (Aftermarket) (Homebrew)" - rom ( name "Blockade (World) (Aftermarket) (Homebrew).gb" size 262144 crc b8cfab16 sha1 9e753048a0eb036ac17c08f5a64d860abd1aaaf5 ) + name "Blockade (World) (Aftermarket) (Unl)" + description "Blockade (World) (Aftermarket) (Unl)" + rom ( name "Blockade (World) (Aftermarket) (Unl).gb" size 262144 crc b8cfab16 sha1 9e753048a0eb036ac17c08f5a64d860abd1aaaf5 ) ) game ( @@ -21910,6 +22392,12 @@ game ( rom ( name "Blodia (Japan).gb" size 65536 crc 51ff6e53 sha1 a3927543ca471f479ba7aae7295788434672464e ) ) +game ( + name "Blues Brothers (USA, Europe) (Beta)" + description "Blues Brothers (USA, Europe) (Beta)" + rom ( name "Blues Brothers (USA, Europe) (Beta).gb" size 131072 crc 1f90d12a sha1 f7139bb55c3cc7bae672ade9022fb548b72f6f2f ) +) + game ( name "Blues Brothers, The (USA, Europe)" description "Blues Brothers, The (USA, Europe)" @@ -21925,7 +22413,7 @@ game ( game ( name "Bo Jackson - Two Games in One (USA)" description "Bo Jackson - Two Games in One (USA)" - rom ( name "Bo Jackson - Two Games in One (USA).gb" size 131072 crc 7edb78ab sha1 cb360f44a398ff5cd3818b0e244ffdf7019d85c7 ) + rom ( name "Bo Jackson - Two Games in One (USA).gb" size 131072 crc 7edb78ab sha1 cb360f44a398ff5cd3818b0e244ffdf7019d85c7 flags verified ) ) game ( @@ -21947,9 +22435,9 @@ game ( ) game ( - name "BOING! (World) (Aftermarket) (Homebrew)" - description "BOING! (World) (Aftermarket) (Homebrew)" - rom ( name "BOING! (World) (Aftermarket) (Homebrew).gb" size 2097152 crc 91961796 sha1 09fde4065941784bab4cf8f624a0398049ed4add ) + name "BOING! (World) (Aftermarket) (Unl)" + description "BOING! (World) (Aftermarket) (Unl)" + rom ( name "BOING! (World) (Aftermarket) (Unl).gb" size 2097152 crc 91961796 sha1 09fde4065941784bab4cf8f624a0398049ed4add ) ) game ( @@ -22015,7 +22503,7 @@ game ( game ( name "Bomberman GB (USA, Europe) (SGB Enhanced)" description "Bomberman GB (USA, Europe) (SGB Enhanced)" - rom ( name "Bomberman GB (USA, Europe) (SGB Enhanced).gb" size 262144 crc f372d175 sha1 f7058f31ddaec63f3b9c45ee9caf3c8e2cae1ca8 ) + rom ( name "Bomberman GB (USA, Europe) (SGB Enhanced).gb" size 262144 crc f372d175 sha1 f7058f31ddaec63f3b9c45ee9caf3c8e2cae1ca8 flags verified ) ) game ( @@ -22061,9 +22549,9 @@ game ( ) game ( - name "Borbo's Quest (World) (Aftermarket) (Homebrew)" - description "Borbo's Quest (World) (Aftermarket) (Homebrew)" - rom ( name "Borbo's Quest (World) (Aftermarket) (Homebrew).gb" size 1048576 crc e92ce3d4 sha1 1ddf864188dfd741ccda0b0715e75e162d212605 ) + name "Borbo's Quest (World) (Aftermarket) (Unl)" + description "Borbo's Quest (World) (Aftermarket) (Unl)" + rom ( name "Borbo's Quest (World) (Aftermarket) (Unl).gb" size 1048576 crc e92ce3d4 sha1 1ddf864188dfd741ccda0b0715e75e162d212605 ) ) game ( @@ -22085,9 +22573,15 @@ game ( ) game ( - name "Bounce (World) (v1.1) (Homebrew)" - description "Bounce (World) (v1.1) (Homebrew)" - rom ( name "Bounce (World) (v1.1) (Homebrew).gb" size 32768 crc 827f2bd5 sha1 e9abf64de3faeb48151003ac8bb77dea61e838b5 ) + name "Bounce (World) (v1.1) (Aftermarket) (Unl)" + description "Bounce (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Bounce (World) (v1.1) (Aftermarket) (Unl).gb" size 32768 crc 827f2bd5 sha1 e9abf64de3faeb48151003ac8bb77dea61e838b5 ) +) + +game ( + name "Bouncing Ball, The (World) (Aftermarket)" + description "Bouncing Ball, The (World) (Aftermarket)" + rom ( name "Bouncing Ball, The (World) (Aftermarket).gb" size 65536 crc 42ddf53e sha1 5d331f2e66d7d3f4b4b0fcbaf6ab4b1a0147db3e ) ) game ( @@ -22235,21 +22729,21 @@ game ( ) game ( - name "Bung Greetings Demo (World) (Demo) (Homebrew)" - description "Bung Greetings Demo (World) (Demo) (Homebrew)" - rom ( name "Bung Greetings Demo (World) (Demo) (Homebrew).gb" size 65536 crc 1ae05af4 sha1 bb01f845d330377749aeb6df1f7bea0eff78bb89 ) + name "Bung Greetings Demo (World) (Demo) (Unl)" + description "Bung Greetings Demo (World) (Demo) (Unl)" + rom ( name "Bung Greetings Demo (World) (Demo) (Unl).gb" size 65536 crc 1ae05af4 sha1 bb01f845d330377749aeb6df1f7bea0eff78bb89 ) ) game ( - name "Bung New Year's Greetings Demo (World) (Demo) (Homebrew)" - description "Bung New Year's Greetings Demo (World) (Demo) (Homebrew)" - rom ( name "Bung New Year's Greetings Demo (World) (Demo) (Homebrew).gb" size 65536 crc 493d4a1e sha1 737402fe70924f893fc212393eeb7f46f225745a ) + name "Bung New Year's Greetings Demo (World) (Demo) (Unl)" + description "Bung New Year's Greetings Demo (World) (Demo) (Unl)" + rom ( name "Bung New Year's Greetings Demo (World) (Demo) (Unl).gb" size 65536 crc 493d4a1e sha1 737402fe70924f893fc212393eeb7f46f225745a ) ) game ( - name "Bung's Math Test (World) (Homebrew)" - description "Bung's Math Test (World) (Homebrew)" - rom ( name "Bung's Math Test (World) (Homebrew).gb" size 32768 crc ebeda9d2 sha1 68581c24793e8ea63f3273efa7bc098309d77d1a ) + name "Bung's Math Test (World) (Unl)" + description "Bung's Math Test (World) (Unl)" + rom ( name "Bung's Math Test (World) (Unl).gb" size 32768 crc ebeda9d2 sha1 68581c24793e8ea63f3273efa7bc098309d77d1a ) ) game ( @@ -22295,9 +22789,9 @@ game ( ) game ( - name "Busty Bunny the Bounty Babe (World) (v1.02) (Aftermarket) (Homebrew)" - description "Busty Bunny the Bounty Babe (World) (v1.02) (Aftermarket) (Homebrew)" - rom ( name "Busty Bunny the Bounty Babe (World) (v1.02) (Aftermarket) (Homebrew).gb" size 1048576 crc d0ed199c sha1 a1cbaacdf32cb8fb9a9c59fcb624c729e0ca17bf ) + name "Busty Bunny the Bounty Babe (World) (v1.02) (Aftermarket) (Unl)" + description "Busty Bunny the Bounty Babe (World) (v1.02) (Aftermarket) (Unl)" + rom ( name "Busty Bunny the Bounty Babe (World) (v1.02) (Aftermarket) (Unl).gb" size 1048576 crc d0ed199c sha1 a1cbaacdf32cb8fb9a9c59fcb624c729e0ca17bf ) ) game ( @@ -22468,6 +22962,12 @@ game ( rom ( name "Castlevania Legends (USA, Europe) (SGB Enhanced).gb" size 262144 crc ad9c17fb sha1 91a8e49bf6eac5fe62ec2cc5e6decbd08ce9b515 flags verified ) ) +game ( + name "Cat Boy's Stellar Journey (World) (Aftermarket) (Unl)" + description "Cat Boy's Stellar Journey (World) (Aftermarket) (Unl)" + rom ( name "Cat Boy's Stellar Journey (World) (Aftermarket) (Unl).gb" size 262144 crc 280937cc sha1 9ec53942f0136076b6468671562764e1ab9dee3b ) +) + game ( name "Catrap (USA)" description "Catrap (USA)" @@ -22480,6 +22980,12 @@ game ( rom ( name "Catrap (USA) (Beta).gb" size 32768 crc ca3bc888 sha1 928b9b3cb2ed5e6d6fc754679b3c994f7ed803c3 ) ) +game ( + name "Cave Fighter (World) (Aftermarket) (Unl)" + description "Cave Fighter (World) (Aftermarket) (Unl)" + rom ( name "Cave Fighter (World) (Aftermarket) (Unl).gb" size 262144 crc e8f91f6a sha1 290048717d0a8cabb3cc591161339550eb53c161 ) +) + game ( name "Cave Noire (Japan)" description "Cave Noire (Japan)" @@ -22529,9 +23035,9 @@ game ( ) game ( - name "Cherry Rescue! (World) (Aftermarket) (Homebrew)" - description "Cherry Rescue! (World) (Aftermarket) (Homebrew)" - rom ( name "Cherry Rescue! (World) (Aftermarket) (Homebrew).gb" size 524288 crc ba65812a sha1 740dba1827c730cc5d8bf67495bcede3a5352643 ) + name "Cherry Rescue! (World) (Aftermarket) (Unl)" + description "Cherry Rescue! (World) (Aftermarket) (Unl)" + rom ( name "Cherry Rescue! (World) (Aftermarket) (Unl).gb" size 524288 crc ba65812a sha1 740dba1827c730cc5d8bf67495bcede3a5352643 ) ) game ( @@ -22655,9 +23161,9 @@ game ( ) game ( - name "Christmas Carols (World) (Aftermarket) (Homebrew)" - description "Christmas Carols (World) (Aftermarket) (Homebrew)" - rom ( name "Christmas Carols (World) (Aftermarket) (Homebrew).gb" size 262144 crc a00bb310 sha1 e19b0a698d8194467d57c00664f00f9898ee5368 ) + name "Christmas Carols (World) (Aftermarket) (Unl)" + description "Christmas Carols (World) (Aftermarket) (Unl)" + rom ( name "Christmas Carols (World) (Aftermarket) (Unl).gb" size 262144 crc a00bb310 sha1 e19b0a698d8194467d57c00664f00f9898ee5368 ) ) game ( @@ -22667,27 +23173,27 @@ game ( ) game ( - name "Ciao Nonna (World) (v1.1) (Aftermarket) (Homebrew)" - description "Ciao Nonna (World) (v1.1) (Aftermarket) (Homebrew)" - rom ( name "Ciao Nonna (World) (v1.1) (Aftermarket) (Homebrew).gb" size 1048576 crc 4cd61bbf sha1 4bcae0e12b4d999099ce7ceb6d44c5ac45eeb292 ) + name "Ciao Nonna (World) (v1.1) (Aftermarket) (Unl)" + description "Ciao Nonna (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Ciao Nonna (World) (v1.1) (Aftermarket) (Unl).gb" size 1048576 crc 2165f2e4 sha1 f8e5020298183c3f47836da3890b6bb398e2ecab ) ) game ( - name "Ciao Nonna (World) (v2.0) (Aftermarket) (Homebrew)" - description "Ciao Nonna (World) (v2.0) (Aftermarket) (Homebrew)" - rom ( name "Ciao Nonna (World) (v2.0) (Aftermarket) (Homebrew).gb" size 1048576 crc 85f7d6e3 sha1 468b4902440ba87fbb7c7dbf89b094ec5e24d123 ) + name "Ciao Nonna (World) (v2.0) (Aftermarket) (Unl)" + description "Ciao Nonna (World) (v2.0) (Aftermarket) (Unl)" + rom ( name "Ciao Nonna (World) (v2.0) (Aftermarket) (Unl).gb" size 1048576 crc 6afee818 sha1 602709c8655febb42899881a8ef598fb4b3da0c9 ) ) game ( - name "Ciao Nonna (World) (v2.1) (Aftermarket) (Homebrew)" - description "Ciao Nonna (World) (v2.1) (Aftermarket) (Homebrew)" - rom ( name "Ciao Nonna (World) (v2.1) (Aftermarket) (Homebrew).gb" size 1048576 crc 225ce296 sha1 45aebfe4e7f5382a4b31c3db270b75d2c458d1b2 ) + name "Ciao Nonna (World) (v2.1) (Aftermarket) (Unl)" + description "Ciao Nonna (World) (v2.1) (Aftermarket) (Unl)" + rom ( name "Ciao Nonna (World) (v2.1) (Aftermarket) (Unl).gb" size 1048576 crc 225ce296 sha1 45aebfe4e7f5382a4b31c3db270b75d2c458d1b2 ) ) game ( - name "Ciao Nonna (World) (v1.0) (Aftermarket) (Homebrew)" - description "Ciao Nonna (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Ciao Nonna (World) (v1.0) (Aftermarket) (Homebrew).gb" size 1048576 crc dd7576d6 sha1 0556eb6160fdca6e7959bba1a619b8e34d974375 ) + name "Ciao Nonna (World) (v1.0) (Aftermarket) (Unl)" + description "Ciao Nonna (World) (v1.0) (Aftermarket) (Unl)" + rom ( name "Ciao Nonna (World) (v1.0) (Aftermarket) (Unl).gb" size 1048576 crc dd7576d6 sha1 0556eb6160fdca6e7959bba1a619b8e34d974375 ) ) game ( @@ -22697,9 +23203,9 @@ game ( ) game ( - name "Clockmaker's Tale, A (World) (Aftermarket) (Homebrew)" - description "Clockmaker's Tale, A (World) (Aftermarket) (Homebrew)" - rom ( name "Clockmaker's Tale, A (World) (Aftermarket) (Homebrew).gb" size 1048576 crc c4b00adb sha1 07e4254c7f74b9d4caa6a1231558b2430dff163a ) + name "Clockmaker's Tale, A (World) (Aftermarket) (Unl)" + description "Clockmaker's Tale, A (World) (Aftermarket) (Unl)" + rom ( name "Clockmaker's Tale, A (World) (Aftermarket) (Unl).gb" size 1048576 crc c4b00adb sha1 07e4254c7f74b9d4caa6a1231558b2430dff163a ) ) game ( @@ -22715,9 +23221,9 @@ game ( ) game ( - name "Commando (World) (Aftermarket) (Homebrew)" - description "Commando (World) (Aftermarket) (Homebrew)" - rom ( name "Commando (World) (Aftermarket) (Homebrew).gb" size 262144 crc 48173941 sha1 c861858e9f2cf7470e739c26ae9f17d3834ce464 ) + name "Commando (World) (Aftermarket) (Unl)" + description "Commando (World) (Aftermarket) (Unl)" + rom ( name "Commando (World) (Aftermarket) (Unl).gb" size 262144 crc 48173941 sha1 c861858e9f2cf7470e739c26ae9f17d3834ce464 ) ) game ( @@ -22745,9 +23251,9 @@ game ( ) game ( - name "Cookie's Bakery (World) (v1.0.3) (Aftermarket) (Homebrew)" - description "Cookie's Bakery (World) (v1.0.3) (Aftermarket) (Homebrew)" - rom ( name "Cookie's Bakery (World) (v1.0.3) (Aftermarket) (Homebrew).gb" size 524288 crc c53ace00 sha1 02fdc52bc934b80aa41520d92cf0340753d8f668 ) + name "Cookie's Bakery (World) (v1.0.3) (Aftermarket) (Unl)" + description "Cookie's Bakery (World) (v1.0.3) (Aftermarket) (Unl)" + rom ( name "Cookie's Bakery (World) (v1.0.3) (Aftermarket) (Unl).gb" size 524288 crc c53ace00 sha1 02fdc52bc934b80aa41520d92cf0340753d8f668 ) ) game ( @@ -22787,15 +23293,15 @@ game ( ) game ( - name "Coria and the Sunken City (Unknown) (Demo)" - description "Coria and the Sunken City (Unknown) (Demo)" - rom ( name "Coria and the Sunken City (Unknown) (Demo).gb" size 131072 crc fe1bdae6 sha1 00d86bbdbc228ff3ea0684984a261cadbed5fb0a ) + name "Coria and the Sunken City (World) (Demo) (Aftermarket) (Unl)" + description "Coria and the Sunken City (World) (Demo) (Aftermarket) (Unl)" + rom ( name "Coria and the Sunken City (World) (Demo) (Aftermarket) (Unl).gb" size 131072 crc fe1bdae6 sha1 00d86bbdbc228ff3ea0684984a261cadbed5fb0a ) ) game ( - name "Cosmic Courier - Trapped in Limbo (World) (Aftermarket) (Homebrew)" - description "Cosmic Courier - Trapped in Limbo (World) (Aftermarket) (Homebrew)" - rom ( name "Cosmic Courier - Trapped in Limbo (World) (Aftermarket) (Homebrew).gb" size 262144 crc fd304687 sha1 7df11ec2946099d49dd62928118b6d76703dc57c ) + name "Cosmic Courier - Trapped in Limbo (World) (Aftermarket) (Unl)" + description "Cosmic Courier - Trapped in Limbo (World) (Aftermarket) (Unl)" + rom ( name "Cosmic Courier - Trapped in Limbo (World) (Aftermarket) (Unl).gb" size 262144 crc fd304687 sha1 7df11ec2946099d49dd62928118b6d76703dc57c ) ) game ( @@ -22823,9 +23329,15 @@ game ( ) game ( - name "coucou (World) (Aftermarket) (Homebrew)" - description "coucou (World) (Aftermarket) (Homebrew)" - rom ( name "coucou (World) (Aftermarket) (Homebrew).gb" size 32768 crc e6aabd72 sha1 5283268e3640e2924d00aea3b12b2d3930bef43c ) + name "Coucou (World) (Aftermarket) (Unl)" + description "Coucou (World) (Aftermarket) (Unl)" + rom ( name "Coucou (World) (Aftermarket) (Unl).gb" size 32768 crc e6aabd72 sha1 5283268e3640e2924d00aea3b12b2d3930bef43c ) +) + +game ( + name "Counting Sheep (World) (Aftermarket) (Unl)" + description "Counting Sheep (World) (Aftermarket) (Unl)" + rom ( name "Counting Sheep (World) (Aftermarket) (Unl).GB" size 65536 crc 6e97c837 sha1 e7e251ad86fa00803837cf871de62d3f100c83ce ) ) game ( @@ -22877,9 +23389,9 @@ game ( ) game ( - name "Cuthbert in the Cooler (World) (Aftermarket) (Homebrew)" - description "Cuthbert in the Cooler (World) (Aftermarket) (Homebrew)" - rom ( name "Cuthbert in the Cooler (World) (Aftermarket) (Homebrew).gb" size 262144 crc 10838580 sha1 c740bc995adf2bb638bb125a36edc416558fd4c6 ) + name "Cuthbert in the Cooler (World) (Aftermarket) (Unl)" + description "Cuthbert in the Cooler (World) (Aftermarket) (Unl)" + rom ( name "Cuthbert in the Cooler (World) (Aftermarket) (Unl).gb" size 262144 crc 10838580 sha1 c740bc995adf2bb638bb125a36edc416558fd4c6 ) ) game ( @@ -22949,21 +23461,21 @@ game ( ) game ( - name "Dangan (World) (v1.1) (Aftermarket) (Homebrew)" - description "Dangan (World) (v1.1) (Aftermarket) (Homebrew)" - rom ( name "Dangan (World) (v1.1) (Aftermarket) (Homebrew).gb" size 262144 crc 5359d6db sha1 10029774046dabec2d8c0533caf94091f6e19071 ) + name "Dangan (World) (v1.1) (Aftermarket) (Unl)" + description "Dangan (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Dangan (World) (v1.1) (Aftermarket) (Unl).gb" size 262144 crc 5359d6db sha1 10029774046dabec2d8c0533caf94091f6e19071 ) ) game ( - name "Dangan (World) (v1.0) (Aftermarket) (Homebrew)" - description "Dangan (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Dangan (World) (v1.0) (Aftermarket) (Homebrew).gb" size 262144 crc daa59c9c sha1 8375d845fcfe4c236cd68f3d56a290f8c7b76c06 ) + name "Dangan (World) (v1.0) (Aftermarket) (Unl)" + description "Dangan (World) (v1.0) (Aftermarket) (Unl)" + rom ( name "Dangan (World) (v1.0) (Aftermarket) (Unl).gb" size 262144 crc daa59c9c sha1 8375d845fcfe4c236cd68f3d56a290f8c7b76c06 ) ) game ( - name "Dark Winter Wander, A (World) (Aftermarket) (Homebrew)" - description "Dark Winter Wander, A (World) (Aftermarket) (Homebrew)" - rom ( name "Dark Winter Wander, A (World) (Aftermarket) (Homebrew).gb" size 1048576 crc df19aa9f sha1 412f18ba08f2f2a2afbf3bfb5281e360d7aba31d ) + name "Dark Winter Wander, A (World) (Aftermarket) (Unl)" + description "Dark Winter Wander, A (World) (Aftermarket) (Unl)" + rom ( name "Dark Winter Wander, A (World) (Aftermarket) (Unl).gb" size 1048576 crc df19aa9f sha1 412f18ba08f2f2a2afbf3bfb5281e360d7aba31d ) ) game ( @@ -22975,7 +23487,7 @@ game ( game ( name "Darkwing Duck (Europe)" description "Darkwing Duck (Europe)" - rom ( name "Darkwing Duck (Europe).gb" size 131072 crc be975b4f sha1 4e51919597aa72dd32182f9afee34f148036655f ) + rom ( name "Darkwing Duck (Europe).gb" size 131072 crc be975b4f sha1 4e51919597aa72dd32182f9afee34f148036655f flags verified ) ) game ( @@ -22984,6 +23496,12 @@ game ( rom ( name "Darkwing Duck (USA).gb" size 131072 crc 238b9646 sha1 cc1f12f3ec3852657a14d11c13d1ef91fbdda5bb ) ) +game ( + name "Darkwing Duck (USA) (Beta)" + description "Darkwing Duck (USA) (Beta)" + rom ( name "Darkwing Duck (USA) (Beta).gb" size 131072 crc 311ade03 sha1 2883b8854529369cce4d473e71fa0193c7df02b6 ) +) + game ( name "Darkwing Duck (Germany)" description "Darkwing Duck (Germany)" @@ -22997,9 +23515,9 @@ game ( ) game ( - name "Dash (World) (Aftermarket) (Homebrew)" - description "Dash (World) (Aftermarket) (Homebrew)" - rom ( name "Dash (World) (Aftermarket) (Homebrew).gb" size 262144 crc 73868683 sha1 c1ffe7c25a34d65ed166293bc7d2b48b65ca922b ) + name "Dash (World) (Aftermarket) (Unl)" + description "Dash (World) (Aftermarket) (Unl)" + rom ( name "Dash (World) (Aftermarket) (Unl).gb" size 262144 crc 73868683 sha1 c1ffe7c25a34d65ed166293bc7d2b48b65ca922b ) ) game ( @@ -23015,9 +23533,9 @@ game ( ) game ( - name "Dawn Will Come (World) (Aftermarket) (Homebrew)" - description "Dawn Will Come (World) (Aftermarket) (Homebrew)" - rom ( name "Dawn Will Come (World) (Aftermarket) (Homebrew).gb" size 2097152 crc bb64f51c sha1 1611ddfdb9af72f20ddeca4133d8eebd0f14e424 ) + name "Dawn Will Come (World) (Aftermarket) (Unl)" + description "Dawn Will Come (World) (Aftermarket) (Unl)" + rom ( name "Dawn Will Come (World) (Aftermarket) (Unl).gb" size 2097152 crc bb64f51c sha1 1611ddfdb9af72f20ddeca4133d8eebd0f14e424 ) ) game ( @@ -23039,15 +23557,15 @@ game ( ) game ( - name "Deadeus (World) (1.3.8) (Aftermarket) (Unl)" - description "Deadeus (World) (1.3.8) (Aftermarket) (Unl)" - rom ( name "Deadeus (World) (1.3.8) (Aftermarket) (Unl).gb" size 1048576 crc 7da95971 sha1 23cff594ef4b0bb21883b422940526c7fe81f1fd ) + name "Deadeus (World) (v1.3.8) (Aftermarket) (Unl)" + description "Deadeus (World) (v1.3.8) (Aftermarket) (Unl)" + rom ( name "Deadeus (World) (v1.3.8) (Aftermarket) (Unl).gb" size 1048576 crc 7da95971 sha1 23cff594ef4b0bb21883b422940526c7fe81f1fd flags verified ) ) game ( - name "Deadeus (World) (1.2.5) (Aftermarket) (Unl)" - description "Deadeus (World) (1.2.5) (Aftermarket) (Unl)" - rom ( name "Deadeus (World) (1.2.5) (Aftermarket) (Unl).gb" size 1048576 crc 9e2bf649 sha1 3feeba5c438880f70cdfdc4ea7e29f77e645e9be ) + name "Deadeus (World) (v1.2.5) (Aftermarket) (Unl)" + description "Deadeus (World) (v1.2.5) (Aftermarket) (Unl)" + rom ( name "Deadeus (World) (v1.2.5) (Aftermarket) (Unl).gb" size 1048576 crc 9e2bf649 sha1 3feeba5c438880f70cdfdc4ea7e29f77e645e9be flags verified ) ) game ( @@ -23057,9 +23575,9 @@ game ( ) game ( - name "Deep Forest (World) (v1.1) (Aftermarket) (Homebrew)" - description "Deep Forest (World) (v1.1) (Aftermarket) (Homebrew)" - rom ( name "Deep Forest (World) (v1.1) (Aftermarket) (Homebrew).gb" size 1048576 crc 3ddf177d sha1 517302d20cb5140975e6cd1b3c495786b6390aaf ) + name "Deep Forest (World) (v1.1) (Aftermarket) (Unl)" + description "Deep Forest (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Deep Forest (World) (v1.1) (Aftermarket) (Unl).gb" size 1048576 crc 3ddf177d sha1 517302d20cb5140975e6cd1b3c495786b6390aaf ) ) game ( @@ -23105,9 +23623,9 @@ game ( ) game ( - name "DiaMaze (World) (Aftermarket) (Homebrew)" - description "DiaMaze (World) (Aftermarket) (Homebrew)" - rom ( name "DiaMaze (World) (Aftermarket) (Homebrew).gb" size 262144 crc 956fa901 sha1 f41b98ac4669920ffede1736ddbd9e1f62d4cb0d ) + name "DiaMaze (World) (Aftermarket) (Unl)" + description "DiaMaze (World) (Aftermarket) (Unl)" + rom ( name "DiaMaze (World) (Aftermarket) (Unl).gb" size 262144 crc 956fa901 sha1 f41b98ac4669920ffede1736ddbd9e1f62d4cb0d ) ) game ( @@ -23147,9 +23665,9 @@ game ( ) game ( - name "Dino's Offline Adventure (World) (Aftermarket) (Homebrew)" - description "Dino's Offline Adventure (World) (Aftermarket) (Homebrew)" - rom ( name "Dino's Offline Adventure (World) (Aftermarket) (Homebrew).gb" size 32768 crc d6bd0e6a sha1 6d11c145606f8e7ab25b2b07c299e36c8b442d23 ) + name "Dino's Offline Adventure (World) (Aftermarket) (Unl)" + description "Dino's Offline Adventure (World) (Aftermarket) (Unl)" + rom ( name "Dino's Offline Adventure (World) (Aftermarket) (Unl).gb" size 32768 crc d6bd0e6a sha1 6d11c145606f8e7ab25b2b07c299e36c8b442d23 ) ) game ( @@ -23165,81 +23683,69 @@ game ( ) game ( - name "Disco Elysium - Game Boy Edition (World) (Aftermarket) (Homebrew)" - description "Disco Elysium - Game Boy Edition (World) (Aftermarket) (Homebrew)" - rom ( name "Disco Elysium - Game Boy Edition (World) (Aftermarket) (Homebrew).gb" size 1048576 crc ef349515 sha1 06fe25086432c82f1e4b4c44473dd5b487a8af05 ) + name "Disco Elysium - Game Boy Edition (World) (Aftermarket) (Unl)" + description "Disco Elysium - Game Boy Edition (World) (Aftermarket) (Unl)" + rom ( name "Disco Elysium - Game Boy Edition (World) (Aftermarket) (Unl).gb" size 1048576 crc ef349515 sha1 06fe25086432c82f1e4b4c44473dd5b487a8af05 ) ) game ( - name "DMG Deals Damage (World) (Aftermarket) (Homebrew)" - description "DMG Deals Damage (World) (Aftermarket) (Homebrew)" - rom ( name "DMG Deals Damage (World) (Aftermarket) (Homebrew).gb" size 32768 crc 250e0cbd sha1 a15539199e4b6bd2a71d1ac0e7c61ae4f19a65e7 ) + name "DMG Deals Damage (World) (Aftermarket) (Unl)" + description "DMG Deals Damage (World) (Aftermarket) (Unl)" + rom ( name "DMG Deals Damage (World) (Aftermarket) (Unl).gb" size 32768 crc 250e0cbd sha1 a15539199e4b6bd2a71d1ac0e7c61ae4f19a65e7 ) ) game ( - name "DMG Express (World) (v3.0.1) (Aftermarket) (Homebrew)" - description "DMG Express (World) (v3.0.1) (Aftermarket) (Homebrew)" - rom ( name "DMG Express (World) (v3.0.1) (Aftermarket) (Homebrew).gb" size 262144 crc 56dbbbd4 sha1 07babaa6980fdd6c6470291511280008bfdec26c ) + name "Do I Pass (World) (Demo) (Aftermarket) (Unl)" + description "Do I Pass (World) (Demo) (Aftermarket) (Unl)" + rom ( name "Do I Pass (World) (Demo) (Aftermarket) (Unl).gb" size 1048576 crc f339fa82 sha1 bdbc082017bdf2e4caa84e7d00dad9707727e9d4 ) ) game ( - name "DMG Express (World) (v2.0.1) (Aftermarket) (Homebrew)" - description "DMG Express (World) (v2.0.1) (Aftermarket) (Homebrew)" - rom ( name "DMG Express (World) (v2.0.1) (Aftermarket) (Homebrew).gbc" size 262144 crc 4c2364a4 sha1 3077afd1920ea041dd2631834276a6234076b485 flags verified ) + name "Do I Pass (World) (v1.4) (Aftermarket) (Unl)" + description "Do I Pass (World) (v1.4) (Aftermarket) (Unl)" + rom ( name "Do I Pass (World) (v1.4) (Aftermarket) (Unl).gb" size 1048576 crc cfb44d68 sha1 6dde71bbec8b807af5132d1ef2e99c6bb6af3a1c ) ) game ( - name "Do I Pass (World) (Demo) (Aftermarket) (Homebrew)" - description "Do I Pass (World) (Demo) (Aftermarket) (Homebrew)" - rom ( name "Do I Pass (World) (Demo) (Aftermarket) (Homebrew).gb" size 1048576 crc f339fa82 sha1 bdbc082017bdf2e4caa84e7d00dad9707727e9d4 ) + name "Do I Pass (World) (v1.4) (Web) (Aftermarket) (Unl)" + description "Do I Pass (World) (v1.4) (Web) (Aftermarket) (Unl)" + rom ( name "Do I Pass (World) (v1.4) (Web) (Aftermarket) (Unl).gb" size 1048576 crc 62e0c3a2 sha1 e1b516e472e0c681b4ad1c7f4aeda55c91e7f340 ) ) game ( - name "Do I Pass (World) (v1.4) (Aftermarket) (Homebrew)" - description "Do I Pass (World) (v1.4) (Aftermarket) (Homebrew)" - rom ( name "Do I Pass (World) (v1.4) (Aftermarket) (Homebrew).gb" size 1048576 crc cfb44d68 sha1 6dde71bbec8b807af5132d1ef2e99c6bb6af3a1c ) + name "Do I Pass (World) (Fr) (v1.4.2) (Aftermarket) (Unl)" + description "Do I Pass (World) (Fr) (v1.4.2) (Aftermarket) (Unl)" + rom ( name "Do I Pass (World) (Fr) (v1.4.2) (Aftermarket) (Unl).gb" size 1048576 crc e393d4aa sha1 d45dfcab4a76b62ea1e10730d01f755e12137484 ) ) game ( - name "Do I Pass (World) (v1.4) (Web) (Aftermarket) (Homebrew)" - description "Do I Pass (World) (v1.4) (Web) (Aftermarket) (Homebrew)" - rom ( name "Do I Pass (World) (v1.4) (Web) (Aftermarket) (Homebrew).gb" size 1048576 crc 62e0c3a2 sha1 e1b516e472e0c681b4ad1c7f4aeda55c91e7f340 ) + name "Do I Pass (World) (v1.5) (Aftermarket) (Unl)" + description "Do I Pass (World) (v1.5) (Aftermarket) (Unl)" + rom ( name "Do I Pass (World) (v1.5) (Aftermarket) (Unl).gb" size 1048576 crc 3890e0a4 sha1 6998e1bbf2ccb9db3d661ddac2d7edf7571d0af7 ) ) game ( - name "Do I Pass (World) (Fr) (v1.4.2) (Aftermarket) (Homebrew)" - description "Do I Pass (World) (Fr) (v1.4.2) (Aftermarket) (Homebrew)" - rom ( name "Do I Pass (World) (Fr) (v1.4.2) (Aftermarket) (Homebrew).gb" size 1048576 crc e393d4aa sha1 d45dfcab4a76b62ea1e10730d01f755e12137484 ) + name "Doctor GB Card 16M Loader (World) (Unl)" + description "Doctor GB Card 16M Loader (World) (Unl)" + rom ( name "Doctor GB Card 16M Loader (World) (Unl).gb" size 32768 crc 1bddf36f sha1 c298eacc0c73e648e81fbf5192aec3b6a438e053 ) ) game ( - name "Do I Pass (World) (v1.5) (Aftermarket) (Homebrew)" - description "Do I Pass (World) (v1.5) (Aftermarket) (Homebrew)" - rom ( name "Do I Pass (World) (v1.5) (Aftermarket) (Homebrew).gb" size 1048576 crc 3890e0a4 sha1 6998e1bbf2ccb9db3d661ddac2d7edf7571d0af7 ) + name "Doctor GB Card 4M (World) (Unl)" + description "Doctor GB Card 4M (World) (Unl)" + rom ( name "Doctor GB Card 4M (World) (Unl).gb" size 524288 crc 5d2e8cff sha1 43962a7459c8ccc0e5988060abd0f8668f30dfde ) ) game ( - name "Doctor GB Card 16M Loader (World) (Homebrew)" - description "Doctor GB Card 16M Loader (World) (Homebrew)" - rom ( name "Doctor GB Card 16M Loader (World) (Homebrew).gb" size 32768 crc 1bddf36f sha1 c298eacc0c73e648e81fbf5192aec3b6a438e053 ) + name "Doctor GB Card Demo (World) (Demo) (Unl)" + description "Doctor GB Card Demo (World) (Demo) (Unl)" + rom ( name "Doctor GB Card Demo (World) (Demo) (Unl).gb" size 32768 crc b5dfdc26 sha1 1d403b254971faa5834bc48adfd42cc0bbed3fe4 ) ) game ( - name "Doctor GB Card 4M (World) (Homebrew)" - description "Doctor GB Card 4M (World) (Homebrew)" - rom ( name "Doctor GB Card 4M (World) (Homebrew).gb" size 524288 crc 5d2e8cff sha1 43962a7459c8ccc0e5988060abd0f8668f30dfde ) -) - -game ( - name "Doctor GB Card Demo (World) (Demo) (Homebrew) (Alt)" - description "Doctor GB Card Demo (World) (Demo) (Homebrew) (Alt)" - rom ( name "Doctor GB Card Demo (World) (Demo) (Homebrew) (Alt).gb" size 32768 crc 3592390b sha1 a6e3b81b8664f41eb18770cd5757ed4d8338be54 ) -) - -game ( - name "Doctor GB Card Demo (World) (Demo) (Homebrew)" - description "Doctor GB Card Demo (World) (Demo) (Homebrew)" - rom ( name "Doctor GB Card Demo (World) (Demo) (Homebrew).gb" size 32768 crc b5dfdc26 sha1 1d403b254971faa5834bc48adfd42cc0bbed3fe4 ) + name "Doctor GB Card Demo (World) (Demo) (Unl) (Alt)" + description "Doctor GB Card Demo (World) (Demo) (Unl) (Alt)" + rom ( name "Doctor GB Card Demo (World) (Demo) (Unl) (Alt).gb" size 32768 crc 3592390b sha1 a6e3b81b8664f41eb18770cd5757ed4d8338be54 ) ) game ( @@ -23249,15 +23755,27 @@ game ( ) game ( - name "Dog's Muck Island (World) (Aftermarket) (Homebrew)" - description "Dog's Muck Island (World) (Aftermarket) (Homebrew)" - rom ( name "Dog's Muck Island (World) (Aftermarket) (Homebrew).gb" size 262144 crc 79a7a06c sha1 eb4cea3b9db770bf3b586578af1ad7427d88ee8e ) + name "Dog's Muck Island (World) (Aftermarket) (Unl)" + description "Dog's Muck Island (World) (Aftermarket) (Unl)" + rom ( name "Dog's Muck Island (World) (Aftermarket) (Unl).gb" size 262144 crc 79a7a06c sha1 eb4cea3b9db770bf3b586578af1ad7427d88ee8e ) ) game ( - name "Don't Forget About Me (World) (Prototype) (Aftermarket) (Homebrew)" - description "Don't Forget About Me (World) (Prototype) (Aftermarket) (Homebrew)" - rom ( name "Don't Forget About Me (World) (Prototype) (Aftermarket) (Homebrew).gb" size 524288 crc 52cf7153 sha1 74e6246405dd52d4c5c2eabc417946550c637164 ) + name "Don't Forget About Me (World) (v1.0) (Aftermarket) (Unl)" + description "Don't Forget About Me (World) (v1.0) (Aftermarket) (Unl)" + rom ( name "Don't Forget About Me (World) (v1.0) (Aftermarket) (Unl).gb" size 524288 crc 52cf7153 sha1 74e6246405dd52d4c5c2eabc417946550c637164 ) +) + +game ( + name "Don't Forget About Me (World) (v1.2) (Aftermarket) (Unl)" + description "Don't Forget About Me (World) (v1.2) (Aftermarket) (Unl)" + rom ( name "Don't Forget About Me (World) (v1.2) (Aftermarket) (Unl).gb" size 1048576 crc 3d87efd8 sha1 83201c43e9fc89f4393678ade8c6107b4cfdb628 ) +) + +game ( + name "Don't Forget About Me (World) (v1.1) (Aftermarket) (Unl)" + description "Don't Forget About Me (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Don't Forget About Me (World) (v1.1) (Aftermarket) (Unl).gb" size 1048576 crc 8ca9dbe0 sha1 cf6eaac37c4ae1b3cfc4ed8d2ce73a143635b525 ) ) game ( @@ -23665,7 +24183,7 @@ game ( game ( name "DX Bakenou Z (Japan) (Rev 1)" description "DX Bakenou Z (Japan) (Rev 1)" - rom ( name "DX Bakenou Z (Japan) (Rev 1).gb" size 131072 crc fadfd0f6 sha1 6e2b9b21ea7d8d482be1f17722a579f720f2029d ) + rom ( name "DX Bakenou Z (Japan) (Rev 1).gb" size 131072 crc fadfd0f6 sha1 6e2b9b21ea7d8d482be1f17722a579f720f2029d flags verified ) ) game ( @@ -23677,7 +24195,7 @@ game ( game ( name "Earthworm Jim (Europe)" description "Earthworm Jim (Europe)" - rom ( name "Earthworm Jim (Europe).gb" size 262144 crc b1a7a008 sha1 4249abe39285b02ccb0e739b5a2cf96ace1281ff ) + rom ( name "Earthworm Jim (Europe).gb" size 262144 crc b1a7a008 sha1 4249abe39285b02ccb0e739b5a2cf96ace1281ff flags verified ) ) game ( @@ -23716,6 +24234,12 @@ game ( rom ( name "Elite Soccer (USA) (SGB Enhanced).gb" size 131072 crc f54158a6 sha1 4576881ea25e29ebba91c49c89f8e70e105fbc60 ) ) +game ( + name "Empire of Dreams, The (World) (Aftermarket) (Unl)" + description "Empire of Dreams, The (World) (Aftermarket) (Unl)" + rom ( name "Empire of Dreams, The (World) (Aftermarket) (Unl).gb" size 524288 crc d87e5e68 sha1 92b2da6bd10337950ddcb8bb372cabccdce4f6bd ) +) + game ( name "Exodus - Journey to the Promised Land (USA) (Unl)" description "Exodus - Journey to the Promised Land (USA) (Unl)" @@ -23759,9 +24283,9 @@ game ( ) game ( - name "F-Zero - Project (World) (Aftermarket) (Homebrew)" - description "F-Zero - Project (World) (Aftermarket) (Homebrew)" - rom ( name "F-Zero - Project (World) (Aftermarket) (Homebrew).gb" size 1048576 crc 4c707059 sha1 5b823ee17691d286a45f0667cddd59ccaaab5d8b ) + name "F-Zero - Project (World) (Aftermarket) (Unl)" + description "F-Zero - Project (World) (Aftermarket) (Unl)" + rom ( name "F-Zero - Project (World) (Aftermarket) (Unl).gb" size 1048576 crc 4c707059 sha1 5b823ee17691d286a45f0667cddd59ccaaab5d8b ) ) game ( @@ -23785,7 +24309,7 @@ game ( game ( name "Faceball 2000 (USA)" description "Faceball 2000 (USA)" - rom ( name "Faceball 2000 (USA).gb" size 131072 crc 7d890cd0 sha1 b0bd15bace04e0a3eb89773f231ac3a532181a0a ) + rom ( name "Faceball 2000 (USA).gb" size 131072 crc 7d890cd0 sha1 b0bd15bace04e0a3eb89773f231ac3a532181a0a flags verified ) ) game ( @@ -23819,9 +24343,9 @@ game ( ) game ( - name "Farm, The (World) (Aftermarket) (Homebrew)" - description "Farm, The (World) (Aftermarket) (Homebrew)" - rom ( name "Farm, The (World) (Aftermarket) (Homebrew).gb" size 524288 crc fbc1b5e8 sha1 d0fe06920f7e771d76507f60881904370ffbc145 ) + name "Farm, The (World) (Aftermarket) (Unl)" + description "Farm, The (World) (Aftermarket) (Unl)" + rom ( name "Farm, The (World) (Aftermarket) (Unl).gb" size 524288 crc fbc1b5e8 sha1 d0fe06920f7e771d76507f60881904370ffbc145 ) ) game ( @@ -23963,9 +24487,9 @@ game ( ) game ( - name "Finders Keepers (World) (Aftermarket) (Homebrew)" - description "Finders Keepers (World) (Aftermarket) (Homebrew)" - rom ( name "Finders Keepers (World) (Aftermarket) (Homebrew).gb" size 524288 crc 6a98ac61 sha1 e40dd804388df8c08d3890d79eeafd8542cc8805 ) + name "Finders Keepers (World) (Aftermarket) (Unl)" + description "Finders Keepers (World) (Aftermarket) (Unl)" + rom ( name "Finders Keepers (World) (Aftermarket) (Unl).gb" size 524288 crc 6a98ac61 sha1 e40dd804388df8c08d3890d79eeafd8542cc8805 ) ) game ( @@ -23987,9 +24511,9 @@ game ( ) game ( - name "Fix My Heart (World) (Aftermarket) (Homebrew)" - description "Fix My Heart (World) (Aftermarket) (Homebrew)" - rom ( name "Fix My Heart (World) (Aftermarket) (Homebrew).gb" size 524288 crc 507f9ea2 sha1 28ad64e379dc60d6e1c2617e073f6361c0feb475 ) + name "Fix My Heart (World) (Aftermarket) (Unl)" + description "Fix My Heart (World) (Aftermarket) (Unl)" + rom ( name "Fix My Heart (World) (Aftermarket) (Unl).gb" size 524288 crc 507f9ea2 sha1 28ad64e379dc60d6e1c2617e073f6361c0feb475 ) ) game ( @@ -24011,9 +24535,9 @@ game ( ) game ( - name "Flashin' (World) (v3.0) (Aftermarket) (Homebrew)" - description "Flashin' (World) (v3.0) (Aftermarket) (Homebrew)" - rom ( name "Flashin' (World) (v3.0) (Aftermarket) (Homebrew).gb" size 1048576 crc fcc05ec4 sha1 64d31f34e189b3af08bf0c659a93f71dbf83ef71 ) + name "Flashin' (World) (v3.0) (Aftermarket) (Unl)" + description "Flashin' (World) (v3.0) (Aftermarket) (Unl)" + rom ( name "Flashin' (World) (v3.0) (Aftermarket) (Unl).gb" size 1048576 crc fcc05ec4 sha1 64d31f34e189b3af08bf0c659a93f71dbf83ef71 ) ) game ( @@ -24053,9 +24577,9 @@ game ( ) game ( - name "Flooder (World) (Aftermarket) (Homebrew)" - description "Flooder (World) (Aftermarket) (Homebrew)" - rom ( name "Flooder (World) (Aftermarket) (Homebrew).gb" size 32768 crc 253dcbe0 sha1 8fdcd8b02604ac5259fb6aa80e5ba67a03c861fb ) + name "Flooder (World) (Aftermarket) (Unl)" + description "Flooder (World) (Aftermarket) (Unl)" + rom ( name "Flooder (World) (Aftermarket) (Unl).gb" size 32768 crc 253dcbe0 sha1 8fdcd8b02604ac5259fb6aa80e5ba67a03c861fb ) ) game ( @@ -24077,9 +24601,15 @@ game ( ) game ( - name "Forest of Fallen Knights (World) (Aftermarket) (Homebrew)" - description "Forest of Fallen Knights (World) (Aftermarket) (Homebrew)" - rom ( name "Forest of Fallen Knights (World) (Aftermarket) (Homebrew).gb" size 524288 crc 4c21e563 sha1 f6f84f1a44d41fa7d26a9195ddf791fae53dfcdf ) + name "Forest of Fallen Knights (World) (Aftermarket) (Unl)" + description "Forest of Fallen Knights (World) (Aftermarket) (Unl)" + rom ( name "Forest of Fallen Knights (World) (Aftermarket) (Unl).gb" size 524288 crc fa2d92a1 sha1 24f02d116aafb7b55d6cfd6d263b7595986af843 ) +) + +game ( + name "Forest of Fallen Knights (World) (Beta) (Aftermarket) (Unl)" + description "Forest of Fallen Knights (World) (Beta) (Aftermarket) (Unl)" + rom ( name "Forest of Fallen Knights (World) (Beta) (Aftermarket) (Unl).gb" size 524288 crc 4c21e563 sha1 f6f84f1a44d41fa7d26a9195ddf791fae53dfcdf ) ) game ( @@ -24101,9 +24631,9 @@ game ( ) game ( - name "Friday the 13th - The GB Game (World) (Aftermarket) (Homebrew)" - description "Friday the 13th - The GB Game (World) (Aftermarket) (Homebrew)" - rom ( name "Friday the 13th - The GB Game (World) (Aftermarket) (Homebrew).gb" size 1048576 crc 7543586a sha1 397821751ac6464582f61566f3a7c731a46b24ba ) + name "Friday the 13th - The GB Game (World) (Aftermarket) (Unl)" + description "Friday the 13th - The GB Game (World) (Aftermarket) (Unl)" + rom ( name "Friday the 13th - The GB Game (World) (Aftermarket) (Unl).gb" size 1048576 crc 7543586a sha1 397821751ac6464582f61566f3a7c731a46b24ba ) ) game ( @@ -24161,15 +24691,15 @@ game ( ) game ( - name "G-Man (World) (Aftermarket) (Homebrew)" - description "G-Man (World) (Aftermarket) (Homebrew)" - rom ( name "G-Man (World) (Aftermarket) (Homebrew).gb" size 524288 crc 7296da69 sha1 fdc9933d46a063575c175453b1da8042fd28b135 ) + name "G-Man (World) (Aftermarket) (Unl)" + description "G-Man (World) (Aftermarket) (Unl)" + rom ( name "G-Man (World) (Aftermarket) (Unl).gb" size 524288 crc 7296da69 sha1 fdc9933d46a063575c175453b1da8042fd28b135 ) ) game ( - name "G-ZERO (World) (v2.6) (Aftermarket) (Homebrew)" - description "G-ZERO (World) (v2.6) (Aftermarket) (Homebrew)" - rom ( name "G-ZERO (World) (v2.6) (Aftermarket) (Homebrew).gb" size 65536 crc 7dd0c878 sha1 929d25a612308614ac3ac2ee5a19a9cd4a9968d6 ) + name "G-ZERO (World) (v2.6) (Aftermarket) (Unl)" + description "G-ZERO (World) (v2.6) (Aftermarket) (Unl)" + rom ( name "G-ZERO (World) (v2.6) (Aftermarket) (Unl).gb" size 65536 crc 7dd0c878 sha1 929d25a612308614ac3ac2ee5a19a9cd4a9968d6 ) ) game ( @@ -24209,9 +24739,9 @@ game ( ) game ( - name "Game Boy Camera Gallery 2022, The (World) (Aftermarket) (Homebrew)" - description "Game Boy Camera Gallery 2022, The (World) (Aftermarket) (Homebrew)" - rom ( name "Game Boy Camera Gallery 2022, The (World) (Aftermarket) (Homebrew).gb" size 524288 crc f1948966 sha1 14849ab5831c71949ec3a1fe7657050057d2cf29 ) + name "Game Boy Camera Gallery 2022, The (World) (Aftermarket) (Unl)" + description "Game Boy Camera Gallery 2022, The (World) (Aftermarket) (Unl)" + rom ( name "Game Boy Camera Gallery 2022, The (World) (Aftermarket) (Unl).gb" size 524288 crc f1948966 sha1 14849ab5831c71949ec3a1fe7657050057d2cf29 ) ) game ( @@ -24233,9 +24763,15 @@ game ( ) game ( - name "Game Boy Digital Sampling Oscilloscope (Europe) (v3.6) (Unl)" - description "Game Boy Digital Sampling Oscilloscope (Europe) (v3.6) (Unl)" - rom ( name "Game Boy Digital Sampling Oscilloscope (Europe) (v3.6) (Unl).gb" size 32768 crc 572ea59c sha1 68128bd8b01970054590a88b785d553456b68ec1 ) + name "Game Boy Digital Sampling Oscilloscope (Europe) (Unl)" + description "Game Boy Digital Sampling Oscilloscope (Europe) (Unl)" + rom ( name "Game Boy Digital Sampling Oscilloscope (Europe) (Unl).gb" size 32768 crc 572ea59c sha1 68128bd8b01970054590a88b785d553456b68ec1 ) +) + +game ( + name "Game Boy Digital Sampling Oscilloscope (Europe) (Demo) (Unl)" + description "Game Boy Digital Sampling Oscilloscope (Europe) (Demo) (Unl)" + rom ( name "Game Boy Digital Sampling Oscilloscope (Europe) (Demo) (Unl).gb" size 32768 crc a800444b sha1 db3e76bced7bd9844aedf23345c4307403006600 ) ) game ( @@ -24323,9 +24859,9 @@ game ( ) game ( - name "GameShark (USA) (Unl)" - description "GameShark (USA) (Unl)" - rom ( name "GameShark (USA) (Unl).gb" size 131072 crc d6909596 sha1 09a6dae5e5faae1c4b5cf20e1b913b1c5e72297e ) + name "GameShark (USA) (Unl) [b]" + description "GameShark (USA) (Unl) [b]" + rom ( name "GameShark (USA) (Unl) [b].gb" size 131072 crc d6909596 sha1 09a6dae5e5faae1c4b5cf20e1b913b1c5e72297e flags baddump ) ) game ( @@ -24400,6 +24936,12 @@ game ( rom ( name "GB Basketball (Japan).gb" size 131072 crc d9b24d21 sha1 65f73db2942d400a4c555fbcfd6313f07303d4e4 ) ) +game ( + name "GB Corp. (World) (Aftermarket) (Unl)" + description "GB Corp. (World) (Aftermarket) (Unl)" + rom ( name "GB Corp. (World) (Aftermarket) (Unl).gb" size 32768 crc fd2e40f4 sha1 e442316132506ff223a9e5f33d874b71ec09d71a ) +) + game ( name "GB Genjin (Japan)" description "GB Genjin (Japan)" @@ -24425,63 +24967,63 @@ game ( ) game ( - name "GB-Wordyl (World) (Pt-BR) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (Pt-BR) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (Pt-BR) (Aftermarket) (Homebrew).gb" size 32768 crc f20c0097 sha1 691af25434a09931a302ced52c757d28b9b06619 ) + name "GB-Wordyl (World) (Pt-BR) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (Pt-BR) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (Pt-BR) (Aftermarket) (Unl).gb" size 32768 crc f20c0097 sha1 691af25434a09931a302ced52c757d28b9b06619 ) ) game ( - name "GB-Wordyl (World) (Ca) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (Ca) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (Ca) (Aftermarket) (Homebrew).gb" size 32768 crc 164999eb sha1 197d3d194d4cb6cae9a358f7fb53d6de649e7c5f ) + name "GB-Wordyl (World) (Ca) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (Ca) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (Ca) (Aftermarket) (Unl).gb" size 32768 crc 164999eb sha1 197d3d194d4cb6cae9a358f7fb53d6de649e7c5f ) ) game ( - name "GB-Wordyl (World) (De) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (De) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (De) (Aftermarket) (Homebrew).gb" size 32768 crc 60b075d1 sha1 0a66254b2327b48657de64d6102ac05f1e8700e7 ) + name "GB-Wordyl (World) (De) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (De) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (De) (Aftermarket) (Unl).gb" size 32768 crc 60b075d1 sha1 0a66254b2327b48657de64d6102ac05f1e8700e7 ) ) game ( - name "GB-Wordyl (World) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (Aftermarket) (Homebrew).gb" size 32768 crc 8780e125 sha1 7163bfbaa2cae2f9d41c472128f69a4fa5879180 ) + name "GB-Wordyl (World) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (Aftermarket) (Unl).gb" size 32768 crc 8780e125 sha1 7163bfbaa2cae2f9d41c472128f69a4fa5879180 ) ) game ( - name "GB-Wordyl (World) (Es) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (Es) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (Es) (Aftermarket) (Homebrew).gb" size 32768 crc 1a169897 sha1 0636b968e8e24fe7c1910f61db6e94fa84824494 ) + name "GB-Wordyl (World) (Es) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (Es) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (Es) (Aftermarket) (Unl).gb" size 32768 crc 1a169897 sha1 0636b968e8e24fe7c1910f61db6e94fa84824494 ) ) game ( - name "GB-Wordyl (World) (Fr) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (Fr) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (Fr) (Aftermarket) (Homebrew).gb" size 32768 crc 593965ff sha1 75d566045a680aaed4d40b83b6418cbfab5b4422 ) + name "GB-Wordyl (World) (Fr) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (Fr) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (Fr) (Aftermarket) (Unl).gb" size 32768 crc 593965ff sha1 75d566045a680aaed4d40b83b6418cbfab5b4422 ) ) game ( - name "GB-Wordyl (World) (It) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (It) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (It) (Aftermarket) (Homebrew).gb" size 32768 crc d81ab567 sha1 1dfd3c4508ffb6598662bcf1b16ec109a9282dd5 ) + name "GB-Wordyl (World) (It) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (It) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (It) (Aftermarket) (Unl).gb" size 32768 crc d81ab567 sha1 1dfd3c4508ffb6598662bcf1b16ec109a9282dd5 ) ) game ( - name "GB-Wordyl (World) (Cornish Edition) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (Cornish Edition) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (Cornish Edition) (Aftermarket) (Homebrew).gb" size 32768 crc f795689c sha1 9a332127182e8155d1a3dc53e1382a6c9dde6aeb ) + name "GB-Wordyl (World) (Kw) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (Kw) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (Kw) (Aftermarket) (Unl).gb" size 32768 crc f795689c sha1 9a332127182e8155d1a3dc53e1382a6c9dde6aeb ) ) game ( - name "GB-Wordyl (World) (Es-XL) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (Es-XL) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (Es-XL) (Aftermarket) (Homebrew).gb" size 32768 crc 660ed37b sha1 0792fbef010cac21c12cbcb8b3c85b3af30faec4 ) + name "GB-Wordyl (World) (Es-XL) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (Es-XL) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (Es-XL) (Aftermarket) (Unl).gb" size 32768 crc 660ed37b sha1 0792fbef010cac21c12cbcb8b3c85b3af30faec4 ) ) game ( - name "GB-Wordyl (World) (Nl) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (Nl) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (Nl) (Aftermarket) (Homebrew).gb" size 32768 crc 6dc4faaa sha1 7617083f9b13b2f95bdf1b0b327ffb4f8ccbe3c9 ) + name "GB-Wordyl (World) (Nl) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (Nl) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (Nl) (Aftermarket) (Unl).gb" size 32768 crc 6dc4faaa sha1 7617083f9b13b2f95bdf1b0b327ffb4f8ccbe3c9 ) ) game ( @@ -24514,6 +25056,12 @@ game ( rom ( name "Gem Gem (Japan).gb" size 65536 crc a64a8710 sha1 90a29d7a56f64b596cda1c64c8998b63d12c321e ) ) +game ( + name "Genesis (World) (Aftermarket) (Unl)" + description "Genesis (World) (Aftermarket) (Unl)" + rom ( name "Genesis (World) (Aftermarket) (Unl).gb" size 65536 crc 74b3ec78 sha1 ca43f82d73ba0b3e43ec17f6bc6761c09ca23626 ) +) + game ( name "Genjin Collection (Japan) (SGB Enhanced)" description "Genjin Collection (Japan) (SGB Enhanced)" @@ -24551,9 +25099,9 @@ game ( ) game ( - name "Ghost Town (World) (Aftermarket) (Homebrew)" - description "Ghost Town (World) (Aftermarket) (Homebrew)" - rom ( name "Ghost Town (World) (Aftermarket) (Homebrew).gb" size 262144 crc 2d27cdf2 sha1 af526273cdaa6423b92d0484fb27af56fe355a5d ) + name "Ghost Town (World) (Aftermarket) (Unl)" + description "Ghost Town (World) (Aftermarket) (Unl)" + rom ( name "Ghost Town (World) (Aftermarket) (Unl).gb" size 262144 crc 2d27cdf2 sha1 af526273cdaa6423b92d0484fb27af56fe355a5d ) ) game ( @@ -24671,9 +25219,9 @@ game ( ) game ( - name "Gorf the Ghost Saves Halloween (World) (Aftermarket) (Homebrew)" - description "Gorf the Ghost Saves Halloween (World) (Aftermarket) (Homebrew)" - rom ( name "Gorf the Ghost Saves Halloween (World) (Aftermarket) (Homebrew).gb" size 262144 crc ab10cec6 sha1 9dcaa6824fab806683737bdf1c76609c68c451a5 ) + name "Gorf the Ghost Saves Halloween (World) (Aftermarket) (Unl)" + description "Gorf the Ghost Saves Halloween (World) (Aftermarket) (Unl)" + rom ( name "Gorf the Ghost Saves Halloween (World) (Aftermarket) (Unl).gb" size 262144 crc ab10cec6 sha1 9dcaa6824fab806683737bdf1c76609c68c451a5 ) ) game ( @@ -24791,15 +25339,15 @@ game ( ) game ( - name "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eijukugo 350 (Japan) (IE Institute)" - description "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eijukugo 350 (Japan) (IE Institute)" - rom ( name "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eijukugo 350 (Japan) (IE Institute).gb" size 262144 crc 5855b281 sha1 25f69cc4da819f7d5c449f6ad0cf4963011dc5a7 ) + name "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eijukugo 350 (Japan) (Possible Proto) (IE Institute)" + description "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eijukugo 350 (Japan) (Possible Proto) (IE Institute)" + rom ( name "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eijukugo 350 (Japan) (Possible Proto) (IE Institute).gb" size 262144 crc 5855b281 sha1 25f69cc4da819f7d5c449f6ad0cf4963011dc5a7 ) ) game ( - name "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eitango 1700 (Japan) (IE Institute)" - description "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eitango 1700 (Japan) (IE Institute)" - rom ( name "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eitango 1700 (Japan) (IE Institute).gb" size 262144 crc bc14fce6 sha1 48775723113ac0e3d1cbcf7381ba876d6dc30a7c ) + name "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eitango 1700 (Japan) (Possible Proto) (IE Institute)" + description "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eitango 1700 (Japan) (Possible Proto) (IE Institute)" + rom ( name "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eitango 1700 (Japan) (Possible Proto) (IE Institute).gb" size 262144 crc bc14fce6 sha1 48775723113ac0e3d1cbcf7381ba876d6dc30a7c ) ) game ( @@ -24959,21 +25507,21 @@ game ( ) game ( - name "Gun Law (World) (Aftermarket) (Homebrew)" - description "Gun Law (World) (Aftermarket) (Homebrew)" - rom ( name "Gun Law (World) (Aftermarket) (Homebrew).gb" size 262144 crc b0d53211 sha1 ef6e3d287e99bd61861a333165c92b306238a45f ) + name "Gun Law (World) (Aftermarket) (Unl)" + description "Gun Law (World) (Aftermarket) (Unl)" + rom ( name "Gun Law (World) (Aftermarket) (Unl).gb" size 262144 crc b0d53211 sha1 ef6e3d287e99bd61861a333165c92b306238a45f ) ) game ( - name "Gunman Clive (World) (Aftermarket) (Homebrew)" - description "Gunman Clive (World) (Aftermarket) (Homebrew)" - rom ( name "Gunman Clive (World) (Aftermarket) (Homebrew).gb" size 65536 crc 11f5fded sha1 ec03763db2c0d754e2eb7e98384ed92fc8aeeb1d ) + name "Gunman Clive (World) (Aftermarket) (Unl)" + description "Gunman Clive (World) (Aftermarket) (Unl)" + rom ( name "Gunman Clive (World) (Aftermarket) (Unl).gb" size 65536 crc 11f5fded sha1 ec03763db2c0d754e2eb7e98384ed92fc8aeeb1d ) ) game ( - name "Gunship (USA) (Aftermarket) (Homebrew)" - description "Gunship (USA) (Aftermarket) (Homebrew)" - rom ( name "Gunship (USA) (Aftermarket) (Homebrew).gb" size 131072 crc bd31eef8 sha1 a801977c3746799cfb4d8bdfd679e45cccd3b719 ) + name "Gunship (USA) (Aftermarket) (Unl)" + description "Gunship (USA) (Aftermarket) (Unl)" + rom ( name "Gunship (USA) (Aftermarket) (Unl).gb" size 131072 crc bd31eef8 sha1 a801977c3746799cfb4d8bdfd679e45cccd3b719 ) ) game ( @@ -25000,6 +25548,12 @@ game ( rom ( name "Hammerin' Harry - Ghost Building Company (USA) (Proto).gb" size 262144 crc 6c4d0377 sha1 c5f73b09f001fc4d7eaa40ffee00234ca6a41a41 ) ) +game ( + name "Harbour Attack (World) (Aftermarket) (Unl)" + description "Harbour Attack (World) (Aftermarket) (Unl)" + rom ( name "Harbour Attack (World) (Aftermarket) (Unl).gb" size 262144 crc 4018ebf7 sha1 5bbbe727ebc6a489f1a498d067e4f05d0d69b60f ) +) + game ( name "Harvest Moon GB (USA) (SGB Enhanced)" description "Harvest Moon GB (USA) (SGB Enhanced)" @@ -25013,9 +25567,9 @@ game ( ) game ( - name "Hauntsfield (World) (Aftermarket) (Homebrew)" - description "Hauntsfield (World) (Aftermarket) (Homebrew)" - rom ( name "Hauntsfield (World) (Aftermarket) (Homebrew).gb" size 1048576 crc 287afc75 sha1 42ccc76883c041bc5c6c25a2e61ccc939c14811f ) + name "Hauntsfield (World) (Aftermarket) (Unl)" + description "Hauntsfield (World) (Aftermarket) (Unl)" + rom ( name "Hauntsfield (World) (Aftermarket) (Unl).gb" size 1048576 crc 287afc75 sha1 42ccc76883c041bc5c6c25a2e61ccc939c14811f ) ) game ( @@ -25031,9 +25585,9 @@ game ( ) game ( - name "Heart Knight (World) (v1.0) (Aftermarket) (Homebrew)" - description "Heart Knight (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Heart Knight (World) (v1.0) (Aftermarket) (Homebrew).gb" size 32768 crc c7ecac73 sha1 dedcab41f58c58834e3534e877c494669a0d8952 ) + name "Heart Knight (World) (Aftermarket) (Unl)" + description "Heart Knight (World) (Aftermarket) (Unl)" + rom ( name "Heart Knight (World) (Aftermarket) (Unl).gb" size 32768 crc c7ecac73 sha1 dedcab41f58c58834e3534e877c494669a0d8952 ) ) game ( @@ -25205,9 +25759,9 @@ game ( ) game ( - name "Hook (Europe) (Beta)" - description "Hook (Europe) (Beta)" - rom ( name "Hook (Europe) (Beta).gb" size 131072 crc c091abc8 sha1 0299d84e5f722e9298c1ce190b8e4a785849b868 ) + name "Hook (USA) (Sample)" + description "Hook (USA) (Sample)" + rom ( name "Hook (USA) (Sample).gb" size 131072 crc c091abc8 sha1 0299d84e5f722e9298c1ce190b8e4a785849b868 ) ) game ( @@ -25252,6 +25806,12 @@ game ( rom ( name "Hugo (Europe) (SGB Enhanced).gb" size 131072 crc 74aa5e0f sha1 d314dffcf5fb441d5d6d6419e01dc825e90cf0fc flags verified ) ) +game ( + name "Hugo (World) (Aftermarket) (Unl)" + description "Hugo (World) (Aftermarket) (Unl)" + rom ( name "Hugo (World) (Aftermarket) (Unl).gb" size 262144 crc 8cc87f60 sha1 53fac2774b29a32501788e1eac65db23bf5c7b8f ) +) + game ( name "Hugo 2 (Germany)" description "Hugo 2 (Germany)" @@ -25313,9 +25873,9 @@ game ( ) game ( - name "If (World) (Aftermarket) (Homebrew)" - description "If (World) (Aftermarket) (Homebrew)" - rom ( name "If (World) (Aftermarket) (Homebrew).gb" size 1048576 crc be7e4454 sha1 c11d8dc9ce96133f679678b07822a82f985e16f9 ) + name "If (World) (Aftermarket) (Unl)" + description "If (World) (Aftermarket) (Unl)" + rom ( name "If (World) (Aftermarket) (Unl).gb" size 1048576 crc be7e4454 sha1 c11d8dc9ce96133f679678b07822a82f985e16f9 ) ) game ( @@ -25330,10 +25890,16 @@ game ( rom ( name "Ikari no Yousai 2 (Japan).gb" size 131072 crc 6d4fd9aa sha1 ae437d4fb39d7438fc9eb98c91820aa2b5161b4f ) ) +game ( + name "In the Dark (World) (Aftermarket) (Unl)" + description "In the Dark (World) (Aftermarket) (Unl)" + rom ( name "In the Dark (World) (Aftermarket) (Unl).gb" size 1048576 crc 89ac8948 sha1 dfed92c906d9fd4e4ee405453ee391e0e9b174bf ) +) + game ( name "In Your Face (USA)" description "In Your Face (USA)" - rom ( name "In Your Face (USA).gb" size 131072 crc 80ac487e sha1 0be0f2a952497b321655dbee5c89678f40bf0b5a ) + rom ( name "In Your Face (USA).gb" size 131072 crc 80ac487e sha1 0be0f2a952497b321655dbee5c89678f40bf0b5a flags verified ) ) game ( @@ -25342,6 +25908,12 @@ game ( rom ( name "Incredible Crash Dummies, The (USA, Europe).gb" size 131072 crc d81c08fa sha1 f62d369b576cfd2882f8e03a5a32238eb1599477 ) ) +game ( + name "IndestructoTank! (World) (Aftermarket) (Unl)" + description "IndestructoTank! (World) (Aftermarket) (Unl)" + rom ( name "IndestructoTank! (World) (Aftermarket) (Unl).gb" size 32768 crc aa176926 sha1 e70992f8598c489bd34c30bd3a4c3789dddb8057 ) +) + game ( name "Indiana Jones - Saigo no Seisen (Japan)" description "Indiana Jones - Saigo no Seisen (Japan)" @@ -25363,7 +25935,7 @@ game ( game ( name "InfoGenius Productivity Pak - Berlitz French Translator (USA, Europe)" description "InfoGenius Productivity Pak - Berlitz French Translator (USA, Europe)" - rom ( name "InfoGenius Productivity Pak - Berlitz French Translator (USA, Europe).gb" size 131072 crc 80485fda sha1 ea77a00e9982ffa710ce9e179d4614419f9e1a35 ) + rom ( name "InfoGenius Productivity Pak - Berlitz French Translator (USA, Europe).gb" size 131072 crc 80485fda sha1 ea77a00e9982ffa710ce9e179d4614419f9e1a35 flags verified ) ) game ( @@ -25414,6 +25986,12 @@ game ( rom ( name "Initial D Gaiden (Japan) (SGB Enhanced).gb" size 262144 crc 6cc56612 sha1 1b3c4c1c4dfca46a009eb2e5cd45b343d7ee6681 ) ) +game ( + name "Interblocked (World) (Aftermarket) (Unl)" + description "Interblocked (World) (Aftermarket) (Unl)" + rom ( name "Interblocked (World) (Aftermarket) (Unl).gb" size 262144 crc 5c208855 sha1 8e46486533a3de9ee87cc07bf8efaf818752a61a ) +) + game ( name "International Superstar Soccer (USA, Europe) (SGB Enhanced)" description "International Superstar Soccer (USA, Europe) (SGB Enhanced)" @@ -25481,9 +26059,9 @@ game ( ) game ( - name "Jabberwocky (World) (Aftermarket) (Homebrew)" - description "Jabberwocky (World) (Aftermarket) (Homebrew)" - rom ( name "Jabberwocky (World) (Aftermarket) (Homebrew).gb" size 1048576 crc cfc51717 sha1 b4e447f2197688c740a45dce27879a62c742fb96 ) + name "Jabberwocky (World) (Aftermarket) (Unl)" + description "Jabberwocky (World) (Aftermarket) (Unl)" + rom ( name "Jabberwocky (World) (Aftermarket) (Unl).gb" size 1048576 crc cfc51717 sha1 b4e447f2197688c740a45dce27879a62c742fb96 ) ) game ( @@ -25511,9 +26089,9 @@ game ( ) game ( - name "Jana of the Jungle (World) (Demo) (Aftermarket) (Homebrew)" - description "Jana of the Jungle (World) (Demo) (Aftermarket) (Homebrew)" - rom ( name "Jana of the Jungle (World) (Demo) (Aftermarket) (Homebrew).gb" size 262144 crc c68e751a sha1 dfc65dcf700a26b273d705b7cfececbc44b80587 ) + name "Jane in the Jungle (World) (Aftermarket) (Unl)" + description "Jane in the Jungle (World) (Aftermarket) (Unl)" + rom ( name "Jane in the Jungle (World) (Aftermarket) (Unl).gb" size 262144 crc c68e751a sha1 dfc65dcf700a26b273d705b7cfececbc44b80587 ) ) game ( @@ -25543,7 +26121,7 @@ game ( game ( name "Jeep Jamboree - Off-Road Adventure (USA)" description "Jeep Jamboree - Off-Road Adventure (USA)" - rom ( name "Jeep Jamboree - Off-Road Adventure (USA).gb" size 131072 crc a1e76a33 sha1 988a40823f7db661bc91ffff8fc1bcd80e64f18d ) + rom ( name "Jeep Jamboree - Off-Road Adventure (USA).gb" size 131072 crc a1e76a33 sha1 988a40823f7db661bc91ffff8fc1bcd80e64f18d flags verified ) ) game ( @@ -25600,6 +26178,18 @@ game ( rom ( name "Jet Pak Man (Europe) (Proto).gb" size 32768 crc e40dfc1b sha1 0080f76961164810d6c0543d951a465cfa1cab54 ) ) +game ( + name "Jet Set Willy (World) (Aftermarket) (Unl)" + description "Jet Set Willy (World) (Aftermarket) (Unl)" + rom ( name "Jet Set Willy (World) (Aftermarket) (Unl).gb" size 262144 crc 1686d7ed sha1 0e594224506fd087e36c66bb66905d4c91b02461 ) +) + +game ( + name "Jetsons, The - Robot Panic (USA, Europe)" + description "Jetsons, The - Robot Panic (USA, Europe)" + rom ( name "Jetsons, The - Robot Panic (USA, Europe).gb" size 131072 crc 6386c870 sha1 1790c7462907f803e9641a330df6f06aa5e4e985 flags verified ) +) + game ( name "Jetsons, The - Robot Panic (USA) (Rev 1) (Possible Proto)" description "Jetsons, The - Robot Panic (USA) (Rev 1) (Possible Proto)" @@ -25612,12 +26202,6 @@ game ( rom ( name "Jetsons, The - Robot Panic (Japan) (Proto).gb" size 131072 crc d016d141 sha1 6035ee9364413b9eac7eb2290a806ffb57dc25cc ) ) -game ( - name "Jetsons, The - Robot Panic (USA, Europe)" - description "Jetsons, The - Robot Panic (USA, Europe)" - rom ( name "Jetsons, The - Robot Panic (USA, Europe).gb" size 131072 crc 6386c870 sha1 1790c7462907f803e9641a330df6f06aa5e4e985 flags verified ) -) - game ( name "Jikuu Senki Mu (Japan)" description "Jikuu Senki Mu (Japan)" @@ -25672,6 +26256,12 @@ game ( rom ( name "Joe & Mac - Caveman Ninja (Europe) (En,Fr,De,Es,It,Nl,Sv) (Beta).gb" size 262144 crc 381c62ee sha1 979e36765291153b82025b1f036c08fe9c23cfa5 ) ) +game ( + name "Joe Blade 2 (World) (Aftermarket) (Unl)" + description "Joe Blade 2 (World) (Aftermarket) (Unl)" + rom ( name "Joe Blade 2 (World) (Aftermarket) (Unl).gb" size 524288 crc 09f75c70 sha1 f001dffcd16be670c36a98dd9136f6f9fbf5b85d ) +) + game ( name "John Madden Football (USA) (Proto 2) (SGB Enhanced)" description "John Madden Football (USA) (Proto 2) (SGB Enhanced)" @@ -25801,7 +26391,7 @@ game ( game ( name "Kaisen Game - Navyblue (Japan)" description "Kaisen Game - Navyblue (Japan)" - rom ( name "Kaisen Game - Navyblue (Japan).gb" size 65536 crc afd8c47c sha1 7fbf13dd0b5a4e7798724270a81006be9950f81a ) + rom ( name "Kaisen Game - Navyblue (Japan).gb" size 65536 crc afd8c47c sha1 7fbf13dd0b5a4e7798724270a81006be9950f81a flags verified ) ) game ( @@ -25889,9 +26479,9 @@ game ( ) game ( - name "Kenzie's Birthday Dash (World) (Aftermarket) (Homebrew)" - description "Kenzie's Birthday Dash (World) (Aftermarket) (Homebrew)" - rom ( name "Kenzie's Birthday Dash (World) (Aftermarket) (Homebrew).gb" size 1048576 crc 986bfd75 sha1 6d15b5bc33d7c7771ba62d56ae7f25fa081bc564 ) + name "Kenzie's Birthday Dash (World) (Aftermarket) (Unl)" + description "Kenzie's Birthday Dash (World) (Aftermarket) (Unl)" + rom ( name "Kenzie's Birthday Dash (World) (Aftermarket) (Unl).gb" size 1048576 crc 986bfd75 sha1 6d15b5bc33d7c7771ba62d56ae7f25fa081bc564 ) ) game ( @@ -26005,19 +26595,19 @@ game ( game ( name "Kirby no Block Ball (Japan) (SGB Enhanced)" description "Kirby no Block Ball (Japan) (SGB Enhanced)" - rom ( name "Kirby no Block Ball (Japan) (SGB Enhanced).gb" size 524288 crc df3bbcd7 sha1 3fecc964d2c13ad98abac1df49f05c488833ef5a ) + rom ( name "Kirby no Block Ball (Japan) (SGB Enhanced).gb" size 524288 crc df3bbcd7 sha1 3fecc964d2c13ad98abac1df49f05c488833ef5a flags verified ) ) game ( name "Kirby no Kirakira Kids (Japan) (SGB Enhanced)" description "Kirby no Kirakira Kids (Japan) (SGB Enhanced)" - rom ( name "Kirby no Kirakira Kids (Japan) (SGB Enhanced).gb" size 262144 crc 47f42f42 sha1 2c46c42be76eca134e188814345afa390e298811 ) + rom ( name "Kirby no Kirakira Kids (Japan) (SGB Enhanced).gb" size 262144 crc 47f42f42 sha1 2c46c42be76eca134e188814345afa390e298811 flags verified ) ) game ( name "Kirby no Pinball (Japan)" description "Kirby no Pinball (Japan)" - rom ( name "Kirby no Pinball (Japan).gb" size 262144 crc 8b44fb7d sha1 678ca586f5a2e2fc894fb8e9f7b9efa54fabba44 ) + rom ( name "Kirby no Pinball (Japan).gb" size 262144 crc 8b44fb7d sha1 678ca586f5a2e2fc894fb8e9f7b9efa54fabba44 flags verified ) ) game ( @@ -26206,6 +26796,12 @@ game ( rom ( name "Kung-Fu Master (USA, Europe).gb" size 65536 crc 3340e600 sha1 b0bb485e2b57793da9f153db074c13a060a1e0d4 flags verified ) ) +game ( + name "Kung-Fu Master (USA, Europe) (Beta)" + description "Kung-Fu Master (USA, Europe) (Beta)" + rom ( name "Kung-Fu Master (USA, Europe) (Beta).gb" size 65536 crc e0c33c1a sha1 1ce6301d56294b71068514c666ad2157551134e7 ) +) + game ( name "Kuusou Kagaku Sekai Gulliver Boy - Kuusou Kagaku Puzzle Purittopon!! (Japan) (SGB Enhanced)" description "Kuusou Kagaku Sekai Gulliver Boy - Kuusou Kagaku Puzzle Purittopon!! (Japan) (SGB Enhanced)" @@ -26243,9 +26839,9 @@ game ( ) game ( - name "Laser Squad Alter (World) (Aftermarket) (Homebrew)" - description "Laser Squad Alter (World) (Aftermarket) (Homebrew)" - rom ( name "Laser Squad Alter (World) (Aftermarket) (Homebrew).gb" size 1048576 crc a39b3a5a sha1 1620e585e77a2457cffde82453bec0c7a04e07cf ) + name "Laser Squad Alter (World) (Aftermarket) (Unl)" + description "Laser Squad Alter (World) (Aftermarket) (Unl)" + rom ( name "Laser Squad Alter (World) (Aftermarket) (Unl).gb" size 1048576 crc a39b3a5a sha1 1620e585e77a2457cffde82453bec0c7a04e07cf ) ) game ( @@ -26255,9 +26851,9 @@ game ( ) game ( - name "Lawn Mower Land (World) (Aftermarket) (Homebrew)" - description "Lawn Mower Land (World) (Aftermarket) (Homebrew)" - rom ( name "Lawn Mower Land (World) (Aftermarket) (Homebrew).gb" size 32768 crc 11c62e39 sha1 1d6aa817ebf2a7e5d22285b906540272b67169da ) + name "Lawn Mower Land (World) (Aftermarket) (Unl)" + description "Lawn Mower Land (World) (Aftermarket) (Unl)" + rom ( name "Lawn Mower Land (World) (Aftermarket) (Unl).gb" size 32768 crc 11c62e39 sha1 1d6aa817ebf2a7e5d22285b906540272b67169da ) ) game ( @@ -26284,6 +26880,12 @@ game ( rom ( name "Lazlos' Leap (USA).gb" size 65536 crc 31fb404b sha1 c111470fcdadf191cf843e50c8f3d3dbb995a5c5 ) ) +game ( + name "Leak, The (World) (Aftermarket) (Unl)" + description "Leak, The (World) (Aftermarket) (Unl)" + rom ( name "Leak, The (World) (Aftermarket) (Unl).GB" size 65536 crc ba3cfeae sha1 98de2d8be75e6d9cb4e87afe14b1380c033de797 ) +) + game ( name "Learn and Play - Blackjack & Solitaire (USA) (1994-08-09) (Proto)" description "Learn and Play - Blackjack & Solitaire (USA) (1994-08-09) (Proto)" @@ -26543,9 +27145,9 @@ game ( ) game ( - name "Lucy and the Gem Dungeon (World) (Aftermarket) (Homebrew)" - description "Lucy and the Gem Dungeon (World) (Aftermarket) (Homebrew)" - rom ( name "Lucy and the Gem Dungeon (World) (Aftermarket) (Homebrew).gb" size 524288 crc 1a5106a2 sha1 f56c1b324b8c1e17da86e40c5e9a887ec19b0c80 ) + name "Lucy and the Gem Dungeon (World) (Aftermarket) (Unl)" + description "Lucy and the Gem Dungeon (World) (Aftermarket) (Unl)" + rom ( name "Lucy and the Gem Dungeon (World) (Aftermarket) (Unl).gb" size 524288 crc 1a5106a2 sha1 f56c1b324b8c1e17da86e40c5e9a887ec19b0c80 ) ) game ( @@ -26572,6 +27174,12 @@ game ( rom ( name "Mach Go Go Go (Japan) (SGB Enhanced).gb" size 262144 crc 016d230b sha1 baea8b49ce2402c5c094ddae0320a382c0d95d86 ) ) +game ( + name "Machine, The (World) (Aftermarket) (Unl)" + description "Machine, The (World) (Aftermarket) (Unl)" + rom ( name "Machine, The (World) (Aftermarket) (Unl).gb" size 2097152 crc b06036a9 sha1 50c5d1eb7c8946ac2d7e2c566b1ea4a9b0d58e90 ) +) + game ( name "Madden 95 (USA, Europe) (SGB Enhanced)" description "Madden 95 (USA, Europe) (SGB Enhanced)" @@ -26621,9 +27229,15 @@ game ( ) game ( - name "Magipanels (World) (Rev 1) (Demo) (Aftermarket) (Homebrew)" - description "Magipanels (World) (Rev 1) (Demo) (Aftermarket) (Homebrew)" - rom ( name "Magipanels (World) (Rev 1) (Demo) (Aftermarket) (Homebrew).gb" size 131072 crc 76fcbc30 sha1 7a759372bd013dcdb59c9131a2908085fc04d648 ) + name "Magipanels (World) (2022-02-09) (Demo) (Aftermarket) (Unl)" + description "Magipanels (World) (2022-02-09) (Demo) (Aftermarket) (Unl)" + rom ( name "Magipanels (World) (2022-02-09) (Demo) (Aftermarket) (Unl).gb" size 131072 crc 76fcbc30 sha1 7a759372bd013dcdb59c9131a2908085fc04d648 ) +) + +game ( + name "Magipanels (World) (Aftermarket) (Unl)" + description "Magipanels (World) (Aftermarket) (Unl)" + rom ( name "Magipanels (World) (Aftermarket) (Unl).gb" size 131072 crc 0b66561b sha1 c9513f6769098f2185f632f87386788f0f0b6ad8 ) ) game ( @@ -26663,9 +27277,9 @@ game ( ) game ( - name "Make Way (World) (Aftermarket) (Homebrew)" - description "Make Way (World) (Aftermarket) (Homebrew)" - rom ( name "Make Way (World) (Aftermarket) (Homebrew).gb" size 1048576 crc edeee5a8 sha1 19fd02e9318ed81ec968a2fca31ea0c3d8d94a7b ) + name "Make Way (World) (Aftermarket) (Unl)" + description "Make Way (World) (Aftermarket) (Unl)" + rom ( name "Make Way (World) (Aftermarket) (Unl).gb" size 1048576 crc edeee5a8 sha1 19fd02e9318ed81ec968a2fca31ea0c3d8d94a7b ) ) game ( @@ -26735,15 +27349,15 @@ game ( ) game ( - name "Marron Helps a Friend (World) (Aftermarket) (Homebrew)" - description "Marron Helps a Friend (World) (Aftermarket) (Homebrew)" - rom ( name "Marron Helps a Friend (World) (Aftermarket) (Homebrew).gb" size 1048576 crc 08a3a987 sha1 adcd56fa3eba93edd20be25ae5cb349070cd0323 ) + name "Marron Helps a Friend (World) (Aftermarket) (Unl)" + description "Marron Helps a Friend (World) (Aftermarket) (Unl)" + rom ( name "Marron Helps a Friend (World) (Aftermarket) (Unl).gb" size 1048576 crc 08a3a987 sha1 adcd56fa3eba93edd20be25ae5cb349070cd0323 flags verified ) ) game ( name "Maru's Mission (USA)" description "Maru's Mission (USA)" - rom ( name "Maru's Mission (USA).gb" size 131072 crc 6e4f1eb3 sha1 91446b1cc6dbf3b98b81f13e35ffe7bfaaa027aa ) + rom ( name "Maru's Mission (USA).gb" size 131072 crc 6e4f1eb3 sha1 91446b1cc6dbf3b98b81f13e35ffe7bfaaa027aa flags verified ) ) game ( @@ -26902,6 +27516,12 @@ game ( rom ( name "Mega Man V (USA) (SGB Enhanced).gb" size 524288 crc 72e6d21d sha1 9a7da0e4d3f49e4a0b94e85cd64e28a687d81260 ) ) +game ( + name "Mega Memory (World) (Unl)" + description "Mega Memory (World) (Unl)" + rom ( name "Mega Memory (World) (Unl).gb" size 32768 crc dba81e98 sha1 7690ccaa54b71188d07cb546d3c4d42c1d6363a8 ) +) + game ( name "Megalit (Japan)" description "Megalit (Japan)" @@ -26939,9 +27559,9 @@ game ( ) game ( - name "Melanie and the Magic Forest (World) (Aftermarket) (Homebrew)" - description "Melanie and the Magic Forest (World) (Aftermarket) (Homebrew)" - rom ( name "Melanie and the Magic Forest (World) (Aftermarket) (Homebrew).gb" size 262144 crc f614bdff sha1 7e46c193f6566f620acf20ead3a1fca1d677c221 ) + name "Melanie and the Magic Forest (World) (Aftermarket) (Unl)" + description "Melanie and the Magic Forest (World) (Aftermarket) (Unl)" + rom ( name "Melanie and the Magic Forest (World) (Aftermarket) (Unl).gb" size 262144 crc f614bdff sha1 7e46c193f6566f620acf20ead3a1fca1d677c221 ) ) game ( @@ -26962,6 +27582,12 @@ game ( rom ( name "Metal Masters (USA) (Beta).gb" size 131072 crc 3378064b sha1 572c820f7ec7a3c96df9af441da244befa5223b9 ) ) +game ( + name "Meteorite (World) (Aftermarket) (Unl)" + description "Meteorite (World) (Aftermarket) (Unl)" + rom ( name "Meteorite (World) (Aftermarket) (Unl).gb" size 262144 crc 50cf593d sha1 0e88a47faf3273f87c1ead7f789faeb06d5333f3 ) +) + game ( name "Metroid II - Return of Samus (World)" description "Metroid II - Return of Samus (World)" @@ -27079,13 +27705,13 @@ game ( game ( name "Midori no Makibaou (Japan) (SGB Enhanced)" description "Midori no Makibaou (Japan) (SGB Enhanced)" - rom ( name "Midori no Makibaou (Japan) (SGB Enhanced).gb" size 262144 crc 1dd93d95 sha1 4de380b202e375761f9fe7fb13349f5b02e2dac2 ) + rom ( name "Midori no Makibaou (Japan) (SGB Enhanced).gb" size 262144 crc 1dd93d95 sha1 4de380b202e375761f9fe7fb13349f5b02e2dac2 flags verified ) ) game ( - name "Midterm Moments (World) (Aftermarket) (Homebrew)" - description "Midterm Moments (World) (Aftermarket) (Homebrew)" - rom ( name "Midterm Moments (World) (Aftermarket) (Homebrew).gb" size 262144 crc f9a5ed4e sha1 8b3e18287d5adf3b5a711e28a896f843927f22a2 ) + name "Midterm Moments (World) (Aftermarket) (Unl)" + description "Midterm Moments (World) (Aftermarket) (Unl)" + rom ( name "Midterm Moments (World) (Aftermarket) (Unl).gb" size 262144 crc f9a5ed4e sha1 8b3e18287d5adf3b5a711e28a896f843927f22a2 ) ) game ( @@ -27203,9 +27829,9 @@ game ( ) game ( - name "Mission to Mars (World) (Aftermarket) (Homebrew)" - description "Mission to Mars (World) (Aftermarket) (Homebrew)" - rom ( name "Mission to Mars (World) (Aftermarket) (Homebrew).gb" size 262144 crc cb3a9ab8 sha1 31625a90b61d28ac782909ed8b00b3a48df7742e ) + name "Mission to Mars (World) (Aftermarket) (Unl)" + description "Mission to Mars (World) (Aftermarket) (Unl)" + rom ( name "Mission to Mars (World) (Aftermarket) (Unl).gb" size 262144 crc cb3a9ab8 sha1 31625a90b61d28ac782909ed8b00b3a48df7742e ) ) game ( @@ -27281,9 +27907,9 @@ game ( ) game ( - name "Mona and the Witch's Hat (World) (Aftermarket) (Homebrew)" - description "Mona and the Witch's Hat (World) (Aftermarket) (Homebrew)" - rom ( name "Mona and the Witch's Hat (World) (Aftermarket) (Homebrew).gb" size 65536 crc 32ed7f4a sha1 625d3664d99dee075021b03ea6ff074ae2e49204 ) + name "Mona and the Witch's Hat (World) (Aftermarket) (Unl)" + description "Mona and the Witch's Hat (World) (Aftermarket) (Unl)" + rom ( name "Mona and the Witch's Hat (World) (Aftermarket) (Unl).gb" size 65536 crc 32ed7f4a sha1 625d3664d99dee075021b03ea6ff074ae2e49204 ) ) game ( @@ -27377,9 +28003,9 @@ game ( ) game ( - name "Monty on the Run (World) (Aftermarket) (Homebrew)" - description "Monty on the Run (World) (Aftermarket) (Homebrew)" - rom ( name "Monty on the Run (World) (Aftermarket) (Homebrew).gb" size 524288 crc e2ebb406 sha1 41ac5993fbff58be0712b554aae5e4367b68677b ) + name "Monty on the Run (World) (Aftermarket) (Unl)" + description "Monty on the Run (World) (Aftermarket) (Unl)" + rom ( name "Monty on the Run (World) (Aftermarket) (Unl).gb" size 524288 crc e2ebb406 sha1 41ac5993fbff58be0712b554aae5e4367b68677b ) ) game ( @@ -27461,9 +28087,9 @@ game ( ) game ( - name "Mountain Climber (World) (Aftermarket) (Homebrew)" - description "Mountain Climber (World) (Aftermarket) (Homebrew)" - rom ( name "Mountain Climber (World) (Aftermarket) (Homebrew).gb" size 262144 crc 362a84ba sha1 b4b3afa2379a07acab6965ae8fcf6d2ba82d6d7e ) + name "Mountain Climber (World) (En,Es) (Aftermarket) (Unl)" + description "Mountain Climber (World) (En,Es) (Aftermarket) (Unl)" + rom ( name "Mountain Climber (World) (En,Es) (Aftermarket) (Unl).gb" size 262144 crc 362a84ba sha1 b4b3afa2379a07acab6965ae8fcf6d2ba82d6d7e ) ) game ( @@ -27515,9 +28141,9 @@ game ( ) game ( - name "Mud Warriors (World) (Aftermarket) (Homebrew)" - description "Mud Warriors (World) (Aftermarket) (Homebrew)" - rom ( name "Mud Warriors (World) (Aftermarket) (Homebrew).gb" size 524288 crc 846afc79 sha1 dae84fbc60839da29878714cd845c3704b915323 ) + name "Mud Warriors (World) (Aftermarket) (Unl)" + description "Mud Warriors (World) (Aftermarket) (Unl)" + rom ( name "Mud Warriors (World) (Aftermarket) (Unl).gb" size 524288 crc 846afc79 sha1 dae84fbc60839da29878714cd845c3704b915323 ) ) game ( @@ -27571,7 +28197,7 @@ game ( game ( name "Mystic Quest (France)" description "Mystic Quest (France)" - rom ( name "Mystic Quest (France).gb" size 262144 crc b6e134af sha1 b52d82248849f1ead9bf22954b3cbf7bf8e02907 ) + rom ( name "Mystic Quest (France).gb" size 262144 crc b6e134af sha1 b52d82248849f1ead9bf22954b3cbf7bf8e02907 flags verified ) ) game ( @@ -27766,6 +28392,12 @@ game ( rom ( name "Nekketsu! Beach Volley Da yo Kunio-kun (Japan) (SGB Enhanced).gb" size 262144 crc abfb84df sha1 d3ad43c40c21d9825b4879dbf28ca58835da658a ) ) +game ( + name "Neko Can Dream (World) (Ja) (Demo) (Aftermarket) (Unl)" + description "Neko Can Dream (World) (Ja) (Demo) (Aftermarket) (Unl)" + rom ( name "Neko Can Dream (World) (Ja) (Demo) (Aftermarket) (Unl).gb" size 2097152 crc a0ad7a18 sha1 5fb1a09e04cf1704b059468de8855dfa7e23043b ) +) + game ( name "Nekojara Monogatari (Japan)" description "Nekojara Monogatari (Japan)" @@ -28097,9 +28729,9 @@ game ( ) game ( - name "Oblique Strategies (World) (Aftermarket) (Homebrew)" - description "Oblique Strategies (World) (Aftermarket) (Homebrew)" - rom ( name "Oblique Strategies (World) (Aftermarket) (Homebrew).gb" size 524288 crc 30eca8d3 sha1 6a09aabf30e2ebab2e31ae845cd2e8796c84797f ) + name "Oblique Strategies (World) (Aftermarket) (Unl)" + description "Oblique Strategies (World) (Aftermarket) (Unl)" + rom ( name "Oblique Strategies (World) (Aftermarket) (Unl).gb" size 524288 crc 30eca8d3 sha1 6a09aabf30e2ebab2e31ae845cd2e8796c84797f ) ) game ( @@ -28109,9 +28741,9 @@ game ( ) game ( - name "Office Combat (World) (Aftermarket) (Homebrew)" - description "Office Combat (World) (Aftermarket) (Homebrew)" - rom ( name "Office Combat (World) (Aftermarket) (Homebrew).gb" size 262144 crc 9742276d sha1 cd8f18d6e5fabaa130a42e5618e29c5ff26f6a93 ) + name "Office Combat (World) (Aftermarket) (Unl)" + description "Office Combat (World) (Aftermarket) (Unl)" + rom ( name "Office Combat (World) (Aftermarket) (Unl).gb" size 262144 crc 9742276d sha1 cd8f18d6e5fabaa130a42e5618e29c5ff26f6a93 ) ) game ( @@ -28121,9 +28753,9 @@ game ( ) game ( - name "Olympic Skier (World) (Aftermarket) (Homebrew)" - description "Olympic Skier (World) (Aftermarket) (Homebrew)" - rom ( name "Olympic Skier (World) (Aftermarket) (Homebrew).gb" size 524288 crc 93174431 sha1 7bcdf10533f8fe053e22534c4962ed4b1a5cf2e2 ) + name "Olympic Skier (World) (Aftermarket) (Unl)" + description "Olympic Skier (World) (Aftermarket) (Unl)" + rom ( name "Olympic Skier (World) (Aftermarket) (Unl).gb" size 524288 crc 93174431 sha1 7bcdf10533f8fe053e22534c4962ed4b1a5cf2e2 ) ) game ( @@ -28234,6 +28866,18 @@ game ( rom ( name "Out of Gas (USA).gb" size 131072 crc 1b67e8b1 sha1 770ac35c0780cf432235593bd5674e72edd6cf7d ) ) +game ( + name "Out on a Limb (World) (Aftermarket) (Unl)" + description "Out on a Limb (World) (Aftermarket) (Unl)" + rom ( name "Out on a Limb (World) (Aftermarket) (Unl).gb" size 262144 crc be197f87 sha1 2ddc2378539316cf7a10e2cf8b562f17e1cf7a6a ) +) + +game ( + name "Outburst (Japan) (Demo)" + description "Outburst (Japan) (Demo)" + rom ( name "Outburst (Japan) (Demo).gb" size 262144 crc 9837ffac sha1 4a45cc555b486f5dafa4e844a7b6afceb709a1b9 ) +) + game ( name "Outburst (Japan)" description "Outburst (Japan)" @@ -28277,9 +28921,9 @@ game ( ) game ( - name "Pac-In-Time (Europe) (Rev 1) (Possible Proto) (SGB Enhanced)" - description "Pac-In-Time (Europe) (Rev 1) (Possible Proto) (SGB Enhanced)" - rom ( name "Pac-In-Time (Europe) (Rev 1) (Possible Proto) (SGB Enhanced).gb" size 262144 crc 7ff79df9 sha1 f30108b8e422c4b9776c90c884710194508545ea ) + name "Pac-In-Time (Europe) (Rev 1) (SGB Enhanced)" + description "Pac-In-Time (Europe) (Rev 1) (SGB Enhanced)" + rom ( name "Pac-In-Time (Europe) (Rev 1) (SGB Enhanced).gb" size 262144 crc 7ff79df9 sha1 f30108b8e422c4b9776c90c884710194508545ea ) ) game ( @@ -28468,6 +29112,12 @@ game ( rom ( name "Pang (Europe) (Beta).gb" size 131072 crc 54d2754a sha1 b57d436d8a83f2fb1e42bad3150893ef8ffab0d4 ) ) +game ( + name "Panty Hunty (World) (v1.4) (Aftermarket) (Unl)" + description "Panty Hunty (World) (v1.4) (Aftermarket) (Unl)" + rom ( name "Panty Hunty (World) (v1.4) (Aftermarket) (Unl).gb" size 524288 crc ce707b9a sha1 b5757b268a09d9085d469eda0676991f7361ad8c ) +) + game ( name "Paperboy (USA, Europe)" description "Paperboy (USA, Europe)" @@ -28487,9 +29137,9 @@ game ( ) game ( - name "Parasol Islands (World) (Aftermarket) (Homebrew)" - description "Parasol Islands (World) (Aftermarket) (Homebrew)" - rom ( name "Parasol Islands (World) (Aftermarket) (Homebrew).gb" size 1048576 crc b1d79c80 sha1 381f76af67f77e575beae2f45aefd21f079709e6 ) + name "Parasol Islands (World) (Aftermarket) (Unl)" + description "Parasol Islands (World) (Aftermarket) (Unl)" + rom ( name "Parasol Islands (World) (Aftermarket) (Unl).gb" size 1048576 crc b1d79c80 sha1 381f76af67f77e575beae2f45aefd21f079709e6 ) ) game ( @@ -28547,15 +29197,15 @@ game ( ) game ( - name "Perfect Blend (World) (Aftermarket) (Homebrew)" - description "Perfect Blend (World) (Aftermarket) (Homebrew)" - rom ( name "Perfect Blend (World) (Aftermarket) (Homebrew).gb" size 262144 crc 9b4bacaa sha1 09b0691f6823c54f1515dd3b66f135ac45a7dbc3 ) + name "Perfect Blend (World) (v0.9) (Aftermarket) (Unl)" + description "Perfect Blend (World) (v0.9) (Aftermarket) (Unl)" + rom ( name "Perfect Blend (World) (v0.9) (Aftermarket) (Unl).gb" size 262144 crc 9b4bacaa sha1 09b0691f6823c54f1515dd3b66f135ac45a7dbc3 ) ) game ( - name "Perfect Blend (World) (Rev 1) (Aftermarket) (Homebrew)" - description "Perfect Blend (World) (Rev 1) (Aftermarket) (Homebrew)" - rom ( name "Perfect Blend (World) (Rev 1) (Aftermarket) (Homebrew).gb" size 262144 crc 1e8b2a29 sha1 a25dfc43408080ac116479fa55c9e7320e5cc82a ) + name "Perfect Blend (World) (v0.9) (Bugfix) (Aftermarket) (Unl)" + description "Perfect Blend (World) (v0.9) (Bugfix) (Aftermarket) (Unl)" + rom ( name "Perfect Blend (World) (v0.9) (Bugfix) (Aftermarket) (Unl).gb" size 262144 crc 1e8b2a29 sha1 a25dfc43408080ac116479fa55c9e7320e5cc82a ) ) game ( @@ -28583,9 +29233,9 @@ game ( ) game ( - name "Phantom Fright (World) (Aftermarket) (Homebrew)" - description "Phantom Fright (World) (Aftermarket) (Homebrew)" - rom ( name "Phantom Fright (World) (Aftermarket) (Homebrew).gb" size 524288 crc 154cb547 sha1 3b217c595e6a5b115dc7130123a321049cf01d22 ) + name "Phantom Fright (World) (Aftermarket) (Unl)" + description "Phantom Fright (World) (Aftermarket) (Unl)" + rom ( name "Phantom Fright (World) (Aftermarket) (Unl).gb" size 524288 crc 154cb547 sha1 3b217c595e6a5b115dc7130123a321049cf01d22 ) ) game ( @@ -28595,15 +29245,15 @@ game ( ) game ( - name "Phobos Dere .GB (World) (Rev 1) (Demo) (Aftermarket) (Homebrew)" - description "Phobos Dere .GB (World) (Rev 1) (Demo) (Aftermarket) (Homebrew)" - rom ( name "Phobos Dere .GB (World) (Rev 1) (Demo) (Aftermarket) (Homebrew).gbc" size 262144 crc 58362b86 sha1 b4cc3c3735bc134e34b97e08ed24c57aef5c5ccf flags verified ) + name "Phobos Dere .GB (World) (Aftermarket) (Unl)" + description "Phobos Dere .GB (World) (Aftermarket) (Unl)" + rom ( name "Phobos Dere .GB (World) (Aftermarket) (Unl).gb" size 1048576 crc ae97f4f8 sha1 e541505078ee5e7e5c897b4621cc693ce71e35ba flags verified ) ) game ( - name "Phobos Dere .GB (World) (Aftermarket) (Homebrew)" - description "Phobos Dere .GB (World) (Aftermarket) (Homebrew)" - rom ( name "Phobos Dere .GB (World) (Aftermarket) (Homebrew).gbc" size 1048576 crc ae97f4f8 sha1 e541505078ee5e7e5c897b4621cc693ce71e35ba ) + name "Phobos Dere .GB (World) (2022-03-06) (Demo) (Aftermarket) (Unl)" + description "Phobos Dere .GB (World) (2022-03-06) (Demo) (Aftermarket) (Unl)" + rom ( name "Phobos Dere .GB (World) (2022-03-06) (Demo) (Aftermarket) (Unl).gb" size 262144 crc 58362b86 sha1 b4cc3c3735bc134e34b97e08ed24c57aef5c5ccf flags verified ) ) game ( @@ -28660,6 +29310,12 @@ game ( rom ( name "Pinball Mania (Europe).gb" size 262144 crc edc8d122 sha1 0541161b4b93fffc3c75708aca86ea0873747ced ) ) +game ( + name "Pineapple Kid (World) (Aftermarket) (Unl)" + description "Pineapple Kid (World) (Aftermarket) (Unl)" + rom ( name "Pineapple Kid (World) (Aftermarket) (Unl).gb" size 131072 crc 9541488b sha1 fd574b5d407f3654db3227900a8ed7eff4fe48ae ) +) + game ( name "Pingu - Sekai de 1ban Genki na Penguin (Japan)" description "Pingu - Sekai de 1ban Genki na Penguin (Japan)" @@ -28703,9 +29359,15 @@ game ( ) game ( - name "Pixel Who - The Lost Legions (World) (v1.0) (Aftermarket) (Homebrew)" - description "Pixel Who - The Lost Legions (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Pixel Who - The Lost Legions (World) (v1.0) (Aftermarket) (Homebrew).gb" size 1048576 crc f41cf2a7 sha1 c07378c774602060f1f93f18fbfd3f0d552fe2d2 ) + name "Pixel Who - The Lost Legions (World) (v1.0) (Aftermarket) (Unl)" + description "Pixel Who - The Lost Legions (World) (v1.0) (Aftermarket) (Unl)" + rom ( name "Pixel Who - The Lost Legions (World) (v1.0) (Aftermarket) (Unl).gb" size 1048576 crc f41cf2a7 sha1 c07378c774602060f1f93f18fbfd3f0d552fe2d2 ) +) + +game ( + name "Plants Eat My Zombies (World) (v1.1) (Aftermarket) (Unl)" + description "Plants Eat My Zombies (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Plants Eat My Zombies (World) (v1.1) (Aftermarket) (Unl).gb" size 524288 crc 60c84f2e sha1 c39ec5e6a9a0be0970b56e788c3f60e9821dbbd3 ) ) game ( @@ -28715,9 +29377,9 @@ game ( ) game ( - name "Pluto's Corner (World) (Aftermarket) (Homebrew)" - description "Pluto's Corner (World) (Aftermarket) (Homebrew)" - rom ( name "Pluto's Corner (World) (Aftermarket) (Homebrew).gb" size 65536 crc 21fc06b3 sha1 94e4a442205079d85d5ca37042eaec555f7f49db ) + name "Pluto's Corner (World) (Aftermarket) (Unl)" + description "Pluto's Corner (World) (Aftermarket) (Unl)" + rom ( name "Pluto's Corner (World) (Aftermarket) (Unl).gb" size 65536 crc 21fc06b3 sha1 94e4a442205079d85d5ca37042eaec555f7f49db ) ) game ( @@ -28753,7 +29415,7 @@ game ( game ( name "Pocket Camera (Japan) (Rev 1) (SGB Enhanced)" description "Pocket Camera (Japan) (Rev 1) (SGB Enhanced)" - rom ( name "Pocket Camera (Japan) (Rev 1) (SGB Enhanced).gb" size 1048576 crc 73e8ef96 sha1 912205ea22e1dd735ed4f41d247039eebf39dddc ) + rom ( name "Pocket Camera (Japan) (Rev 1) (SGB Enhanced).gb" size 1048576 crc 73e8ef96 sha1 912205ea22e1dd735ed4f41d247039eebf39dddc flags verified ) ) game ( @@ -28919,9 +29581,9 @@ game ( ) game ( - name "Pogo Pete (World) (Aftermarket) (Homebrew)" - description "Pogo Pete (World) (Aftermarket) (Homebrew)" - rom ( name "Pogo Pete (World) (Aftermarket) (Homebrew).gb" size 262144 crc 185ce1d5 sha1 74021e84c1d095b2401302e26352e810dbc432d8 ) + name "Pogo Pete (World) (Aftermarket) (Unl)" + description "Pogo Pete (World) (Aftermarket) (Unl)" + rom ( name "Pogo Pete (World) (Aftermarket) (Unl).gb" size 262144 crc 185ce1d5 sha1 74021e84c1d095b2401302e26352e810dbc432d8 ) ) game ( @@ -29050,6 +29712,12 @@ game ( rom ( name "Pop'n TwinBee (Europe).gb" size 131072 crc d07db274 sha1 0206230b55c15a8c18fbb85f852967e44b33a0a4 ) ) +game ( + name "Popcorn Caravan (World) (Aftermarket) (Unl)" + description "Popcorn Caravan (World) (Aftermarket) (Unl)" + rom ( name "Popcorn Caravan (World) (Aftermarket) (Unl).gb" size 65536 crc ead528f9 sha1 909d3421747d211a2dc70918d2c952db953e49b2 ) +) + game ( name "Popeye (Japan)" description "Popeye (Japan)" @@ -29105,9 +29773,15 @@ game ( ) game ( - name "Porklike (World) (Aftermarket) (Homebrew)" - description "Porklike (World) (Aftermarket) (Homebrew)" - rom ( name "Porklike (World) (Aftermarket) (Homebrew).gb" size 65536 crc 6c57e55e sha1 143f31656a5007f65b19d6e32967698bb8f72a66 ) + name "Porklike (World) (v1.0.3) (Aftermarket) (Unl)" + description "Porklike (World) (v1.0.3) (Aftermarket) (Unl)" + rom ( name "Porklike (World) (v1.0.3) (Aftermarket) (Unl).gb" size 65536 crc 6c57e55e sha1 143f31656a5007f65b19d6e32967698bb8f72a66 ) +) + +game ( + name "Porklike (World) (v1.0.9) (Aftermarket) (Unl)" + description "Porklike (World) (v1.0.9) (Aftermarket) (Unl)" + rom ( name "Porklike (World) (v1.0.9) (Aftermarket) (Unl).gb" size 65536 crc 7aaa853a sha1 0868bffd52b2bdc5601ab6cf1e25f40924ca643d ) ) game ( @@ -29212,6 +29886,12 @@ game ( rom ( name "Prince of Persia (Europe) (En,Fr,De,Es,It) (Beta).gb" size 131072 crc e0aaad99 sha1 42b6dfbf283946998caa6c383fa70bf1b646217e ) ) +game ( + name "Prince of Persia (USA) (Beta)" + description "Prince of Persia (USA) (Beta)" + rom ( name "Prince of Persia (USA) (Beta).gb" size 131072 crc c81ca14b sha1 355709bd9d4b19be5ee57bd62a0b58b73b25e0bf ) +) + game ( name "Prince YehRude (Taiwan) (Unl)" description "Prince YehRude (Taiwan) (Unl)" @@ -29315,9 +29995,15 @@ game ( ) game ( - name "Pushingo (World) (Aftermarket) (Homebrew)" - description "Pushingo (World) (Aftermarket) (Homebrew)" - rom ( name "Pushingo (World) (Aftermarket) (Homebrew).gb" size 262144 crc a8541ed0 sha1 1ac1e22b191a95fdfa00c34ccba7387c991c827b ) + name "Purple Turtles (World) (Aftermarket) (Unl)" + description "Purple Turtles (World) (Aftermarket) (Unl)" + rom ( name "Purple Turtles (World) (Aftermarket) (Unl).gb" size 262144 crc f7041a97 sha1 d2d7eff063104c8f205c192a902a53092c0b5f4a ) +) + +game ( + name "Pushingo (World) (Aftermarket) (Unl)" + description "Pushingo (World) (Aftermarket) (Unl)" + rom ( name "Pushingo (World) (Aftermarket) (Unl).gb" size 262144 crc a8541ed0 sha1 1ac1e22b191a95fdfa00c34ccba7387c991c827b ) ) game ( @@ -29405,9 +30091,9 @@ game ( ) game ( - name "Quartet (World) (Aftermarket) (Homebrew)" - description "Quartet (World) (Aftermarket) (Homebrew)" - rom ( name "Quartet (World) (Aftermarket) (Homebrew).gb" size 32768 crc 46743216 sha1 bf866b438a602af386f8fad02727b4084edf6047 ) + name "Quartet (World) (Aftermarket) (Unl)" + description "Quartet (World) (Aftermarket) (Unl)" + rom ( name "Quartet (World) (Aftermarket) (Unl).gb" size 32768 crc 46743216 sha1 bf866b438a602af386f8fad02727b4084edf6047 ) ) game ( @@ -29423,9 +30109,15 @@ game ( ) game ( - name "Quest Arrest (World) (v1.1) (Aftermarket) (Homebrew)" - description "Quest Arrest (World) (v1.1) (Aftermarket) (Homebrew)" - rom ( name "Quest Arrest (World) (v1.1) (Aftermarket) (Homebrew).gb" size 1048576 crc 9ac546d5 sha1 25b3a21135bfc7587c096b10f4a20d8b3095d721 ) + name "Quest Arrest (World) (v1.1) (Aftermarket) (Unl)" + description "Quest Arrest (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Quest Arrest (World) (v1.1) (Aftermarket) (Unl).gb" size 1048576 crc 9ac546d5 sha1 25b3a21135bfc7587c096b10f4a20d8b3095d721 ) +) + +game ( + name "Quick Draw (World) (Aftermarket) (Unl)" + description "Quick Draw (World) (Aftermarket) (Unl)" + rom ( name "Quick Draw (World) (Aftermarket) (Unl).gb" size 262144 crc 7704671f sha1 75f96ce54ed8739d58153780e9b573a65f1c5880 ) ) game ( @@ -29455,7 +30147,7 @@ game ( game ( name "R-Type II (Europe)" description "R-Type II (Europe)" - rom ( name "R-Type II (Europe).gb" size 131072 crc 2fe2a72d sha1 f19436dfed41b9c2e94e070a76c5bdbcc7f993c3 ) + rom ( name "R-Type II (Europe).gb" size 131072 crc 2fe2a72d sha1 f19436dfed41b9c2e94e070a76c5bdbcc7f993c3 flags verified ) ) game ( @@ -29503,7 +30195,7 @@ game ( game ( name "Racing Damashii (Japan)" description "Racing Damashii (Japan)" - rom ( name "Racing Damashii (Japan).gb" size 65536 crc 796fbb66 sha1 4466b36294176b3e1fc1558cd00abdfa4247a75d ) + rom ( name "Racing Damashii (Japan).gb" size 65536 crc 796fbb66 sha1 4466b36294176b3e1fc1558cd00abdfa4247a75d flags verified ) ) game ( @@ -29512,6 +30204,12 @@ game ( rom ( name "Radar Mission (USA, Europe).gb" size 131072 crc 581da9c9 sha1 5ab5998a84eebc769f75c482cb0a5586ed97e888 ) ) +game ( + name "Raffles (World) (Aftermarket) (Unl)" + description "Raffles (World) (Aftermarket) (Unl)" + rom ( name "Raffles (World) (Aftermarket) (Unl).gb" size 262144 crc 0b400a91 sha1 aadfe3fd0720696547b73b272354b592b96f98f7 ) +) + game ( name "Raging Fighter (USA, Europe)" description "Raging Fighter (USA, Europe)" @@ -29579,9 +30277,9 @@ game ( ) game ( - name "Remute - Living Electronics (World) (Aftermarket) (Homebrew)" - description "Remute - Living Electronics (World) (Aftermarket) (Homebrew)" - rom ( name "Remute - Living Electronics (World) (Aftermarket) (Homebrew).gb" size 2097152 crc 1ced0a62 sha1 bc4bf12344d3b9d028a7013c84b94aea515be196 ) + name "Remute - Living Electronics (World) (Aftermarket) (Unl)" + description "Remute - Living Electronics (World) (Aftermarket) (Unl)" + rom ( name "Remute - Living Electronics (World) (Aftermarket) (Unl).gb" size 2097152 crc 1ced0a62 sha1 bc4bf12344d3b9d028a7013c84b94aea515be196 ) ) game ( @@ -29615,21 +30313,21 @@ game ( ) game ( - name "Rewind Time (World) (Aftermarket) (Homebrew)" - description "Rewind Time (World) (Aftermarket) (Homebrew)" - rom ( name "Rewind Time (World) (Aftermarket) (Homebrew).gb" size 262144 crc d8ad184a sha1 903ef8074e58a52bc98dcb9e3f2c88b52fc1335b ) + name "Rewind Time (World) (Aftermarket) (Unl)" + description "Rewind Time (World) (Aftermarket) (Unl)" + rom ( name "Rewind Time (World) (Aftermarket) (Unl).gb" size 262144 crc d8ad184a sha1 903ef8074e58a52bc98dcb9e3f2c88b52fc1335b ) ) game ( - name "Rhythm Land (World) (v1.0) (Aftermarket) (Homebrew)" - description "Rhythm Land (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Rhythm Land (World) (v1.0) (Aftermarket) (Homebrew).gb" size 131072 crc 4312c7ff sha1 c79f26ce187edea18cf92be8340ec0a0c8beec87 ) + name "Rhythm Land (World) (v1.0.0) (Aftermarket) (Unl)" + description "Rhythm Land (World) (v1.0.0) (Aftermarket) (Unl)" + rom ( name "Rhythm Land (World) (v1.0.0) (Aftermarket) (Unl).gb" size 131072 crc 4312c7ff sha1 c79f26ce187edea18cf92be8340ec0a0c8beec87 ) ) game ( - name "Rhythm Land (World) (v1.01) (Aftermarket) (Homebrew)" - description "Rhythm Land (World) (v1.01) (Aftermarket) (Homebrew)" - rom ( name "Rhythm Land (World) (v1.01) (Aftermarket) (Homebrew).gb" size 131072 crc fbf98400 sha1 1b831806d32e1ec35bc94a84384c6989433a274d ) + name "Rhythm Land (World) (v1.0.1) (Aftermarket) (Unl)" + description "Rhythm Land (World) (v1.0.1) (Aftermarket) (Unl)" + rom ( name "Rhythm Land (World) (v1.0.1) (Aftermarket) (Unl).gb" size 131072 crc fbf98400 sha1 1b831806d32e1ec35bc94a84384c6989433a274d ) ) game ( @@ -29651,9 +30349,9 @@ game ( ) game ( - name "Rig Attack (World) (Aftermarket) (Homebrew)" - description "Rig Attack (World) (Aftermarket) (Homebrew)" - rom ( name "Rig Attack (World) (Aftermarket) (Homebrew).gb" size 262144 crc dea8749d sha1 6f0fbac6b6c2e8f2d8df0a0a0e2f0c947e37b39a ) + name "Rig Attack (World) (Aftermarket) (Unl)" + description "Rig Attack (World) (Aftermarket) (Unl)" + rom ( name "Rig Attack (World) (Aftermarket) (Unl).gb" size 262144 crc dea8749d sha1 6f0fbac6b6c2e8f2d8df0a0a0e2f0c947e37b39a ) ) game ( @@ -29687,9 +30385,9 @@ game ( ) game ( - name "Robby's Day Out (World) (Aftermarket) (Homebrew)" - description "Robby's Day Out (World) (Aftermarket) (Homebrew)" - rom ( name "Robby's Day Out (World) (Aftermarket) (Homebrew).gb" size 262144 crc c3c89cd9 sha1 9d38b69ab9a4ff7bf387c88f2afed410d450f2e0 ) + name "Robby's Day Out (World) (Aftermarket) (Unl)" + description "Robby's Day Out (World) (Aftermarket) (Unl)" + rom ( name "Robby's Day Out (World) (Aftermarket) (Unl).gb" size 262144 crc c3c89cd9 sha1 9d38b69ab9a4ff7bf387c88f2afed410d450f2e0 ) ) game ( @@ -29887,7 +30585,7 @@ game ( game ( name "Sa-Ga 3 - Jikuu no Hasha (Japan)" description "Sa-Ga 3 - Jikuu no Hasha (Japan)" - rom ( name "Sa-Ga 3 - Jikuu no Hasha (Japan).gb" size 262144 crc 575d6d9d sha1 c8dcbefc0352b0590fd85a683d983b5510a63519 ) + rom ( name "Sa-Ga 3 - Jikuu no Hasha (Japan).gb" size 262144 crc 575d6d9d sha1 c8dcbefc0352b0590fd85a683d983b5510a63519 flags verified ) ) game ( @@ -29921,9 +30619,9 @@ game ( ) game ( - name "Sam Mallard - The Case of the Missing Swan (World) (Aftermarket) (Homebrew)" - description "Sam Mallard - The Case of the Missing Swan (World) (Aftermarket) (Homebrew)" - rom ( name "Sam Mallard - The Case of the Missing Swan (World) (Aftermarket) (Homebrew).gb" size 1048576 crc 994a2edd sha1 0132f0311d8478d4f45b3b8e2728698570091d69 ) + name "Sam Mallard - The Case of the Missing Swan (World) (Aftermarket) (Unl)" + description "Sam Mallard - The Case of the Missing Swan (World) (Aftermarket) (Unl)" + rom ( name "Sam Mallard - The Case of the Missing Swan (World) (Aftermarket) (Unl).gb" size 1048576 crc 994a2edd sha1 0132f0311d8478d4f45b3b8e2728698570091d69 ) ) game ( @@ -29974,6 +30672,12 @@ game ( rom ( name "Sanrio Uranai Party (Japan) (Rev 1).gb" size 262144 crc d0687d9f sha1 72dfcc83dbd78ad2b5b43014fc9adf05431e230c flags verified ) ) +game ( + name "Sapphire & Shiny Kidnap the Crows (World) (v2) (Aftermarket) (Unl)" + description "Sapphire & Shiny Kidnap the Crows (World) (v2) (Aftermarket) (Unl)" + rom ( name "Sapphire & Shiny Kidnap the Crows (World) (v2) (Aftermarket) (Unl).gb" size 1048576 crc edb63c15 sha1 4617b71836c3ddabcfef46b33aa8a83803eaea90 ) +) + game ( name "Sarakon (Europe) (Proto)" description "Sarakon (Europe) (Proto)" @@ -30148,12 +30852,31 @@ game ( rom ( name "Shanghai Pocket (Japan) (SGB Enhanced).gb" size 262144 crc 6b8eff2c sha1 82fe97442aa625fd36cf865d82f78b07108ede7f ) ) +game ( + name "Shapeshifter 2, The (World) (Aftermarket)" + description "Shapeshifter 2, The (World) (Aftermarket)" + rom ( name "Shapeshifter 2, The (World) (Aftermarket) (Cartridge 1).gb" size 2097152 crc 276125f1 sha1 b181b58984cb44c950fc56f88a4fb8112da8fcff ) + rom ( name "Shapeshifter 2, The (World) (Aftermarket) (Cartridge 2).gb" size 2097152 crc 5b45bce2 sha1 79d16ef5383ddc7ae299f825cd03268d00887500 ) +) + +game ( + name "Shapeshifter, The (World) (Aftermarket)" + description "Shapeshifter, The (World) (Aftermarket)" + rom ( name "Shapeshifter, The (World) (Aftermarket).gb" size 1048576 crc ef735794 sha1 179e69199f19b403b4c1a9675a3ee431ee570797 ) +) + game ( name "Shaq Fu (USA) (SGB Enhanced)" description "Shaq Fu (USA) (SGB Enhanced)" rom ( name "Shaq Fu (USA) (SGB Enhanced).gb" size 524288 crc 7ed43fe6 sha1 5e9e68d9235cf8149232b85fd6080ba8796cb85e ) ) +game ( + name "Shark Attack (World) (Aftermarket) (Unl)" + description "Shark Attack (World) (Aftermarket) (Unl)" + rom ( name "Shark Attack (World) (Aftermarket) (Unl).gb" size 262144 crc 3032025a sha1 6cc65bb719af53128bb04c9ee7c6abbf4ff1c3cf ) +) + game ( name "Shikinjou (Japan)" description "Shikinjou (Japan)" @@ -30221,21 +30944,21 @@ game ( ) game ( - name "Shock Lobster (World) (Aftermarket) (Homebrew)" - description "Shock Lobster (World) (Aftermarket) (Homebrew)" - rom ( name "Shock Lobster (World) (Aftermarket) (Homebrew).gb" size 32768 crc 7a0622e6 sha1 10c1816724cd6e332d2a5aeed4e546e7bd764ebf ) + name "Shock Lobster (World) (Aftermarket) (Unl)" + description "Shock Lobster (World) (Aftermarket) (Unl)" + rom ( name "Shock Lobster (World) (Aftermarket) (Unl).gb" size 32768 crc 7a0622e6 sha1 10c1816724cd6e332d2a5aeed4e546e7bd764ebf ) ) game ( - name "Shock Lobster (World) (v1.3) (Aftermarket) (Homebrew)" - description "Shock Lobster (World) (v1.3) (Aftermarket) (Homebrew)" - rom ( name "Shock Lobster (World) (v1.3) (Aftermarket) (Homebrew).gb" size 32768 crc 8f244e86 sha1 7b8c8a9ec4758c6880598b9d508bc5805293dd87 ) + name "Shock Lobster (World) (v1.3) (Aftermarket) (Unl)" + description "Shock Lobster (World) (v1.3) (Aftermarket) (Unl)" + rom ( name "Shock Lobster (World) (v1.3) (Aftermarket) (Unl).gb" size 32768 crc 8f244e86 sha1 7b8c8a9ec4758c6880598b9d508bc5805293dd87 ) ) game ( - name "Shootris (World) (Aftermarket) (Homebrew)" - description "Shootris (World) (Aftermarket) (Homebrew)" - rom ( name "Shootris (World) (Aftermarket) (Homebrew).gb" size 262144 crc 50180f64 sha1 5d978e6fc4bd5d9717d022360122458e95fffc29 ) + name "Shootris (World) (Aftermarket) (Unl)" + description "Shootris (World) (Aftermarket) (Unl)" + rom ( name "Shootris (World) (Aftermarket) (Unl).gb" size 262144 crc 50180f64 sha1 5d978e6fc4bd5d9717d022360122458e95fffc29 ) ) game ( @@ -30323,9 +31046,15 @@ game ( ) game ( - name "Sloth Story (World) (v1.0) (Aftermarket) (Homebrew)" - description "Sloth Story (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Sloth Story (World) (v1.0) (Aftermarket) (Homebrew).gb" size 262144 crc c873f232 sha1 369d2afec4ffb59d4f3a59ee684c5bfec0b34ff8 ) + name "Sloth Story (World) (Aftermarket) (Unl)" + description "Sloth Story (World) (Aftermarket) (Unl)" + rom ( name "Sloth Story (World) (Aftermarket) (Unl).gb" size 262144 crc c873f232 sha1 369d2afec4ffb59d4f3a59ee684c5bfec0b34ff8 ) +) + +game ( + name "Sludge & Sorcery (World) (Aftermarket) (Unl)" + description "Sludge & Sorcery (World) (Aftermarket) (Unl)" + rom ( name "Sludge & Sorcery (World) (Aftermarket) (Unl).gb" size 1048576 crc c906c5af sha1 0e0d472b282e3ea590e0f126baf633ef0d9298fa ) ) game ( @@ -30335,15 +31064,15 @@ game ( ) game ( - name "SMARTCOM (Europe) (Unl)" - description "SMARTCOM (Europe) (Unl)" - rom ( name "SMARTCOM (Europe) (Unl).gb" size 262144 crc 1ef0abf1 sha1 a04bbf813bcd9b7e3af97d388ad77d25c1403831 ) + name "SmartCom (Europe) (Unl)" + description "SmartCom (Europe) (Unl)" + rom ( name "SmartCom (Europe) (Unl).gb" size 262144 crc 1ef0abf1 sha1 a04bbf813bcd9b7e3af97d388ad77d25c1403831 ) ) game ( name "Smurfs Nightmare, The (Europe) (En,Fr,De,Es)" description "Smurfs Nightmare, The (Europe) (En,Fr,De,Es)" - rom ( name "Smurfs Nightmare, The (Europe) (En,Fr,De,Es).gb" size 262144 crc 8ef938c4 sha1 efbc4bc9714393d5f493ad653fc1fa2180b40892 ) + rom ( name "Smurfs Nightmare, The (Europe) (En,Fr,De,Es).gb" size 262144 crc 8ef938c4 sha1 efbc4bc9714393d5f493ad653fc1fa2180b40892 flags verified ) ) game ( @@ -30365,21 +31094,21 @@ game ( ) game ( - name "Snail DX, The (World) (v1.0) (Aftermarket) (Homebrew)" - description "Snail DX, The (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Snail DX, The (World) (v1.0) (Aftermarket) (Homebrew).gb" size 262144 crc 1aecd096 sha1 8ce256d963a55badf71fd45047c173a74d76a92c ) + name "Snail DX, The (World) (Aftermarket) (Unl)" + description "Snail DX, The (World) (Aftermarket) (Unl)" + rom ( name "Snail DX, The (World) (Aftermarket) (Unl).gb" size 262144 crc 1aecd096 sha1 8ce256d963a55badf71fd45047c173a74d76a92c ) ) game ( - name "Snail, The (World) (v1.3) (Aftermarket) (Homebrew)" - description "Snail, The (World) (v1.3) (Aftermarket) (Homebrew)" - rom ( name "Snail, The (World) (v1.3) (Aftermarket) (Homebrew).gb" size 262144 crc 422a7b44 sha1 776f7578d747f935ebccda93d6487ffa4dc4bce3 ) + name "Snail, The (World) (v1.3) (Aftermarket) (Unl)" + description "Snail, The (World) (v1.3) (Aftermarket) (Unl)" + rom ( name "Snail, The (World) (v1.3) (Aftermarket) (Unl).gb" size 262144 crc 422a7b44 sha1 776f7578d747f935ebccda93d6487ffa4dc4bce3 ) ) game ( - name "Snakebird (World) (Aftermarket) (Homebrew)" - description "Snakebird (World) (Aftermarket) (Homebrew)" - rom ( name "Snakebird (World) (Aftermarket) (Homebrew).gb" size 131072 crc 4627f98e sha1 99cc82279d65b86b4366bf61db9bf67a78e051dc ) + name "Snakebird (World) (Aftermarket) (Unl)" + description "Snakebird (World) (Aftermarket) (Unl)" + rom ( name "Snakebird (World) (Aftermarket) (Unl).gb" size 131072 crc 4627f98e sha1 99cc82279d65b86b4366bf61db9bf67a78e051dc ) ) game ( @@ -30407,9 +31136,9 @@ game ( ) game ( - name "Snooze (World) (Aftermarket) (Homebrew)" - description "Snooze (World) (Aftermarket) (Homebrew)" - rom ( name "Snooze (World) (Aftermarket) (Homebrew).gb" size 262144 crc b246095f sha1 b7fdcc006c9c06dd391ee26d44b278d9d3793f6d ) + name "Snooze (World) (Aftermarket) (Unl)" + description "Snooze (World) (Aftermarket) (Unl)" + rom ( name "Snooze (World) (Aftermarket) (Unl).gb" size 262144 crc b246095f sha1 b7fdcc006c9c06dd391ee26d44b278d9d3793f6d ) ) game ( @@ -30497,9 +31226,9 @@ game ( ) game ( - name "Sonic 6 (Unknown) (En) (Pirate)" - description "Sonic 6 (Unknown) (En) (Pirate)" - rom ( name "Sonic 6 (Unknown) (En) (Pirate).gb" size 524288 crc aa4e9379 sha1 5f70ee1a1265536c6ed3657bfd5e54516add287d ) + name "Sonic 6 (USA) (Pirate)" + description "Sonic 6 (USA) (Pirate)" + rom ( name "Sonic 6 (USA) (Pirate).gb" size 524288 crc aa4e9379 sha1 5f70ee1a1265536c6ed3657bfd5e54516add287d ) ) game ( @@ -30581,9 +31310,9 @@ game ( ) game ( - name "Speedball 2 - Brutal Deluxe (Japan) (En)" - description "Speedball 2 - Brutal Deluxe (Japan) (En)" - rom ( name "Speedball 2 - Brutal Deluxe (Japan) (En).gb" size 131072 crc d0e0116f sha1 2b9bc1e7eadf90611112463053a9915c8159c3bc ) + name "Speedball 2 - Brutal Deluxe (Japan) (En) (Possible Proto)" + description "Speedball 2 - Brutal Deluxe (Japan) (En) (Possible Proto)" + rom ( name "Speedball 2 - Brutal Deluxe (Japan) (En) (Possible Proto).gb" size 131072 crc d0e0116f sha1 2b9bc1e7eadf90611112463053a9915c8159c3bc ) ) game ( @@ -30622,6 +31351,12 @@ game ( rom ( name "Spider-Man 3 - Invasion of the Spider-Slayers (USA, Europe) (Beta 2) (1993-04-11).gb" size 131072 crc bf2d1f10 sha1 34740dc60663ef33c42573a26e7b207f2cfe4a16 ) ) +game ( + name "Spiky Harold (World) (Aftermarket) (Unl)" + description "Spiky Harold (World) (Aftermarket) (Unl)" + rom ( name "Spiky Harold (World) (Aftermarket) (Unl).gb" size 524288 crc 2ac1b945 sha1 b8f37a64524bde4c2b6e9a1938ed6e5c0a5aad7c ) +) + game ( name "Spirit of F-1, The (Europe)" description "Spirit of F-1, The (Europe)" @@ -30821,9 +31556,9 @@ game ( ) game ( - name "StarFox - Grounded (World) (Aftermarket) (Homebrew)" - description "StarFox - Grounded (World) (Aftermarket) (Homebrew)" - rom ( name "StarFox - Grounded (World) (Aftermarket) (Homebrew).gb" size 524288 crc 82658661 sha1 dd45b0e3f346ce33668d49092b29a44597686061 ) + name "StarFox - Grounded (World) (Aftermarket) (Unl)" + description "StarFox - Grounded (World) (Aftermarket) (Unl)" + rom ( name "StarFox - Grounded (World) (Aftermarket) (Unl).gb" size 524288 crc 82658661 sha1 dd45b0e3f346ce33668d49092b29a44597686061 ) ) game ( @@ -30880,6 +31615,12 @@ game ( rom ( name "Street Racer (USA, Europe).gb" size 131072 crc fcfb4ce4 sha1 4e1d17772eb939cb0a3bce73e4378384d96836ce flags verified ) ) +game ( + name "Suicide Run (World) (Aftermarket) (Unl)" + description "Suicide Run (World) (Aftermarket) (Unl)" + rom ( name "Suicide Run (World) (Aftermarket) (Unl).gb" size 262144 crc d29ab1aa sha1 df373f58e51155e32fb81825a9082150f6d263c8 ) +) + game ( name "Sumo Fighter (USA)" description "Sumo Fighter (USA)" @@ -30899,9 +31640,9 @@ game ( ) game ( - name "Super 69 in 1 (World) (Unl)" - description "Super 69 in 1 (World) (Unl)" - rom ( name "Super 69 in 1 (World) (Unl).gb" size 32768 crc 9cf64fb6 sha1 91e7012c1eeeea280117c245b031400d060bd978 ) + name "Super 69 in 1 (Europe) (Unl)" + description "Super 69 in 1 (Europe) (Unl)" + rom ( name "Super 69 in 1 (Europe) (Unl).gb" size 32768 crc 9cf64fb6 sha1 91e7012c1eeeea280117c245b031400d060bd978 ) ) game ( @@ -31007,9 +31748,9 @@ game ( ) game ( - name "Super Covid Go Get Biscuits Adventure (World) (Aftermarket) (Homebrew)" - description "Super Covid Go Get Biscuits Adventure (World) (Aftermarket) (Homebrew)" - rom ( name "Super Covid Go Get Biscuits Adventure (World) (Aftermarket) (Homebrew).gb" size 262144 crc 51946e8d sha1 cac4917d56da02cf88ba68e0c5189a18c2c234ac ) + name "Super Covid Go Get Biscuits Adventure (World) (Aftermarket) (Unl)" + description "Super Covid Go Get Biscuits Adventure (World) (Aftermarket) (Unl)" + rom ( name "Super Covid Go Get Biscuits Adventure (World) (Aftermarket) (Unl).gb" size 262144 crc 51946e8d sha1 cac4917d56da02cf88ba68e0c5189a18c2c234ac ) ) game ( @@ -31043,9 +31784,9 @@ game ( ) game ( - name "Super Imposter Bros. (World) (Aftermarket) (Homebrew)" - description "Super Imposter Bros. (World) (Aftermarket) (Homebrew)" - rom ( name "Super Imposter Bros. (World) (Aftermarket) (Homebrew).gb" size 1048576 crc a38d702f sha1 301f93da4e5b0cd8dc3f1bf3e66e38176b8a909b ) + name "Super Imposter Bros. (World) (Aftermarket) (Unl)" + description "Super Imposter Bros. (World) (Aftermarket) (Unl)" + rom ( name "Super Imposter Bros. (World) (Aftermarket) (Unl).gb" size 1048576 crc a38d702f sha1 301f93da4e5b0cd8dc3f1bf3e66e38176b8a909b ) ) game ( @@ -31211,9 +31952,9 @@ game ( ) game ( - name "Sushi Gun (World) (Aftermarket) (Homebrew)" - description "Sushi Gun (World) (Aftermarket) (Homebrew)" - rom ( name "Sushi Gun (World) (Aftermarket) (Homebrew).gb" size 262144 crc a9d405d6 sha1 c075856939d4fe3cd250083b527b3e09be70052d ) + name "Sushi Gun (World) (Aftermarket) (Unl)" + description "Sushi Gun (World) (Aftermarket) (Unl)" + rom ( name "Sushi Gun (World) (Aftermarket) (Unl).gb" size 262144 crc a9d405d6 sha1 c075856939d4fe3cd250083b527b3e09be70052d ) ) game ( @@ -31241,9 +31982,9 @@ game ( ) game ( - name "SWAPLATFORMER (World) (Aftermarket) (Homebrew)" - description "SWAPLATFORMER (World) (Aftermarket) (Homebrew)" - rom ( name "SWAPLATFORMER (World) (Aftermarket) (Homebrew).gb" size 1048576 crc babe7935 sha1 3df0c37862b7c391a0d091a55db0d305341529c2 ) + name "SWAPLATFORMER (World) (Aftermarket) (Unl)" + description "SWAPLATFORMER (World) (Aftermarket) (Unl)" + rom ( name "SWAPLATFORMER (World) (Aftermarket) (Unl).gb" size 1048576 crc babe7935 sha1 3df0c37862b7c391a0d091a55db0d305341529c2 ) ) game ( @@ -31282,6 +32023,12 @@ game ( rom ( name "Sword of Hope, The (Europe) (Proto).gb" size 131072 crc c360f279 sha1 383ef07e04280f82da374852d04e7f98e9a00ae1 ) ) +game ( + name "Swordbird Song - The Iron Owl Tower (World) (v3.1) (Aftermarket) (Unl)" + description "Swordbird Song - The Iron Owl Tower (World) (v3.1) (Aftermarket) (Unl)" + rom ( name "Swordbird Song - The Iron Owl Tower (World) (v3.1) (Aftermarket) (Unl).gb" size 1048576 crc 64921bb1 sha1 b36ec89e0299c19767cc5c6467fa3664abbc3017 ) +) + game ( name "T2 - The Arcade Game (Japan)" description "T2 - The Arcade Game (Japan)" @@ -31348,6 +32095,12 @@ game ( rom ( name "Takeda Nobuhiro no Ace Striker (Japan).gb" size 262144 crc 7ff546df sha1 e354310c9946f48eb325c070e9c2c5ccb2e9f429 ) ) +game ( + name "Tales of Monsterland (World) (v2.83) (Aftermarket) (Unl)" + description "Tales of Monsterland (World) (v2.83) (Aftermarket) (Unl)" + rom ( name "Tales of Monsterland (World) (v2.83) (Aftermarket) (Unl).gb" size 2097152 crc a685f89a sha1 15a709d4300464b03ede4d774ce69cfb699c0fcf ) +) + game ( name "TaleSpin (Europe)" description "TaleSpin (Europe)" @@ -31415,9 +32168,9 @@ game ( ) game ( - name "Tech and Blood (World) (Aftermarket) (Homebrew)" - description "Tech and Blood (World) (Aftermarket) (Homebrew)" - rom ( name "Tech and Blood (World) (Aftermarket) (Homebrew).gb" size 262144 crc 3da2a358 sha1 40a00fce49b16cb36793df30e5156e5bf3e47778 ) + name "Tech and Blood (World) (Aftermarket) (Unl)" + description "Tech and Blood (World) (Aftermarket) (Unl)" + rom ( name "Tech and Blood (World) (Aftermarket) (Unl).gb" size 262144 crc 3da2a358 sha1 40a00fce49b16cb36793df30e5156e5bf3e47778 ) ) game ( @@ -31462,6 +32215,18 @@ game ( rom ( name "Teenage Mutant Ninja Turtles (Japan).gb" size 131072 crc 74078236 sha1 1f17169d70365831537376beb2477374649f830b ) ) +game ( + name "Teenage Mutant Ninja Turtles (Japan) (Cowabunga Collection, The)" + description "Teenage Mutant Ninja Turtles (Japan) (Cowabunga Collection, The)" + rom ( name "Teenage Mutant Ninja Turtles (Japan) (Cowabunga Collection, The).gb" size 131072 crc b3f6eec2 sha1 8e5398a96523b83da1ed0600f57a774cde7729a7 ) +) + +game ( + name "Teenage Mutant Ninja Turtles - Fall of the Foot Clan (USA) (Cowabunga Collection, The)" + description "Teenage Mutant Ninja Turtles - Fall of the Foot Clan (USA) (Cowabunga Collection, The)" + rom ( name "Teenage Mutant Ninja Turtles - Fall of the Foot Clan (USA) (Cowabunga Collection, The).gb" size 131072 crc 3b11a875 sha1 d20ba3c691a484bfcc80331e11bb76f652b4290c ) +) + game ( name "Teenage Mutant Ninja Turtles - Fall of the Foot Clan (USA)" description "Teenage Mutant Ninja Turtles - Fall of the Foot Clan (USA)" @@ -31480,6 +32245,18 @@ game ( rom ( name "Teenage Mutant Ninja Turtles 2 (Japan) (En) (Beta) (1991-05-20).gb" size 262144 crc 64a667a3 sha1 864a686f74a22ff8d27da511b08bbfbef1bd563d ) ) +game ( + name "Teenage Mutant Ninja Turtles 2 (Japan) (Cowabunga Collection, The)" + description "Teenage Mutant Ninja Turtles 2 (Japan) (Cowabunga Collection, The)" + rom ( name "Teenage Mutant Ninja Turtles 2 (Japan) (Cowabunga Collection, The).gb" size 262144 crc 3e83abd3 sha1 a05025f1840f27d4a12574fd6ee60e0b42c68ed8 ) +) + +game ( + name "Teenage Mutant Ninja Turtles 3 - Turtles Kikiippatsu (Japan) (Cowabunga Collection, The)" + description "Teenage Mutant Ninja Turtles 3 - Turtles Kikiippatsu (Japan) (Cowabunga Collection, The)" + rom ( name "Teenage Mutant Ninja Turtles 3 - Turtles Kikiippatsu (Japan) (Cowabunga Collection, The).gb" size 131072 crc cc91ab3b sha1 fc94ee81585583a39542b31a80c05f176da8b9e2 ) +) + game ( name "Teenage Mutant Ninja Turtles 3 - Turtles Kikiippatsu (Japan)" description "Teenage Mutant Ninja Turtles 3 - Turtles Kikiippatsu (Japan)" @@ -31498,6 +32275,18 @@ game ( rom ( name "Teenage Mutant Ninja Turtles II - Back from the Sewers (USA) (Beta).gb" size 262144 crc ee25b752 sha1 7fd1df99ed3bc088c737f0ef8b7f0caa95d73b65 ) ) +game ( + name "Teenage Mutant Ninja Turtles II - Back from the Sewers (USA) (Cowabunga Collection, The)" + description "Teenage Mutant Ninja Turtles II - Back from the Sewers (USA) (Cowabunga Collection, The)" + rom ( name "Teenage Mutant Ninja Turtles II - Back from the Sewers (USA) (Cowabunga Collection, The).gb" size 262144 crc 78b901d5 sha1 41c8ed509ec914e50f7e28601e0f6e5736de90fb ) +) + +game ( + name "Teenage Mutant Ninja Turtles III - Radical Rescue (USA) (Cowabunga Collection, The)" + description "Teenage Mutant Ninja Turtles III - Radical Rescue (USA) (Cowabunga Collection, The)" + rom ( name "Teenage Mutant Ninja Turtles III - Radical Rescue (USA) (Cowabunga Collection, The).gb" size 131072 crc 3a4d4b09 sha1 ade406d5ec0f3253c4d3be3b7507e027098f04a3 ) +) + game ( name "Teenage Mutant Ninja Turtles III - Radical Rescue (USA)" description "Teenage Mutant Ninja Turtles III - Radical Rescue (USA)" @@ -31637,15 +32426,15 @@ game ( ) game ( - name "There's Nothing To Do In This Town (World) (v1.0) (Aftermarket) (Homebrew)" - description "There's Nothing To Do In This Town (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "There's Nothing To Do In This Town (World) (v1.0) (Aftermarket) (Homebrew).gb" size 1048576 crc 173b549a sha1 e000fbecda9ea6bbbd0c0370cf8c3faaa54bf64e ) + name "There's Nothing To Do In This Town (World) (Aftermarket) (Unl)" + description "There's Nothing To Do In This Town (World) (Aftermarket) (Unl)" + rom ( name "There's Nothing To Do In This Town (World) (Aftermarket) (Unl).gb" size 1048576 crc 173b549a sha1 e000fbecda9ea6bbbd0c0370cf8c3faaa54bf64e ) ) game ( - name "Thin Ice Rescue, The (World) (Aftermarket) (Homebrew)" - description "Thin Ice Rescue, The (World) (Aftermarket) (Homebrew)" - rom ( name "Thin Ice Rescue, The (World) (Aftermarket) (Homebrew).gb" size 32768 crc 3e0483d6 sha1 b789ced0fe26785e7bce765a9d6ee783f9d967d8 ) + name "Thin Ice Rescue, The (World) (Aftermarket) (Unl)" + description "Thin Ice Rescue, The (World) (Aftermarket) (Unl)" + rom ( name "Thin Ice Rescue, The (World) (Aftermarket) (Unl).gb" size 32768 crc 3e0483d6 sha1 b789ced0fe26785e7bce765a9d6ee783f9d967d8 ) ) game ( @@ -31673,15 +32462,15 @@ game ( ) game ( - name "Tiny Grasshopper Goes Away (World) (v1.4) (Aftermarket) (Homebrew)" - description "Tiny Grasshopper Goes Away (World) (v1.4) (Aftermarket) (Homebrew)" - rom ( name "Tiny Grasshopper Goes Away (World) (v1.4) (Aftermarket) (Homebrew).gb" size 262144 crc af13e718 sha1 307e51664db2101755b6c617f3d81ee58676c485 ) + name "Tiny Grasshopper Goes Away (World) (En,Hu) (v1.4) (Aftermarket) (Unl)" + description "Tiny Grasshopper Goes Away (World) (En,Hu) (v1.4) (Aftermarket) (Unl)" + rom ( name "Tiny Grasshopper Goes Away (World) (En,Hu) (v1.4) (Aftermarket) (Unl).gb" size 262144 crc af13e718 sha1 307e51664db2101755b6c617f3d81ee58676c485 ) ) game ( - name "Tiny Grasshopper Goes Away (World) (v1.2) (Aftermarket) (Homebrew)" - description "Tiny Grasshopper Goes Away (World) (v1.2) (Aftermarket) (Homebrew)" - rom ( name "Tiny Grasshopper Goes Away (World) (v1.2) (Aftermarket) (Homebrew).gb" size 262144 crc 0518f5aa sha1 85e5cd0cf7a6cc4a5946c93fa8665235a686e554 ) + name "Tiny Grasshopper Goes Away (World) (En,Hu) (v1.2) (Aftermarket) (Unl)" + description "Tiny Grasshopper Goes Away (World) (En,Hu) (v1.2) (Aftermarket) (Unl)" + rom ( name "Tiny Grasshopper Goes Away (World) (En,Hu) (v1.2) (Aftermarket) (Unl).gb" size 262144 crc 0518f5aa sha1 85e5cd0cf7a6cc4a5946c93fa8665235a686e554 ) ) game ( @@ -31751,9 +32540,9 @@ game ( ) game ( - name "Tobu Tobu Girl (World) (Aftermarket) (Homebrew)" - description "Tobu Tobu Girl (World) (Aftermarket) (Homebrew)" - rom ( name "Tobu Tobu Girl (World) (Aftermarket) (Homebrew).gb" size 262144 crc ed12be6c sha1 8a8f3c1f21f903ea5a7df8fc8b0a6aa5a602e150 ) + name "Tobu Tobu Girl (World) (Aftermarket) (Unl)" + description "Tobu Tobu Girl (World) (Aftermarket) (Unl)" + rom ( name "Tobu Tobu Girl (World) (Aftermarket) (Unl).gb" size 262144 crc ed12be6c sha1 8a8f3c1f21f903ea5a7df8fc8b0a6aa5a602e150 ) ) game ( @@ -31921,13 +32710,13 @@ game ( game ( name "Trax (USA, Europe)" description "Trax (USA, Europe)" - rom ( name "Trax (USA, Europe).gb" size 131072 crc 4a38be7d sha1 3f3a31ed7e47319c5815dd6e31ca27a52377423c ) + rom ( name "Trax (USA, Europe).gb" size 131072 crc 4a38be7d sha1 3f3a31ed7e47319c5815dd6e31ca27a52377423c flags verified ) ) game ( - name "Treasure Island (World) (Aftermarket) (Homebrew)" - description "Treasure Island (World) (Aftermarket) (Homebrew)" - rom ( name "Treasure Island (World) (Aftermarket) (Homebrew).gb" size 262144 crc cbdec393 sha1 adeaba2723d286d143e0987f51ce9c8ad2f5e839 ) + name "Treasure Island (World) (Aftermarket) (Unl)" + description "Treasure Island (World) (Aftermarket) (Unl)" + rom ( name "Treasure Island (World) (Aftermarket) (Unl).gb" size 262144 crc cbdec393 sha1 adeaba2723d286d143e0987f51ce9c8ad2f5e839 ) ) game ( @@ -31955,9 +32744,9 @@ game ( ) game ( - name "Trouble City - Pocket Mission (World) (Aftermarket) (Homebrew)" - description "Trouble City - Pocket Mission (World) (Aftermarket) (Homebrew)" - rom ( name "Trouble City - Pocket Mission (World) (Aftermarket) (Homebrew).gb" size 524288 crc 4b9143ad sha1 0e51e398d44aaad74a7c622f7818b56a3b34db56 ) + name "Trouble City - Pocket Mission (World) (Aftermarket) (Unl)" + description "Trouble City - Pocket Mission (World) (Aftermarket) (Unl)" + rom ( name "Trouble City - Pocket Mission (World) (Aftermarket) (Unl).gb" size 524288 crc 4b9143ad sha1 0e51e398d44aaad74a7c622f7818b56a3b34db56 ) ) game ( @@ -32177,9 +32966,15 @@ game ( ) game ( - name "Ungrateful Son, The (World) (Aftermarket) (Homebrew)" - description "Ungrateful Son, The (World) (Aftermarket) (Homebrew)" - rom ( name "Ungrateful Son, The (World) (Aftermarket) (Homebrew).gb" size 262144 crc 977b54f4 sha1 016dde406896d827dcad682d40b52d0e9eeefa88 ) + name "Unearthed (World) (v1.3) (Aftermarket) (Unl)" + description "Unearthed (World) (v1.3) (Aftermarket) (Unl)" + rom ( name "Unearthed (World) (v1.3) (Aftermarket) (Unl).gb" size 2097152 crc 29bb9238 sha1 39fbfb220e9be0120090f770249049b3f3583f64 ) +) + +game ( + name "Ungrateful Son, The (World) (v1.1) (Aftermarket) (Unl)" + description "Ungrateful Son, The (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Ungrateful Son, The (World) (v1.1) (Aftermarket) (Unl).gb" size 262144 crc 977b54f4 sha1 016dde406896d827dcad682d40b52d0e9eeefa88 ) ) game ( @@ -32224,6 +33019,12 @@ game ( rom ( name "V-Rally - Championship Edition (Europe) (En,Fr,De).gb" size 262144 crc d149652b sha1 67d2a6e41b6a1d8372808535ac8c8222d7c53480 flags verified ) ) +game ( + name "Vampire Night Shift (World) (Aftermarket) (Unl)" + description "Vampire Night Shift (World) (Aftermarket) (Unl)" + rom ( name "Vampire Night Shift (World) (Aftermarket) (Unl).gb" size 262144 crc 88acbc1a sha1 ef617803c2cdb14cfe3b3b2111aa239a3744e29e ) +) + game ( name "Vattle Giuce (Japan)" description "Vattle Giuce (Japan)" @@ -32279,9 +33080,9 @@ game ( ) game ( - name "Waifu Clicker (World) (Aftermarket) (Homebrew)" - description "Waifu Clicker (World) (Aftermarket) (Homebrew)" - rom ( name "Waifu Clicker (World) (Aftermarket) (Homebrew).gb" size 65536 crc 155f85dc sha1 bb571ed69c3a33e45ba33fbb03a066ba98ae0b20 ) + name "Waifu Clicker (World) (Aftermarket) (Unl)" + description "Waifu Clicker (World) (Aftermarket) (Unl)" + rom ( name "Waifu Clicker (World) (Aftermarket) (Unl).gb" size 65536 crc 155f85dc sha1 bb571ed69c3a33e45ba33fbb03a066ba98ae0b20 ) ) game ( @@ -32303,21 +33104,21 @@ game ( ) game ( - name "Warp Coin Catastrophe, The (World) (v1.1) (Aftermarket) (Homebrew)" - description "Warp Coin Catastrophe, The (World) (v1.1) (Aftermarket) (Homebrew)" - rom ( name "Warp Coin Catastrophe, The (World) (v1.1) (Aftermarket) (Homebrew).gb" size 1048576 crc a381c914 sha1 adf37c5d2f706743b2a4946378df49ed19212039 ) + name "Warp Coin Catastrophe, The (World) (v1.1) (Aftermarket) (Unl)" + description "Warp Coin Catastrophe, The (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Warp Coin Catastrophe, The (World) (v1.1) (Aftermarket) (Unl).gb" size 1048576 crc a381c914 sha1 adf37c5d2f706743b2a4946378df49ed19212039 ) ) game ( - name "Warp Coin Catastrophe, The (World) (v1.0) (Aftermarket) (Homebrew)" - description "Warp Coin Catastrophe, The (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Warp Coin Catastrophe, The (World) (v1.0) (Aftermarket) (Homebrew).gb" size 1048576 crc 1bfe1bf9 sha1 fae12dbbb75ae024e96a7ee21ad4077fdb5ed9a1 ) + name "Warp Coin Catastrophe, The (World) (v1.0) (Aftermarket) (Unl)" + description "Warp Coin Catastrophe, The (World) (v1.0) (Aftermarket) (Unl)" + rom ( name "Warp Coin Catastrophe, The (World) (v1.0) (Aftermarket) (Unl).gb" size 1048576 crc 1bfe1bf9 sha1 fae12dbbb75ae024e96a7ee21ad4077fdb5ed9a1 ) ) game ( - name "Warp Coin Catastrophe, The (World) (v1.1.2) (Aftermarket) (Homebrew)" - description "Warp Coin Catastrophe, The (World) (v1.1.2) (Aftermarket) (Homebrew)" - rom ( name "Warp Coin Catastrophe, The (World) (v1.1.2) (Aftermarket) (Homebrew).gb" size 1048576 crc 5519c167 sha1 04a53af6a77b884af91f8047d70fc0331bf23913 ) + name "Warp Coin Catastrophe, The (World) (v1.1.2) (Aftermarket) (Unl)" + description "Warp Coin Catastrophe, The (World) (v1.1.2) (Aftermarket) (Unl)" + rom ( name "Warp Coin Catastrophe, The (World) (v1.1.2) (Aftermarket) (Unl).gb" size 1048576 crc 5519c167 sha1 04a53af6a77b884af91f8047d70fc0331bf23913 ) ) game ( @@ -32362,6 +33163,12 @@ game ( rom ( name "Welcome Nakayoshi Park (Japan).gb" size 262144 crc f6e2baae sha1 8779a1557ba0de3c5c8b770413f30bd31717b2ef ) ) +game ( + name "What's Updog (World) (Aftermarket) (Unl)" + description "What's Updog (World) (Aftermarket) (Unl)" + rom ( name "What's Updog (World) (Aftermarket) (Unl).gb" size 262144 crc 2c3d5cbf sha1 a235c0915ac2b826eeb5884d0eccd321236fdb0c ) +) + game ( name "Wheel of Fortune (USA)" description "Wheel of Fortune (USA)" @@ -32398,6 +33205,12 @@ game ( rom ( name "Wily & Right no Rockboard - That's Paradise (Japan) (Proto).gb" size 262144 crc af84dc97 sha1 cc917f113c5cdd1afc3340feb8bf2094d0902cec ) ) +game ( + name "Windows93 Adventure (World) (Aftermarket) (Unl)" + description "Windows93 Adventure (World) (Aftermarket) (Unl)" + rom ( name "Windows93 Adventure (World) (Aftermarket) (Unl).gb" size 1048576 crc e1ad0b6f sha1 3ad7327f6f94976c3d12054cf6deb2666ba9fa66 ) +) + game ( name "Winner's Horse (Japan)" description "Winner's Horse (Japan)" @@ -32423,9 +33236,9 @@ game ( ) game ( - name "Wizard of Wor (World) (Aftermarket) (Homebrew)" - description "Wizard of Wor (World) (Aftermarket) (Homebrew)" - rom ( name "Wizard of Wor (World) (Aftermarket) (Homebrew).gb" size 524288 crc bfb1563e sha1 108eefa154412e3422dce4e63bf34f5826ec1baa ) + name "Wizard of Wor (World) (Aftermarket) (Unl)" + description "Wizard of Wor (World) (Aftermarket) (Unl)" + rom ( name "Wizard of Wor (World) (Aftermarket) (Unl).gb" size 524288 crc bfb1563e sha1 108eefa154412e3422dce4e63bf34f5826ec1baa ) ) game ( @@ -32453,9 +33266,9 @@ game ( ) game ( - name "Woolball's Backyard (World) (Aftermarket) (Homebrew)" - description "Woolball's Backyard (World) (Aftermarket) (Homebrew)" - rom ( name "Woolball's Backyard (World) (Aftermarket) (Homebrew).gb" size 65536 crc 69e26eb6 sha1 28795c4b829440549b3ac2afe3282d85a439f7d7 ) + name "Woolball's Backyard (World) (Aftermarket) (Unl)" + description "Woolball's Backyard (World) (Aftermarket) (Unl)" + rom ( name "Woolball's Backyard (World) (Aftermarket) (Unl).gb" size 65536 crc 69e26eb6 sha1 28795c4b829440549b3ac2afe3282d85a439f7d7 ) ) game ( @@ -32687,27 +33500,27 @@ game ( ) game ( - name "Year After, The (USA) (Aftermarket) (Homebrew)" - description "Year After, The (USA) (Aftermarket) (Homebrew)" - rom ( name "Year After, The (USA) (Aftermarket) (Homebrew).gb" size 1048576 crc 4e463a1a sha1 5d84f53f38aa24f54a6f897ca69ffc5d978805af ) + name "Year After, The (World) (Beta 2) (Aftermarket) (Unl)" + description "Year After, The (World) (Beta 2) (Aftermarket) (Unl)" + rom ( name "Year After, The (World) (Beta 2) (Aftermarket) (Unl).gb" size 1048576 crc 4e463a1a sha1 5d84f53f38aa24f54a6f897ca69ffc5d978805af ) ) game ( - name "Year After, The (World) (Beta) (Aftermarket) (Homebrew)" - description "Year After, The (World) (Beta) (Aftermarket) (Homebrew)" - rom ( name "Year After, The (World) (Beta) (Aftermarket) (Homebrew).gb" size 1048576 crc f15351c8 sha1 a78ff2eef780287bbdc37df122cfc1c504526662 ) + name "Year After, The (World) (Beta) (Aftermarket) (Unl)" + description "Year After, The (World) (Beta) (Aftermarket) (Unl)" + rom ( name "Year After, The (World) (Beta) (Aftermarket) (Unl).gb" size 1048576 crc f15351c8 sha1 a78ff2eef780287bbdc37df122cfc1c504526662 ) ) game ( - name "Year After, The (World) (Pt) (Beta) (Aftermarket) (Homebrew)" - description "Year After, The (World) (Pt) (Beta) (Aftermarket) (Homebrew)" - rom ( name "Year After, The (World) (Pt) (Beta) (Aftermarket) (Homebrew).gb" size 1048576 crc b70c1c7f sha1 ce25cdf6a6264586423e76e34dc42779d39a1cb1 ) + name "Year After, The (World) (Pt) (Beta) (Aftermarket) (Unl)" + description "Year After, The (World) (Pt) (Beta) (Aftermarket) (Unl)" + rom ( name "Year After, The (World) (Pt) (Beta) (Aftermarket) (Unl).gb" size 1048576 crc b70c1c7f sha1 ce25cdf6a6264586423e76e34dc42779d39a1cb1 ) ) game ( - name "Year After, The (World) (Fr) (Beta) (Aftermarket) (Homebrew)" - description "Year After, The (World) (Fr) (Beta) (Aftermarket) (Homebrew)" - rom ( name "Year After, The (World) (Fr) (Beta) (Aftermarket) (Homebrew).gb" size 1048576 crc fbc50ee8 sha1 52c27e64fb37412c97a53d5dde52c3803fd1e4f7 ) + name "Year After, The (World) (Fr) (Beta) (Aftermarket) (Unl)" + description "Year After, The (World) (Fr) (Beta) (Aftermarket) (Unl)" + rom ( name "Year After, The (World) (Fr) (Beta) (Aftermarket) (Unl).gb" size 1048576 crc fbc50ee8 sha1 52c27e64fb37412c97a53d5dde52c3803fd1e4f7 ) ) game ( @@ -32795,9 +33608,15 @@ game ( ) game ( - name "Yuuto Ichika Makes Friends (World) (Aftermarket) (Homebrew)" - description "Yuuto Ichika Makes Friends (World) (Aftermarket) (Homebrew)" - rom ( name "Yuuto Ichika Makes Friends (World) (Aftermarket) (Homebrew).gb" size 524288 crc 8a667058 sha1 e765d6915f543a5a65e8be440b3f29eb674a5448 ) + name "Yuuto Ichika Makes Friends (World) (En,Ja) (Aftermarket) (Unl)" + description "Yuuto Ichika Makes Friends (World) (En,Ja) (Aftermarket) (Unl)" + rom ( name "Yuuto Ichika Makes Friends (World) (En,Ja) (Aftermarket) (Unl).gb" size 524288 crc 8a667058 sha1 e765d6915f543a5a65e8be440b3f29eb674a5448 ) +) + +game ( + name "Zagan Warrior (World) (Aftermarket) (Unl)" + description "Zagan Warrior (World) (Aftermarket) (Unl)" + rom ( name "Zagan Warrior (World) (Aftermarket) (Unl).gb" size 262144 crc 97fe7a94 sha1 88f76297a4ccf0e3a5ba16ca15237c88346c134a ) ) game ( @@ -32905,8 +33724,8 @@ game ( clrmamepro ( name "Nintendo - Game Boy Color" description "Nintendo - Game Boy Color" - version 20220829-075630 - author "akubi, Arctic Circle System, Aringon, Bent, BigFred, BitLooter, C. V. Reynolds, chillerecke, coraz, darthcloud, DeadSkullzJr, Densetsu, DeriLoko3, Flashfire42, foxe, fuzzball, Hiccup, hking0036, InternalLoss, kazumi213, Lesserkuma, Madeline, Money_114, NGEfreak, nnssxx, norkmetnoil577, NovaAurora, omonim2007, PPLToast, relax, Rifu, sCZther, SonGoku, Tauwasser, togemet2, UnlockerPT, Whovian9369, xuom2, zg" + version 20230422-225414 + author "akubi, Arctic Circle System, Aringon, baldjared, Bent, BigFred, BitLooter, C. V. Reynolds, chillerecke, coraz, darthcloud, DeadSkullzJr, Densetsu, DeriLoko3, Flashfire42, foxe, fuzzball, Gefflon, Hiccup, hking0036, InternalLoss, Just001Kim, kazumi213, Lesserkuma, Madeline, Money_114, NESBrew12, NGEfreak, nnssxx, norkmetnoil577, NovaAurora, omonim2007, PPLToast, rarenight, relax, Rifu, sCZther, SonGoku, Tauwasser, togemet2, UnlockerPT, Whovian9369, xuom2, zg" homepage No-Intro url "https://www.no-intro.org" forcenodump required @@ -33006,6 +33825,78 @@ game ( rom ( name "1942 (USA, Europe).gbc" size 1048576 crc 87431672 sha1 d960e951b18d07e79d046313df49c18313664224 ) ) +game ( + name "2002 Adventure Digimon 7 (Taiwan) (En) (Unl)" + description "2002 Adventure Digimon 7 (Taiwan) (En) (Unl)" + rom ( name "2002 Adventure Digimon 7 (Taiwan) (En) (Unl).gbc" size 2097152 crc 1bfc7099 sha1 532206800253fce0e81a0825ba49d60a7d6c5ca4 ) +) + +game ( + name "2002 Digimon Adventure 6 (Taiwan) (En) (Unl)" + description "2002 Digimon Adventure 6 (Taiwan) (En) (Unl)" + rom ( name "2002 Digimon Adventure 6 (Taiwan) (En) (Unl).gbc" size 1048576 crc 3f0d046b sha1 eea8928b277beb56ca8878b6dbfc094b0c3c5463 ) +) + +game ( + name "2003 Crash II Advance (USA) (Unl)" + description "2003 Crash II Advance (USA) (Unl)" + rom ( name "2003 Crash II Advance (USA) (Unl).gbc" size 2097152 crc 973d38a8 sha1 acdc42716f61bf11ecf08d50a980047f8fb7dc57 ) +) + +game ( + name "2003 Digimom Sapphii (Taiwan) (En) (Unl)" + description "2003 Digimom Sapphii (Taiwan) (En) (Unl)" + rom ( name "2003 Digimom Sapphii (Taiwan) (En) (Unl).gbc" size 2097152 crc 7cff9f0b sha1 1e42c461e0c6705637a698dbda8b34bc37acc51d ) +) + +game ( + name "2003 Gu Huo Lang II (Taiwan) (Unl)" + description "2003 Gu Huo Lang II (Taiwan) (Unl)" + rom ( name "2003 Gu Huo Lang II (Taiwan) (Unl).gbc" size 2097152 crc 74d71b0c sha1 fcbb769de0896ae69fe1063fa5d87cf079c8606a ) +) + +game ( + name "2003 Hali Bote 2 - Xiaoshi de Mishi (Taiwan) (Unl)" + description "2003 Hali Bote 2 - Xiaoshi de Mishi (Taiwan) (Unl)" + rom ( name "2003 Hali Bote 2 - Xiaoshi de Mishi (Taiwan) (Unl).gbc" size 2097152 crc 1b3e1243 sha1 0829cbcab3ef5d03c94d5efe769f4f4a93ac47fa ) +) + +game ( + name "2003 Hali Xiaozi IV (Taiwan) (Unl)" + description "2003 Hali Xiaozi IV (Taiwan) (Unl)" + rom ( name "2003 Hali Xiaozi IV (Taiwan) (Unl).gbc" size 2097152 crc db3c8b95 sha1 0d230b20388f4396d6345b8eb59f54fc169ad8ee ) +) + +game ( + name "2003 Harry Potter 3 (Taiwan) (Unl)" + description "2003 Harry Potter 3 (Taiwan) (Unl)" + rom ( name "2003 Harry Potter 3 (Taiwan) (Unl).gbc" size 1048576 crc 4ea2c869 sha1 739c0e5dc0efd4ddb912236a6f630c3d6987d064 ) +) + +game ( + name "2003 King Lion Advance III (USA) (Unl)" + description "2003 King Lion Advance III (USA) (Unl)" + rom ( name "2003 King Lion Advance III (USA) (Unl).gbc" size 2097152 crc 0c22466d sha1 7c43ccfca93cae23e04e58fa0d7d7400a9694e0e ) +) + +game ( + name "2003 Koudai Guaishou - Lanbaoshi (Taiwan) (Unl)" + description "2003 Koudai Guaishou - Lanbaoshi (Taiwan) (Unl)" + rom ( name "2003 Koudai Guaishou - Lanbaoshi (Taiwan) (Unl).gbc" size 2097152 crc 4c76d4d8 sha1 d688201e6031b88f122ec753a37daedfb233b48d ) +) + +game ( + name "2003 Pocket Monster - Carbuncle (USA) (Unl)" + description "2003 Pocket Monster - Carbuncle (USA) (Unl)" + rom ( name "2003 Pocket Monster - Carbuncle (USA) (Unl).gbc" size 2097152 crc 3a0e9b6f sha1 025973627743f2f1ae1ff5b8a3f549cbcc227ef3 ) +) + +game ( + name "2003 Shuma Baolong - Gedou Ban (Taiwan) (Unl)" + description "2003 Shuma Baolong - Gedou Ban (Taiwan) (Unl)" + rom ( name "2003 Shuma Baolong - Gedou Ban (Taiwan) (Unl).gbc" size 2097152 crc 1219eec6 sha1 4a722c10e3893e04c67a66d0aea401d6d220ec7e ) +) + game ( name "23 in 1 (Taiwan) (Unl)" description "23 in 1 (Taiwan) (Unl)" @@ -33048,6 +33939,12 @@ game ( rom ( name "3D Pool Allstars (USA) (En,Fr,Es) (Proto).gbc" size 1048576 crc 245de3e2 sha1 f7289c3eed275286d0fb3dc7097098276b746786 ) ) +game ( + name "3D Quasars (World) (Aftermarket) (Unl)" + description "3D Quasars (World) (Aftermarket) (Unl)" + rom ( name "3D Quasars (World) (Aftermarket) (Unl).gbc" size 262144 crc 1fd94e67 sha1 3f32226538d8c7f0c866c4b73e9f828265f2bb5b ) +) + game ( name "4 in 1 + 8 in 1 (World) (4B-001, 4B-009, 8B-001, Sachen) (Unl)" description "4 in 1 + 8 in 1 (World) (4B-001, 4B-009, 8B-001, Sachen) (Unl)" @@ -33085,9 +33982,9 @@ game ( ) game ( - name "Aardvark (World) (Aftermarket) (Homebrew)" - description "Aardvark (World) (Aftermarket) (Homebrew)" - rom ( name "Aardvark (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 270d45b9 sha1 34a8d00af6be9083409e6fccd0825c6f185d135d flags verified ) + name "Aardvark (World) (Aftermarket) (Unl)" + description "Aardvark (World) (Aftermarket) (Unl)" + rom ( name "Aardvark (World) (Aftermarket) (Unl).gbc" size 262144 crc 270d45b9 sha1 34a8d00af6be9083409e6fccd0825c6f185d135d flags verified ) ) game ( @@ -33114,12 +34011,6 @@ game ( rom ( name "Action Replay Xtreme - Special Edition for Pokemon Crystal (Europe) (Unl).gbc" size 131072 crc c288e400 sha1 0280b05885fe5aca8c1884a16bd01513b99f4dc2 flags verified ) ) -game ( - name "Adventure Digimon 7 2002 (Taiwan) (En) (Unl)" - description "Adventure Digimon 7 2002 (Taiwan) (En) (Unl)" - rom ( name "Adventure Digimon 7 2002 (Taiwan) (En) (Unl).gbc" size 2097152 crc 1bfc7099 sha1 532206800253fce0e81a0825ba49d60a7d6c5ca4 ) -) - game ( name "Adventures of the Smurfs, The (Europe) (En,Fr,De,Es,It,Nl)" description "Adventures of the Smurfs, The (Europe) (En,Fr,De,Es,It,Nl)" @@ -33127,15 +34018,15 @@ game ( ) game ( - name "Agent B (World) (Aftermarket) (Homebrew)" - description "Agent B (World) (Aftermarket) (Homebrew)" - rom ( name "Agent B (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 31b6e3fa sha1 11b3ce59c4eb86cee05873eb473dbf262cf986bd ) + name "Agent B (World) (2021-12-29) (GB Compatible) (Aftermarket) (Unl)" + description "Agent B (World) (2021-12-29) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Agent B (World) (2021-12-29) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 31b6e3fa sha1 11b3ce59c4eb86cee05873eb473dbf262cf986bd ) ) game ( - name "Agent B (World) (Demo) (Aftermarket) (Homebrew)" - description "Agent B (World) (Demo) (Aftermarket) (Homebrew)" - rom ( name "Agent B (World) (Demo) (Aftermarket) (Homebrew).gbc" size 262144 crc ae767ad7 sha1 1a332aac66b24381f16819d1145fd4a917f2e0f3 ) + name "Agent B (World) (2021-12-29) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Agent B (World) (2021-12-29) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Agent B (World) (2021-12-29) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc ae767ad7 sha1 1a332aac66b24381f16819d1145fd4a917f2e0f3 ) ) game ( @@ -33216,6 +34107,24 @@ game ( rom ( name "All-Star Baseball 2001 (USA).gbc" size 1048576 crc bc562466 sha1 f702df64d368b763187a5b1263a60fb3e842962b ) ) +game ( + name "Alley Cat (World) (Aftermarket) (Unl)" + description "Alley Cat (World) (Aftermarket) (Unl)" + rom ( name "Alley Cat (World) (Aftermarket) (Unl).gbc" size 262144 crc edb3ac37 sha1 a9aa1ecad6b67a6e5096fb1c10e39899c00a96a0 ) +) + +game ( + name "Alone in the Dark - The New Nightmare (Europe) (En,Fr,De,Es,It,Nl) (Beta)" + description "Alone in the Dark - The New Nightmare (Europe) (En,Fr,De,Es,It,Nl) (Beta)" + rom ( name "Alone in the Dark - The New Nightmare (Europe) (En,Fr,De,Es,It,Nl) (Beta).gbc" size 4194304 crc 7c101475 sha1 120e2dd012cfe47a7e37cc91fcbbce92494bdf2a ) +) + +game ( + name "Alone in the Dark - The New Nightmare (USA) (Beta) (2001-02-21)" + description "Alone in the Dark - The New Nightmare (USA) (Beta) (2001-02-21)" + rom ( name "Alone in the Dark - The New Nightmare (USA) (Beta) (2001-02-21).gbc" size 4194304 crc 3821d140 sha1 fc2a7dbe5ff60e2704ac78f83d6b6df2bf9dcc97 ) +) + game ( name "Alone in the Dark - The New Nightmare (Europe) (En,Fr,De,Es,It,Nl)" description "Alone in the Dark - The New Nightmare (Europe) (En,Fr,De,Es,It,Nl)" @@ -33228,12 +34137,6 @@ game ( rom ( name "Alone in the Dark - The New Nightmare (USA) (En,Fr,Es).gbc" size 4194304 crc c145c036 sha1 a348aeac500d0d8fdaf90f5277631a026504ff44 ) ) -game ( - name "Alone in the Dark - The New Nightmare (Europe) (En,Fr,De,Es,It,Nl) (Beta)" - description "Alone in the Dark - The New Nightmare (Europe) (En,Fr,De,Es,It,Nl) (Beta)" - rom ( name "Alone in the Dark - The New Nightmare (Europe) (En,Fr,De,Es,It,Nl) (Beta).gbc" size 4194304 crc 7c101475 sha1 120e2dd012cfe47a7e37cc91fcbbce92494bdf2a ) -) - game ( name "AMF Bowling (USA) (Proto)" description "AMF Bowling (USA) (Proto)" @@ -33469,9 +34372,9 @@ game ( ) game ( - name "Auto Zone (World) (Aftermarket) (Homebrew)" - description "Auto Zone (World) (Aftermarket) (Homebrew)" - rom ( name "Auto Zone (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 2a86d386 sha1 83a37fb5c5d82e0ff06bb22b63761af996e4ad61 ) + name "Auto Zone (World) (Aftermarket) (Unl)" + description "Auto Zone (World) (Aftermarket) (Unl)" + rom ( name "Auto Zone (World) (Aftermarket) (Unl).gbc" size 524288 crc 2a86d386 sha1 83a37fb5c5d82e0ff06bb22b63761af996e4ad61 ) ) game ( @@ -33513,7 +34416,7 @@ game ( game ( name "Babe and Friends (Europe) (En,Fr,De,Es,It) (GB Compatible)" description "Babe and Friends (Europe) (En,Fr,De,Es,It) (GB Compatible)" - rom ( name "Babe and Friends (Europe) (En,Fr,De,Es,It) (GB Compatible).gbc" size 1048576 crc b3681854 sha1 52a560185b7e77e5286771f7851f69323512658e ) + rom ( name "Babe and Friends (Europe) (En,Fr,De,Es,It) (GB Compatible).gbc" size 1048576 crc b3681854 sha1 52a560185b7e77e5286771f7851f69323512658e flags verified ) ) game ( @@ -33535,9 +34438,9 @@ game ( ) game ( - name "Back to Nature (World) (Aftermarket) (Homebrew)" - description "Back to Nature (World) (Aftermarket) (Homebrew)" - rom ( name "Back to Nature (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 861f9a63 sha1 3f4a6cd105d4c8af0312bef482c08784bbbfb0eb ) + name "Back to Nature (World) (Aftermarket) (Unl)" + description "Back to Nature (World) (Aftermarket) (Unl)" + rom ( name "Back to Nature (World) (Aftermarket) (Unl).gbc" size 262144 crc 861f9a63 sha1 3f4a6cd105d4c8af0312bef482c08784bbbfb0eb ) ) game ( @@ -33607,9 +34510,9 @@ game ( ) game ( - name "Bandits at Zero (World) (Aftermarket) (Homebrew)" - description "Bandits at Zero (World) (Aftermarket) (Homebrew)" - rom ( name "Bandits at Zero (World) (Aftermarket) (Homebrew).gbc" size 524288 crc c70f413e sha1 c885e109fbb53445db9618c6768c2259e6135921 ) + name "Bandits at Zero (World) (Aftermarket) (Unl)" + description "Bandits at Zero (World) (Aftermarket) (Unl)" + rom ( name "Bandits at Zero (World) (Aftermarket) (Unl).gbc" size 524288 crc c70f413e sha1 c885e109fbb53445db9618c6768c2259e6135921 ) ) game ( @@ -33738,6 +34641,12 @@ game ( rom ( name "Battle Fishers (Japan).gbc" size 2097152 crc c99cf3c5 sha1 25d063b151c2d45a14d6952196490615b7e341c5 ) ) +game ( + name "Battle Star (World) (Aftermarket) (Unl)" + description "Battle Star (World) (Aftermarket) (Unl)" + rom ( name "Battle Star (World) (Aftermarket) (Unl).gbc" size 524288 crc b2fd062f sha1 ac4206129704e31386c9c0a1c961a148398f0ba3 ) +) + game ( name "Battleship (USA, Europe) (GB Compatible)" description "Battleship (USA, Europe) (GB Compatible)" @@ -33777,7 +34686,7 @@ game ( game ( name "Beatmania GB (Japan) (SGB Enhanced) (GB Compatible)" description "Beatmania GB (Japan) (SGB Enhanced) (GB Compatible)" - rom ( name "Beatmania GB (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 0d9cb195 sha1 640c4633fe8ec60b767c15a094c87ab7e113d555 ) + rom ( name "Beatmania GB (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 0d9cb195 sha1 640c4633fe8ec60b767c15a094c87ab7e113d555 flags verified ) ) game ( @@ -33811,9 +34720,9 @@ game ( ) game ( - name "Berks (World) (Aftermarket) (Homebrew)" - description "Berks (World) (Aftermarket) (Homebrew)" - rom ( name "Berks (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 10ded9ab sha1 8b4c282cf973cdd9926a69ae1d8fcbaf79f8552b ) + name "Berks (World) (Aftermarket) (Unl)" + description "Berks (World) (Aftermarket) (Unl)" + rom ( name "Berks (World) (Aftermarket) (Unl).gbc" size 262144 crc 10ded9ab sha1 8b4c282cf973cdd9926a69ae1d8fcbaf79f8552b ) ) game ( @@ -33853,21 +34762,21 @@ game ( ) game ( - name "Bing Yuan Li Xian Ji (Taiwan) (Unl)" - description "Bing Yuan Li Xian Ji (Taiwan) (Unl)" - rom ( name "Bing Yuan Li Xian Ji (Taiwan) (Unl).gbc" size 2097152 crc 7ca57891 sha1 1bebc84f0fbfa731f33343c56032c8fe5803e4ae ) + name "Bingyuan Lixian Ji (Taiwan) (Unl)" + description "Bingyuan Lixian Ji (Taiwan) (Unl)" + rom ( name "Bingyuan Lixian Ji (Taiwan) (Unl).gbc" size 2097152 crc 7ca57891 sha1 1bebc84f0fbfa731f33343c56032c8fe5803e4ae ) ) game ( - name "Bing Yuan Li Xian Ji II (Taiwan) (Unl) (Alt)" - description "Bing Yuan Li Xian Ji II (Taiwan) (Unl) (Alt)" - rom ( name "Bing Yuan Li Xian Ji II (Taiwan) (Unl) (Alt).gbc" size 2097152 crc b149f421 sha1 39060bd6fa872738ae126b64f123a1242d06680a ) + name "Bingyuan Lixian Ji II (Taiwan) (Unl) (Alt)" + description "Bingyuan Lixian Ji II (Taiwan) (Unl) (Alt)" + rom ( name "Bingyuan Lixian Ji II (Taiwan) (Unl) (Alt).gbc" size 2097152 crc b149f421 sha1 39060bd6fa872738ae126b64f123a1242d06680a ) ) game ( - name "Bing Yuan Li Xian Ji II (Taiwan) (Unl)" - description "Bing Yuan Li Xian Ji II (Taiwan) (Unl)" - rom ( name "Bing Yuan Li Xian Ji II (Taiwan) (Unl).gbc" size 2097152 crc c4eba914 sha1 9e1c0c019630d7e4884ad3d5a447bd1acf668277 ) + name "Bingyuan Lixian Ji II (Taiwan) (Unl)" + description "Bingyuan Lixian Ji II (Taiwan) (Unl)" + rom ( name "Bingyuan Lixian Ji II (Taiwan) (Unl).gbc" size 2097152 crc c4eba914 sha1 9e1c0c019630d7e4884ad3d5a447bd1acf668277 ) ) game ( @@ -33907,9 +34816,9 @@ game ( ) game ( - name "Blinky's Revenge (World) (Aftermarket) (Homebrew)" - description "Blinky's Revenge (World) (Aftermarket) (Homebrew)" - rom ( name "Blinky's Revenge (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 2d2f9c2b sha1 1c39cc02396c74e7b0b282ead510e94c77ff7f1e ) + name "Blinky's Revenge (World) (Aftermarket) (Unl)" + description "Blinky's Revenge (World) (Aftermarket) (Unl)" + rom ( name "Blinky's Revenge (World) (Aftermarket) (Unl).gbc" size 262144 crc 2d2f9c2b sha1 1c39cc02396c74e7b0b282ead510e94c77ff7f1e ) ) game ( @@ -33984,12 +34893,6 @@ game ( rom ( name "Bokujou Monogatari 3 GB - Boy Meets Girl (Japan) (Rev 1).gbc" size 2097152 crc 75af0e84 sha1 acb2e549fb7d107d20507af88c36f40234f74958 flags verified ) ) -game ( - name "Bomberman 3 (Taiwan) (Unl)" - description "Bomberman 3 (Taiwan) (Unl)" - rom ( name "Bomberman 3 (Taiwan) (Unl).gbc" size 2097152 crc 8059e009 sha1 84011e3ae407613cfd7b6b1fe85f3812097afc66 ) -) - game ( name "Bomberman Max - Ain Version (Japan)" description "Bomberman Max - Ain Version (Japan)" @@ -34005,7 +34908,7 @@ game ( game ( name "Bomberman Max - Hikari no Yuusha (Japan)" description "Bomberman Max - Hikari no Yuusha (Japan)" - rom ( name "Bomberman Max - Hikari no Yuusha (Japan).gbc" size 2097152 crc 7a44ce88 sha1 9c38166e83e45707cbc2e0aff38fd4590dce7c3f ) + rom ( name "Bomberman Max - Hikari no Yuusha (Japan).gbc" size 2097152 crc 7a44ce88 sha1 9c38166e83e45707cbc2e0aff38fd4590dce7c3f flags verified ) ) game ( @@ -34017,7 +34920,7 @@ game ( game ( name "Bomberman Max - Yami no Senshi (Japan)" description "Bomberman Max - Yami no Senshi (Japan)" - rom ( name "Bomberman Max - Yami no Senshi (Japan).gbc" size 2097152 crc 48b60e8e sha1 12addfb8f890a44b87efb900e9d6ad5028b58936 ) + rom ( name "Bomberman Max - Yami no Senshi (Japan).gbc" size 2097152 crc 48b60e8e sha1 12addfb8f890a44b87efb900e9d6ad5028b58936 flags verified ) ) game ( @@ -34057,15 +34960,15 @@ game ( ) game ( - name "Booty (World) (Aftermarket) (Homebrew)" - description "Booty (World) (Aftermarket) (Homebrew)" - rom ( name "Booty (World) (Aftermarket) (Homebrew).gbc" size 262144 crc ef6c39c8 sha1 3c487ddc03d935b583e186d1cc395966ef490412 ) + name "Booty (World) (Aftermarket) (Unl)" + description "Booty (World) (Aftermarket) (Unl)" + rom ( name "Booty (World) (Aftermarket) (Unl).gbc" size 262144 crc ef6c39c8 sha1 3c487ddc03d935b583e186d1cc395966ef490412 ) ) game ( - name "Bouken! Dondoko-tou (Japan)" - description "Bouken! Dondoko-tou (Japan)" - rom ( name "Bouken! Dondoko-tou (Japan).gbc" size 2097152 crc 5fe759c7 sha1 c054abfea01a3ceb7b20b27e7ea937abe5157834 ) + name "Bouken! Dondoko Shima (Japan)" + description "Bouken! Dondoko Shima (Japan)" + rom ( name "Bouken! Dondoko Shima (Japan).gbc" size 2097152 crc 5fe759c7 sha1 c054abfea01a3ceb7b20b27e7ea937abe5157834 ) ) game ( @@ -34081,9 +34984,9 @@ game ( ) game ( - name "Bubble Trouble (World) (Aftermarket) (Homebrew)" - description "Bubble Trouble (World) (Aftermarket) (Homebrew)" - rom ( name "Bubble Trouble (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 3374918b sha1 892388f81f7815ea3bca17632a6a9a33a6edafac ) + name "Bubble Trouble (World) (Aftermarket) (Unl)" + description "Bubble Trouble (World) (Aftermarket) (Unl)" + rom ( name "Bubble Trouble (World) (Aftermarket) (Unl).gbc" size 262144 crc 3374918b sha1 892388f81f7815ea3bca17632a6a9a33a6edafac ) ) game ( @@ -34147,9 +35050,9 @@ game ( ) game ( - name "Bulb! (World) (Aftermarket) (Homebrew)" - description "Bulb! (World) (Aftermarket) (Homebrew)" - rom ( name "Bulb! (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 650114a2 sha1 0c32d4b48a34614fb11fc69c17187d35ee22fabe ) + name "Bulb! (World) (v2.5) (Aftermarket) (Unl)" + description "Bulb! (World) (v2.5) (Aftermarket) (Unl)" + rom ( name "Bulb! (World) (v2.5) (Aftermarket) (Unl).gbc" size 524288 crc 650114a2 sha1 0c32d4b48a34614fb11fc69c17187d35ee22fabe flags verified ) ) game ( @@ -34386,6 +35289,12 @@ game ( rom ( name "Catz (USA).gbc" size 1048576 crc 769a2c5a sha1 5c3c9a4d85a92779c7e8304f2c122296f62f9a9c ) ) +game ( + name "Cave Fighter (World) (Aftermarket) (Unl)" + description "Cave Fighter (World) (Aftermarket) (Unl)" + rom ( name "Cave Fighter (World) (Aftermarket) (Unl).gbc" size 262144 crc 6f4641f0 sha1 05b441e3542452b1724017d20e2db602d0997773 ) +) + game ( name "Centipede (Europe) (En,Fr,De,Es,It,Nl) (GB Compatible)" description "Centipede (Europe) (En,Fr,De,Es,It,Nl) (GB Compatible)" @@ -34411,39 +35320,45 @@ game ( ) game ( - name "Chao Ji Ji Qi Ren Da Zhan X - Super Robot War X (Taiwan) (Unl)" - description "Chao Ji Ji Qi Ren Da Zhan X - Super Robot War X (Taiwan) (Unl)" - rom ( name "Chao Ji Ji Qi Ren Da Zhan X - Super Robot War X (Taiwan) (Unl).gbc" size 2097152 crc 279be0cc sha1 c2a64ba4c1fd60829429330bc13b70d9bd18f023 ) + name "Chao Jinhua - Shuma Baobei D-3 (Taiwan) (Unl)" + description "Chao Jinhua - Shuma Baobei D-3 (Taiwan) (Unl)" + rom ( name "Chao Jinhua - Shuma Baobei D-3 (Taiwan) (Unl).gbc" size 1048576 crc 1517d27e sha1 0d14f2e2ab630b6f99a8125e5c0120c2d7967270 ) ) game ( - name "Chao Ji Ji Qi Ren Da Zhan X - Super Robot War X (Taiwan) (Unl) (Alt)" - description "Chao Ji Ji Qi Ren Da Zhan X - Super Robot War X (Taiwan) (Unl) (Alt)" - rom ( name "Chao Ji Ji Qi Ren Da Zhan X - Super Robot War X (Taiwan) (Unl) (Alt).gbc" size 2097152 crc 1feaeb47 sha1 26d5841fc898dab17458dc39e871dab4947a7f2b ) -) - -game ( - name "Chao Jin Hua - Shu Ma Bao Bei D-3 (Taiwan) (Unl)" - description "Chao Jin Hua - Shu Ma Bao Bei D-3 (Taiwan) (Unl)" - rom ( name "Chao Jin Hua - Shu Ma Bao Bei D-3 (Taiwan) (Unl).gbc" size 1048576 crc 1517d27e sha1 0d14f2e2ab630b6f99a8125e5c0120c2d7967270 ) -) - -game ( - name "Chao Jinhua Shuma Baolong - Zuanshi Ban (Taiwan) (Unl)" - description "Chao Jinhua Shuma Baolong - Zuanshi Ban (Taiwan) (Unl)" - rom ( name "Chao Jinhua Shuma Baolong - Zuanshi Ban (Taiwan) (Unl).gbc" size 1048576 crc 50babe99 sha1 c153550886e812f5e37746fe1be6f294f13f97b3 ) + name "Chao Jinhua - Shuma Baolong - Zuanshi Ban (Taiwan) (Unl)" + description "Chao Jinhua - Shuma Baolong - Zuanshi Ban (Taiwan) (Unl)" + rom ( name "Chao Jinhua - Shuma Baolong - Zuanshi Ban (Taiwan) (Unl).gbc" size 1048576 crc 50babe99 sha1 c153550886e812f5e37746fe1be6f294f13f97b3 ) ) game ( name "Chaoji Gedou 2001 Alpha (Taiwan) (Unl)" description "Chaoji Gedou 2001 Alpha (Taiwan) (Unl)" - rom ( name "Chaoji Gedou 2001 Alpha (Taiwan) (Unl).gbc" size 2097152 crc afd7a0cc sha1 f44f629687ec91aade40ff52014587003f728ec9 ) + rom ( name "Chaoji Gedou 2001 Alpha (Taiwan) (Unl).gbc" size 2097152 crc afd7a0cc sha1 f44f629687ec91aade40ff52014587003f728ec9 flags verified ) ) game ( - name "Chaoji Yinsu Xiaozi II - Super Sonik II (Taiwan) (Unl)" - description "Chaoji Yinsu Xiaozi II - Super Sonik II (Taiwan) (Unl)" - rom ( name "Chaoji Yinsu Xiaozi II - Super Sonik II (Taiwan) (Unl).gbc" size 2097152 crc a9dd9da7 sha1 006df0ddb10c5970002134da4d75ccc466e95edd ) + name "Chaoji Jiqiren Dazhan X - Super Robot War X (Taiwan) (Unl)" + description "Chaoji Jiqiren Dazhan X - Super Robot War X (Taiwan) (Unl)" + rom ( name "Chaoji Jiqiren Dazhan X - Super Robot War X (Taiwan) (Unl).gbc" size 2097152 crc 279be0cc sha1 c2a64ba4c1fd60829429330bc13b70d9bd18f023 ) +) + +game ( + name "Chaoji Jiqiren Dazhan X - Super Robot War X (Taiwan) (Unl) (Alt)" + description "Chaoji Jiqiren Dazhan X - Super Robot War X (Taiwan) (Unl) (Alt)" + rom ( name "Chaoji Jiqiren Dazhan X - Super Robot War X (Taiwan) (Unl) (Alt).gbc" size 2097152 crc 1feaeb47 sha1 26d5841fc898dab17458dc39e871dab4947a7f2b ) +) + +game ( + name "Chaoji Yinsu de Xiaozi II - Super Sonik II (Taiwan) (Unl)" + description "Chaoji Yinsu de Xiaozi II - Super Sonik II (Taiwan) (Unl)" + rom ( name "Chaoji Yinsu de Xiaozi II - Super Sonik II (Taiwan) (Unl).gbc" size 2097152 crc a9dd9da7 sha1 006df0ddb10c5970002134da4d75ccc466e95edd ) +) + +game ( + name "Chaoren Tegong Dui (Taiwan) (Unl)" + description "Chaoren Tegong Dui (Taiwan) (Unl)" + rom ( name "Chaoren Tegong Dui (Taiwan) (Unl).gbc" size 2097152 crc e1d61242 sha1 31f54bdfa59d1e76777f044c1ef4686552f715f6 flags verified ) ) game ( @@ -34501,9 +35416,9 @@ game ( ) game ( - name "Chong Wu Xiao Jing Ling - Jie Jin Ta Zhi Wang (Taiwan) (Unl)" - description "Chong Wu Xiao Jing Ling - Jie Jin Ta Zhi Wang (Taiwan) (Unl)" - rom ( name "Chong Wu Xiao Jing Ling - Jie Jin Ta Zhi Wang (Taiwan) (Unl).gbc" size 1048576 crc 620e785d sha1 74da832c2eeb27a4260fe6f42514539473f48b11 ) + name "Chongwu Xiao Jingling - Jiejin Ta Zhi Wang (Taiwan) (Unl)" + description "Chongwu Xiao Jingling - Jiejin Ta Zhi Wang (Taiwan) (Unl)" + rom ( name "Chongwu Xiao Jingling - Jiejin Ta Zhi Wang (Taiwan) (Unl).gbc" size 1048576 crc 620e785d sha1 74da832c2eeb27a4260fe6f42514539473f48b11 ) ) game ( @@ -34513,9 +35428,9 @@ game ( ) game ( - name "Chuan Shuo (Taiwan) (Unl)" - description "Chuan Shuo (Taiwan) (Unl)" - rom ( name "Chuan Shuo (Taiwan) (Unl).gbc" size 2097152 crc 0b20d3af sha1 f390b702d4b160149c1030bcccb5ed6f76c0b94a ) + name "Chuanshuo (Taiwan) (Unl)" + description "Chuanshuo (Taiwan) (Unl)" + rom ( name "Chuanshuo (Taiwan) (Unl).gbc" size 2097152 crc 0b20d3af sha1 f390b702d4b160149c1030bcccb5ed6f76c0b94a ) ) game ( @@ -34531,9 +35446,9 @@ game ( ) game ( - name "Climb It (World) (Aftermarket) (Homebrew)" - description "Climb It (World) (Aftermarket) (Homebrew)" - rom ( name "Climb It (World) (Aftermarket) (Homebrew).gbc" size 262144 crc e7210290 sha1 19f3b825eada3eda3135350bd0ddca6a20a5281f ) + name "Climb It (World) (Aftermarket) (Unl)" + description "Climb It (World) (Aftermarket) (Unl)" + rom ( name "Climb It (World) (Aftermarket) (Unl).gbc" size 262144 crc e7210290 sha1 19f3b825eada3eda3135350bd0ddca6a20a5281f ) ) game ( @@ -34561,9 +35476,9 @@ game ( ) game ( - name "Commando (World) (Aftermarket) (Homebrew)" - description "Commando (World) (Aftermarket) (Homebrew)" - rom ( name "Commando (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 5381b103 sha1 414bb6bdc1ed5647707d7d49f194c387d4098f00 ) + name "Commando (World) (Aftermarket) (Unl)" + description "Commando (World) (Aftermarket) (Unl)" + rom ( name "Commando (World) (Aftermarket) (Unl).gbc" size 262144 crc 5381b103 sha1 414bb6bdc1ed5647707d7d49f194c387d4098f00 ) ) game ( @@ -34584,12 +35499,6 @@ game ( rom ( name "Cool Hand (Europe) (En,Fr,De) (GB Compatible).gbc" size 524288 crc e6c91fb8 sha1 d116a77c501d5e553d1490993f8c0a9a92c3ea0c ) ) -game ( - name "Crash II Advance 2003 (USA) (Unl)" - description "Crash II Advance 2003 (USA) (Unl)" - rom ( name "Crash II Advance 2003 (USA) (Unl).gbc" size 2097152 crc 973d38a8 sha1 acdc42716f61bf11ecf08d50a980047f8fb7dc57 ) -) - game ( name "Crazy Bikers (Europe)" description "Crazy Bikers (Europe)" @@ -34609,9 +35518,9 @@ game ( ) game ( - name "Crazy Golf (World) (Aftermarket) (Homebrew)" - description "Crazy Golf (World) (Aftermarket) (Homebrew)" - rom ( name "Crazy Golf (World) (Aftermarket) (Homebrew).gbc" size 262144 crc f7fe3d01 sha1 b06f47a713b66efa9133be43653c7c4cb5ebc93c ) + name "Crazy Golf (World) (Aftermarket) (Unl)" + description "Crazy Golf (World) (Aftermarket) (Unl)" + rom ( name "Crazy Golf (World) (Aftermarket) (Unl).gbc" size 262144 crc f7fe3d01 sha1 b06f47a713b66efa9133be43653c7c4cb5ebc93c ) ) game ( @@ -34669,9 +35578,9 @@ game ( ) game ( - name "Cuthbert in the Cooler (World) (Aftermarket) (Homebrew)" - description "Cuthbert in the Cooler (World) (Aftermarket) (Homebrew)" - rom ( name "Cuthbert in the Cooler (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 30204c4e sha1 d6fb35f3bdd44429f88c10551692e1adf14356ab ) + name "Cuthbert in the Cooler (World) (Aftermarket) (Unl)" + description "Cuthbert in the Cooler (World) (Aftermarket) (Unl)" + rom ( name "Cuthbert in the Cooler (World) (Aftermarket) (Unl).gbc" size 262144 crc 30204c4e sha1 d6fb35f3bdd44429f88c10551692e1adf14356ab ) ) game ( @@ -34830,12 +35739,24 @@ game ( rom ( name "Dear Daniel no Sweet Adventure - Kitty-chan o Sagashite (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 0fd34427 sha1 8598594285f0342b3d93c447b475fadd4722c6cb ) ) +game ( + name "Death Race 16 (World) (Aftermarket) (Unl)" + description "Death Race 16 (World) (Aftermarket) (Unl)" + rom ( name "Death Race 16 (World) (Aftermarket) (Unl).gbc" size 262144 crc 7b9488ec sha1 56b8a2d7117b12ff5e709e6a258f9a0b3b0ebe24 ) +) + game ( name "Deer Hunter (USA)" description "Deer Hunter (USA)" rom ( name "Deer Hunter (USA).gbc" size 1048576 crc 40a715fb sha1 87a012e82f2361760ae337b30e9d41ef2245419a ) ) +game ( + name "Deisanebe (World) (GB Compatible) (Aftermarket) (Unl)" + description "Deisanebe (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Deisanebe (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 7cff943e sha1 065b5325d44cb0e40f7b7803d707b8c1ea9ea5b2 ) +) + game ( name "Deja Vu I & II (Japan)" description "Deja Vu I & II (Japan)" @@ -34902,12 +35823,6 @@ game ( rom ( name "Dexter's Laboratory - Robot Rampage (USA, Europe).gbc" size 1048576 crc d24d6601 sha1 db107af55df07fb8c771c712b05d6aebb175e3c5 ) ) -game ( - name "Digimom Sapphii 2003 (Taiwan) (En) (Unl)" - description "Digimom Sapphii 2003 (Taiwan) (En) (Unl)" - rom ( name "Digimom Sapphii 2003 (Taiwan) (En) (Unl).gbc" size 2097152 crc 7cff9f0b sha1 1e42c461e0c6705637a698dbda8b34bc37acc51d ) -) - game ( name "Digimon - Yellow Jade (USA) (Unl)" description "Digimon - Yellow Jade (USA) (Unl)" @@ -34938,12 +35853,6 @@ game ( rom ( name "Digimon Adventure 2001 (USA) (Unl).gbc" size 1048576 crc 01f1fcee sha1 cb4327829bc90c7bb448e7591e6ee3729a12a8cd ) ) -game ( - name "Digimon Adventure 6 2002 (Taiwan) (En) (Unl)" - description "Digimon Adventure 6 2002 (Taiwan) (En) (Unl)" - rom ( name "Digimon Adventure 6 2002 (Taiwan) (En) (Unl).gbc" size 1048576 crc 3f0d046b sha1 eea8928b277beb56ca8878b6dbfc094b0c3c5463 ) -) - game ( name "Digimon Crystal II (USA) (Unl)" description "Digimon Crystal II (USA) (Unl)" @@ -35022,6 +35931,12 @@ game ( rom ( name "Diva Starz - Mall Mania (USA).gbc" size 1048576 crc ebe0ecd6 sha1 1d0edb87404409f1e4c124503be4cc59165c01da ) ) +game ( + name "Doc Cosmos - The Saga Begins (World) (Aftermarket) (Unl)" + description "Doc Cosmos - The Saga Begins (World) (Aftermarket) (Unl)" + rom ( name "Doc Cosmos - The Saga Begins (World) (Aftermarket) (Unl).gbc" size 262144 crc 754dff51 sha1 a066d9973a2b90a08067dfcd22b04844557e89e2 ) +) + game ( name "Dogz (Europe)" description "Dogz (Europe)" @@ -35073,7 +35988,7 @@ game ( game ( name "Donkey Kong 2001 (Japan)" description "Donkey Kong 2001 (Japan)" - rom ( name "Donkey Kong 2001 (Japan).gbc" size 4194304 crc cb065eba sha1 5be5500d9ff9c4416df77816ebd21cca7a0b19de ) + rom ( name "Donkey Kong 2001 (Japan).gbc" size 4194304 crc cb065eba sha1 5be5500d9ff9c4416df77816ebd21cca7a0b19de flags verified ) ) game ( @@ -35161,9 +36076,9 @@ game ( ) game ( - name "Dork's Dilemma (World) (Aftermarket) (Homebrew)" - description "Dork's Dilemma (World) (Aftermarket) (Homebrew)" - rom ( name "Dork's Dilemma (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 77b8b43b sha1 bd525f97cc316fed3f6271ed0929414df4c0389c ) + name "Dork's Dilemma (World) (Aftermarket) (Unl)" + description "Dork's Dilemma (World) (Aftermarket) (Unl)" + rom ( name "Dork's Dilemma (World) (Aftermarket) (Unl).gbc" size 524288 crc 77b8b43b sha1 bd525f97cc316fed3f6271ed0929414df4c0389c ) ) game ( @@ -35214,6 +36129,12 @@ game ( rom ( name "Dragon Ball - Final Bout (Taiwan) (Unl).gbc" size 1048576 crc d9849157 sha1 51ebe1a27e8295340f996d2841c5cc6e2bc9f548 ) ) +game ( + name "Dragon Ball Z - 2002 Fighting (Taiwan) (Zh) (Unl)" + description "Dragon Ball Z - 2002 Fighting (Taiwan) (Zh) (Unl)" + rom ( name "Dragon Ball Z - 2002 Fighting (Taiwan) (Zh) (Unl).gbc" size 2097152 crc 8fa35740 sha1 008a559635c9d351d9f68b3a1ac0c687521821cf ) +) + game ( name "Dragon Ball Z - Densetsu no Chou Senshi-tachi (Japan) (Beta) (All Unlocked)" description "Dragon Ball Z - Densetsu no Chou Senshi-tachi (Japan) (Beta) (All Unlocked)" @@ -35323,15 +36244,9 @@ game ( ) game ( - name "Dragon Ball Z 3 2002 Fighting (Taiwan) (En) (Unl)" - description "Dragon Ball Z 3 2002 Fighting (Taiwan) (En) (Unl)" - rom ( name "Dragon Ball Z 3 2002 Fighting (Taiwan) (En) (Unl).gbc" size 2097152 crc dbe0f44e sha1 baea9e5ee1411988039d563ece97d8d7f765396a ) -) - -game ( - name "Dragon Ball Z Fighting 2002 (Taiwan) (Zh) (Unl)" - description "Dragon Ball Z Fighting 2002 (Taiwan) (Zh) (Unl)" - rom ( name "Dragon Ball Z Fighting 2002 (Taiwan) (Zh) (Unl).gbc" size 2097152 crc 8fa35740 sha1 008a559635c9d351d9f68b3a1ac0c687521821cf ) + name "Dragon Ball Z 3 - 2002 Fighting (Taiwan) (En) (Unl)" + description "Dragon Ball Z 3 - 2002 Fighting (Taiwan) (En) (Unl)" + rom ( name "Dragon Ball Z 3 - 2002 Fighting (Taiwan) (En) (Unl).gbc" size 2097152 crc dbe0f44e sha1 baea9e5ee1411988039d563ece97d8d7f765396a ) ) game ( @@ -35389,9 +36304,9 @@ game ( ) game ( - name "Dragon Quest Monsters - Terry no Wonderland (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP)" - description "Dragon Quest Monsters - Terry no Wonderland (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP)" - rom ( name "Dragon Quest Monsters - Terry no Wonderland (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP).gbc" size 2097152 crc 66b83e3a sha1 378794e041f2eb286f2d5243a3cada2faf265bb5 ) + name "Dragon Quest Monsters - Terry no Wonderland (Japan) (Rev 1) (Possible Proto) (SGB Enhanced) (GB Compatible) (Alt)" + description "Dragon Quest Monsters - Terry no Wonderland (Japan) (Rev 1) (Possible Proto) (SGB Enhanced) (GB Compatible) (Alt)" + rom ( name "Dragon Quest Monsters - Terry no Wonderland (Japan) (Rev 1) (Possible Proto) (SGB Enhanced) (GB Compatible) (Alt).gbc" size 2097152 crc 66b83e3a sha1 378794e041f2eb286f2d5243a3cada2faf265bb5 ) ) game ( @@ -35557,9 +36472,9 @@ game ( ) game ( - name "E Mo Dao (Taiwan) (Unl)" - description "E Mo Dao (Taiwan) (Unl)" - rom ( name "E Mo Dao (Taiwan) (Unl).gbc" size 524288 crc 2b2acb79 sha1 929784599694efc6db16163f5bf91e49c2e459fc ) + name "E.T. - The Extra-Terrestrial - Digital Companion (USA)" + description "E.T. - The Extra-Terrestrial - Digital Companion (USA)" + rom ( name "E.T. - The Extra-Terrestrial - Digital Companion (USA).gbc" size 1048576 crc 84872999 sha1 7dd940c8044ccc0e18a511fa32c73551ce30463e ) ) game ( @@ -35568,12 +36483,6 @@ game ( rom ( name "E.T. - The Extra-Terrestrial - Digital Companion (Europe) (Proto).gbc" size 1048576 crc 513f38b7 sha1 14ad36a7939be12d1212c12a59de31d27e4df8f3 ) ) -game ( - name "E.T. - The Extra-Terrestrial - Digital Companion (USA)" - description "E.T. - The Extra-Terrestrial - Digital Companion (USA)" - rom ( name "E.T. - The Extra-Terrestrial - Digital Companion (USA).gbc" size 1048576 crc 84872999 sha1 7dd940c8044ccc0e18a511fa32c73551ce30463e ) -) - game ( name "E.T. - The Extra-Terrestrial - Escape from Planet Earth (Europe) (En,Fr,De,Es,It,Nl)" description "E.T. - The Extra-Terrestrial - Escape from Planet Earth (Europe) (En,Fr,De,Es,It,Nl)" @@ -35601,7 +36510,7 @@ game ( game ( name "Earthworm Jim - Menace 2 the Galaxy (USA, Europe) (GB Compatible)" description "Earthworm Jim - Menace 2 the Galaxy (USA, Europe) (GB Compatible)" - rom ( name "Earthworm Jim - Menace 2 the Galaxy (USA, Europe) (GB Compatible).gbc" size 1048576 crc 2e65daaf sha1 78f9bd7f8c40274cf7282892a45e814103944060 ) + rom ( name "Earthworm Jim - Menace 2 the Galaxy (USA, Europe) (GB Compatible).gbc" size 1048576 crc 2e65daaf sha1 78f9bd7f8c40274cf7282892a45e814103944060 flags verified ) ) game ( @@ -35652,6 +36561,12 @@ game ( rom ( name "Emo Cheng DX (Taiwan) (Beta) (Unl).gbc" size 1048576 crc ebf7bf6e sha1 29b1b595ca8f5ad76ebf4f997d0e4fb8069829f1 ) ) +game ( + name "Emo Dao (Taiwan) (Unl)" + description "Emo Dao (Taiwan) (Unl)" + rom ( name "Emo Dao (Taiwan) (Unl).gbc" size 524288 crc 2b2acb79 sha1 929784599694efc6db16163f5bf91e49c2e459fc ) +) + game ( name "Emperor's New Groove, The (Europe) (En,Fr,De,Es,It)" description "Emperor's New Groove, The (Europe) (En,Fr,De,Es,It)" @@ -35719,15 +36634,15 @@ game ( ) game ( - name "Expiration Date (World) (Aftermarket) (Homebrew)" - description "Expiration Date (World) (Aftermarket) (Homebrew)" - rom ( name "Expiration Date (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 050caa5e sha1 e4b61c220b046e2287272f9b1bef85ad2d835fe3 ) + name "Expiration Date (World) (GB Compatible) (Aftermarket) (Unl)" + description "Expiration Date (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Expiration Date (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 050caa5e sha1 e4b61c220b046e2287272f9b1bef85ad2d835fe3 ) ) game ( - name "Exploits of Fingers Malone, The (World) (Aftermarket) (Homebrew)" - description "Exploits of Fingers Malone, The (World) (Aftermarket) (Homebrew)" - rom ( name "Exploits of Fingers Malone, The (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 20fe84af sha1 18056b5b032955099c606651f26164de131e54a6 ) + name "Exploits of Fingers Malone, The (World) (Aftermarket) (Unl)" + description "Exploits of Fingers Malone, The (World) (Aftermarket) (Unl)" + rom ( name "Exploits of Fingers Malone, The (World) (Aftermarket) (Unl).gbc" size 524288 crc 20fe84af sha1 18056b5b032955099c606651f26164de131e54a6 ) ) game ( @@ -35845,15 +36760,9 @@ game ( ) game ( - name "Feng Kuang Da Fu Weng (Taiwan) (Unl)" - description "Feng Kuang Da Fu Weng (Taiwan) (Unl)" - rom ( name "Feng Kuang Da Fu Weng (Taiwan) (Unl).gbc" size 2097152 crc fbf62c35 sha1 7128a6ddbb44ba5ba226516996eb9a6077d365a9 ) -) - -game ( - name "Feng Kuang Da Fu Weng (Taiwan) (Unl) (Alt)" - description "Feng Kuang Da Fu Weng (Taiwan) (Unl) (Alt)" - rom ( name "Feng Kuang Da Fu Weng (Taiwan) (Unl) (Alt).gbc" size 2097152 crc 45b79edc sha1 cc86e1247a352a7c7134db927cc41a1e9950fb96 ) + name "Feng Kuang A Gei III - Chaoji Zhadan Ren (Taiwan) (Unl)" + description "Feng Kuang A Gei III - Chaoji Zhadan Ren (Taiwan) (Unl)" + rom ( name "Feng Kuang A Gei III - Chaoji Zhadan Ren (Taiwan) (Unl).gbc" size 2097152 crc 8059e009 sha1 84011e3ae407613cfd7b6b1fe85f3812097afc66 flags verified ) ) game ( @@ -35862,6 +36771,18 @@ game ( rom ( name "Feng Zhi Gou II (Taiwan) (Unl).gbc" size 2097152 crc 842cb4fe sha1 4322980e5b21332a71fb7fcadbf57a79aad6346b ) ) +game ( + name "Fengkuang Dafuweng (Taiwan) (Unl)" + description "Fengkuang Dafuweng (Taiwan) (Unl)" + rom ( name "Fengkuang Dafuweng (Taiwan) (Unl).gbc" size 2097152 crc fbf62c35 sha1 7128a6ddbb44ba5ba226516996eb9a6077d365a9 ) +) + +game ( + name "Fengkuang Dafuweng (Taiwan) (Unl) (Alt)" + description "Fengkuang Dafuweng (Taiwan) (Unl) (Alt)" + rom ( name "Fengkuang Dafuweng (Taiwan) (Unl) (Alt).gbc" size 2097152 crc 45b79edc sha1 cc86e1247a352a7c7134db927cc41a1e9950fb96 ) +) + game ( name "Ferret Monogatari (Japan)" description "Ferret Monogatari (Japan)" @@ -35881,33 +36802,39 @@ game ( ) game ( - name "Fillo - Crystal Version (World) (Aftermarket) (Homebrew)" - description "Fillo - Crystal Version (World) (Aftermarket) (Homebrew)" - rom ( name "Fillo - Crystal Version (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 88bb855c sha1 2ffc4199d94f8b29b3f84b7bdb2694154a0bd285 ) + name "Fillo - Crystal Version (World) (Aftermarket) (Unl)" + description "Fillo - Crystal Version (World) (Aftermarket) (Unl)" + rom ( name "Fillo - Crystal Version (World) (Aftermarket) (Unl).gbc" size 524288 crc 88bb855c sha1 2ffc4199d94f8b29b3f84b7bdb2694154a0bd285 ) ) game ( - name "Find Out (World) (Aftermarket) (Homebrew)" - description "Find Out (World) (Aftermarket) (Homebrew)" - rom ( name "Find Out (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 5f8b660e sha1 3605a96f6713157a88e4d88989d2e2f11b9db42f ) + name "Find Out (World) (GB Compatible) (Aftermarket) (Unl)" + description "Find Out (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Find Out (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 5f8b660e sha1 3605a96f6713157a88e4d88989d2e2f11b9db42f ) ) game ( - name "Finders Keepers (World) (Aftermarket) (Homebrew)" - description "Finders Keepers (World) (Aftermarket) (Homebrew)" - rom ( name "Finders Keepers (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 4cfa0cfd sha1 db002f4a2500a9dc98a9ac7c263293d6e4a72cf1 ) + name "Finders Keepers (World) (Aftermarket) (Unl)" + description "Finders Keepers (World) (Aftermarket) (Unl)" + rom ( name "Finders Keepers (World) (Aftermarket) (Unl).gbc" size 524288 crc 4cfa0cfd sha1 db002f4a2500a9dc98a9ac7c263293d6e4a72cf1 ) ) game ( - name "Fire Ant (World) (Aftermarket) (Homebrew)" - description "Fire Ant (World) (Aftermarket) (Homebrew)" - rom ( name "Fire Ant (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 154cc020 sha1 b58ed66838db34fc7f8475ccb670267119d623a5 ) + name "Fire Ant (World) (Aftermarket) (Unl)" + description "Fire Ant (World) (Aftermarket) (Unl)" + rom ( name "Fire Ant (World) (Aftermarket) (Unl).gbc" size 262144 crc 154cc020 sha1 b58ed66838db34fc7f8475ccb670267119d623a5 ) ) game ( - name "Firemen (World) (Aftermarket) (Homebrew)" - description "Firemen (World) (Aftermarket) (Homebrew)" - rom ( name "Firemen (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 7299401f sha1 84c68614334578f866cfa929df736377f04f5d04 ) + name "Fireman Fred (World) (Aftermarket) (Unl)" + description "Fireman Fred (World) (Aftermarket) (Unl)" + rom ( name "Fireman Fred (World) (Aftermarket) (Unl).gbc" size 524288 crc 563d9ca7 sha1 e62a9c6b6a39f0cb662fdb07b72669355b706f57 ) +) + +game ( + name "Firemen (World) (Aftermarket) (Unl)" + description "Firemen (World) (Aftermarket) (Unl)" + rom ( name "Firemen (World) (Aftermarket) (Unl).gbc" size 262144 crc 7299401f sha1 84c68614334578f866cfa929df736377f04f5d04 ) ) game ( @@ -35923,9 +36850,9 @@ game ( ) game ( - name "Fix It Felix Jr. (World) (Aftermarket) (Homebrew)" - description "Fix It Felix Jr. (World) (Aftermarket) (Homebrew)" - rom ( name "Fix It Felix Jr. (World) (Aftermarket) (Homebrew).gbc" size 131072 crc c8014615 sha1 7221f5e53f6027183301d06e258c6cb417007b8c ) + name "Fix It Felix Jr. (World) (GB Compatible) (Aftermarket) (Unl)" + description "Fix It Felix Jr. (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Fix It Felix Jr. (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 131072 crc c8014615 sha1 7221f5e53f6027183301d06e258c6cb417007b8c ) ) game ( @@ -36091,9 +37018,9 @@ game ( ) game ( - name "G-Man (World) (Aftermarket) (Homebrew)" - description "G-Man (World) (Aftermarket) (Homebrew)" - rom ( name "G-Man (World) (Aftermarket) (Homebrew).gbc" size 524288 crc ff3beab1 sha1 cb9486ca9a6ad3903662963aa966145257ef92a6 ) + name "G-Man (World) (Aftermarket) (Unl)" + description "G-Man (World) (Aftermarket) (Unl)" + rom ( name "G-Man (World) (Aftermarket) (Unl).gbc" size 524288 crc ff3beab1 sha1 cb9486ca9a6ad3903662963aa966145257ef92a6 ) ) game ( @@ -36109,9 +37036,9 @@ game ( ) game ( - name "Gakkyuu Ou Yamazaki (Japan) (GB Compatible)" - description "Gakkyuu Ou Yamazaki (Japan) (GB Compatible)" - rom ( name "Gakkyuu Ou Yamazaki (Japan) (GB Compatible).gbc" size 1048576 crc b8147b5c sha1 132b872391565d418ccc9d0e1c643510d778c066 ) + name "Gakkyuu Ou Yamazaki (Japan) (Possible Proto) (GB Compatible)" + description "Gakkyuu Ou Yamazaki (Japan) (Possible Proto) (GB Compatible)" + rom ( name "Gakkyuu Ou Yamazaki (Japan) (Possible Proto) (GB Compatible).gbc" size 1048576 crc b8147b5c sha1 132b872391565d418ccc9d0e1c643510d778c066 ) ) game ( @@ -36151,9 +37078,9 @@ game ( ) game ( - name "Game Boy Gallery 2 (Japan) (SGB Enhanced, GB Compatible) (NP)" - description "Game Boy Gallery 2 (Japan) (SGB Enhanced, GB Compatible) (NP)" - rom ( name "Game Boy Gallery 2 (Japan) (SGB Enhanced, GB Compatible) (NP).gbc" size 1048576 crc e99beba5 sha1 90a9b368bca0f47fe72028ff4d50fb3d3060c32f ) + name "Game Boy Gallery 2 (Japan) (Possible Proto) (SGB Enhanced, GB Compatible) (NP)" + description "Game Boy Gallery 2 (Japan) (Possible Proto) (SGB Enhanced, GB Compatible) (NP)" + rom ( name "Game Boy Gallery 2 (Japan) (Possible Proto) (SGB Enhanced, GB Compatible) (NP).gbc" size 1048576 crc e99beba5 sha1 90a9b368bca0f47fe72028ff4d50fb3d3060c32f ) ) game ( @@ -36199,15 +37126,15 @@ game ( ) game ( - name "Gamer Boy Mission (World) (Aftermarket) (Homebrew)" - description "Gamer Boy Mission (World) (Aftermarket) (Homebrew)" - rom ( name "Gamer Boy Mission (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 96721e5d sha1 90fad94433a5b0dbcf73f4a2d4ebb75d7ab065c4 ) + name "Gamer Boy Mission (World) (GB Compatible) (Aftermarket) (Unl)" + description "Gamer Boy Mission (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Gamer Boy Mission (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 524288 crc 96721e5d sha1 90fad94433a5b0dbcf73f4a2d4ebb75d7ab065c4 ) ) game ( - name "Gamer Boy Mission (World) (Demo) (Aftermarket) (Homebrew)" - description "Gamer Boy Mission (World) (Demo) (Aftermarket) (Homebrew)" - rom ( name "Gamer Boy Mission (World) (Demo) (Aftermarket) (Homebrew).gbc" size 262144 crc a02d2ae2 sha1 7eb6865c5c265193763ec0bf751d26964e870120 ) + name "Gamer Boy Mission (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Gamer Boy Mission (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Gamer Boy Mission (World) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc a02d2ae2 sha1 7eb6865c5c265193763ec0bf751d26964e870120 ) ) game ( @@ -36282,10 +37209,16 @@ game ( rom ( name "GB Memory Multi Menu (Japan) (SGB Enhanced, GB Compatible) (NP).gbc" size 131072 crc ec823cc1 sha1 0781eaecb7fd25c068e396b5eb02c6231baf6ea3 ) ) +game ( + name "Gedou Jian Shen - Soul Falchion (Taiwan) (Unl) (Alt)" + description "Gedou Jian Shen - Soul Falchion (Taiwan) (Unl) (Alt)" + rom ( name "Gedou Jian Shen - Soul Falchion (Taiwan) (Unl) (Alt).gbc" size 1048576 crc c0ac1b50 sha1 4dcef6fcfe009e17da0bb13e50e7410a3f586e23 ) +) + game ( name "Gedou Jian Shen - Soul Falchion (Taiwan) (Unl)" description "Gedou Jian Shen - Soul Falchion (Taiwan) (Unl)" - rom ( name "Gedou Jian Shen - Soul Falchion (Taiwan) (Unl).gbc" size 1048576 crc c0ac1b50 sha1 4dcef6fcfe009e17da0bb13e50e7410a3f586e23 ) + rom ( name "Gedou Jian Shen - Soul Falchion (Taiwan) (Unl).gbc" size 1048576 crc 1b1c6f68 sha1 fd06c62b42878e8094e610b180c1848c785f67e9 ) ) game ( @@ -36343,9 +37276,9 @@ game ( ) game ( - name "Ghost Town (World) (Aftermarket) (Homebrew)" - description "Ghost Town (World) (Aftermarket) (Homebrew)" - rom ( name "Ghost Town (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 52efcc35 sha1 1a4a7846d6c87f0bd8b267aa6a8aa98e797fedf8 ) + name "Ghost Town (World) (Aftermarket) (Unl)" + description "Ghost Town (World) (Aftermarket) (Unl)" + rom ( name "Ghost Town (World) (Aftermarket) (Unl).gbc" size 262144 crc 52efcc35 sha1 1a4a7846d6c87f0bd8b267aa6a8aa98e797fedf8 ) ) game ( @@ -36589,27 +37522,21 @@ game ( ) game ( - name "Gu Huo Lang II 2003 (Taiwan) (Unl)" - description "Gu Huo Lang II 2003 (Taiwan) (Unl)" - rom ( name "Gu Huo Lang II 2003 (Taiwan) (Unl).gbc" size 2097152 crc 74d71b0c sha1 fcbb769de0896ae69fe1063fa5d87cf079c8606a ) + name "Guaishou Go! Go! II (Taiwan) (Unl)" + description "Guaishou Go! Go! II (Taiwan) (Unl)" + rom ( name "Guaishou Go! Go! II (Taiwan) (Unl).gbc" size 1048576 crc b0237467 sha1 8ed6f39a9973d634266806455ef845b6bdb5ab95 ) ) game ( - name "Guai Shou Go! Go! II (Taiwan)" - description "Guai Shou Go! Go! II (Taiwan)" - rom ( name "Guai Shou Go! Go! II (Taiwan).gbc" size 1048576 crc b0237467 sha1 8ed6f39a9973d634266806455ef845b6bdb5ab95 ) + name "Guiwu Zhe 2 (Taiwan) (Unl)" + description "Guiwu Zhe 2 (Taiwan) (Unl)" + rom ( name "Guiwu Zhe 2 (Taiwan) (Unl).gbc" size 2097152 crc 955bc6ad sha1 5fe41ac064f4b4d4476ef4177a2f4d69fae1a933 ) ) game ( - name "Gui Wu Zhe 2 (Taiwan) (Unl)" - description "Gui Wu Zhe 2 (Taiwan) (Unl)" - rom ( name "Gui Wu Zhe 2 (Taiwan) (Unl).gbc" size 2097152 crc 955bc6ad sha1 5fe41ac064f4b4d4476ef4177a2f4d69fae1a933 ) -) - -game ( - name "Gun Law (World) (Aftermarket) (Homebrew)" - description "Gun Law (World) (Aftermarket) (Homebrew)" - rom ( name "Gun Law (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 3a0b6823 sha1 c49f7e0a5055f836528a7bc0cb6f45f221c76475 ) + name "Gun Law (World) (Aftermarket) (Unl)" + description "Gun Law (World) (Aftermarket) (Unl)" + rom ( name "Gun Law (World) (Aftermarket) (Unl).gbc" size 262144 crc 3a0b6823 sha1 c49f7e0a5055f836528a7bc0cb6f45f221c76475 ) ) game ( @@ -36637,27 +37564,9 @@ game ( ) game ( - name "Ha Li Bo Te 2 - Xiao Shi De Mi Shi 2003 (Taiwan) (Unl)" - description "Ha Li Bo Te 2 - Xiao Shi De Mi Shi 2003 (Taiwan) (Unl)" - rom ( name "Ha Li Bo Te 2 - Xiao Shi De Mi Shi 2003 (Taiwan) (Unl).gbc" size 2097152 crc 1b3e1243 sha1 0829cbcab3ef5d03c94d5efe769f4f4a93ac47fa ) -) - -game ( - name "Ha Li Xiao Zi Di Er Bu - Mi Shi De Mi (Taiwan) (Unl)" - description "Ha Li Xiao Zi Di Er Bu - Mi Shi De Mi (Taiwan) (Unl)" - rom ( name "Ha Li Xiao Zi Di Er Bu - Mi Shi De Mi (Taiwan) (Unl).gbc" size 2097152 crc 859457c8 sha1 3844b61eb455507d634ee7048dc8c9e64216fffe ) -) - -game ( - name "Ha Li Xiao Zi IV 2003 (Taiwan) (Unl)" - description "Ha Li Xiao Zi IV 2003 (Taiwan) (Unl)" - rom ( name "Ha Li Xiao Zi IV 2003 (Taiwan) (Unl).gbc" size 2097152 crc db3c8b95 sha1 0d230b20388f4396d6345b8eb59f54fc169ad8ee ) -) - -game ( - name "Hai Zhan Qi Bing (Taiwan) (Unl)" - description "Hai Zhan Qi Bing (Taiwan) (Unl)" - rom ( name "Hai Zhan Qi Bing (Taiwan) (Unl).gbc" size 2097152 crc a962ad73 sha1 36f6454e3e70bb29f27782f2ec4200d5aef5ca98 ) + name "Haizhan Qibing (Taiwan) (Unl)" + description "Haizhan Qibing (Taiwan) (Unl)" + rom ( name "Haizhan Qibing (Taiwan) (Unl).gbc" size 2097152 crc a962ad73 sha1 36f6454e3e70bb29f27782f2ec4200d5aef5ca98 ) ) game ( @@ -36666,6 +37575,12 @@ game ( rom ( name "Hajimari no Mori (Japan) (Proto).gbc" size 2097152 crc fecc8ee8 sha1 75a3e1794fd6bd6d35f0a357a82612c016dfba24 ) ) +game ( + name "Hali Xiaozi Dier Bu - Mishi de Mi (Taiwan) (Unl)" + description "Hali Xiaozi Dier Bu - Mishi de Mi (Taiwan) (Unl)" + rom ( name "Hali Xiaozi Dier Bu - Mishi de Mi (Taiwan) (Unl).gbc" size 2097152 crc 859457c8 sha1 3844b61eb455507d634ee7048dc8c9e64216fffe ) +) + game ( name "Halloween Racer (Europe) (En,Fr,De,Es,It,Pt)" description "Halloween Racer (Europe) (En,Fr,De,Es,It,Pt)" @@ -36774,6 +37689,12 @@ game ( rom ( name "Hang Time Basketball (Europe) (Unl).gbc" size 262144 crc 3207b7d9 sha1 340201c045c020be6de3504fdecbce1ff07a2139 ) ) +game ( + name "Harbour Attack (World) (Aftermarket) (Unl)" + description "Harbour Attack (World) (Aftermarket) (Unl)" + rom ( name "Harbour Attack (World) (Aftermarket) (Unl).gbc" size 262144 crc 7455782e sha1 1d0b3779007ba8f34e11b189552955dbf5c75a28 ) +) + game ( name "Harley-Davidson Motor Cycles - Race Across America (USA)" description "Harley-Davidson Motor Cycles - Race Across America (USA)" @@ -36804,12 +37725,6 @@ game ( rom ( name "Harry Potter 3 - Shen Qi Zhi Guang Lun (Taiwan) (Unl).gbc" size 524288 crc aa83a110 sha1 7e572f24a302902cd78b238be5732d838bada7b2 ) ) -game ( - name "Harry Potter 3 2003 (Taiwan) (Unl)" - description "Harry Potter 3 2003 (Taiwan) (Unl)" - rom ( name "Harry Potter 3 2003 (Taiwan) (Unl).gbc" size 1048576 crc 4ea2c869 sha1 739c0e5dc0efd4ddb912236a6f630c3d6987d064 ) -) - game ( name "Harry Potter and the Chamber of Secrets (USA, Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,Da)" description "Harry Potter and the Chamber of Secrets (USA, Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,Da)" @@ -36817,9 +37732,9 @@ game ( ) game ( - name "Harry Potter and the Sorcerer's Stone (Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,No,Da,Fi)" - description "Harry Potter and the Sorcerer's Stone (Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,No,Da,Fi)" - rom ( name "Harry Potter and the Sorcerer's Stone (Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,No,Da,Fi).gbc" size 4194304 crc 4fd8b7c5 sha1 4e6f676ec15e0e6238cb81853b5a74bbb20657a1 ) + name "Harry Potter and the Sorcerer's Stone (USA, Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,No,Da,Fi)" + description "Harry Potter and the Sorcerer's Stone (USA, Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,No,Da,Fi)" + rom ( name "Harry Potter and the Sorcerer's Stone (USA, Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,No,Da,Fi).gbc" size 4194304 crc 4fd8b7c5 sha1 4e6f676ec15e0e6238cb81853b5a74bbb20657a1 flags verified ) ) game ( @@ -36877,9 +37792,9 @@ game ( ) game ( - name "He Jin Zhuang Bei II (Taiwan) (Unl)" - description "He Jin Zhuang Bei II (Taiwan) (Unl)" - rom ( name "He Jin Zhuang Bei II (Taiwan) (Unl).gbc" size 2097152 crc ef4bbb34 sha1 6a15fc7dbb3b9d7c130b9a25b568d39c655d68ca ) + name "Hejin Zhuangbei II (Taiwan) (Unl)" + description "Hejin Zhuangbei II (Taiwan) (Unl)" + rom ( name "Hejin Zhuangbei II (Taiwan) (Unl).gbc" size 2097152 crc ef4bbb34 sha1 6a15fc7dbb3b9d7c130b9a25b568d39c655d68ca ) ) game ( @@ -36973,9 +37888,15 @@ game ( ) game ( - name "High Noon (World) (Aftermarket) (Homebrew)" - description "High Noon (World) (Aftermarket) (Homebrew)" - rom ( name "High Noon (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 1d00e48d sha1 21d5e871ed009a674659344d3d768fb2389c0d04 ) + name "High Noon (World) (Aftermarket) (Unl)" + description "High Noon (World) (Aftermarket) (Unl)" + rom ( name "High Noon (World) (Aftermarket) (Unl).gbc" size 262144 crc 1d00e48d sha1 21d5e871ed009a674659344d3d768fb2389c0d04 ) +) + +game ( + name "Hime's Quest (World) (Aftermarket) (Unl)" + description "Hime's Quest (World) (Aftermarket) (Unl)" + rom ( name "Hime's Quest (World) (Aftermarket) (Unl).gbc" size 2097152 crc de5c21c2 sha1 7b6672c46c23aaa282bd4fded0713c6dfae2eb66 ) ) game ( @@ -37057,9 +37978,9 @@ game ( ) game ( - name "Hugo (World) (Aftermarket) (Homebrew)" - description "Hugo (World) (Aftermarket) (Homebrew)" - rom ( name "Hugo (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 6581c78a sha1 390d2665f34ceaa5e34cc9f8f7c26d9e7583a92b ) + name "Hugo (World) (Aftermarket) (Unl)" + description "Hugo (World) (Aftermarket) (Unl)" + rom ( name "Hugo (World) (Aftermarket) (Unl).gbc" size 524288 crc 6581c78a sha1 390d2665f34ceaa5e34cc9f8f7c26d9e7583a92b ) ) game ( @@ -37279,9 +38200,9 @@ game ( ) game ( - name "Jeremy McGrath Supercross 2000 (Japan) (En) (NP)" - description "Jeremy McGrath Supercross 2000 (Japan) (En) (NP)" - rom ( name "Jeremy McGrath Supercross 2000 (Japan) (En) (NP).gbc" size 1048576 crc 666d2a75 sha1 dd204c1d47290f52ef264ce32d59cb5f58a2701d ) + name "Jeremy McGrath Supercross 2000 (Japan) (En) (Possible Proto) (NP)" + description "Jeremy McGrath Supercross 2000 (Japan) (En) (Possible Proto) (NP)" + rom ( name "Jeremy McGrath Supercross 2000 (Japan) (En) (Possible Proto) (NP).gbc" size 1048576 crc 666d2a75 sha1 dd204c1d47290f52ef264ce32d59cb5f58a2701d ) ) game ( @@ -37345,9 +38266,9 @@ game ( ) game ( - name "Jing Ling Wang III (Taiwan) (Unl)" - description "Jing Ling Wang III (Taiwan) (Unl)" - rom ( name "Jing Ling Wang III (Taiwan) (Unl).gbc" size 2097152 crc f19e780c sha1 8ef6ceb1b7894f43caa5f422d62d6c437f81e56d ) + name "Jingling Wang III (Taiwan) (Unl)" + description "Jingling Wang III (Taiwan) (Unl)" + rom ( name "Jingling Wang III (Taiwan) (Unl).gbc" size 2097152 crc f19e780c sha1 8ef6ceb1b7894f43caa5f422d62d6c437f81e56d ) ) game ( @@ -37369,9 +38290,9 @@ game ( ) game ( - name "Jissen ni Yakudatsu Tsumego (Japan)" - description "Jissen ni Yakudatsu Tsumego (Japan)" - rom ( name "Jissen ni Yakudatsu Tsumego (Japan).gbc" size 262144 crc 69c6dbef sha1 f7ed6cdce7637a11d7fa7f5cb59b8f8ce131f9d1 ) + name "Jissen ni Yakudatsu Tsumego (Japan) (Possible Proto)" + description "Jissen ni Yakudatsu Tsumego (Japan) (Possible Proto)" + rom ( name "Jissen ni Yakudatsu Tsumego (Japan) (Possible Proto).gbc" size 262144 crc 69c6dbef sha1 f7ed6cdce7637a11d7fa7f5cb59b8f8ce131f9d1 ) ) game ( @@ -37392,6 +38313,12 @@ game ( rom ( name "Joryuu Janshi ni Chousen GB - Watashi-tachi ni Chousen Shitene! (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 9fa5cdb5 sha1 a332817b9561dcab7d1f3dc0cf300e1656b253c3 ) ) +game ( + name "Juedui Wuli (Taiwan) (Unl)" + description "Juedui Wuli (Taiwan) (Unl)" + rom ( name "Juedui Wuli (Taiwan) (Unl).gbc" size 2097152 crc f4d63a7e sha1 f0b9f02335ee89aa6189e687e6e759d8c49bc2b3 ) +) + game ( name "JumpStart Dino Adventure - Field Trip (USA)" description "JumpStart Dino Adventure - Field Trip (USA)" @@ -37527,7 +38454,13 @@ game ( game ( name "Kaseki Sousei Reborn II - Monster Digger (Japan) (SGB Enhanced) (GB Compatible)" description "Kaseki Sousei Reborn II - Monster Digger (Japan) (SGB Enhanced) (GB Compatible)" - rom ( name "Kaseki Sousei Reborn II - Monster Digger (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc bf80c897 sha1 857fb62dc310268e0b92e9383c6b6fd61aa33fa0 ) + rom ( name "Kaseki Sousei Reborn II - Monster Digger (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc bf80c897 sha1 857fb62dc310268e0b92e9383c6b6fd61aa33fa0 flags verified ) +) + +game ( + name "Katakis 3D (USA, Europe) (Proto)" + description "Katakis 3D (USA, Europe) (Proto)" + rom ( name "Katakis 3D (USA, Europe) (Proto).gbc" size 2097152 crc 7c4b3795 sha1 8cd4be6772c592bbc2908fa826c090ec122e384c ) ) game ( @@ -37543,9 +38476,9 @@ game ( ) game ( - name "Kawa no Nushi Tsuri 4 (Japan) (Rev 1) (Rumble Version) (SGB Enhanced, GB Compatible) (NP)" - description "Kawa no Nushi Tsuri 4 (Japan) (Rev 1) (Rumble Version) (SGB Enhanced, GB Compatible) (NP)" - rom ( name "Kawa no Nushi Tsuri 4 (Japan) (Rev 1) (Rumble Version) (SGB Enhanced, GB Compatible) (NP).gbc" size 1048576 crc 456d4dfc sha1 69e56fd73eb365dba87713c2a3ea2e74bd63f63c ) + name "Kawa no Nushi Tsuri 4 (Japan) (Rev 1) (Possible Proto) (Rumble Version) (SGB Enhanced, GB Compatible) (NP)" + description "Kawa no Nushi Tsuri 4 (Japan) (Rev 1) (Possible Proto) (Rumble Version) (SGB Enhanced, GB Compatible) (NP)" + rom ( name "Kawa no Nushi Tsuri 4 (Japan) (Rev 1) (Possible Proto) (Rumble Version) (SGB Enhanced, GB Compatible) (NP).gbc" size 1048576 crc 456d4dfc sha1 69e56fd73eb365dba87713c2a3ea2e74bd63f63c ) ) game ( @@ -37621,9 +38554,9 @@ game ( ) game ( - name "Kikstart (World) (Aftermarket) (Homebrew)" - description "Kikstart (World) (Aftermarket) (Homebrew)" - rom ( name "Kikstart (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 9a9f483b sha1 bc472506f73f0c1a3f0a610c0136b324b7635c7f ) + name "Kikstart (World) (Aftermarket) (Unl)" + description "Kikstart (World) (Aftermarket) (Unl)" + rom ( name "Kikstart (World) (Aftermarket) (Unl).gbc" size 524288 crc 9a9f483b sha1 bc472506f73f0c1a3f0a610c0136b324b7635c7f ) ) game ( @@ -37632,12 +38565,6 @@ game ( rom ( name "Kindaichi Shounen no Jikenbo - 10 Nenme no Shoutaijou (Japan) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc de52ffdc sha1 468ea148eb15eac3b771a2520e0b5248ea5085a1 ) ) -game ( - name "King Lion Advance III 2003 (USA) (Unl)" - description "King Lion Advance III 2003 (USA) (Unl)" - rom ( name "King Lion Advance III 2003 (USA) (Unl).gbc" size 2097152 crc 0c22466d sha1 7c43ccfca93cae23e04e58fa0d7d7400a9694e0e ) -) - game ( name "Kinniku Banzuke GB - Chousensha wa Kimida! (Japan) (SGB Enhanced) (GB Compatible)" description "Kinniku Banzuke GB - Chousensha wa Kimida! (Japan) (SGB Enhanced) (GB Compatible)" @@ -37723,9 +38650,9 @@ game ( ) game ( - name "Knit-Wit (World) (v1.1) (Aftermarket) (Homebrew)" - description "Knit-Wit (World) (v1.1) (Aftermarket) (Homebrew)" - rom ( name "Knit-Wit (World) (v1.1) (Aftermarket) (Homebrew).gbc" size 262144 crc f19d5e86 sha1 0f0e01a143182a9784c80a882ac206d02d8679d7 ) + name "Knit-Wit (World) (v1.1) (GB Compatible) (Aftermarket) (Unl)" + description "Knit-Wit (World) (v1.1) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Knit-Wit (World) (v1.1) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc f19d5e86 sha1 0f0e01a143182a9784c80a882ac206d02d8679d7 ) ) game ( @@ -37813,27 +38740,21 @@ game ( ) game ( - name "Kou Dai Guai Shou - Dong Zuo Pian (Taiwan) (En) (Unl)" - description "Kou Dai Guai Shou - Dong Zuo Pian (Taiwan) (En) (Unl)" - rom ( name "Kou Dai Guai Shou - Dong Zuo Pian (Taiwan) (En) (Unl).gbc" size 1048576 crc 14355371 sha1 f398086d1f8cb435a632e5ea45fb19e980b0568d ) + name "Koudai Guaishou - Dongzuo Pian (Taiwan) (En) (Unl)" + description "Koudai Guaishou - Dongzuo Pian (Taiwan) (En) (Unl)" + rom ( name "Koudai Guaishou - Dongzuo Pian (Taiwan) (En) (Unl).gbc" size 1048576 crc 14355371 sha1 f398086d1f8cb435a632e5ea45fb19e980b0568d ) ) game ( - name "Kou Dai Guai Shou - Dong Zuo Pian (Taiwan) (En) (Unl) (Alt)" - description "Kou Dai Guai Shou - Dong Zuo Pian (Taiwan) (En) (Unl) (Alt)" - rom ( name "Kou Dai Guai Shou - Dong Zuo Pian (Taiwan) (En) (Unl) (Alt).gbc" size 1048576 crc ec2fbdfd sha1 c7a1020797b57d292e16c5297199d977e025256b ) + name "Koudai Guaishou - Dongzuo Pian (Taiwan) (En) (Unl) (Alt)" + description "Koudai Guaishou - Dongzuo Pian (Taiwan) (En) (Unl) (Alt)" + rom ( name "Koudai Guaishou - Dongzuo Pian (Taiwan) (En) (Unl) (Alt).gbc" size 1048576 crc ec2fbdfd sha1 c7a1020797b57d292e16c5297199d977e025256b ) ) game ( - name "Kou Dai Guai Shou - Lan Bao Shi 2003 (Taiwan) (Unl)" - description "Kou Dai Guai Shou - Lan Bao Shi 2003 (Taiwan) (Unl)" - rom ( name "Kou Dai Guai Shou - Lan Bao Shi 2003 (Taiwan) (Unl).gbc" size 2097152 crc 4c76d4d8 sha1 d688201e6031b88f122ec753a37daedfb233b48d ) -) - -game ( - name "Kou Dai Yao Guai - Bai Jin Ban (Taiwan) (Unl)" - description "Kou Dai Yao Guai - Bai Jin Ban (Taiwan) (Unl)" - rom ( name "Kou Dai Yao Guai - Bai Jin Ban (Taiwan) (Unl).gbc" size 2097152 crc 998ad382 sha1 f9013985d7c40eccfa863e2ba808e64deaa4b384 ) + name "Koudai Yaoguai - Baijin Ban (Taiwan) (Unl)" + description "Koudai Yaoguai - Baijin Ban (Taiwan) (Unl)" + rom ( name "Koudai Yaoguai - Baijin Ban (Taiwan) (Unl).gbc" size 2097152 crc 998ad382 sha1 f9013985d7c40eccfa863e2ba808e64deaa4b384 ) ) game ( @@ -37861,9 +38782,9 @@ game ( ) game ( - name "Lao Fuzi Chuanqi (Taiwan) (Unl)" - description "Lao Fuzi Chuanqi (Taiwan) (Unl)" - rom ( name "Lao Fuzi Chuanqi (Taiwan) (Unl).gbc" size 2097152 crc aeca45be sha1 b7193fcb8b9b8958a2522be0d1a3874cffc1e1db ) + name "Laofuzi Chuanqi (Taiwan) (Unl)" + description "Laofuzi Chuanqi (Taiwan) (Unl)" + rom ( name "Laofuzi Chuanqi (Taiwan) (Unl).gbc" size 2097152 crc aeca45be sha1 b7193fcb8b9b8958a2522be0d1a3874cffc1e1db ) ) game ( @@ -37873,9 +38794,9 @@ game ( ) game ( - name "Laser Squad (World) (Aftermarket) (Homebrew)" - description "Laser Squad (World) (Aftermarket) (Homebrew)" - rom ( name "Laser Squad (World) (Aftermarket) (Homebrew).gbc" size 1048576 crc 32f24248 sha1 26f4efe636214e72ec724f8887ca79fbc8abad80 ) + name "Laser Squad (World) (Aftermarket) (Unl)" + description "Laser Squad (World) (Aftermarket) (Unl)" + rom ( name "Laser Squad (World) (Aftermarket) (Unl).gbc" size 1048576 crc 32f24248 sha1 26f4efe636214e72ec724f8887ca79fbc8abad80 ) ) game ( @@ -38131,9 +39052,9 @@ game ( ) game ( - name "Liberator (World) (Aftermarket) (Homebrew)" - description "Liberator (World) (Aftermarket) (Homebrew)" - rom ( name "Liberator (World) (Aftermarket) (Homebrew).gbc" size 262144 crc ebe70d3d sha1 e3afce5a2357732ed15d7f3c0900f7b48113efdc ) + name "Liberator (World) (Aftermarket) (Unl)" + description "Liberator (World) (Aftermarket) (Unl)" + rom ( name "Liberator (World) (Aftermarket) (Unl).gbc" size 262144 crc ebe70d3d sha1 e3afce5a2357732ed15d7f3c0900f7b48113efdc ) ) game ( @@ -38163,7 +39084,7 @@ game ( game ( name "Little Mermaid II, The - Pinball Frenzy (USA) (En,Fr,De,Es,It) (Rumble Version)" description "Little Mermaid II, The - Pinball Frenzy (USA) (En,Fr,De,Es,It) (Rumble Version)" - rom ( name "Little Mermaid II, The - Pinball Frenzy (USA) (En,Fr,De,Es,It) (Rumble Version).gbc" size 1048576 crc 364f9ccd sha1 0940a1a86127cf8228a6a015035d8189218f57db ) + rom ( name "Little Mermaid II, The - Pinball Frenzy (USA) (En,Fr,De,Es,It) (Rumble Version).gbc" size 1048576 crc 364f9ccd sha1 0940a1a86127cf8228a6a015035d8189218f57db flags verified ) ) game ( @@ -38184,6 +39105,12 @@ game ( rom ( name "LNF Stars 2001 (France).gbc" size 1048576 crc f8bf3ee7 sha1 07a0e1c0ddde6371dbaf25fd016bdc77c0eca090 ) ) +game ( + name "Loco-coco (World) (Aftermarket) (Unl)" + description "Loco-coco (World) (Aftermarket) (Unl)" + rom ( name "Loco-coco (World) (Aftermarket) (Unl).gbc" size 262144 crc f6b9fe4e sha1 8a8972ff3e208280a746a2015a20f67248180a1c ) +) + game ( name "Lode Runner - Domudomu Dan no Yabou (Japan) (GB Compatible)" description "Lode Runner - Domudomu Dan no Yabou (Japan) (GB Compatible)" @@ -38323,9 +39250,9 @@ game ( ) game ( - name "Loppi Puzzle Magazine - Kangaeru Puzzle Dai-2-gou (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP)" - description "Loppi Puzzle Magazine - Kangaeru Puzzle Dai-2-gou (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP)" - rom ( name "Loppi Puzzle Magazine - Kangaeru Puzzle Dai-2-gou (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP).gbc" size 262144 crc 27ab2187 sha1 49a63339bc253d1bd55f6db75f7324980572925a ) + name "Loppi Puzzle Magazine - Kangaeru Puzzle Dai-2-gou (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP) [b]" + description "Loppi Puzzle Magazine - Kangaeru Puzzle Dai-2-gou (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP) [b]" + rom ( name "Loppi Puzzle Magazine - Kangaeru Puzzle Dai-2-gou (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP) [b].gbc" size 262144 crc a30ad68d sha1 bbb3c1b9d0dcfabc177f443ea11efa6b6e3112e1 flags baddump ) ) game ( @@ -38431,15 +39358,15 @@ game ( ) game ( - name "Lunar Docking (World) (Aftermarket) (Homebrew)" - description "Lunar Docking (World) (Aftermarket) (Homebrew)" - rom ( name "Lunar Docking (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 9edd066c sha1 4d43a38e8b5df41309f5b6ec13e4829db739d980 ) + name "Lunar Docking (World) (Aftermarket) (Unl)" + description "Lunar Docking (World) (Aftermarket) (Unl)" + rom ( name "Lunar Docking (World) (Aftermarket) (Unl).gbc" size 262144 crc 9edd066c sha1 4d43a38e8b5df41309f5b6ec13e4829db739d980 ) ) game ( - name "Luo Ke Ying Xiong EXE5 (Taiwan) (Unl)" - description "Luo Ke Ying Xiong EXE5 (Taiwan) (Unl)" - rom ( name "Luo Ke Ying Xiong EXE5 (Taiwan) (Unl).gbc" size 2097152 crc 51f92403 sha1 c498a5fb261e921828831bff5c2c705453ea314e ) + name "Luoke Yingxiong EXE5 (Taiwan) (Unl)" + description "Luoke Yingxiong EXE5 (Taiwan) (Unl)" + rom ( name "Luoke Yingxiong EXE5 (Taiwan) (Unl).gbc" size 2097152 crc 51f92403 sha1 c498a5fb261e921828831bff5c2c705453ea314e ) ) game ( @@ -38467,15 +39394,21 @@ game ( ) game ( - name "Machine, The (USA) (v1.1) (Demo) (Aftermarket) (Homebrew)" - description "Machine, The (USA) (v1.1) (Demo) (Aftermarket) (Homebrew)" - rom ( name "Machine, The (USA) (v1.1) (Demo) (Aftermarket) (Homebrew).gbc" size 2097152 crc f55a9d95 sha1 2b22fbd76e11e6e45053b1e6989de303e3033c9e ) + name "Machine, The (USA) (v1.1) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Machine, The (USA) (v1.1) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Machine, The (USA) (v1.1) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc f55a9d95 sha1 2b22fbd76e11e6e45053b1e6989de303e3033c9e ) ) game ( - name "Machine, The (USA) (Demo) (Aftermarket) (Homebrew)" - description "Machine, The (USA) (Demo) (Aftermarket) (Homebrew)" - rom ( name "Machine, The (USA) (Demo) (Aftermarket) (Homebrew).gbc" size 2097152 crc 5662865e sha1 a5ac99a4087bd5ea45417e5b4a7c9af10433911a ) + name "Machine, The (USA) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Machine, The (USA) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Machine, The (USA) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc 5662865e sha1 a5ac99a4087bd5ea45417e5b4a7c9af10433911a ) +) + +game ( + name "Machine, The (World) (GB Compatible) (Aftermarket) (Unl)" + description "Machine, The (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Machine, The (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc da01eb3e sha1 cd9a27077ba7ed49d2f2878577cb6ff005d6f785 ) ) game ( @@ -38490,6 +39423,12 @@ game ( rom ( name "Madden NFL 2000 (USA, Europe) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 482944aa sha1 df03f3e5e3f6fd3905dc332b307b5566f0f35252 flags verified ) ) +game ( + name "Madden NFL 2000 (USA, Europe) (Beta) (SGB Enhanced) (GB Compatible)" + description "Madden NFL 2000 (USA, Europe) (Beta) (SGB Enhanced) (GB Compatible)" + rom ( name "Madden NFL 2000 (USA, Europe) (Beta) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc b3fead85 sha1 09bf3dea1dc01b018ba86e3affbcbab7b9d2eb63 ) +) + game ( name "Madden NFL 2001 (USA)" description "Madden NFL 2001 (USA)" @@ -38532,6 +39471,12 @@ game ( rom ( name "Magical Drop (USA).gbc" size 1048576 crc e4188a79 sha1 2106b22776a87825d3edb408503e719f4927c3e6 ) ) +game ( + name "Magical Drop (Europe) (En,Fr,De) (Beta)" + description "Magical Drop (Europe) (En,Fr,De) (Beta)" + rom ( name "Magical Drop (Europe) (En,Fr,De) (Beta).gbc" size 1048576 crc 799e2020 sha1 99fbf24ca924acf5a4ea64817fb29d6e408cb722 ) +) + game ( name "Magical Tetris Challenge (Europe) (En,Fr,De,Es,It,Nl,Sv)" description "Magical Tetris Challenge (Europe) (En,Fr,De,Es,It,Nl,Sv)" @@ -38551,9 +39496,9 @@ game ( ) game ( - name "Magician's Curse, The (World) (Aftermarket) (Homebrew)" - description "Magician's Curse, The (World) (Aftermarket) (Homebrew)" - rom ( name "Magician's Curse, The (World) (Aftermarket) (Homebrew).gbc" size 524288 crc f2b1967e sha1 3b5f97f9a0b4d63a3795695b0bda6e969de8e051 ) + name "Magician's Curse, The (World) (Aftermarket) (Unl)" + description "Magician's Curse, The (World) (Aftermarket) (Unl)" + rom ( name "Magician's Curse, The (World) (Aftermarket) (Unl).gbc" size 524288 crc f2b1967e sha1 3b5f97f9a0b4d63a3795695b0bda6e969de8e051 ) ) game ( @@ -38736,6 +39681,12 @@ game ( rom ( name "Maya the Bee & Her Friends (Europe) (En,Fr,De) (GB Compatible).gbc" size 1048576 crc 983b1d26 sha1 522d7f33fd39bd00f48208743fb2246ef0bd3ff1 ) ) +game ( + name "Mayhem (World) (Aftermarket) (Unl)" + description "Mayhem (World) (Aftermarket) (Unl)" + rom ( name "Mayhem (World) (Aftermarket) (Unl).gbc" size 262144 crc 4d739ad7 sha1 9f141728a74432820f47cc24d230dc56d6ac099b ) +) + game ( name "McDonald's Monogatari - Honobono Tenchou Ikusei Game (Japan)" description "McDonald's Monogatari - Honobono Tenchou Ikusei Game (Japan)" @@ -38817,7 +39768,7 @@ game ( game ( name "Mega Man Xtreme (USA, Europe) (GB Compatible)" description "Mega Man Xtreme (USA, Europe) (GB Compatible)" - rom ( name "Mega Man Xtreme (USA, Europe) (GB Compatible).gbc" size 1048576 crc 3a4d94d5 sha1 c877449ba0889fdcacf23c49b0611d0ca57283c5 ) + rom ( name "Mega Man Xtreme (USA, Europe) (GB Compatible).gbc" size 1048576 crc 3a4d94d5 sha1 c877449ba0889fdcacf23c49b0611d0ca57283c5 flags verified ) ) game ( @@ -38869,9 +39820,9 @@ game ( ) game ( - name "Memory Mania Challenge (World) (v1.3) (Aftermarket) (Homebrew)" - description "Memory Mania Challenge (World) (v1.3) (Aftermarket) (Homebrew)" - rom ( name "Memory Mania Challenge (World) (v1.3) (Aftermarket) (Homebrew).gbc" size 1048576 crc 50618f4b sha1 c8d74af4746313800df3a23c1ad01f931e0e2eca flags verified ) + name "Memory Mania Challenge (World) (v1.3) (GB Compatible) (Aftermarket) (Unl)" + description "Memory Mania Challenge (World) (v1.3) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Memory Mania Challenge (World) (v1.3) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc 50618f4b sha1 c8d74af4746313800df3a23c1ad01f931e0e2eca flags verified ) ) game ( @@ -38940,6 +39891,12 @@ game ( rom ( name "Metamode (Japan).gbc" size 2097152 crc a76eed5b sha1 1c1d74c810b90cf4d2ee32859eaea569a5b45c3f ) ) +game ( + name "Meteorite (World) (Aftermarket) (Unl)" + description "Meteorite (World) (Aftermarket) (Unl)" + rom ( name "Meteorite (World) (Aftermarket) (Unl).gbc" size 131072 crc fafbcebf sha1 25a81d916f1ed80a2bca2fe7a555433f06d025e3 ) +) + game ( name "Mia Hamm Soccer Shootout (USA)" description "Mia Hamm Soccer Shootout (USA)" @@ -38964,6 +39921,12 @@ game ( rom ( name "Micro Machines 1 and 2 - Twin Turbo (USA, Europe).gbc" size 2097152 crc 5dd337eb sha1 18b5c14ee3b3f3c16b9d641c3b3824b15c0a0c78 ) ) +game ( + name "Micro Machines 2 - Turbo Tournament (Europe) (Proto)" + description "Micro Machines 2 - Turbo Tournament (Europe) (Proto)" + rom ( name "Micro Machines 2 - Turbo Tournament (Europe) (Proto).gbc" size 2097152 crc e932c818 sha1 cbdefc7f63bf9a6c0fc15d7ab72b4b532b0d2422 ) +) + game ( name "Micro Machines V3 (USA, Europe)" description "Micro Machines V3 (USA, Europe)" @@ -39061,9 +40024,9 @@ game ( ) game ( - name "Mirror Between Us, The (World) (Aftermarket) (Homebrew)" - description "Mirror Between Us, The (World) (Aftermarket) (Homebrew)" - rom ( name "Mirror Between Us, The (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 854f4297 sha1 a1a44ce8052f06349df3cbdf9a6d976609213d68 ) + name "Mirror Between Us, The (World) (GB Compatible) (Aftermarket) (Unl)" + description "Mirror Between Us, The (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Mirror Between Us, The (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 854f4297 sha1 a1a44ce8052f06349df3cbdf9a6d976609213d68 ) ) game ( @@ -39091,9 +40054,9 @@ game ( ) game ( - name "Mission - Impossible (Europe) (En,Fr,De,Es,It) (Rev 1)" - description "Mission - Impossible (Europe) (En,Fr,De,Es,It) (Rev 1)" - rom ( name "Mission - Impossible (Europe) (En,Fr,De,Es,It) (Rev 1).gbc" size 1048576 crc 9c51f4c7 sha1 f822bda01d881f34d1e17664465faf6a3ff64356 ) + name "Mission - Impossible (Europe) (En,Fr,De,Es,It) (Rev 1) (Possible Proto)" + description "Mission - Impossible (Europe) (En,Fr,De,Es,It) (Rev 1) (Possible Proto)" + rom ( name "Mission - Impossible (Europe) (En,Fr,De,Es,It) (Rev 1) (Possible Proto).gbc" size 1048576 crc 9c51f4c7 sha1 f822bda01d881f34d1e17664465faf6a3ff64356 ) ) game ( @@ -39103,9 +40066,9 @@ game ( ) game ( - name "Mission to Mars (World) (Aftermarket) (Homebrew)" - description "Mission to Mars (World) (Aftermarket) (Homebrew)" - rom ( name "Mission to Mars (World) (Aftermarket) (Homebrew).gbc" size 262144 crc f651fcf4 sha1 479c70dd9c63cdcad99ca44720a9e0e2582df119 ) + name "Mission to Mars (World) (Aftermarket) (Unl)" + description "Mission to Mars (World) (Aftermarket) (Unl)" + rom ( name "Mission to Mars (World) (Aftermarket) (Unl).gbc" size 262144 crc f651fcf4 sha1 479c70dd9c63cdcad99ca44720a9e0e2582df119 ) ) game ( @@ -39115,21 +40078,9 @@ game ( ) game ( - name "Mo Jie 3 - Bu Wanme De Shijie (Taiwan) (Unl)" - description "Mo Jie 3 - Bu Wanme De Shijie (Taiwan) (Unl)" - rom ( name "Mo Jie 3 - Bu Wanme De Shijie (Taiwan) (Unl).gbc" size 524288 crc d91e3f98 sha1 556913f2687a49034e1229ef1e375f6036ec5719 ) -) - -game ( - name "Mo Jie Chuan Shuo (Taiwan) (Unl)" - description "Mo Jie Chuan Shuo (Taiwan) (Unl)" - rom ( name "Mo Jie Chuan Shuo (Taiwan) (Unl).gbc" size 524288 crc 7aa7eea5 sha1 109e4ba61b04afe15cc5a5ee994f56405938cee3 ) -) - -game ( - name "Mo Shou Shi Ji - Zhan Shen (Taiwan) (Unl)" - description "Mo Shou Shi Ji - Zhan Shen (Taiwan) (Unl)" - rom ( name "Mo Shou Shi Ji - Zhan Shen (Taiwan) (Unl).gbc" size 2097152 crc 80c4fd52 sha1 d2569f8176164cef1cc2a8bd94949ea9cd7dbbea ) + name "Mo Jie 3-bu Wanme de Shijie (Taiwan) (Unl)" + description "Mo Jie 3-bu Wanme de Shijie (Taiwan) (Unl)" + rom ( name "Mo Jie 3-bu Wanme de Shijie (Taiwan) (Unl).gbc" size 524288 crc d91e3f98 sha1 556913f2687a49034e1229ef1e375f6036ec5719 ) ) game ( @@ -39144,6 +40095,12 @@ game ( rom ( name "Mobile Trainer (Japan).gbc" size 2097152 crc 7226ead0 sha1 ecc0579edeaf9eccd722d605cc288cd023c8576a flags verified ) ) +game ( + name "Mojie Chuanshuo (Taiwan) (Unl)" + description "Mojie Chuanshuo (Taiwan) (Unl)" + rom ( name "Mojie Chuanshuo (Taiwan) (Unl).gbc" size 524288 crc 7aa7eea5 sha1 109e4ba61b04afe15cc5a5ee994f56405938cee3 ) +) + game ( name "Momotarou Densetsu 1-2 (Japan)" description "Momotarou Densetsu 1-2 (Japan)" @@ -39151,9 +40108,15 @@ game ( ) game ( - name "Mona and the Witch's Hat Deluxe (World) (Aftermarket) (Homebrew)" - description "Mona and the Witch's Hat Deluxe (World) (Aftermarket) (Homebrew)" - rom ( name "Mona and the Witch's Hat Deluxe (World) (Aftermarket) (Homebrew).gbc" size 131072 crc 39480c79 sha1 4348a7903e538e5367f2a60718f43cbeeccf96df ) + name "Mona and the Witch's Hat Deluxe (World) (GB Compatible) (Aftermarket) (Unl)" + description "Mona and the Witch's Hat Deluxe (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Mona and the Witch's Hat Deluxe (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 131072 crc 39480c79 sha1 4348a7903e538e5367f2a60718f43cbeeccf96df ) +) + +game ( + name "Monkey Magic (World) (Aftermarket) (Unl)" + description "Monkey Magic (World) (Aftermarket) (Unl)" + rom ( name "Monkey Magic (World) (Aftermarket) (Unl).gbc" size 262144 crc c9c68715 sha1 bf5dc5593334fe1114461065a2b7284d28b31462 ) ) game ( @@ -39183,7 +40146,7 @@ game ( game ( name "Monster Farm Battle Card GB (Japan) (SGB Enhanced) (GB Compatible)" description "Monster Farm Battle Card GB (Japan) (SGB Enhanced) (GB Compatible)" - rom ( name "Monster Farm Battle Card GB (Japan) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc 69b88f1e sha1 7e036e175c9e865572ab613ae0f8e6d3642ffb0e ) + rom ( name "Monster Farm Battle Card GB (Japan) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc 69b88f1e sha1 7e036e175c9e865572ab613ae0f8e6d3642ffb0e flags verified ) ) game ( @@ -39217,9 +40180,9 @@ game ( ) game ( - name "Monster Traveler (Japan)" - description "Monster Traveler (Japan)" - rom ( name "Monster Traveler (Japan).gbc" size 4194304 crc 5d596cf6 sha1 5195f55ad6aaa302c0a0e11c0ec6ecad4efe0e27 ) + name "Monster Traveler (Japan) (Possible Proto)" + description "Monster Traveler (Japan) (Possible Proto)" + rom ( name "Monster Traveler (Japan) (Possible Proto).gbc" size 4194304 crc 5d596cf6 sha1 5195f55ad6aaa302c0a0e11c0ec6ecad4efe0e27 ) ) game ( @@ -39259,9 +40222,9 @@ game ( ) game ( - name "Monty on the Run (World) (Aftermarket) (Homebrew)" - description "Monty on the Run (World) (Aftermarket) (Homebrew)" - rom ( name "Monty on the Run (World) (Aftermarket) (Homebrew).gbc" size 524288 crc fd1066ac sha1 8390175e6be4731d9afa91715d7f8dd11693bb69 ) + name "Monty on the Run (World) (Aftermarket) (Unl)" + description "Monty on the Run (World) (Aftermarket) (Unl)" + rom ( name "Monty on the Run (World) (Aftermarket) (Unl).gbc" size 524288 crc fd1066ac sha1 8390175e6be4731d9afa91715d7f8dd11693bb69 ) ) game ( @@ -39306,6 +40269,12 @@ game ( rom ( name "Mortal Kombat 4 (USA, Europe) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 4eb71448 sha1 ea54fb35cd8c4d4cea234423a81f8f953b1d33e4 ) ) +game ( + name "Moshou Shiji - Zhanshen (Taiwan) (Unl)" + description "Moshou Shiji - Zhanshen (Taiwan) (Unl)" + rom ( name "Moshou Shiji - Zhanshen (Taiwan) (Unl).gbc" size 2097152 crc 80c4fd52 sha1 d2569f8176164cef1cc2a8bd94949ea9cd7dbbea ) +) + game ( name "Motocross Maniacs 2 (USA)" description "Motocross Maniacs 2 (USA)" @@ -39313,9 +40282,9 @@ game ( ) game ( - name "Mr. Angry (World) (Aftermarket) (Homebrew)" - description "Mr. Angry (World) (Aftermarket) (Homebrew)" - rom ( name "Mr. Angry (World) (Aftermarket) (Homebrew).gbc" size 524288 crc b85e63a5 sha1 127ad5315e2bce37614a1e9f8605798e68718926 ) + name "Mr. Angry (World) (Aftermarket) (Unl)" + description "Mr. Angry (World) (Aftermarket) (Unl)" + rom ( name "Mr. Angry (World) (Aftermarket) (Unl).gbc" size 524288 crc b85e63a5 sha1 127ad5315e2bce37614a1e9f8605798e68718926 ) ) game ( @@ -39385,9 +40354,9 @@ game ( ) game ( - name "Mu Chang Wu Yu GB 6 (Taiwan) (Unl)" - description "Mu Chang Wu Yu GB 6 (Taiwan) (Unl)" - rom ( name "Mu Chang Wu Yu GB 6 (Taiwan) (Unl).gbc" size 2097152 crc 416e6efa sha1 86b2de0fc2806f3d13145e2c0296389ec0897a6a ) + name "Muchang Wuyu GB 6 (Taiwan) (Unl)" + description "Muchang Wuyu GB 6 (Taiwan) (Unl)" + rom ( name "Muchang Wuyu GB 6 (Taiwan) (Unl).gbc" size 2097152 crc 416e6efa sha1 86b2de0fc2806f3d13145e2c0296389ec0897a6a ) ) game ( @@ -39495,7 +40464,7 @@ game ( game ( name "Nakayoshi Pet Series 4 - Kawaii Koneko (Japan)" description "Nakayoshi Pet Series 4 - Kawaii Koneko (Japan)" - rom ( name "Nakayoshi Pet Series 4 - Kawaii Koneko (Japan).gbc" size 1048576 crc 1a382367 sha1 e5d5f1092e991a7538b9d24a12de35da21e23edd ) + rom ( name "Nakayoshi Pet Series 4 - Kawaii Koneko (Japan).gbc" size 1048576 crc 1a382367 sha1 e5d5f1092e991a7538b9d24a12de35da21e23edd flags verified ) ) game ( @@ -39559,9 +40528,9 @@ game ( ) game ( - name "NBA in the Zone (USA) (SGB Enhanced) (GB Compatible)" - description "NBA in the Zone (USA) (SGB Enhanced) (GB Compatible)" - rom ( name "NBA in the Zone (USA) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc f6bae9a0 sha1 7f0d5a37956ae58077656a42b26629784c203038 ) + name "NBA in the Zone (USA) (Possible Proto) (SGB Enhanced) (GB Compatible)" + description "NBA in the Zone (USA) (Possible Proto) (SGB Enhanced) (GB Compatible)" + rom ( name "NBA in the Zone (USA) (Possible Proto) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc f6bae9a0 sha1 7f0d5a37956ae58077656a42b26629784c203038 ) ) game ( @@ -39607,15 +40576,15 @@ game ( ) game ( - name "Neclaus' Quest (World) (v1.0) (Aftermarket) (Homebrew)" - description "Neclaus' Quest (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Neclaus' Quest (World) (v1.0) (Aftermarket) (Homebrew).gbc" size 1048576 crc eedefa0c sha1 e63c16fe987fe655408be66198366a0147ac607c ) + name "Neclaus' Quest (World) (v1.0) (GB Compatible) (Aftermarket) (Unl)" + description "Neclaus' Quest (World) (v1.0) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Neclaus' Quest (World) (v1.0) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc eedefa0c sha1 e63c16fe987fe655408be66198366a0147ac607c ) ) game ( - name "Neclaus' Quest (World) (v1.1) (Aftermarket) (Homebrew)" - description "Neclaus' Quest (World) (v1.1) (Aftermarket) (Homebrew)" - rom ( name "Neclaus' Quest (World) (v1.1) (Aftermarket) (Homebrew).gbc" size 1048576 crc 53cf930c sha1 bd3272ca7ead64da3f68f774661a560add433b94 ) + name "Neclaus' Quest (World) (v1.1) (GB Compatible) (Aftermarket) (Unl)" + description "Neclaus' Quest (World) (v1.1) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Neclaus' Quest (World) (v1.1) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc 53cf930c sha1 bd3272ca7ead64da3f68f774661a560add433b94 ) ) game ( @@ -39708,6 +40677,12 @@ game ( rom ( name "Nicktoons Racing (USA).gbc" size 1048576 crc a3f079d4 sha1 8b023f596d9b37ae442578c02c6ba859bc81c5c6 ) ) +game ( + name "Ninja JaJaMaru - The Great World Adventure DX (USA, Europe) (Ninja JaJaMaru Retro Collection) (Switch)" + description "Ninja JaJaMaru - The Great World Adventure DX (USA, Europe) (Ninja JaJaMaru Retro Collection) (Switch)" + rom ( name "Ninja JaJaMaru - The Great World Adventure DX (USA, Europe) (Ninja JaJaMaru Retro Collection) (Switch).gbc" size 262144 crc b5af4ca5 sha1 73efaca14c998dd6790e08f2ce2b6f73e7909ce4 flags verified ) +) + game ( name "Nintama Rantarou - Ninjutsu Gakuen ni Nyuugaku Shiyou no Dan (Japan)" description "Nintama Rantarou - Ninjutsu Gakuen ni Nyuugaku Shiyou no Dan (Japan)" @@ -39751,9 +40726,51 @@ game ( ) game ( - name "Nuwang Gedou (Taiwan) (Unl)" - description "Nuwang Gedou (Taiwan) (Unl)" - rom ( name "Nuwang Gedou (Taiwan) (Unl).gbc" size 2097152 crc e1668b49 sha1 37516139aa317a16440379c1dc00bdfc4c1e607a ) + name "Nv Wang Gedou 2000 (Taiwan) (Unl)" + description "Nv Wang Gedou 2000 (Taiwan) (Unl)" + rom ( name "Nv Wang Gedou 2000 (Taiwan) (Unl).gbc" size 2097152 crc e1668b49 sha1 37516139aa317a16440379c1dc00bdfc4c1e607a flags verified ) +) + +game ( + name "Nyghtmare - The Ninth King (World) (v0.2.6) (Beta) (Aftermarket) (Unl)" + description "Nyghtmare - The Ninth King (World) (v0.2.6) (Beta) (Aftermarket) (Unl)" + rom ( name "Nyghtmare - The Ninth King (World) (v0.2.6) (Beta) (Aftermarket) (Unl).gbc" size 1048576 crc e983e8d1 sha1 fc2a58d49327c88c895ae6681bde57e1a2687974 flags verified ) +) + +game ( + name "Nyghtmare - The Ninth King (World) (v0.1.2) (Beta, Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Nyghtmare - The Ninth King (World) (v0.1.2) (Beta, Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Nyghtmare - The Ninth King (World) (v0.1.2) (Beta, Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 524288 crc ef42955e sha1 485b4d672d9edda40f9191f5f5cc223046000e67 flags verified ) +) + +game ( + name "Nyghtmare - The Ninth King (World) (v0.2.1) (Beta) (Aftermarket) (Unl)" + description "Nyghtmare - The Ninth King (World) (v0.2.1) (Beta) (Aftermarket) (Unl)" + rom ( name "Nyghtmare - The Ninth King (World) (v0.2.1) (Beta) (Aftermarket) (Unl).gbc" size 1048576 crc 581efe4d sha1 125bb0564ee17f8e55046a1c25e53ee9a4254303 ) +) + +game ( + name "Nyghtmare - The Ninth King (World) (v0.2.3) (Beta) (Aftermarket) (Unl)" + description "Nyghtmare - The Ninth King (World) (v0.2.3) (Beta) (Aftermarket) (Unl)" + rom ( name "Nyghtmare - The Ninth King (World) (v0.2.3) (Beta) (Aftermarket) (Unl).gbc" size 1048576 crc e26ddbbb sha1 853115369b8ca583d038d00c1dde59b6a74b73a2 ) +) + +game ( + name "Nyghtmare - The Ninth King (World) (v0.2.5) (Beta) (Aftermarket) (Unl)" + description "Nyghtmare - The Ninth King (World) (v0.2.5) (Beta) (Aftermarket) (Unl)" + rom ( name "Nyghtmare - The Ninth King (World) (v0.2.5) (Beta) (Aftermarket) (Unl).gbc" size 1048576 crc 1e8ac5b9 sha1 99c11bef7feb17a9e7212cf9b6593478f671bc32 ) +) + +game ( + name "Nyghtmare - The Ninth King (World) (Free Version) (GB Compatible) (Aftermarket) (Unl)" + description "Nyghtmare - The Ninth King (World) (Free Version) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Nyghtmare - The Ninth King (World) (Free Version) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc f5df28c2 sha1 7b511444e1eb86fffcf93599a78e0e2c44aecc8a ) +) + +game ( + name "Nyghtmare - The Ninth King (World) (Rev 1) (Free Version) (Aftermarket) (Unl)" + description "Nyghtmare - The Ninth King (World) (Rev 1) (Free Version) (Aftermarket) (Unl)" + rom ( name "Nyghtmare - The Ninth King (World) (Rev 1) (Free Version) (Aftermarket) (Unl).gbc" size 2097152 crc 63bddc68 sha1 fab777e607bd8aba70eb65829143039dbe6ac08e ) ) game ( @@ -39811,15 +40828,15 @@ game ( ) game ( - name "Olympic Skier (World) (Aftermarket) (Homebrew)" - description "Olympic Skier (World) (Aftermarket) (Homebrew)" - rom ( name "Olympic Skier (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 5e81cef7 sha1 a7a552a9eb984098a67e063c2eca907eec000fc1 ) + name "Olympic Skier (World) (Aftermarket) (Unl)" + description "Olympic Skier (World) (Aftermarket) (Unl)" + rom ( name "Olympic Skier (World) (Aftermarket) (Unl).gbc" size 524288 crc 5e81cef7 sha1 a7a552a9eb984098a67e063c2eca907eec000fc1 ) ) game ( - name "Opossum Country (World) (Aftermarket) (Homebrew)" - description "Opossum Country (World) (Aftermarket) (Homebrew)" - rom ( name "Opossum Country (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 7348bb32 sha1 e63e06d602473206b21138f5322acf86b5b6b9b8 ) + name "Opossum Country (World) (GB Compatible) (Aftermarket) (Unl)" + description "Opossum Country (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Opossum Country (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 524288 crc 7348bb32 sha1 e63e06d602473206b21138f5322acf86b5b6b9b8 ) ) game ( @@ -39858,6 +40875,12 @@ game ( rom ( name "Ou Dorobou Jing - Devil Version (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 2a39a874 sha1 71797aa125b88614182ce3d0f8252d6ed8cfa08e ) ) +game ( + name "Out on a Limb (World) (Aftermarket) (Unl)" + description "Out on a Limb (World) (Aftermarket) (Unl)" + rom ( name "Out on a Limb (World) (Aftermarket) (Unl).gbc" size 262144 crc a6671528 sha1 f21cb71adcea1029151930eb98c4e9b446e4cfee ) +) + game ( name "Owarai Yoiko no Geemumichi - Oyaji Sagashite 3 Choume (Japan) (SGB Enhanced) (GB Compatible)" description "Owarai Yoiko no Geemumichi - Oyaji Sagashite 3 Choume (Japan) (SGB Enhanced) (GB Compatible)" @@ -39867,7 +40890,7 @@ game ( game ( name "Pac-Man - Special Color Edition (USA) (SGB Enhanced) (GB Compatible)" description "Pac-Man - Special Color Edition (USA) (SGB Enhanced) (GB Compatible)" - rom ( name "Pac-Man - Special Color Edition (USA) (SGB Enhanced) (GB Compatible).gbc" size 262144 crc 3485ef86 sha1 a0a6f55c15dca60350b3a74b1973cc13ff328730 ) + rom ( name "Pac-Man - Special Color Edition (USA) (SGB Enhanced) (GB Compatible).gbc" size 262144 crc 3485ef86 sha1 a0a6f55c15dca60350b3a74b1973cc13ff328730 flags verified ) ) game ( @@ -39901,9 +40924,9 @@ game ( ) game ( - name "Panik!16 (World) (Aftermarket) (Homebrew)" - description "Panik!16 (World) (Aftermarket) (Homebrew)" - rom ( name "Panik!16 (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 7b31122d sha1 98d74fd64e4f7e835f1101b05816b8938a34a416 ) + name "Panik!16 (World) (Aftermarket) (Unl)" + description "Panik!16 (World) (Aftermarket) (Unl)" + rom ( name "Panik!16 (World) (Aftermarket) (Unl).gbc" size 262144 crc 7b31122d sha1 98d74fd64e4f7e835f1101b05816b8938a34a416 ) ) game ( @@ -39939,7 +40962,7 @@ game ( game ( name "Peugeot - Orbital Diagnostic System (Unknown) (Unl) [b]" description "Peugeot - Orbital Diagnostic System (Unknown) (Unl) [b]" - rom ( name "Peugeot - Orbital Diagnostic System (Unknown) (Unl) [b].gbc" size 32768 crc 731c6ff4 sha1 2d4f9dc954d9485fbd7d402e07edf42beebbeb8c flags baddump ) + rom ( name "Peugeot - Orbital Diagnostic System (Unknown) (Unl) [b].gbc" size 1048576 crc f971fce9 sha1 6bc140722fad759830fd6e4b6ac0b41443795fb2 flags baddump ) ) game ( @@ -39961,27 +40984,27 @@ game ( ) game ( - name "Pian Wai Zhang Huang Jin Tai Yang - Feng Yin De Yuan Gu Lian Jin Shu (Taiwan) (Unl)" - description "Pian Wai Zhang Huang Jin Tai Yang - Feng Yin De Yuan Gu Lian Jin Shu (Taiwan) (Unl)" - rom ( name "Pian Wai Zhang Huang Jin Tai Yang - Feng Yin De Yuan Gu Lian Jin Shu (Taiwan) (Unl).gbc" size 2097152 crc 0db9fdfa sha1 6d2acdca141001bfefdcbf0076702fec6619e368 ) + name "Pian Wai Zhang - Huangjin Taiyang - Fengyin de Yuangu Lianjin Shu (Taiwan) (Unl)" + description "Pian Wai Zhang - Huangjin Taiyang - Fengyin de Yuangu Lianjin Shu (Taiwan) (Unl)" + rom ( name "Pian Wai Zhang - Huangjin Taiyang - Fengyin de Yuangu Lianjin Shu (Taiwan) (Unl).gbc" size 2097152 crc 0db9fdfa sha1 6d2acdca141001bfefdcbf0076702fec6619e368 ) ) game ( - name "Pie Crust (World) (v1.0) (Homebrew)" - description "Pie Crust (World) (v1.0) (Homebrew)" - rom ( name "Pie Crust (World) (v1.0) (Homebrew).gbc" size 32768 crc f39c8119 sha1 b01cad9652bf1c65151dd8d2bd21251a9cf44d43 ) + name "Pie Crust (World) (v1.0) (Unl)" + description "Pie Crust (World) (v1.0) (Unl)" + rom ( name "Pie Crust (World) (v1.0) (Unl).gbc" size 32768 crc f39c8119 sha1 b01cad9652bf1c65151dd8d2bd21251a9cf44d43 ) ) game ( - name "Pine Creek (World) (En-US,Es-MX,Pt-BR) (Aftermarket) (Homebrew)" - description "Pine Creek (World) (En-US,Es-MX,Pt-BR) (Aftermarket) (Homebrew)" - rom ( name "Pine Creek (World) (En-US,Es-MX,Pt-BR) (Aftermarket) (Homebrew).gbc" size 2097152 crc 189aa999 sha1 2cf46a36502eaf22ac9572fe1136d369fcbe9e46 ) + name "Pine Creek (World) (En-US,Es-MX,Pt-BR) (GB Compatible) (Aftermarket) (Unl)" + description "Pine Creek (World) (En-US,Es-MX,Pt-BR) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Pine Creek (World) (En-US,Es-MX,Pt-BR) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc 189aa999 sha1 2cf46a36502eaf22ac9572fe1136d369fcbe9e46 ) ) game ( - name "Pinecone Pizza Party (World) (Prototype) (Aftermarket) (Homebrew)" - description "Pinecone Pizza Party (World) (Prototype) (Aftermarket) (Homebrew)" - rom ( name "Pinecone Pizza Party (World) (Prototype) (Aftermarket) (Homebrew).gbc" size 262144 crc 1601d6fa sha1 87b4edf0398f0154d7556e3cd96f97b19e699a40 ) + name "Pinecone Pizza Party (World) (Prototype) (GB Compatible) (Aftermarket) (Unl)" + description "Pinecone Pizza Party (World) (Prototype) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Pinecone Pizza Party (World) (Prototype) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 1601d6fa sha1 87b4edf0398f0154d7556e3cd96f97b19e699a40 ) ) game ( @@ -40123,9 +41146,9 @@ game ( ) game ( - name "Pocket Monster Carbuncle 2003 (USA) (Unl)" - description "Pocket Monster Carbuncle 2003 (USA) (Unl)" - rom ( name "Pocket Monster Carbuncle 2003 (USA) (Unl).gbc" size 2097152 crc 3a0e9b6f sha1 025973627743f2f1ae1ff5b8a3f549cbcc227ef3 ) + name "Pocket Monsters - Crystal Version (Japan)" + description "Pocket Monsters - Crystal Version (Japan)" + rom ( name "Pocket Monsters - Crystal Version (Japan).gbc" size 2097152 crc 270c4ecc sha1 95127b901bbce2407daf43cce9f45d4c27ef635d flags verified ) ) game ( @@ -40134,12 +41157,6 @@ game ( rom ( name "Pocket Monsters - Crystal Version (Taiwan) (Unl).gbc" size 2097152 crc 4b2b8b57 sha1 9a2b381a134bb7ed1b566bb6f94596b57e690a4e ) ) -game ( - name "Pocket Monsters - Crystal Version (Japan)" - description "Pocket Monsters - Crystal Version (Japan)" - rom ( name "Pocket Monsters - Crystal Version (Japan).gbc" size 2097152 crc 270c4ecc sha1 95127b901bbce2407daf43cce9f45d4c27ef635d flags verified ) -) - game ( name "Pocket Monsters Diamond (Taiwan) (En) (Unl)" description "Pocket Monsters Diamond (Taiwan) (En) (Unl)" @@ -40261,9 +41278,9 @@ game ( ) game ( - name "Pogo Pete (World) (Aftermarket) (Homebrew)" - description "Pogo Pete (World) (Aftermarket) (Homebrew)" - rom ( name "Pogo Pete (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 3001ed1b sha1 f9dab150843c2b8f300d36b1dd53dd1a22b481a3 ) + name "Pogo Pete (World) (Aftermarket) (Unl)" + description "Pogo Pete (World) (Aftermarket) (Unl)" + rom ( name "Pogo Pete (World) (Aftermarket) (Unl).gbc" size 262144 crc 3001ed1b sha1 f9dab150843c2b8f300d36b1dd53dd1a22b481a3 ) ) game ( @@ -40335,7 +41352,7 @@ game ( game ( name "Pokemon - Silberne Edition (Germany) (Beta) (SGB Enhanced) (GB Compatible)" description "Pokemon - Silberne Edition (Germany) (Beta) (SGB Enhanced) (GB Compatible)" - rom ( name "Pokemon - Silberne Edition (Germany) (Beta) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc 576f5ced sha1 76fa60d66b2f22a035adc54c61aad9a415c894cd ) + rom ( name "Pokemon - Silberne Edition (Germany) (Beta) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc 576f5ced sha1 76fa60d66b2f22a035adc54c61aad9a415c894cd flags verified ) ) game ( @@ -40512,10 +41529,16 @@ game ( rom ( name "Pokemon Vision Jade (Taiwan) (De) (Unl).gbc" size 2097152 crc 2705ca20 sha1 72d5c6b8a570e67e7bce59070417cb89e985efb0 ) ) +game ( + name "Polaris SnoCross (USA) (Beta) (Rumble Version)" + description "Polaris SnoCross (USA) (Beta) (Rumble Version)" + rom ( name "Polaris SnoCross (USA) (Beta) (Rumble Version).gbc" size 1048576 crc 673623c4 sha1 4176ef1f9fd37156a4cac37a2d146ed2deeb5a7b ) +) + game ( name "Polaris SnoCross (USA) (Rumble Version)" description "Polaris SnoCross (USA) (Rumble Version)" - rom ( name "Polaris SnoCross (USA) (Rumble Version).gbc" size 1048576 crc dd8b189e sha1 e893808fe227a1608c0604382d6a0340ec704c3b ) + rom ( name "Polaris SnoCross (USA) (Rumble Version).gbc" size 1048576 crc dd8b189e sha1 e893808fe227a1608c0604382d6a0340ec704c3b flags verified ) ) game ( @@ -40573,21 +41596,21 @@ game ( ) game ( - name "Powa! (Europe)" - description "Powa! (Europe)" - rom ( name "Powa! (Europe).gbc" size 262144 crc f0191467 sha1 3876f1aa896759f427ad2dc88a48788817e60c06 ) + name "Powa! (World) (En) (GB Compatible) (Aftermarket) (Unl)" + description "Powa! (World) (En) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Powa! (World) (En) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc f0191467 sha1 3876f1aa896759f427ad2dc88a48788817e60c06 flags verified ) ) game ( - name "Powa! (Japan)" - description "Powa! (Japan)" - rom ( name "Powa! (Japan).gbc" size 262144 crc e8f68acc sha1 156d315b7a2a035d5cd429fb8c7e051e67e83c93 ) + name "Powa! (World) (Ja) (GB Compatible) (Aftermarket) (Unl)" + description "Powa! (World) (Ja) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Powa! (World) (Ja) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc e8f68acc sha1 156d315b7a2a035d5cd429fb8c7e051e67e83c93 flags verified ) ) game ( - name "Powa! (Unknown) (Demo)" - description "Powa! (Unknown) (Demo)" - rom ( name "Powa! (Unknown) (Demo).gbc" size 262144 crc 592b6097 sha1 874d5e4ffc7d36259bef6ec6a86a1de8289b8d54 ) + name "Powa! (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Powa! (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Powa! (World) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 592b6097 sha1 874d5e4ffc7d36259bef6ec6a86a1de8289b8d54 ) ) game ( @@ -40605,7 +41628,7 @@ game ( game ( name "Power Pro Kun Pocket 2 (Japan) (SGB Enhanced) (GB Compatible)" description "Power Pro Kun Pocket 2 (Japan) (SGB Enhanced) (GB Compatible)" - rom ( name "Power Pro Kun Pocket 2 (Japan) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc c2a4a3eb sha1 5c0ba404cf30296dceeb427a54e3644415c9e8db ) + rom ( name "Power Pro Kun Pocket 2 (Japan) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc c2a4a3eb sha1 5c0ba404cf30296dceeb427a54e3644415c9e8db flags verified ) ) game ( @@ -40680,6 +41703,12 @@ game ( rom ( name "Powerpuff Girls, The - Bulle Contre Lui (France).gbc" size 2097152 crc 7085270c sha1 5c1c53b9c002680c2da8b24aa31d4a247586263d ) ) +game ( + name "Powerpuff Girls, The - Il Terribile Mojo Jojo (Italy) (Proto)" + description "Powerpuff Girls, The - Il Terribile Mojo Jojo (Italy) (Proto)" + rom ( name "Powerpuff Girls, The - Il Terribile Mojo Jojo (Italy) (Proto).gbc" size 2097152 crc 5c265103 sha1 5ca837632930689fad9086b772928bf5995193f9 ) +) + game ( name "Powerpuff Girls, The - L'Affreux Mojo Jojo (France)" description "Powerpuff Girls, The - L'Affreux Mojo Jojo (France)" @@ -40801,15 +41830,21 @@ game ( ) game ( - name "Proof of Destruction (World) (Aftermarket) (Homebrew)" - description "Proof of Destruction (World) (Aftermarket) (Homebrew)" - rom ( name "Proof of Destruction (World) (Aftermarket) (Homebrew).gbc" size 262144 crc fa528f88 sha1 5062f16346c31cae1b13e469cb610ce84d4eecb2 ) + name "Proof of Destruction (World) (Aftermarket) (Unl)" + description "Proof of Destruction (World) (Aftermarket) (Unl)" + rom ( name "Proof of Destruction (World) (Aftermarket) (Unl).gbc" size 262144 crc fa528f88 sha1 5062f16346c31cae1b13e469cb610ce84d4eecb2 ) ) game ( - name "Puchi Carat (USA) (Proto) (SGB Enhanced) (GB Compatible)" - description "Puchi Carat (USA) (Proto) (SGB Enhanced) (GB Compatible)" - rom ( name "Puchi Carat (USA) (Proto) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc a97ea742 sha1 de755124425f9de8bef5eea8026751c54f7f1e00 ) + name "Puchi Carat (USA) (Proto 2) (SGB Enhanced) (GB Compatible)" + description "Puchi Carat (USA) (Proto 2) (SGB Enhanced) (GB Compatible)" + rom ( name "Puchi Carat (USA) (Proto 2) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc a97ea742 sha1 de755124425f9de8bef5eea8026751c54f7f1e00 ) +) + +game ( + name "Puchi Carat (USA) (Proto 1) (SGB Enhanced) (GB Compatible)" + description "Puchi Carat (USA) (Proto 1) (SGB Enhanced) (GB Compatible)" + rom ( name "Puchi Carat (USA) (Proto 1) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc d79b6f46 sha1 b91b121a6951bcc74f0e4af33d41a1fba62f9822 ) ) game ( @@ -40836,6 +41871,12 @@ game ( rom ( name "Pumuckls Abenteuer im Geisterschloss (Germany).gbc" size 1048576 crc 87fcec24 sha1 0fe14ef81b11c854f080e804c5d3cb14601b2794 ) ) +game ( + name "Purple Turtles (World) (Aftermarket) (Unl)" + description "Purple Turtles (World) (Aftermarket) (Unl)" + rom ( name "Purple Turtles (World) (Aftermarket) (Unl).gbc" size 262144 crc 471277df sha1 a73cb69d1fb42f45f3649c4d1b4775e98fe35b78 ) +) + game ( name "Puyo Puyo Gaiden - Puyo Wars (Japan) (SGB Enhanced) (GB Compatible)" description "Puyo Puyo Gaiden - Puyo Wars (Japan) (SGB Enhanced) (GB Compatible)" @@ -40891,15 +41932,15 @@ game ( ) game ( - name "Qi Long Zhu Z 3 (Taiwan) (Unl)" - description "Qi Long Zhu Z 3 (Taiwan) (Unl)" - rom ( name "Qi Long Zhu Z 3 (Taiwan) (Unl).gbc" size 2097152 crc ccfdd63a sha1 4f3b63cd11522b6fb291661f4d918601d95138e0 ) + name "Qi Tian Dasheng - Sun Wukong (Taiwan) (Unl)" + description "Qi Tian Dasheng - Sun Wukong (Taiwan) (Unl)" + rom ( name "Qi Tian Dasheng - Sun Wukong (Taiwan) (Unl).gbc" size 524288 crc a336c40c sha1 3d7e987f1315242422c0db45c4aea120c1b41429 ) ) game ( - name "Qi Tian Da Sheng - Sun Wu Kong (Taiwan) (Unl)" - description "Qi Tian Da Sheng - Sun Wu Kong (Taiwan) (Unl)" - rom ( name "Qi Tian Da Sheng - Sun Wu Kong (Taiwan) (Unl).gbc" size 524288 crc a336c40c sha1 3d7e987f1315242422c0db45c4aea120c1b41429 ) + name "Qilongzhu Z3 (Taiwan) (Unl)" + description "Qilongzhu Z3 (Taiwan) (Unl)" + rom ( name "Qilongzhu Z3 (Taiwan) (Unl).gbc" size 2097152 crc ccfdd63a sha1 4f3b63cd11522b6fb291661f4d918601d95138e0 ) ) game ( @@ -40921,9 +41962,9 @@ game ( ) game ( - name "Quan Ba Tian Xia (Taiwan) (Unl)" - description "Quan Ba Tian Xia (Taiwan) (Unl)" - rom ( name "Quan Ba Tian Xia (Taiwan) (Unl).gbc" size 4194304 crc 2c922ed6 sha1 c19eb98a9745d2c2ce9e4dba3f17ae0b3dee0f49 ) + name "Quan Ba Tianxia (Taiwan) (Unl)" + description "Quan Ba Tianxia (Taiwan) (Unl)" + rom ( name "Quan Ba Tianxia (Taiwan) (Unl).gbc" size 4194304 crc 2c922ed6 sha1 c19eb98a9745d2c2ce9e4dba3f17ae0b3dee0f49 ) ) game ( @@ -41040,6 +42081,12 @@ game ( rom ( name "Rats! (USA) (En,Es) (GB Compatible).gbc" size 524288 crc 17635ad1 sha1 5e423dfab8221b69a641d2e535ebfe1e3759a2e4 ) ) +game ( + name "Ravenia (World) (GB Compatible) (Aftermarket) (Unl)" + description "Ravenia (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Ravenia (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 09196a32 sha1 8218ad22b29fb32c66430a17a97775dd30b2f34b flags verified ) +) + game ( name "Rayman (Europe) (En,Fr,De,Es,It,Nl)" description "Rayman (Europe) (En,Fr,De,Es,It,Nl)" @@ -41118,6 +42165,18 @@ game ( rom ( name "Real Pro Yakyuu! - Pacific League Hen (Japan) (SGB Enhanced) (GB Compatible).gbc" size 524288 crc 16b81c36 sha1 a7a2cd7bbb4cae171b5f2c798293afefe2882afc ) ) +game ( + name "Repugnant Bounty (World) (Demo) (Aftermarket) (Unl)" + description "Repugnant Bounty (World) (Demo) (Aftermarket) (Unl)" + rom ( name "Repugnant Bounty (World) (Demo) (Aftermarket) (Unl).gbc" size 2097152 crc f8c7f180 sha1 07674d42f5d4efaec14750ae56d8f28c4b54c020 ) +) + +game ( + name "Repugnant Bounty (World) (Rev B) (Beta) (Aftermarket) (Unl)" + description "Repugnant Bounty (World) (Rev B) (Beta) (Aftermarket) (Unl)" + rom ( name "Repugnant Bounty (World) (Rev B) (Beta) (Aftermarket) (Unl).gbc" size 4194304 crc 9cce02d9 sha1 b83f9aca2a382729a490856c4100dc4a17594921 ) +) + game ( name "Rescue Heroes - Fire Frenzy (USA)" description "Rescue Heroes - Fire Frenzy (USA)" @@ -41185,9 +42244,9 @@ game ( ) game ( - name "Rig Attack (World) (Aftermarket) (Homebrew)" - description "Rig Attack (World) (Aftermarket) (Homebrew)" - rom ( name "Rig Attack (World) (Aftermarket) (Homebrew).gbc" size 262144 crc d013d775 sha1 90bfd921c986abff4555b761cedc90ec36c43fff ) + name "Rig Attack (World) (Aftermarket) (Unl)" + description "Rig Attack (World) (Aftermarket) (Unl)" + rom ( name "Rig Attack (World) (Aftermarket) (Unl).gbc" size 262144 crc d013d775 sha1 90bfd921c986abff4555b761cedc90ec36c43fff ) ) game ( @@ -41233,9 +42292,9 @@ game ( ) game ( - name "Robin to the Rescue (World) (Aftermarket) (Homebrew)" - description "Robin to the Rescue (World) (Aftermarket) (Homebrew)" - rom ( name "Robin to the Rescue (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 9cd52c18 sha1 ef3bea9ed0de41e311b5f15fccbb0f451848d56c ) + name "Robin to the Rescue (World) (Aftermarket) (Unl)" + description "Robin to the Rescue (World) (Aftermarket) (Unl)" + rom ( name "Robin to the Rescue (World) (Aftermarket) (Unl).gbc" size 262144 crc 9cd52c18 sha1 ef3bea9ed0de41e311b5f15fccbb0f451848d56c ) ) game ( @@ -41253,7 +42312,7 @@ game ( game ( name "Robopon - Sun Version (USA) (SGB Enhanced) (GB Compatible)" description "Robopon - Sun Version (USA) (SGB Enhanced) (GB Compatible)" - rom ( name "Robopon - Sun Version (USA) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 32caef11 sha1 399c928a38a3901b7a1093bd61f5a4d8c05b9771 ) + rom ( name "Robopon - Sun Version (USA) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 32caef11 sha1 399c928a38a3901b7a1093bd61f5a4d8c05b9771 flags verified ) ) game ( @@ -41295,7 +42354,7 @@ game ( game ( name "Rocket Power - Gettin' Air (USA, Europe)" description "Rocket Power - Gettin' Air (USA, Europe)" - rom ( name "Rocket Power - Gettin' Air (USA, Europe).gbc" size 1048576 crc 7025eb63 sha1 d9c5c22f1f5a2a922cb2b39d5e8f3df31c1155d7 ) + rom ( name "Rocket Power - Gettin' Air (USA, Europe).gbc" size 1048576 crc 7025eb63 sha1 d9c5c22f1f5a2a922cb2b39d5e8f3df31c1155d7 flags verified ) ) game ( @@ -41305,9 +42364,9 @@ game ( ) game ( - name "Rockman (World) (Aftermarket) (Homebrew)" - description "Rockman (World) (Aftermarket) (Homebrew)" - rom ( name "Rockman (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 490b5676 sha1 6f78200acb6698e268e26c1912d64a002cbd3d89 ) + name "Rockman (World) (Aftermarket) (Unl)" + description "Rockman (World) (Aftermarket) (Unl)" + rom ( name "Rockman (World) (Aftermarket) (Unl).gbc" size 262144 crc 490b5676 sha1 6f78200acb6698e268e26c1912d64a002cbd3d89 ) ) game ( @@ -41425,27 +42484,33 @@ game ( ) game ( - name "RPG Tsukuru GB (Japan) (Rev 1) (NP)" - description "RPG Tsukuru GB (Japan) (Rev 1) (NP)" - rom ( name "RPG Tsukuru GB (Japan) (Rev 1) (NP).gbc" size 2097152 crc 57f82031 sha1 f4c36b3cbb13d3cbaaa81e97b0636c4515ce501e ) + name "RPG Tsukuru GB (Japan) (Rev 1) (Possible Proto) (NP)" + description "RPG Tsukuru GB (Japan) (Rev 1) (Possible Proto) (NP)" + rom ( name "RPG Tsukuru GB (Japan) (Rev 1) (Possible Proto) (NP).gbc" size 2097152 crc 57f82031 sha1 f4c36b3cbb13d3cbaaa81e97b0636c4515ce501e ) ) game ( - name "Ruby & Rusty Save the Crows (World) (v1.0) (Aftermarket) (Homebrew)" - description "Ruby & Rusty Save the Crows (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Ruby & Rusty Save the Crows (World) (v1.0) (Aftermarket) (Homebrew).gbc" size 2097152 crc a46baa15 sha1 87dd5a6ccb5db59dae09f1953062420977ad2d92 ) + name "Ruby & Rusty Save the Crows (World) (v1.0) (GB Compatible) (Aftermarket) (Unl)" + description "Ruby & Rusty Save the Crows (World) (v1.0) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Ruby & Rusty Save the Crows (World) (v1.0) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc a46baa15 sha1 87dd5a6ccb5db59dae09f1953062420977ad2d92 ) ) game ( - name "Ruby & Rusty Save the Crows (World) (v2.0) (Aftermarket) (Homebrew)" - description "Ruby & Rusty Save the Crows (World) (v2.0) (Aftermarket) (Homebrew)" - rom ( name "Ruby & Rusty Save the Crows (World) (v2.0) (Aftermarket) (Homebrew).gbc" size 2097152 crc 5602ca84 sha1 49d692796a6d0f3dcfc988648b446c0934f0e794 ) + name "Ruby & Rusty Save the Crows (World) (v2.0) (GB Compatible) (Aftermarket) (Unl)" + description "Ruby & Rusty Save the Crows (World) (v2.0) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Ruby & Rusty Save the Crows (World) (v2.0) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc 5602ca84 sha1 49d692796a6d0f3dcfc988648b446c0934f0e794 ) ) game ( - name "Ruby & Rusty Save the Crows (World) (Beta 3) (Aftermarket) (Homebrew)" - description "Ruby & Rusty Save the Crows (World) (Beta 3) (Aftermarket) (Homebrew)" - rom ( name "Ruby & Rusty Save the Crows (World) (Beta 3) (Aftermarket) (Homebrew).gbc" size 2097152 crc b2547aa6 sha1 981a464d6e1c9e5ac59549845b41dae4817ed3b8 ) + name "Ruby & Rusty Save the Crows (World) (Beta 3) (GB Compatible) (Aftermarket) (Unl)" + description "Ruby & Rusty Save the Crows (World) (Beta 3) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Ruby & Rusty Save the Crows (World) (Beta 3) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc b2547aa6 sha1 981a464d6e1c9e5ac59549845b41dae4817ed3b8 ) +) + +game ( + name "Ruby & Rusty Save the Crows (World) (v2.6) (GB Compatible) (Aftermarket) (Unl)" + description "Ruby & Rusty Save the Crows (World) (v2.6) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Ruby & Rusty Save the Crows (World) (v2.6) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc c18f82cb sha1 bd8cd5822dcc61f7eb0cf2584c9a5b5a94bde14a ) ) game ( @@ -41517,7 +42582,7 @@ game ( game ( name "Sabrina - The Animated Series - Spooked! (USA, Europe)" description "Sabrina - The Animated Series - Spooked! (USA, Europe)" - rom ( name "Sabrina - The Animated Series - Spooked! (USA, Europe).gbc" size 1048576 crc 2cf48188 sha1 7a219159ef46c5ef88eb6b478667c2ec80194edc ) + rom ( name "Sabrina - The Animated Series - Spooked! (USA, Europe).gbc" size 1048576 crc 2cf48188 sha1 7a219159ef46c5ef88eb6b478667c2ec80194edc flags verified ) ) game ( @@ -41550,6 +42615,12 @@ game ( rom ( name "Sakura Taisen GB 2 - Thunderbolt Sakusen (Japan).gbc" size 4194304 crc 47636a2c sha1 13092603ea1d54264bc48f02c2796947badb462c ) ) +game ( + name "Sam the Optimistic Hedgehog (World) (GB Compatible) (Aftermarket) (Unl)" + description "Sam the Optimistic Hedgehog (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Sam the Optimistic Hedgehog (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 4e4af60b sha1 ab8d49a99d02eb5c97d75f62dfb3720eec2b5abd ) +) + game ( name "Samurai Kid (Japan)" description "Samurai Kid (Japan)" @@ -41569,9 +42640,9 @@ game ( ) game ( - name "San Guo Wu Shang 5 (Taiwan) (Unl)" - description "San Guo Wu Shang 5 (Taiwan) (Unl)" - rom ( name "San Guo Wu Shang 5 (Taiwan) (Unl).gbc" size 2097152 crc 8a64c933 sha1 e97508fa49564d56550ad467f4c543f3f218db76 ) + name "San Guo Wushang 5 (Taiwan) (Unl)" + description "San Guo Wushang 5 (Taiwan) (Unl)" + rom ( name "San Guo Wushang 5 (Taiwan) (Unl).gbc" size 2097152 crc 8a64c933 sha1 e97508fa49564d56550ad467f4c543f3f218db76 ) ) game ( @@ -41622,6 +42693,12 @@ game ( rom ( name "Santa Claus Junior (Europe).gbc" size 1048576 crc a744df64 sha1 ab74474cd63a2c74bf9617907270d26b5d183b89 ) ) +game ( + name "Sapphire Hotel - The Little Tales of (World) (v1.2) (Demo) (MBC5) (Aftermarket) (Unl)" + description "Sapphire Hotel - The Little Tales of (World) (v1.2) (Demo) (MBC5) (Aftermarket) (Unl)" + rom ( name "Sapphire Hotel - The Little Tales of (World) (v1.2) (Demo) (MBC5) (Aftermarket) (Unl).gbc" size 262144 crc db928e22 sha1 e175d35e6112c347ecd1e0a379e98f430823ba94 ) +) + game ( name "Saru Puncher (Japan) (SGB Enhanced) (GB Compatible)" description "Saru Puncher (Japan) (SGB Enhanced) (GB Compatible)" @@ -41652,6 +42729,12 @@ game ( rom ( name "Sea-Doo HydroCross (USA) (Proto).gbc" size 1048576 crc fb1783f3 sha1 3612e549ba2a71585fc9a57033c97562386cdea4 ) ) +game ( + name "Second Edition Harry Boy, The - The Secret of the Chamber of Secrets (USA) (Unl)" + description "Second Edition Harry Boy, The - The Secret of the Chamber of Secrets (USA) (Unl)" + rom ( name "Second Edition Harry Boy, The - The Secret of the Chamber of Secrets (USA) (Unl).gbc" size 2097152 crc cba2c784 sha1 99d852998fa84c8c0160cc9dfb2e2165a94756ca ) +) + game ( name "Sei Hai Densetsu (Japan) (SGB Enhanced) (GB Compatible)" description "Sei Hai Densetsu (Japan) (SGB Enhanced) (GB Compatible)" @@ -41821,9 +42904,21 @@ game ( ) game ( - name "Shao Lin Shi San Gun - Ying Xiong Jiu Zhu (Taiwan) (Zh) (Unl)" - description "Shao Lin Shi San Gun - Ying Xiong Jiu Zhu (Taiwan) (Zh) (Unl)" - rom ( name "Shao Lin Shi San Gun - Ying Xiong Jiu Zhu (Taiwan) (Zh) (Unl).gbc" size 2097152 crc 25a857af sha1 2fbbf4e3a40eda75b0876d207502727f8dacca11 ) + name "Shantae (World) (Switch)" + description "Shantae (World) (Switch)" + rom ( name "Shantae (World) (Switch).gbc" size 4194304 crc 5009b832 sha1 e1cc66f1950d585d5b644bf9720c87a356354e6a ) +) + +game ( + name "Shantae (World) (GBA Enhanced) (Switch)" + description "Shantae (World) (GBA Enhanced) (Switch)" + rom ( name "Shantae (World) (GBA Enhanced) (Switch).gbc" size 4194304 crc 1ef9fa6b sha1 d14e8b0029b0aff52134a4fa6794eea72f25f4a6 ) +) + +game ( + name "Shaolin Shisan Gun - Ying Xiong Jiu Zhu (Taiwan) (Zh) (Unl)" + description "Shaolin Shisan Gun - Ying Xiong Jiu Zhu (Taiwan) (Zh) (Unl)" + rom ( name "Shaolin Shisan Gun - Ying Xiong Jiu Zhu (Taiwan) (Zh) (Unl).gbc" size 2097152 crc 25a857af sha1 2fbbf4e3a40eda75b0876d207502727f8dacca11 ) ) game ( @@ -41832,6 +42927,12 @@ game ( rom ( name "Shaoling Legend - Hero, the Saver (Taiwan) (En) (Unl).gbc" size 2097152 crc a321ff7d sha1 b1194edd0962c50cf909262004a102f01f556723 ) ) +game ( + name "Shark Attack (World) (Aftermarket) (Unl)" + description "Shark Attack (World) (Aftermarket) (Unl)" + rom ( name "Shark Attack (World) (Aftermarket) (Unl).gbc" size 262144 crc cf80a2ba sha1 291f453a185f836fcafbd7e41945a409638980c0 ) +) + game ( name "Shaun Palmer's Pro Snowboarder (USA, Australia)" description "Shaun Palmer's Pro Snowboarder (USA, Australia)" @@ -41845,9 +42946,9 @@ game ( ) game ( - name "Sheng Shou Wu Yu - Shen Long Chuan Shuo (Taiwan) (Unl)" - description "Sheng Shou Wu Yu - Shen Long Chuan Shuo (Taiwan) (Unl)" - rom ( name "Sheng Shou Wu Yu - Shen Long Chuan Shuo (Taiwan) (Unl).gbc" size 1048576 crc 99a6e58a sha1 13bd97249b68eb824858a9d7a6061edf94140ac3 ) + name "Sheng Shou Wuyu - Shenlong Chuanshuo (Taiwan) (Unl)" + description "Sheng Shou Wuyu - Shenlong Chuanshuo (Taiwan) (Unl)" + rom ( name "Sheng Shou Wuyu - Shenlong Chuanshuo (Taiwan) (Unl).gbc" size 1048576 crc 99a6e58a sha1 13bd97249b68eb824858a9d7a6061edf94140ac3 ) ) game ( @@ -41856,18 +42957,18 @@ game ( rom ( name "Shengui Diguo Zhi Emo Cheng (Taiwan) (Unl).gbc" size 2097152 crc 1823e24d sha1 3fc2533f71cea3aec6310f5f7c33fa7eac427b99 ) ) -game ( - name "Shi Kong Xing Shou (Taiwan) (Unl)" - description "Shi Kong Xing Shou (Taiwan) (Unl)" - rom ( name "Shi Kong Xing Shou (Taiwan) (Unl).gbc" size 2097152 crc ec970863 sha1 bc43036da01bb1aef4daa9e7316907193285011f ) -) - game ( name "Shi Mian Maifu - Fengyun Pian (Taiwan) (Unl)" description "Shi Mian Maifu - Fengyun Pian (Taiwan) (Unl)" rom ( name "Shi Mian Maifu - Fengyun Pian (Taiwan) (Unl).gbc" size 1048576 crc a5f686c3 sha1 68798ac090df9f5fb1ea9c409adf3dc639bc9842 ) ) +game ( + name "Shikong Xing Shou (Taiwan) (Unl)" + description "Shikong Xing Shou (Taiwan) (Unl)" + rom ( name "Shikong Xing Shou (Taiwan) (Unl).gbc" size 2097152 crc ec970863 sha1 bc43036da01bb1aef4daa9e7316907193285011f ) +) + game ( name "Shin Megami Tensei Devil Children - Aka no Sho (Japan) (SGB Enhanced) (GB Compatible)" description "Shin Megami Tensei Devil Children - Aka no Sho (Japan) (SGB Enhanced) (GB Compatible)" @@ -41940,30 +43041,6 @@ game ( rom ( name "Shrek - Fairy Tale Freakdown (USA, Europe) (En,Fr,De,Es,It).gbc" size 2097152 crc 387e6459 sha1 e6a728cafd14a4df952467f2a7434ff14e53e268 flags verified ) ) -game ( - name "Shu Ma Bao Long - Ge Dou Ban 2003 (Taiwan) (Unl)" - description "Shu Ma Bao Long - Ge Dou Ban 2003 (Taiwan) (Unl)" - rom ( name "Shu Ma Bao Long - Ge Dou Ban 2003 (Taiwan) (Unl).gbc" size 2097152 crc 1219eec6 sha1 4a722c10e3893e04c67a66d0aea401d6d220ec7e ) -) - -game ( - name "Shu Ma Bao Long - Shui Jing Ban II (Taiwan) (Unl)" - description "Shu Ma Bao Long - Shui Jing Ban II (Taiwan) (Unl)" - rom ( name "Shu Ma Bao Long - Shui Jing Ban II (Taiwan) (Unl).gbc" size 2097152 crc b67999ae sha1 435c26f47c4e244e8adb3b7f18f2b1d129e163e9 ) -) - -game ( - name "Shu Ma Bao Long 9 - Bao Long Pian 2002 (Taiwan) (Zh) (Unl)" - description "Shu Ma Bao Long 9 - Bao Long Pian 2002 (Taiwan) (Zh) (Unl)" - rom ( name "Shu Ma Bao Long 9 - Bao Long Pian 2002 (Taiwan) (Zh) (Unl).gbc" size 1048576 crc c10fa909 sha1 bdc4a2ca7170f41bc10b89a2e6e96fb990a44f0b ) -) - -game ( - name "Shu Ma Bao Long Zhuan Ji 10 in 1 (Taiwan) (Unl)" - description "Shu Ma Bao Long Zhuan Ji 10 in 1 (Taiwan) (Unl)" - rom ( name "Shu Ma Bao Long Zhuan Ji 10 in 1 (Taiwan) (Unl).gbc" size 4194304 crc 37603b3a sha1 37366ff0b3a722da867327c62cae6ad0e51d4a12 ) -) - game ( name "Shuihu Shenshou (Taiwan) (Unl)" description "Shuihu Shenshou (Taiwan) (Unl)" @@ -41989,9 +43066,9 @@ game ( ) game ( - name "Shuma Baobei - Chao Mengmeng Fanji Zhan (Taiwan) (Zh)" - description "Shuma Baobei - Chao Mengmeng Fanji Zhan (Taiwan) (Zh)" - rom ( name "Shuma Baobei - Chao Mengmeng Fanji Zhan (Taiwan) (Zh).gbc" size 524288 crc fc844e0e sha1 6abec431a861315fb1b284ff964c2abf933444ae ) + name "Shuma Baobei - Chao Mengmeng Fanji Zhan (Taiwan) (Zh) (Unl)" + description "Shuma Baobei - Chao Mengmeng Fanji Zhan (Taiwan) (Zh) (Unl)" + rom ( name "Shuma Baobei - Chao Mengmeng Fanji Zhan (Taiwan) (Zh) (Unl).gbc" size 524288 crc fc844e0e sha1 6abec431a861315fb1b284ff964c2abf933444ae ) ) game ( @@ -42042,12 +43119,30 @@ game ( rom ( name "Shuma Baolong - Shuijing Ban (Taiwan) (Li Cheng) (Unl).gbc" size 1048576 crc ba03bd71 sha1 39d8ebe977cf139be398d87be735ea4f7e6885f7 ) ) +game ( + name "Shuma Baolong - Shuijing Ban II (Taiwan) (Unl)" + description "Shuma Baolong - Shuijing Ban II (Taiwan) (Unl)" + rom ( name "Shuma Baolong - Shuijing Ban II (Taiwan) (Unl).gbc" size 2097152 crc b67999ae sha1 435c26f47c4e244e8adb3b7f18f2b1d129e163e9 ) +) + game ( name "Shuma Baolong 02 4 (Taiwan) (Zh) (Unl)" description "Shuma Baolong 02 4 (Taiwan) (Zh) (Unl)" rom ( name "Shuma Baolong 02 4 (Taiwan) (Zh) (Unl).gbc" size 1048576 crc 2ee18ab2 sha1 839f0880749735ba2113e437f8efede171b7474d ) ) +game ( + name "Shuma Baolong 9 - Baolong Pian (Taiwan) (Zh) (Unl)" + description "Shuma Baolong 9 - Baolong Pian (Taiwan) (Zh) (Unl)" + rom ( name "Shuma Baolong 9 - Baolong Pian (Taiwan) (Zh) (Unl).gbc" size 1048576 crc c10fa909 sha1 bdc4a2ca7170f41bc10b89a2e6e96fb990a44f0b ) +) + +game ( + name "Shuma Baolong Zhuanji 10 in 1 (Taiwan) (Unl)" + description "Shuma Baolong Zhuanji 10 in 1 (Taiwan) (Unl)" + rom ( name "Shuma Baolong Zhuanji 10 in 1 (Taiwan) (Unl).gbc" size 4194304 crc 37603b3a sha1 37366ff0b3a722da867327c62cae6ad0e51d4a12 ) +) + game ( name "Shutokou Racing, The (Japan) (SGB Enhanced) (GB Compatible)" description "Shutokou Racing, The (Japan) (SGB Enhanced) (GB Compatible)" @@ -42061,15 +43156,15 @@ game ( ) game ( - name "Skelby (World) (Aftermarket) (Homebrew)" - description "Skelby (World) (Aftermarket) (Homebrew)" - rom ( name "Skelby (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 6ec6c18c sha1 887555aa7e2e61b68a21020237b7d508bb71aded ) + name "Skelby (World) (Aftermarket) (Unl)" + description "Skelby (World) (Aftermarket) (Unl)" + rom ( name "Skelby (World) (Aftermarket) (Unl).gbc" size 524288 crc 6ec6c18c sha1 887555aa7e2e61b68a21020237b7d508bb71aded ) ) game ( - name "Skycon (World) (Aftermarket) (Homebrew)" - description "Skycon (World) (Aftermarket) (Homebrew)" - rom ( name "Skycon (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 195dbc30 sha1 df29ece6c5f5b14840663495230135dd79c5163a ) + name "Skycon (World) (Aftermarket) (Unl)" + description "Skycon (World) (Aftermarket) (Unl)" + rom ( name "Skycon (World) (Aftermarket) (Unl).gbc" size 524288 crc 195dbc30 sha1 df29ece6c5f5b14840663495230135dd79c5163a ) ) game ( @@ -42300,6 +43395,12 @@ game ( rom ( name "Spider-Man 3 - Movie Version (USA) (Unl).gbc" size 2097152 crc 2c0d43a9 sha1 8a6b6a1300db59b86ddf87599cfc8edd2e52e2b0 ) ) +game ( + name "Spiky Harold (World) (Aftermarket) (Unl)" + description "Spiky Harold (World) (Aftermarket) (Unl)" + rom ( name "Spiky Harold (World) (Aftermarket) (Unl).gbc" size 262144 crc 7e9eda35 sha1 486d294b8cf226876581d543e3a08018321bc402 ) +) + game ( name "Spirou - The Robot Invasion (Europe) (En,Fr,De,Es,It,Nl,Da)" description "Spirou - The Robot Invasion (Europe) (En,Fr,De,Es,It,Nl,Da)" @@ -42403,9 +43504,9 @@ game ( ) game ( - name "Stellar Wars (World) (Aftermarket) (Homebrew)" - description "Stellar Wars (World) (Aftermarket) (Homebrew)" - rom ( name "Stellar Wars (World) (Aftermarket) (Homebrew).gbc" size 262144 crc e0b96256 sha1 ba2d10a2a5f3f073aea11e26ae5c53c9cf7866f8 ) + name "Stellar Wars (World) (Aftermarket) (Unl)" + description "Stellar Wars (World) (Aftermarket) (Unl)" + rom ( name "Stellar Wars (World) (Aftermarket) (Unl).gbc" size 262144 crc e0b96256 sha1 ba2d10a2a5f3f073aea11e26ae5c53c9cf7866f8 ) ) game ( @@ -42456,6 +43557,12 @@ game ( rom ( name "Stuart Little - The Journey Home (USA, Europe).gbc" size 1048576 crc eb273887 sha1 d3c31e41709c54af328787036db1b98997f508ea ) ) +game ( + name "Suicide Run (World) (GB Compatible) (Aftermarket) (Unl)" + description "Suicide Run (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Suicide Run (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 2dc3fdae sha1 a551d904c4af661bd4b6e88063cb5541900117b0 ) +) + game ( name "Super 16 in 1 (Taiwan) (En) (Sachen) (Unl)" description "Super 16 in 1 (Taiwan) (En) (Sachen) (Unl)" @@ -42541,9 +43648,21 @@ game ( ) game ( - name "Super Jacked Up Tomato Face Johnson (World) (Aftermarket) (Homebrew)" - description "Super Jacked Up Tomato Face Johnson (World) (Aftermarket) (Homebrew)" - rom ( name "Super Jacked Up Tomato Face Johnson (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 87956ddf sha1 b23320d5d3fde2a55b301f7bea324833c31c32d5 ) + name "Super Gran (World) (Aftermarket) (Unl)" + description "Super Gran (World) (Aftermarket) (Unl)" + rom ( name "Super Gran (World) (Aftermarket) (Unl).gbc" size 262144 crc 98ee2e8a sha1 5afe9c7ab1b9d86048a570e7a37a0c244cb84e43 ) +) + +game ( + name "Super Jacked Up Tomato Face Johnson (World) (Aftermarket) (Unl)" + description "Super Jacked Up Tomato Face Johnson (World) (Aftermarket) (Unl)" + rom ( name "Super Jacked Up Tomato Face Johnson (World) (Aftermarket) (Unl).gbc" size 524288 crc 87956ddf sha1 b23320d5d3fde2a55b301f7bea324833c31c32d5 ) +) + +game ( + name "Super JetPak DX (World) (GB Compatible) (Aftermarket)" + description "Super JetPak DX (World) (GB Compatible) (Aftermarket)" + rom ( name "Super JetPak DX (World) (GB Compatible) (Aftermarket).gbc" size 131072 crc 22def6f9 sha1 e7438db01fbbdeea2404dbf3d093370421ec4e1c ) ) game ( @@ -42571,9 +43690,9 @@ game ( ) game ( - name "Super Mario Bros. Deluxe (Japan) (En) (Rev 1) (NP)" - description "Super Mario Bros. Deluxe (Japan) (En) (Rev 1) (NP)" - rom ( name "Super Mario Bros. Deluxe (Japan) (En) (Rev 1) (NP).gbc" size 1048576 crc f4e91f63 sha1 e61d564e1ff19eb4b7c62a6cd96214f2bca4b01d ) + name "Super Mario Bros. Deluxe (Japan) (Rev 1) (NP)" + description "Super Mario Bros. Deluxe (Japan) (Rev 1) (NP)" + rom ( name "Super Mario Bros. Deluxe (Japan) (Rev 1) (NP).gbc" size 1048576 crc f4e91f63 sha1 e61d564e1ff19eb4b7c62a6cd96214f2bca4b01d ) ) game ( @@ -42673,9 +43792,9 @@ game ( ) game ( - name "Survival Kids 2 - Dasshutsu!! Futago-Jima! (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP)" - description "Survival Kids 2 - Dasshutsu!! Futago-Jima! (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP)" - rom ( name "Survival Kids 2 - Dasshutsu!! Futago-Jima! (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP).gbc" size 1048576 crc bd366d22 sha1 505ffc930057719cf28560333342dfd6944c15da ) + name "Survival Kids 2 - Dasshutsu!! Futago-Jima! (Japan) (Rev 1) (Possible Proto) (SGB Enhanced, GB Compatible) (NP)" + description "Survival Kids 2 - Dasshutsu!! Futago-Jima! (Japan) (Rev 1) (Possible Proto) (SGB Enhanced, GB Compatible) (NP)" + rom ( name "Survival Kids 2 - Dasshutsu!! Futago-Jima! (Japan) (Rev 1) (Possible Proto) (SGB Enhanced, GB Compatible) (NP).gbc" size 1048576 crc bd366d22 sha1 505ffc930057719cf28560333342dfd6944c15da ) ) game ( @@ -42745,9 +43864,9 @@ game ( ) game ( - name "Sylvanian Families 2 - Irozuku Mori no Fantasy (Japan) (Rev 1) (NP)" - description "Sylvanian Families 2 - Irozuku Mori no Fantasy (Japan) (Rev 1) (NP)" - rom ( name "Sylvanian Families 2 - Irozuku Mori no Fantasy (Japan) (Rev 1) (NP).gbc" size 2097152 crc ad6f3bdf sha1 c96044d57a2367ffb7b2325aa8d0c9b659e0aaba ) + name "Sylvanian Families 2 - Irozuku Mori no Fantasy (Japan) (Rev 1) (Possible Proto)" + description "Sylvanian Families 2 - Irozuku Mori no Fantasy (Japan) (Rev 1) (Possible Proto)" + rom ( name "Sylvanian Families 2 - Irozuku Mori no Fantasy (Japan) (Rev 1) (Possible Proto).gbc" size 2097152 crc ad6f3bdf sha1 c96044d57a2367ffb7b2325aa8d0c9b659e0aaba ) ) game ( @@ -42828,10 +43947,16 @@ game ( rom ( name "Tanimura Hitoshi Ryuu Pachinko Kouryaku Daisakusen - Don Quijote ga Iku (Japan) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc ce8ae58c sha1 f752e96602274a29006425a06086d8ab45e1075c ) ) +game ( + name "Tank Attack (World) (Aftermarket) (Unl)" + description "Tank Attack (World) (Aftermarket) (Unl)" + rom ( name "Tank Attack (World) (Aftermarket) (Unl).gbc" size 262144 crc c77dc518 sha1 4751a6999f4dbe7e66b997372588f6bd15e4d9d0 ) +) + game ( name "Tarzan (France)" description "Tarzan (France)" - rom ( name "Tarzan (France).gbc" size 2097152 crc c503afbb sha1 9a4821570f565f7a1b3c7aba3c7cd2e1c1a0b570 ) + rom ( name "Tarzan (France).gbc" size 2097152 crc c503afbb sha1 9a4821570f565f7a1b3c7aba3c7cd2e1c1a0b570 flags verified ) ) game ( @@ -42882,6 +44007,18 @@ game ( rom ( name "Taxi 3 (France).gbc" size 1048576 crc 2838996f sha1 e43817c673d47b7587f542dcc9f74190c63629ff ) ) +game ( + name "Tazz (World) (2022-10-14) (Aftermarket) (Unl)" + description "Tazz (World) (2022-10-14) (Aftermarket) (Unl)" + rom ( name "Tazz (World) (2022-10-14) (Aftermarket) (Unl).gbc" size 262144 crc 1d97b754 sha1 39226ed715ae8d94f9187c3e5e8a849eac92ca26 ) +) + +game ( + name "Tazz (World) (2022-10-18) (Aftermarket) (Unl)" + description "Tazz (World) (2022-10-18) (Aftermarket) (Unl)" + rom ( name "Tazz (World) (2022-10-18) (Aftermarket) (Unl).gbc" size 262144 crc 6fb0ba23 sha1 543118e54b166ea2b8b5979f8468cc2f5662161f ) +) + game ( name "Tech Deck Skateboarding (USA, Europe)" description "Tech Deck Skateboarding (USA, Europe)" @@ -42990,18 +44127,6 @@ game ( rom ( name "TG Rally 2 (United Kingdom).gbc" size 1048576 crc 795a9992 sha1 906f886c19c4800a7ac2612951e03a092665b22c ) ) -game ( - name "The Powerpuff Girls - Il Terribile Mojo Jojo (Italy) (Proto)" - description "The Powerpuff Girls - Il Terribile Mojo Jojo (Italy) (Proto)" - rom ( name "The Powerpuff Girls - Il Terribile Mojo Jojo (Italy) (Proto).gbc" size 2097152 crc 5c265103 sha1 5ca837632930689fad9086b772928bf5995193f9 ) -) - -game ( - name "The Second Edition Harry Boy - The Secret of the Chamber of Secrets (USA) (Unl)" - description "The Second Edition Harry Boy - The Secret of the Chamber of Secrets (USA) (Unl)" - rom ( name "The Second Edition Harry Boy - The Secret of the Chamber of Secrets (USA) (Unl).gbc" size 2097152 crc cba2c784 sha1 99d852998fa84c8c0160cc9dfb2e2165a94756ca ) -) - game ( name "Three Lions (United Kingdom) (En,Fr,De,Es,It,Nl,Sv) (GB Compatible)" description "Three Lions (United Kingdom) (En,Fr,De,Es,It,Nl,Sv) (GB Compatible)" @@ -43074,6 +44199,12 @@ game ( rom ( name "Tiny Toon Adventures - Dizzy's Candy Quest (USA) (Proto).gbc" size 1048576 crc 23bb87a5 sha1 4eb0359a278173ae6c12e9452a7a2a9573a58c77 ) ) +game ( + name "Tir Na Nog (World) (Aftermarket) (Unl)" + description "Tir Na Nog (World) (Aftermarket) (Unl)" + rom ( name "Tir Na Nog (World) (Aftermarket) (Unl).gbc" size 1048576 crc 44524c90 sha1 ac712228460e9d4268f90ec4cf9ac2137fa667c4 ) +) + game ( name "Titi - Le Tour du Monde en 80 Chats (France)" description "Titi - Le Tour du Monde en 80 Chats (France)" @@ -43099,9 +44230,9 @@ game ( ) game ( - name "Tobu Tobu Girl Deluxe (World) (GB Compatible) (Aftermarket) (Homebrew)" - description "Tobu Tobu Girl Deluxe (World) (GB Compatible) (Aftermarket) (Homebrew)" - rom ( name "Tobu Tobu Girl Deluxe (World) (GB Compatible) (Aftermarket) (Homebrew).gbc" size 262144 crc 16650a8b sha1 fe6eef70d48dda741f7ad3b6cc5e753e8cd13239 ) + name "Tobu Tobu Girl Deluxe (World) (GB Compatible) (Aftermarket) (Unl)" + description "Tobu Tobu Girl Deluxe (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Tobu Tobu Girl Deluxe (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 16650a8b sha1 fe6eef70d48dda741f7ad3b6cc5e753e8cd13239 ) ) game ( @@ -43381,9 +44512,9 @@ game ( ) game ( - name "Tower of Evil (World) (Aftermarket) (Homebrew)" - description "Tower of Evil (World) (Aftermarket) (Homebrew)" - rom ( name "Tower of Evil (World) (Aftermarket) (Homebrew).gbc" size 524288 crc aeb6e36f sha1 e9a6a007e935afb522c67d355e0354b0fb9af3c6 ) + name "Tower of Evil (World) (Aftermarket) (Unl)" + description "Tower of Evil (World) (Aftermarket) (Unl)" + rom ( name "Tower of Evil (World) (Aftermarket) (Unl).gbc" size 524288 crc aeb6e36f sha1 e9a6a007e935afb522c67d355e0354b0fb9af3c6 ) ) game ( @@ -43441,9 +44572,9 @@ game ( ) game ( - name "Treasure Island Color (World) (Aftermarket) (Homebrew)" - description "Treasure Island Color (World) (Aftermarket) (Homebrew)" - rom ( name "Treasure Island Color (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 4ab0eb5a sha1 3ebddd4e1f5b3a87726ef34a5b7d36de8c722f36 ) + name "Treasure Island Color (World) (Aftermarket) (Unl)" + description "Treasure Island Color (World) (Aftermarket) (Unl)" + rom ( name "Treasure Island Color (World) (Aftermarket) (Unl).gbc" size 262144 crc 4ab0eb5a sha1 3ebddd4e1f5b3a87726ef34a5b7d36de8c722f36 ) ) game ( @@ -43530,6 +44661,12 @@ game ( rom ( name "Turok 3 - Shadow of Oblivion (USA, Europe) (En,Fr,De,Es).gbc" size 1048576 crc 6d48765e sha1 4b240f6b8e3648f2cbafa2fd6ee4e5b508950122 flags verified ) ) +game ( + name "Tutti Frutti (World) (Aftermarket) (Unl)" + description "Tutti Frutti (World) (Aftermarket) (Unl)" + rom ( name "Tutti Frutti (World) (Aftermarket) (Unl).gbc" size 262144 crc ca377ca6 sha1 53d97b93b9d190de40ed2d81e18b4cbe44f99a23 ) +) + game ( name "Tutty (Europe) (Demo)" description "Tutty (Europe) (Demo)" @@ -43573,9 +44710,9 @@ game ( ) game ( - name "Tycoon Tex (World) (Aftermarket) (Homebrew)" - description "Tycoon Tex (World) (Aftermarket) (Homebrew)" - rom ( name "Tycoon Tex (World) (Aftermarket) (Homebrew).gbc" size 262144 crc fa8436ba sha1 d37bcd93d647ac7efb761c539785a4ef570cdcc8 ) + name "Tycoon Tex (World) (Aftermarket) (Unl)" + description "Tycoon Tex (World) (Aftermarket) (Unl)" + rom ( name "Tycoon Tex (World) (Aftermarket) (Unl).gbc" size 262144 crc fa8436ba sha1 d37bcd93d647ac7efb761c539785a4ef570cdcc8 ) ) game ( @@ -43656,6 +44793,12 @@ game ( rom ( name "Uno (USA) (GB Compatible).gbc" size 1048576 crc f026d509 sha1 20868148461618d1195570775b183a065781ce35 ) ) +game ( + name "UXB (World) (Aftermarket) (Unl)" + description "UXB (World) (Aftermarket) (Unl)" + rom ( name "UXB (World) (Aftermarket) (Unl).gbc" size 262144 crc dd42be4c sha1 6ef7abb689b1c75f613ed253644fbb0f377b7a95 ) +) + game ( name "V-Rally - Championship Edition (Europe) (En,Fr,De,Es)" description "V-Rally - Championship Edition (Europe) (En,Fr,De,Es)" @@ -43674,6 +44817,12 @@ game ( rom ( name "V-Rally - Edition 99 (USA) (En,Fr,Es).gbc" size 1048576 crc da300c6c sha1 638266c9d2d16486c2ed00510176112071b05e2c ) ) +game ( + name "Varmit (World) (Aftermarket) (Unl)" + description "Varmit (World) (Aftermarket) (Unl)" + rom ( name "Varmit (World) (Aftermarket) (Unl).gbc" size 2097152 crc e4827006 sha1 b61876c714c47f62a77304b79f6e34b8d0e7f8a2 ) +) + game ( name "Vegas Games (Europe) (En,Fr,De)" description "Vegas Games (Europe) (En,Fr,De)" @@ -43716,12 +44865,24 @@ game ( rom ( name "Visiteurs, Les (France) (GB Compatible).gbc" size 1048576 crc d843f898 sha1 307b5d80fd7def049d446bf3406ec8d57dfee93d ) ) +game ( + name "VOX (World) (Aftermarket) (Unl)" + description "VOX (World) (Aftermarket) (Unl)" + rom ( name "VOX (World) (Aftermarket) (Unl).gbc" size 262144 crc 346561fb sha1 54a03c77653a19eb1b0d8a6ac82f498f83f35ef2 ) +) + game ( name "VR Sports - Powerboat Racing (USA) (Proto)" description "VR Sports - Powerboat Racing (USA) (Proto)" rom ( name "VR Sports - Powerboat Racing (USA) (Proto).gbc" size 1048576 crc cff671f1 sha1 1c77f032cdd373bfa108ea82c463f8f9f6874c71 ) ) +game ( + name "Wacky Painter (World) (Aftermarket) (Unl)" + description "Wacky Painter (World) (Aftermarket) (Unl)" + rom ( name "Wacky Painter (World) (Aftermarket) (Unl).gbc" size 262144 crc 09daa18d sha1 5425dc675101d63bea03b1c8f46f86fe200ad392 ) +) + game ( name "Wacky Races (Europe) (En,Fr,De,Es,It,Nl)" description "Wacky Races (Europe) (En,Fr,De,Es,It,Nl)" @@ -43734,6 +44895,12 @@ game ( rom ( name "Wacky Races (USA) (En,Fr,Es).gbc" size 1048576 crc 543abb1b sha1 3ec148027d0e5a075d7a4597232e64215c060fa7 ) ) +game ( + name "Wai Xing Tanxian Zhi Xingqiu Dazhan (Taiwan) (Unl)" + description "Wai Xing Tanxian Zhi Xingqiu Dazhan (Taiwan) (Unl)" + rom ( name "Wai Xing Tanxian Zhi Xingqiu Dazhan (Taiwan) (Unl).gbc" size 2097152 crc 4e600093 sha1 32067be39f0bdc354c31a7a02f24ba00b61ae4f1 flags verified ) +) + game ( name "Walt Disney World Quest - Magical Racing Tour (Europe) (Fr,De,Es)" description "Walt Disney World Quest - Magical Racing Tour (Europe) (Fr,De,Es)" @@ -43746,6 +44913,12 @@ game ( rom ( name "Walt Disney World Quest - Magical Racing Tour (USA, Europe).gbc" size 2097152 crc 56beb694 sha1 529289ccfd21397405d08305409c5d9b2119505e ) ) +game ( + name "Wangzu Tiantang (Taiwan) (Unl)" + description "Wangzu Tiantang (Taiwan) (Unl)" + rom ( name "Wangzu Tiantang (Taiwan) (Unl).gbc" size 2097152 crc dee597fc sha1 4e01c94ab55f2d299bd0bea83c0ef500fb9e738c ) +) + game ( name "Warau Inu no Bouken - Silly Go Lucky! (Japan)" description "Warau Inu no Bouken - Silly Go Lucky! (Japan)" @@ -43819,21 +44992,21 @@ game ( ) game ( - name "Waternet (World) (Aftermarket) (Homebrew)" - description "Waternet (World) (Aftermarket) (Homebrew)" - rom ( name "Waternet (World) (Aftermarket) (Homebrew).gbc" size 32768 crc ef202121 sha1 a11f931b18ba42f48a91e817b7117fa0e3e79518 ) + name "Waternet (World) (GB Compatible) (Aftermarket) (Unl)" + description "Waternet (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Waternet (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 32768 crc ef202121 sha1 a11f931b18ba42f48a91e817b7117fa0e3e79518 ) ) game ( - name "Waternet (World) (Batteryless Save Flash Cartridge Version) (Aftermarket) (Homebrew)" - description "Waternet (World) (Batteryless Save Flash Cartridge Version) (Aftermarket) (Homebrew)" - rom ( name "Waternet (World) (Batteryless Save Flash Cartridge Version) (Aftermarket) (Homebrew).gbc" size 131072 crc 75591760 sha1 e04b01ada17e6924503029cd09a107b7f5d06f17 ) + name "Waternet (World) (Batteryless Save Flash Cartridge Version) (GB Compatible) (Aftermarket) (Unl)" + description "Waternet (World) (Batteryless Save Flash Cartridge Version) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Waternet (World) (Batteryless Save Flash Cartridge Version) (GB Compatible) (Aftermarket) (Unl).gbc" size 131072 crc 75591760 sha1 e04b01ada17e6924503029cd09a107b7f5d06f17 ) ) game ( - name "Waternet (World) (Batteryless Generic Save Flash Cartridge Version) (Aftermarket) (Homebrew)" - description "Waternet (World) (Batteryless Generic Save Flash Cartridge Version) (Aftermarket) (Homebrew)" - rom ( name "Waternet (World) (Batteryless Generic Save Flash Cartridge Version) (Aftermarket) (Homebrew).gbc" size 131072 crc f8402dc5 sha1 6feb4178589e87c6267683078201f0630cca23d6 ) + name "Waternet (World) (Batteryless Generic Save Flash Cartridge Version) (GB Compatible) (Aftermarket) (Unl)" + description "Waternet (World) (Batteryless Generic Save Flash Cartridge Version) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Waternet (World) (Batteryless Generic Save Flash Cartridge Version) (GB Compatible) (Aftermarket) (Unl).gbc" size 131072 crc f8402dc5 sha1 6feb4178589e87c6267683078201f0630cca23d6 ) ) game ( @@ -43884,6 +45057,12 @@ game ( rom ( name "Wetrix GB (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 6215c5b3 sha1 92944052b6e4448abf0103f085998662e0140825 ) ) +game ( + name "Where's My Cake (World) (GB Compatible) (Aftermarket) (Unl)" + description "Where's My Cake (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Where's My Cake (World) (GB Compatible) (Aftermarket) (Unl).GBC" size 65536 crc 28114b89 sha1 05b90f0a7e92d42b3008c749d5edeab2a2eae973 ) +) + game ( name "Who Wants to Be a Millionaire - 2nd Edition (USA)" description "Who Wants to Be a Millionaire - 2nd Edition (USA)" @@ -43897,9 +45076,9 @@ game ( ) game ( - name "Wing Warriors (USA) (En,Fr,Es) (Aftermarket) (Homebrew)" - description "Wing Warriors (USA) (En,Fr,Es) (Aftermarket) (Homebrew)" - rom ( name "Wing Warriors (USA) (En,Fr,Es) (Aftermarket) (Homebrew).gbc" size 131072 crc 04e04980 sha1 574cf911d6d8f73699203cd6db42ceec777f7f93 ) + name "Wing Warriors (World) (En,Fr,Es) (GB Compatible) (Aftermarket) (Unl)" + description "Wing Warriors (World) (En,Fr,Es) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Wing Warriors (World) (En,Fr,Es) (GB Compatible) (Aftermarket) (Unl).gbc" size 131072 crc 7328cfb6 sha1 039874148f27ffe533de4ef086f29e724a3217a9 ) ) game ( @@ -43927,9 +45106,9 @@ game ( ) game ( - name "Wizard of Wor (World) (Aftermarket) (Homebrew)" - description "Wizard of Wor (World) (Aftermarket) (Homebrew)" - rom ( name "Wizard of Wor (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 492a9529 sha1 a391646d259a39e7026574145e0a2bacf5f6937b ) + name "Wizard of Wor (World) (Aftermarket) (Unl)" + description "Wizard of Wor (World) (Aftermarket) (Unl)" + rom ( name "Wizard of Wor (World) (Aftermarket) (Unl).gbc" size 524288 crc 492a9529 sha1 a391646d259a39e7026574145e0a2bacf5f6937b ) ) game ( @@ -43999,9 +45178,9 @@ game ( ) game ( - name "World Cup (World) (Aftermarket) (Homebrew)" - description "World Cup (World) (Aftermarket) (Homebrew)" - rom ( name "World Cup (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 56b3ab5f sha1 075890510b3ef205ca50ebbbcf7c0a0c56032cb9 ) + name "World Cup (World) (Aftermarket) (Unl)" + description "World Cup (World) (Aftermarket) (Unl)" + rom ( name "World Cup (World) (Aftermarket) (Unl).gbc" size 262144 crc 56b3ab5f sha1 075890510b3ef205ca50ebbbcf7c0a0c56032cb9 ) ) game ( @@ -44095,27 +45274,27 @@ game ( ) game ( - name "Xi You Ji (Taiwan) (Unl)" - description "Xi You Ji (Taiwan) (Unl)" - rom ( name "Xi You Ji (Taiwan) (Unl).gbc" size 524288 crc f2fc4884 sha1 1f1bb35c47b8bd51edf9511513bb5e5e5e07eec1 ) + name "Xian Dan Chaoren - Ultraman (Taiwan) (Unl)" + description "Xian Dan Chaoren - Ultraman (Taiwan) (Unl)" + rom ( name "Xian Dan Chaoren - Ultraman (Taiwan) (Unl).gbc" size 2097152 crc e81701e8 sha1 2a96a6f2164d80aa81eebdebc0d8f2ff45c3180f ) ) game ( - name "Xiao Tai Ji - Shen Hua Li Xian (Taiwan) (Unl)" - description "Xiao Tai Ji - Shen Hua Li Xian (Taiwan) (Unl)" - rom ( name "Xiao Tai Ji - Shen Hua Li Xian (Taiwan) (Unl).gbc" size 2097152 crc 322c3816 sha1 c89f8d19a52ab5f183a03d65680bf80d1af4a0ee ) + name "Xiao Taiji - Shenhua Lixian (Taiwan) (Unl)" + description "Xiao Taiji - Shenhua Lixian (Taiwan) (Unl)" + rom ( name "Xiao Taiji - Shenhua Lixian (Taiwan) (Unl).gbc" size 2097152 crc 322c3816 sha1 c89f8d19a52ab5f183a03d65680bf80d1af4a0ee ) ) game ( - name "Xin Feng Shen Bang (Taiwan) (Unl)" - description "Xin Feng Shen Bang (Taiwan) (Unl)" - rom ( name "Xin Feng Shen Bang (Taiwan) (Unl).gbc" size 2097152 crc cf8bd780 sha1 99344231bfe3f73dfb95766e0d02a43fbf2e4acc ) + name "Xin Fengshenbang (Taiwan) (Unl)" + description "Xin Fengshenbang (Taiwan) (Unl)" + rom ( name "Xin Fengshenbang (Taiwan) (Unl).gbc" size 2097152 crc cf8bd780 sha1 99344231bfe3f73dfb95766e0d02a43fbf2e4acc ) ) game ( - name "Xin Guangming Yu Hei'an 2 - Zhushen De Yichan (Taiwan) (Unl)" - description "Xin Guangming Yu Hei'an 2 - Zhushen De Yichan (Taiwan) (Unl)" - rom ( name "Xin Guangming Yu Hei'an 2 - Zhushen De Yichan (Taiwan) (Unl).gbc" size 1048576 crc 0881ed84 sha1 c1985e8a7a241af5a35748528b3e6a9d49b788e7 ) + name "Xin Guangming Yu Hei'an 2 - Zhushen de Yichan (Taiwan) (Unl)" + description "Xin Guangming Yu Hei'an 2 - Zhushen de Yichan (Taiwan) (Unl)" + rom ( name "Xin Guangming Yu Hei'an 2 - Zhushen de Yichan (Taiwan) (Unl).gbc" size 1048576 crc 0881ed84 sha1 c1985e8a7a241af5a35748528b3e6a9d49b788e7 ) ) game ( @@ -44125,15 +45304,21 @@ game ( ) game ( - name "Xin Tiao Hui Yi (Taiwan) (Unl)" - description "Xin Tiao Hui Yi (Taiwan) (Unl)" - rom ( name "Xin Tiao Hui Yi (Taiwan) (Unl).gbc" size 4194304 crc 104968e7 sha1 86e2b619976d22818e4a7c23d7f8a97e9358e51e ) + name "Xingqiu Dazhan II - Kelong Ren Zhanyi (Taiwan) (Unl)" + description "Xingqiu Dazhan II - Kelong Ren Zhanyi (Taiwan) (Unl)" + rom ( name "Xingqiu Dazhan II - Kelong Ren Zhanyi (Taiwan) (Unl).gbc" size 2097152 crc 6df86db6 sha1 b6089f2d1064d1851a89ffb20f8e2f8aeb5fe732 ) ) game ( - name "Xing Qiu Da Zhan II - Ke Long Ren Zhan Yi (Taiwan) (Unl)" - description "Xing Qiu Da Zhan II - Ke Long Ren Zhan Yi (Taiwan) (Unl)" - rom ( name "Xing Qiu Da Zhan II - Ke Long Ren Zhan Yi (Taiwan) (Unl).gbc" size 2097152 crc 6df86db6 sha1 b6089f2d1064d1851a89ffb20f8e2f8aeb5fe732 ) + name "Xintiao Huiyi (Taiwan) (Unl)" + description "Xintiao Huiyi (Taiwan) (Unl)" + rom ( name "Xintiao Huiyi (Taiwan) (Unl).gbc" size 4194304 crc 104968e7 sha1 86e2b619976d22818e4a7c23d7f8a97e9358e51e ) +) + +game ( + name "Xiyou Ji (Taiwan) (Unl)" + description "Xiyou Ji (Taiwan) (Unl)" + rom ( name "Xiyou Ji (Taiwan) (Unl).gbc" size 524288 crc f2fc4884 sha1 1f1bb35c47b8bd51edf9511513bb5e5e5e07eec1 ) ) game ( @@ -44167,9 +45352,9 @@ game ( ) game ( - name "Xtreme Wheels (Japan) (NP)" - description "Xtreme Wheels (Japan) (NP)" - rom ( name "Xtreme Wheels (Japan) (NP).gbc" size 1048576 crc 30132ab8 sha1 a9f67640d20771e64b1474144fcca9beebdde85d ) + name "Xtreme Wheels (Japan) (Possible Proto) (NP)" + description "Xtreme Wheels (Japan) (Possible Proto) (NP)" + rom ( name "Xtreme Wheels (Japan) (Possible Proto) (NP).gbc" size 1048576 crc 30132ab8 sha1 a9f67640d20771e64b1474144fcca9beebdde85d ) ) game ( @@ -44184,6 +45369,12 @@ game ( rom ( name "Yars' Revenge (USA, Europe) (GB Compatible).gbc" size 1048576 crc d6a26444 sha1 45fb176d539ae4a65af1f6340a9bd398dd7956d2 ) ) +game ( + name "Year After, The (World) (En,Fr,Pt) (GB Compatible) (Aftermarket) (Unl)" + description "Year After, The (World) (En,Fr,Pt) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Year After, The (World) (En,Fr,Pt) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc b304833d sha1 c2be46b8230bfcf509d031902c9bf144de97ef58 ) +) + game ( name "Yin Ban Zhongwen RPG Zhanlve + Dongzuo + Yizhi 12 in 1 (Taiwan) (Unl)" description "Yin Ban Zhongwen RPG Zhanlve + Dongzuo + Yizhi 12 in 1 (Taiwan) (Unl)" @@ -44202,6 +45393,12 @@ game ( rom ( name "Yingxiong Tianxia (Taiwan) (Zh) (Unl).gbc" size 2097152 crc 5f2d6317 sha1 41f5f0c11e4e99b7d2d1433b09922a0fd2c48d7f ) ) +game ( + name "Yixing VS Tiexue Zhanshi (Taiwan) (Unl)" + description "Yixing VS Tiexue Zhanshi (Taiwan) (Unl)" + rom ( name "Yixing VS Tiexue Zhanshi (Taiwan) (Unl).gbc" size 2097152 crc 45b048ee sha1 5ab50c386a0d9644f0d0337b6a1db22f5cb6ceb9 ) +) + game ( name "Yogi Bear - Great Balloon Blast (USA)" description "Yogi Bear - Great Balloon Blast (USA)" @@ -44209,9 +45406,9 @@ game ( ) game ( - name "Yong Zhe Dou E Long VIII (Taiwan) (Unl)" - description "Yong Zhe Dou E Long VIII (Taiwan) (Unl)" - rom ( name "Yong Zhe Dou E Long VIII (Taiwan) (Unl).gbc" size 2097152 crc 8b8b84ec sha1 d71788eabb4d2535e31773338523a721e2784bb5 ) + name "Yongzhe Dou E Long VIII (Taiwan) (Unl)" + description "Yongzhe Dou E Long VIII (Taiwan) (Unl)" + rom ( name "Yongzhe Dou E Long VIII (Taiwan) (Unl).gbc" size 2097152 crc 8b8b84ec sha1 d71788eabb4d2535e31773338523a721e2784bb5 ) ) game ( @@ -44298,18 +45495,18 @@ game ( rom ( name "Yu-Gi-Oh! Duel Monsters III - Tri Holy God Advant (Japan).gbc" size 4194304 crc 9f9dbab4 sha1 dc4753a2f12360d921a147287673b4448394a9d3 flags verified ) ) -game ( - name "Yue Nan Zhan Yi 3 (Taiwan) (Unl)" - description "Yue Nan Zhan Yi 3 (Taiwan) (Unl)" - rom ( name "Yue Nan Zhan Yi 3 (Taiwan) (Unl).gbc" size 2097152 crc 9268bb9f sha1 17ceb35582c89303f206eb7e93cbfe8a4c2b5aaa ) -) - game ( name "Yuenan Zhanyi - Chong Jian Tian Ri (Taiwan) (Unl)" description "Yuenan Zhanyi - Chong Jian Tian Ri (Taiwan) (Unl)" rom ( name "Yuenan Zhanyi - Chong Jian Tian Ri (Taiwan) (Unl).gbc" size 2097152 crc c19b912a sha1 09b0f8fe8c7948e847ad01f24d05513c49b77ad5 ) ) +game ( + name "Yuenan Zhanyi 3 (Taiwan) (Unl)" + description "Yuenan Zhanyi 3 (Taiwan) (Unl)" + rom ( name "Yuenan Zhanyi 3 (Taiwan) (Unl).gbc" size 2097152 crc 9268bb9f sha1 17ceb35582c89303f206eb7e93cbfe8a4c2b5aaa ) +) + game ( name "Yuenan Zhanyi X - Shenru Dihou (Taiwan) (Unl)" description "Yuenan Zhanyi X - Shenru Dihou (Taiwan) (Unl)" @@ -44317,9 +45514,15 @@ game ( ) game ( - name "Zap'em (World) (Aftermarket) (Homebrew)" - description "Zap'em (World) (Aftermarket) (Homebrew)" - rom ( name "Zap'em (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 66f17c5e sha1 63d44c8c2492dc32bcf632feb2d994e9af25fc97 ) + name "Zagan Warrior (World) (Aftermarket) (Unl)" + description "Zagan Warrior (World) (Aftermarket) (Unl)" + rom ( name "Zagan Warrior (World) (Aftermarket) (Unl).gbc" size 262144 crc e770f71a sha1 99d9267946e7bf26942a8276863ba50ef0b8f90e ) +) + +game ( + name "Zap'em (World) (Aftermarket) (Unl)" + description "Zap'em (World) (Aftermarket) (Unl)" + rom ( name "Zap'em (World) (Aftermarket) (Unl).gbc" size 262144 crc 66f17c5e sha1 63d44c8c2492dc32bcf632feb2d994e9af25fc97 ) ) game ( @@ -44395,21 +45598,21 @@ game ( ) game ( - name "Zhen San Guo Wu Shuang 2 - Shin Sangokumusou (Taiwan) (Unl)" - description "Zhen San Guo Wu Shuang 2 - Shin Sangokumusou (Taiwan) (Unl)" - rom ( name "Zhen San Guo Wu Shuang 2 - Shin Sangokumusou (Taiwan) (Unl).gbc" size 2097152 crc 95c322c9 sha1 e6ab58d212de83cb18e9eadac4cee5d0f6e4e31f ) + name "Zhen Sanguo Wushuang 2 - Shin Sangokumusou (Taiwan) (Unl)" + description "Zhen Sanguo Wushuang 2 - Shin Sangokumusou (Taiwan) (Unl)" + rom ( name "Zhen Sanguo Wushuang 2 - Shin Sangokumusou (Taiwan) (Unl).gbc" size 2097152 crc 95c322c9 sha1 e6ab58d212de83cb18e9eadac4cee5d0f6e4e31f ) ) game ( - name "Zhi Huan Wang - Shou Bu Qu (Taiwan) (Zh) (Unl)" - description "Zhi Huan Wang - Shou Bu Qu (Taiwan) (Zh) (Unl)" - rom ( name "Zhi Huan Wang - Shou Bu Qu (Taiwan) (Zh) (Unl).gbc" size 2097152 crc 8160b3f5 sha1 97d7e0d05205b02c6cb69e68978a3f0fd2e3fde0 ) + name "Zhihuan Wang - Shoubu Qu (Taiwan) (Zh) (Unl)" + description "Zhihuan Wang - Shoubu Qu (Taiwan) (Zh) (Unl)" + rom ( name "Zhihuan Wang - Shoubu Qu (Taiwan) (Zh) (Unl).gbc" size 2097152 crc 8160b3f5 sha1 97d7e0d05205b02c6cb69e68978a3f0fd2e3fde0 ) ) game ( - name "Zhi Zhu Xia III - Dian Ying Ban (Taiwan) (Unl)" - description "Zhi Zhu Xia III - Dian Ying Ban (Taiwan) (Unl)" - rom ( name "Zhi Zhu Xia III - Dian Ying Ban (Taiwan) (Unl).gbc" size 2097152 crc d311efcc sha1 9eb0c468d5f898ef7b4d6ff74d7ff497b8c7818d ) + name "Zhizhu Xia III - Dianying Ban (Taiwan) (Unl)" + description "Zhizhu Xia III - Dianying Ban (Taiwan) (Unl)" + rom ( name "Zhizhu Xia III - Dianying Ban (Taiwan) (Unl).gbc" size 2097152 crc d311efcc sha1 9eb0c468d5f898ef7b4d6ff74d7ff497b8c7818d ) ) game ( @@ -44418,12 +45621,24 @@ game ( rom ( name "Zidane - Football Generation (Europe) (En,Fr,De,Es,It).gbc" size 1048576 crc e96dbfb5 sha1 06385470bb0b4ef6c743ab6a5e7f8d7a97f6100b ) ) +game ( + name "Zidane - Football Generation (Europe) (En,Fr,De,Es,It) (Beta)" + description "Zidane - Football Generation (Europe) (En,Fr,De,Es,It) (Beta)" + rom ( name "Zidane - Football Generation (Europe) (En,Fr,De,Es,It) (Beta).gbc" size 1048576 crc c2fde3c4 sha1 cba5d2fc5dbbe184e9ed58afe8525044bd8ea040 ) +) + game ( name "Zoboomafoo - Playtime in Zobooland (USA)" description "Zoboomafoo - Playtime in Zobooland (USA)" rom ( name "Zoboomafoo - Playtime in Zobooland (USA).gbc" size 1048576 crc 38d91885 sha1 a85a113bc266325f807f110daaf30feeea4b2738 ) ) +game ( + name "Zodiac (World) (Aftermarket) (Unl)" + description "Zodiac (World) (Aftermarket) (Unl)" + rom ( name "Zodiac (World) (Aftermarket) (Unl).gbc" size 262144 crc c4d86c05 sha1 73bd9e6e3bede8de489680ba84129e8fd17abbc0 ) +) + game ( name "Zoids - Jashin Fukkatsu! Genobreaker Hen (Japan) (SGB Enhanced) (GB Compatible)" description "Zoids - Jashin Fukkatsu! Genobreaker Hen (Japan) (SGB Enhanced) (GB Compatible)" diff --git a/res/patrons.txt b/res/patrons.txt index c9346ce64..5126cc891 100644 --- a/res/patrons.txt +++ b/res/patrons.txt @@ -8,9 +8,8 @@ gocha Jaime J. Denizard MichaelK__ Miras Absar +Nic Losby Petru-Sebastian Toader Stevoisiak -Tyler Jenkins William K. Leung -Zach Zhongchao Qian diff --git a/res/scripts/analog-interpolate.lua b/res/scripts/analog-interpolate.lua new file mode 100644 index 000000000..d9d985a20 --- /dev/null +++ b/res/scripts/analog-interpolate.lua @@ -0,0 +1,77 @@ +local state = {} +state.period = 4 +state.phase = 0 +state.x = 0 +state.y = 0 + +function state.update() + state.phase = state.phase + 1 + if state.phase == state.period then + state.phase = 0 + end + if state.phase == 0 then + if input.activeGamepad then + local x = input.activeGamepad.axes[1] / 30000 + local y = input.activeGamepad.axes[2] / 30000 + -- Map the circle onto a square, since we don't + -- want to have a duty of 1/sqrt(2) on the angles + local theta = math.atan(y, x) + local r = math.sqrt(x * x + y * y) + if theta < math.pi * -3 / 4 then + r = -r / math.cos(theta) + elseif theta < math.pi * -1 / 4 then + r = -r / math.sin(theta) + elseif theta < math.pi * 1 / 4 then + r = r / math.cos(theta) + elseif theta < math.pi * 3 / 4 then + r = r / math.sin(theta) + elseif theta < math.pi * 5 / 4 then + r = -r / math.cos(theta) + end + state.x = math.cos(theta) * r + state.y = math.sin(theta) * r + else + state.x = 0 + state.y = 0 + end + end +end + +function state.read() + emu:clearKeys(0xF0) + if math.floor(math.abs(state.x) * state.period) > state.phase then + if state.x > 0 then + emu:addKey(C.GB_KEY.RIGHT) + else + emu:addKey(C.GB_KEY.LEFT) + end + end + if math.floor(math.abs(state.y) * state.period) > state.phase then + if state.y > 0 then + emu:addKey(C.GB_KEY.DOWN) + else + emu:addKey(C.GB_KEY.UP) + end + end + + -- The duty cycle approach can confuse menus and the like, + -- so the POV hat setting should force a direction on + if input.activeGamepad and #input.activeGamepad.hats > 0 then + local hat = input.activeGamepad.hats[1] + if hat & C.INPUT_DIR.UP ~= 0 then + emu:addKey(C.GB_KEY.UP) + end + if hat & C.INPUT_DIR.DOWN ~= 0 then + emu:addKey(C.GB_KEY.DOWN) + end + if hat & C.INPUT_DIR.LEFT ~= 0 then + emu:addKey(C.GB_KEY.LEFT) + end + if hat & C.INPUT_DIR.RIGHT ~= 0 then + emu:addKey(C.GB_KEY.RIGHT) + end + end +end + +callbacks:add("frame", state.update) +callbacks:add("keysRead", state.read) diff --git a/res/scripts/light-oscillate.lua b/res/scripts/light-oscillate.lua new file mode 100644 index 000000000..6a2848842 --- /dev/null +++ b/res/scripts/light-oscillate.lua @@ -0,0 +1,8 @@ +local theta = 0 + +function readLight() + theta = math.fmod(theta + math.pi / 120, math.pi * 2) + return (math.sin(theta) + 1) * 75 +end + +emu:setSolarSensorCallback(readLight) diff --git a/res/scripts/logo-bounce.lua b/res/scripts/logo-bounce.lua new file mode 100644 index 000000000..c8192638c --- /dev/null +++ b/res/scripts/logo-bounce.lua @@ -0,0 +1,44 @@ +math.randomseed(os.time()) +local state = {} +state.logo = image.load(script.dir .. "/logo.png") +state.overlay = canvas:newLayer(state.logo.width, state.logo.height) +state.overlay.image:drawImageOpaque(state.logo, 0, 0) +state.x = math.random() * (canvas:screenWidth() - state.logo.width) +state.y = math.random() * (canvas:screenHeight() - state.logo.height) +state.direction = math.floor(math.random() * 3) +state.speed = 0.5 + +state.overlay:setPosition(math.floor(state.x), math.floor(state.y)) +state.overlay:update() + +function state.update() + if state.direction & 1 == 1 then + state.x = state.x + 1 + if state.x > canvas:screenWidth() - state.logo.width then + state.x = (canvas:screenWidth() - state.logo.width) * 2 - state.x + state.direction = state.direction ~ 1 + end + else + state.x = state.x - 1 + if state.x < 0 then + state.x = -state.x + state.direction = state.direction ~ 1 + end + end + if state.direction & 2 == 2 then + state.y = state.y + 1 + if state.y > canvas:screenHeight() - state.logo.height then + state.y = (canvas:screenHeight() - state.logo.height) * 2 - state.y + state.direction = state.direction ~ 2 + end + else + state.y = state.y - 1 + if state.y < 0 then + state.y = -state.y + state.direction = state.direction ~ 2 + end + end + state.overlay:setPosition(math.floor(state.x), math.floor(state.y)) +end + +callbacks:add("frame", state.update) diff --git a/res/scripts/logo.png b/res/scripts/logo.png new file mode 100644 index 000000000..77ec0693a Binary files /dev/null and b/res/scripts/logo.png differ diff --git a/res/scripts/pokemon.lua b/res/scripts/pokemon.lua index d909396d0..c89fb8e75 100644 --- a/res/scripts/pokemon.lua +++ b/res/scripts/pokemon.lua @@ -174,7 +174,7 @@ function Generation1En._readPartyMon(game, address, nameAddress, otAddress) return mon end -function Generation2En._readBoxMon(game, address, nameAddress, otAddress) +function Generation2En._readBoxMon(game, address, nameAddress, otAddress) local mon = {} mon.species = emu:read8(address + 0) mon.item = emu:read8(address + 1) diff --git a/res/scripts/tilt-random-walk.lua b/res/scripts/tilt-random-walk.lua new file mode 100644 index 000000000..41fc7f04b --- /dev/null +++ b/res/scripts/tilt-random-walk.lua @@ -0,0 +1,20 @@ +local r = 0 +local theta = 0 +local rotation = {} + +math.randomseed() + +function rotation.sample() + theta = math.fmod(theta + math.random() / 20, math.pi * 2) + r = math.min(math.max(r + (math.random() - 0.5) / 50, -1), 1) +end + +function rotation.readTiltX() + return math.cos(theta) * r +end + +function rotation.readTiltY() + return math.sin(theta) * r +end + +emu:setRotationCallbacks(rotation) diff --git a/res/sgb-icon-128.png b/res/sgb-icon-128.png new file mode 100644 index 000000000..473adac4c Binary files /dev/null and b/res/sgb-icon-128.png differ diff --git a/res/sgb-icon-16.png b/res/sgb-icon-16.png new file mode 100644 index 000000000..6a76a156f Binary files /dev/null and b/res/sgb-icon-16.png differ diff --git a/res/sgb-icon-24.png b/res/sgb-icon-24.png new file mode 100644 index 000000000..5b8bd7a0d Binary files /dev/null and b/res/sgb-icon-24.png differ diff --git a/res/sgb-icon-256.png b/res/sgb-icon-256.png new file mode 100644 index 000000000..6e3132c57 Binary files /dev/null and b/res/sgb-icon-256.png differ diff --git a/res/sgb-icon-32.png b/res/sgb-icon-32.png new file mode 100644 index 000000000..ea28b979d Binary files /dev/null and b/res/sgb-icon-32.png differ diff --git a/res/sgb-icon-48.png b/res/sgb-icon-48.png new file mode 100644 index 000000000..3a801c3b9 Binary files /dev/null and b/res/sgb-icon-48.png differ diff --git a/res/sgb-icon-64.png b/res/sgb-icon-64.png new file mode 100644 index 000000000..1c9007dda Binary files /dev/null and b/res/sgb-icon-64.png differ diff --git a/res/sgb-icon.svg b/res/sgb-icon.svg new file mode 100644 index 000000000..ca922f1aa --- /dev/null +++ b/res/sgb-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/shaders/hq2x.shader/hq2x.fs b/res/shaders/hq2x.shader/hq2x.fs new file mode 100644 index 000000000..97019bd3c --- /dev/null +++ b/res/shaders/hq2x.shader/hq2x.fs @@ -0,0 +1,143 @@ +/* MIT License +* +* Copyright (c) 2015-2023 Lior Halphon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ +/* Based on this (really good) article: http://blog.pkh.me/p/19-butchering-hqx-scaling-filters.html */ + +/* The colorspace used by the HQnx filters is not really YUV, despite the algorithm description claims it is. It is + also not normalized. Therefore, we shall call the colorspace used by HQnx "HQ Colorspace" to avoid confusion. */ +varying vec2 texCoord; +uniform sampler2D tex; +uniform vec2 texSize; + +vec3 rgb_to_hq_colospace(vec4 rgb) +{ + return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b, + 0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b, + -0.125 * rgb.r + 0.250 * rgb.g - 0.125 * rgb.b); +} + +bool is_different(vec4 a, vec4 b) +{ + vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b)); + return diff.x > 0.018 || diff.y > 0.002 || diff.z > 0.005; +} + +#define P(m, r) ((pattern & (m)) == (r)) + +vec4 interp_2px(vec4 c1, float w1, vec4 c2, float w2) +{ + return (c1 * w1 + c2 * w2) / (w1 + w2); +} + +vec4 interp_3px(vec4 c1, float w1, vec4 c2, float w2, vec4 c3, float w3) +{ + return (c1 * w1 + c2 * w2 + c3 * w3) / (w1 + w2 + w3); +} + +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution) +{ + // o = offset, the width of a pixel + vec2 o = vec2(1, 1) / input_resolution; + + /* We always calculate the top left pixel. If we need a different pixel, we flip the image */ + + // p = the position within a pixel [0...1] + vec2 p = fract(position * input_resolution); + + if (p.x > 0.5) o.x = -o.x; + if (p.y > 0.5) o.y = -o.y; + + vec4 w0 = texture2D(image, position + vec2( -o.x, -o.y)); + vec4 w1 = texture2D(image, position + vec2( 0, -o.y)); + vec4 w2 = texture2D(image, position + vec2( o.x, -o.y)); + vec4 w3 = texture2D(image, position + vec2( -o.x, 0)); + vec4 w4 = texture2D(image, position + vec2( 0, 0)); + vec4 w5 = texture2D(image, position + vec2( o.x, 0)); + vec4 w6 = texture2D(image, position + vec2( -o.x, o.y)); + vec4 w7 = texture2D(image, position + vec2( 0, o.y)); + vec4 w8 = texture2D(image, position + vec2( o.x, o.y)); + + int pattern = 0; + if (is_different(w0, w4)) pattern |= 1; + if (is_different(w1, w4)) pattern |= 2; + if (is_different(w2, w4)) pattern |= 4; + if (is_different(w3, w4)) pattern |= 8; + if (is_different(w5, w4)) pattern |= 16; + if (is_different(w6, w4)) pattern |= 32; + if (is_different(w7, w4)) pattern |= 64; + if (is_different(w8, w4)) pattern |= 128; + + if ((P(0xBF,0x37) || P(0xDB,0x13)) && is_different(w1, w5)) { + return interp_2px(w4, 3.0, w3, 1.0); + } + if ((P(0xDB,0x49) || P(0xEF,0x6D)) && is_different(w7, w3)) { + return interp_2px(w4, 3.0, w1, 1.0); + } + if ((P(0x0B,0x0B) || P(0xFE,0x4A) || P(0xFE,0x1A)) && is_different(w3, w1)) { + return w4; + } + if ((P(0x6F,0x2A) || P(0x5B,0x0A) || P(0xBF,0x3A) || P(0xDF,0x5A) || + P(0x9F,0x8A) || P(0xCF,0x8A) || P(0xEF,0x4E) || P(0x3F,0x0E) || + P(0xFB,0x5A) || P(0xBB,0x8A) || P(0x7F,0x5A) || P(0xAF,0x8A) || + P(0xEB,0x8A)) && is_different(w3, w1)) { + return interp_2px(w4, 3.0, w0, 1.0); + } + if (P(0x0B,0x08)) { + return interp_3px(w4, 2.0, w0, 1.0, w1, 1.0); + } + if (P(0x0B,0x02)) { + return interp_3px(w4, 2.0, w0, 1.0, w3, 1.0); + } + if (P(0x2F,0x2F)) { + return interp_3px(w4, 4.0, w3, 1.0, w1, 1.0); + } + if (P(0xBF,0x37) || P(0xDB,0x13)) { + return interp_3px(w4, 5.0, w1, 2.0, w3, 1.0); + } + if (P(0xDB,0x49) || P(0xEF,0x6D)) { + return interp_3px(w4, 5.0, w3, 2.0, w1, 1.0); + } + if (P(0x1B,0x03) || P(0x4F,0x43) || P(0x8B,0x83) || P(0x6B,0x43)) { + return interp_2px(w4, 3.0, w3, 1.0); + } + if (P(0x4B,0x09) || P(0x8B,0x89) || P(0x1F,0x19) || P(0x3B,0x19)) { + return interp_2px(w4, 3.0, w1, 1.0); + } + if (P(0x7E,0x2A) || P(0xEF,0xAB) || P(0xBF,0x8F) || P(0x7E,0x0E)) { + return interp_3px(w4, 2.0, w3, 3.0, w1, 3.0); + } + if (P(0xFB,0x6A) || P(0x6F,0x6E) || P(0x3F,0x3E) || P(0xFB,0xFA) || + P(0xDF,0xDE) || P(0xDF,0x1E)) { + return interp_2px(w4, 3.0, w0, 1.0); + } + if (P(0x0A,0x00) || P(0x4F,0x4B) || P(0x9F,0x1B) || P(0x2F,0x0B) || + P(0xBE,0x0A) || P(0xEE,0x0A) || P(0x7E,0x0A) || P(0xEB,0x4B) || + P(0x3B,0x1B)) { + return interp_3px(w4, 2.0, w3, 1.0, w1, 1.0); + } + + return interp_3px(w4, 6.0, w3, 1.0, w1, 1.0); +} + +void main() { + gl_FragColor = scale(tex, texCoord, texSize); +} diff --git a/res/shaders/hq2x.shader/manifest.ini b/res/shaders/hq2x.shader/manifest.ini new file mode 100644 index 000000000..c0ab0ef8a --- /dev/null +++ b/res/shaders/hq2x.shader/manifest.ini @@ -0,0 +1,11 @@ +[shader] +name=hq2x +author=Lior Halphon +description="High Quality" 2x scaling +passes=1 + +[pass.0] +fragmentShader=hq2x.fs +blend=0 +width=-2 +height=-2 diff --git a/res/shaders/omniscale.shader/manifest.ini b/res/shaders/omniscale.shader/manifest.ini new file mode 100644 index 000000000..a98e34ce2 --- /dev/null +++ b/res/shaders/omniscale.shader/manifest.ini @@ -0,0 +1,9 @@ +[shader] +name=OmniScale +author=Lior Halphon +description=Resolution-indepedent scaler inspired by the hqx family scalers +passes=1 + +[pass.0] +fragmentShader=omniscale.fs +blend=0 diff --git a/res/shaders/omniscale.shader/omniscale.fs b/res/shaders/omniscale.shader/omniscale.fs new file mode 100644 index 000000000..d6b71475b --- /dev/null +++ b/res/shaders/omniscale.shader/omniscale.fs @@ -0,0 +1,292 @@ +/* MIT License +* +* Copyright (c) 2015-2023 Lior Halphon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ +/* OmniScale is derived from the pattern based design of HQnx, but with the following general differences: + - The actual output calculating was completely redesigned as resolution independent graphic generator. This allows + scaling to any factor. + - HQnx approximations that were good enough for a 2x/3x/4x factor were refined, creating smoother gradients. + - "Quarters" can be interpolated in more ways than in the HQnx filters + - If a pattern does not provide enough information to determine the suitable scaling interpolation, up to 16 pixels + per quarter are sampled (in contrast to the usual 9) in order to determine the best interpolation. + */ +varying vec2 texCoord; +uniform sampler2D tex; +uniform vec2 texSize; +uniform vec2 outputSize; + +/* We use the same colorspace as the HQ algorithms. */ +vec3 rgb_to_hq_colospace(vec4 rgb) +{ + return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b, + 0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b, + -0.125 * rgb.r + 0.250 * rgb.g - 0.125 * rgb.b); +} + + +bool is_different(vec4 a, vec4 b) +{ + vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b)); + return diff.x > 0.018 || diff.y > 0.002 || diff.z > 0.005; +} + +#define P(m, r) ((pattern & (m)) == (r)) + +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + // o = offset, the width of a pixel + vec2 o = vec2(1, 1) / input_resolution; + + /* We always calculate the top left quarter. If we need a different quarter, we flip our co-ordinates */ + + // p = the position within a pixel [0...1] + vec2 p = fract(position * input_resolution); + + if (p.x > 0.5) { + o.x = -o.x; + p.x = 1.0 - p.x; + } + if (p.y > 0.5) { + o.y = -o.y; + p.y = 1.0 - p.y; + } + + vec4 w0 = texture2D(image, position + vec2( -o.x, -o.y)); + vec4 w1 = texture2D(image, position + vec2( 0, -o.y)); + vec4 w2 = texture2D(image, position + vec2( o.x, -o.y)); + vec4 w3 = texture2D(image, position + vec2( -o.x, 0)); + vec4 w4 = texture2D(image, position + vec2( 0, 0)); + vec4 w5 = texture2D(image, position + vec2( o.x, 0)); + vec4 w6 = texture2D(image, position + vec2( -o.x, o.y)); + vec4 w7 = texture2D(image, position + vec2( 0, o.y)); + vec4 w8 = texture2D(image, position + vec2( o.x, o.y)); + + int pattern = 0; + if (is_different(w0, w4)) pattern |= 1 << 0; + if (is_different(w1, w4)) pattern |= 1 << 1; + if (is_different(w2, w4)) pattern |= 1 << 2; + if (is_different(w3, w4)) pattern |= 1 << 3; + if (is_different(w5, w4)) pattern |= 1 << 4; + if (is_different(w6, w4)) pattern |= 1 << 5; + if (is_different(w7, w4)) pattern |= 1 << 6; + if (is_different(w8, w4)) pattern |= 1 << 7; + + if ((P(0xBF,0x37) || P(0xDB,0x13)) && is_different(w1, w5)) { + return mix(w4, w3, 0.5 - p.x); + } + if ((P(0xDB,0x49) || P(0xEF,0x6D)) && is_different(w7, w3)) { + return mix(w4, w1, 0.5 - p.y); + } + if ((P(0x0B,0x0B) || P(0xFE,0x4A) || P(0xFE,0x1A)) && is_different(w3, w1)) { + return w4; + } + if ((P(0x6F,0x2A) || P(0x5B,0x0A) || P(0xBF,0x3A) || P(0xDF,0x5A) || + P(0x9F,0x8A) || P(0xCF,0x8A) || P(0xEF,0x4E) || P(0x3F,0x0E) || + P(0xFB,0x5A) || P(0xBB,0x8A) || P(0x7F,0x5A) || P(0xAF,0x8A) || + P(0xEB,0x8A)) && is_different(w3, w1)) { + return mix(w4, mix(w4, w0, 0.5 - p.x), 0.5 - p.y); + } + if (P(0x0B,0x08)) { + return mix(mix(w0 * 0.375 + w1 * 0.25 + w4 * 0.375, w4 * 0.5 + w1 * 0.5, p.x * 2.0), w4, p.y * 2.0); + } + if (P(0x0B,0x02)) { + return mix(mix(w0 * 0.375 + w3 * 0.25 + w4 * 0.375, w4 * 0.5 + w3 * 0.5, p.y * 2.0), w4, p.x * 2.0); + } + if (P(0x2F,0x2F)) { + float dist = length(p - vec2(0.5)); + float pixel_size = length(1.0 / (output_resolution / input_resolution)); + if (dist < 0.5 - pixel_size / 2) { + return w4; + } + vec4 r; + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist > 0.5 + pixel_size / 2) { + return r; + } + return mix(w4, r, (dist - 0.5 + pixel_size / 2) / pixel_size); + } + if (P(0xBF,0x37) || P(0xDB,0x13)) { + float dist = p.x - 2.0 * p.y; + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); + if (dist > pixel_size / 2) { + return w1; + } + vec4 r = mix(w3, w4, p.x + 0.5); + if (dist < -pixel_size / 2) { + return r; + } + return mix(r, w1, (dist + pixel_size / 2) / pixel_size); + } + if (P(0xDB,0x49) || P(0xEF,0x6D)) { + float dist = p.y - 2.0 * p.x; + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); + if (p.y - 2.0 * p.x > pixel_size / 2) { + return w3; + } + vec4 r = mix(w1, w4, p.x + 0.5); + if (dist < -pixel_size / 2) { + return r; + } + return mix(r, w3, (dist + pixel_size / 2) / pixel_size); + } + if (P(0xBF,0x8F) || P(0x7E,0x0E)) { + float dist = p.x + 2.0 * p.y; + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); + + if (dist > 1.0 + pixel_size / 2) { + return w4; + } + + vec4 r; + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist < 1.0 - pixel_size / 2) { + return r; + } + + return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size); + } + + if (P(0x7E,0x2A) || P(0xEF,0xAB)) { + float dist = p.y + 2.0 * p.x; + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); + + if (p.y + 2.0 * p.x > 1.0 + pixel_size / 2) { + return w4; + } + + vec4 r; + + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist < 1.0 - pixel_size / 2) { + return r; + } + + return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size); + } + + if (P(0x1B,0x03) || P(0x4F,0x43) || P(0x8B,0x83) || P(0x6B,0x43)) { + return mix(w4, w3, 0.5 - p.x); + } + + if (P(0x4B,0x09) || P(0x8B,0x89) || P(0x1F,0x19) || P(0x3B,0x19)) { + return mix(w4, w1, 0.5 - p.y); + } + + if (P(0xFB,0x6A) || P(0x6F,0x6E) || P(0x3F,0x3E) || P(0xFB,0xFA) || + P(0xDF,0xDE) || P(0xDF,0x1E)) { + return mix(w4, w0, (1.0 - p.x - p.y) / 2.0); + } + + if (P(0x4F,0x4B) || P(0x9F,0x1B) || P(0x2F,0x0B) || + P(0xBE,0x0A) || P(0xEE,0x0A) || P(0x7E,0x0A) || P(0xEB,0x4B) || + P(0x3B,0x1B)) { + float dist = p.x + p.y; + float pixel_size = length(1.0 / (output_resolution / input_resolution)); + + if (dist > 0.5 + pixel_size / 2) { + return w4; + } + + vec4 r; + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist < 0.5 - pixel_size / 2) { + return r; + } + + return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size); + } + + if (P(0x0B,0x01)) { + return mix(mix(w4, w3, 0.5 - p.x), mix(w1, (w1 + w3) / 2.0, 0.5 - p.x), 0.5 - p.y); + } + + if (P(0x0B,0x00)) { + return mix(mix(w4, w3, 0.5 - p.x), mix(w1, w0, 0.5 - p.x), 0.5 - p.y); + } + + float dist = p.x + p.y; + float pixel_size = length(1.0 / (output_resolution / input_resolution)); + + if (dist > 0.5 + pixel_size / 2) { + return w4; + } + + /* We need more samples to "solve" this diagonal */ + vec4 x0 = texture2D(image, position + vec2( -o.x * 2.0, -o.y * 2.0)); + vec4 x1 = texture2D(image, position + vec2( -o.x , -o.y * 2.0)); + vec4 x2 = texture2D(image, position + vec2( 0.0 , -o.y * 2.0)); + vec4 x3 = texture2D(image, position + vec2( o.x , -o.y * 2.0)); + vec4 x4 = texture2D(image, position + vec2( -o.x * 2.0, -o.y )); + vec4 x5 = texture2D(image, position + vec2( -o.x * 2.0, 0.0 )); + vec4 x6 = texture2D(image, position + vec2( -o.x * 2.0, o.y )); + + if (is_different(x0, w4)) pattern |= 1 << 8; + if (is_different(x1, w4)) pattern |= 1 << 9; + if (is_different(x2, w4)) pattern |= 1 << 10; + if (is_different(x3, w4)) pattern |= 1 << 11; + if (is_different(x4, w4)) pattern |= 1 << 12; + if (is_different(x5, w4)) pattern |= 1 << 13; + if (is_different(x6, w4)) pattern |= 1 << 14; + + int diagonal_bias = -7; + while (pattern != 0) { + diagonal_bias += pattern & 1; + pattern >>= 1; + } + + if (diagonal_bias <= 0) { + vec4 r = mix(w1, w3, p.y - p.x + 0.5); + if (dist < 0.5 - pixel_size / 2) { + return r; + } + return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size); + } + + return w4; +} + +void main() { + gl_FragColor = scale(tex, texCoord, texSize, outputSize); +} diff --git a/src/arm/debugger/cli-debugger.c b/src/arm/debugger/cli-debugger.c index 4df448151..60caa7a21 100644 --- a/src/arm/debugger/cli-debugger.c +++ b/src/arm/debugger/cli-debugger.c @@ -52,7 +52,7 @@ static inline void _printPSR(struct CLIDebuggerBackend* be, union PSR psr) { } static void _disassemble(struct CLIDebuggerSystem* debugger, struct CLIDebugVector* dv) { - struct ARMCore* cpu = debugger->p->d.core->cpu; + struct ARMCore* cpu = debugger->p->d.p->core->cpu; _disassembleMode(debugger->p, dv, cpu->executionMode); } @@ -65,7 +65,7 @@ static void _disassembleThumb(struct CLIDebugger* debugger, struct CLIDebugVecto } static void _disassembleMode(struct CLIDebugger* debugger, struct CLIDebugVector* dv, enum ExecutionMode mode) { - struct ARMCore* cpu = debugger->d.core->cpu; + struct ARMCore* cpu = debugger->d.p->core->cpu; uint32_t address; int size; int wordSize; @@ -98,7 +98,7 @@ static void _disassembleMode(struct CLIDebugger* debugger, struct CLIDebugVector static inline uint32_t _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode) { struct CLIDebuggerBackend* be = debugger->backend; - struct mCore* core = debugger->d.core; + struct mCore* core = debugger->d.p->core; char disassembly[64]; struct ARMInstructionInfo info; address &= ~(WORD_SIZE_THUMB - 1); @@ -130,7 +130,7 @@ static inline uint32_t _printLine(struct CLIDebugger* debugger, uint32_t address static void _printStatus(struct CLIDebuggerSystem* debugger) { struct CLIDebuggerBackend* be = debugger->p->backend; - struct ARMCore* cpu = debugger->p->d.core->cpu; + struct ARMCore* cpu = debugger->p->d.p->core->cpu; int r; for (r = 0; r < 16; r += 4) { be->printf(be, "%sr%i: %08X %sr%i: %08X %sr%i: %08X %sr%i: %08X\n", @@ -141,7 +141,7 @@ static void _printStatus(struct CLIDebuggerSystem* debugger) { } be->printf(be, "cpsr: "); _printPSR(be, cpu->cpsr); - be->printf(be, "Cycle: %" PRIu64 "\n", mTimingGlobalTime(debugger->p->d.core->timing)); + be->printf(be, "Cycle: %" PRIu64 "\n", mTimingGlobalTime(debugger->p->d.p->core->timing)); int instructionLength; enum ExecutionMode mode = cpu->cpsr.t; if (mode == MODE_ARM) { @@ -159,7 +159,7 @@ static void _setBreakpointARM(struct CLIDebugger* debugger, struct CLIDebugVecto return; } uint32_t address = dv->intValue; - ssize_t id = ARMDebuggerSetSoftwareBreakpoint(debugger->d.platform, address, MODE_ARM); + ssize_t id = ARMDebuggerSetSoftwareBreakpoint(debugger->d.p->platform, &debugger->d, address, MODE_ARM); if (id > 0) { debugger->backend->printf(debugger->backend, INFO_BREAKPOINT_ADDED, id); } @@ -172,7 +172,7 @@ static void _setBreakpointThumb(struct CLIDebugger* debugger, struct CLIDebugVec return; } uint32_t address = dv->intValue; - ssize_t id = ARMDebuggerSetSoftwareBreakpoint(debugger->d.platform, address, MODE_THUMB); + ssize_t id = ARMDebuggerSetSoftwareBreakpoint(debugger->d.p->platform, &debugger->d, address, MODE_THUMB); if (id > 0) { debugger->backend->printf(debugger->backend, INFO_BREAKPOINT_ADDED, id); } diff --git a/src/arm/debugger/debugger.c b/src/arm/debugger/debugger.c index afc1e0c49..640e2849a 100644 --- a/src/arm/debugger/debugger.c +++ b/src/arm/debugger/debugger.c @@ -187,16 +187,18 @@ static struct ARMDebugBreakpoint* _lookupBreakpoint(struct ARMDebugBreakpointLis return 0; } -static void _destroyBreakpoint(struct ARMDebugBreakpoint* breakpoint) { +static void _destroyBreakpoint(struct mDebugger* debugger, struct ARMDebugBreakpoint* breakpoint) { if (breakpoint->d.condition) { parseFree(breakpoint->d.condition); } + TableRemove(&debugger->pointOwner, breakpoint->d.id); } -static void _destroyWatchpoint(struct mWatchpoint* watchpoint) { +static void _destroyWatchpoint(struct mDebugger* debugger, struct mWatchpoint* watchpoint) { if (watchpoint->condition) { parseFree(watchpoint->condition); } + TableRemove(&debugger->pointOwner, watchpoint->id); } static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { @@ -220,7 +222,8 @@ static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { struct mDebuggerEntryInfo info = { .address = breakpoint->d.address, .type.bp.breakType = BREAKPOINT_HARDWARE, - .pointId = breakpoint->d.id + .pointId = breakpoint->d.id, + .target = TableLookup(&d->p->pointOwner, breakpoint->d.id) }; mDebuggerEnter(d->p, DEBUGGER_ENTER_BREAKPOINT, &info); } @@ -230,11 +233,11 @@ static void ARMDebuggerDeinit(struct mDebuggerPlatform* platform); static void ARMDebuggerEnter(struct mDebuggerPlatform* d, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info); -static ssize_t ARMDebuggerSetBreakpoint(struct mDebuggerPlatform*, const struct mBreakpoint*); +static ssize_t ARMDebuggerSetBreakpoint(struct mDebuggerPlatform*, struct mDebuggerModule* owner, const struct mBreakpoint*); static bool ARMDebuggerClearBreakpoint(struct mDebuggerPlatform*, ssize_t id); -static void ARMDebuggerListBreakpoints(struct mDebuggerPlatform*, struct mBreakpointList*); -static ssize_t ARMDebuggerSetWatchpoint(struct mDebuggerPlatform*, const struct mWatchpoint*); -static void ARMDebuggerListWatchpoints(struct mDebuggerPlatform*, struct mWatchpointList*); +static void ARMDebuggerListBreakpoints(struct mDebuggerPlatform*, struct mDebuggerModule* owner, struct mBreakpointList*); +static ssize_t ARMDebuggerSetWatchpoint(struct mDebuggerPlatform*, struct mDebuggerModule* owner, const struct mWatchpoint*); +static void ARMDebuggerListWatchpoints(struct mDebuggerPlatform*, struct mDebuggerModule* owner, struct mWatchpointList*); static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform*); static bool ARMDebuggerHasBreakpoints(struct mDebuggerPlatform*); static void ARMDebuggerTrace(struct mDebuggerPlatform*, char* out, size_t* length); @@ -291,12 +294,12 @@ void ARMDebuggerDeinit(struct mDebuggerPlatform* platform) { size_t i; for (i = 0; i < ARMDebugBreakpointListSize(&debugger->breakpoints); ++i) { - _destroyBreakpoint(ARMDebugBreakpointListGetPointer(&debugger->breakpoints, i)); + _destroyBreakpoint(debugger->d.p, ARMDebugBreakpointListGetPointer(&debugger->breakpoints, i)); } ARMDebugBreakpointListDeinit(&debugger->breakpoints); for (i = 0; i < mWatchpointListSize(&debugger->watchpoints); ++i) { - _destroyWatchpoint(mWatchpointListGetPointer(&debugger->watchpoints, i)); + _destroyWatchpoint(debugger->d.p, mWatchpointListGetPointer(&debugger->watchpoints, i)); } ARMDebugBreakpointListDeinit(&debugger->swBreakpoints); mWatchpointListDeinit(&debugger->watchpoints); @@ -323,12 +326,9 @@ static void ARMDebuggerEnter(struct mDebuggerPlatform* platform, enum mDebuggerE } } } - if (debugger->d.p->entered) { - debugger->d.p->entered(debugger->d.p, reason, info); - } } -ssize_t ARMDebuggerSetSoftwareBreakpoint(struct mDebuggerPlatform* d, uint32_t address, enum ExecutionMode mode) { +ssize_t ARMDebuggerSetSoftwareBreakpoint(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, uint32_t address, enum ExecutionMode mode) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; uint32_t opcode; if (!debugger->setSoftwareBreakpoint || !debugger->setSoftwareBreakpoint(debugger, address, mode, &opcode)) { @@ -345,11 +345,12 @@ ssize_t ARMDebuggerSetSoftwareBreakpoint(struct mDebuggerPlatform* d, uint32_t a breakpoint->d.type = BREAKPOINT_SOFTWARE; breakpoint->sw.opcode = opcode; breakpoint->sw.mode = mode; + TableInsert(&debugger->d.p->pointOwner, id, owner); return id; } -static ssize_t ARMDebuggerSetBreakpoint(struct mDebuggerPlatform* d, const struct mBreakpoint* info) { +static ssize_t ARMDebuggerSetBreakpoint(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, const struct mBreakpoint* info) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; struct ARMDebugBreakpoint* breakpoint = ARMDebugBreakpointListAppend(&debugger->breakpoints); ssize_t id = debugger->nextId; @@ -357,6 +358,7 @@ static ssize_t ARMDebuggerSetBreakpoint(struct mDebuggerPlatform* d, const struc breakpoint->d = *info; breakpoint->d.address &= ~1; // Clear Thumb bit since it's not part of a valid address breakpoint->d.id = id; + TableInsert(&debugger->d.p->pointOwner, id, owner); if (info->type == BREAKPOINT_SOFTWARE) { // TODO abort(); @@ -371,7 +373,7 @@ static bool ARMDebuggerClearBreakpoint(struct mDebuggerPlatform* d, ssize_t id) struct ARMDebugBreakpointList* breakpoints = &debugger->breakpoints; for (i = 0; i < ARMDebugBreakpointListSize(breakpoints); ++i) { if (ARMDebugBreakpointListGetPointer(breakpoints, i)->d.id == id) { - _destroyBreakpoint(ARMDebugBreakpointListGetPointer(breakpoints, i)); + _destroyBreakpoint(debugger->d.p, ARMDebugBreakpointListGetPointer(breakpoints, i)); ARMDebugBreakpointListShift(breakpoints, i, 1); return true; } @@ -391,7 +393,7 @@ static bool ARMDebuggerClearBreakpoint(struct mDebuggerPlatform* d, ssize_t id) struct mWatchpointList* watchpoints = &debugger->watchpoints; for (i = 0; i < mWatchpointListSize(watchpoints); ++i) { if (mWatchpointListGetPointer(watchpoints, i)->id == id) { - _destroyWatchpoint(mWatchpointListGetPointer(watchpoints, i)); + _destroyWatchpoint(debugger->d.p, mWatchpointListGetPointer(watchpoints, i)); mWatchpointListShift(watchpoints, i, 1); if (!mWatchpointListSize(&debugger->watchpoints)) { ARMDebuggerRemoveMemoryShim(debugger); @@ -402,7 +404,7 @@ static bool ARMDebuggerClearBreakpoint(struct mDebuggerPlatform* d, ssize_t id) return false; } -static void ARMDebuggerListBreakpoints(struct mDebuggerPlatform* d, struct mBreakpointList* list) { +static void ARMDebuggerListBreakpoints(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, struct mBreakpointList* list) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; mBreakpointListClear(list); size_t i, s; @@ -411,10 +413,20 @@ static void ARMDebuggerListBreakpoints(struct mDebuggerPlatform* d, struct mBrea struct ARMDebugBreakpoint* sw = NULL; if (i < ARMDebugBreakpointListSize(&debugger->breakpoints)) { hw = ARMDebugBreakpointListGetPointer(&debugger->breakpoints, i); + if (owner && TableLookup(&debugger->d.p->pointOwner, hw->d.id) != owner) { + hw = NULL; + } } if (s < ARMDebugBreakpointListSize(&debugger->swBreakpoints)) { sw = ARMDebugBreakpointListGetPointer(&debugger->swBreakpoints, s); + if (owner && TableLookup(&debugger->d.p->pointOwner, sw->d.id) != owner) { + sw = NULL; + } } + if (!hw && !sw) { + continue; + } + struct mBreakpoint* b = mBreakpointListAppend(list); if (hw && sw) { if (hw->d.id < sw->d.id) { @@ -430,8 +442,6 @@ static void ARMDebuggerListBreakpoints(struct mDebuggerPlatform* d, struct mBrea } else if (sw) { *b = sw->d; ++s; - } else { - abort(); // Should be unreachable } } } @@ -441,7 +451,7 @@ static bool ARMDebuggerHasBreakpoints(struct mDebuggerPlatform* d) { return ARMDebugBreakpointListSize(&debugger->breakpoints) || mWatchpointListSize(&debugger->watchpoints) || debugger->stackTraceMode != STACK_TRACE_DISABLED; } -static ssize_t ARMDebuggerSetWatchpoint(struct mDebuggerPlatform* d, const struct mWatchpoint* info) { +static ssize_t ARMDebuggerSetWatchpoint(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, const struct mWatchpoint* info) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; if (!mWatchpointListSize(&debugger->watchpoints)) { ARMDebuggerInstallMemoryShim(debugger); @@ -451,13 +461,25 @@ static ssize_t ARMDebuggerSetWatchpoint(struct mDebuggerPlatform* d, const struc ++debugger->nextId; *watchpoint = *info; watchpoint->id = id; + TableInsert(&debugger->d.p->pointOwner, id, owner); return id; } -static void ARMDebuggerListWatchpoints(struct mDebuggerPlatform* d, struct mWatchpointList* list) { +static void ARMDebuggerListWatchpoints(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, struct mWatchpointList* list) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; mWatchpointListClear(list); - mWatchpointListCopy(list, &debugger->watchpoints); + if (owner) { + size_t i; + for (i = 0; i < mWatchpointListSize(&debugger->watchpoints); ++i) { + struct mWatchpoint* point = mWatchpointListGetPointer(&debugger->watchpoints, i); + if (TableLookup(&debugger->d.p->pointOwner, point->id) != owner) { + continue; + } + memcpy(mWatchpointListAppend(list), point, sizeof(*point)); + } + } else { + mWatchpointListCopy(list, &debugger->watchpoints); + } } static void ARMDebuggerTrace(struct mDebuggerPlatform* d, char* out, size_t* length) { diff --git a/src/arm/debugger/memory-debugger.c b/src/arm/debugger/memory-debugger.c index 85c9995ae..d8fe42eda 100644 --- a/src/arm/debugger/memory-debugger.c +++ b/src/arm/debugger/memory-debugger.c @@ -12,7 +12,7 @@ #include -static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct mDebuggerEntryInfo* info, enum mWatchpointType type, uint32_t newValue, int width); +static void _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, enum mWatchpointType type, uint32_t newValue, int width); #define FIND_DEBUGGER(DEBUGGER, CPU) \ do { \ @@ -39,10 +39,7 @@ static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, st static RETURN DebuggerShim_ ## NAME TYPES { \ struct ARMDebugger* debugger; \ FIND_DEBUGGER(debugger, cpu); \ - struct mDebuggerEntryInfo info; \ - if (_checkWatchpoints(debugger, address, &info, WATCHPOINT_READ, 0, WIDTH)) { \ - mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); \ - } \ + _checkWatchpoints(debugger, address, WATCHPOINT_READ, 0, WIDTH); \ return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \ } @@ -50,10 +47,7 @@ static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, st static RETURN DebuggerShim_ ## NAME TYPES { \ struct ARMDebugger* debugger; \ FIND_DEBUGGER(debugger, cpu); \ - struct mDebuggerEntryInfo info; \ - if (_checkWatchpoints(debugger, address, &info, WATCHPOINT_WRITE, value, WIDTH)) { \ - mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); \ - } \ + _checkWatchpoints(debugger, address, WATCHPOINT_WRITE, value, WIDTH); \ return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \ } @@ -73,10 +67,7 @@ static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, st } \ unsigned i; \ for (i = 0; i < popcount; ++i) { \ - struct mDebuggerEntryInfo info; \ - if (_checkWatchpoints(debugger, base + 4 * i, &info, ACCESS_TYPE, 0, 4)) { \ - mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); \ - } \ + _checkWatchpoints(debugger, base + 4 * i, ACCESS_TYPE, 0, 4); \ } \ return debugger->originalMemory.NAME(cpu, address, mask, direction, cycleCounter); \ } @@ -91,7 +82,7 @@ CREATE_MULTIPLE_WATCHPOINT_SHIM(loadMultiple, WATCHPOINT_READ) CREATE_MULTIPLE_WATCHPOINT_SHIM(storeMultiple, WATCHPOINT_WRITE) CREATE_SHIM(setActiveRegion, void, (struct ARMCore* cpu, uint32_t address), address) -static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct mDebuggerEntryInfo* info, enum mWatchpointType type, uint32_t newValue, int width) { +static void _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, enum mWatchpointType type, uint32_t newValue, int width) { struct mWatchpoint* watchpoint; size_t i; uint32_t minAddress = address & ~(width - 1); @@ -124,16 +115,18 @@ static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, st if ((watchpoint->type & WATCHPOINT_CHANGE) && newValue == oldValue) { continue; } - info->type.wp.oldValue = oldValue; - info->type.wp.newValue = newValue; - info->address = address; - info->type.wp.watchType = watchpoint->type; - info->type.wp.accessType = type; - info->pointId = watchpoint->id; - return true; + + struct mDebuggerEntryInfo info; + info.type.wp.oldValue = oldValue; + info.type.wp.newValue = newValue; + info.address = address; + info.type.wp.watchType = watchpoint->type; + info.type.wp.accessType = type; + info.pointId = watchpoint->id; + info.target = TableLookup(&debugger->d.p->pointOwner, watchpoint->id); + mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); } } - return false; } void ARMDebuggerInstallMemoryShim(struct ARMDebugger* debugger) { diff --git a/src/arm/isa-arm.c b/src/arm/isa-arm.c index b3bb3c9da..7f158dc6e 100644 --- a/src/arm/isa-arm.c +++ b/src/arm/isa-arm.c @@ -663,7 +663,9 @@ DEFINE_INSTRUCTION_ARM(MRC, ARM_STUB) // Begin miscellaneous definitions -DEFINE_INSTRUCTION_ARM(BKPT, cpu->irqh.bkpt32(cpu, ((opcode >> 4) & 0xFFF0) | (opcode & 0xF))); // Not strictly in ARMv4T, but here for convenience +DEFINE_INSTRUCTION_ARM(BKPT, + cpu->irqh.bkpt32(cpu, ((opcode >> 4) & 0xFFF0) | (opcode & 0xF)); + currentCycles = 0;); // Not strictly in ARMv4T, but here for convenience DEFINE_INSTRUCTION_ARM(ILL, ARM_ILL) // Illegal opcode DEFINE_INSTRUCTION_ARM(MSR, diff --git a/src/arm/isa-thumb.c b/src/arm/isa-thumb.c index 6e3c30e11..0bc8fcb12 100644 --- a/src/arm/isa-thumb.c +++ b/src/arm/isa-thumb.c @@ -381,7 +381,9 @@ DEFINE_LOAD_STORE_MULTIPLE_THUMB(PUSHR, cpu->gprs[ARM_SP] = address) DEFINE_INSTRUCTION_THUMB(ILL, ARM_ILL) -DEFINE_INSTRUCTION_THUMB(BKPT, cpu->irqh.bkpt16(cpu, opcode & 0xFF);) +DEFINE_INSTRUCTION_THUMB(BKPT, + cpu->irqh.bkpt16(cpu, opcode & 0xFF); + currentCycles = 0;) // Not strictly in ARMv4T, but here for convenience DEFINE_INSTRUCTION_THUMB(B, int16_t immediate = (opcode & 0x07FF) << 5; cpu->gprs[ARM_PC] += (((int32_t) immediate) >> 4); @@ -401,11 +403,7 @@ DEFINE_INSTRUCTION_THUMB(BL2, DEFINE_INSTRUCTION_THUMB(BX, int rm = (opcode >> 3) & 0xF; _ARMSetMode(cpu, cpu->gprs[rm] & 0x00000001); - int misalign = 0; - if (rm == ARM_PC) { - misalign = cpu->gprs[rm] & 0x00000002; - } - cpu->gprs[ARM_PC] = (cpu->gprs[rm] & 0xFFFFFFFE) - misalign; + cpu->gprs[ARM_PC] = cpu->gprs[rm] & 0xFFFFFFFE; if (cpu->executionMode == MODE_THUMB) { currentCycles += ThumbWritePC(cpu); } else { diff --git a/src/core/cache-set.c b/src/core/cache-set.c index a638c786e..04d42a233 100644 --- a/src/core/cache-set.c +++ b/src/core/cache-set.c @@ -34,12 +34,15 @@ void mCacheSetDeinit(struct mCacheSet* cache) { for (i = 0; i < mMapCacheSetSize(&cache->maps); ++i) { mMapCacheDeinit(mMapCacheSetGetPointer(&cache->maps, i)); } + mMapCacheSetDeinit(&cache->maps); for (i = 0; i < mBitmapCacheSetSize(&cache->bitmaps); ++i) { mBitmapCacheDeinit(mBitmapCacheSetGetPointer(&cache->bitmaps, i)); } + mBitmapCacheSetDeinit(&cache->bitmaps); for (i = 0; i < mTileCacheSetSize(&cache->tiles); ++i) { mTileCacheDeinit(mTileCacheSetGetPointer(&cache->tiles, i)); } + mTileCacheSetDeinit(&cache->tiles); } void mCacheSetAssignVRAM(struct mCacheSet* cache, void* vram) { diff --git a/src/core/cheats.c b/src/core/cheats.c index 688b32ebe..60f73abcc 100644 --- a/src/core/cheats.c +++ b/src/core/cheats.c @@ -489,8 +489,10 @@ bool mCheatParseEZFChtFile(struct mCheatDevice* device, struct VFile* vf) { return false; } char* name = gbkToUtf8(&cheat[1], end - cheat - 1); - strncpy(cheatName, name, sizeof(cheatName) - 1); - free(name); + if (name) { + strncpy(cheatName, name, sizeof(cheatName) - 1); + free(name); + } cheatNameLength = strlen(cheatName); continue; } @@ -501,7 +503,10 @@ bool mCheatParseEZFChtFile(struct mCheatDevice* device, struct VFile* vf) { } if (strncmp(cheat, "ON", eq - cheat) != 0) { char* subname = gbkToUtf8(cheat, eq - cheat); - snprintf(&cheatName[cheatNameLength], sizeof(cheatName) - cheatNameLength - 1, ": %s", subname); + if (subname) { + snprintf(&cheatName[cheatNameLength], sizeof(cheatName) - cheatNameLength - 1, ": %s", subname); + free(subname); + } } set = device->createSet(device, cheatName); set->enabled = false; diff --git a/src/core/config.c b/src/core/config.c index 6bfcb8e8b..78a527332 100644 --- a/src/core/config.c +++ b/src/core/config.c @@ -79,7 +79,7 @@ static const char* _lookupValue(const struct mCoreConfig* config, const char* ke static bool _lookupCharValue(const struct mCoreConfig* config, const char* key, char** out) { const char* value = _lookupValue(config, key); - if (!value) { + if (!value || !value[0]) { return false; } if (*out) { @@ -169,14 +169,14 @@ void mCoreConfigDeinit(struct mCoreConfig* config) { #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 bool mCoreConfigLoad(struct mCoreConfig* config) { - char path[PATH_MAX]; + char path[PATH_MAX + 1]; mCoreConfigDirectory(path, PATH_MAX); strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path)); return mCoreConfigLoadPath(config, path); } bool mCoreConfigSave(const struct mCoreConfig* config) { - char path[PATH_MAX]; + char path[PATH_MAX + 1]; mCoreConfigDirectory(path, PATH_MAX); strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path)); return mCoreConfigSavePath(config, path); @@ -279,8 +279,7 @@ void mCoreConfigDirectory(char* out, size_t outLength) { void mCoreConfigPortablePath(char* out, size_t outLength) { #ifdef _WIN32 wchar_t wpath[MAX_PATH]; - HMODULE hModule = GetModuleHandleW(NULL); - GetModuleFileNameW(hModule, wpath, MAX_PATH); + GetModuleFileNameW(NULL, wpath, MAX_PATH); PathRemoveFileSpecW(wpath); if (PATH_SEP[0] != '\\') { WCHAR* pathSep; @@ -304,7 +303,7 @@ void mCoreConfigPortablePath(char* out, size_t outLength) { CFRelease(suburl); } #endif - strncat(out, PATH_SEP "portable.ini", outLength - strlen(out)); + strncat(out, PATH_SEP "portable.ini", outLength - strlen(out) - 1); #endif } @@ -407,6 +406,7 @@ void mCoreConfigMap(const struct mCoreConfig* config, struct mCoreOptions* opts) _lookupIntValue(config, "frameskip", &opts->frameskip); _lookupIntValue(config, "volume", &opts->volume); _lookupIntValue(config, "rewindBufferCapacity", &opts->rewindBufferCapacity); + _lookupIntValue(config, "rewindBufferInterval", &opts->rewindBufferInterval); _lookupFloatValue(config, "fpsTarget", &opts->fpsTarget); unsigned audioBuffers; if (_lookupUIntValue(config, "audioBuffers", &audioBuffers)) { @@ -449,6 +449,7 @@ void mCoreConfigLoadDefaults(struct mCoreConfig* config, const struct mCoreOptio ConfigurationSetIntValue(&config->defaultsTable, 0, "frameskip", opts->frameskip); ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindEnable", opts->rewindEnable); ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferCapacity", opts->rewindBufferCapacity); + ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferInterval", opts->rewindBufferInterval); ConfigurationSetFloatValue(&config->defaultsTable, 0, "fpsTarget", opts->fpsTarget); ConfigurationSetUIntValue(&config->defaultsTable, 0, "audioBuffers", opts->audioBuffers); ConfigurationSetUIntValue(&config->defaultsTable, 0, "sampleRate", opts->sampleRate); diff --git a/src/core/core.c b/src/core/core.c index c4c3a3ce8..61f29d71f 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -87,7 +87,7 @@ struct mCore* mCoreCreate(enum mPlatform platform) { } #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 -#include +#include #ifdef PSP2 #include @@ -361,11 +361,11 @@ bool mCoreTakeScreenshotVF(struct mCore* core, struct VFile* vf) { size_t stride; const void* pixels = 0; unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); core->getPixels(core, &pixels, &stride); png_structp png = PNGWriteOpen(vf); - png_infop info = PNGWriteHeader(png, width, height); - bool success = PNGWritePixels(png, width, height, stride, pixels); + png_infop info = PNGWriteHeader(png, width, height, mCOLOR_NATIVE); + bool success = PNGWritePixels(png, width, height, stride, pixels, mCOLOR_NATIVE); PNGWriteClose(png, info); return success; #else diff --git a/src/core/directories.c b/src/core/directories.c index 63c66e564..c35773f1e 100644 --- a/src/core/directories.c +++ b/src/core/directories.c @@ -116,10 +116,15 @@ struct VFile* mDirectorySetOpenSuffix(struct mDirectorySet* dirs, struct VDir* d } void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptions* opts) { + char abspath[PATH_MAX + 1]; + char configDir[PATH_MAX + 1]; + mCoreConfigDirectory(configDir, sizeof(configDir)); + if (opts->savegamePath) { - struct VDir* dir = VDirOpen(opts->savegamePath); - if (!dir && VDirCreate(opts->savegamePath)) { - dir = VDirOpen(opts->savegamePath); + makeAbsolute(opts->savegamePath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->save && dirs->save != dirs->base) { @@ -130,9 +135,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio } if (opts->savestatePath) { - struct VDir* dir = VDirOpen(opts->savestatePath); - if (!dir && VDirCreate(opts->savestatePath)) { - dir = VDirOpen(opts->savestatePath); + makeAbsolute(opts->savestatePath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->state && dirs->state != dirs->base) { @@ -143,9 +149,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio } if (opts->screenshotPath) { - struct VDir* dir = VDirOpen(opts->screenshotPath); - if (!dir && VDirCreate(opts->screenshotPath)) { - dir = VDirOpen(opts->screenshotPath); + makeAbsolute(opts->screenshotPath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->screenshot && dirs->screenshot != dirs->base) { @@ -156,9 +163,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio } if (opts->patchPath) { - struct VDir* dir = VDirOpen(opts->patchPath); - if (!dir && VDirCreate(opts->patchPath)) { - dir = VDirOpen(opts->patchPath); + makeAbsolute(opts->patchPath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->patch && dirs->patch != dirs->base) { @@ -169,9 +177,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio } if (opts->cheatsPath) { - struct VDir* dir = VDirOpen(opts->cheatsPath); - if (!dir && VDirCreate(opts->cheatsPath)) { - dir = VDirOpen(opts->cheatsPath); + makeAbsolute(opts->cheatsPath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->cheats && dirs->cheats != dirs->base) { diff --git a/src/core/flags.h.in b/src/core/flags.h.in index 71ea562ef..ec9db8de5 100644 --- a/src/core/flags.h.in +++ b/src/core/flags.h.in @@ -79,6 +79,10 @@ #cmakedefine USE_GDB_STUB #endif +#ifndef USE_JSON_C +#cmakedefine USE_JSON_C +#endif + #ifndef USE_LIBAV #cmakedefine USE_LIBAV #endif diff --git a/src/core/input.c b/src/core/input.c index 787e31f86..1c06d48a9 100644 --- a/src/core/input.c +++ b/src/core/input.c @@ -9,8 +9,6 @@ #include #include -#include - #define SECTION_NAME_MAX 128 #define KEY_NAME_MAX 32 #define KEY_VALUE_MAX 16 diff --git a/src/core/log.c b/src/core/log.c index 2d90fe05e..5fe2fe9ee 100644 --- a/src/core/log.c +++ b/src/core/log.c @@ -50,7 +50,7 @@ const char* mLogCategoryName(int category) { } const char* mLogCategoryId(int category) { - if (category < MAX_CATEGORY) { + if (category >= 0 && category < MAX_CATEGORY) { return _categoryIds[category]; } return NULL; @@ -88,6 +88,7 @@ void mLogExplicit(struct mLogger* context, int category, enum mLogLevel level, c if (!context->filter || mLogFilterTest(context->filter, category, level)) { context->log(context, category, level, format, args); } + va_end(args); } void mLogFilterInit(struct mLogFilter* filter) { diff --git a/src/core/rewind.c b/src/core/rewind.c index 95f47ece4..21120ec21 100644 --- a/src/core/rewind.c +++ b/src/core/rewind.c @@ -30,6 +30,7 @@ void mCoreRewindContextInit(struct mCoreRewindContext* context, size_t entries, context->previousState = VFileMemChunk(0, 0); context->currentState = VFileMemChunk(0, 0); context->size = 0; + context->rewindFrameCounter = 0; #ifndef DISABLE_THREADING context->onThread = onThread; context->ready = false; @@ -175,7 +176,7 @@ THREAD_ENTRY _rewindThread(void* context) { rewindContext->ready = false; } MutexUnlock(&rewindContext->mutex); - return 0; + THREAD_EXIT(0); } #endif diff --git a/src/core/scripting.c b/src/core/scripting.c index a67297598..ff7f7f255 100644 --- a/src/core/scripting.c +++ b/src/core/scripting.c @@ -7,6 +7,10 @@ #include #include +#ifdef M_CORE_GBA +#include +#endif +#include #include #include #include @@ -155,10 +159,49 @@ struct mScriptMemoryDomain { struct mCoreMemoryBlock block; }; +#ifdef USE_DEBUGGERS +struct mScriptBreakpointName { + uint32_t address; + uint32_t maxAddress; + int segment : 9; + int type : 1; + int subtype : 3; +}; + +struct mScriptBreakpoint { + ssize_t id; + struct mScriptBreakpointName name; + struct Table callbacks; +}; + +struct mScriptCoreAdapter; +struct mScriptDebugger { + struct mDebuggerModule d; + struct mScriptCoreAdapter* p; + struct Table breakpoints; + struct Table cbidMap; + struct Table bpidMap; + int64_t nextBreakpoint; +}; +#endif + struct mScriptCoreAdapter { struct mCore* core; struct mScriptContext* context; struct mScriptValue memory; +#ifdef USE_DEBUGGERS + struct mScriptDebugger debugger; +#endif + struct mRumble rumble; + struct mRumble* oldRumble; + struct mRotationSource rotation; + struct mScriptValue* rotationCbTable; + struct mRotationSource* oldRotation; +#ifdef M_CORE_GBA + struct GBALuminanceSource luminance; + struct mScriptValue* luminanceCb; + struct GBALuminanceSource* oldLuminance; +#endif }; struct mScriptConsole { @@ -399,6 +442,7 @@ static int _mScriptCoreLoadStateFile(struct mCore* core, const char* path, int f vf->close(vf); return ok; } + static void _mScriptCoreTakeScreenshot(struct mCore* core, const char* filename) { if (filename) { struct VFile* vf = VFileOpen(filename, O_WRONLY | O_CREAT | O_TRUNC); @@ -412,6 +456,29 @@ static void _mScriptCoreTakeScreenshot(struct mCore* core, const char* filename) } } +static struct mScriptValue* _mScriptCoreTakeScreenshotToImage(struct mCore* core) { + size_t stride; + const void* pixels = 0; + unsigned width, height; + core->currentVideoSize(core, &width, &height); + core->getPixels(core, &pixels, &stride); + if (!pixels) { + return NULL; + } +#ifndef COLOR_16_BIT + struct mImage* image = mImageCreateFromConstBuffer(width, height, stride, mCOLOR_XBGR8, pixels); +#elif COLOR_5_6_5 + struct mImage* image = mImageCreateFromConstBuffer(width, height, stride, mCOLOR_RGB565, pixels); +#else + struct mImage* image = mImageCreateFromConstBuffer(width, height, stride, mCOLOR_BGR5, pixels); +#endif + + struct mScriptValue* result = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mImage)); + result->value.opaque = image; + result->flags = mSCRIPT_VALUE_FLAG_DEINIT; + return result; +} + // Loading functions mSCRIPT_DECLARE_STRUCT_METHOD(mCore, BOOL, loadFile, mCoreLoadFile, 1, CHARP, path); mSCRIPT_DECLARE_STRUCT_METHOD(mCore, BOOL, autoloadSave, mCoreAutoloadSave, 0); @@ -464,6 +531,7 @@ mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mCore, BOOL, loadStateFile, _mScript // Miscellaneous functions mSCRIPT_DECLARE_STRUCT_VOID_METHOD_WITH_DEFAULTS(mCore, screenshot, _mScriptCoreTakeScreenshot, 1, CHARP, filename); +mSCRIPT_DECLARE_STRUCT_METHOD(mCore, W(mImage), screenshotToImage, _mScriptCoreTakeScreenshotToImage, 0); mSCRIPT_DEFINE_STRUCT(mCore) mSCRIPT_DEFINE_CLASS_DOCSTRING( @@ -549,8 +617,10 @@ mSCRIPT_DEFINE_STRUCT(mCore) mSCRIPT_DEFINE_DOCSTRING("Load state from the given path. See C.SAVESTATE for possible values for `flags`") mSCRIPT_DEFINE_STRUCT_METHOD(mCore, loadStateFile) - mSCRIPT_DEFINE_DOCSTRING("Save a screenshot") + mSCRIPT_DEFINE_DOCSTRING("Save a screenshot to a file") mSCRIPT_DEFINE_STRUCT_METHOD(mCore, screenshot) + mSCRIPT_DEFINE_DOCSTRING("Get a screenshot in an struct::mImage") + mSCRIPT_DEFINE_STRUCT_METHOD(mCore, screenshotToImage) mSCRIPT_DEFINE_END; mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, checksum) @@ -631,9 +701,239 @@ static void _rebuildMemoryMap(struct mScriptContext* context, struct mScriptCore } } +#ifdef USE_DEBUGGERS +static void _freeBreakpoint(void* bp) { + struct mScriptBreakpoint* point = bp; + HashTableDeinit(&point->callbacks); + free(bp); +} + +static struct mScriptBreakpoint* _ensureBreakpoint(struct mScriptDebugger* debugger, struct mBreakpoint* breakpoint) { + struct mDebuggerModule* module = &debugger->d; + struct mScriptBreakpointName name = { + .address = breakpoint->address, + .maxAddress = 0, + .segment = breakpoint->segment, + .type = 0, + .subtype = breakpoint->type + }; + struct mScriptBreakpoint* point = HashTableLookupBinary(&debugger->breakpoints, &name, sizeof(name)); + if (point) { + return point; + } + point = calloc(1, sizeof(*point)); + point->id = module->p->platform->setBreakpoint(module->p->platform, module, breakpoint); + point->name = name; + HashTableInit(&point->callbacks, 0, (void (*)(void*)) mScriptValueDeref); + HashTableInsertBinary(&debugger->bpidMap, &point->id, sizeof(point->id), point); + HashTableInsertBinary(&debugger->breakpoints, &name, sizeof(name), point); + return point; +} + +static struct mScriptBreakpoint* _ensureWatchpoint(struct mScriptDebugger* debugger, struct mWatchpoint* watchpoint) { + struct mDebuggerModule* module = &debugger->d; + struct mScriptBreakpointName name = { + .address = watchpoint->minAddress, + .maxAddress = watchpoint->maxAddress, + .segment = watchpoint->segment, + .type = 1, + .subtype = watchpoint->type + }; + struct mScriptBreakpoint* point = HashTableLookupBinary(&debugger->breakpoints, &name, sizeof(name)); + if (point) { + return point; + } + point = calloc(1, sizeof(*point)); + point->id = module->p->platform->setWatchpoint(module->p->platform, module, watchpoint); + point->name = name; + HashTableInit(&point->callbacks, 0, (void (*)(void*)) mScriptValueDeref); + HashTableInsertBinary(&debugger->bpidMap, &point->id, sizeof(point->id), point); + HashTableInsertBinary(&debugger->breakpoints, &name, sizeof(name), point); + return point; +} + +static int64_t _addCallbackToBreakpoint(struct mScriptDebugger* debugger, struct mScriptBreakpoint* point, struct mScriptValue* callback) { + int64_t cbid = debugger->nextBreakpoint; + ++debugger->nextBreakpoint; + HashTableInsertBinary(&debugger->cbidMap, &cbid, sizeof(cbid), point); + mScriptValueRef(callback); + HashTableInsertBinary(&point->callbacks, &cbid, sizeof(cbid), callback); + return cbid; +} + +static void _runCallbacks(struct mScriptBreakpoint* point) { + struct TableIterator iter; + if (!HashTableIteratorStart(&point->callbacks, &iter)) { + return; + } + do { + struct mScriptValue* fn = HashTableIteratorGetValue(&point->callbacks, &iter); + struct mScriptFrame frame; + mScriptFrameInit(&frame); + mScriptInvoke(fn, &frame); + mScriptFrameDeinit(&frame); + } while (HashTableIteratorNext(&point->callbacks, &iter)); +} + +static void _scriptDebuggerInit(struct mDebuggerModule* debugger) { + struct mScriptDebugger* scriptDebugger = (struct mScriptDebugger*) debugger; + debugger->isPaused = false; + debugger->needsCallback = false; + + HashTableInit(&scriptDebugger->breakpoints, 0, _freeBreakpoint); + HashTableInit(&scriptDebugger->cbidMap, 0, NULL); + HashTableInit(&scriptDebugger->bpidMap, 0, NULL); +} + +static void _scriptDebuggerDeinit(struct mDebuggerModule* debugger) { + struct mScriptDebugger* scriptDebugger = (struct mScriptDebugger*) debugger; + HashTableDeinit(&scriptDebugger->cbidMap); + HashTableDeinit(&scriptDebugger->bpidMap); + HashTableDeinit(&scriptDebugger->breakpoints); +} + +static void _scriptDebuggerPaused(struct mDebuggerModule* debugger, int32_t timeoutMs) { + UNUSED(debugger); + UNUSED(timeoutMs); +} + +static void _scriptDebuggerUpdate(struct mDebuggerModule* debugger) { + UNUSED(debugger); +} + +static void _scriptDebuggerEntered(struct mDebuggerModule* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) { + struct mScriptDebugger* scriptDebugger = (struct mScriptDebugger*) debugger; + struct mScriptBreakpoint* point; + switch (reason) { + case DEBUGGER_ENTER_BREAKPOINT: + case DEBUGGER_ENTER_WATCHPOINT: + point = HashTableLookupBinary(&scriptDebugger->bpidMap, &info->pointId, sizeof(info->pointId)); + break; + default: + return; + } + _runCallbacks(point); + debugger->isPaused = false; +} + +static void _scriptDebuggerCustom(struct mDebuggerModule* debugger) { + UNUSED(debugger); +} + +static void _scriptDebuggerInterrupt(struct mDebuggerModule* debugger) { + UNUSED(debugger); +} + +static bool _setupDebugger(struct mScriptCoreAdapter* adapter) { + if (!adapter->core->debugger) { + return false; + } + + if (adapter->debugger.d.p) { + return true; + } + adapter->debugger.p = adapter; + adapter->debugger.d.type = DEBUGGER_CUSTOM; + adapter->debugger.d.init = _scriptDebuggerInit; + adapter->debugger.d.deinit = _scriptDebuggerDeinit; + adapter->debugger.d.paused = _scriptDebuggerPaused; + adapter->debugger.d.update = _scriptDebuggerUpdate; + adapter->debugger.d.entered = _scriptDebuggerEntered; + adapter->debugger.d.custom = _scriptDebuggerCustom; + adapter->debugger.d.interrupt = _scriptDebuggerInterrupt; + adapter->debugger.d.isPaused = false; + adapter->debugger.d.needsCallback = false; + adapter->debugger.nextBreakpoint = 1; + mDebuggerAttachModule(adapter->core->debugger, &adapter->debugger.d); + return true; +} + +static int64_t _mScriptCoreAdapterSetBreakpoint(struct mScriptCoreAdapter* adapter, struct mScriptValue* callback, uint32_t address, int32_t segment) { + if (!_setupDebugger(adapter)) { + return -1; + } + struct mBreakpoint breakpoint = { + .address = address, + .segment = segment, + .type = BREAKPOINT_HARDWARE + }; + + struct mDebuggerModule* module = &adapter->debugger.d; + if (!module->p->platform->setBreakpoint) { + return -1; + } + struct mScriptBreakpoint* point = _ensureBreakpoint(&adapter->debugger, &breakpoint); + return _addCallbackToBreakpoint(&adapter->debugger, point, callback); +} + +static int64_t _mScriptCoreAdapterSetWatchpoint(struct mScriptCoreAdapter* adapter, struct mScriptValue* callback, uint32_t address, int type, int32_t segment) { + if (!_setupDebugger(adapter)) { + return -1; + } + + struct mWatchpoint watchpoint = { + .minAddress = address, + .maxAddress = address + 1, + .segment = segment, + .type = type, + }; + struct mDebuggerModule* module = &adapter->debugger.d; + if (!module->p->platform->setWatchpoint) { + return -1; + } + struct mScriptBreakpoint* point = _ensureWatchpoint(&adapter->debugger, &watchpoint); + return _addCallbackToBreakpoint(&adapter->debugger, point, callback); +} + +static int64_t _mScriptCoreAdapterSetRangeWatchpoint(struct mScriptCoreAdapter* adapter, struct mScriptValue* callback, uint32_t minAddress, uint32_t maxAddress, int type, int32_t segment) { + if (!_setupDebugger(adapter)) { + return -1; + } + + struct mWatchpoint watchpoint = { + .minAddress = minAddress, + .maxAddress = maxAddress, + .segment = segment, + .type = type, + }; + struct mDebuggerModule* module = &adapter->debugger.d; + if (!module->p->platform->setWatchpoint) { + return -1; + } + struct mScriptBreakpoint* point = _ensureWatchpoint(&adapter->debugger, &watchpoint); + return _addCallbackToBreakpoint(&adapter->debugger, point, callback); +} + +static bool _mScriptCoreAdapterClearBreakpoint(struct mScriptCoreAdapter* adapter, int64_t cbid) { + if (!_setupDebugger(adapter)) { + return false; + } + struct mScriptBreakpoint* point = HashTableLookupBinary(&adapter->debugger.cbidMap, &cbid, sizeof(cbid)); + if (!point) { + return false; + } + HashTableRemoveBinary(&adapter->debugger.cbidMap, &cbid, sizeof(cbid)); + HashTableRemoveBinary(&point->callbacks, &cbid, sizeof(cbid)); + + if (!HashTableSize(&point->callbacks)) { + struct mDebuggerModule* module = &adapter->debugger.d; + module->p->platform->clearBreakpoint(module->p->platform, point->id); + + struct mScriptBreakpointName name = point->name; + HashTableRemoveBinary(&adapter->debugger.breakpoints, &name, sizeof(name)); + } + return true; +} +#endif + static void _mScriptCoreAdapterDeinit(struct mScriptCoreAdapter* adapter) { _clearMemoryMap(adapter->context, adapter, false); adapter->memory.type->free(&adapter->memory); +#ifdef USE_DEBUGGERS + if (adapter->core->debugger) { + mDebuggerDetachModule(adapter->core->debugger, &adapter->debugger.d); + } +#endif } static struct mScriptValue* _mScriptCoreAdapterGet(struct mScriptCoreAdapter* adapter, const char* name) { @@ -654,10 +954,61 @@ static void _mScriptCoreAdapterReset(struct mScriptCoreAdapter* adapter) { mScriptContextTriggerCallback(adapter->context, "reset", NULL); } +static struct mScriptValue* _mScriptCoreAdapterSetRotationCbTable(struct mScriptCoreAdapter* adapter, struct mScriptValue* cbTable) { + if (cbTable) { + mScriptValueRef(cbTable); + } + struct mScriptValue* oldTable = adapter->rotationCbTable; + adapter->rotationCbTable = cbTable; + return oldTable; +} + +static void _mScriptCoreAdapterSetLuminanceCb(struct mScriptCoreAdapter* adapter, struct mScriptValue* callback) { + if (callback) { + if (callback->type->base != mSCRIPT_TYPE_FUNCTION) { + return; + } + mScriptValueRef(callback); + } + if (adapter->luminanceCb) { + mScriptValueDeref(adapter->luminanceCb); + } + adapter->luminanceCb = callback; +} + mSCRIPT_DECLARE_STRUCT(mScriptCoreAdapter); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCoreAdapter, W(mCore), _get, _mScriptCoreAdapterGet, 1, CHARP, name); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCoreAdapter, _deinit, _mScriptCoreAdapterDeinit, 0); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCoreAdapter, reset, _mScriptCoreAdapterReset, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCoreAdapter, WTABLE, setRotationCallbacks, _mScriptCoreAdapterSetRotationCbTable, 1, WTABLE, cbTable); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCoreAdapter, setSolarSensorCallback, _mScriptCoreAdapterSetLuminanceCb, 1, WRAPPER, callback); +#ifdef USE_DEBUGGERS +mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptCoreAdapter, S64, setBreakpoint, _mScriptCoreAdapterSetBreakpoint, 3, WRAPPER, callback, U32, address, S32, segment); +mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptCoreAdapter, S64, setWatchpoint, _mScriptCoreAdapterSetWatchpoint, 4, WRAPPER, callback, U32, address, S32, type, S32, segment); +mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptCoreAdapter, S64, setRangeWatchpoint, _mScriptCoreAdapterSetRangeWatchpoint, 5, WRAPPER, callback, U32, minAddress, U32, maxAddress, S32, type, S32, segment); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCoreAdapter, BOOL, clearBreakpoint, _mScriptCoreAdapterClearBreakpoint, 1, S64, cbid); +#endif + +mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptCoreAdapter, setBreakpoint) + mSCRIPT_NO_DEFAULT, + mSCRIPT_NO_DEFAULT, + mSCRIPT_S32(-1) +mSCRIPT_DEFINE_DEFAULTS_END; + +mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptCoreAdapter, setWatchpoint) + mSCRIPT_NO_DEFAULT, + mSCRIPT_NO_DEFAULT, + mSCRIPT_NO_DEFAULT, + mSCRIPT_S32(-1) +mSCRIPT_DEFINE_DEFAULTS_END; + +mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptCoreAdapter, setRangeWatchpoint) + mSCRIPT_NO_DEFAULT, + mSCRIPT_NO_DEFAULT, + mSCRIPT_NO_DEFAULT, + mSCRIPT_NO_DEFAULT, + mSCRIPT_S32(-1) +mSCRIPT_DEFINE_DEFAULTS_END; mSCRIPT_DEFINE_STRUCT(mScriptCoreAdapter) mSCRIPT_DEFINE_CLASS_DOCSTRING( @@ -672,10 +1023,157 @@ mSCRIPT_DEFINE_STRUCT(mScriptCoreAdapter) mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(mScriptCoreAdapter) mSCRIPT_DEFINE_DOCSTRING("Reset the emulation. As opposed to struct::mCore.reset, this version calls the **reset** callback") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, reset) + mSCRIPT_DEFINE_DOCSTRING( + "Sets the table of functions to be called when the game requests rotation data, for either a gyroscope or accelerometer. " + "The following functions are supported, and if any isn't set then then default implementation for that function is called instead:\n\n" + "- `sample`: Update (\"sample\") the values returned by the other functions. The values returned shouldn't change until the next time this is called\n" + "- `readTiltX`: Return a value between -1.0 and +1.0 representing the X (left/right axis) direction of the linear acceleration vector, as for an accelerometer.\n" + "- `readTiltY`: Return a value between -1.0 and +1.0 representing the Y (up/down axis) direction of the linear acceleration vector, as for an accelerometer.\n" + "- `readGyroZ`: Return a value between -1.0 and +1.0 representing the roll (front/back axis) value of the rotational acceleration vector, as for an gyroscope.\n\n" + "Optionally, you can also set a value `context` on the table that will be passed to the callbacks. This table is copied by value, so changes made to the table " + "after being passed to this function will not be seen unless the function is called again. Therefore, the recommended usage of the `context` field is as an index " + "or key into a separate table. Use cases may vary. If this function is called more than once, the previous value of the table is returned." + ) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, setRotationCallbacks) + mSCRIPT_DEFINE_DOCSTRING( + "Set a callback that will be used to get the current value of the solar sensors between 0 (darkest) and 255 (brightest). " + "Note that the full range of values is not used by games, and the exact range depends on the calibration done by the game itself." + ) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, setSolarSensorCallback) +#ifdef USE_DEBUGGERS + mSCRIPT_DEFINE_DOCSTRING("Set a breakpoint at a given address") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, setBreakpoint) + mSCRIPT_DEFINE_DOCSTRING("Clear a breakpoint or watchpoint for a given id returned by a previous call") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, clearBreakpoint) + mSCRIPT_DEFINE_DOCSTRING("Set a watchpoint at a given address of a given type") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, setWatchpoint) + mSCRIPT_DEFINE_DOCSTRING( + "Set a watchpoint in a given range of a given type. Note that the range is exclusive on the end, " + "as though you've added the size, i.e. a 4-byte watch would specify the maximum as the minimum address + 4" + ) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, setRangeWatchpoint) +#endif mSCRIPT_DEFINE_STRUCT_CAST_TO_MEMBER(mScriptCoreAdapter, S(mCore), _core) mSCRIPT_DEFINE_STRUCT_CAST_TO_MEMBER(mScriptCoreAdapter, CS(mCore), _core) mSCRIPT_DEFINE_END; +static void _setRumble(struct mRumble* rumble, int enable) { + struct mScriptCoreAdapter* adapter = containerof(rumble, struct mScriptCoreAdapter, rumble); + + if (adapter->oldRumble) { + adapter->oldRumble->setRumble(adapter->oldRumble, enable); + } + + struct mScriptList args; + mScriptListInit(&args, 1); + *mScriptListAppend(&args) = mSCRIPT_MAKE_BOOL(!!enable); + mScriptContextTriggerCallback(adapter->context, "rumble", &args); + mScriptListDeinit(&args); +} + +static bool _callRotationCb(struct mScriptCoreAdapter* adapter, const char* cbName, struct mScriptValue* out) { + if (!adapter->rotationCbTable) { + return false; + } + struct mScriptValue* cb = mScriptTableLookup(adapter->rotationCbTable, &mSCRIPT_MAKE_CHARP(cbName)); + if (!cb || cb->type->base != mSCRIPT_TYPE_FUNCTION) { + return false; + } + struct mScriptFrame frame; + struct mScriptValue* context = mScriptTableLookup(adapter->rotationCbTable, &mSCRIPT_MAKE_CHARP("context")); + mScriptFrameInit(&frame); + if (context) { + mScriptValueWrap(context, mScriptListAppend(&frame.arguments)); + } + bool ok = mScriptInvoke(cb, &frame); + if (ok && out && mScriptListSize(&frame.returnValues) == 1) { + if (!mScriptCast(mSCRIPT_TYPE_MS_F32, mScriptListGetPointer(&frame.returnValues, 0), out)) { + ok = false; + } + } + mScriptFrameDeinit(&frame); + return ok; +} + +static void _rotationSample(struct mRotationSource* rotation) { + struct mScriptCoreAdapter* adapter = containerof(rotation, struct mScriptCoreAdapter, rotation); + + _callRotationCb(adapter, "sample", NULL); + + if (adapter->oldRotation && adapter->oldRotation->sample) { + adapter->oldRotation->sample(adapter->oldRotation); + } +} + +static int32_t _rotationReadTiltX(struct mRotationSource* rotation) { + struct mScriptCoreAdapter* adapter = containerof(rotation, struct mScriptCoreAdapter, rotation); + + struct mScriptValue out; + if (_callRotationCb(adapter, "readTiltX", &out)) { + return out.value.f32 * INT32_MAX; + } + + if (adapter->oldRotation && adapter->oldRotation->readTiltX) { + return adapter->oldRotation->readTiltX(adapter->oldRotation); + } + return 0; +} + +static int32_t _rotationReadTiltY(struct mRotationSource* rotation) { + struct mScriptCoreAdapter* adapter = containerof(rotation, struct mScriptCoreAdapter, rotation); + + struct mScriptValue out; + if (_callRotationCb(adapter, "readTiltY", &out)) { + return out.value.f32 * INT32_MAX; + } + + if (adapter->oldRotation && adapter->oldRotation->readTiltY) { + return adapter->oldRotation->readTiltY(adapter->oldRotation); + } + return 0; +} + +static int32_t _rotationReadGyroZ(struct mRotationSource* rotation) { + struct mScriptCoreAdapter* adapter = containerof(rotation, struct mScriptCoreAdapter, rotation); + + struct mScriptValue out; + if (_callRotationCb(adapter, "readGyroZ", &out)) { + return out.value.f32 * INT32_MAX; + } + + if (adapter->oldRotation && adapter->oldRotation->readGyroZ) { + return adapter->oldRotation->readGyroZ(adapter->oldRotation); + } + return 0; +} + +#ifdef M_CORE_GBA +static uint8_t _readLuminance(struct GBALuminanceSource* luminance) { + struct mScriptCoreAdapter* adapter = containerof(luminance, struct mScriptCoreAdapter, luminance); + + if (adapter->luminanceCb) { + struct mScriptFrame frame; + mScriptFrameInit(&frame); + bool ok = mScriptInvoke(adapter->luminanceCb, &frame); + struct mScriptValue out = {0}; + if (ok && mScriptListSize(&frame.returnValues) == 1) { + if (!mScriptCast(mSCRIPT_TYPE_MS_U8, mScriptListGetPointer(&frame.returnValues, 0), &out)) { + ok = false; + } + } + mScriptFrameDeinit(&frame); + if (ok) { + return 0xFF - out.value.u32; + } + } + if (adapter->oldLuminance) { + adapter->oldLuminance->sample(adapter->oldLuminance); + return adapter->oldLuminance->readLuminance(adapter->oldLuminance); + } + return 0; +} +#endif + void mScriptContextAttachCore(struct mScriptContext* context, struct mCore* core) { struct mScriptValue* coreValue = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptCoreAdapter)); struct mScriptCoreAdapter* adapter = calloc(1, sizeof(*adapter)); @@ -687,6 +1185,25 @@ void mScriptContextAttachCore(struct mScriptContext* context, struct mCore* core adapter->memory.type = mSCRIPT_TYPE_MS_TABLE; adapter->memory.type->alloc(&adapter->memory); + adapter->rumble.setRumble = _setRumble; + adapter->rotation.sample = _rotationSample; + adapter->rotation.readTiltX = _rotationReadTiltX; + adapter->rotation.readTiltY = _rotationReadTiltY; + adapter->rotation.readGyroZ = _rotationReadGyroZ; + + adapter->oldRumble = core->getPeripheral(core, mPERIPH_RUMBLE); + adapter->oldRotation = core->getPeripheral(core, mPERIPH_ROTATION); + core->setPeripheral(core, mPERIPH_RUMBLE, &adapter->rumble); + core->setPeripheral(core, mPERIPH_ROTATION, &adapter->rotation); + +#ifdef M_CORE_GBA + adapter->luminance.readLuminance = _readLuminance; + if (core->platform(core) == mPLATFORM_GBA) { + adapter->oldLuminance = core->getPeripheral(core, mPERIPH_GBA_LUMINANCE); + core->setPeripheral(core, mPERIPH_GBA_LUMINANCE, &adapter->luminance); + } +#endif + _rebuildMemoryMap(context, adapter); coreValue->value.opaque = adapter; @@ -703,7 +1220,24 @@ void mScriptContextDetachCore(struct mScriptContext* context) { if (!value) { return; } - _clearMemoryMap(context, value->value.opaque, true); + + struct mScriptCoreAdapter* adapter = value->value.opaque; + _clearMemoryMap(context, adapter, true); + struct mCore* core = adapter->core; + core->setPeripheral(core, mPERIPH_RUMBLE, adapter->oldRumble); + core->setPeripheral(core, mPERIPH_ROTATION, adapter->oldRotation); + if (adapter->rotationCbTable) { + mScriptValueDeref(adapter->rotationCbTable); + } +#ifdef M_CORE_GBA + if (core->platform(core) == mPLATFORM_GBA) { + core->setPeripheral(core, mPERIPH_GBA_LUMINANCE, adapter->oldLuminance); + } + if (adapter->luminanceCb) { + mScriptValueDeref(adapter->luminanceCb); + } +#endif + mScriptContextRemoveGlobal(context, "emu"); } diff --git a/src/core/serialize.c b/src/core/serialize.c index 53e0aa168..b721cdafa 100644 --- a/src/core/serialize.c +++ b/src/core/serialize.c @@ -13,7 +13,7 @@ #include #ifdef USE_PNG -#include +#include #include #include #endif @@ -176,15 +176,15 @@ static bool _savePNGState(struct mCore* core, struct VFile* vf, struct mStateExt mappedMemoryFree(state, stateSize); unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); png_structp png = PNGWriteOpen(vf); - png_infop info = PNGWriteHeader(png, width, height); + png_infop info = PNGWriteHeader(png, width, height, mCOLOR_NATIVE); if (!png || !info) { PNGWriteClose(png, info); free(buffer); return false; } - PNGWritePixels(png, width, height, stride, pixels); + PNGWritePixels(png, width, height, stride, pixels, mCOLOR_NATIVE); PNGWriteCustomChunk(png, "gbAs", len, buffer); if (extdata) { uint32_t i; @@ -453,7 +453,7 @@ bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) { UNUSED(flags); #endif vf->truncate(vf, stateSize); - struct GBASerializedState* state = vf->map(vf, stateSize, MAP_WRITE); + void* state = vf->map(vf, stateSize, MAP_WRITE); if (!state) { mStateExtdataDeinit(&extdata); if (cheatVf) { @@ -529,7 +529,7 @@ bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags) { mappedMemoryFree(state, core->stateSize(core)); unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); struct mStateExtdataItem item; if (flags & SAVESTATE_SCREENSHOT && mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) { diff --git a/src/core/test/scripting.c b/src/core/test/scripting.c index b9cfa03e8..c2593bf14 100644 --- a/src/core/test/scripting.c +++ b/src/core/test/scripting.c @@ -9,8 +9,7 @@ #include #include #include -#include -#include +#include #include "script/test.h" @@ -143,8 +142,8 @@ M_TEST_DEFINE(globals) { LOAD_PROGRAM("assert(emu)"); assert_true(lua->run(lua)); - TEARDOWN_CORE; mScriptContextDeinit(&context); + TEARDOWN_CORE; } M_TEST_DEFINE(infoFuncs) { @@ -161,8 +160,8 @@ M_TEST_DEFINE(infoFuncs) { TEST_VALUE(S32, "frequency", core->frequency(core)); TEST_VALUE(S32, "frameCycles", core->frameCycles(core)); - TEARDOWN_CORE; mScriptContextDeinit(&context); + TEARDOWN_CORE; } M_TEST_DEFINE(detach) { @@ -195,8 +194,8 @@ M_TEST_DEFINE(detach) { ); assert_false(lua->run(lua)); - TEARDOWN_CORE; mScriptContextDeinit(&context); + TEARDOWN_CORE; } M_TEST_DEFINE(runFrame) { @@ -215,8 +214,8 @@ M_TEST_DEFINE(runFrame) { TEST_VALUE(S32, "frame", i); } - TEARDOWN_CORE; mScriptContextDeinit(&context); + TEARDOWN_CORE; } M_TEST_DEFINE(memoryRead) { @@ -250,8 +249,8 @@ M_TEST_DEFINE(memoryRead) { TEST_VALUE(S32, "b16", 0x0807); TEST_VALUE(S32, "a32", 0x0C0B0A09); - TEARDOWN_CORE; mScriptContextDeinit(&context); + TEARDOWN_CORE; } M_TEST_DEFINE(memoryWrite) { @@ -278,8 +277,8 @@ M_TEST_DEFINE(memoryWrite) { assert_int_equal(core->busRead8(core, RAM_BASE + i), i + 1); } - TEARDOWN_CORE; mScriptContextDeinit(&context); + TEARDOWN_CORE; } M_TEST_DEFINE(logging) { @@ -310,6 +309,415 @@ M_TEST_DEFINE(logging) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(screenshot) { + SETUP_LUA; + CREATE_CORE; + color_t* buffer = malloc(240 * 160 * sizeof(color_t)); + core->setVideoBuffer(core, buffer, 240); + core->reset(core); + core->runFrame(core); + + TEST_PROGRAM("im = emu:screenshotToImage()"); + TEST_PROGRAM("assert(im)"); + TEST_PROGRAM("assert(im.width >= 160)"); + TEST_PROGRAM("assert(im.height >= 144)"); + + free(buffer); + mScriptContextDeinit(&context); + TEARDOWN_CORE; +} + +#ifdef USE_DEBUGGERS +void _setupBp(struct mCore* core) { + switch (core->platform(core)) { +#ifdef M_CORE_GBA + case mPLATFORM_GBA: + core->busWrite32(core, 0x020000C0, 0xE0000000); // nop + core->busWrite32(core, 0x020000C4, 0xE0000000); // nop + core->busWrite32(core, 0x020000C8, 0xEAFFFFFD); // b 0x020000C4 + break; +#endif +#ifdef M_CORE_GB + case mPLATFORM_GB: + core->rawWrite8(core, 0x101, 0, 0xEE); // Jump to 0xF0 + core->rawWrite8(core, 0xF0, 0, 0x00); // nop + core->rawWrite8(core, 0xF1, 0, 0x18); // Loop forecer + core->rawWrite8(core, 0xF2, 0, 0xFD); // jr $-3 + break; +#endif + } +} + +#ifdef M_CORE_GBA +M_TEST_DEFINE(basicBreakpointGBA) { + SETUP_LUA; + struct mCore* core = mCoreCreate(mPLATFORM_GBA); + struct mDebugger debugger; + assert_non_null(core); + assert_true(core->init(core)); + mCoreInitConfig(core, NULL); + core->reset(core); + _setupBp(core); + mScriptContextAttachCore(&context, core); + + mDebuggerInit(&debugger); + mDebuggerAttach(&debugger, core); + + TEST_PROGRAM( + "hit = 0\n" + "function bkpt()\n" + " hit = hit + 1\n" + "end" + ); + TEST_PROGRAM("cbid = emu:setBreakpoint(bkpt, 0x020000C4)"); + TEST_PROGRAM("assert(cbid == 1)"); + + int i; + for (i = 0; i < 20; ++i) { + mDebuggerRun(&debugger); + } + + assert_int_equal(debugger.state, DEBUGGER_RUNNING); + TEST_PROGRAM("assert(hit >= 1)"); + + mScriptContextDeinit(&context); + TEARDOWN_CORE; + mDebuggerDeinit(&debugger); +} +#endif + +#ifdef M_CORE_GB +M_TEST_DEFINE(basicBreakpointGB) { + SETUP_LUA; + struct mCore* core = mCoreCreate(mPLATFORM_GB); + struct mDebugger debugger; + assert_non_null(core); + assert_true(core->init(core)); + mCoreInitConfig(core, NULL); + assert_true(core->loadROM(core, VFileFromConstMemory(_fakeGBROM, sizeof(_fakeGBROM)))); + core->reset(core); + _setupBp(core); + mScriptContextAttachCore(&context, core); + + mDebuggerInit(&debugger); + mDebuggerAttach(&debugger, core); + + TEST_PROGRAM( + "hit = 0\n" + "function bkpt()\n" + " hit = hit + 1\n" + "end" + ); + TEST_PROGRAM("cbid = emu:setBreakpoint(bkpt, 0xF0)"); + TEST_PROGRAM("assert(cbid == 1)"); + + int i; + for (i = 0; i < 20; ++i) { + mDebuggerRun(&debugger); + } + + assert_int_equal(debugger.state, DEBUGGER_RUNNING); + TEST_PROGRAM("assert(hit >= 1)"); + + mScriptContextDeinit(&context); + TEARDOWN_CORE; + mDebuggerDeinit(&debugger); +} +#endif + +M_TEST_DEFINE(multipleBreakpoint) { + SETUP_LUA; + struct mCore* core = mCoreCreate(TEST_PLATFORM); + struct mDebugger debugger; + assert_non_null(core); + assert_true(core->init(core)); + mCoreInitConfig(core, NULL); + core->reset(core); + _setupBp(core); + mScriptContextAttachCore(&context, core); + + mDebuggerInit(&debugger); + mDebuggerAttach(&debugger, core); + + TEST_PROGRAM( + "hit = 0\n" + "function bkpt1()\n" + " hit = hit + 1\n" + "end\n" + "function bkpt2()\n" + " hit = hit + 100\n" + "end" + ); +#ifdef M_CORE_GBA + TEST_PROGRAM("cbid1 = emu:setBreakpoint(bkpt1, 0x020000C4)"); + TEST_PROGRAM("cbid2 = emu:setBreakpoint(bkpt2, 0x020000C8)"); +#else + TEST_PROGRAM("cbid1 = emu:setBreakpoint(bkpt1, 0xF0)"); + TEST_PROGRAM("cbid2 = emu:setBreakpoint(bkpt2, 0xF1)"); +#endif + TEST_PROGRAM("assert(cbid1 == 1)"); + TEST_PROGRAM("assert(cbid2 == 2)"); + + int i; + for (i = 0; i < 20; ++i) { + mDebuggerRun(&debugger); + } + + assert_int_equal(debugger.state, DEBUGGER_RUNNING); + TEST_PROGRAM("assert(hit >= 101)"); + + mScriptContextDeinit(&context); + TEARDOWN_CORE; + mDebuggerDeinit(&debugger); +} + +M_TEST_DEFINE(basicWatchpoint) { + SETUP_LUA; + mScriptContextAttachStdlib(&context); + CREATE_CORE; + struct mDebugger debugger; + core->reset(core); + mScriptContextAttachCore(&context, core); + + mDebuggerInit(&debugger); + mDebuggerAttach(&debugger, core); + + TEST_PROGRAM( + "hit = 0\n" + "function bkpt()\n" + " hit = hit + 1\n" + "end" + ); + struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE); + lua->setGlobal(lua, "base", &base); + TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.READ))"); + TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base + 1, C.WATCHPOINT_TYPE.WRITE))"); + TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base + 2, C.WATCHPOINT_TYPE.RW))"); + TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base + 3, C.WATCHPOINT_TYPE.WRITE_CHANGE))"); + TEST_PROGRAM("assert(hit == 0)"); + + uint8_t value; + + // Read + TEST_PROGRAM("hit = 0"); + value = core->rawRead8(core, RAM_BASE, -1); + TEST_PROGRAM("assert(hit == 0)"); + core->busRead8(core, RAM_BASE); + TEST_PROGRAM("assert(hit == 1)"); + core->busWrite8(core, RAM_BASE, value); + TEST_PROGRAM("assert(hit == 1)"); + core->busWrite8(core, RAM_BASE, ~value); + TEST_PROGRAM("assert(hit == 1)"); + + // Write + TEST_PROGRAM("hit = 0"); + value = core->rawRead8(core, RAM_BASE + 1, -1); + TEST_PROGRAM("assert(hit == 0)"); + core->busRead8(core, RAM_BASE + 1); + TEST_PROGRAM("assert(hit == 0)"); + core->busWrite8(core, RAM_BASE + 1, value); + TEST_PROGRAM("assert(hit == 1)"); + core->busWrite8(core, RAM_BASE + 1, ~value); + TEST_PROGRAM("assert(hit == 2)"); + + // RW + TEST_PROGRAM("hit = 0"); + value = core->rawRead8(core, RAM_BASE + 2, -1); + TEST_PROGRAM("assert(hit == 0)"); + core->busRead8(core, RAM_BASE + 2); + TEST_PROGRAM("assert(hit == 1)"); + core->busWrite8(core, RAM_BASE + 2, value); + TEST_PROGRAM("assert(hit == 2)"); + core->busWrite8(core, RAM_BASE + 2, ~value); + TEST_PROGRAM("assert(hit == 3)"); + + // Change + TEST_PROGRAM("hit = 0"); + value = core->rawRead8(core, RAM_BASE + 3, -1); + TEST_PROGRAM("assert(hit == 0)"); + core->busRead8(core, RAM_BASE + 3); + TEST_PROGRAM("assert(hit == 0)"); + core->busWrite8(core, RAM_BASE + 3, value); + TEST_PROGRAM("assert(hit == 0)"); + core->busWrite8(core, RAM_BASE + 3, ~value); + TEST_PROGRAM("assert(hit == 1)"); + + mScriptContextDeinit(&context); + TEARDOWN_CORE; + mDebuggerDeinit(&debugger); +} + +M_TEST_DEFINE(removeBreakpoint) { + SETUP_LUA; + mScriptContextAttachStdlib(&context); + CREATE_CORE; + struct mDebugger debugger; + core->reset(core); + mScriptContextAttachCore(&context, core); + + mDebuggerInit(&debugger); + mDebuggerAttach(&debugger, core); + + TEST_PROGRAM( + "hit = 0\n" + "function bkpt()\n" + " hit = hit + 1\n" + "end" + ); + struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE); + lua->setGlobal(lua, "base", &base); + TEST_PROGRAM("cbid = emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.READ)"); + + TEST_PROGRAM("assert(hit == 0)"); + core->busRead8(core, RAM_BASE); + TEST_PROGRAM("assert(hit == 1)"); + core->busRead8(core, RAM_BASE); + TEST_PROGRAM("assert(hit == 2)"); + TEST_PROGRAM("assert(emu:clearBreakpoint(cbid))"); + core->busRead8(core, RAM_BASE); + TEST_PROGRAM("assert(hit == 2)"); + + mScriptContextDeinit(&context); + TEARDOWN_CORE; + mDebuggerDeinit(&debugger); +} + + +M_TEST_DEFINE(overlappingBreakpoint) { + SETUP_LUA; + struct mCore* core = mCoreCreate(TEST_PLATFORM); + struct mDebugger debugger; + assert_non_null(core); + assert_true(core->init(core)); + mCoreInitConfig(core, NULL); + core->reset(core); + _setupBp(core); + mScriptContextAttachCore(&context, core); + + mDebuggerInit(&debugger); + mDebuggerAttach(&debugger, core); + + TEST_PROGRAM( + "hit = 0\n" + "function bkpt1()\n" + " hit = hit + 1\n" + "end\n" + "function bkpt2()\n" + " hit = hit + 100\n" + "end" + ); +#ifdef M_CORE_GBA + TEST_PROGRAM("cbid1 = emu:setBreakpoint(bkpt1, 0x020000C4)"); + TEST_PROGRAM("cbid2 = emu:setBreakpoint(bkpt2, 0x020000C4)"); +#else + TEST_PROGRAM("cbid1 = emu:setBreakpoint(bkpt1, 0xF0)"); + TEST_PROGRAM("cbid2 = emu:setBreakpoint(bkpt2, 0xF0)"); +#endif + TEST_PROGRAM("assert(cbid1 == 1)"); + TEST_PROGRAM("assert(cbid2 == 2)"); + + int i; + for (i = 0; i < 20; ++i) { + mDebuggerRun(&debugger); + } + + assert_int_equal(debugger.state, DEBUGGER_RUNNING); + TEST_PROGRAM("assert(hit >= 101)"); + TEST_PROGRAM("oldHit = hit"); + + TEST_PROGRAM("assert(emu:clearBreakpoint(cbid2))"); + + for (i = 0; i < 10; ++i) { + mDebuggerRun(&debugger); + } + TEST_PROGRAM("assert(hit - oldHit > 0)"); + TEST_PROGRAM("assert(hit - oldHit < 100)"); + + mScriptContextDeinit(&context); + TEARDOWN_CORE; + mDebuggerDeinit(&debugger); +} + +M_TEST_DEFINE(overlappingWatchpoint) { + SETUP_LUA; + mScriptContextAttachStdlib(&context); + CREATE_CORE; + struct mDebugger debugger; + core->reset(core); + mScriptContextAttachCore(&context, core); + + mDebuggerInit(&debugger); + mDebuggerAttach(&debugger, core); + + TEST_PROGRAM( + "hit = 0\n" + "function bkpt()\n" + " hit = hit + 1\n" + "end" + ); + struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE); + lua->setGlobal(lua, "base", &base); + TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.READ))"); + TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.WRITE))"); + TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.RW))"); + TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.WRITE_CHANGE))"); + TEST_PROGRAM("assert(hit == 0)"); + + uint8_t value; + + // Read + TEST_PROGRAM("hit = 0"); + value = core->rawRead8(core, RAM_BASE, -1); + TEST_PROGRAM("assert(hit == 0)"); + core->busRead8(core, RAM_BASE); + TEST_PROGRAM("assert(hit == 2)"); // Read, RW + core->busWrite8(core, RAM_BASE, value); + TEST_PROGRAM("assert(hit == 4)"); // Write, RW + core->busWrite8(core, RAM_BASE, ~value); + TEST_PROGRAM("assert(hit == 7)"); // Write, RW, change + + mScriptContextDeinit(&context); + TEARDOWN_CORE; + mDebuggerDeinit(&debugger); +} + +M_TEST_DEFINE(rangeWatchpoint) { + SETUP_LUA; + mScriptContextAttachStdlib(&context); + CREATE_CORE; + struct mDebugger debugger; + core->reset(core); + mScriptContextAttachCore(&context, core); + + mDebuggerInit(&debugger); + mDebuggerAttach(&debugger, core); + + TEST_PROGRAM( + "hit = 0\n" + "function bkpt()\n" + " hit = hit + 1\n" + "end" + ); + struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE); + lua->setGlobal(lua, "base", &base); + TEST_PROGRAM("assert(0 < emu:setRangeWatchpoint(bkpt, base, base + 2, C.WATCHPOINT_TYPE.READ))"); + TEST_PROGRAM("assert(0 < emu:setRangeWatchpoint(bkpt, base + 1, base + 3, C.WATCHPOINT_TYPE.READ))"); + + // Read + TEST_PROGRAM("assert(hit == 0)"); + core->busRead8(core, RAM_BASE); + TEST_PROGRAM("assert(hit == 1)"); + core->busRead8(core, RAM_BASE + 1); + TEST_PROGRAM("assert(hit == 3)"); + core->busRead8(core, RAM_BASE + 2); + TEST_PROGRAM("assert(hit == 4)"); + + mScriptContextDeinit(&context); + TEARDOWN_CORE; + mDebuggerDeinit(&debugger); +} +#endif + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore, cmocka_unit_test(globals), cmocka_unit_test(infoFuncs), @@ -318,4 +726,19 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore, cmocka_unit_test(memoryRead), cmocka_unit_test(memoryWrite), cmocka_unit_test(logging), + cmocka_unit_test(screenshot), +#ifdef USE_DEBUGGERS +#ifdef M_CORE_GBA + cmocka_unit_test(basicBreakpointGBA), +#endif +#ifdef M_CORE_GB + cmocka_unit_test(basicBreakpointGB), +#endif + cmocka_unit_test(multipleBreakpoint), + cmocka_unit_test(basicWatchpoint), + cmocka_unit_test(removeBreakpoint), + cmocka_unit_test(overlappingBreakpoint), + cmocka_unit_test(overlappingWatchpoint), + cmocka_unit_test(rangeWatchpoint), +#endif ) diff --git a/src/core/thread.c b/src/core/thread.c index 9f4be082b..983395c12 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -109,10 +109,7 @@ static void _wait(struct mCoreThreadInternal* threadContext) { #ifdef USE_DEBUGGERS if (threadContext->core && threadContext->core->debugger) { - struct mDebugger* debugger = threadContext->core->debugger; - if (debugger->interrupt) { - debugger->interrupt(debugger); - } + mDebuggerInterrupt(threadContext->core->debugger); } #endif @@ -157,7 +154,11 @@ void _frameStarted(void* context) { } if (thread->core->opts.rewindEnable && thread->core->opts.rewindBufferCapacity > 0) { if (!thread->impl->rewinding || !mCoreRewindRestore(&thread->impl->rewind, thread->core)) { - mCoreRewindAppend(&thread->impl->rewind, thread->core); + if (thread->impl->rewind.rewindFrameCounter == 0) { + mCoreRewindAppend(&thread->impl->rewind, thread->core); + thread->impl->rewind.rewindFrameCounter = thread->core->opts.rewindBufferInterval; + } + thread->impl->rewind.rewindFrameCounter--; } } } @@ -348,8 +349,8 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { while (impl->state >= mTHREAD_MIN_WAITING && impl->state <= mTHREAD_MAX_WAITING) { #ifdef USE_DEBUGGERS - if (debugger && debugger->update && debugger->state != DEBUGGER_SHUTDOWN) { - debugger->update(debugger); + if (debugger && debugger->state != DEBUGGER_SHUTDOWN) { + mDebuggerUpdate(debugger); ConditionWaitTimed(&impl->stateCond, &impl->stateMutex, 10); } else #endif diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index 12cd7af18..398a993e5 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -191,7 +191,7 @@ static void _breakInto(struct CLIDebugger* debugger, struct CLIDebugVector* dv) #endif static bool CLIDebuggerCheckTraceMode(struct CLIDebugger* debugger, bool requireEnabled) { - struct mDebuggerPlatform* platform = debugger->d.platform; + struct mDebuggerPlatform* platform = debugger->d.p->platform; if (!platform->getStackTraceMode) { debugger->backend->printf(debugger->backend, "Stack tracing is not supported by this platform.\n"); return false; @@ -204,13 +204,14 @@ static bool CLIDebuggerCheckTraceMode(struct CLIDebugger* debugger, bool require static void _continue(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); - debugger->d.state = debugger->traceRemaining != 0 ? DEBUGGER_CALLBACK : DEBUGGER_RUNNING; + debugger->d.needsCallback = debugger->traceRemaining != 0; + debugger->d.isPaused = false; } static void _next(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); - struct mDebuggerPlatform* platform = debugger->d.platform; - debugger->d.core->step(debugger->d.core); + struct mDebuggerPlatform* platform = debugger->d.p->platform; + debugger->d.p->core->step(debugger->d.p->core); if (platform->getStackTraceMode && platform->getStackTraceMode(platform) != STACK_TRACE_DISABLED) { platform->updateStackTrace(platform); } @@ -221,7 +222,7 @@ static void _disassemble(struct CLIDebugger* debugger, struct CLIDebugVector* dv debugger->system->disassemble(debugger->system, dv); } -static bool _parseExpression(struct mDebugger* debugger, struct CLIDebugVector* dv, int32_t* intValue, int* segmentValue) { +static bool _parseExpression(struct mDebuggerModule* debugger, struct CLIDebugVector* dv, int32_t* intValue, int* segmentValue) { size_t args = 0; struct CLIDebugVector* accum; for (accum = dv; accum; accum = accum->next) { @@ -240,7 +241,7 @@ static bool _parseExpression(struct mDebugger* debugger, struct CLIDebugVector* if (!tree) { return false; } - if (!mDebuggerEvaluateParseTree(debugger, tree, intValue, segmentValue)) { + if (!mDebuggerEvaluateParseTree(debugger->p, tree, intValue, segmentValue)) { parseFree(tree); return false; } @@ -366,7 +367,7 @@ static void _printHelp(struct CLIDebugger* debugger, struct CLIDebugVector* dv) static void _quit(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); - debugger->d.state = DEBUGGER_SHUTDOWN; + mDebuggerShutdown(debugger->d.p); } static void _readByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { @@ -377,17 +378,17 @@ static void _readByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { uint32_t address = dv->intValue; uint8_t value; if (dv->segmentValue >= 0) { - value = debugger->d.core->rawRead8(debugger->d.core, address, dv->segmentValue); + value = debugger->d.p->core->rawRead8(debugger->d.p->core, address, dv->segmentValue); } else { - value = debugger->d.core->busRead8(debugger->d.core, address); + value = debugger->d.p->core->busRead8(debugger->d.p->core, address); } debugger->backend->printf(debugger->backend, " 0x%02X\n", value); } static void _reset(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); - mStackTraceClear(&debugger->d.stackTrace); - debugger->d.core->reset(debugger->d.core); + mStackTraceClear(&debugger->d.p->stackTrace); + debugger->d.p->core->reset(debugger->d.p->core); _printStatus(debugger, 0); } @@ -399,9 +400,9 @@ static void _readHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* d uint32_t address = dv->intValue; uint16_t value; if (dv->segmentValue >= 0) { - value = debugger->d.core->rawRead16(debugger->d.core, address & -1, dv->segmentValue); + value = debugger->d.p->core->rawRead16(debugger->d.p->core, address & -1, dv->segmentValue); } else { - value = debugger->d.core->busRead16(debugger->d.core, address & ~1); + value = debugger->d.p->core->busRead16(debugger->d.p->core, address & ~1); } debugger->backend->printf(debugger->backend, " 0x%04X\n", value); } @@ -414,9 +415,9 @@ static void _readWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { uint32_t address = dv->intValue; uint32_t value; if (dv->segmentValue >= 0) { - value = debugger->d.core->rawRead32(debugger->d.core, address & -3, dv->segmentValue); + value = debugger->d.p->core->rawRead32(debugger->d.p->core, address & -3, dv->segmentValue); } else { - value = debugger->d.core->busRead32(debugger->d.core, address & ~3); + value = debugger->d.p->core->busRead32(debugger->d.p->core, address & ~3); } debugger->backend->printf(debugger->backend, " 0x%08X\n", value); } @@ -437,9 +438,9 @@ static void _writeByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) return; } if (dv->segmentValue >= 0) { - debugger->d.core->rawWrite8(debugger->d.core, address, value, dv->segmentValue); + debugger->d.p->core->rawWrite8(debugger->d.p->core, address, dv->segmentValue, value); } else { - debugger->d.core->busWrite8(debugger->d.core, address, value); + debugger->d.p->core->busWrite8(debugger->d.p->core, address, value); } } @@ -459,9 +460,9 @@ static void _writeHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* return; } if (dv->segmentValue >= 0) { - debugger->d.core->rawWrite16(debugger->d.core, address, value, dv->segmentValue); + debugger->d.p->core->rawWrite16(debugger->d.p->core, address, dv->segmentValue, value); } else { - debugger->d.core->busWrite16(debugger->d.core, address, value); + debugger->d.p->core->busWrite16(debugger->d.p->core, address, value); } } @@ -474,7 +475,7 @@ static void _writeRegister(struct CLIDebugger* debugger, struct CLIDebugVector* debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); return; } - if (!debugger->d.core->writeRegister(debugger->d.core, dv->charValue, &dv->next->intValue)) { + if (!debugger->d.p->core->writeRegister(debugger->d.p->core, dv->charValue, &dv->next->intValue)) { debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); } } @@ -491,9 +492,9 @@ static void _writeWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) uint32_t address = dv->intValue; uint32_t value = dv->next->intValue; if (dv->segmentValue >= 0) { - debugger->d.core->rawWrite32(debugger->d.core, address, value, dv->segmentValue); + debugger->d.p->core->rawWrite32(debugger->d.p->core, address, dv->segmentValue, value); } else { - debugger->d.core->busWrite32(debugger->d.core, address, value); + debugger->d.p->core->busWrite32(debugger->d.p->core, address, value); } } @@ -516,9 +517,9 @@ static void _dumpByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { for (; line > 0; --line, ++address, --words) { uint32_t value; if (dv->segmentValue >= 0) { - value = debugger->d.core->rawRead8(debugger->d.core, address, dv->segmentValue); + value = debugger->d.p->core->rawRead8(debugger->d.p->core, address, dv->segmentValue); } else { - value = debugger->d.core->busRead8(debugger->d.core, address); + value = debugger->d.p->core->busRead8(debugger->d.p->core, address); } debugger->backend->printf(debugger->backend, " %02X", value); } @@ -545,9 +546,9 @@ static void _dumpHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* d for (; line > 0; --line, address += 2, --words) { uint32_t value; if (dv->segmentValue >= 0) { - value = debugger->d.core->rawRead16(debugger->d.core, address, dv->segmentValue); + value = debugger->d.p->core->rawRead16(debugger->d.p->core, address, dv->segmentValue); } else { - value = debugger->d.core->busRead16(debugger->d.core, address); + value = debugger->d.p->core->busRead16(debugger->d.p->core, address); } debugger->backend->printf(debugger->backend, " %04X", value); } @@ -574,9 +575,9 @@ static void _dumpWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { for (; line > 0; --line, address += 4, --words) { uint32_t value; if (dv->segmentValue >= 0) { - value = debugger->d.core->rawRead32(debugger->d.core, address, dv->segmentValue); + value = debugger->d.p->core->rawRead32(debugger->d.p->core, address, dv->segmentValue); } else { - value = debugger->d.core->busRead32(debugger->d.core, address); + value = debugger->d.p->core->busRead32(debugger->d.p->core, address); } debugger->backend->printf(debugger->backend, " %08X", value); } @@ -590,8 +591,8 @@ static void _source(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { debugger->backend->printf(debugger->backend, "Needs a filename\n"); return; } - if (debugger->d.bridge && mScriptBridgeLoadScript(debugger->d.bridge, dv->charValue)) { - mScriptBridgeRun(debugger->d.bridge); + if (debugger->d.p->bridge && mScriptBridgeLoadScript(debugger->d.p->bridge, dv->charValue)) { + mScriptBridgeRun(debugger->d.p->bridge); } else { debugger->backend->printf(debugger->backend, "Failed to load script\n"); } @@ -647,7 +648,7 @@ static void _setBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* return; } } - ssize_t id = debugger->d.platform->setBreakpoint(debugger->d.platform, &breakpoint); + ssize_t id = debugger->d.p->platform->setBreakpoint(debugger->d.p->platform, &debugger->d, &breakpoint); if (id > 0) { debugger->backend->printf(debugger->backend, INFO_BREAKPOINT_ADDED, id); } @@ -658,7 +659,7 @@ static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); return; } - if (!debugger->d.platform->setWatchpoint) { + if (!debugger->d.p->platform->setWatchpoint) { debugger->backend->printf(debugger->backend, "Watchpoints are not supported by this platform.\n"); return; } @@ -677,7 +678,7 @@ static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* return; } } - ssize_t id = debugger->d.platform->setWatchpoint(debugger->d.platform, &watchpoint); + ssize_t id = debugger->d.p->platform->setWatchpoint(debugger->d.p->platform, &debugger->d, &watchpoint); if (id > 0) { debugger->backend->printf(debugger->backend, INFO_WATCHPOINT_ADDED, id); } @@ -692,7 +693,7 @@ static void _setRangeWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVec debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); return; } - if (!debugger->d.platform->setWatchpoint) { + if (!debugger->d.p->platform->setWatchpoint) { debugger->backend->printf(debugger->backend, "Watchpoints are not supported by this platform.\n"); return; } @@ -719,7 +720,7 @@ static void _setRangeWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVec return; } } - ssize_t id = debugger->d.platform->setWatchpoint(debugger->d.platform, &watchpoint); + ssize_t id = debugger->d.p->platform->setWatchpoint(debugger->d.p->platform, &debugger->d, &watchpoint); if (id > 0) { debugger->backend->printf(debugger->backend, INFO_WATCHPOINT_ADDED, id); } @@ -763,14 +764,14 @@ static void _clearBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector return; } uint64_t id = dv->intValue; - debugger->d.platform->clearBreakpoint(debugger->d.platform, id); + debugger->d.p->platform->clearBreakpoint(debugger->d.p->platform, id); } static void _listBreakpoints(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); struct mBreakpointList breakpoints; mBreakpointListInit(&breakpoints, 0); - debugger->d.platform->listBreakpoints(debugger->d.platform, &breakpoints); + debugger->d.p->platform->listBreakpoints(debugger->d.p->platform, &debugger->d, &breakpoints); size_t i; for (i = 0; i < mBreakpointListSize(&breakpoints); ++i) { struct mBreakpoint* breakpoint = mBreakpointListGetPointer(&breakpoints, i); @@ -787,7 +788,7 @@ static void _listWatchpoints(struct CLIDebugger* debugger, struct CLIDebugVector UNUSED(dv); struct mWatchpointList watchpoints; mWatchpointListInit(&watchpoints, 0); - debugger->d.platform->listWatchpoints(debugger->d.platform, &watchpoints); + debugger->d.p->platform->listWatchpoints(debugger->d.p->platform, &debugger->d, &watchpoints); size_t i; for (i = 0; i < mWatchpointListSize(&watchpoints); ++i) { struct mWatchpoint* watchpoint = mWatchpointListGetPointer(&watchpoints, i); @@ -809,16 +810,21 @@ static void _listWatchpoints(struct CLIDebugger* debugger, struct CLIDebugVector } static void _trace(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - if (!dv || dv->type != CLIDV_INT_TYPE) { + if (!dv) { debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); return; } + if (dv->type != CLIDV_INT_TYPE || dv->intValue < 0) { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + return; + } debugger->traceRemaining = dv->intValue; if (debugger->traceVf) { debugger->traceVf->close(debugger->traceVf); debugger->traceVf = NULL; } + debugger->d.needsCallback = debugger->traceRemaining != 0; if (debugger->traceRemaining == 0) { return; } @@ -826,7 +832,7 @@ static void _trace(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { debugger->traceVf = VFileOpen(dv->next->charValue, O_CREAT | O_WRONLY | O_APPEND); } if (_doTrace(debugger)) { - debugger->d.state = DEBUGGER_CALLBACK; + mDebuggerUpdatePaused(debugger->d.p); } else { debugger->system->printStatus(debugger->system); } @@ -836,7 +842,7 @@ static bool _doTrace(struct CLIDebugger* debugger) { char trace[1024]; trace[sizeof(trace) - 1] = '\0'; size_t traceSize = sizeof(trace) - 2; - debugger->d.platform->trace(debugger->d.platform, trace, &traceSize); + debugger->d.p->platform->trace(debugger->d.p->platform, trace, &traceSize); if (traceSize + 2 <= sizeof(trace)) { trace[traceSize] = '\n'; trace[traceSize + 1] = '\0'; @@ -854,6 +860,7 @@ static bool _doTrace(struct CLIDebugger* debugger) { debugger->traceVf->close(debugger->traceVf); debugger->traceVf = NULL; } + debugger->d.needsCallback = false; return false; } return true; @@ -866,7 +873,7 @@ static void _printStatus(struct CLIDebugger* debugger, struct CLIDebugVector* dv static void _events(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); - struct mTiming* timing = debugger->d.core->timing; + struct mTiming* timing = debugger->d.p->core->timing; struct mTimingEvent* next = timing->root; for (; next; next = next->next) { debugger->backend->printf(debugger->backend, "%s in %i cycles\n", next->name, mTimingUntil(timing, next)); @@ -891,7 +898,7 @@ struct CLIDebugVector* CLIDVParse(struct CLIDebugger* debugger, const char* stri if (!parseLexedExpression(tree, &lv)) { dvTemp.type = CLIDV_ERROR_TYPE; } else { - if (!mDebuggerEvaluateParseTree(&debugger->d, tree, &dvTemp.intValue, &dvTemp.segmentValue)) { + if (!mDebuggerEvaluateParseTree(debugger->d.p, tree, &dvTemp.intValue, &dvTemp.segmentValue)) { dvTemp.type = CLIDV_ERROR_TYPE; } } @@ -1085,7 +1092,7 @@ bool CLIDebuggerRunCommand(struct CLIDebugger* debugger, const char* line, size_ return false; } -static void _commandLine(struct mDebugger* debugger) { +static void _commandLine(struct mDebuggerModule* debugger, int32_t timeoutMs) { struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; const char* line; size_t len; @@ -1094,14 +1101,19 @@ static void _commandLine(struct mDebugger* debugger) { } else { _printStatus(cliDebugger, 0); } - while (debugger->state == DEBUGGER_PAUSED) { - line = cliDebugger->backend->readline(cliDebugger->backend, &len); - if (!line || len == 0) { - debugger->state = DEBUGGER_SHUTDOWN; + while (debugger->isPaused && !mDebuggerIsShutdown(debugger->p)) { + int poll = cliDebugger->backend->poll(cliDebugger->backend, timeoutMs); + if (poll <= 0) { + if (poll < 0) { + mDebuggerShutdown(debugger->p); + } else { + cliDebugger->skipStatus = true; + } return; } - if (line[0] == '\033') { - cliDebugger->skipStatus = true; + line = cliDebugger->backend->readline(cliDebugger->backend, &len); + if (!line || len == 0) { + mDebuggerShutdown(debugger->p); return; } if (line[0] == '\n') { @@ -1120,7 +1132,7 @@ static void _commandLine(struct mDebugger* debugger) { } } -static void _reportEntry(struct mDebugger* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) { +static void _reportEntry(struct mDebuggerModule* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) { struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; if (cliDebugger->traceRemaining > 0) { cliDebugger->traceRemaining = 0; @@ -1162,7 +1174,7 @@ static void _reportEntry(struct mDebugger* debugger, enum mDebuggerEntryReason r case DEBUGGER_ENTER_STACK: if (info) { if (info->type.st.traceType == STACK_TRACE_BREAK_ON_CALL) { - struct mStackTrace* stack = &cliDebugger->d.stackTrace; + struct mStackTrace* stack = &cliDebugger->d.p->stackTrace; struct mStackFrame* frame = mStackTraceGetFrame(stack, 0); if (frame->interrupt) { cliDebugger->backend->printf(cliDebugger->backend, "Hit interrupt at at 0x%08X\n", info->address); @@ -1180,7 +1192,7 @@ static void _reportEntry(struct mDebugger* debugger, enum mDebuggerEntryReason r } } -static void _cliDebuggerInit(struct mDebugger* debugger) { +static void _cliDebuggerInit(struct mDebuggerModule* debugger) { struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; cliDebugger->traceRemaining = 0; cliDebugger->traceVf = NULL; @@ -1191,7 +1203,7 @@ static void _cliDebuggerInit(struct mDebugger* debugger) { } } -static void _cliDebuggerDeinit(struct mDebugger* debugger) { +static void _cliDebuggerDeinit(struct mDebuggerModule* debugger) { struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; if (cliDebugger->traceVf) { cliDebugger->traceVf->close(cliDebugger->traceVf); @@ -1211,23 +1223,22 @@ static void _cliDebuggerDeinit(struct mDebugger* debugger) { } } -static void _cliDebuggerCustom(struct mDebugger* debugger) { +static void _cliDebuggerCustom(struct mDebuggerModule* debugger) { struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; - bool retain = true; - enum mDebuggerState next = DEBUGGER_RUNNING; if (cliDebugger->traceRemaining) { - retain = _doTrace(cliDebugger) && retain; - next = DEBUGGER_PAUSED; + if (!_doTrace(cliDebugger)) { + debugger->isPaused = true; + debugger->needsCallback = false; + } } - if (cliDebugger->system) { - retain = cliDebugger->system->custom(cliDebugger->system) && retain; - } - if (!retain && debugger->state == DEBUGGER_CALLBACK) { - debugger->state = next; + if (cliDebugger->system && cliDebugger->system->custom) { + debugger->needsCallback = cliDebugger->system->custom(cliDebugger->system) || debugger->needsCallback; } + + mDebuggerUpdatePaused(debugger->p); } -static void _cliDebuggerInterrupt(struct mDebugger* debugger) { +static void _cliDebuggerInterrupt(struct mDebuggerModule* debugger) { struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; if (cliDebugger->backend->interrupt) { cliDebugger->backend->interrupt(cliDebugger->backend); @@ -1323,13 +1334,13 @@ static void _backtrace(struct CLIDebugger* debugger, struct CLIDebugVector* dv) if (!CLIDebuggerCheckTraceMode(debugger, true)) { return; } - struct mStackTrace* stack = &debugger->d.stackTrace; + struct mStackTrace* stack = &debugger->d.p->stackTrace; ssize_t frames = mStackTraceGetDepth(stack); if (dv && dv->type == CLIDV_INT_TYPE && dv->intValue < frames) { frames = dv->intValue; } ssize_t i; - struct mDebuggerSymbols* symbolTable = debugger->d.core->symbolTable; + struct mDebuggerSymbols* symbolTable = debugger->d.p->core->symbolTable; for (i = 0; i < frames; ++i) { char trace[1024]; size_t traceSize = sizeof(trace) - 2; @@ -1343,7 +1354,7 @@ static void _finish(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { if (!CLIDebuggerCheckTraceMode(debugger, true)) { return; } - struct mStackTrace* stack = &debugger->d.stackTrace; + struct mStackTrace* stack = &debugger->d.p->stackTrace; struct mStackFrame* frame = mStackTraceGetFrame(stack, 0); if (!frame) { debugger->backend->printf(debugger->backend, "No current stack frame.\n"); @@ -1369,7 +1380,7 @@ static void _setStackTraceMode(struct CLIDebugger* debugger, struct CLIDebugVect debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); return; } - struct mDebuggerPlatform* platform = debugger->d.platform; + struct mDebuggerPlatform* platform = debugger->d.p->platform; if (strcmp(dv->charValue, "off") == 0) { platform->setStackTraceMode(platform, STACK_TRACE_DISABLED); } else if (strcmp(dv->charValue, "trace-only") == 0) { @@ -1386,7 +1397,7 @@ static void _setStackTraceMode(struct CLIDebugger* debugger, struct CLIDebugVect } static void _loadSymbols(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - struct mDebuggerSymbols* symbolTable = debugger->d.core->symbolTable; + struct mDebuggerSymbols* symbolTable = debugger->d.p->core->symbolTable; if (!symbolTable) { debugger->backend->printf(debugger->backend, "No symbol table available.\n"); return; @@ -1420,7 +1431,7 @@ static void _loadSymbols(struct CLIDebugger* debugger, struct CLIDebugVector* dv } static void _setSymbol(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - struct mDebuggerSymbols* symbolTable = debugger->d.core->symbolTable; + struct mDebuggerSymbols* symbolTable = debugger->d.p->core->symbolTable; if (!symbolTable) { debugger->backend->printf(debugger->backend, "No symbol table available.\n"); return; @@ -1437,7 +1448,7 @@ static void _setSymbol(struct CLIDebugger* debugger, struct CLIDebugVector* dv) } static void _findSymbol(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - struct mDebuggerSymbols* symbolTable = debugger->d.core->symbolTable; + struct mDebuggerSymbols* symbolTable = debugger->d.p->core->symbolTable; if (!symbolTable) { debugger->backend->printf(debugger->backend, "No symbol table available.\n"); return; diff --git a/src/debugger/debugger.c b/src/debugger/debugger.c index cc6beab9c..cc0b32683 100644 --- a/src/debugger/debugger.c +++ b/src/debugger/debugger.c @@ -24,17 +24,18 @@ mLOG_DEFINE_CATEGORY(DEBUGGER, "Debugger", "core.debugger"); DEFINE_VECTOR(mBreakpointList, struct mBreakpoint); DEFINE_VECTOR(mWatchpointList, struct mWatchpoint); +DEFINE_VECTOR(mDebuggerModuleList, struct mDebuggerModule*); -static void mDebuggerInit(void* cpu, struct mCPUComponent* component); -static void mDebuggerDeinit(struct mCPUComponent* component); +static void _mDebuggerInit(void* cpu, struct mCPUComponent* component); +static void _mDebuggerDeinit(struct mCPUComponent* component); -struct mDebugger* mDebuggerCreate(enum mDebuggerType type, struct mCore* core) { +struct mDebuggerModule* mDebuggerCreateModule(enum mDebuggerType type, struct mCore* core) { if (!core->supportsDebuggerType(core, type)) { return NULL; } union DebugUnion { - struct mDebugger d; + struct mDebuggerModule d; struct CLIDebugger cli; #ifdef USE_GDB_STUB struct GDBStub gdb; @@ -67,10 +68,21 @@ struct mDebugger* mDebuggerCreate(enum mDebuggerType type, struct mCore* core) { return &debugger->d; } +void mDebuggerInit(struct mDebugger* debugger) { + memset(debugger, 0, sizeof(*debugger)); + mDebuggerModuleListInit(&debugger->modules, 4); + TableInit(&debugger->pointOwner, 0, NULL); +} + +void mDebuggerDeinit(struct mDebugger* debugger) { + mDebuggerModuleListDeinit(&debugger->modules); + TableDeinit(&debugger->pointOwner); +} + void mDebuggerAttach(struct mDebugger* debugger, struct mCore* core) { debugger->d.id = DEBUGGER_ID; - debugger->d.init = mDebuggerInit; - debugger->d.deinit = mDebuggerDeinit; + debugger->d.init = _mDebuggerInit; + debugger->d.deinit = _mDebuggerDeinit; debugger->core = core; if (!debugger->core->symbolTable) { debugger->core->loadSymbols(debugger->core, NULL); @@ -80,7 +92,36 @@ void mDebuggerAttach(struct mDebugger* debugger, struct mCore* core) { core->attachDebugger(core, debugger); } -void mDebuggerRun(struct mDebugger* debugger) { +void mDebuggerAttachModule(struct mDebugger* debugger, struct mDebuggerModule* module) { + module->p = debugger; + *mDebuggerModuleListAppend(&debugger->modules) = module; + if (debugger->state > DEBUGGER_CREATED && debugger->state < DEBUGGER_SHUTDOWN) { + if (module->init) { + module->init(module); + } + } +} + +void mDebuggerDetachModule(struct mDebugger* debugger, struct mDebuggerModule* module) { + size_t i; + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + if (module != *mDebuggerModuleListGetPointer(&debugger->modules, i)) { + continue; + } + if (debugger->state > DEBUGGER_CREATED && debugger->state < DEBUGGER_SHUTDOWN) { + if (module->deinit) { + module->deinit(module); + } + } + mDebuggerModuleListShift(&debugger->modules, i, 1); + break; + } +} + +void mDebuggerRunTimeout(struct mDebugger* debugger, int32_t timeoutMs) { + size_t i; + size_t anyPaused = 0; + switch (debugger->state) { case DEBUGGER_RUNNING: if (!debugger->platform->hasBreakpoints(debugger->platform)) { @@ -93,20 +134,43 @@ void mDebuggerRun(struct mDebugger* debugger) { case DEBUGGER_CALLBACK: debugger->core->step(debugger->core); debugger->platform->checkBreakpoints(debugger->platform); - debugger->custom(debugger); + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); + if (module->needsCallback) { + module->custom(module); + } + } break; case DEBUGGER_PAUSED: - if (debugger->paused) { - debugger->paused(debugger); - } else { + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); + if (module->isPaused) { + if (module->paused) { + module->paused(module, timeoutMs); + } + if (module->isPaused) { + ++anyPaused; + } + } else if (module->needsCallback) { + module->custom(module); + } + } + if (debugger->state == DEBUGGER_PAUSED && !anyPaused) { debugger->state = DEBUGGER_RUNNING; } break; + case DEBUGGER_CREATED: + mLOG(DEBUGGER, ERROR, "Attempted to run debugger before initializtion"); + return; case DEBUGGER_SHUTDOWN: return; } } +void mDebuggerRun(struct mDebugger* debugger) { + mDebuggerRunTimeout(debugger, 50); +} + void mDebuggerRunFrame(struct mDebugger* debugger) { uint32_t frame = debugger->core->frameCounter(debugger->core); do { @@ -115,30 +179,115 @@ void mDebuggerRunFrame(struct mDebugger* debugger) { } void mDebuggerEnter(struct mDebugger* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) { - debugger->state = DEBUGGER_PAUSED; if (debugger->platform->entered) { debugger->platform->entered(debugger->platform, reason, info); } + + size_t i; + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); + if (info && info->target) { + // This check needs to be in the loop to make sure we don't + // accidentally enter a module that isn't registered. + // This is an error by the caller, but it's good to check for. + if (info->target != module) { + continue; + } + // Make this the last loop so we don't hit this one twice + i = mDebuggerModuleListSize(&debugger->modules) - 1; + } + module->isPaused = true; + if (module->entered) { + module->entered(module, reason, info); + } + } + #ifdef ENABLE_SCRIPTING if (debugger->bridge) { mScriptBridgeDebuggerEntered(debugger->bridge, reason, info); } #endif + + mDebuggerUpdatePaused(debugger); } -static void mDebuggerInit(void* cpu, struct mCPUComponent* component) { - struct mDebugger* debugger = (struct mDebugger*) component; - debugger->state = DEBUGGER_RUNNING; - debugger->platform->init(cpu, debugger->platform); - if (debugger->init) { - debugger->init(debugger); +void mDebuggerInterrupt(struct mDebugger* debugger) { + size_t i; + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); + if (module->interrupt) { + module->interrupt(module); + } } } -static void mDebuggerDeinit(struct mCPUComponent* component) { +void mDebuggerUpdatePaused(struct mDebugger* debugger) { + if (debugger->state == DEBUGGER_SHUTDOWN) { + return; + } + + size_t anyPaused = 0; + size_t anyCallback = 0; + size_t i; + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); + if (module->isPaused) { + ++anyPaused; + } + if (module->needsCallback) { + ++anyCallback; + } + } + if (anyPaused) { + debugger->state = DEBUGGER_PAUSED; + } else if (anyCallback) { + debugger->state = DEBUGGER_CALLBACK; + } else { + debugger->state = DEBUGGER_RUNNING; + } +} + +void mDebuggerShutdown(struct mDebugger* debugger) { + debugger->state = DEBUGGER_SHUTDOWN; +} + +bool mDebuggerIsShutdown(const struct mDebugger* debugger) { + return debugger->state == DEBUGGER_SHUTDOWN; +} + +void mDebuggerUpdate(struct mDebugger* debugger) { + size_t i; + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); + if (module->update) { + module->update(module); + } + } +} + +static void _mDebuggerInit(void* cpu, struct mCPUComponent* component) { struct mDebugger* debugger = (struct mDebugger*) component; - if (debugger->deinit) { - debugger->deinit(debugger); + debugger->state = DEBUGGER_RUNNING; + debugger->platform->init(cpu, debugger->platform); + + size_t i; + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); + if (module->init) { + module->init(module); + } + } +} + +static void _mDebuggerDeinit(struct mCPUComponent* component) { + struct mDebugger* debugger = (struct mDebugger*) component; + debugger->state = DEBUGGER_SHUTDOWN; + size_t i; + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); + if (module->deinit) { + module->deinit(module); + } } debugger->platform->deinit(debugger->platform); } @@ -161,3 +310,8 @@ bool mDebuggerLookupIdentifier(struct mDebugger* debugger, const char* name, int } return false; } + +void mDebuggerModuleSetNeedsCallback(struct mDebuggerModule* debugger) { + debugger->needsCallback = true; + mDebuggerUpdatePaused(debugger->p); +} diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index ca8c0d1ec..8a7d400ee 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -17,8 +17,6 @@ #define SIGTRAP 5 /* Win32 Signals do not include SIGTRAP */ #endif -#define SOCKET_TIMEOUT 50 - enum GDBError { GDB_NO_ERROR = 0x00, GDB_BAD_ARGUMENTS = 0x06, @@ -50,20 +48,30 @@ static const char* TARGET_XML = "" "" "" "" - "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" "" ""; static void _sendMessage(struct GDBStub* stub); -static void _gdbStubDeinit(struct mDebugger* debugger) { +static void _gdbStubDeinit(struct mDebuggerModule* debugger) { struct GDBStub* stub = (struct GDBStub*) debugger; if (!SOCKET_FAILED(stub->socket)) { GDBStubShutdown(stub); } } -static void _gdbStubEntered(struct mDebugger* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) { +static void _gdbStubEntered(struct mDebuggerModule* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) { struct GDBStub* stub = (struct GDBStub*) debugger; switch (reason) { case DEBUGGER_ENTER_MANUAL: @@ -118,27 +126,24 @@ static void _gdbStubEntered(struct mDebugger* debugger, enum mDebuggerEntryReaso _sendMessage(stub); } -static void _gdbStubPoll(struct mDebugger* debugger) { +static void _gdbStubPoll(struct mDebuggerModule* debugger) { struct GDBStub* stub = (struct GDBStub*) debugger; --stub->untilPoll; if (stub->untilPoll > 0) { return; } stub->untilPoll = GDB_STUB_INTERVAL; - stub->shouldBlock = false; - GDBStubUpdate(stub); + GDBStubUpdate(stub, 0); } -static void _gdbStubWait(struct mDebugger* debugger) { +static void _gdbStubWait(struct mDebuggerModule* debugger, int32_t timeoutMs) { struct GDBStub* stub = (struct GDBStub*) debugger; - stub->shouldBlock = true; - GDBStubUpdate(stub); + GDBStubUpdate(stub, timeoutMs); } -static void _gdbStubUpdate(struct mDebugger* debugger) { +static void _gdbStubUpdate(struct mDebuggerModule* debugger) { struct GDBStub* stub = (struct GDBStub*) debugger; - stub->shouldBlock = false; - GDBStubUpdate(stub); + GDBStubUpdate(stub, 0); } static void _ack(struct GDBStub* stub) { @@ -242,14 +247,15 @@ static void _writeHostInfo(struct GDBStub* stub) { } static void _continue(struct GDBStub* stub, const char* message) { - stub->d.state = DEBUGGER_CALLBACK; + mDebuggerModuleSetNeedsCallback(&stub->d); stub->untilPoll = GDB_STUB_INTERVAL; + stub->d.isPaused = false; // TODO: parse message UNUSED(message); } static void _step(struct GDBStub* stub, const char* message) { - stub->d.core->step(stub->d.core); + stub->d.p->core->step(stub->d.p->core); snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP); _sendMessage(stub); // TODO: parse message @@ -271,7 +277,7 @@ static void _writeMemoryBinary(struct GDBStub* stub, const char* message) { return; } - struct ARMCore* cpu = stub->d.core->cpu; + struct ARMCore* cpu = stub->d.p->core->cpu; for (i = 0; i < size; i++) { uint8_t byte = *readAddress; ++readAddress; @@ -305,7 +311,7 @@ static void _writeMemory(struct GDBStub* stub, const char* message) { return; } - struct ARMCore* cpu = stub->d.core->cpu; + struct ARMCore* cpu = stub->d.p->core->cpu; for (i = 0; i < size; ++i, readAddress += 2) { uint8_t byte = _hex2int(readAddress, 2); GBAPatch8(cpu, address + i, byte, 0); @@ -325,7 +331,7 @@ static void _readMemory(struct GDBStub* stub, const char* message) { _error(stub, GDB_BAD_ARGUMENTS); return; } - struct ARMCore* cpu = stub->d.core->cpu; + struct ARMCore* cpu = stub->d.p->core->cpu; int writeAddress = 0; for (i = 0; i < size; ++i, writeAddress += 2) { uint8_t byte = cpu->memory.load8(cpu, address + i, 0); @@ -336,7 +342,7 @@ static void _readMemory(struct GDBStub* stub, const char* message) { } static void _writeGPRs(struct GDBStub* stub, const char* message) { - struct ARMCore* cpu = stub->d.core->cpu; + struct ARMCore* cpu = stub->d.p->core->cpu; const char* readAddress = message; int r; @@ -355,7 +361,7 @@ static void _writeGPRs(struct GDBStub* stub, const char* message) { } static void _readGPRs(struct GDBStub* stub, const char* message) { - struct ARMCore* cpu = stub->d.core->cpu; + struct ARMCore* cpu = stub->d.p->core->cpu; UNUSED(message); int r; int i = 0; @@ -379,7 +385,7 @@ static void _readGPRs(struct GDBStub* stub, const char* message) { } static void _writeRegister(struct GDBStub* stub, const char* message) { - struct ARMCore* cpu = stub->d.core->cpu; + struct ARMCore* cpu = stub->d.p->core->cpu; const char* readAddress = message; unsigned i = 0; @@ -412,7 +418,7 @@ static void _writeRegister(struct GDBStub* stub, const char* message) { } static void _readRegister(struct GDBStub* stub, const char* message) { - struct ARMCore* cpu = stub->d.core->cpu; + struct ARMCore* cpu = stub->d.p->core->cpu; const char* readAddress = message; unsigned i = 0; uint32_t reg = _readHex(readAddress, &i); @@ -497,7 +503,7 @@ static void _generateMemoryMapXml(struct GDBStub* stub, char* memoryMap) { strncpy(memoryMap, "", 27); index += strlen(memoryMap); const struct mCoreMemoryBlock* blocks; - size_t nBlocks = stub->d.core->listMemoryBlocks(stub->d.core, &blocks); + size_t nBlocks = stub->d.p->core->listMemoryBlocks(stub->d.p->core, &blocks); size_t i; for (i = 0; i < nBlocks; ++i) { if (!(blocks[i].flags & mCORE_MEMORY_MAPPED)) { @@ -559,7 +565,11 @@ static void _processVReadCommand(struct GDBStub* stub, const char* message) { stub->outgoing[0] = '\0'; if (!strncmp("Attach", message, 6)) { strncpy(stub->outgoing, "1", GDB_STUB_MAX_LINE - 4); - mDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL, 0); + stub->d.isPaused = true; + struct mDebuggerEntryInfo info = { + .target = &stub->d + }; + mDebuggerEnter(stub->d.p, DEBUGGER_ENTER_MANUAL, &info); } _sendMessage(stub); } @@ -582,19 +592,19 @@ static void _setBreakpoint(struct GDBStub* stub, const char* message) { switch (message[0]) { case '0': case '1': - stub->d.platform->setBreakpoint(stub->d.platform, &breakpoint); + stub->d.p->platform->setBreakpoint(stub->d.p->platform, &stub->d, &breakpoint); break; case '2': watchpoint.type = stub->watchpointsBehavior == GDB_WATCHPOINT_OVERRIDE_LOGIC_ANY_WRITE ? WATCHPOINT_WRITE : WATCHPOINT_WRITE_CHANGE; - stub->d.platform->setWatchpoint(stub->d.platform, &watchpoint); + stub->d.p->platform->setWatchpoint(stub->d.p->platform, &stub->d, &watchpoint); break; case '3': watchpoint.type = WATCHPOINT_READ; - stub->d.platform->setWatchpoint(stub->d.platform, &watchpoint); + stub->d.p->platform->setWatchpoint(stub->d.p->platform, &stub->d, &watchpoint); break; case '4': watchpoint.type = WATCHPOINT_RW; - stub->d.platform->setWatchpoint(stub->d.platform, &watchpoint); + stub->d.p->platform->setWatchpoint(stub->d.p->platform, &stub->d, &watchpoint); break; default: stub->outgoing[0] = '\0'; @@ -617,12 +627,12 @@ static void _clearBreakpoint(struct GDBStub* stub, const char* message) { case '0': case '1': mBreakpointListInit(&breakpoints, 0); - stub->d.platform->listBreakpoints(stub->d.platform, &breakpoints); + stub->d.p->platform->listBreakpoints(stub->d.p->platform, &stub->d, &breakpoints); for (index = 0; index < mBreakpointListSize(&breakpoints); ++index) { if (mBreakpointListGetPointer(&breakpoints, index)->address != address) { continue; } - stub->d.platform->clearBreakpoint(stub->d.platform, mBreakpointListGetPointer(&breakpoints, index)->id); + stub->d.p->platform->clearBreakpoint(stub->d.p->platform, mBreakpointListGetPointer(&breakpoints, index)->id); } mBreakpointListDeinit(&breakpoints); break; @@ -630,13 +640,13 @@ static void _clearBreakpoint(struct GDBStub* stub, const char* message) { case '3': case '4': mWatchpointListInit(&watchpoints, 0); - stub->d.platform->listWatchpoints(stub->d.platform, &watchpoints); + stub->d.p->platform->listWatchpoints(stub->d.p->platform, &stub->d, &watchpoints); for (index = 0; index < mWatchpointListSize(&watchpoints); ++index) { struct mWatchpoint* watchpoint = mWatchpointListGetPointer(&watchpoints, index); if (address >= watchpoint->minAddress && address < watchpoint->maxAddress) { continue; } - stub->d.platform->clearBreakpoint(stub->d.platform, watchpoint->id); + stub->d.p->platform->clearBreakpoint(stub->d.p->platform, watchpoint->id); } mWatchpointListDeinit(&watchpoints); break; @@ -650,6 +660,9 @@ static void _clearBreakpoint(struct GDBStub* stub, const char* message) { size_t _parseGDBMessage(struct GDBStub* stub, const char* message) { uint8_t checksum = 0; int parsed = 1; + struct mDebuggerEntryInfo info = { + .target = &stub->d + }; switch (*message) { case '+': stub->lineAck = GDB_ACK_RECEIVED; @@ -661,7 +674,8 @@ size_t _parseGDBMessage(struct GDBStub* stub, const char* message) { ++message; break; case '\x03': - mDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL, 0); + stub->d.isPaused = true; + mDebuggerEnter(stub->d.p, DEBUGGER_ENTER_MANUAL, &info); return parsed; default: _nak(stub); @@ -773,7 +787,6 @@ void GDBStubCreate(struct GDBStub* stub) { stub->d.type = DEBUGGER_GDB; stub->untilPoll = GDB_STUB_INTERVAL; stub->lineAck = GDB_ACK_PENDING; - stub->shouldBlock = false; } bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAddress, enum GDBWatchpointsBehvaior watchpointsBehavior) { @@ -810,9 +823,9 @@ void GDBStubHangup(struct GDBStub* stub) { SocketClose(stub->connection); stub->connection = INVALID_SOCKET; } - if (stub->d.state == DEBUGGER_PAUSED) { - stub->d.state = DEBUGGER_RUNNING; - } + stub->d.needsCallback = false; + stub->d.isPaused = false; + mDebuggerUpdatePaused(stub->d.p); } void GDBStubShutdown(struct GDBStub* stub) { @@ -823,54 +836,57 @@ void GDBStubShutdown(struct GDBStub* stub) { } } -void GDBStubUpdate(struct GDBStub* stub) { +bool GDBStubUpdate(struct GDBStub* stub, int32_t timeoutMs) { if (stub->socket == INVALID_SOCKET) { - if (stub->d.state == DEBUGGER_PAUSED) { - stub->d.state = DEBUGGER_RUNNING; - } - return; + stub->d.needsCallback = false; + stub->d.isPaused = false; + mDebuggerUpdatePaused(stub->d.p); + return false; } if (stub->connection == INVALID_SOCKET) { - if (stub->shouldBlock) { + if (timeoutMs) { Socket reads = stub->socket; - SocketPoll(1, &reads, 0, 0, SOCKET_TIMEOUT); + SocketPoll(1, &reads, 0, 0, timeoutMs); } stub->connection = SocketAccept(stub->socket, 0); if (!SOCKET_FAILED(stub->connection)) { if (!SocketSetBlocking(stub->connection, false)) { goto connectionLost; } - mDebuggerEnter(&stub->d, DEBUGGER_ENTER_ATTACHED, 0); + mDebuggerEnter(stub->d.p, DEBUGGER_ENTER_ATTACHED, 0); } else if (SocketWouldBlock()) { - return; + return false; } else { goto connectionLost; } + SocketSetTCPPush(stub->connection, 1); } - while (true) { - if (stub->shouldBlock) { - Socket reads = stub->connection; - SocketPoll(1, &reads, 0, 0, SOCKET_TIMEOUT); - } - ssize_t messageLen = SocketRecv(stub->connection, stub->line, GDB_STUB_MAX_LINE - 1); - if (messageLen == 0) { - goto connectionLost; - } - if (messageLen == -1) { - if (SocketWouldBlock()) { - return; - } - goto connectionLost; - } - stub->line[messageLen] = '\0'; - mLOG(DEBUGGER, DEBUG, "< %s", stub->line); - ssize_t position = 0; - while (position < messageLen) { - position += _parseGDBMessage(stub, &stub->line[position]); - } + + if (timeoutMs) { + Socket reads = stub->connection; + SocketPoll(1, &reads, 0, 0, timeoutMs); } + ssize_t messageLen = SocketRecv(stub->connection, stub->line, GDB_STUB_MAX_LINE - 1); + if (messageLen == 0) { + goto connectionLost; + } + if (messageLen == -1) { + if (SocketWouldBlock()) { + return false; + } + goto connectionLost; + } + + stub->line[messageLen] = '\0'; + mLOG(DEBUGGER, DEBUG, "< %s", stub->line); + ssize_t position = 0; + while (position < messageLen) { + position += _parseGDBMessage(stub, &stub->line[position]); + } + return true; connectionLost: mLOG(DEBUGGER, WARN, "Connection lost"); GDBStubHangup(stub); + return false; } diff --git a/src/feature/CMakeLists.txt b/src/feature/CMakeLists.txt index ffb469953..b692338f4 100644 --- a/src/feature/CMakeLists.txt +++ b/src/feature/CMakeLists.txt @@ -1,8 +1,10 @@ include(ExportDirectory) set(SOURCE_FILES commandline.c + proxy-backend.c thread-proxy.c updater.c + video-backend.c video-logger.c) set(GUI_FILES diff --git a/src/feature/commandline.c b/src/feature/commandline.c index 72e5f6c26..e529a2eb3 100644 --- a/src/feature/commandline.c +++ b/src/feature/commandline.c @@ -137,18 +137,14 @@ bool mArgumentsParse(struct mArguments* args, int argc, char* const* argv, struc break; #ifdef USE_EDITLINE case 'd': - if (args->debuggerType != DEBUGGER_NONE) { - return false; - } - args->debuggerType = DEBUGGER_CLI; + args->debugAtStart = true; + args->debugCli = true; break; #endif #ifdef USE_GDB_STUB case 'g': - if (args->debuggerType != DEBUGGER_NONE) { - return false; - } - args->debuggerType = DEBUGGER_GDB; + args->debugAtStart = true; + args->debugGdb = true; break; #endif case 'h': diff --git a/src/feature/editline/cli-el-backend.c b/src/feature/editline/cli-el-backend.c index 30cb9399f..c76bb5d67 100644 --- a/src/feature/editline/cli-el-backend.c +++ b/src/feature/editline/cli-el-backend.c @@ -7,12 +7,31 @@ #include #include +#include #include #include +struct CLIDebuggerEditLineBackend { + struct CLIDebuggerBackend d; + + EditLine* elstate; + History* histate; + + int count; + const char* prompt; + bool doPrompt; + Thread promptThread; + Mutex promptMutex; + Condition promptRead; + Condition promptWrite; + bool exitThread; +}; + static struct CLIDebugger* _activeDebugger; +static THREAD_ENTRY _promptThread(void*); + static char* _prompt(EditLine* el) { UNUSED(el); return "> "; @@ -20,7 +39,10 @@ static char* _prompt(EditLine* el) { static void _breakIntoDefault(int signal) { UNUSED(signal); - mDebuggerEnter(&_activeDebugger->d, DEBUGGER_ENTER_MANUAL, 0); + struct mDebuggerEntryInfo info = { + .target = &_activeDebugger->d + }; + mDebuggerEnter(_activeDebugger->d.p, DEBUGGER_ENTER_MANUAL, &info); } static unsigned char _tabComplete(EditLine* elstate, int ch) { @@ -40,7 +62,7 @@ static unsigned char _tabComplete(EditLine* elstate, int ch) { } ATTRIBUTE_FORMAT(printf, 2, 3) -void _CLIDebuggerEditLinePrintf(struct CLIDebuggerBackend* be, const char* fmt, ...) { +static void CLIDebuggerEditLinePrintf(struct CLIDebuggerBackend* be, const char* fmt, ...) { UNUSED(be); va_list args; va_start(args, fmt); @@ -48,7 +70,7 @@ void _CLIDebuggerEditLinePrintf(struct CLIDebuggerBackend* be, const char* fmt, va_end(args); } -void _CLIDebuggerEditLineInit(struct CLIDebuggerBackend* be) { +static void CLIDebuggerEditLineInit(struct CLIDebuggerBackend* be) { struct CLIDebuggerEditLineBackend* elbe = (struct CLIDebuggerEditLineBackend*) be; // TODO: get argv[0] elbe->elstate = el_init(binaryName, stdin, stdout, stderr); @@ -78,12 +100,26 @@ void _CLIDebuggerEditLineInit(struct CLIDebuggerBackend* be) { } } + MutexInit(&elbe->promptMutex); + ConditionInit(&elbe->promptRead); + ConditionInit(&elbe->promptWrite); + elbe->prompt = NULL; + elbe->exitThread = false; + elbe->doPrompt = false; + ThreadCreate(&elbe->promptThread, _promptThread, elbe); + _activeDebugger = be->p; signal(SIGINT, _breakIntoDefault); } -void _CLIDebuggerEditLineDeinit(struct CLIDebuggerBackend* be) { +static void CLIDebuggerEditLineDeinit(struct CLIDebuggerBackend* be) { struct CLIDebuggerEditLineBackend* elbe = (struct CLIDebuggerEditLineBackend*) be; + MutexLock(&elbe->promptMutex); + elbe->exitThread = true; + ConditionWake(&elbe->promptWrite); + MutexUnlock(&elbe->promptMutex); + ThreadJoin(&elbe->promptThread); + char path[PATH_MAX + 1]; mCoreConfigDirectory(path, PATH_MAX); if (path[0]) { @@ -108,11 +144,52 @@ void _CLIDebuggerEditLineDeinit(struct CLIDebuggerBackend* be) { free(elbe); } -const char* _CLIDebuggerEditLineReadLine(struct CLIDebuggerBackend* be, size_t* len) { +static THREAD_ENTRY _promptThread(void* context) { + struct CLIDebuggerEditLineBackend* elbe = context; + + MutexLock(&elbe->promptMutex); + while (!elbe->exitThread) { + if (elbe->doPrompt) { + MutexUnlock(&elbe->promptMutex); + elbe->prompt = el_gets(elbe->elstate, &elbe->count); + MutexLock(&elbe->promptMutex); + elbe->doPrompt = false; + ConditionWake(&elbe->promptRead); + } + ConditionWait(&elbe->promptWrite, &elbe->promptMutex); + } + MutexUnlock(&elbe->promptMutex); + THREAD_EXIT(0); +} + +static int CLIDebuggerEditLinePoll(struct CLIDebuggerBackend* be, int32_t timeoutMs) { + struct CLIDebuggerEditLineBackend* elbe = (struct CLIDebuggerEditLineBackend*) be; + int gotPrompt = 0; + MutexLock(&elbe->promptMutex); + if (!elbe->prompt) { + elbe->doPrompt = true; + ConditionWake(&elbe->promptWrite); + ConditionWaitTimed(&elbe->promptRead, &elbe->promptMutex, timeoutMs); + } + if (elbe->prompt) { + gotPrompt = 1; + } + MutexUnlock(&elbe->promptMutex); + + return gotPrompt; +} + +static const char* CLIDebuggerEditLineReadLine(struct CLIDebuggerBackend* be, size_t* len) { struct CLIDebuggerEditLineBackend* elbe = (struct CLIDebuggerEditLineBackend*) be; - int count; *len = 0; - const char* line = el_gets(elbe->elstate, &count); + if (CLIDebuggerEditLinePoll(be, -1) != 1) { + return NULL; + } + MutexLock(&elbe->promptMutex); + int count = elbe->count; + const char* line = elbe->prompt; + elbe->prompt = NULL; + MutexUnlock(&elbe->promptMutex); if (line) { if (count > 1) { // Crop off newline @@ -123,12 +200,13 @@ const char* _CLIDebuggerEditLineReadLine(struct CLIDebuggerBackend* be, size_t* } return line; } -void _CLIDebuggerEditLineLineAppend(struct CLIDebuggerBackend* be, const char* line) { + +static void CLIDebuggerEditLineLineAppend(struct CLIDebuggerBackend* be, const char* line) { struct CLIDebuggerEditLineBackend* elbe = (struct CLIDebuggerEditLineBackend*) be; el_insertstr(elbe->elstate, line); } -const char* _CLIDebuggerEditLineHistoryLast(struct CLIDebuggerBackend* be, size_t* len) { +static const char* CLIDebuggerEditLineHistoryLast(struct CLIDebuggerBackend* be, size_t* len) { struct CLIDebuggerEditLineBackend* elbe = (struct CLIDebuggerEditLineBackend*) be; HistEvent ev; if (history(elbe->histate, &ev, H_FIRST) < 0) { @@ -145,7 +223,7 @@ const char* _CLIDebuggerEditLineHistoryLast(struct CLIDebuggerBackend* be, size_ return ev.str; } -void _CLIDebuggerEditLineHistoryAppend(struct CLIDebuggerBackend* be, const char* line) { +static void CLIDebuggerEditLineHistoryAppend(struct CLIDebuggerBackend* be, const char* line) { struct CLIDebuggerEditLineBackend* elbe = (struct CLIDebuggerEditLineBackend*) be; HistEvent ev; history(elbe->histate, &ev, H_ENTER, line); @@ -153,13 +231,14 @@ void _CLIDebuggerEditLineHistoryAppend(struct CLIDebuggerBackend* be, const char struct CLIDebuggerBackend* CLIDebuggerEditLineBackendCreate(void) { struct CLIDebuggerEditLineBackend* elbe = calloc(1, sizeof(*elbe)); - elbe->d.printf = _CLIDebuggerEditLinePrintf; - elbe->d.init = _CLIDebuggerEditLineInit; - elbe->d.deinit = _CLIDebuggerEditLineDeinit; - elbe->d.readline = _CLIDebuggerEditLineReadLine; - elbe->d.lineAppend = _CLIDebuggerEditLineLineAppend; - elbe->d.historyLast = _CLIDebuggerEditLineHistoryLast; - elbe->d.historyAppend = _CLIDebuggerEditLineHistoryAppend; + elbe->d.printf = CLIDebuggerEditLinePrintf; + elbe->d.init = CLIDebuggerEditLineInit; + elbe->d.deinit = CLIDebuggerEditLineDeinit; + elbe->d.poll = CLIDebuggerEditLinePoll; + elbe->d.readline = CLIDebuggerEditLineReadLine; + elbe->d.lineAppend = CLIDebuggerEditLineLineAppend; + elbe->d.historyLast = CLIDebuggerEditLineHistoryLast; + elbe->d.historyAppend = CLIDebuggerEditLineHistoryAppend; elbe->d.interrupt = NULL; return &elbe->d; } diff --git a/src/feature/editline/cli-el-backend.h b/src/feature/editline/cli-el-backend.h index 89ceec641..312798a38 100644 --- a/src/feature/editline/cli-el-backend.h +++ b/src/feature/editline/cli-el-backend.h @@ -14,13 +14,6 @@ CXX_GUARD_START #include -struct CLIDebuggerEditLineBackend { - struct CLIDebuggerBackend d; - - EditLine* elstate; - History* histate; -}; - struct CLIDebuggerBackend* CLIDebuggerEditLineBackendCreate(void); CXX_GUARD_END diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index f1a26ec32..090614d1a 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -62,13 +62,13 @@ void FFmpegEncoderInit(struct FFmpegEncoder* encoder) { encoder->audioCodec = NULL; encoder->videoCodec = NULL; encoder->containerFormat = NULL; + encoder->isampleRate = PREFERRED_SAMPLE_RATE; FFmpegEncoderSetAudio(encoder, "flac", 0); FFmpegEncoderSetVideo(encoder, "libx264", 0, 0); FFmpegEncoderSetContainer(encoder, "matroska"); FFmpegEncoderSetDimensions(encoder, GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); encoder->iwidth = GBA_VIDEO_HORIZONTAL_PIXELS; encoder->iheight = GBA_VIDEO_VERTICAL_PIXELS; - encoder->isampleRate = PREFERRED_SAMPLE_RATE; encoder->frameskip = 1; encoder->skipResidue = 0; encoder->loop = false; @@ -154,19 +154,35 @@ bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, un } encoder->sampleRate = encoder->isampleRate; if (codec->supported_samplerates) { + bool gotSampleRate = false; + int highestSampleRate = 0; for (i = 0; codec->supported_samplerates[i]; ++i) { + if (codec->supported_samplerates[i] > highestSampleRate) { + highestSampleRate = codec->supported_samplerates[i]; + } if (codec->supported_samplerates[i] < encoder->isampleRate) { continue; } - if (encoder->sampleRate == encoder->isampleRate || encoder->sampleRate > codec->supported_samplerates[i]) { + if (!gotSampleRate || encoder->sampleRate > codec->supported_samplerates[i]) { encoder->sampleRate = codec->supported_samplerates[i]; + gotSampleRate = true; } } + if (!gotSampleRate) { + // There are no available sample rates that are higher than the input sample rate + // Let's use the highest available instead + encoder->sampleRate = highestSampleRate; + } } else if (codec->id == AV_CODEC_ID_FLAC) { // HACK: FLAC doesn't support > 65535Hz unless it's divisible by 10 if (encoder->sampleRate >= 65535) { encoder->sampleRate -= encoder->isampleRate % 10; } + } else if (codec->id == AV_CODEC_ID_VORBIS) { + // HACK: FLAC doesn't support > 48000Hz but doesn't tell us + if (encoder->sampleRate > 48000) { + encoder->sampleRate = 48000; + } } else if (codec->id == AV_CODEC_ID_AAC) { // HACK: AAC doesn't support 32768Hz (it rounds to 32000), but libfaac doesn't tell us that encoder->sampleRate = 48000; @@ -881,7 +897,7 @@ void FFmpegEncoderSetInputSampleRate(struct FFmpegEncoder* encoder, int sampleRa } void _ffmpegOpenResampleContext(struct FFmpegEncoder* encoder) { - encoder->audioBufferSize = av_rescale_q(encoder->audioFrame->nb_samples, (AVRational) { 4, encoder->sampleRate }, (AVRational) { 1, encoder->isampleRate }); + encoder->audioBufferSize = av_rescale_q(encoder->audioFrame->nb_samples, (AVRational) { 1, encoder->sampleRate }, (AVRational) { 1, encoder->isampleRate }) * 4; encoder->audioBuffer = av_malloc(encoder->audioBufferSize); #ifdef USE_LIBAVRESAMPLE encoder->resampleContext = avresample_alloc_context(); diff --git a/src/feature/gui/cheats.c b/src/feature/gui/cheats.c index fda129a5b..9c3199631 100644 --- a/src/feature/gui/cheats.c +++ b/src/feature/gui/cheats.c @@ -9,6 +9,7 @@ #include #include "feature/gui/gui-runner.h" #include +#include enum mGUICheatAction { CHEAT_BACK = 0, diff --git a/src/feature/gui/gui-config.c b/src/feature/gui/gui-config.c index 8ab4dd7dc..4a6c79871 100644 --- a/src/feature/gui/gui-config.c +++ b/src/feature/gui/gui-config.c @@ -31,7 +31,7 @@ static bool _biosNamed(const char* name) { char ext[PATH_MAX + 1] = {}; separatePath(name, NULL, NULL, ext); - if (strstr(name, "bios")) { + if (strcasestr(name, "bios")) { return true; } if (!strncmp(ext, "bin", PATH_MAX)) { diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 33146ef88..267b0e0b0 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -15,8 +15,8 @@ #include #include #include +#include #include -#include #include #ifdef PSP2 @@ -456,6 +456,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { mLOG(GUI_RUNNER, DEBUG, "Loading save..."); mCoreAutoloadSave(runner->core); mCoreAutoloadCheats(runner->core); + mCoreAutoloadPatch(runner->core); if (runner->setup) { mLOG(GUI_RUNNER, DEBUG, "Setting up runner..."); runner->setup(runner); @@ -655,6 +656,9 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { runner->core->reset(runner->core); break; case RUNNER_SAVE_STATE: + // If we are saving state, then the screenshot stored for the state previously should no longer be considered up-to-date. + // Therefore, mark it as stale so that at draw time we load the new save state's screenshot. + ((struct mGUIBackground*) stateSaveMenu.background)->screenshotId |= SCREENSHOT_INVALID; mCoreSaveState(runner->core, item->data.v.u >> 16, SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA); break; case RUNNER_LOAD_STATE: @@ -840,5 +844,6 @@ THREAD_ENTRY mGUIAutosaveThread(void* context) { } } MutexUnlock(&autosave->mutex); + THREAD_EXIT(0); } #endif diff --git a/src/feature/proxy-backend.c b/src/feature/proxy-backend.c new file mode 100644 index 000000000..fb6e555ed --- /dev/null +++ b/src/feature/proxy-backend.c @@ -0,0 +1,284 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +static void _mVideoProxyBackendInit(struct VideoBackend* v, WHandle handle) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_INIT, + .handle = handle + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendDeinit(struct VideoBackend* v) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_DEINIT, + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct mRectangle* dims) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_SET_LAYER_DIMENSIONS, + .layer = layer, + .data = { + .dims = *dims + } + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct mRectangle* dims) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_LAYER_DIMENSIONS, + .layer = layer, + }; + union mVideoBackendCommandData out; + mVideoProxyBackendSubmit(proxy, &cmd, &out); + memcpy(dims, &out.dims, sizeof(*dims)); +} + +static void _mVideoProxyBackendSwap(struct VideoBackend* v) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_SWAP, + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendClear(struct VideoBackend* v) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_CLEAR, + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendContextResized(struct VideoBackend* v, unsigned w, unsigned h) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_CONTEXT_RESIZED, + .data = { + .u = { + .width = w, + .height = h, + } + } + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendSetImageSize(struct VideoBackend* v, enum VideoLayer layer, int w, int h) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_SET_IMAGE_SIZE, + .layer = layer, + .data = { + .s = { + .width = w, + .height = h, + } + } + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendImageSize(struct VideoBackend* v, enum VideoLayer layer, int* w, int* h) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_IMAGE_SIZE, + .layer = layer, + }; + union mVideoBackendCommandData out; + mVideoProxyBackendSubmit(proxy, &cmd, &out); + *w = out.s.width; + *h = out.s.height; +} + +static void _mVideoProxyBackendSetImage(struct VideoBackend* v, enum VideoLayer layer, const void* frame) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_SET_IMAGE, + .layer = layer, + .data = { + .image = frame + } + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendDrawFrame(struct VideoBackend* v) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_DRAW_FRAME, + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static bool mVideoProxyBackendReadIn(struct mVideoProxyBackend* proxy, struct mVideoBackendCommand* cmd, bool block); +static void mVideoProxyBackendWriteOut(struct mVideoProxyBackend* proxy, const union mVideoBackendCommandData* out); + +void mVideoProxyBackendInit(struct mVideoProxyBackend* proxy, struct VideoBackend* backend) { + proxy->d.init = _mVideoProxyBackendInit; + proxy->d.deinit = _mVideoProxyBackendDeinit; + proxy->d.setLayerDimensions = _mVideoProxyBackendSetLayerDimensions; + proxy->d.layerDimensions = _mVideoProxyBackendLayerDimensions; + proxy->d.swap = _mVideoProxyBackendSwap; + proxy->d.clear = _mVideoProxyBackendClear; + proxy->d.contextResized = _mVideoProxyBackendContextResized; + proxy->d.setImageSize = _mVideoProxyBackendSetImageSize; + proxy->d.imageSize = _mVideoProxyBackendImageSize; + proxy->d.setImage = _mVideoProxyBackendSetImage; + proxy->d.drawFrame = _mVideoProxyBackendDrawFrame; + proxy->backend = backend; + + RingFIFOInit(&proxy->in, 0x400); + RingFIFOInit(&proxy->out, 0x400); + MutexInit(&proxy->inLock); + MutexInit(&proxy->outLock); + ConditionInit(&proxy->inWait); + ConditionInit(&proxy->outWait); + + proxy->wakeupCb = NULL; + proxy->context = NULL; +} + +void mVideoProxyBackendDeinit(struct mVideoProxyBackend* proxy) { + ConditionDeinit(&proxy->inWait); + ConditionDeinit(&proxy->outWait); + MutexDeinit(&proxy->inLock); + MutexDeinit(&proxy->outLock); + RingFIFODeinit(&proxy->in); + RingFIFODeinit(&proxy->out); +} + +void mVideoProxyBackendSubmit(struct mVideoProxyBackend* proxy, const struct mVideoBackendCommand* cmd, union mVideoBackendCommandData* out) { + MutexLock(&proxy->inLock); + while (!RingFIFOWrite(&proxy->in, cmd, sizeof(*cmd))) { + mLOG(VIDEO, DEBUG, "Can't write command. Proxy thread asleep?"); + ConditionWait(&proxy->inWait, &proxy->inLock); + } + MutexUnlock(&proxy->inLock); + if (proxy->wakeupCb) { + proxy->wakeupCb(proxy, proxy->context); + } + + if (!mVideoProxyBackendCommandIsBlocking(cmd->cmd)) { + return; + } + + MutexLock(&proxy->outLock); + while (!RingFIFORead(&proxy->out, out, sizeof(*out))) { + ConditionWait(&proxy->outWait, &proxy->outLock); + } + MutexUnlock(&proxy->outLock); +} + +bool mVideoProxyBackendRun(struct mVideoProxyBackend* proxy, bool block) { + bool ok = false; + do { + struct mVideoBackendCommand cmd; + union mVideoBackendCommandData out; + if (mVideoProxyBackendReadIn(proxy, &cmd, block)) { + switch (cmd.cmd) { + case mVB_CMD_DUMMY: + break; + case mVB_CMD_INIT: + proxy->backend->init(proxy->backend, cmd.handle); + break; + case mVB_CMD_DEINIT: + proxy->backend->deinit(proxy->backend); + break; + case mVB_CMD_SET_LAYER_DIMENSIONS: + proxy->backend->setLayerDimensions(proxy->backend, cmd.layer, &cmd.data.dims); + break; + case mVB_CMD_LAYER_DIMENSIONS: + proxy->backend->layerDimensions(proxy->backend, cmd.layer, &out.dims); + break; + case mVB_CMD_SWAP: + proxy->backend->swap(proxy->backend); + break; + case mVB_CMD_CLEAR: + proxy->backend->clear(proxy->backend); + break; + case mVB_CMD_CONTEXT_RESIZED: + proxy->backend->contextResized(proxy->backend, cmd.data.u.width, cmd.data.u.height); + break; + case mVB_CMD_SET_IMAGE_SIZE: + proxy->backend->setImageSize(proxy->backend, cmd.layer, cmd.data.s.width, cmd.data.s.height); + break; + case mVB_CMD_IMAGE_SIZE: + proxy->backend->imageSize(proxy->backend, cmd.layer, &out.s.width, &out.s.height); + break; + case mVB_CMD_SET_IMAGE: + proxy->backend->setImage(proxy->backend, cmd.layer, cmd.data.image); + break; + case mVB_CMD_DRAW_FRAME: + proxy->backend->drawFrame(proxy->backend); + break; + } + if (mVideoProxyBackendCommandIsBlocking(cmd.cmd)) { + mVideoProxyBackendWriteOut(proxy, &out); + } + ok = true; + } + } while (block); + return ok; +} + +bool mVideoProxyBackendReadIn(struct mVideoProxyBackend* proxy, struct mVideoBackendCommand* cmd, bool block) { + bool gotCmd = false; + MutexLock(&proxy->inLock); + do { + gotCmd = RingFIFORead(&proxy->in, cmd, sizeof(*cmd)); + ConditionWake(&proxy->inWait); + // TODO: interlock? + if (block && !gotCmd) { + mLOG(VIDEO, DEBUG, "Can't read command. Runner thread asleep?"); + } + } while (block && !gotCmd); + MutexUnlock(&proxy->inLock); + return gotCmd; +} + +void mVideoProxyBackendWriteOut(struct mVideoProxyBackend* proxy, const union mVideoBackendCommandData* out) { + bool gotReply = false; + MutexLock(&proxy->outLock); + while (!gotReply) { + gotReply = RingFIFOWrite(&proxy->out, out, sizeof(*out)); + ConditionWake(&proxy->outWait); + // TOOD: interlock? + if (!gotReply) { + mLOG(VIDEO, DEBUG, "Can't write reply. Runner thread asleep?"); + } + } + MutexUnlock(&proxy->outLock); +} + +bool mVideoProxyBackendCommandIsBlocking(enum mVideoBackendCommandType cmd) { + switch (cmd) { + case mVB_CMD_DUMMY: + case mVB_CMD_CONTEXT_RESIZED: + case mVB_CMD_SET_LAYER_DIMENSIONS: + case mVB_CMD_CLEAR: + case mVB_CMD_SET_IMAGE_SIZE: + case mVB_CMD_DRAW_FRAME: + return false; + case mVB_CMD_INIT: + case mVB_CMD_DEINIT: + case mVB_CMD_LAYER_DIMENSIONS: + case mVB_CMD_SWAP: + case mVB_CMD_IMAGE_SIZE: + case mVB_CMD_SET_IMAGE: + return true; + } + + return true; +} diff --git a/src/feature/sqlite3/no-intro.c b/src/feature/sqlite3/no-intro.c index d54e188ff..649364de6 100644 --- a/src/feature/sqlite3/no-intro.c +++ b/src/feature/sqlite3/no-intro.c @@ -17,7 +17,7 @@ struct NoIntroDB { }; struct NoIntroDB* NoIntroDBLoad(const char* path) { - struct NoIntroDB* db = malloc(sizeof(*db)); + struct NoIntroDB* db = calloc(1, sizeof(*db)); if (sqlite3_open_v2(path, &db->db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL)) { goto error; @@ -60,9 +60,6 @@ struct NoIntroDB* NoIntroDBLoad(const char* path) { return db; error: - if (db->crc32) { - sqlite3_finalize(db->crc32); - } NoIntroDBDestroy(db); return NULL; @@ -285,8 +282,12 @@ bool NoIntroDBLoadClrMamePro(struct NoIntroDB* db, struct VFile* vf) { } void NoIntroDBDestroy(struct NoIntroDB* db) { - sqlite3_finalize(db->crc32); - sqlite3_close(db->db); + if (db->crc32) { + sqlite3_finalize(db->crc32); + } + if (db->db) { + sqlite3_close(db->db); + } free(db); } diff --git a/src/feature/thread-proxy.c b/src/feature/thread-proxy.c index d2956ed95..c3878e896 100644 --- a/src/feature/thread-proxy.c +++ b/src/feature/thread-proxy.c @@ -207,7 +207,7 @@ static THREAD_ENTRY _proxyThread(void* logger) { } } MutexUnlock(&proxyRenderer->mutex); - return 0; + THREAD_EXIT(0); } #endif diff --git a/src/feature/updater-main.c b/src/feature/updater-main.c index bc29e5cd4..66582a431 100644 --- a/src/feature/updater-main.c +++ b/src/feature/updater-main.c @@ -21,6 +21,9 @@ #include #define mkdir(X, Y) _mkdir(X) +#ifndef S_ISDIR +#define S_ISDIR(MODE) (((MODE) & _S_IFMT) == _S_IFDIR) +#endif #elif defined(_POSIX_C_SOURCE) #include #endif @@ -31,6 +34,39 @@ FILE* logfile; +bool rmdirRecursive(struct VDir* dir) { + if (!dir) { + return false; + } + bool ok = true; + struct VDirEntry* vde; + while ((vde = dir->listNext(dir))) { + const char* name = vde->name(vde); + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { + continue; + } + switch (vde->type(vde)) { + case VFS_DIRECTORY: + fprintf(logfile, "cd %s\n", name); + if (!rmdirRecursive(dir->openDir(dir, name))) { + ok = false; + } + fprintf(logfile, "cd ..\n"); + // Fall through + case VFS_FILE: + case VFS_UNKNOWN: + fprintf(logfile, "rm %s\n", name); + if (!dir->deleteFile(dir, name)) { + fprintf(logfile, "error\n"); + ok = false; + } + break; + } + } + dir->close(dir); + return ok; +} + bool extractArchive(struct VDir* archive, const char* root, bool prefix) { char path[PATH_MAX] = {0}; struct VDirEntry* vde; @@ -56,12 +92,32 @@ bool extractArchive(struct VDir* archive, const char* root, bool prefix) { switch (vde->type(vde)) { case VFS_DIRECTORY: fprintf(logfile, "mkdir %s\n", fname); - if (mkdir(path, 0755) < 0 && errno != EEXIST) { - return false; + if (mkdir(path, 0755) < 0) { + bool redo = false; + if (errno == EEXIST) { + struct stat st; + if (stat(path, &st) >= 0 && !S_ISDIR(st.st_mode)) { +#ifdef _WIN32 + wchar_t wpath[MAX_PATH + 1]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH); + DeleteFileW(wpath); +#else + unlink(path); +#endif + if (mkdir(path, 0755) >= 0) { + redo = true; + } + } + } + if (!redo) { + fprintf(logfile, "error %i\n", errno); + return false; + } } if (!prefix) { struct VDir* subdir = archive->openDir(archive, fname); if (!subdir) { + fprintf(logfile, "error\n"); return false; } if (!extractArchive(subdir, path, false)) { @@ -76,13 +132,28 @@ bool extractArchive(struct VDir* archive, const char* root, bool prefix) { vfIn = archive->openFile(archive, vde->name(vde), O_RDONLY); errno = 0; vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); - if (!vfOut && errno == EACCES) { + if (!vfOut) { + if (errno == EACCES) { #ifdef _WIN32 - Sleep(1000); + Sleep(1000); #else - sleep(1); + sleep(1); #endif - vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); + vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); + } else if (errno == EISDIR) { + fprintf(logfile, "rm -r %s\n", path); + if (!rmdirRecursive(VDirOpen(path))) { + return false; + } +#ifdef _WIN32 + wchar_t wpath[MAX_PATH + 1]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH); + RemoveDirectoryW(wpath); +#else + rmdir(path); +#endif + vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); + } } if (!vfOut) { vfIn->close(vfIn); @@ -114,7 +185,7 @@ int main(int argc, char* argv[]) { int ok = 1; mCoreConfigDirectory(bin, sizeof(bin)); - strncat(bin, "/updater.log", sizeof(bin)); + strncat(bin, "/updater.log", sizeof(bin) - 1); logfile = fopen(bin, "w"); mCoreConfigInit(&config, "updater"); @@ -274,7 +345,7 @@ int main(int argc, char* argv[]) { const char* argv[] = { qbin, NULL }; _execv(bin, argv); #elif defined(_POSIX_C_SOURCE) || defined(__APPLE__) - const char* argv[] = { bin, NULL }; + char* const argv[] = { bin, NULL }; execv(bin, argv); #endif } diff --git a/src/feature/updater.c b/src/feature/updater.c index e9f47f440..e50ed0fa2 100644 --- a/src/feature/updater.c +++ b/src/feature/updater.c @@ -148,7 +148,7 @@ bool mUpdateLoad(const struct mCoreConfig* config, const char* prefix, struct mU snprintf(key, sizeof(key), "%s.path", prefix); update->path = mCoreConfigGetValue(config, key); snprintf(key, sizeof(key), "%s.size", prefix); - uint32_t size = 0; + unsigned size = 0; mCoreConfigGetUIntValue(config, key, &size); if (!update->path && !size) { return false; diff --git a/src/feature/video-backend.c b/src/feature/video-backend.c new file mode 100644 index 000000000..ae2f0257d --- /dev/null +++ b/src/feature/video-backend.c @@ -0,0 +1,25 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +mLOG_DEFINE_CATEGORY(VIDEO, "Video backend", "video"); + +void VideoBackendGetFrame(const struct VideoBackend* v, struct mRectangle* frame) { + memset(frame, 0, sizeof(*frame)); + int i; + for (i = 0; i < VIDEO_LAYER_MAX; ++i) { + struct mRectangle dims; + v->layerDimensions(v, i, &dims); + mRectangleUnion(frame, &dims); + } +} + +void VideoBackendGetFrameSize(const struct VideoBackend* v, unsigned* width, unsigned* height) { + struct mRectangle frame; + VideoBackendGetFrame(v, &frame); + *width = frame.width; + *height = frame.height; +} diff --git a/src/feature/video-logger.c b/src/feature/video-logger.c index ae2fa046e..fb2065b42 100644 --- a/src/feature/video-logger.c +++ b/src/feature/video-logger.c @@ -382,10 +382,7 @@ static void _copyVf(struct VFile* dest, struct VFile* src) { static void _compress(struct VFile* dest, struct VFile* src) { uint8_t writeBuffer[0x800]; uint8_t compressBuffer[0x400]; - z_stream zstr; - zstr.zalloc = Z_NULL; - zstr.zfree = Z_NULL; - zstr.opaque = Z_NULL; + z_stream zstr = {0}; zstr.avail_in = 0; zstr.avail_out = sizeof(compressBuffer); zstr.next_out = (Bytef*) compressBuffer; @@ -425,10 +422,7 @@ static void _compress(struct VFile* dest, struct VFile* src) { static bool _decompress(struct VFile* dest, struct VFile* src, size_t compressedLength) { uint8_t fbuffer[0x400]; uint8_t zbuffer[0x800]; - z_stream zstr; - zstr.zalloc = Z_NULL; - zstr.zfree = Z_NULL; - zstr.opaque = Z_NULL; + z_stream zstr = {0}; zstr.avail_in = 0; zstr.avail_out = sizeof(zbuffer); zstr.next_out = (Bytef*) zbuffer; diff --git a/src/gb/audio.c b/src/gb/audio.c index d2d6a9260..e9c014d05 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -100,22 +100,24 @@ void GBAudioReset(struct GBAudio* audio) { audio->ch3 = (struct GBAudioWaveChannel) { .bank = 0 }; audio->ch4 = (struct GBAudioNoiseChannel) { .nSamples = 0 }; // TODO: DMG randomness - audio->ch3.wavedata8[0] = 0x00; - audio->ch3.wavedata8[1] = 0xFF; - audio->ch3.wavedata8[2] = 0x00; - audio->ch3.wavedata8[3] = 0xFF; - audio->ch3.wavedata8[4] = 0x00; - audio->ch3.wavedata8[5] = 0xFF; - audio->ch3.wavedata8[6] = 0x00; - audio->ch3.wavedata8[7] = 0xFF; - audio->ch3.wavedata8[8] = 0x00; - audio->ch3.wavedata8[9] = 0xFF; - audio->ch3.wavedata8[10] = 0x00; - audio->ch3.wavedata8[11] = 0xFF; - audio->ch3.wavedata8[12] = 0x00; - audio->ch3.wavedata8[13] = 0xFF; - audio->ch3.wavedata8[14] = 0x00; - audio->ch3.wavedata8[15] = 0xFF; + if (audio->style != GB_AUDIO_GBA) { + audio->ch3.wavedata8[0] = 0x00; + audio->ch3.wavedata8[1] = 0xFF; + audio->ch3.wavedata8[2] = 0x00; + audio->ch3.wavedata8[3] = 0xFF; + audio->ch3.wavedata8[4] = 0x00; + audio->ch3.wavedata8[5] = 0xFF; + audio->ch3.wavedata8[6] = 0x00; + audio->ch3.wavedata8[7] = 0xFF; + audio->ch3.wavedata8[8] = 0x00; + audio->ch3.wavedata8[9] = 0xFF; + audio->ch3.wavedata8[10] = 0x00; + audio->ch3.wavedata8[11] = 0xFF; + audio->ch3.wavedata8[12] = 0x00; + audio->ch3.wavedata8[13] = 0xFF; + audio->ch3.wavedata8[14] = 0x00; + audio->ch3.wavedata8[15] = 0xFF; + } audio->ch4 = (struct GBAudioNoiseChannel) { .envelope = { .dead = 2 } }; audio->frame = 0; audio->sampleInterval = SAMPLE_INTERVAL * GB_MAX_SAMPLES; @@ -138,6 +140,9 @@ void GBAudioReset(struct GBAudio* audio) { } void GBAudioResizeBuffer(struct GBAudio* audio, size_t samples) { + if (samples > BLIP_BUFFER_SIZE / 2) { + samples = BLIP_BUFFER_SIZE / 2; + } mCoreSyncLockAudio(audio->p->sync); audio->samples = samples; blip_clear(audio->left); @@ -380,11 +385,7 @@ void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) { if (GBAudioRegisterNoiseControlIsRestart(value)) { audio->playingCh4 = _resetEnvelope(&audio->ch4.envelope); - if (audio->ch4.power) { - audio->ch4.lfsr = 0x7F; - } else { - audio->ch4.lfsr = 0x7FFF; - } + audio->ch4.lfsr = 0; if (!audio->ch4.length) { audio->ch4.length = 64; if (audio->ch4.stop && !(audio->frame & 1)) { @@ -503,7 +504,7 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) { GBAudioSample(audio, timestamp); } - if (audio->playingCh1 && (channels & 0x1) && audio->ch1.envelope.dead != 2) { + if ((channels & 0x1) && ((audio->playingCh1 && audio->ch1.envelope.dead != 2) || timestamp - audio->ch1.lastUpdate > 0x40000000 || (channels == 0x1))) { int period = 4 * (2048 - audio->ch1.control.frequency) * audio->timingFactor; int32_t diff = timestamp - audio->ch1.lastUpdate; if (diff >= period) { @@ -513,7 +514,7 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) { _updateSquareSample(&audio->ch1); } } - if (audio->playingCh2 && (channels & 0x2) && audio->ch2.envelope.dead != 2) { + if ((channels & 0x2) && ((audio->playingCh2 && audio->ch2.envelope.dead != 2) || timestamp - audio->ch2.lastUpdate > 0x40000000 || (channels == 0x2))) { int period = 4 * (2048 - audio->ch2.control.frequency) * audio->timingFactor; int32_t diff = timestamp - audio->ch2.lastUpdate; if (diff >= period) { @@ -596,24 +597,58 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) { } } if (audio->playingCh4 && (channels & 0x8)) { + const uint16_t noiseMaskTable[0x40] = { + 0x3f, 0x3e, 0x3c, 0x3d, 0x39, 0x38, 0x3a, 0x3b, + 0x33, 0x32, 0x30, 0x31, 0x35, 0x34, 0x36, 0x37, + 0x27, 0x26, 0x24, 0x25, 0x21, 0x20, 0x22, 0x23, + 0x2b, 0x2a, 0x28, 0x29, 0x2d, 0x2c, 0x2e, 0x2f, + 0x0f, 0x0e, 0x0c, 0x0d, 0x09, 0x08, 0x0a, 0x0b, + 0x03, 0x02, 0x00, 0x01, 0x05, 0x04, 0x06, 0x07, + 0x17, 0x16, 0x14, 0x15, 0x11, 0x10, 0x12, 0x13, + 0x1b, 0x1a, 0x18, 0x19, 0x1d, 0x1c, 0x1e, 0x1f + }; + const uint16_t noisePopulationTable[0x40] = { + 6, 5, 4, 5, 4, 3, 4, 5, 4, 3, 2, 3, 4, 3, 4, 5, + 4, 3, 2, 3, 2, 1, 2, 3, 4, 3, 2, 3, 4, 3, 4, 5, + 4, 3, 2, 3, 2, 1, 2, 3, 2, 1, 0, 1, 2, 1, 2, 3, + 4, 3, 2, 3, 2, 1, 2, 3, 4, 3, 2, 3, 4, 3, 4, 5 + }; int32_t cycles = audio->ch4.ratio ? 2 * audio->ch4.ratio : 1; cycles <<= audio->ch4.frequency; cycles *= 8 * audio->timingFactor; int32_t diff = timestamp - audio->ch4.lastEvent; if (diff >= cycles) { - int32_t last; + int32_t last = 0; int samples = 0; int positiveSamples = 0; int lsb; - int coeff = 0x60; - if (!audio->ch4.power) { - coeff <<= 8; + int coeff; + if (audio->ch4.power) { + // TODO: Can this be batched too? + coeff = 0x4040; + } else { + int bits = 0; + // Batch 5 steps at a time when possible + for (; last + cycles * 5 <= diff; last += cycles * 5) { + bits = audio->ch4.lfsr & 0x3F; + audio->ch4.lfsr >>= 5; + audio->ch4.lfsr |= 0x4000 * noiseMaskTable[bits] >> 4; + audio->ch4.lfsr &= 0x7FFF; + samples += 5; + positiveSamples += noisePopulationTable[bits]; + } + lsb = noiseMaskTable[bits] & 1; + coeff = 0x4000; } - for (last = 0; last + cycles <= diff; last += cycles) { - lsb = audio->ch4.lfsr & 1; + for (; last + cycles <= diff; last += cycles) { + lsb = (audio->ch4.lfsr ^ (audio->ch4.lfsr >> 1) ^ 1) & 1; audio->ch4.lfsr >>= 1; - audio->ch4.lfsr ^= lsb * coeff; + if (lsb) { + audio->ch4.lfsr |= coeff; + } else { + audio->ch4.lfsr &= ~coeff; + } ++samples; positiveSamples += lsb; } @@ -644,7 +679,9 @@ void GBAudioUpdateFrame(struct GBAudio* audio) { if (audio->ch1.sweep.enable) { --audio->ch1.sweep.step; if (audio->ch1.sweep.step == 0) { - audio->playingCh1 = _updateSweep(&audio->ch1, false); + if (!_updateSweep(&audio->ch1, false)) { + audio->playingCh1 = false; + } *audio->nr52 &= ~0x0001; *audio->nr52 |= audio->playingCh1; } @@ -1089,6 +1126,8 @@ void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGSt audio->ch4.lastEvent = currentTime + (when & (cycles - 1)) - cycles; } } + audio->ch4.nSamples = 0; + audio->ch4.samples = 0; } void GBAudioSerialize(const struct GBAudio* audio, struct GBSerializedState* state) { diff --git a/src/gb/core.c b/src/gb/core.c index 7edb1a607..ca7ba1593 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -60,6 +60,15 @@ static const struct mCoreMemoryBlock _GBCMemoryBlocks[] = { { GB_BASE_HRAM, "hram", "HRAM", "High RAM", GB_BASE_HRAM, GB_BASE_HRAM + GB_SIZE_HRAM, GB_SIZE_HRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, }; +static const struct mCoreScreenRegion _GBScreenRegions[] = { + { 0, "Screen", 0, 0, GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS } +}; + +static const struct mCoreScreenRegion _SGBScreenRegions[] = { + { 0, "Screen", (SGB_VIDEO_HORIZONTAL_PIXELS - GB_VIDEO_HORIZONTAL_PIXELS) / 2, (SGB_VIDEO_VERTICAL_PIXELS - GB_VIDEO_VERTICAL_PIXELS) / 2, GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS }, + { 1, "Border", 0, 0, SGB_VIDEO_HORIZONTAL_PIXELS, SGB_VIDEO_VERTICAL_PIXELS }, +}; + static const struct mCoreRegisterInfo _GBRegisters[] = { { "b", NULL, 1, 0xFF, mCORE_REGISTER_GPR }, { "c", NULL, 1, 0xFF, mCORE_REGISTER_GPR }, @@ -90,6 +99,8 @@ struct GBCore { uint8_t keys; struct mCPUComponent* components[CPU_COMPONENT_MAX]; const struct Configuration* overrides; + struct GBCartridgeOverride override; + bool hasOverride; struct mDebuggerPlatform* debuggerPlatform; struct mCheatDevice* cheatDevice; struct mCoreMemoryBlock memoryBlocks[8]; @@ -115,6 +126,8 @@ static bool _GBCoreInit(struct mCore* core) { gbcore->logContext = NULL; #endif memcpy(gbcore->memoryBlocks, _GBMemoryBlocks, sizeof(_GBMemoryBlocks)); + memset(&gbcore->override, 0, sizeof(gbcore->override)); + gbcore->hasOverride = false; GBCreate(gb); memset(gbcore->components, 0, sizeof(gbcore->components)); @@ -355,14 +368,42 @@ static void _GBCoreReloadConfigOption(struct mCore* core, const char* option, co } } -static void _GBCoreDesiredVideoDimensions(const struct mCore* core, unsigned* width, unsigned* height) { +static void _GBCoreSetOverride(struct mCore* core, const void* override) { + struct GBCore* gbcore = (struct GBCore*) core; + memcpy(&gbcore->override, override, sizeof(gbcore->override)); + gbcore->hasOverride = true; +} + +static void _GBCoreBaseVideoSize(const struct mCore* core, unsigned* width, unsigned* height) { + UNUSED(core); + *width = SGB_VIDEO_HORIZONTAL_PIXELS; + *height = SGB_VIDEO_VERTICAL_PIXELS; +} + +static void _GBCoreCurrentVideoSize(const struct mCore* core, unsigned* width, unsigned* height) { const struct GB* gb = core->board; if (gb && (!(gb->model & GB_MODEL_SGB) || !gb->video.sgbBorders)) { *width = GB_VIDEO_HORIZONTAL_PIXELS; *height = GB_VIDEO_VERTICAL_PIXELS; } else { - *width = 256; - *height = 224; + *width = SGB_VIDEO_HORIZONTAL_PIXELS; + *height = SGB_VIDEO_VERTICAL_PIXELS; + } +} + +static unsigned _GBCoreVideoScale(const struct mCore* core) { + UNUSED(core); + return 1; +} + +static size_t _GBCoreScreenRegions(const struct mCore* core, const struct mCoreScreenRegion** regions) { + const struct GB* gb = core->board; + if (gb && (!(gb->model & GB_MODEL_SGB) || !gb->video.sgbBorders)) { + *regions = _GBScreenRegions; + return 1; + } else { + *regions = _SGBScreenRegions; + return 2; } } @@ -424,7 +465,7 @@ static void _GBCoreSetAVStream(struct mCore* core, struct mAVStream* stream) { gb->stream = stream; if (stream && stream->videoDimensionsChanged) { unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); stream->videoDimensionsChanged(stream, width, height); } if (stream && stream->audioRateChanged) { @@ -510,14 +551,15 @@ static void _GBCoreReset(struct mCore* core) { mCoreConfigGetIntValue(&core->config, "useCgbColors", &doColorOverride); } - struct GBCartridgeOverride override; const struct GBCartridge* cart = (const struct GBCartridge*) &gb->memory.rom[0x100]; - override.headerCrc32 = doCrc32(cart, sizeof(*cart)); - bool modelOverride = GBOverrideFind(gbcore->overrides, &override) || (doColorOverride && GBOverrideColorFind(&override, doColorOverride)); - if (modelOverride) { - GBOverrideApply(gb, &override); + if (!gbcore->hasOverride) { + gbcore->override.headerCrc32 = doCrc32(cart, sizeof(*cart)); + gbcore->hasOverride = GBOverrideFind(gbcore->overrides, &gbcore->override) || (doColorOverride && GBOverrideColorFind(&gbcore->override, doColorOverride)); } - if (!modelOverride || override.model == GB_MODEL_AUTODETECT) { + if (gbcore->hasOverride) { + GBOverrideApply(gb, &gbcore->override); + } + if (!gbcore->hasOverride || gbcore->override.model == GB_MODEL_AUTODETECT) { const char* modelGB = mCoreConfigGetValue(&core->config, "gb.model"); const char* modelSGB = mCoreConfigGetValue(&core->config, "sgb.model"); const char* modelCGB = mCoreConfigGetValue(&core->config, "cgb.model"); @@ -607,16 +649,16 @@ static void _GBCoreReset(struct mCore* core) { switch (gb->model) { case GB_MODEL_DMG: case GB_MODEL_MGB: // TODO - strncat(path, PATH_SEP "gb_bios.bin", PATH_MAX - strlen(path)); + strncat(path, PATH_SEP "gb_bios.bin", PATH_MAX - strlen(path) - 1); break; case GB_MODEL_SGB: case GB_MODEL_SGB2: // TODO - strncat(path, PATH_SEP "sgb_bios.bin", PATH_MAX - strlen(path)); + strncat(path, PATH_SEP "sgb_bios.bin", PATH_MAX - strlen(path) - 1); break; case GB_MODEL_CGB: case GB_MODEL_AGB: case GB_MODEL_SCGB: - strncat(path, PATH_SEP "gbc_bios.bin", PATH_MAX - strlen(path)); + strncat(path, PATH_SEP "gbc_bios.bin", PATH_MAX - strlen(path) - 1); break; default: break; @@ -764,6 +806,20 @@ static void _GBCoreSetPeripheral(struct mCore* core, int type, void* periph) { } } +static void* _GBCoreGetPeripheral(struct mCore* core, int type) { + struct GB* gb = core->board; + switch (type) { + case mPERIPH_ROTATION: + return gb->memory.rotation; + case mPERIPH_RUMBLE: + return gb->memory.rumble; + case mPERIPH_IMAGE_SOURCE: + return gb->memory.cam; + default: + return NULL; + } +} + static uint32_t _GBCoreBusRead8(struct mCore* core, uint32_t address) { struct SM83Core* cpu = core->cpu; return cpu->memory.load8(cpu, address); @@ -1030,6 +1086,9 @@ static struct CLIDebuggerSystem* _GBCoreCliDebuggerSystem(struct mCore* core) { static void _GBCoreAttachDebugger(struct mCore* core, struct mDebugger* debugger) { struct SM83Core* cpu = core->cpu; + if (core->debugger == debugger) { + return; + } if (core->debugger) { SM83HotplugDetach(cpu, CPU_COMPONENT_DEBUGGER); } @@ -1050,7 +1109,7 @@ static void _GBCoreDetachDebugger(struct mCore* core) { static void _GBCoreLoadSymbols(struct mCore* core, struct VFile* vf) { core->symbolTable = mDebuggerSymbolTableCreate(); #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 - if (!vf) { + if (!vf && core->dirs.base) { vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".sym", O_RDONLY); } #endif @@ -1232,7 +1291,11 @@ struct mCore* GBCoreCreate(void) { core->setSync = _GBCoreSetSync; core->loadConfig = _GBCoreLoadConfig; core->reloadConfigOption = _GBCoreReloadConfigOption; - core->desiredVideoDimensions = _GBCoreDesiredVideoDimensions; + core->setOverride = _GBCoreSetOverride; + core->baseVideoSize = _GBCoreBaseVideoSize; + core->currentVideoSize = _GBCoreCurrentVideoSize; + core->videoScale = _GBCoreVideoScale; + core->screenRegions = _GBCoreScreenRegions; core->setVideoBuffer = _GBCoreSetVideoBuffer; core->setVideoGLTex = _GBCoreSetVideoGLTex; core->getPixels = _GBCoreGetPixels; @@ -1269,6 +1332,7 @@ struct mCore* GBCoreCreate(void) { core->getGameTitle = _GBCoreGetGameTitle; core->getGameCode = _GBCoreGetGameCode; core->setPeripheral = _GBCoreSetPeripheral; + core->getPeripheral = _GBCoreGetPeripheral; core->busRead8 = _GBCoreBusRead8; core->busRead16 = _GBCoreBusRead16; core->busRead32 = _GBCoreBusRead32; diff --git a/src/gb/debugger/cli.c b/src/gb/debugger/cli.c index d4089d40f..03aa2a992 100644 --- a/src/gb/debugger/cli.c +++ b/src/gb/debugger/cli.c @@ -58,7 +58,7 @@ static bool _GBCLIDebuggerCustom(struct CLIDebuggerSystem* debugger) { if (gbDebugger->frameAdvance) { if (!gbDebugger->inVblank && GBRegisterSTATGetMode(((struct GB*) gbDebugger->core->board)->memory.io[GB_REG_STAT]) == 1) { - mDebuggerEnter(&gbDebugger->d.p->d, DEBUGGER_ENTER_MANUAL, 0); + mDebuggerEnter(gbDebugger->d.p->d.p, DEBUGGER_ENTER_MANUAL, 0); gbDebugger->frameAdvance = false; return false; } @@ -70,7 +70,8 @@ static bool _GBCLIDebuggerCustom(struct CLIDebuggerSystem* debugger) { static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); - debugger->d.state = DEBUGGER_CALLBACK; + debugger->d.needsCallback = true; + mDebuggerUpdatePaused(debugger->d.p); struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger->system; gbDebugger->frameAdvance = true; diff --git a/src/gb/debugger/debugger.c b/src/gb/debugger/debugger.c index e1dda7a8d..cc05bb7a5 100644 --- a/src/gb/debugger/debugger.c +++ b/src/gb/debugger/debugger.c @@ -28,7 +28,7 @@ static const struct SM83Segment _GBCSegments[] = { static void _printStatus(struct CLIDebuggerSystem* debugger) { struct CLIDebuggerBackend* be = debugger->p->backend; - struct GB* gb = debugger->p->d.core->board; + struct GB* gb = debugger->p->d.p->core->board; be->printf(be, "IE: %02X IF: %02X IME: %i\n", gb->memory.ie, gb->memory.io[GB_REG_IF], gb->memory.ime); be->printf(be, "LCDC: %02X STAT: %02X LY: %02X\n", gb->memory.io[GB_REG_LCDC], gb->memory.io[GB_REG_STAT] | 0x80, gb->memory.io[GB_REG_LY]); be->printf(be, "Next video mode: %i\n", mTimingUntil(&gb->timing, &gb->video.modeEvent) / 4); diff --git a/src/gb/gb.c b/src/gb/gb.c index a3a870f28..899d5cfc7 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -405,7 +405,9 @@ void GBUnloadROM(struct GB* gb) { if (gb->romVf) { #ifndef FIXED_ROM_BUFFER - gb->romVf->unmap(gb->romVf, gb->memory.rom, gb->pristineRomSize); + if (gb->isPristine && gb->memory.rom) { + gb->romVf->unmap(gb->romVf, gb->memory.rom, gb->pristineRomSize); + } #endif gb->romVf->close(gb->romVf); gb->romVf = NULL; @@ -528,6 +530,23 @@ bool GBIsBIOS(struct VFile* vf) { } } +bool GBIsCompatibleBIOS(struct VFile* vf, enum GBModel model) { + switch (_GBBiosCRC32(vf)) { + case DMG_BIOS_CHECKSUM: + case DMG0_BIOS_CHECKSUM: + case MGB_BIOS_CHECKSUM: + case SGB_BIOS_CHECKSUM: + case SGB2_BIOS_CHECKSUM: + return model < GB_MODEL_CGB; + case CGB_BIOS_CHECKSUM: + case CGB0_BIOS_CHECKSUM: + case AGB_BIOS_CHECKSUM: + return model >= GB_MODEL_CGB; + default: + return false; + } +} + void GBReset(struct SM83Core* cpu) { struct GB* gb = (struct GB*) cpu->master; gb->memory.romBase = gb->memory.rom; @@ -560,7 +579,7 @@ void GBReset(struct SM83Core* cpu) { GBMemoryReset(gb); if (gb->biosVf) { - if (!GBIsBIOS(gb->biosVf)) { + if (!GBIsCompatibleBIOS(gb->biosVf, gb->model)) { gb->biosVf->close(gb->biosVf); gb->biosVf = NULL; } else { @@ -815,6 +834,7 @@ void GBDetectModel(struct GB* gb) { gb->model = GB_MODEL_SGB2; break; case CGB_BIOS_CHECKSUM: + case CGB0_BIOS_CHECKSUM: gb->model = GB_MODEL_CGB; break; case AGB_BIOS_CHECKSUM: diff --git a/src/gb/io.c b/src/gb/io.c index ba4473550..560ad7e62 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -491,7 +491,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { return; case GB_REG_SVBK: GBMemorySwitchWramBank(&gb->memory, value); - value = gb->memory.wramCurrentBank; + value &= 7; break; default: goto failed; diff --git a/src/gb/mbc.c b/src/gb/mbc.c index 27ccfe784..76c005f96 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -219,7 +219,7 @@ static enum GBMemoryBankControllerType _detectUnlMBC(const uint8_t* mem, size_t if (cart->type == 0x01) { // Make sure we're not using a "fixed" version return GB_UNL_LI_CHENG; } - if ((0x8000 << cart->romSize) != size) { + if ((0x8000U << cart->romSize) != size) { return GB_UNL_LI_CHENG; } break; diff --git a/src/gb/mbc/unlicensed.c b/src/gb/mbc/unlicensed.c index 58725fa98..1577a15fa 100644 --- a/src/gb/mbc/unlicensed.c +++ b/src/gb/mbc/unlicensed.c @@ -227,7 +227,7 @@ void _GBNTOld2(struct GB* gb, uint16_t address, uint8_t value) { mbcState->rumble = !!(value & 0x80); } - if (mbcState->rumble) { + if (mbcState->rumble && memory->rumble) { memory->rumble->setRumble(memory->rumble, !!(mbcState->swapped ? value & 0x08 : value & 0x02)); } break; diff --git a/src/gb/memory.c b/src/gb/memory.c index 5c4d97876..e41050784 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -14,6 +14,7 @@ #include #include +#include mLOG_DEFINE_CATEGORY(GB_MEM, "GB Memory", "gb.memory"); @@ -474,13 +475,14 @@ uint8_t GBView8(struct SM83Core* cpu, uint16_t address, int segment) { if (memory->rtcAccess) { return memory->rtcRegs[memory->activeRtcReg]; } else if (memory->sramAccess) { - if (segment < 0 && memory->sram) { - return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; - } else if ((size_t) segment * GB_SIZE_EXTERNAL_RAM < gb->sramSize) { - return memory->sram[(address & (GB_SIZE_EXTERNAL_RAM - 1)) + segment *GB_SIZE_EXTERNAL_RAM]; - } else { - return 0xFF; + if (memory->sram) { + if (segment < 0) { + return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; + } else if ((size_t) segment * GB_SIZE_EXTERNAL_RAM < gb->sramSize) { + return memory->sram[(address & (GB_SIZE_EXTERNAL_RAM - 1)) + segment *GB_SIZE_EXTERNAL_RAM]; + } } + return 0xFF; } else if (memory->mbcRead) { return memory->mbcRead(memory, address); } else if (memory->mbcType == GB_HuC3) { @@ -797,6 +799,10 @@ void GBMemorySerialize(const struct GB* gb, struct GBSerializedState* state) { state->huc3Registers[i] |= memory->mbcState.huc3.registers[i * 2 + 1] << 4; } break; + case GB_POCKETCAM: + state->memory.pocketCam.registersActive = memory->mbcState.pocketCam.registersActive; + memcpy(state->pocketCamRegisters, memory->mbcState.pocketCam.registers, sizeof(memory->mbcState.pocketCam.registers)); + break; case GB_MMM01: state->memory.mmm01.locked = memory->mbcState.mmm01.locked; state->memory.mmm01.bank0 = memory->mbcState.mmm01.currentBank0; @@ -948,6 +954,10 @@ void GBMemoryDeserialize(struct GB* gb, const struct GBSerializedState* state) { memory->mbcState.huc3.registers[i * 2 + 1] = state->huc3Registers[i] >> 4; } break; + case GB_POCKETCAM: + memory->mbcState.pocketCam.registersActive = state->memory.pocketCam.registersActive; + memcpy(memory->mbcState.pocketCam.registers, state->pocketCamRegisters, sizeof(memory->mbcState.pocketCam.registers)); + break; case GB_MMM01: memory->mbcState.mmm01.locked = state->memory.mmm01.locked; memory->mbcState.mmm01.currentBank0 = state->memory.mmm01.bank0; @@ -1005,6 +1015,11 @@ void _pristineCow(struct GB* gb) { if (gb->memory.rom == gb->memory.romBase) { gb->memory.romBase = newRom; } + if (gb->romVf) { + gb->romVf->unmap(gb->romVf, gb->memory.rom, gb->memory.romSize); + gb->romVf->close(gb->romVf); + gb->romVf = NULL; + } gb->memory.rom = newRom; GBMBCSwitchBank(gb, gb->memory.currentBank); gb->isPristine = false; diff --git a/src/gb/renderers/software.c b/src/gb/renderers/software.c index a5e41162f..d157920fa 100644 --- a/src/gb/renderers/software.c +++ b/src/gb/renderers/software.c @@ -571,20 +571,38 @@ static void _cleanOAM(struct GBVideoSoftwareRenderer* renderer, int y) { } int o = 0; int i; + int16_t ids[GB_VIDEO_MAX_LINE_OBJ]; for (i = 0; i < GB_VIDEO_MAX_OBJ && o < GB_VIDEO_MAX_LINE_OBJ; ++i) { uint8_t oy = renderer->d.oam->obj[i].y; if (y < oy - 16 || y >= oy - 16 + spriteHeight) { continue; } - // TODO: Sort - renderer->obj[o].obj = renderer->d.oam->obj[i]; - renderer->obj[o].index = i; + ids[o] = (renderer->d.oam->obj[i].x << 7) | i; ++o; - if (o == 10) { - break; - } } renderer->objMax = o; + if (renderer->model < GB_MODEL_CGB) { + // Terrble n^2 sort, but it's only 10 elements so it shouldn't be that bad + int16_t ids2[GB_VIDEO_MAX_LINE_OBJ]; + int min = -1; + int j; + for (i = 0; i < o; ++i) { + int min2 = 0xFFFF; + for (j = 0; j < o; ++j) { + if (ids[j] > min && ids[j] < min2) { + min2 = ids[j]; + } + } + min = min2; + ids2[i] = min; + } + memcpy(ids, ids2, sizeof(ids)); + } + for (i = 0; i < o; ++i) { + int id = ids[i] & 0x7F; + renderer->obj[i].obj = renderer->d.oam->obj[id]; + renderer->obj[i].index = id; + } } static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y) { diff --git a/src/gb/sio.c b/src/gb/sio.c index d327b606d..649213168 100644 --- a/src/gb/sio.c +++ b/src/gb/sio.c @@ -91,11 +91,13 @@ void GBSIOWriteSB(struct GBSIO* sio, uint8_t sb) { void GBSIOWriteSC(struct GBSIO* sio, uint8_t sc) { sio->period = GBSIOCyclesPerTransfer[GBRegisterSCGetClockSpeed(sc)]; // TODO Shift Clock if (GBRegisterSCIsEnable(sc)) { - mTimingDeschedule(&sio->p->timing, &sio->event); if (GBRegisterSCIsShiftClock(sc)) { + mTimingDeschedule(&sio->p->timing, &sio->event); mTimingSchedule(&sio->p->timing, &sio->event, sio->period * (2 - sio->p->doubleSpeed)); sio->remainingBits = 8; } + } else { + mTimingDeschedule(&sio->p->timing, &sio->event); } if (sio->driver) { sio->driver->writeSC(sio->driver, sc); diff --git a/src/gba/audio.c b/src/gba/audio.c index f957ef191..ed4ea8474 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -106,6 +106,9 @@ void GBAAudioDeinit(struct GBAAudio* audio) { } void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) { + if (samples > 0x2000) { + samples = 0x2000; + } mCoreSyncLockAudio(audio->p->sync); audio->samples = samples; blip_clear(audio->psg.left); @@ -231,16 +234,35 @@ void GBAAudioWriteSOUNDCNT_HI(struct GBAAudio* audio, uint16_t value) { } void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value) { + GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing)); audio->enable = GBAudioEnableGetEnable(value); GBAudioWriteNR52(&audio->psg, value); + if (!audio->enable) { + int i; + for (i = REG_SOUND1CNT_LO; i < REG_SOUNDCNT_HI; i += 2) { + audio->p->memory.io[i >> 1] = 0; + } + audio->psg.ch3.size = 0; + audio->psg.ch3.bank = 0; + audio->psg.ch3.volume = 0; + audio->volume = 0; + audio->volumeChA = 0; + audio->volumeChB = 0; + audio->p->memory.io[REG_SOUNDCNT_HI >> 1] &= 0xFF00; + } } void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value) { + GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing)); audio->soundbias = value; int32_t oldSampleInterval = audio->sampleInterval; audio->sampleInterval = 0x200 >> GBARegisterSOUNDBIASGetResolution(value); - if (oldSampleInterval != audio->sampleInterval && audio->p->stream && audio->p->stream->audioRateChanged) { - audio->p->stream->audioRateChanged(audio->p->stream, GBA_ARM7TDMI_FREQUENCY / audio->sampleInterval); + if (oldSampleInterval != audio->sampleInterval) { + audio->lastSample += oldSampleInterval * audio->sampleIndex; + audio->sampleIndex = 0; + if (audio->p->stream && audio->p->stream->audioRateChanged) { + audio->p->stream->audioRateChanged(audio->p->stream, GBA_ARM7TDMI_FREQUENCY / audio->sampleInterval); + } } } @@ -506,6 +528,16 @@ void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state) { GBAudioPSGDeserialize(&audio->psg, &state->audio.psg, &state->audio.flags); + uint16_t reg; + LOAD_16(reg, REG_SOUND1CNT_X, state->io); + GBAIOWrite(audio->p, REG_SOUND1CNT_X, reg & 0x7FFF); + LOAD_16(reg, REG_SOUND2CNT_HI, state->io); + GBAIOWrite(audio->p, REG_SOUND2CNT_HI, reg & 0x7FFF); + LOAD_16(reg, REG_SOUND3CNT_X, state->io); + GBAIOWrite(audio->p, REG_SOUND3CNT_X, reg & 0x7FFF); + LOAD_16(reg, REG_SOUND4CNT_HI, state->io); + GBAIOWrite(audio->p, REG_SOUND4CNT_HI, reg & 0x7FFF); + LOAD_32(audio->chA.internalSample, 0, &state->audio.internalA); LOAD_32(audio->chB.internalSample, 0, &state->audio.internalB); memcpy(audio->chA.samples, state->samples.chA, sizeof(audio->chA.samples)); diff --git a/src/gba/cart/ereader.c b/src/gba/cart/ereader.c index 030e0d183..6a3ce58b0 100644 --- a/src/gba/cart/ereader.c +++ b/src/gba/cart/ereader.c @@ -13,7 +13,7 @@ #ifdef USE_FFMPEG #include #ifdef USE_PNG -#include +#include #include #endif @@ -888,7 +888,11 @@ struct EReaderScan* EReaderScanLoadImagePNG(const char* filename) { } png_infop info = png_create_info_struct(png); png_infop end = png_create_info_struct(png); - PNGReadHeader(png, info); + if (!PNGReadHeader(png, info)) { + PNGReadClose(png, info, end); + vf->close(vf); + return NULL; + } unsigned height = png_get_image_height(png, info); unsigned width = png_get_image_width(png, info); int type = png_get_color_type(png, info); @@ -900,19 +904,34 @@ struct EReaderScan* EReaderScanLoadImagePNG(const char* filename) { break; } image = malloc(height * width * 3); - PNGReadPixels(png, info, image, width, height, width); + if (!image) { + goto out; + } + if (!PNGReadPixels(png, info, image, width, height, width)) { + free(image); + image = NULL; + goto out; + } break; case PNG_COLOR_TYPE_RGBA: if (depth != 8) { break; } image = malloc(height * width * 4); - PNGReadPixelsA(png, info, image, width, height, width); + if (!image) { + goto out; + } + if (!PNGReadPixelsA(png, info, image, width, height, width)) { + free(image); + image = NULL; + goto out; + } break; default: break; } PNGReadFooter(png, end); +out: PNGReadClose(png, info, end); vf->close(vf); if (!image) { @@ -1480,7 +1499,7 @@ bool EReaderScanCard(struct EReaderScan* scan) { size_t i; for (i = 0; i < blocks; ++i) { EReaderScanDetectBlockThreshold(scan, i); - int errors = 36 * 36; + unsigned errors = 36 * 36; while (!EReaderScanScanBlock(scan, i, true)) { if (errors < EReaderBlockListGetPointer(&scan->blocks, i)->errors) { return false; diff --git a/src/gba/cart/gpio.c b/src/gba/cart/gpio.c index 0bb7f3429..62feb7335 100644 --- a/src/gba/cart/gpio.c +++ b/src/gba/cart/gpio.c @@ -374,7 +374,9 @@ void _lightReadPins(struct GBACartridgeHardware* hw) { mLOG(GBA_HW, DEBUG, "[SOLAR] Got reset"); hw->lightCounter = 0; if (lux) { - lux->sample(lux); + if (lux->sample) { + lux->sample(lux); + } hw->lightSample = lux->readLuminance(lux); } else { hw->lightSample = 0xFF; @@ -421,8 +423,8 @@ void GBAHardwareTiltWrite(struct GBACartridgeHardware* hw, uint32_t address, uin int32_t x = rotationSource->readTiltX(rotationSource); int32_t y = rotationSource->readTiltY(rotationSource); // Normalize to ~12 bits, focused on 0x3A0 - hw->tiltX = (x >> 21) + 0x3A0; // Crop off an extra bit so that we can't go negative - hw->tiltY = (y >> 21) + 0x3A0; + hw->tiltX = 0x3A0 - (x >> 22); + hw->tiltY = 0x3A0 - (y >> 22); } else { mLOG(GBA_HW, GAME_ERROR, "Tilt sensor wrote wrong byte to %04x: %02x", address, value); } diff --git a/src/gba/core.c b/src/gba/core.c index c5f1946d6..aa6128b26 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -129,6 +129,10 @@ static const struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = { { GBA_REGION_SRAM_MIRROR, "eeprom", "EEPROM", "EEPROM (8kiB)", 0, GBA_SIZE_EEPROM, GBA_SIZE_EEPROM, mCORE_MEMORY_RW }, }; +static const struct mCoreScreenRegion _GBAScreenRegions[] = { + { 0, "Screen", 0, 0, GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS } +}; + static const struct mCoreRegisterInfo _GBARegisters[] = { { "r0", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR }, { "r1", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR }, @@ -178,6 +182,8 @@ struct GBACore { #endif struct mCPUComponent* components[CPU_COMPONENT_MAX]; const struct Configuration* overrides; + struct GBACartridgeOverride override; + bool hasOverride; struct mDebuggerPlatform* debuggerPlatform; struct mCheatDevice* cheatDevice; struct GBAAudioMixer* audioMixer; @@ -199,6 +205,7 @@ static bool _GBACoreInit(struct mCore* core) { core->debugger = NULL; core->symbolTable = NULL; core->videoLogger = NULL; + gbacore->hasOverride = false; gbacore->overrides = NULL; gbacore->debuggerPlatform = NULL; gbacore->cheatDevice = NULL; @@ -422,19 +429,51 @@ static void _GBACoreReloadConfigOption(struct mCore* core, const char* option, c } } -static void _GBACoreDesiredVideoDimensions(const struct mCore* core, unsigned* width, unsigned* height) { +static void _GBACoreSetOverride(struct mCore* core, const void* override) { + struct GBACore* gbacore = (struct GBACore*) core; + memcpy(&gbacore->override, override, sizeof(gbacore->override)); + gbacore->hasOverride = true; +} + +static void _GBACoreBaseVideoSize(const struct mCore* core, unsigned* width, unsigned* height) { + UNUSED(core); + *width = GBA_VIDEO_HORIZONTAL_PIXELS; + *height = GBA_VIDEO_VERTICAL_PIXELS; +} + +static void _GBACoreCurrentVideoSize(const struct mCore* core, unsigned* width, unsigned* height) { + int scale = 1; #ifdef BUILD_GLES3 const struct GBACore* gbacore = (const struct GBACore*) core; - int scale = gbacore->glRenderer.scale; + if (gbacore->glRenderer.outputTex != (unsigned) -1) { + scale = gbacore->glRenderer.scale; + } #else UNUSED(core); - int scale = 1; #endif *width = GBA_VIDEO_HORIZONTAL_PIXELS * scale; *height = GBA_VIDEO_VERTICAL_PIXELS * scale; } +static unsigned _GBACoreVideoScale(const struct mCore* core) { +#ifdef BUILD_GLES3 + const struct GBACore* gbacore = (const struct GBACore*) core; + if (gbacore->glRenderer.outputTex != (unsigned) -1) { + return gbacore->glRenderer.scale; + } +#else + UNUSED(core); +#endif + return 1; +} + +static size_t _GBACoreScreenRegions(const struct mCore* core, const struct mCoreScreenRegion** regions) { + UNUSED(core); + *regions = _GBAScreenRegions; + return 1; +} + static void _GBACoreSetVideoBuffer(struct mCore* core, color_t* buffer, size_t stride) { struct GBACore* gbacore = (struct GBACore*) core; gbacore->renderer.outputBuffer = buffer; @@ -500,7 +539,7 @@ static void _GBACoreSetAVStream(struct mCore* core, struct mAVStream* stream) { gba->stream = stream; if (stream && stream->videoDimensionsChanged) { unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); stream->videoDimensionsChanged(stream, width, height); } if (stream && stream->audioRateChanged) { @@ -648,7 +687,11 @@ static void _GBACoreReset(struct mCore* core) { if (!forceGbp) { gba->memory.hw.devices &= ~HW_GB_PLAYER_DETECTION; } - GBAOverrideApplyDefaults(gba, gbacore->overrides); + if (gbacore->hasOverride) { + GBAOverrideApply(gba, &gbacore->override); + } else { + GBAOverrideApplyDefaults(gba, gbacore->overrides); + } if (forceGbp) { gba->memory.hw.devices |= HW_GB_PLAYER_DETECTION; } @@ -684,7 +727,7 @@ static void _GBACoreReset(struct mCore* core) { if (!found) { char path[PATH_MAX]; mCoreConfigDirectory(path, PATH_MAX); - strncat(path, PATH_SEP "gba_bios.bin", PATH_MAX - strlen(path)); + strncat(path, PATH_SEP "gba_bios.bin", PATH_MAX - strlen(path) - 1); bios = VFileOpen(path, O_RDONLY); if (bios && GBAIsBIOS(bios)) { found = true; @@ -814,6 +857,20 @@ static void _GBACoreSetPeripheral(struct mCore* core, int type, void* periph) { } } +static void* _GBACoreGetPeripheral(struct mCore* core, int type) { + struct GBA* gba = core->board; + switch (type) { + case mPERIPH_ROTATION: + return gba->rotationSource; + case mPERIPH_RUMBLE: + return gba->rumble; + case mPERIPH_GBA_LUMINANCE: + return gba->luminanceSource; + default: + return NULL; + } +} + static uint32_t _GBACoreBusRead8(struct mCore* core, uint32_t address) { struct ARMCore* cpu = core->cpu; return cpu->memory.load8(cpu, address, 0); @@ -1099,6 +1156,9 @@ static struct CLIDebuggerSystem* _GBACoreCliDebuggerSystem(struct mCore* core) { } static void _GBACoreAttachDebugger(struct mCore* core, struct mDebugger* debugger) { + if (core->debugger == debugger) { + return; + } if (core->debugger) { GBADetachDebugger(core->board); } @@ -1116,12 +1176,12 @@ static void _GBACoreLoadSymbols(struct mCore* core, struct VFile* vf) { core->symbolTable = mDebuggerSymbolTableCreate(); #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 #ifdef USE_ELF - if (!vf) { + if (!vf && core->dirs.base) { closeAfter = true; vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".elf", O_RDONLY); } #endif - if (!vf) { + if (!vf && core->dirs.base) { closeAfter = true; vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".sym", O_RDONLY); } @@ -1358,7 +1418,11 @@ struct mCore* GBACoreCreate(void) { core->setSync = _GBACoreSetSync; core->loadConfig = _GBACoreLoadConfig; core->reloadConfigOption = _GBACoreReloadConfigOption; - core->desiredVideoDimensions = _GBACoreDesiredVideoDimensions; + core->setOverride = _GBACoreSetOverride; + core->baseVideoSize = _GBACoreBaseVideoSize; + core->currentVideoSize = _GBACoreCurrentVideoSize; + core->videoScale = _GBACoreVideoScale; + core->screenRegions = _GBACoreScreenRegions; core->setVideoBuffer = _GBACoreSetVideoBuffer; core->setVideoGLTex = _GBACoreSetVideoGLTex; core->getPixels = _GBACoreGetPixels; @@ -1395,6 +1459,7 @@ struct mCore* GBACoreCreate(void) { core->getGameTitle = _GBACoreGetGameTitle; core->getGameCode = _GBACoreGetGameCode; core->setPeripheral = _GBACoreSetPeripheral; + core->getPeripheral = _GBACoreGetPeripheral; core->busRead8 = _GBACoreBusRead8; core->busRead16 = _GBACoreBusRead16; core->busRead32 = _GBACoreBusRead32; diff --git a/src/gba/debugger/cli.c b/src/gba/debugger/cli.c index 5349911ab..fd36c2dbb 100644 --- a/src/gba/debugger/cli.c +++ b/src/gba/debugger/cli.c @@ -57,7 +57,7 @@ static bool _GBACLIDebuggerCustom(struct CLIDebuggerSystem* debugger) { if (gbaDebugger->frameAdvance) { if (!gbaDebugger->inVblank && GBARegisterDISPSTATIsInVblank(((struct GBA*) gbaDebugger->core->board)->memory.io[REG_DISPSTAT >> 1])) { - mDebuggerEnter(&gbaDebugger->d.p->d, DEBUGGER_ENTER_MANUAL, 0); + mDebuggerEnter(gbaDebugger->d.p->d.p, DEBUGGER_ENTER_MANUAL, 0); gbaDebugger->frameAdvance = false; return false; } @@ -69,7 +69,8 @@ static bool _GBACLIDebuggerCustom(struct CLIDebuggerSystem* debugger) { static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); - debugger->d.state = DEBUGGER_CALLBACK; + debugger->d.needsCallback = true; + mDebuggerUpdatePaused(debugger->d.p); struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system; gbaDebugger->frameAdvance = true; diff --git a/src/gba/gba.c b/src/gba/gba.c index 588a138f3..a839d0e7f 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -663,7 +663,6 @@ bool GBAIsROM(struct VFile* vf) { #ifdef USE_ELF struct ELF* elf = ELFOpen(vf); if (elf) { - uint32_t entry = ELFEntry(elf); bool isGBA = true; isGBA = isGBA && ELFMachine(elf) == EM_ARM; isGBA = isGBA && (GBAVerifyELFEntry(elf, GBA_BASE_ROM0) || GBAVerifyELFEntry(elf, GBA_BASE_EWRAM + 0xC0)); @@ -887,9 +886,6 @@ void GBAIllegal(struct ARMCore* cpu, uint32_t opcode) { void GBABreakpoint(struct ARMCore* cpu, int immediate) { struct GBA* gba = (struct GBA*) cpu->master; - if (immediate >= CPU_COMPONENT_MAX) { - return; - } switch (immediate) { #ifdef USE_DEBUGGERS case CPU_COMPONENT_DEBUGGER: @@ -900,6 +896,7 @@ void GBABreakpoint(struct ARMCore* cpu, int immediate) { .pointId = -1 }; mDebuggerEnter(gba->debugger->d.p, DEBUGGER_ENTER_BREAKPOINT, &info); + return; } break; #endif @@ -918,11 +915,13 @@ void GBABreakpoint(struct ARMCore* cpu, int immediate) { if (hook) { ARMRunFake(cpu, hook->patchedOpcode); } + return; } break; default: break; } + ARMRaiseUndefined(cpu); } void GBAFrameStarted(struct GBA* gba) { diff --git a/src/gba/hle-bios.c b/src/gba/hle-bios.c index 84737afd4..841caf543 100644 --- a/src/gba/hle-bios.c +++ b/src/gba/hle-bios.c @@ -3,9 +3,9 @@ #include const uint8_t hleBios[GBA_SIZE_BIOS] = { - 0xd3, 0x00, 0x00, 0xea, 0x66, 0x00, 0x00, 0xea, 0x0c, 0x00, 0x00, 0xea, - 0xfe, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe1, - 0x59, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x00, 0x00, 0x00, 0x00, + 0xd3, 0x00, 0x00, 0xea, 0xe1, 0x00, 0x00, 0xea, 0x0c, 0x00, 0x00, 0xea, + 0xdf, 0x00, 0x00, 0xea, 0xde, 0x00, 0x00, 0xea, 0x00, 0x00, 0xa0, 0xe1, + 0x59, 0x00, 0x00, 0xea, 0xdb, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0xe3, 0x01, 0xd3, 0xa0, 0x03, @@ -50,13 +50,13 @@ const uint8_t hleBios[GBA_SIZE_BIOS] = { 0x0c, 0x80, 0xbd, 0xe8, 0x30, 0x40, 0x2d, 0xe9, 0x02, 0x46, 0xa0, 0xe1, 0x00, 0xc0, 0xa0, 0xe1, 0x01, 0x50, 0xa0, 0xe1, 0x01, 0x04, 0x12, 0xe3, 0x0f, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x12, 0xe3, 0x05, 0x00, 0x00, 0x0a, - 0x24, 0x45, 0x85, 0xe0, 0x08, 0x00, 0xbc, 0xe8, 0x04, 0x00, 0x55, 0xe1, - 0x08, 0x00, 0xa5, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x14, 0x00, 0x00, 0xea, + 0x24, 0x45, 0x85, 0xe0, 0x08, 0x00, 0xb0, 0xe8, 0x04, 0x00, 0x51, 0xe1, + 0x08, 0x00, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x14, 0x00, 0x00, 0xea, 0x01, 0xc0, 0xcc, 0xe3, 0x01, 0x50, 0xc5, 0xe3, 0xa4, 0x45, 0x85, 0xe0, 0xb0, 0x30, 0xdc, 0xe1, 0x04, 0x00, 0x55, 0xe1, 0xb2, 0x30, 0xc5, 0xb0, 0xfc, 0xff, 0xff, 0xba, 0x0c, 0x00, 0x00, 0xea, 0x01, 0x03, 0x12, 0xe3, - 0x05, 0x00, 0x00, 0x0a, 0x24, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x55, 0xe1, - 0x08, 0x00, 0xbc, 0xb8, 0x08, 0x00, 0xa5, 0xb8, 0xfb, 0xff, 0xff, 0xba, + 0x05, 0x00, 0x00, 0x0a, 0x24, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x51, 0xe1, + 0x08, 0x00, 0xb0, 0xb8, 0x08, 0x00, 0xa1, 0xb8, 0xfb, 0xff, 0xff, 0xba, 0x04, 0x00, 0x00, 0xea, 0xa4, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x55, 0xe1, 0xb2, 0x30, 0xdc, 0xb0, 0xb2, 0x30, 0xc5, 0xb0, 0xfb, 0xff, 0xff, 0xba, 0x17, 0x3e, 0xa0, 0xe3, 0x30, 0x80, 0xbd, 0xe8, 0xf0, 0x47, 0x2d, 0xe9, @@ -78,5 +78,13 @@ const uint8_t hleBios[GBA_SIZE_BIOS] = { 0x00, 0x10, 0xa0, 0x13, 0x1e, 0xff, 0x2f, 0x11, 0x1c, 0xe0, 0x9f, 0xe5, 0x00, 0x10, 0x9e, 0xe5, 0x00, 0x00, 0x51, 0xe3, 0x00, 0x10, 0xa0, 0xe3, 0x1e, 0xff, 0x2f, 0x11, 0xc0, 0xe0, 0x4e, 0xe2, 0x1e, 0xff, 0x2f, 0xe1, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x29, 0xe1, 0xc0, 0x00, 0x00, 0x02 + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x29, 0xe1, 0xc0, 0x00, 0x00, 0x02, + 0x4c, 0xd0, 0x9f, 0xe5, 0x00, 0x50, 0x2d, 0xe9, 0x00, 0xc0, 0x4f, 0xe1, + 0x00, 0xe0, 0x0f, 0xe1, 0x00, 0x50, 0x2d, 0xe9, 0x02, 0xe3, 0xa0, 0xe3, + 0x9c, 0xc0, 0xde, 0xe5, 0xa5, 0x00, 0x5c, 0xe3, 0x04, 0x00, 0x00, 0x1a, + 0xb4, 0xc0, 0xde, 0xe5, 0x80, 0x00, 0x1c, 0xe3, 0x04, 0xe0, 0x8f, 0xe2, + 0x20, 0xf0, 0x9f, 0x15, 0x20, 0xf0, 0x9f, 0x05, 0x14, 0xd0, 0x9f, 0xe5, + 0x10, 0xc0, 0x1d, 0xe5, 0x0c, 0xf0, 0x69, 0xe1, 0x00, 0x50, 0x3d, 0xe9, + 0x04, 0xf0, 0x5e, 0xe2, 0x00, 0x00, 0x00, 0x00, 0x04, 0xe0, 0xa0, 0x03, + 0xf0, 0x7f, 0x00, 0x03, 0x00, 0x20, 0xfe, 0x09, 0x00, 0xc0, 0xff, 0x09 }; diff --git a/src/gba/hle-bios.s b/src/gba/hle-bios.s index c891479a5..07f827b26 100644 --- a/src/gba/hle-bios.s +++ b/src/gba/hle-bios.s @@ -123,7 +123,7 @@ subs pc, lr, #4 .word 0 .word 0xE55EC002 -undefBase: +@ Padding for back compat subs pc, lr, #4 .word 0 .word 0x03A0E004 @@ -209,10 +209,10 @@ tst r2, #0x04000000 beq 1f @ Word add r4, r5, r4, lsr #10 -ldmia r12!, {r3} +ldmia r0!, {r3} 2: -cmp r5, r4 -stmltia r5!, {r3} +cmp r1, r4 +stmltia r1!, {r3} blt 2b b 3f @ Halfword @@ -233,9 +233,9 @@ beq 1f @ Word add r4, r5, r4, lsr #10 2: -cmp r5, r4 -ldmltia r12!, {r3} -stmltia r5!, {r3} +cmp r1, r4 +ldmltia r0!, {r3} +stmltia r1!, {r3} blt 2b b 3f @ Halfword @@ -328,3 +328,32 @@ sub lr, #0xC0 bx lr .word 0 .word 0xE129F000 + +.ltorg + +undefBase: +pabtBase: +dabtBase: +fiqBase: +ldr sp, =0x03007FF0 +stmdb sp!, {r12, lr} +mrs r12, spsr +mrs lr, cpsr +stmdb sp!, {r12, lr} +mov lr, #0x08000000 +ldrb r12, [lr, #0x9C] +cmp r12, #0xA5 +bne 1f +ldrb r12, [lr, #0xB4] +tst r12, #0x80 +adr lr, 1f +ldrne pc, =0x09FE2000 +ldreq pc, =0x09FFC000 +1: +ldr sp, =0x03007FF0 +ldr r12, [sp, #-0x10] +msr spsr, r12 +ldmdb sp!, {r12, lr} +subs pc, lr, #4 +.word 0 +.word 0x03A0E004 diff --git a/src/gba/io.c b/src/gba/io.c index 4e3763536..0b08772ed 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -296,8 +296,8 @@ static const int _isWSpecialRegister[REG_INTERNAL_MAX >> 1] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Audio - 1, 1, 1, 0, 1, 0, 1, 0, - 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, diff --git a/src/gba/memory.c b/src/gba/memory.c index 4f3063cfb..cf228076a 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -405,7 +405,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { #define LOAD_CART \ wait += waitstatesRegion[address >> BASE_OFFSET]; \ - if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) { \ + if ((address & (GBA_SIZE_ROM0 - 4)) < memory->romSize) { \ LOAD_32(value, address & (GBA_SIZE_ROM0 - 4), memory->rom); \ } else if (memory->vfame.cartType) { \ value = GBAVFameGetPatternValue(address, 32); \ @@ -570,11 +570,11 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { case GBA_REGION_ROM1_EX: case GBA_REGION_ROM2: wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; - if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) { + if ((address & (GBA_SIZE_ROM0 - 2)) < memory->romSize) { LOAD_16(value, address & (GBA_SIZE_ROM0 - 2), memory->rom); } else if (memory->vfame.cartType) { value = GBAVFameGetPatternValue(address, 16); - } else if ((address & (GBA_SIZE_ROM0 - 1)) >= AGB_PRINT_BASE) { + } else if ((address & (GBA_SIZE_ROM0 - 2)) >= AGB_PRINT_BASE) { uint32_t agbPrintAddr = address & 0x00FFFFFF; if (agbPrintAddr == AGB_PRINT_PROTECT) { value = memory->agbPrintProtect; @@ -595,7 +595,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { value = GBASavedataReadEEPROM(&memory->savedata); } else if ((address & 0x0DFC0000) >= 0x0DF80000 && memory->hw.devices & HW_EREADER) { value = GBACartEReaderRead(&memory->ereader, address); - } else if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) { + } else if ((address & (GBA_SIZE_ROM0 - 2)) < memory->romSize) { LOAD_16(value, address & (GBA_SIZE_ROM0 - 2), memory->rom); } else if (memory->vfame.cartType) { value = GBAVFameGetPatternValue(address, 16); @@ -1219,7 +1219,7 @@ void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* o mLOG(GBA_MEM, STUB, "Unimplemented memory Patch32: 0x%08X", address); break; case GBA_REGION_PALETTE_RAM: - LOAD_32(oldValue, address & (GBA_SIZE_PALETTE_RAM - 1), gba->video.palette); + LOAD_32(oldValue, address & (GBA_SIZE_PALETTE_RAM - 4), gba->video.palette); STORE_32(value, address & (GBA_SIZE_PALETTE_RAM - 4), gba->video.palette); gba->video.renderer->writePalette(gba->video.renderer, address & (GBA_SIZE_PALETTE_RAM - 4), value); gba->video.renderer->writePalette(gba->video.renderer, (address & (GBA_SIZE_PALETTE_RAM - 4)) + 2, value >> 16); @@ -1320,7 +1320,7 @@ void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* o case GBA_REGION_ROM2: case GBA_REGION_ROM2_EX: _pristineCow(gba); - if ((address & (GBA_SIZE_ROM0 - 1)) >= gba->memory.romSize) { + if ((address & (GBA_SIZE_ROM0 - 2)) >= gba->memory.romSize) { gba->memory.romSize = (address & (GBA_SIZE_ROM0 - 2)) + 2; gba->memory.romMask = toPow2(gba->memory.romSize) - 1; } @@ -1747,15 +1747,13 @@ int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait) { maxLoads -= previousLoads; } - int32_t s = cpu->memory.activeSeqCycles16; - int32_t n2s = cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16 + 1; - // Figure out how many sequential loads we can jam in + int32_t s = cpu->memory.activeSeqCycles16; int32_t stall = s + 1; int32_t loads = 1; while (stall < wait && loads < maxLoads) { - stall += s; + stall += s + 1; ++loads; } memory->lastPrefetchedPc = cpu->gprs[ARM_PC] + WORD_SIZE_THUMB * (loads + previousLoads - 1); @@ -1766,10 +1764,10 @@ int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait) { } // This instruction used to have an N, convert it to an S. - wait -= n2s; + wait -= cpu->memory.activeNonseqCycles16 - s; // The next |loads|S waitstates disappear entirely, so long as they're all in a row - wait -= stall - 1; + wait -= stall; return wait; } diff --git a/src/gba/overrides.c b/src/gba/overrides.c index e15db50d8..1d417d86b 100644 --- a/src/gba/overrides.c +++ b/src/gba/overrides.c @@ -123,10 +123,8 @@ static const struct GBACartridgeOverride _overrides[] = { { "BPEF", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6, false }, // Pokemon Mystery Dungeon - { "B24J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false }, { "B24E", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false }, { "B24P", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false }, - { "B24U", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false }, // Pokemon FireRed { "BPRJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false }, @@ -159,6 +157,10 @@ static const struct GBACartridgeOverride _overrides[] = { // Shin Bokura no Taiyou: Gyakushuu no Sabata { "U33J", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE, false }, + // Stuart Little 2 + { "ASLE", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE, false }, + { "ASLF", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE, false }, + // Super Mario Advance 2 { "AA2J", SAVEDATA_EEPROM, HW_NONE, 0x800052E, false }, { "AA2E", SAVEDATA_EEPROM, HW_NONE, 0x800052E, false }, diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index b6ffa2aac..e4d6415d6 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -233,6 +233,29 @@ static const char* const _interpolate = " aff[2] = transform[start + 2].zw;\n" " mat[3] = transform[start + 3].xy;\n" " aff[3] = transform[start + 3].zw;\n" + "}\n" + + "ivec2 affineInterpolate() {\n" + " ivec2 mat[4];\n" + " ivec2 offset[4];\n" + " vec2 incoord = texCoord;\n" + " if (mosaic.x > 1) {\n" + " incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n" + " }\n" + " if (mosaic.y > 1) {\n" + " incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n" + " }\n" + " loadAffine(int(incoord.y), mat, offset);\n" + " float y = fract(incoord.y);\n" + " float start = 2. / 3.;\n" + " if (int(incoord.y) - range.x < 4) {\n" + " y = incoord.y - float(range.x);\n" + " start -= 1.;\n" + " }\n" + " float lin = start + y / 3.;\n" + " vec2 mixedTransform = interpolate(mat, lin);\n" + " vec2 mixedOffset = interpolate(offset, lin);\n" + " return ivec2(mixedTransform * incoord.x + mixedOffset);\n" "}\n"; static const char* const _renderMode2 = @@ -250,8 +273,7 @@ static const char* const _renderMode2 = "OUT(0) out vec4 color;\n" "int fetchTile(ivec2 coord);\n" - "vec2 interpolate(ivec2 arr[4], float x);\n" - "void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n" + "ivec2 affineInterpolate();\n" "int renderTile(ivec2 coord) {\n" " int map = (coord.x >> 11) + (((coord.y >> 7) & 0x7F0) << size);\n" @@ -278,26 +300,7 @@ static const char* const _renderMode2 = "}\n" "void main() {\n" - " ivec2 mat[4];\n" - " ivec2 offset[4];\n" - " vec2 incoord = texCoord;\n" - " if (mosaic.x > 1) {\n" - " incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n" - " }\n" - " if (mosaic.y > 1) {\n" - " incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n" - " }\n" - " loadAffine(int(incoord.y), mat, offset);\n" - " float y = fract(incoord.y);\n" - " float start = 0.75;\n" - " if (int(incoord.y) - range.x < 4) {\n" - " y = incoord.y - float(range.x);\n" - " start = 0.;\n" - " }\n" - " float lin = start + y * 0.25;\n" - " vec2 mixedTransform = interpolate(mat, lin);\n" - " vec2 mixedOffset = interpolate(offset, lin);\n" - " int paletteEntry = fetchTile(ivec2(mixedTransform * incoord.x + mixedOffset));\n" + " int paletteEntry = fetchTile(affineInterpolate());\n" " color = texelFetch(palette, ivec2(paletteEntry, int(texCoord.y)), 0);\n" "}"; @@ -325,30 +328,10 @@ static const char* const _renderMode35 = "uniform ivec2 mosaic;\n" "OUT(0) out vec4 color;\n" - "vec2 interpolate(ivec2 arr[4], float x);\n" - "void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n" + "ivec2 affineInterpolate();\n" "void main() {\n" - " ivec2 mat[4];\n" - " ivec2 offset[4];\n" - " vec2 incoord = texCoord;\n" - " if (mosaic.x > 1) {\n" - " incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n" - " }\n" - " if (mosaic.y > 1) {\n" - " incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n" - " }\n" - " loadAffine(int(incoord.y), mat, offset);\n" - " float y = fract(incoord.y);\n" - " float start = 0.75;\n" - " if (int(incoord.y) - range.x < 4) {\n" - " y = incoord.y - float(range.x);\n" - " start = 0.;\n" - " }\n" - " float lin = start + y * 0.25;\n" - " vec2 mixedTransform = interpolate(mat, lin);\n" - " vec2 mixedOffset = interpolate(offset, lin);\n" - " ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n" + " ivec2 coord = affineInterpolate();\n" " if (coord.x < 0 || coord.x >= (size.x << 8)) {\n" " discard;\n" " }\n" @@ -386,30 +369,10 @@ static const char* const _renderMode4 = "uniform ivec2 mosaic;\n" "OUT(0) out vec4 color;\n" - "vec2 interpolate(ivec2 arr[4], float x);\n" - "void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n" + "ivec2 affineInterpolate();\n" "void main() {\n" - " ivec2 mat[4];\n" - " ivec2 offset[4];\n" - " vec2 incoord = texCoord;\n" - " if (mosaic.x > 1) {\n" - " incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n" - " }\n" - " if (mosaic.y > 1) {\n" - " incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n" - " }\n" - " loadAffine(int(incoord.y), mat, offset);\n" - " float y = fract(incoord.y);\n" - " float start = 0.75;\n" - " if (int(incoord.y) - range.x < 4) {\n" - " y = incoord.y - float(range.x);\n" - " start = 0.;\n" - " }\n" - " float lin = start + y * 0.25;\n" - " vec2 mixedTransform = interpolate(mat, lin);\n" - " vec2 mixedOffset = interpolate(offset, lin);\n" - " ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n" + " ivec2 coord = affineInterpolate();\n" " if (coord.x < 0 || coord.x >= (size.x << 8)) {\n" " discard;\n" " }\n" diff --git a/src/gba/savedata.c b/src/gba/savedata.c index 455ee0802..512af02c9 100644 --- a/src/gba/savedata.c +++ b/src/gba/savedata.c @@ -13,6 +13,10 @@ #include #include +#ifdef PSP2 +#include +#endif + #include #include @@ -184,7 +188,7 @@ size_t GBASavedataSize(const struct GBASavedata* savedata) { bool GBASavedataLoad(struct GBASavedata* savedata, struct VFile* in) { if (savedata->data) { - if (!in && savedata->type != SAVEDATA_FORCE_NONE) { + if (!in || savedata->type == SAVEDATA_FORCE_NONE) { return false; } ssize_t size = GBASavedataSize(savedata); @@ -637,6 +641,9 @@ void GBASavedataRTCRead(struct GBASavedata* savedata) { } LOAD_64LE(savedata->gpio->rtc.lastLatch, 0, &buffer.lastLatch); + time_t rtcTime; + +#ifndef PSP2 struct tm date; date.tm_year = _unBCD(savedata->gpio->rtc.time[0]) + 100; date.tm_mon = _unBCD(savedata->gpio->rtc.time[1]) - 1; @@ -645,8 +652,40 @@ void GBASavedataRTCRead(struct GBASavedata* savedata) { date.tm_min = _unBCD(savedata->gpio->rtc.time[5]); date.tm_sec = _unBCD(savedata->gpio->rtc.time[6]); date.tm_isdst = -1; + rtcTime = mktime(&date); +#else + struct SceDateTime date; + date.year = _unBCD(savedata->gpio->rtc.time[0]) + 2000; + date.month = _unBCD(savedata->gpio->rtc.time[1]); + date.day = _unBCD(savedata->gpio->rtc.time[2]); + date.hour = _unBCD(savedata->gpio->rtc.time[4]); + date.minute = _unBCD(savedata->gpio->rtc.time[5]); + date.second = _unBCD(savedata->gpio->rtc.time[6]); + date.microsecond = 0; - savedata->gpio->rtc.offset = savedata->gpio->rtc.lastLatch - mktime(&date); + struct SceRtcTick tick; + int res; + res = sceRtcConvertDateTimeToTick(&date, &tick); + if (res < 0) { + mLOG(GBA_SAVE, ERROR, "sceRtcConvertDateTimeToTick %lx", res); + } + res = sceRtcConvertLocalTimeToUtc(&tick, &tick); + if (res < 0) { + mLOG(GBA_SAVE, ERROR, "sceRtcConvertUtcToLocalTime %lx", res); + } + res = sceRtcConvertTickToDateTime(&tick, &date); + if (res < 0) { + mLOG(GBA_SAVE, ERROR, "sceRtcConvertTickToDateTime %lx", res); + } + res = sceRtcConvertDateTimeToTime_t(&date, &rtcTime); + if (res < 0) { + mLOG(GBA_SAVE, ERROR, "sceRtcConvertDateTimeToTime_t %lx", res); + } +#endif + + savedata->gpio->rtc.offset = savedata->gpio->rtc.lastLatch - rtcTime; + + mLOG(GBA_SAVE, ERROR, "Savegame time offset set to %li", savedata->gpio->rtc.offset); } void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state) { diff --git a/src/gba/sio.c b/src/gba/sio.c index d4df7e8da..9c47335dc 100644 --- a/src/gba/sio.c +++ b/src/gba/sio.c @@ -184,13 +184,13 @@ void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) { switch (sio->mode) { case SIO_NORMAL_8: case SIO_NORMAL_32: - value |= 0x0004; + value = GBASIONormalFillSi(value); if ((value & 0x0081) == 0x0081) { - if (value & 0x4000) { + if (GBASIONormalIsIrq(value)) { // TODO: Test this on hardware to see if this is correct GBARaiseIRQ(sio->p, GBA_IRQ_SIO, 0); } - value &= ~0x0080; + value = GBASIONormalClearStart(value); } break; case SIO_MULTI: diff --git a/src/gba/sio/lockstep.c b/src/gba/sio/lockstep.c index bc5a5fd33..10f1d7f00 100644 --- a/src/gba/sio/lockstep.c +++ b/src/gba/sio/lockstep.c @@ -111,11 +111,27 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) { if (node->id) { node->d.p->rcnt |= 4; node->d.p->siocnt = GBASIOMultiplayerFillSlave(node->d.p->siocnt); + + int try; + for (try = 0; try < 3; ++try) { + uint16_t masterSiocnt; + ATOMIC_LOAD(masterSiocnt, node->p->players[0]->d.p->siocnt); + if (ATOMIC_CMPXCHG(node->p->players[0]->d.p->siocnt, masterSiocnt, GBASIOMultiplayerClearSlave(masterSiocnt))) { + break; + } + } + } else { + node->d.p->rcnt &= ~4; + node->d.p->siocnt = GBASIOMultiplayerClearSlave(node->d.p->siocnt); } break; case SIO_NORMAL_8: case SIO_NORMAL_32: - ATOMIC_ADD(node->p->attachedNormal, 1); + if (ATOMIC_ADD(node->p->attachedNormal, 1) > node->id + 1 && node->id > 0) { + node->d.p->siocnt = GBASIONormalSetSi(node->d.p->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt)); + } else { + node->d.p->siocnt = GBASIONormalClearSi(node->d.p->siocnt); + } node->d.writeRegister = GBASIOLockstepNodeNormalWriteRegister; break; default: @@ -447,7 +463,7 @@ static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, struct GBASIOLockstepNode* node = user; mLockstepLock(&node->p->d); - int32_t cycles = cycles = node->nextEvent; + int32_t cycles = node->nextEvent; node->nextEvent -= cyclesLate; node->eventDiff += cyclesLate; if (node->p->d.attached < 2) { @@ -495,11 +511,28 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive if (address == REG_SIOCNT) { mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04X", node->id, value); + int attached; + ATOMIC_LOAD(attached, node->p->attachedNormal); value &= 0xFF8B; - if (!node->id) { + if (node->id > 0) { + value = GBASIONormalSetSi(value, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt)); + } else { value = GBASIONormalClearSi(value); } - if (value & 0x0080) { + + enum mLockstepPhase transferActive; + ATOMIC_LOAD(transferActive, node->p->d.transferActive); + if (node->id < 3 && attached > node->id + 1 && transferActive == TRANSFER_IDLE) { + int try; + for (try = 0; try < 3; ++try) { + GBASIONormal nextSiocnct; + ATOMIC_LOAD(nextSiocnct, node->p->players[node->id + 1]->d.p->siocnt); + if (ATOMIC_CMPXCHG(node->p->players[node->id + 1]->d.p->siocnt, nextSiocnct, GBASIONormalSetSi(nextSiocnct, GBASIONormalGetIdleSo(value)))) { + break; + } + } + } + if ((value & 0x0081) == 0x0081) { if (!node->id) { // Frequency int32_t cycles; @@ -512,9 +545,6 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive cycles *= 4; } - enum mLockstepPhase transferActive; - ATOMIC_LOAD(transferActive, node->p->d.transferActive); - if (transferActive == TRANSFER_IDLE) { mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id); ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING); @@ -529,7 +559,7 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive value &= ~0x0080; } } else { - + // TODO } } } else if (address == REG_SIODATA32_LO) { diff --git a/src/gba/timer.c b/src/gba/timer.c index 8a3a3e8f6..da054e12a 100644 --- a/src/gba/timer.c +++ b/src/gba/timer.c @@ -34,7 +34,7 @@ static void GBATimerUpdate(struct GBA* gba, int timerId, uint32_t cyclesLate) { if (timerId < 3) { struct GBATimer* nextTimer = &gba->timers[timerId + 1]; - if (GBATimerFlagsIsCountUp(nextTimer->flags)) { // TODO: Does this increment while disabled? + if (GBATimerFlagsIsCountUp(nextTimer->flags) && GBATimerFlagsIsEnable(nextTimer->flags)) { ++gba->memory.io[REG_TMCNT_LO(timerId + 1) >> 1]; if (!gba->memory.io[REG_TMCNT_LO(timerId + 1) >> 1] && GBATimerFlagsIsEnable(nextTimer->flags)) { GBATimerUpdate(gba, timerId + 1, cyclesLate); diff --git a/src/platform/3ds/gui-font.c b/src/platform/3ds/gui-font.c index 668c7382d..c9846c628 100644 --- a/src/platform/3ds/gui-font.c +++ b/src/platform/3ds/gui-font.c @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include -#include +#include #include #include "icons.h" diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 5356130ce..470c50db7 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -496,7 +496,7 @@ static void _drawTex(struct mCore* core, bool faded, bool both) { int wide = isWide ? 2 : 1; unsigned corew, coreh; - core->desiredVideoDimensions(core, &corew, &coreh); + core->currentVideoSize(core, &corew, &coreh); int w = corew; int h = coreh; @@ -872,7 +872,7 @@ int main(int argc, char* argv[]) { u8 model = 0; cfguInit(); CFGU_GetSystemModel(&model); - if (model != 3 /* o2DS */) { + if (model != CFG_MODEL_2DS) { gfxSetWide(true); } cfguExit(); diff --git a/src/platform/example/client-server/server.c b/src/platform/example/client-server/server.c index ba0347b69..0dcec9b15 100644 --- a/src/platform/example/client-server/server.c +++ b/src/platform/example/client-server/server.c @@ -83,7 +83,7 @@ bool _mExampleRun(const struct mArguments* args, Socket client) { // Get the dimensions required for this core and send them to the client. unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->baseVideoSize(core, &width, &height); ssize_t bufferSize = width * height * BYTES_PER_PIXEL; uint32_t sendNO; sendNO = htonl(width); diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 68f54f2c7..8c08e7bb6 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -346,7 +346,7 @@ static void _doDeferredSetup(void) { // On the off-hand chance that a core actually expects its buffers to be populated when // you actually first get them, you're out of luck without workarounds. Yup, seriously. // Here's that workaround, but really the API needs to be thrown out and rewritten. - struct VFile* save = VFileFromMemory(savedata, SIZE_CART_FLASH1M); + struct VFile* save = VFileFromMemory(savedata, GBA_SIZE_FLASH1M); if (!core->loadSave(core, save)) { save->close(save); } @@ -414,19 +414,13 @@ void retro_get_system_info(struct retro_system_info* info) { void retro_get_system_av_info(struct retro_system_av_info* info) { unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); info->geometry.base_width = width; info->geometry.base_height = height; -#ifdef M_CORE_GB - if (core->platform(core) == mPLATFORM_GB) { - info->geometry.max_width = 256; - info->geometry.max_height = 224; - } else -#endif - { - info->geometry.max_width = width; - info->geometry.max_height = height; - } + + core->baseVideoSize(core, &width, &height); + info->geometry.max_width = width; + info->geometry.max_height = height; info->geometry.aspect_ratio = width / (double) height; info->timing.fps = core->frequency(core) / (float) core->frameCycles(core); @@ -614,7 +608,7 @@ void retro_run(void) { core->runFrame(core); unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); videoCallback(outputBuffer, width, height, BYTES_PER_PIXEL * 256); #ifdef M_CORE_GBA @@ -930,8 +924,8 @@ bool retro_load_game(const struct retro_game_info* game) { core->setPeripheral(core, mPERIPH_RUMBLE, &rumble); core->setPeripheral(core, mPERIPH_ROTATION, &rotation); - savedata = anonymousMemoryMap(SIZE_CART_FLASH1M); - memset(savedata, 0xFF, SIZE_CART_FLASH1M); + savedata = anonymousMemoryMap(GBA_SIZE_FLASH1M); + memset(savedata, 0xFF, GBA_SIZE_FLASH1M); _reloadSettings(); core->loadROM(core, rom); @@ -1008,7 +1002,7 @@ void retro_unload_game(void) { core->deinit(core); mappedMemoryFree(data, dataSize); data = 0; - mappedMemoryFree(savedata, SIZE_CART_FLASH1M); + mappedMemoryFree(savedata, GBA_SIZE_FLASH1M); savedata = 0; } @@ -1163,7 +1157,7 @@ size_t retro_get_memory_size(unsigned id) { case mPLATFORM_GBA: switch (((struct GBA*) core->board)->memory.savedata.type) { case SAVEDATA_AUTODETECT: - return SIZE_CART_FLASH1M; + return GBA_SIZE_FLASH1M; default: return GBASavedataSize(&((struct GBA*) core->board)->memory.savedata); } @@ -1362,7 +1356,7 @@ static void _startImage(struct mImageSource* image, unsigned w, unsigned h, int static void _stopImage(struct mImageSource* image) { UNUSED(image); - cam.stop(); + cam.stop(); } static void _requestImage(struct mImageSource* image, const void** buffer, size_t* stride, enum mColorFormat* colorFormat) { diff --git a/src/platform/openemu/mGBAGameCore.m b/src/platform/openemu/mGBAGameCore.m index 36bb3464a..7656a8945 100644 --- a/src/platform/openemu/mGBAGameCore.m +++ b/src/platform/openemu/mGBAGameCore.m @@ -68,7 +68,7 @@ outputBuffer = nil; unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->baseVideoSize(core, &width, &height); outputBuffer = malloc(width * height * BYTES_PER_PIXEL); core->setVideoBuffer(core, outputBuffer, width); core->setAudioBufferSize(core, SAMPLES); @@ -143,14 +143,14 @@ - (OEIntRect)screenRect { unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); return OEIntRectMake(0, 0, width, height); } - (OEIntSize)bufferSize { unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->baseVideoSize(core, &width, &height); return OEIntSizeMake(width, height); } diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index 76089c4c0..fe24d04cf 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -9,9 +9,9 @@ static const GLint _glVertices[] = { 0, 0, - 256, 0, - 256, 256, - 0, 256 + 1, 0, + 1, 1, + 0, 1 }; static const GLint _glTexCoords[] = { @@ -21,76 +21,102 @@ static const GLint _glTexCoords[] = { 0, 1 }; +static inline void _initTex(void) { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); +#ifndef _WIN32 + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#endif +} + static void mGLContextInit(struct VideoBackend* v, WHandle handle) { UNUSED(handle); struct mGLContext* context = (struct mGLContext*) v; - v->width = 1; - v->height = 1; + memset(context->layerDims, 0, sizeof(context->layerDims)); + memset(context->imageSizes, -1, sizeof(context->imageSizes)); glGenTextures(2, context->tex); glBindTexture(GL_TEXTURE_2D, context->tex[0]); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); -#ifndef _WIN32 - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#endif + _initTex(); glBindTexture(GL_TEXTURE_2D, context->tex[1]); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); -#ifndef _WIN32 - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#endif + _initTex(); context->activeTex = 0; + + glGenTextures(VIDEO_LAYER_MAX, context->tex); + int i; + for (i = 0; i < VIDEO_LAYER_MAX; ++i) { + glBindTexture(GL_TEXTURE_2D, context->layers[i]); + _initTex(); + } } -static void mGLContextSetDimensions(struct VideoBackend* v, unsigned width, unsigned height) { +static inline void _setTexDims(int width, int height) { +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); +#endif +#elif defined(__BIG_ENDIAN__) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); +#endif +} + +static void mGLContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct mRectangle* dims) { struct mGLContext* context = (struct mGLContext*) v; - if (width == v->width && height == v->height) { + if (layer >= VIDEO_LAYER_MAX) { return; } - v->width = width; - v->height = height; + context->layerDims[layer].x = dims->x; + context->layerDims[layer].y = dims->y; + if (dims->width == context->layerDims[layer].width && dims->height == context->layerDims[layer].height) { + return; + } + context->layerDims[layer].width = dims->width; + context->layerDims[layer].height = dims->height; - glBindTexture(GL_TEXTURE_2D, context->tex[0]); -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); -#endif -#elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); -#endif + if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) { + if (layer == VIDEO_LAYER_IMAGE) { + glBindTexture(GL_TEXTURE_2D, context->tex[0]); + _setTexDims(dims->width, dims->height); + glBindTexture(GL_TEXTURE_2D, context->tex[1]); + _setTexDims(dims->width, dims->height); + } else { + glBindTexture(GL_TEXTURE_2D, context->layers[layer]); + _setTexDims(dims->width, dims->height); + } + } +} - glBindTexture(GL_TEXTURE_2D, context->tex[1]); -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); -#endif -#elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); -#endif +static void mGLContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct mRectangle* dims) { + struct mGLContext* context = (struct mGLContext*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + memcpy(dims, &context->layerDims[layer], sizeof(*dims)); } static void mGLContextDeinit(struct VideoBackend* v) { struct mGLContext* context = (struct mGLContext*) v; glDeleteTextures(2, context->tex); + glDeleteTextures(VIDEO_LAYER_MAX, context->layers); } static void mGLContextResized(struct VideoBackend* v, unsigned w, unsigned h) { unsigned drawW = w; unsigned drawH = h; + + unsigned maxW; + unsigned maxH; + VideoBackendGetFrameSize(v, &maxW, &maxH); + if (v->lockAspectRatio) { - lockAspectRatioUInt(v->width, v->height, &drawW, &drawH); + lockAspectRatioUInt(maxW, maxH, &drawW, &drawH); } if (v->lockIntegerScaling) { - lockIntegerRatioUInt(v->width, &drawW); - lockIntegerRatioUInt(v->height, &drawH); + lockIntegerRatioUInt(maxW, &drawW); + lockIntegerRatioUInt(maxH, &drawH); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); @@ -105,33 +131,7 @@ static void mGLContextClear(struct VideoBackend* v) { glClear(GL_COLOR_BUFFER_BIT); } -void mGLContextDrawFrame(struct VideoBackend* v) { - struct mGLContext* context = (struct mGLContext*) v; - glEnable(GL_TEXTURE_2D); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_VERTEX_ARRAY); - glVertexPointer(2, GL_INT, 0, _glVertices); - glTexCoordPointer(2, GL_INT, 0, _glTexCoords); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, v->width, v->height, 0, 0, 1); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - if (v->interframeBlending) { - glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); - glBlendColor(1, 1, 1, 0.5); - glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex ^ 1]); - if (v->filter) { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } else { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - } - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glEnable(GL_BLEND); - } - glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]); +static void _setFilter(struct VideoBackend* v) { if (v->filter) { glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -139,36 +139,157 @@ void mGLContextDrawFrame(struct VideoBackend* v) { glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glDisable(GL_BLEND); } -void mGLContextPostFrame(struct VideoBackend* v, const void* frame) { +static void _setFrame(struct mRectangle* dims, struct mRectangle* frame) { + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + glScissor(viewport[0] + (dims->x - frame->x) * viewport[2] / frame->width, + viewport[1] + (frame->height + frame->y - dims->height - dims->y) * viewport[3] / frame->height, + dims->width * viewport[2] / frame->width, + dims->height * viewport[3] / frame->height); + glLoadIdentity(); + glTranslatef(dims->x, dims->y, 0); + glScalef(toPow2(dims->width), toPow2(dims->height), 1); +} + +static void _drawLayers(struct mGLContext* context, int start, int end) { + struct mRectangle frame; + VideoBackendGetFrame(&context->d, &frame); + + int layer; + for (layer = start; layer < end; ++layer) { + if (context->layerDims[layer].width < 1 || context->layerDims[layer].height < 1) { + continue; + } + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBindTexture(GL_TEXTURE_2D, context->layers[layer]); + _setFilter(&context->d); + _setFrame(&context->layerDims[layer], &frame); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } +} + +void mGLContextDrawFrame(struct VideoBackend* v) { struct mGLContext* context = (struct mGLContext*) v; - context->activeTex ^= 1; + glEnable(GL_TEXTURE_2D); + glEnable(GL_SCISSOR_TEST); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(2, GL_INT, 0, _glVertices); + glTexCoordPointer(2, GL_INT, 0, _glTexCoords); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + struct mRectangle frame; + VideoBackendGetFrame(v, &frame); + glOrtho(frame.x, frame.x + frame.width, frame.y + frame.height, frame.y, 0, 1); + glMatrixMode(GL_MODELVIEW); + + _drawLayers(context, 0, VIDEO_LAYER_IMAGE); + + _setFrame(&context->layerDims[VIDEO_LAYER_IMAGE], &frame); + glDisable(GL_BLEND); + if (v->interframeBlending) { + glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex ^ 1]); + _setFilter(v); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + glEnable(GL_BLEND); + glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); + glBlendColor(1, 1, 1, 0.5); + } + glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]); + _setFilter(v); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + + _drawLayers(context, VIDEO_LAYER_IMAGE + 1, VIDEO_LAYER_MAX); +} + +static void mGLContextSetImageSize(struct VideoBackend* v, enum VideoLayer layer, int width, int height) { + struct mGLContext* context = (struct mGLContext*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + if (width <= 0 || height <= 0) { + context->imageSizes[layer].width = -1; + context->imageSizes[layer].height = -1; + width = context->layerDims[layer].width; + height = context->layerDims[layer].height; + } else { + context->imageSizes[layer].width = width; + context->imageSizes[layer].height = height; + } + if (layer == VIDEO_LAYER_IMAGE) { + glBindTexture(GL_TEXTURE_2D, context->tex[0]); + _setTexDims(width, height); + glBindTexture(GL_TEXTURE_2D, context->tex[1]); + _setTexDims(width, height); + } else { + glBindTexture(GL_TEXTURE_2D, context->layers[layer]); + _setTexDims(width, height); + } +} + +static void mGLContextImageSize(struct VideoBackend* v, enum VideoLayer layer, int* width, int* height) { + struct mGLContext* context = (struct mGLContext*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + + if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) { + *width = context->layerDims[layer].width; + *height = context->layerDims[layer].height; + } else { + *width = context->imageSizes[layer].width; + *height = context->imageSizes[layer].height; + } +} + +void mGLContextPostFrame(struct VideoBackend* v, enum VideoLayer layer, const void* frame) { + struct mGLContext* context = (struct mGLContext*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + if (layer == VIDEO_LAYER_IMAGE) { + context->activeTex ^= 1; + glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]); + } else { + glBindTexture(GL_TEXTURE_2D, context->layers[layer]); + } + + int width = context->imageSizes[layer].width; + int height = context->imageSizes[layer].height; + + if (width <= 0 || height <= 0) { + width = context->layerDims[layer].width; + height = context->layerDims[layer].height; + } #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); #else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); #endif #elif defined(__BIG_ENDIAN__) - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); #else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_BYTE, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, frame); #endif } void mGLContextCreate(struct mGLContext* context) { context->d.init = mGLContextInit; context->d.deinit = mGLContextDeinit; - context->d.setDimensions = mGLContextSetDimensions; - context->d.resized = mGLContextResized; - context->d.swap = 0; + context->d.setLayerDimensions = mGLContextSetLayerDimensions; + context->d.layerDimensions = mGLContextLayerDimensions; + context->d.contextResized = mGLContextResized; + context->d.swap = NULL; context->d.clear = mGLContextClear; - context->d.postFrame = mGLContextPostFrame; + context->d.setImageSize = mGLContextSetImageSize; + context->d.imageSize = mGLContextImageSize; + context->d.setImage = mGLContextPostFrame; context->d.drawFrame = mGLContextDrawFrame; - context->d.setMessage = 0; - context->d.clearMessage = 0; } diff --git a/src/platform/opengl/gl.h b/src/platform/opengl/gl.h index 3ff528864..28bb5249d 100644 --- a/src/platform/opengl/gl.h +++ b/src/platform/opengl/gl.h @@ -21,13 +21,16 @@ CXX_GUARD_START #include #endif -#include "platform/video-backend.h" +#include struct mGLContext { struct VideoBackend d; - GLuint tex[2]; int activeTex; + GLuint tex[2]; + GLuint layers[VIDEO_LAYER_MAX]; + struct mRectangle layerDims[VIDEO_LAYER_MAX]; + struct mSize imageSizes[VIDEO_LAYER_MAX]; }; void mGLContextCreate(struct mGLContext*); diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 4277b7b76..8ef11e958 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -81,6 +81,15 @@ static const char* const _nullFragmentShader = " gl_FragColor = color;\n" "}"; +static const char* const _thruFragmentShader = + "varying vec2 texCoord;\n" + "uniform sampler2D tex;\n" + + "void main() {\n" + " vec4 color = texture2D(tex, texCoord);\n" + " gl_FragColor = color;\n" + "}"; + static const char* const _interframeFragmentShader = "varying vec2 texCoord;\n" "uniform sampler2D tex;\n" @@ -101,12 +110,15 @@ static const GLfloat _vertices[] = { static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) { UNUSED(handle); struct mGLES2Context* context = (struct mGLES2Context*) v; - v->width = 1; - v->height = 1; - glGenTextures(1, &context->tex); - glBindTexture(GL_TEXTURE_2D, context->tex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + memset(context->layerDims, 0, sizeof(context->layerDims)); + + glGenTextures(VIDEO_LAYER_MAX, context->tex); + int i; + for (i = 0; i < VIDEO_LAYER_MAX; ++i) { + glBindTexture(GL_TEXTURE_2D, context->tex[i]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } glGenBuffers(1, &context->vbo); glBindBuffer(GL_ARRAY_BUFFER, context->vbo); @@ -156,8 +168,9 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) { uniforms[3].max.fvec3[1] = 1.0f; uniforms[3].max.fvec3[2] = 1.0f; mGLES2ShaderInit(&context->initialShader, _vertexShader, _fragmentShader, -1, -1, false, uniforms, 4); - mGLES2ShaderInit(&context->finalShader, 0, 0, 0, 0, false, 0, 0); - mGLES2ShaderInit(&context->interframeShader, 0, _interframeFragmentShader, -1, -1, false, 0, 0); + mGLES2ShaderInit(&context->finalShader, 0, 0, 0, 0, false, NULL, 0); + mGLES2ShaderInit(&context->interframeShader, 0, _interframeFragmentShader, -1, -1, false, NULL, 0); + mGLES2ShaderInit(&context->overlayShader, _vertexShader, _thruFragmentShader, -1, -1, false, NULL, 0); #ifdef BUILD_GLES3 if (context->initialShader.vao != (GLuint) -1) { @@ -167,25 +180,24 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) { glBindBuffer(GL_ARRAY_BUFFER, context->vbo); glBindVertexArray(context->interframeShader.vao); glBindBuffer(GL_ARRAY_BUFFER, context->vbo); + glBindVertexArray(context->overlayShader.vao); + glBindBuffer(GL_ARRAY_BUFFER, context->vbo); glBindVertexArray(0); } #endif + glDeleteFramebuffers(1, &context->overlayShader.fbo); + glDeleteTextures(1, &context->overlayShader.tex); + context->overlayShader.fbo = context->initialShader.fbo; + context->overlayShader.tex = context->initialShader.tex; + glDeleteFramebuffers(1, &context->finalShader.fbo); glDeleteTextures(1, &context->finalShader.tex); context->finalShader.fbo = 0; context->finalShader.tex = 0; } -static void mGLES2ContextSetDimensions(struct VideoBackend* v, unsigned width, unsigned height) { - struct mGLES2Context* context = (struct mGLES2Context*) v; - if (width == v->width && height == v->height) { - return; - } - v->width = width; - v->height = height; - - glBindTexture(GL_TEXTURE_2D, context->tex); +static inline void _setTexDims(int width, int height) { #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); @@ -197,24 +209,61 @@ static void mGLES2ContextSetDimensions(struct VideoBackend* v, unsigned width, u #else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); #endif +} - size_t n; - for (n = 0; n < context->nShaders; ++n) { - if (context->shaders[n].width < 0 || context->shaders[n].height < 0) { - context->shaders[n].dirty = true; +static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct mRectangle* dims) { + struct mGLES2Context* context = (struct mGLES2Context*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + if (dims->width != context->layerDims[layer].width && dims->height != context->layerDims[layer].height) { + context->layerDims[layer].width = dims->width; + context->layerDims[layer].height = dims->height; + + glBindTexture(GL_TEXTURE_2D, context->tex[layer]); + if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) { + _setTexDims(dims->width, dims->height); } } - context->initialShader.dirty = true; - context->interframeShader.dirty = true; + + context->layerDims[layer].x = dims->x; + context->layerDims[layer].y = dims->y; + + struct mRectangle frame; + VideoBackendGetFrame(v, &frame); + if (frame.width != context->width || frame.height != context->height) { + size_t n; + for (n = 0; n < context->nShaders; ++n) { + if (context->shaders[n].width < 0 || context->shaders[n].height < 0) { + context->shaders[n].dirty = true; + } + } + glBindTexture(GL_TEXTURE_2D, context->initialShader.tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, frame.width, frame.height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); + context->width = frame.width; + context->height = frame.height; + } + context->x = frame.x; + context->y = frame.y; +} + +static void mGLES2ContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct mRectangle* dims) { + struct mGLES2Context* context = (struct mGLES2Context*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + memcpy(dims, &context->layerDims[layer], sizeof(*dims)); } static void mGLES2ContextDeinit(struct VideoBackend* v) { struct mGLES2Context* context = (struct mGLES2Context*) v; - glDeleteTextures(1, &context->tex); + glDeleteTextures(VIDEO_LAYER_MAX, context->tex); glDeleteBuffers(1, &context->vbo); mGLES2ShaderDeinit(&context->initialShader); mGLES2ShaderDeinit(&context->finalShader); mGLES2ShaderDeinit(&context->interframeShader); + context->overlayShader.fbo = 0; + mGLES2ShaderDeinit(&context->overlayShader); free(context->initialShader.uniforms); } @@ -222,12 +271,16 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h) struct mGLES2Context* context = (struct mGLES2Context*) v; unsigned drawW = w; unsigned drawH = h; + + unsigned maxW = context->width; + unsigned maxH = context->height; + if (v->lockAspectRatio) { - lockAspectRatioUInt(v->width, v->height, &drawW, &drawH); + lockAspectRatioUInt(maxW, maxH, &drawW, &drawH); } if (v->lockIntegerScaling) { - lockIntegerRatioUInt(v->width, &drawW); - lockIntegerRatioUInt(v->height, &drawH); + lockIntegerRatioUInt(maxW, &drawW); + lockIntegerRatioUInt(maxH, &drawH); } size_t n; for (n = 0; n < context->nShaders; ++n) { @@ -249,7 +302,7 @@ static void mGLES2ContextClear(struct VideoBackend* v) { glClear(GL_COLOR_BUFFER_BIT); } -void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { +static void _drawShaderEx(struct mGLES2Context* context, struct mGLES2Shader* shader, int layer) { GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); int drawW = shader->width; @@ -260,19 +313,19 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { drawW = viewport[2]; padW = viewport[0]; } else if (shader->width < 0) { - drawW = context->d.width * -shader->width; + drawW = context->width * -shader->width; } if (!drawH) { drawH = viewport[3]; padH = viewport[1]; } else if (shader->height < 0) { - drawH = context->d.height * -shader->height; + drawH = context->height * -shader->height; } if (shader->integerScaling) { padW = 0; padH = 0; - drawW -= drawW % context->d.width; - drawH -= drawH % context->d.height; + drawW -= drawW % context->width; + drawH -= drawH % context->height; } if (shader->dirty) { @@ -286,22 +339,30 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { shader->dirty = false; } + if (layer >= 0 && layer < VIDEO_LAYER_MAX) { + glViewport(context->layerDims[layer].x - context->x, context->height - context->layerDims[layer].y - context->layerDims[layer].height + context->y, context->layerDims[layer].width, context->layerDims[layer].height); + } else { + glViewport(padW, padH, drawW, drawH); + } + glBindFramebuffer(GL_FRAMEBUFFER, shader->fbo); - glViewport(padW, padH, drawW, drawH); if (shader->blend) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } else { glDisable(GL_BLEND); - glClearColor(0.f, 0.f, 0.f, 1.f); - glClear(GL_COLOR_BUFFER_BIT); + if (layer <= VIDEO_LAYER_BACKGROUND) { + glClearColor(0.f, 0.f, 0.f, 1.f); + glClear(GL_COLOR_BUFFER_BIT); + } } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glUseProgram(shader->program); glUniform1i(shader->texLocation, 0); - glUniform2f(shader->texSizeLocation, context->d.width - padW, context->d.height - padH); + glUniform2f(shader->texSizeLocation, context->width, context->height); + glUniform2f(shader->outputSizeLocation, drawW, drawH); #ifdef BUILD_GLES3 if (shader->vao != (GLuint) -1) { glBindVertexArray(shader->vao); @@ -367,21 +428,41 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { glBindTexture(GL_TEXTURE_2D, shader->tex); } +static void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { + _drawShaderEx(context, shader, -1); +} + void mGLES2ContextDrawFrame(struct VideoBackend* v) { struct mGLES2Context* context = (struct mGLES2Context*) v; glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, context->tex); GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); context->finalShader.filter = v->filter; - _drawShader(context, &context->initialShader); - if (v->interframeBlending) { - context->interframeShader.blend = true; - glViewport(0, 0, viewport[2], viewport[3]); - _drawShader(context, &context->interframeShader); + + int layer; + for (layer = 0; layer < VIDEO_LAYER_MAX; ++layer) { + if (context->layerDims[layer].width < 1 || context->layerDims[layer].height < 1) { + continue; + } + glBindTexture(GL_TEXTURE_2D, context->tex[layer]); + if (layer != VIDEO_LAYER_IMAGE) { + context->overlayShader.blend = layer > VIDEO_LAYER_BACKGROUND; + _drawShaderEx(context, &context->overlayShader, layer); + } else { + _drawShaderEx(context, &context->initialShader, layer); + } + if (layer != VIDEO_LAYER_IMAGE) { + continue; + } + if (v->interframeBlending) { + context->interframeShader.blend = true; + glViewport(0, 0, viewport[2], viewport[3]); + _drawShader(context, &context->interframeShader); + } } + size_t n; for (n = 0; n < context->nShaders; ++n) { glViewport(0, 0, viewport[2], viewport[3]); @@ -391,8 +472,8 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { _drawShader(context, &context->finalShader); if (v->interframeBlending) { context->interframeShader.blend = false; - glBindTexture(GL_TEXTURE_2D, context->tex); - _drawShader(context, &context->initialShader); + glBindTexture(GL_TEXTURE_2D, context->tex[VIDEO_LAYER_IMAGE]); + _drawShaderEx(context, &context->initialShader, VIDEO_LAYER_IMAGE); glViewport(0, 0, viewport[2], viewport[3]); _drawShader(context, &context->interframeShader); } @@ -405,33 +486,79 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { #endif } -void mGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) { +static void mGLES2ContextSetImageSize(struct VideoBackend* v, enum VideoLayer layer, int width, int height) { struct mGLES2Context* context = (struct mGLES2Context*) v; - glBindTexture(GL_TEXTURE_2D, context->tex); + if (layer >= VIDEO_LAYER_MAX) { + return; + } + + glBindTexture(GL_TEXTURE_2D, context->tex[layer]); + if (width <= 0 || height <= 0) { + context->imageSizes[layer].width = -1; + context->imageSizes[layer].height = -1; + width = context->layerDims[layer].width; + height = context->layerDims[layer].height; + } else { + context->imageSizes[layer].width = width; + context->imageSizes[layer].height = height; + } + _setTexDims(width, height); +} + +static void mGLES2ContextImageSize(struct VideoBackend* v, enum VideoLayer layer, int* width, int* height) { + struct mGLES2Context* context = (struct mGLES2Context*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + + if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) { + *width = context->layerDims[layer].width; + *height = context->layerDims[layer].height; + } else { + *width = context->imageSizes[layer].width; + *height = context->imageSizes[layer].height; + } +} + +void mGLES2ContextPostFrame(struct VideoBackend* v, enum VideoLayer layer, const void* frame) { + struct mGLES2Context* context = (struct mGLES2Context*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + + int width = context->imageSizes[layer].width; + int height = context->imageSizes[layer].height; + + if (width <= 0 || height <= 0) { + width = context->layerDims[layer].width; + height = context->layerDims[layer].height; + } + glBindTexture(GL_TEXTURE_2D, context->tex[layer]); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); #endif #elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); #endif } void mGLES2ContextCreate(struct mGLES2Context* context) { context->d.init = mGLES2ContextInit; context->d.deinit = mGLES2ContextDeinit; - context->d.setDimensions = mGLES2ContextSetDimensions; - context->d.resized = mGLES2ContextResized; - context->d.swap = 0; + context->d.setLayerDimensions = mGLES2ContextSetLayerDimensions; + context->d.layerDimensions = mGLES2ContextLayerDimensions; + context->d.contextResized = mGLES2ContextResized; + context->d.swap = NULL; context->d.clear = mGLES2ContextClear; - context->d.postFrame = mGLES2ContextPostFrame; + context->d.setImageSize = mGLES2ContextSetImageSize; + context->d.imageSize = mGLES2ContextImageSize; + context->d.setImage = mGLES2ContextPostFrame; context->d.drawFrame = mGLES2ContextDrawFrame; - context->d.setMessage = 0; - context->d.clearMessage = 0; context->shaders = 0; context->nShaders = 0; } @@ -532,6 +659,7 @@ void mGLES2ShaderInit(struct mGLES2Shader* shader, const char* vs, const char* f shader->texLocation = glGetUniformLocation(shader->program, "tex"); shader->texSizeLocation = glGetUniformLocation(shader->program, "texSize"); shader->positionLocation = glGetAttribLocation(shader->program, "position"); + shader->outputSizeLocation = glGetUniformLocation(shader->program, "outputSize"); size_t i; for (i = 0; i < shader->nUniforms; ++i) { shader->uniforms[i].location = glGetUniformLocation(shader->program, shader->uniforms[i].name); @@ -555,10 +683,14 @@ void mGLES2ShaderInit(struct mGLES2Shader* shader, const char* vs, const char* f } void mGLES2ShaderDeinit(struct mGLES2Shader* shader) { - glDeleteTextures(1, &shader->tex); + if (shader->tex) { + glDeleteTextures(1, &shader->tex); + } glDeleteShader(shader->fragmentShader); glDeleteProgram(shader->program); - glDeleteFramebuffers(1, &shader->fbo); + if (shader->fbo) { + glDeleteFramebuffers(1, &shader->fbo); + } #ifdef BUILD_GLES3 if (shader->vao != (GLuint) -1) { glDeleteVertexArrays(1, &shader->vao); @@ -1010,8 +1142,11 @@ bool mGLES2ShaderLoad(struct VideoShader* shader, struct VDir* dir) { } } u = mGLES2UniformListSize(&uniformVector); - struct mGLES2Uniform* uniformBlock = calloc(u, sizeof(*uniformBlock)); - memcpy(uniformBlock, mGLES2UniformListGetPointer(&uniformVector, 0), sizeof(*uniformBlock) * u); + struct mGLES2Uniform* uniformBlock; + if (u) { + uniformBlock = calloc(u, sizeof(*uniformBlock)); + memcpy(uniformBlock, mGLES2UniformListGetPointer(&uniformVector, 0), sizeof(*uniformBlock) * u); + } mGLES2UniformListDeinit(&uniformVector); mGLES2ShaderInit(&shaderBlock[n], vssrc, fssrc, width, height, scaling, uniformBlock, u); @@ -1048,6 +1183,7 @@ bool mGLES2ShaderLoad(struct VideoShader* shader, struct VDir* dir) { for (n = 0; n < inShaders; ++n) { mGLES2ShaderDeinit(&shaderBlock[n]); } + free(shaderBlock); } } } diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h index 144a64a13..d98ff22c4 100644 --- a/src/platform/opengl/gles2.h +++ b/src/platform/opengl/gles2.h @@ -24,7 +24,7 @@ CXX_GUARD_START #include #endif -#include "platform/video-backend.h" +#include union mGLES2UniformValue { GLfloat f; @@ -70,6 +70,7 @@ struct mGLES2Shader { GLuint texLocation; GLuint texSizeLocation; GLuint positionLocation; + GLuint outputSizeLocation; struct mGLES2Uniform* uniforms; size_t nUniforms; @@ -78,12 +79,20 @@ struct mGLES2Shader { struct mGLES2Context { struct VideoBackend d; - GLuint tex; + GLuint tex[VIDEO_LAYER_MAX]; GLuint vbo; + struct mRectangle layerDims[VIDEO_LAYER_MAX]; + struct mSize imageSizes[VIDEO_LAYER_MAX]; + int x; + int y; + unsigned width; + unsigned height; + struct mGLES2Shader initialShader; struct mGLES2Shader finalShader; struct mGLES2Shader interframeShader; + struct mGLES2Shader overlayShader; struct mGLES2Shader* shaders; size_t nShaders; diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index fa9847f14..3e638aa64 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -28,6 +28,7 @@ set(OS_LIB -lvita2d -l${M_LIBRARY} -lScePgf_stub -lScePhotoExport_stub -lScePower_stub + -lSceRtc_stub -lSceSysmodule_stub -lSceTouch_stub) set(OS_LIB ${OS_LIB} PARENT_SCOPE) diff --git a/src/platform/psp2/psp2-context.c b/src/platform/psp2/psp2-context.c index f860bc4a1..a7504f7cc 100644 --- a/src/platform/psp2/psp2-context.c +++ b/src/platform/psp2/psp2-context.c @@ -129,7 +129,7 @@ static THREAD_ENTRY _audioThread(void* context) { sceAudioOutOutput(audioPort, buffer); } sceAudioOutReleasePort(audioPort); - return 0; + THREAD_EXIT(0); } static void _sampleRotation(struct mRotationSource* source) { @@ -323,7 +323,7 @@ void mPSP2Setup(struct mGUIRunner* runner) { mInputBindAxis(&runner->core->inputMap, PSP2_INPUT, 1, &desc); unsigned width, height; - runner->core->desiredVideoDimensions(runner->core, &width, &height); + runner->core->baseVideoSize(runner->core, &width, &height); tex[0] = vita2d_create_empty_texture_format(256, toPow2(height), SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); tex[1] = vita2d_create_empty_texture_format(256, toPow2(height), SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); currentTex = 0; @@ -614,7 +614,7 @@ void mPSP2Swap(struct mGUIRunner* runner) { void mPSP2Draw(struct mGUIRunner* runner, bool faded) { unsigned width, height; - runner->core->desiredVideoDimensions(runner->core, &width, &height); + runner->core->currentVideoSize(runner->core, &width, &height); if (interframeBlending) { _drawTex(tex[!currentTex], width, height, faded, false); } diff --git a/src/platform/python/_builder.h b/src/platform/python/_builder.h index 62b5cb870..eef4cf046 100644 --- a/src/platform/python/_builder.h +++ b/src/platform/python/_builder.h @@ -52,7 +52,7 @@ void free(void*); #undef PYEXPORT #ifdef USE_PNG -#include +#include #endif #ifdef M_CORE_GBA #include diff --git a/src/platform/python/_builder.py b/src/platform/python/_builder.py index a69f65616..1e21cc82f 100644 --- a/src/platform/python/_builder.py +++ b/src/platform/python/_builder.py @@ -40,7 +40,7 @@ ffi.set_source("mgba._pylib", """ #include #include #include -#include +#include #include #define PYEXPORT diff --git a/src/platform/python/mgba/core.py b/src/platform/python/mgba/core.py index 3a9b90c3a..51f543d3a 100644 --- a/src/platform/python/mgba/core.py +++ b/src/platform/python/mgba/core.py @@ -235,7 +235,7 @@ class Core(object): def desired_video_dimensions(self): width = ffi.new("unsigned*") height = ffi.new("unsigned*") - self._core.desiredVideoDimensions(self._core, width, height) + self._core.currentVideoSize(self._core, width, height) return width[0], height[0] @protected diff --git a/src/platform/python/mgba/png.py b/src/platform/python/mgba/png.py index ac8b79b86..3df4d3cd9 100644 --- a/src/platform/python/mgba/png.py +++ b/src/platform/python/mgba/png.py @@ -21,20 +21,16 @@ class PNG: def write_header(self, image): self._png = lib.PNGWriteOpen(self._vfile.handle) if self.mode == MODE_RGB: - self._info = lib.PNGWriteHeader(self._png, image.width, image.height) + self._info = lib.PNGWriteHeader(self._png, image.width, image.height, lib.mCOLOR_XBGR8) if self.mode == MODE_RGBA: - self._info = lib.PNGWriteHeaderA(self._png, image.width, image.height) - if self.mode == MODE_INDEX: - self._info = lib.PNGWriteHeader8(self._png, image.width, image.height) + self._info = lib.PNGWriteHeader(self._png, image.width, image.height, lib.mCOLOR_ABGR8) return self._info != ffi.NULL def write_pixels(self, image): if self.mode == MODE_RGB: - return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer) + return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer, lib.mCOLOR_XBGR8) if self.mode == MODE_RGBA: - return lib.PNGWritePixelsA(self._png, image.width, image.height, image.stride, image.buffer) - if self.mode == MODE_INDEX: - return lib.PNGWritePixels8(self._png, image.width, image.height, image.stride, image.buffer) + return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer, lib.mCOLOR_ABGR8) return False def write_close(self): diff --git a/src/platform/qt/ApplicationUpdater.cpp b/src/platform/qt/ApplicationUpdater.cpp index 21def050c..7eebe2c57 100644 --- a/src/platform/qt/ApplicationUpdater.cpp +++ b/src/platform/qt/ApplicationUpdater.cpp @@ -100,6 +100,7 @@ ApplicationUpdater::UpdateInfo ApplicationUpdater::currentVersion() { info.version = QLatin1String(projectVersion); info.rev = gitRevision; info.commit = QLatin1String(gitCommit); + info.size = 0; return info; } diff --git a/src/platform/qt/BattleChipModel.h b/src/platform/qt/BattleChipModel.h index c0f115896..9c22002ee 100644 --- a/src/platform/qt/BattleChipModel.h +++ b/src/platform/qt/BattleChipModel.h @@ -49,10 +49,10 @@ private: BattleChip createChip(int id) const; QMap m_chipIdToName; - int m_flavor; + int m_flavor = 0; int m_scale = 1; QList m_deck; }; -} \ No newline at end of file +} diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 7bd8e095b..2ac06cc3c 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -25,8 +25,12 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(QT_LIBRARIES) -find_package(Qt5 COMPONENTS Core Widgets Network Multimedia) -set(QT Qt5) +set(QT_V 5) +find_package(Qt${QT_V} COMPONENTS Core Widgets Network OPTIONAL_COMPONENTS Multimedia) +if(QT_V GREATER_EQUAL 6) + find_package(Qt${QT_V} COMPONENTS OpenGL OpenGLWidgets) +endif() +set(QT Qt${QT_V}) if(NOT BUILD_GL AND NOT BUILD_GLES2 AND NOT BUILD_GLES3) message(WARNING "OpenGL is recommended to build the Qt port") @@ -64,7 +68,7 @@ if(APPLE) endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations -Werror=narrowing") endif() get_target_property(QT_TYPE ${QT}::Core TYPE) @@ -199,7 +203,7 @@ set(GB_SRC GBOverride.cpp PrinterView.cpp) -set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5") +set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt${QT_V}widgets${QT_V}") set(AUDIO_SRC) if(BUILD_SDL) @@ -219,15 +223,17 @@ if(${QT}Multimedia_FOUND) list(APPEND AUDIO_SRC AudioProcessorQt.cpp AudioDevice.cpp) - list(APPEND SOURCE_FILES - VideoDumper.cpp) + if(QT_V LESS 6) + list(APPEND SOURCE_FILES + VideoDumper.cpp) + endif() if (WIN32 AND QT_STATIC) - list(APPEND QT_LIBRARIES ${QT}::QWindowsAudioPlugin ${QT}::DSServicePlugin ${QT}::QWindowsVistaStylePlugin + list(APPEND QT_LIBRARIES ${QT}::QWindowsAudioPlugin ${QT}::DSServicePlugin ${QT}::QWindowsVistaStylePlugin ${QT}::QJpegPlugin strmiids mfuuid mfplat mf ksguid dxva2 evr d3d9) endif() list(APPEND QT_LIBRARIES ${QT}::Multimedia) list(APPEND QT_DEFINES BUILD_QT_MULTIMEDIA) - set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5multimedia5") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt${QT_V}multimedia${QT_V}") endif() if(NOT AUDIO_SRC) @@ -321,7 +327,19 @@ if(NOT DEFINED DATADIR) endif() endif() if(BUILD_GL OR BUILD_GLES2 OR BUILD_EPOXY) - install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/shaders DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt) + if(NOT USE_LIBZIP AND NOT USE_MINIZIP) + install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/shaders DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt) + else() + file(GLOB SHADERS ${CMAKE_SOURCE_DIR}/res/shaders/*.shader) + message(STATUS ${SHADERS}) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/shaders) + foreach(SHADER_DIR ${SHADERS}) + get_filename_component(SHADER ${SHADER_DIR} NAME) + add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/shaders/${SHADER}" COMMAND "${CMAKE_COMMAND}" -E tar cf "${CMAKE_CURRENT_BINARY_DIR}/shaders/${SHADER}" --format=zip . WORKING_DIRECTORY "${SHADER_DIR}") + add_custom_target("${SHADER}" ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/shaders/${SHADER}") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/shaders/${SHADER}" DESTINATION ${DATADIR}/shaders COMPONENT ${BINARY_NAME}-qt) + endforeach() + endif() endif() if(ENABLE_SCRIPTING AND USE_LUA) install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/scripts DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt) @@ -403,6 +421,9 @@ if(WIN32) endif() list(APPEND QT_LIBRARIES ${QT}::Widgets ${QT}::Network) +if(${QT_V} GREATER_EQUAL 6) + list(APPEND QT_LIBRARIES ${QT}::OpenGL ${QT}::OpenGLWidgets) +endif() if(BUILD_GL OR BUILD_GLES2 OR BUILD_EPOXY) list(APPEND QT_LIBRARIES ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) endif() diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp index 1116dba79..9665439b5 100644 --- a/src/platform/qt/ConfigController.cpp +++ b/src/platform/qt/ConfigController.cpp @@ -125,6 +125,7 @@ ConfigController::ConfigController(QObject* parent) m_opts.logLevel = mLOG_WARN | mLOG_ERROR | mLOG_FATAL; m_opts.rewindEnable = false; m_opts.rewindBufferCapacity = 300; + m_opts.rewindBufferInterval = 1; m_opts.useBios = true; m_opts.suspendScreensaver = true; m_opts.lockAspectRatio = true; @@ -343,6 +344,7 @@ constexpr const char* ConfigController::mruName(ConfigController::MRU mru) { case MRU::Script: return "recentScripts"; } + Q_UNREACHABLE(); } void ConfigController::write() { diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 37fc1b01f..6b21f2c8e 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -49,6 +49,10 @@ CoreController::CoreController(mCore* core, QObject* parent) GBASIODolphinCreate(&m_dolphin); #endif +#ifdef USE_DEBUGGERS + mDebuggerInit(&m_debugger); +#endif + m_resetActions.append([this]() { if (m_autoload) { mCoreLoadState(m_threadContext.core, 0, m_loadStateFlags); @@ -75,20 +79,20 @@ CoreController::CoreController(mCore* core, QObject* parent) controller->updatePlayerSave(); } + if (controller->m_override) { + controller->m_override->identify(context->core); + context->core->setOverride(context->core, controller->m_override->raw()); + } + QMetaObject::invokeMethod(controller, "started"); }; m_threadContext.resetCallback = [](mCoreThread* context) { CoreController* controller = static_cast(context->userData); - for (auto action : controller->m_resetActions) { + for (auto& action : controller->m_resetActions) { action(); } - if (controller->m_override) { - controller->m_override->identify(context->core); - controller->m_override->apply(context->core); - } - controller->m_resetActions.clear(); controller->m_frameCounter = -1; @@ -214,6 +218,10 @@ CoreController::~CoreController() { mCoreThreadJoin(&m_threadContext); +#ifdef USE_DEBUGGERS + mDebuggerDeinit(&m_debugger); +#endif + if (m_cacheSet) { mCacheSetDeinit(m_cacheSet.get()); m_cacheSet.reset(); @@ -223,6 +231,11 @@ CoreController::~CoreController() { m_threadContext.core->deinit(m_threadContext.core); } +void CoreController::setPath(const QString& path, const QString& base) { + m_path = path; + m_baseDirectory = base; +} + const color_t* CoreController::drawContext() { if (m_hwaccel) { return nullptr; @@ -266,11 +279,15 @@ mPlatform CoreController::platform() const { QSize CoreController::screenDimensions() const { unsigned width, height; - m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height); + m_threadContext.core->currentVideoSize(m_threadContext.core, &width, &height); return QSize(width, height); } +unsigned CoreController::videoScale() const { + return m_threadContext.core->videoScale(m_threadContext.core); +} + void CoreController::loadConfig(ConfigController* config) { Interrupter interrupter(this); m_loadStateFlags = config->getOption("loadStateExtdata", m_loadStateFlags).toInt(); @@ -289,13 +306,6 @@ void CoreController::loadConfig(ConfigController* config) { mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "mute"); m_preload = config->getOption("preload").toInt(); - int playerId = m_multiplayer->playerId(this) + 1; - QVariant savePlayerId = config->getOption("savePlayerId"); - if (m_multiplayer->attached() < 2 && savePlayerId.canConvert()) { - playerId = savePlayerId.toInt(); - } - mCoreConfigSetOverrideIntValue(&m_threadContext.core->config, "savePlayerId", playerId); - QSize sizeBefore = screenDimensions(); m_activeBuffer.resize(256 * 224 * sizeof(color_t)); m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast(m_activeBuffer.data()), sizeBefore.width()); @@ -322,14 +332,35 @@ void CoreController::loadConfig(ConfigController* config) { } #ifdef USE_DEBUGGERS -void CoreController::setDebugger(mDebugger* debugger) { +void CoreController::attachDebugger(bool interrupt) { Interrupter interrupter(this); - if (debugger) { - mDebuggerAttach(debugger, m_threadContext.core); - mDebuggerEnter(debugger, DEBUGGER_ENTER_ATTACHED, 0); - } else { - m_threadContext.core->detachDebugger(m_threadContext.core); + if (!m_threadContext.core->debugger) { + mDebuggerAttach(&m_debugger, m_threadContext.core); } + if (interrupt) { + mDebuggerEnter(&m_debugger, DEBUGGER_ENTER_ATTACHED, 0); + } +} + +void CoreController::detachDebugger() { + Interrupter interrupter(this); + if (!m_threadContext.core->debugger) { + return; + } + m_threadContext.core->detachDebugger(m_threadContext.core); +} + +void CoreController::attachDebuggerModule(mDebuggerModule* module, bool interrupt) { + Interrupter interrupter(this); + if (module) { + mDebuggerAttachModule(&m_debugger, module); + } + attachDebugger(interrupt); +} + +void CoreController::detachDebuggerModule(mDebuggerModule* module) { + Interrupter interrupter(this); + mDebuggerDetachModule(&m_debugger, module); } #endif @@ -448,7 +479,7 @@ void CoreController::start() { void CoreController::stop() { setSync(false); #ifdef USE_DEBUGGERS - setDebugger(nullptr); + detachDebugger(); #endif setPaused(false); mCoreThreadEnd(&m_threadContext); @@ -518,9 +549,6 @@ void CoreController::setRewinding(bool rewind) { } void CoreController::rewind(int states) { - if (!states) { - return; - } if (!m_threadContext.core->opts.rewindEnable) { emit statusPosted(tr("Rewinding not currently enabled")); } @@ -785,10 +813,16 @@ void CoreController::loadSave(const QString& path, bool temporary) { return; } + bool ok; if (temporary) { - m_threadContext.core->loadTemporarySave(m_threadContext.core, vf); + ok = m_threadContext.core->loadTemporarySave(m_threadContext.core, vf); } else { - m_threadContext.core->loadSave(m_threadContext.core, vf); + ok = m_threadContext.core->loadSave(m_threadContext.core, vf); + } + if (!ok) { + vf->close(vf); + } else { + m_savePath = path; } }); if (hasStarted()) { @@ -796,12 +830,18 @@ void CoreController::loadSave(const QString& path, bool temporary) { } } -void CoreController::loadSave(VFile* vf, bool temporary) { - m_resetActions.append([this, vf, temporary]() { +void CoreController::loadSave(VFile* vf, bool temporary, const QString& path) { + m_resetActions.append([this, vf, temporary, path]() { + bool ok; if (temporary) { - m_threadContext.core->loadTemporarySave(m_threadContext.core, vf); + ok = m_threadContext.core->loadTemporarySave(m_threadContext.core, vf); } else { - m_threadContext.core->loadSave(m_threadContext.core, vf); + ok = m_threadContext.core->loadSave(m_threadContext.core, vf); + } + if (!ok) { + vf->close(vf); + } else { + m_savePath = path; } }); if (hasStarted()) { @@ -838,6 +878,7 @@ void CoreController::replaceGame(const QString& path) { } else { mCoreLoadFile(m_threadContext.core, fname.toUtf8().constData()); } + // TODO: Make sure updating the path is handled properly by everything that calls path() and baseDirectory() updateROMInfo(); } @@ -1004,9 +1045,9 @@ void CoreController::attachPrinter() { } GB* gb = static_cast(m_threadContext.core->board); clearMultiplayerController(); - GBPrinterCreate(&m_printer.d); + GBPrinterCreate(&m_printer); m_printer.parent = this; - m_printer.d.print = [](GBPrinter* printer, int height, const uint8_t* data) { + m_printer.print = [](GBPrinter* printer, int height, const uint8_t* data) { QGBPrinter* qPrinter = reinterpret_cast(printer); QImage image(GB_VIDEO_HORIZONTAL_PIXELS, height, QImage::Format_Indexed8); QVector colors; @@ -1027,7 +1068,7 @@ void CoreController::attachPrinter() { QMetaObject::invokeMethod(qPrinter->parent, "imagePrinted", Q_ARG(const QImage&, image)); }; Interrupter interrupter(this); - GBSIOSetDriver(&gb->sio, &m_printer.d.d); + GBSIOSetDriver(&gb->sio, &m_printer.d); } void CoreController::detachPrinter() { @@ -1036,7 +1077,7 @@ void CoreController::detachPrinter() { } Interrupter interrupter(this); GB* gb = static_cast(m_threadContext.core->board); - GBPrinterDonePrinting(&m_printer.d); + GBPrinterDonePrinting(&m_printer); GBSIOSetDriver(&gb->sio, nullptr); } @@ -1045,7 +1086,7 @@ void CoreController::endPrint() { return; } Interrupter interrupter(this); - GBPrinterDonePrinting(&m_printer.d); + GBPrinterDonePrinting(&m_printer); } #endif @@ -1189,7 +1230,7 @@ int CoreController::updateAutofire() { void CoreController::finishFrame() { if (!m_hwaccel) { unsigned width, height; - m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height); + m_threadContext.core->currentVideoSize(m_threadContext.core, &width, &height); QMutexLocker locker(&m_bufferMutex); memcpy(m_completeBuffer.data(), m_activeBuffer.constData(), width * height * BYTES_PER_PIXEL); @@ -1216,11 +1257,7 @@ void CoreController::finishFrame() { } void CoreController::updatePlayerSave() { - int savePlayerId = 0; - mCoreConfigGetIntValue(&m_threadContext.core->config, "savePlayerId", &savePlayerId); - if (savePlayerId == 0 || m_multiplayer->attached() > 1) { - savePlayerId = m_multiplayer->playerId(this) + 1; - } + int savePlayerId = m_multiplayer->saveId(this); QString saveSuffix; if (savePlayerId < 2) { @@ -1231,7 +1268,11 @@ void CoreController::updatePlayerSave() { QByteArray saveSuffixBin(saveSuffix.toUtf8()); VFile* save = mDirectorySetOpenSuffix(&m_threadContext.core->dirs, m_threadContext.core->dirs.save, saveSuffixBin.constData(), O_CREAT | O_RDWR); if (save) { - m_threadContext.core->loadSave(m_threadContext.core, save); + if (!m_threadContext.core->loadSave(m_threadContext.core, save)) { + save->close(save); + } else { + m_savePath = QString::fromUtf8(m_threadContext.core->dirs.baseName) + saveSuffix; + } } } diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index e2cd03c12..c46b4d6e9 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -82,6 +82,11 @@ public: mCoreThread* thread() { return &m_threadContext; } + void setPath(const QString& path, const QString& base = {}); + QString path() const { return m_path; } + QString baseDirectory() const { return m_baseDirectory; } + QString savePath() const { return m_savePath; } + const color_t* drawContext(); QImage getPixels(); @@ -94,6 +99,7 @@ public: mPlatform platform() const; QSize screenDimensions() const; + unsigned videoScale() const; bool supportsFeature(Feature feature) const { return m_threadContext.core->supportsFeature(m_threadContext.core, static_cast(feature)); } bool hardwareAccelerated() const { return m_hwaccel; } @@ -102,8 +108,11 @@ public: mCheatDevice* cheatDevice() { return m_threadContext.core->cheatDevice(m_threadContext.core); } #ifdef USE_DEBUGGERS - mDebugger* debugger() { return m_threadContext.core->debugger; } - void setDebugger(mDebugger*); + mDebugger* debugger() { return &m_debugger; } + void attachDebugger(bool interrupt = true); + void detachDebugger(); + void attachDebuggerModule(mDebuggerModule*, bool interrupt = true); + void detachDebuggerModule(mDebuggerModule*); #endif void setMultiplayerController(MultiplayerController*); @@ -158,7 +167,7 @@ public slots: void saveBackupState(); void loadSave(const QString&, bool temporary); - void loadSave(VFile*, bool temporary); + void loadSave(VFile*, bool temporary, const QString& path = {}); void loadPatch(const QString&); void scanCard(const QString&); void scanCards(const QStringList&); @@ -245,6 +254,10 @@ private: CoreController* self; } m_logger{}; + QString m_path; + QString m_baseDirectory; + QString m_savePath; + bool m_patched = false; bool m_preload = false; @@ -292,6 +305,10 @@ private: bool m_autoload; int m_autosaveCounter = 0; +#ifdef USE_DEBUGGERS + struct mDebugger m_debugger; +#endif + int m_fastForward = false; int m_fastForwardForced = false; int m_fastForwardVolume = -1; @@ -313,8 +330,7 @@ private: VFile* m_vlVf = nullptr; #ifdef M_CORE_GB - struct QGBPrinter { - GBPrinter d; + struct QGBPrinter : public GBPrinter { CoreController* parent; } m_printer; #endif diff --git a/src/platform/qt/CoreManager.cpp b/src/platform/qt/CoreManager.cpp index cac1c2560..b2f7cabf0 100644 --- a/src/platform/qt/CoreManager.cpp +++ b/src/platform/qt/CoreManager.cpp @@ -16,6 +16,7 @@ #endif #include +#include #include using namespace QGBA; @@ -121,6 +122,7 @@ CoreController* CoreManager::loadGame(VFile* vf, const QString& path, const QStr if (m_multiplayer) { cc->setMultiplayerController(m_multiplayer); } + cc->setPath(path, info.dir().canonicalPath()); emit coreLoaded(cc); return cc; } @@ -161,7 +163,7 @@ CoreController* CoreManager::loadBIOS(int platform, const QString& path) { mCoreConfigSetOverrideIntValue(&core->config, "skipBios", 0); QByteArray bytes(info.baseName().toUtf8()); - strncpy(core->dirs.baseName, bytes.constData(), sizeof(core->dirs.baseName)); + strlcpy(core->dirs.baseName, bytes.constData(), sizeof(core->dirs.baseName)); bytes = info.dir().canonicalPath().toUtf8(); mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData())); @@ -170,6 +172,7 @@ CoreController* CoreManager::loadBIOS(int platform, const QString& path) { if (m_multiplayer) { cc->setMultiplayerController(m_multiplayer); } + cc->setPath(path, info.dir().canonicalPath()); emit coreLoaded(cc); return cc; } diff --git a/src/platform/qt/DebuggerConsoleController.cpp b/src/platform/qt/DebuggerConsoleController.cpp index 3a9446b15..55d44a0ae 100644 --- a/src/platform/qt/DebuggerConsoleController.cpp +++ b/src/platform/qt/DebuggerConsoleController.cpp @@ -19,26 +19,27 @@ using namespace QGBA; DebuggerConsoleController::DebuggerConsoleController(QObject* parent) : DebuggerController(&m_cliDebugger.d, parent) { - m_backend.d.printf = printf; - m_backend.d.init = init; - m_backend.d.deinit = deinit; - m_backend.d.readline = readLine; - m_backend.d.lineAppend = lineAppend; - m_backend.d.historyLast = historyLast; - m_backend.d.historyAppend = historyAppend; - m_backend.d.interrupt = interrupt; + m_backend.printf = printf; + m_backend.init = init; + m_backend.deinit = deinit; + m_backend.poll = poll; + m_backend.readline = readLine; + m_backend.lineAppend = lineAppend; + m_backend.historyLast = historyLast; + m_backend.historyAppend = historyAppend; + m_backend.interrupt = interrupt; m_backend.self = this; CLIDebuggerCreate(&m_cliDebugger); - CLIDebuggerAttachBackend(&m_cliDebugger, &m_backend.d); + CLIDebuggerAttachBackend(&m_cliDebugger, &m_backend); } void DebuggerConsoleController::enterLine(const QString& line) { CoreController::Interrupter interrupter(m_gameController); QMutexLocker lock(&m_mutex); m_lines.append(line); - if (m_cliDebugger.d.state == DEBUGGER_RUNNING) { - mDebuggerEnter(&m_cliDebugger.d, DEBUGGER_ENTER_MANUAL, nullptr); + if (m_cliDebugger.d.p->state == DEBUGGER_RUNNING) { + mDebuggerEnter(m_cliDebugger.d.p, DEBUGGER_ENTER_MANUAL, nullptr); } m_cond.wakeOne(); } @@ -47,7 +48,7 @@ void DebuggerConsoleController::detach() { { CoreController::Interrupter interrupter(m_gameController); QMutexLocker lock(&m_mutex); - if (m_cliDebugger.d.state != DEBUGGER_SHUTDOWN) { + if (m_cliDebugger.d.p->state != DEBUGGER_SHUTDOWN) { m_lines.append(QString()); m_cond.wakeOne(); } @@ -60,7 +61,7 @@ void DebuggerConsoleController::attachInternal() { CoreController::Interrupter interrupter(m_gameController); QMutexLocker lock(&m_mutex); mCore* core = m_gameController->thread()->core; - CLIDebuggerAttachBackend(&m_cliDebugger, &m_backend.d); + CLIDebuggerAttachBackend(&m_cliDebugger, &m_backend); CLIDebuggerAttachSystem(&m_cliDebugger, core->cliDebuggerSystem(core)); } @@ -82,12 +83,20 @@ void DebuggerConsoleController::init(struct CLIDebuggerBackend* be) { void DebuggerConsoleController::deinit(struct CLIDebuggerBackend* be) { Backend* consoleBe = reinterpret_cast(be); DebuggerConsoleController* self = consoleBe->self; - if (QThread::currentThread() == self->thread() && be->p->d.state != DEBUGGER_SHUTDOWN) { + if (QThread::currentThread() == self->thread() && be->p->d.p->state != DEBUGGER_SHUTDOWN) { self->m_lines.append(QString()); self->m_cond.wakeOne(); } } +int DebuggerConsoleController::poll(struct CLIDebuggerBackend* be, int32_t timeoutMs) { + Backend* consoleBe = reinterpret_cast(be); + DebuggerConsoleController* self = consoleBe->self; + QMutexLocker lock(&self->m_mutex); + self->m_cond.wait(&self->m_mutex, timeoutMs < 0 ? ULONG_MAX : static_cast(timeoutMs)); + return !self->m_lines.isEmpty(); +} + const char* DebuggerConsoleController::readLine(struct CLIDebuggerBackend* be, size_t* len) { Backend* consoleBe = reinterpret_cast(be); DebuggerConsoleController* self = consoleBe->self; @@ -137,10 +146,6 @@ void DebuggerConsoleController::interrupt(struct CLIDebuggerBackend* be) { DebuggerConsoleController* self = consoleBe->self; QMutexLocker lock(&self->m_mutex); self->m_cond.wakeOne(); - if (!self->m_lines.isEmpty()) { - return; - } - self->m_lines.append("\033"); } void DebuggerConsoleController::historyLoad() { diff --git a/src/platform/qt/DebuggerConsoleController.h b/src/platform/qt/DebuggerConsoleController.h index fdf183c08..b4530185b 100644 --- a/src/platform/qt/DebuggerConsoleController.h +++ b/src/platform/qt/DebuggerConsoleController.h @@ -42,6 +42,7 @@ private: static void printf(struct CLIDebuggerBackend* be, const char* fmt, ...); static void init(struct CLIDebuggerBackend* be); static void deinit(struct CLIDebuggerBackend* be); + static int poll(struct CLIDebuggerBackend* be, int32_t timeoutMs); static const char* readLine(struct CLIDebuggerBackend* be, size_t* len); static void lineAppend(struct CLIDebuggerBackend* be, const char* line); static const char* historyLast(struct CLIDebuggerBackend* be, size_t* len); @@ -56,8 +57,7 @@ private: QStringList m_lines; QByteArray m_last; - struct Backend { - CLIDebuggerBackend d; + struct Backend : public CLIDebuggerBackend { DebuggerConsoleController* self; } m_backend; }; diff --git a/src/platform/qt/DebuggerController.cpp b/src/platform/qt/DebuggerController.cpp index 7291b62fa..105549309 100644 --- a/src/platform/qt/DebuggerController.cpp +++ b/src/platform/qt/DebuggerController.cpp @@ -9,7 +9,7 @@ using namespace QGBA; -DebuggerController::DebuggerController(mDebugger* debugger, QObject* parent) +DebuggerController::DebuggerController(mDebuggerModule* debugger, QObject* parent) : QObject(parent) , m_debugger(debugger) { @@ -19,7 +19,7 @@ bool DebuggerController::isAttached() { if (!m_gameController) { return false; } - return m_gameController->debugger() == m_debugger; + return m_gameController->debugger() == m_debugger->p; } void DebuggerController::setController(std::shared_ptr controller) { @@ -45,7 +45,7 @@ void DebuggerController::attach() { } if (m_gameController) { attachInternal(); - m_gameController->setDebugger(m_debugger); + m_gameController->attachDebuggerModule(m_debugger); } else { m_autoattach = true; } @@ -58,7 +58,7 @@ void DebuggerController::detach() { if (m_gameController) { CoreController::Interrupter interrupter(m_gameController); shutdownInternal(); - m_gameController->setDebugger(nullptr); + m_gameController->detachDebuggerModule(m_debugger); } else { m_autoattach = false; } @@ -69,7 +69,7 @@ void DebuggerController::breakInto() { return; } CoreController::Interrupter interrupter(m_gameController); - mDebuggerEnter(m_debugger, DEBUGGER_ENTER_MANUAL, 0); + mDebuggerEnter(m_debugger->p, DEBUGGER_ENTER_MANUAL, 0); } void DebuggerController::shutdown() { diff --git a/src/platform/qt/DebuggerController.h b/src/platform/qt/DebuggerController.h index 45ac434da..722282f27 100644 --- a/src/platform/qt/DebuggerController.h +++ b/src/platform/qt/DebuggerController.h @@ -9,7 +9,7 @@ #include -struct mDebugger; +struct mDebuggerModule; namespace QGBA { @@ -19,7 +19,7 @@ class DebuggerController : public QObject { Q_OBJECT public: - DebuggerController(mDebugger* debugger, QObject* parent = nullptr); + DebuggerController(mDebuggerModule* debugger, QObject* parent = nullptr); public: bool isAttached(); @@ -35,7 +35,7 @@ protected: virtual void attachInternal(); virtual void shutdownInternal(); - mDebugger* const m_debugger; + mDebuggerModule* const m_debugger; std::shared_ptr m_gameController; private: diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index 704f9463a..93b4950ef 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -10,6 +10,7 @@ #include "DisplayGL.h" #include "DisplayQt.h" #include "LogController.h" +#include "VideoProxy.h" #include "utils.h" #include @@ -112,10 +113,11 @@ void QGBA::Display::configure(ConfigController* config) { filter(opts->resampleVideo); config->updateOption("showOSD"); config->updateOption("showFrameCounter"); + config->updateOption("videoSync"); #if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(BUILD_GLES3) - if (opts->shader) { + if (opts->shader && supportsShaders()) { struct VDir* shader = VDirOpen(opts->shader); - if (shader && supportsShaders()) { + if (shader) { setShaders(shader); shader->close(shader); } @@ -123,6 +125,13 @@ void QGBA::Display::configure(ConfigController* config) { #endif } +VideoBackend* QGBA::Display::videoBackend() { + if (m_videoProxy) { + return m_videoProxy->backend(); + } + return nullptr; +} + void QGBA::Display::resizeEvent(QResizeEvent*) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) m_messagePainter.resize(size(), devicePixelRatioF()); diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index 574a0e8e9..3c723ba91 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -14,6 +14,7 @@ #include "MessagePainter.h" struct VDir; +struct VideoBackend; struct VideoShader; namespace QGBA { @@ -54,9 +55,12 @@ public: virtual VideoShader* shaders() = 0; virtual int framebufferHandle() { return -1; } virtual void setVideoScale(int) {} + virtual void setBackgroundImage(const QImage&) = 0; + virtual QSize contentSize() const = 0; virtual void setVideoProxy(std::shared_ptr proxy) { m_videoProxy = proxy; } std::shared_ptr videoProxy() { return m_videoProxy; } + virtual VideoBackend* videoBackend(); signals: void drawingStarted(); @@ -74,6 +78,7 @@ public slots: virtual void showOSDMessages(bool enable); virtual void showFrameCounter(bool enable); virtual void filter(bool filter); + virtual void swapInterval(int interval) = 0; virtual void framePosted() = 0; virtual void setShaders(struct VDir*) = 0; virtual void clearShaders() = 0; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 469b7e315..c90c19aac 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -38,6 +38,21 @@ using QOpenGLFunctions_Baseline = QOpenGLFunctions_3_2_Core; #endif #endif +#ifdef _WIN32 +#include +#elif defined(Q_OS_MAC) +#include +#endif +#ifdef USE_GLX +#define GLX_GLXEXT_PROTOTYPES +typedef struct _XDisplay Display; +#include +#include +#endif +#ifdef USE_EGL +#include +#endif + #ifdef _WIN32 #define OVERHEAD_NSEC 1000000 #else @@ -45,6 +60,7 @@ using QOpenGLFunctions_Baseline = QOpenGLFunctions_3_2_Core; #endif #include "OpenGLBug.h" +#include "utils.h" using namespace QGBA; @@ -65,6 +81,10 @@ mGLWidget::mGLWidget(QWidget* parent) connect(&m_refresh, &QTimer::timeout, this, static_cast(&QWidget::update)); } +mGLWidget::~mGLWidget() { + // This is needed for unique_ptr to work +} + void mGLWidget::initializeGL() { m_vao = std::make_unique(); m_vao->create(); @@ -94,6 +114,8 @@ void mGLWidget::initializeGL() { m_vaoDone = false; m_tex = 0; + + m_paintDev = std::make_unique(); } bool mGLWidget::finalizeVAO() { @@ -145,6 +167,23 @@ void mGLWidget::paintGL() { } else { m_refresh.start(17); } + + if (m_showOSD && m_messagePainter) { + qreal r = window()->devicePixelRatio(); + m_paintDev->setDevicePixelRatio(r); + m_paintDev->setSize(size() * r); + QPainter painter(m_paintDev.get()); + m_messagePainter->paint(&painter); + painter.end(); + } +} + +void mGLWidget::setMessagePainter(MessagePainter* messagePainter) { + m_messagePainter = messagePainter; +} + +void mGLWidget::setShowOSD(bool showOSD) { + m_showOSD = showOSD; } DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) @@ -165,6 +204,7 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) m_gl = new mGLWidget; m_gl->setAttribute(Qt::WA_NativeWindow); m_gl->setFormat(format); + m_gl->setMessagePainter(messagePainter()); QBoxLayout* layout = new QVBoxLayout; layout->addWidget(m_gl); layout->setContentsMargins(0, 0, 0, 0); @@ -177,11 +217,6 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) m_drawThread.setObjectName("Painter Thread"); m_painter->setThread(&m_drawThread); - m_proxyThread.setObjectName("OpenGL Proxy Thread"); - m_proxyContext = std::make_unique(); - m_proxyContext->setFormat(format); - connect(m_painter.get(), &PainterGL::created, this, &DisplayGL::setupProxyThread); - connect(&m_drawThread, &QThread::started, m_painter.get(), &PainterGL::create); connect(m_painter.get(), &PainterGL::started, this, [this] { m_hasStarted = true; @@ -196,11 +231,6 @@ DisplayGL::~DisplayGL() { QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection); m_drawThread.exit(); m_drawThread.wait(); - - if (m_proxyThread.isRunning()) { - m_proxyThread.exit(); - m_proxyThread.wait(); - } } bool DisplayGL::supportsShaders() const { @@ -221,6 +251,9 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { m_painter->setContext(controller); m_painter->setMessagePainter(messagePainter()); m_context = controller; + if (videoProxy()) { + videoProxy()->moveToThread(&m_drawThread); + } lockAspectRatio(isAspectRatioLocked()); lockIntegerScaling(isIntegerScalingLocked()); @@ -235,7 +268,7 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { messagePainter()->resize(size(), devicePixelRatio()); #endif - CoreController::Interrupter interrupter(controller); + CoreController::Interrupter interrupter(m_context); QMetaObject::invokeMethod(m_painter.get(), "start"); if (!m_gl) { if (shouldDisableUpdates()) { @@ -245,6 +278,8 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { show(); m_gl->reset(); } + + QTimer::singleShot(8, this, &DisplayGL::updateContentSize); } bool DisplayGL::supportsFormat(const QSurfaceFormat& format) { @@ -331,12 +366,14 @@ void DisplayGL::unpauseDrawing() { if (!m_gl && shouldDisableUpdates()) { setUpdatesEnabled(false); } + QMetaObject::invokeMethod(this, "updateContentSize", Qt::QueuedConnection); } } void DisplayGL::forceDraw() { if (m_hasStarted) { QMetaObject::invokeMethod(m_painter.get(), "forceDraw"); + QMetaObject::invokeMethod(this, "updateContentSize", Qt::QueuedConnection); } } @@ -357,6 +394,9 @@ void DisplayGL::interframeBlending(bool enable) { void DisplayGL::showOSDMessages(bool enable) { Display::showOSDMessages(enable); + if (m_gl) { + m_gl->setShowOSD(enable); + } QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable)); } @@ -370,6 +410,10 @@ void DisplayGL::filter(bool filter) { QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter)); } +void DisplayGL::swapInterval(int interval) { + QMetaObject::invokeMethod(m_painter.get(), "swapInterval", Q_ARG(int, interval)); +} + void DisplayGL::framePosted() { m_painter->enqueue(m_context->drawContext()); QMetaObject::invokeMethod(m_painter.get(), "draw"); @@ -396,6 +440,11 @@ void DisplayGL::setVideoScale(int scale) { QMetaObject::invokeMethod(m_painter.get(), "resizeContext"); } +void DisplayGL::setBackgroundImage(const QImage& image) { + QMetaObject::invokeMethod(m_painter.get(), "setBackgroundImage", Q_ARG(const QImage&, image)); + QMetaObject::invokeMethod(this, "updateContentSize", Qt::QueuedConnection); +} + void DisplayGL::resizeEvent(QResizeEvent* event) { Display::resizeEvent(event); resizePainter(); @@ -420,32 +469,13 @@ bool DisplayGL::shouldDisableUpdates() { void DisplayGL::setVideoProxy(std::shared_ptr proxy) { Display::setVideoProxy(proxy); if (proxy) { - proxy->moveToThread(&m_proxyThread); + proxy->moveToThread(&m_drawThread); } m_painter->setVideoProxy(proxy); } -void DisplayGL::setupProxyThread() { - m_proxyContext->moveToThread(&m_proxyThread); - m_proxySurface.create(); - connect(&m_proxyThread, &QThread::started, m_proxyContext.get(), [this]() { - m_proxyContext->setShareContext(m_painter->shareContext()); - m_proxyContext->create(); - m_proxyContext->makeCurrent(&m_proxySurface); -#if defined(_WIN32) && defined(USE_EPOXY) - epoxy_handle_external_wglMakeCurrent(); -#endif - }); - connect(m_painter.get(), &PainterGL::texSwapped, m_proxyContext.get(), [this]() { - if (!m_context->hardwareAccelerated()) { - return; - } - if (videoProxy()) { - videoProxy()->processData(); - } - m_painter->updateFramebufferHandle(); - }, Qt::BlockingQueuedConnection); - m_proxyThread.start(); +void DisplayGL::updateContentSize() { + QMetaObject::invokeMethod(m_painter.get(), "contentSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QSize, m_cachedContentSize)); } int DisplayGL::framebufferHandle() { @@ -509,19 +539,15 @@ void PainterGL::create() { mGLES2Context* gl2Backend; #endif - m_paintDev = std::make_unique(); + if (!m_widget) { + m_paintDev = std::make_unique(); + } #if defined(BUILD_GLES2) || defined(BUILD_GLES3) if (m_supportsShaders) { gl2Backend = static_cast(malloc(sizeof(mGLES2Context))); mGLES2ContextCreate(gl2Backend); m_backend = &gl2Backend->d; - QOpenGLFunctions* fn = m_gl->functions(); - fn->glGenTextures(m_bridgeTexes.size(), m_bridgeTexes.data()); - for (auto tex : m_bridgeTexes) { - m_freeTex.enqueue(tex); - } - m_bridgeTexIn = m_freeTex.dequeue(); } #endif @@ -589,11 +615,9 @@ void PainterGL::destroy() { } makeCurrent(); #if defined(BUILD_GLES2) || defined(BUILD_GLES3) - QOpenGLFunctions* fn = m_gl->functions(); if (m_shader.passes) { mGLES2ShaderFree(&m_shader); } - fn->glDeleteTextures(m_bridgeTexes.size(), m_bridgeTexes.data()); #endif m_backend->deinit(m_backend); m_gl->doneCurrent(); @@ -624,18 +648,45 @@ void PainterGL::resizeContext() { return; } dequeueAll(false); - m_backend->setDimensions(m_backend, size.width(), size.height()); + + mRectangle dims = {0, 0, size.width(), size.height()}; + m_backend->setLayerDimensions(m_backend, VIDEO_LAYER_IMAGE, &dims); + recenterLayers(); } void PainterGL::setMessagePainter(MessagePainter* messagePainter) { m_messagePainter = messagePainter; } +void PainterGL::recenterLayers() { + if (!m_context) { + return; + } + const static std::initializer_list centeredLayers{VIDEO_LAYER_BACKGROUND}; + int width, height; + mRectangle frame = {0}; + m_backend->imageSize(m_backend, VIDEO_LAYER_IMAGE, &width, &height); + frame.width = width; + frame.height = height; + unsigned scale = std::max(1U, m_context->videoScale()); + + for (VideoLayer l : centeredLayers) { + mRectangle dims{}; + m_backend->imageSize(m_backend, l, &width, &height); + dims.width = width * scale; + dims.height = height * scale; + mRectangleCenter(&frame, &dims); + m_backend->setLayerDimensions(m_backend, l, &dims); + } +} + void PainterGL::resize(const QSize& size) { qreal r = m_window->devicePixelRatio(); m_size = size; - m_paintDev->setSize(m_size * r); - m_paintDev->setDevicePixelRatio(r); + if (m_paintDev) { + m_paintDev->setSize(m_size * r); + m_paintDev->setDevicePixelRatio(r); + } if (m_started && !m_active) { forceDraw(); } @@ -670,6 +721,32 @@ void PainterGL::filter(bool filter) { } } +void PainterGL::swapInterval(int interval) { + if (!m_started) { + return; + } + m_swapInterval = interval; +#ifdef Q_OS_WIN + wglSwapIntervalEXT(interval); +#elif defined(Q_OS_MAC) + CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &interval); +#else +#ifdef USE_GLX + if (QGuiApplication::platformName() == "xcb") { + ::Display* display = glXGetCurrentDisplay(); + GLXDrawable drawable = glXGetCurrentDrawable(); + glXSwapIntervalEXT(display, drawable, interval); + } +#endif +#ifdef USE_EGL + if (QGuiApplication::platformName().contains("egl") || QGuiApplication::platformName() == "wayland") { + EGLDisplay display = eglGetCurrentDisplay(); + eglSwapInterval(display, interval); + } +#endif +#endif +} + #ifndef GL_DEBUG_OUTPUT_SYNCHRONOUS #define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 #endif @@ -691,16 +768,16 @@ void PainterGL::start() { } #endif resizeContext(); - m_context->addFrameAction(std::bind(&PainterGL::swapTex, this)); m_buffer = nullptr; m_active = true; m_started = true; + swapInterval(1); emit started(); } void PainterGL::draw() { - if (!m_started || (m_queue.isEmpty() && m_queueTex.isEmpty())) { + if (!m_started || m_queue.isEmpty()) { return; } @@ -726,12 +803,16 @@ void PainterGL::draw() { } return; } + int wantSwap = sync->audioWait || sync->videoFrameWait; + if (m_swapInterval != wantSwap) { + swapInterval(wantSwap); + } dequeue(); bool forceRedraw = true; if (!m_delayTimer.isValid()) { m_delayTimer.start(); } else { - if (sync->audioWait || sync->videoFrameWait) { + if (wantSwap) { while (m_delayTimer.nsecsElapsed() + OVERHEAD_NSEC < 1000000000 / sync->fpsTarget) { QThread::usleep(500); } @@ -783,6 +864,11 @@ void PainterGL::doStop() { } m_backend->clear(m_backend); m_backend->swap(m_backend); + if (m_videoProxy) { + m_videoProxy->reset(); + m_videoProxy->moveToThread(m_window->thread()); + m_videoProxy.reset(); + } } void PainterGL::pause() { @@ -797,12 +883,12 @@ void PainterGL::unpause() { void PainterGL::performDraw() { float r = m_window->devicePixelRatio(); - m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r); + m_backend->contextResized(m_backend, m_size.width() * r, m_size.height() * r); if (m_buffer) { - m_backend->postFrame(m_backend, m_buffer); + m_backend->setImage(m_backend, VIDEO_LAYER_IMAGE, m_buffer); } m_backend->drawFrame(m_backend); - if (m_showOSD && m_messagePainter && !glContextHasBug(OpenGLBug::IG4ICD_CRASH)) { + if (m_showOSD && m_messagePainter && m_paintDev && !glContextHasBug(OpenGLBug::IG4ICD_CRASH)) { m_painter.begin(m_paintDev.get()); m_messagePainter->paint(&m_painter); m_painter.end(); @@ -810,33 +896,22 @@ void PainterGL::performDraw() { } void PainterGL::enqueue(const uint32_t* backing) { - if (!backing) { - return; - } QMutexLocker locker(&m_mutex); uint32_t* buffer = nullptr; - if (m_free.isEmpty()) { - buffer = m_queue.dequeue(); - } else { - buffer = m_free.takeLast(); - } - if (buffer) { - QSize size = m_context->screenDimensions(); - memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL); + if (backing) { + if (m_free.isEmpty()) { + buffer = m_queue.dequeue(); + } else { + buffer = m_free.takeLast(); + } + if (buffer) { + QSize size = m_context->screenDimensions(); + memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL); + } } m_queue.enqueue(buffer); } -void PainterGL::enqueue(GLuint tex) { - QMutexLocker locker(&m_mutex); - if (m_freeTex.isEmpty()) { - m_bridgeTexIn = m_queueTex.dequeue(); - } else { - m_bridgeTexIn = m_freeTex.takeLast(); - } - m_queueTex.enqueue(tex); -} - void PainterGL::dequeue() { QMutexLocker locker(&m_mutex); if (!m_queue.isEmpty()) { @@ -846,19 +921,6 @@ void PainterGL::dequeue() { } m_buffer = buffer; } - - if (!m_queueTex.isEmpty()) { - if (m_bridgeTexOut != std::numeric_limits::max()) { - m_freeTex.enqueue(m_bridgeTexOut); - } - m_bridgeTexOut = m_queueTex.dequeue(); -#if defined(BUILD_GLES2) || defined(BUILD_GLES3) - if (supportsShaders()) { - mGLES2Context* gl2Backend = reinterpret_cast(m_backend); - gl2Backend->tex = m_bridgeTexOut; - } -#endif - } } void PainterGL::dequeueAll(bool keep) { @@ -879,23 +941,13 @@ void PainterGL::dequeueAll(bool keep) { m_free.append(m_buffer); m_buffer = nullptr; } - - m_queueTex.clear(); - m_freeTex.clear(); - for (auto tex : m_bridgeTexes) { - if (keep && tex == m_bridgeTexIn) { - continue; - } - m_freeTex.enqueue(tex); - } - if (!keep) { - m_bridgeTexIn = m_freeTex.dequeue(); - m_bridgeTexOut = std::numeric_limits::max(); - } } void PainterGL::setVideoProxy(std::shared_ptr proxy) { m_videoProxy = proxy; + if (proxy) { + proxy->setProxiedBackend(m_backend); + } } void PainterGL::interrupt() { @@ -915,8 +967,9 @@ void PainterGL::setShaders(struct VDir* dir) { mGLES2ShaderDetach(reinterpret_cast(m_backend)); mGLES2ShaderFree(&m_shader); } - mGLES2ShaderLoad(&m_shader, dir); - mGLES2ShaderAttach(reinterpret_cast(m_backend), static_cast(m_shader.passes), m_shader.nPasses); + if (mGLES2ShaderLoad(&m_shader, dir)) { + mGLES2ShaderAttach(reinterpret_cast(m_backend), static_cast(m_shader.passes), m_shader.nPasses); + } if (!m_started) { m_gl->doneCurrent(); @@ -948,11 +1001,18 @@ VideoShader* PainterGL::shaders() { return &m_shader; } +QSize PainterGL::contentSize() const { + unsigned width, height; + VideoBackendGetFrameSize(m_backend, &width, &height); + return {saturateCast(width), + saturateCast(height)}; +} + int PainterGL::glTex() { #if defined(BUILD_GLES2) || defined(BUILD_GLES3) if (supportsShaders()) { mGLES2Context* gl2Backend = reinterpret_cast(m_backend); - return gl2Backend->tex; + return gl2Backend->tex[VIDEO_LAYER_IMAGE]; } #endif #ifdef BUILD_GL @@ -971,31 +1031,25 @@ QOpenGLContext* PainterGL::shareContext() { } } -void PainterGL::updateFramebufferHandle() { - QOpenGLFunctions* fn = m_gl->functions(); - // TODO: Figure out why glFlush doesn't work here on Intel/Windows - if (glContextHasBug(OpenGLBug::CROSS_THREAD_FLUSH)) { - fn->glFinish(); - } else { - fn->glFlush(); - } - - CoreController::Interrupter interrupter(m_context); - if (!m_context->hardwareAccelerated()) { - return; - } - enqueue(m_bridgeTexIn); - m_context->setFramebufferHandle(m_bridgeTexIn); -} - -void PainterGL::swapTex() { +void PainterGL::setBackgroundImage(const QImage& image) { if (!m_started) { - return; + makeCurrent(); } - CoreController::Interrupter interrupter(m_context); - emit texSwapped(); - m_context->addFrameAction(std::bind(&PainterGL::swapTex, this)); + m_backend->setImageSize(m_backend, VIDEO_LAYER_BACKGROUND, image.width(), image.height()); + recenterLayers(); + + if (!image.isNull()) { + m_background = image.convertToFormat(QImage::Format_RGB32); + m_background = m_background.rgbSwapped(); + m_backend->setImage(m_backend, VIDEO_LAYER_BACKGROUND, m_background.constBits()); + } else { + m_background = QImage(); + } + + if (!m_started) { + m_gl->doneCurrent(); + } } #endif diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 3ed31a9d8..d94a68621 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -37,7 +37,7 @@ #include "CoreController.h" #include "VideoProxy.h" -#include "platform/video-backend.h" +#include class QOpenGLPaintDevice; class QOpenGLWidget; @@ -51,9 +51,12 @@ Q_OBJECT public: mGLWidget(QWidget* parent = nullptr); + ~mGLWidget(); void setTex(GLuint tex) { m_tex = tex; } void setVBO(GLuint vbo) { m_vbo = vbo; } + void setMessagePainter(MessagePainter*); + void setShowOSD(bool showOSD); bool finalizeVAO(); void reset(); @@ -72,6 +75,9 @@ private: QTimer m_refresh; int m_refreshResidue = 0; + std::unique_ptr m_paintDev; + MessagePainter* m_messagePainter = nullptr; + bool m_showOSD = false; }; class PainterGL; @@ -88,6 +94,7 @@ public: VideoShader* shaders() override; void setVideoProxy(std::shared_ptr) override; int framebufferHandle() override; + QSize contentSize() const override { return m_cachedContentSize; } static bool supportsFormat(const QSurfaceFormat&); @@ -102,18 +109,20 @@ public slots: void showOSDMessages(bool enable) override; void showFrameCounter(bool enable) override; void filter(bool filter) override; + void swapInterval(int interval) override; void framePosted() override; void setShaders(struct VDir*) override; void clearShaders() override; void resizeContext() override; void setVideoScale(int scale) override; + void setBackgroundImage(const QImage&) override; protected: virtual void paintEvent(QPaintEvent*) override { forceDraw(); } virtual void resizeEvent(QResizeEvent*) override; private slots: - void setupProxyThread(); + void updateContentSize(); private: void resizePainter(); @@ -125,11 +134,9 @@ private: bool m_hasStarted = false; std::unique_ptr m_painter; QThread m_drawThread; - QThread m_proxyThread; std::shared_ptr m_context; mGLWidget* m_gl; - QOffscreenSurface m_proxySurface; - std::unique_ptr m_proxyContext; + QSize m_cachedContentSize; }; class PainterGL : public QObject { @@ -143,7 +150,6 @@ public: void setContext(std::shared_ptr); void setMessagePainter(MessagePainter*); void enqueue(const uint32_t* backing); - void enqueue(GLuint tex); void stop(); @@ -155,9 +161,6 @@ public: void setVideoProxy(std::shared_ptr); void interrupt(); - // Run on main thread - void swapTex(); - public slots: void create(); void destroy(); @@ -174,12 +177,14 @@ public slots: void showOSD(bool enable); void showFrameCounter(bool enable); void filter(bool filter); + void swapInterval(int interval); void resizeContext(); - void updateFramebufferHandle(); + void setBackgroundImage(const QImage&); void setShaders(struct VDir*); void clearShaders(); VideoShader* shaders(); + QSize contentSize() const; signals: void created(); @@ -194,24 +199,19 @@ private: void performDraw(); void dequeue(); void dequeueAll(bool keep = false); + void recenterLayers(); std::array, 3> m_buffers; QList m_free; QQueue m_queue; uint32_t* m_buffer = nullptr; - std::array m_bridgeTexes; - QQueue m_freeTex; - QQueue m_queueTex; - - GLuint m_bridgeTexIn = std::numeric_limits::max(); - GLuint m_bridgeTexOut = std::numeric_limits::max(); - QPainter m_painter; QMutex m_mutex; QWindow* m_window; QSurface* m_surface; QSurfaceFormat m_format; + QImage m_background; std::unique_ptr m_paintDev; std::unique_ptr m_gl; int m_finalTexIdx = 0; @@ -232,6 +232,7 @@ private: MessagePainter* m_messagePainter = nullptr; QElapsedTimer m_delayTimer; std::shared_ptr m_videoProxy; + int m_swapInterval = -1; }; } diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 20ec44f0d..51874cb40 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau +/* Copyright (c) 2013-2023 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -19,13 +19,28 @@ using namespace QGBA; DisplayQt::DisplayQt(QWidget* parent) : Display(parent) { + m_backend.init = &DisplayQt::init; + m_backend.deinit = &DisplayQt::deinit; + m_backend.setLayerDimensions = &DisplayQt::setLayerDimensions; + m_backend.layerDimensions = &DisplayQt::layerDimensions; + m_backend.swap = &DisplayQt::swap; + m_backend.clear = &DisplayQt::clear; + m_backend.contextResized = &DisplayQt::contextResized; + m_backend.setImageSize = &DisplayQt::setImageSize; + m_backend.imageSize = &DisplayQt::imageSize; + m_backend.setImage = &DisplayQt::setImage; + m_backend.drawFrame = &DisplayQt::drawFrame; + m_backend.filter = isFiltered(); + m_backend.lockAspectRatio = isAspectRatioLocked(); + m_backend.lockIntegerScaling = isIntegerScalingLocked(); + m_backend.interframeBlending = hasInterframeBlending(); + m_backend.user = this; } void DisplayQt::startDrawing(std::shared_ptr controller) { QSize size = controller->screenDimensions(); m_width = size.width(); m_height = size.height(); - m_backing = QImage(); m_oldBacking = QImage(); m_isDrawing = true; m_context = controller; @@ -39,44 +54,51 @@ void DisplayQt::stopDrawing() { void DisplayQt::lockAspectRatio(bool lock) { Display::lockAspectRatio(lock); + m_backend.lockAspectRatio = lock; update(); } void DisplayQt::lockIntegerScaling(bool lock) { Display::lockIntegerScaling(lock); + m_backend.lockIntegerScaling = lock; update(); } void DisplayQt::interframeBlending(bool lock) { Display::interframeBlending(lock); + m_backend.interframeBlending = lock; update(); } void DisplayQt::filter(bool filter) { Display::filter(filter); + m_backend.filter = filter; update(); } void DisplayQt::framePosted() { update(); const color_t* buffer = m_context->drawContext(); - if (const_cast(m_backing).bits() == reinterpret_cast(buffer)) { + if (const_cast(m_layers[VIDEO_LAYER_IMAGE]).bits() == reinterpret_cast(buffer)) { return; } - m_oldBacking = m_backing; + m_oldBacking = m_layers[VIDEO_LAYER_IMAGE]; #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - m_backing = QImage(reinterpret_cast(buffer), m_width, m_height, QImage::Format_RGB16); + m_layers[VIDEO_LAYER_IMAGE] = QImage(reinterpret_cast(buffer), m_width, m_height, QImage::Format_RGB16); #else - m_backing = QImage(reinterpret_cast(buffer), m_width, m_height, QImage::Format_RGB555); + m_layers[VIDEO_LAYER_IMAGE] = QImage(reinterpret_cast(buffer), m_width, m_height, QImage::Format_RGB555); #endif #else - m_backing = QImage(reinterpret_cast(buffer), m_width, m_height, QImage::Format_ARGB32); - m_backing = m_backing.convertToFormat(QImage::Format_RGB32); + m_layers[VIDEO_LAYER_IMAGE] = QImage(reinterpret_cast(buffer), m_width, m_height, QImage::Format_ARGB32); + m_layers[VIDEO_LAYER_IMAGE] = m_layers[VIDEO_LAYER_IMAGE].convertToFormat(QImage::Format_RGB32); #endif #ifndef COLOR_5_6_5 - m_backing = m_backing.rgbSwapped(); + m_layers[VIDEO_LAYER_IMAGE] = m_layers[VIDEO_LAYER_IMAGE].rgbSwapped(); #endif + m_layerDims[VIDEO_LAYER_IMAGE].setWidth(m_width); + m_layerDims[VIDEO_LAYER_IMAGE].setHeight(m_height); + redoBounds(); } void DisplayQt::resizeContext() { @@ -88,25 +110,142 @@ void DisplayQt::resizeContext() { m_width = size.width(); m_height = size.height(); m_oldBacking = QImage(); - m_backing = QImage(); + m_layers[VIDEO_LAYER_IMAGE] = QImage(); } } +void DisplayQt::setBackgroundImage(const QImage& image) { + m_layers[VIDEO_LAYER_BACKGROUND] = image; + redoBounds(); + update(); +} + void DisplayQt::paintEvent(QPaintEvent*) { QPainter painter(this); painter.fillRect(QRect(QPoint(), size()), Qt::black); if (isFiltered()) { painter.setRenderHint(QPainter::SmoothPixmapTransform); } - QRect full(clampSize(QSize(m_width, m_height), size(), isAspectRatioLocked(), isIntegerScalingLocked())); + + struct mRectangle frame; + VideoBackendGetFrame(&m_backend, &frame); + QPoint origin(-frame.x, -frame.y); + QRect full(clampSize(contentSize(), size(), isAspectRatioLocked(), isIntegerScalingLocked())); + painter.save(); + painter.translate(full.topLeft()); + painter.scale(full.width() / static_cast(frame.width), full.height() / static_cast(frame.height)); + + if (!m_layers[VIDEO_LAYER_BACKGROUND].isNull()) { + painter.drawImage(m_layerDims[VIDEO_LAYER_BACKGROUND].translated(origin), m_layers[VIDEO_LAYER_BACKGROUND]); + } if (hasInterframeBlending()) { - painter.drawImage(full, m_oldBacking, QRect(0, 0, m_width, m_height)); + painter.drawImage(m_layerDims[VIDEO_LAYER_IMAGE].translated(origin), m_oldBacking, QRect(0, 0, m_width, m_height)); painter.setOpacity(0.5); } - painter.drawImage(full, m_backing, QRect(0, 0, m_width, m_height)); + painter.drawImage(m_layerDims[VIDEO_LAYER_IMAGE].translated(origin), m_layers[VIDEO_LAYER_IMAGE], QRect(0, 0, m_width, m_height)); + + for (int i = VIDEO_LAYER_IMAGE + 1; i < VIDEO_LAYER_MAX; ++i) { + if (m_layers[i].isNull()) { + continue; + } + + painter.drawImage(m_layerDims[i].translated(origin), m_layers[i]); + } + + painter.restore(); painter.setOpacity(1); if (isShowOSD() || isShowFrameCounter()) { messagePainter()->paint(&painter); } } + +void DisplayQt::redoBounds() { + const static std::initializer_list centeredLayers{VIDEO_LAYER_BACKGROUND}; + mRectangle frame = {0}; + frame.width = m_width; + frame.height = m_height; + + for (VideoLayer l : centeredLayers) { + mRectangle dims{}; + dims.width = m_layers[l].width(); + dims.height = m_layers[l].height(); + mRectangleCenter(&frame, &dims); + m_layerDims[l].setX(dims.x); + m_layerDims[l].setY(dims.y); + m_layerDims[l].setWidth(dims.width); + m_layerDims[l].setHeight(dims.height); + } +} + +QSize DisplayQt::contentSize() const { + unsigned w, h; + VideoBackendGetFrameSize(&m_backend, &w, &h); + return {saturateCast(w), saturateCast(h)}; +} + +void DisplayQt::init(struct VideoBackend*, WHandle) { +} + +void DisplayQt::deinit(struct VideoBackend*) { +} + +void DisplayQt::setLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct mRectangle* dims) { + DisplayQt* self = static_cast(v->user); + if (layer > self->m_layerDims.size()) { + return; + } + self->m_layerDims[layer] = QRect(dims->x, dims->y, dims->width, dims->height); +} + +void DisplayQt::layerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct mRectangle* dims) { + DisplayQt* self = static_cast(v->user); + if (layer > self->m_layerDims.size()) { + return; + } + QRect rect = self->m_layerDims[layer]; + dims->x = rect.x(); + dims->y = rect.y(); + dims->width = rect.width(); + dims->height = rect.height(); +} + +void DisplayQt::swap(struct VideoBackend*) { +} + +void DisplayQt::clear(struct VideoBackend*) { +} + +void DisplayQt::contextResized(struct VideoBackend*, unsigned, unsigned) { +} + +void DisplayQt::setImageSize(struct VideoBackend* v, enum VideoLayer layer, int w, int h) { + DisplayQt* self = static_cast(v->user); + if (layer > self->m_layers.size()) { + return; + } + self->m_layers[layer] = QImage(w, h, QImage::Format_ARGB32); +} + +void DisplayQt::imageSize(struct VideoBackend* v, enum VideoLayer layer, int* w, int* h) { + DisplayQt* self = static_cast(v->user); + if (layer > self->m_layers.size()) { + return; + } + *w = self->m_layers[layer].width(); + *h = self->m_layers[layer].height(); +} + +void DisplayQt::setImage(struct VideoBackend* v, enum VideoLayer layer, const void* frame) { + DisplayQt* self = static_cast(v->user); + if (layer > self->m_layers.size()) { + return; + } + QImage image = self->m_layers[layer]; + image = QImage(static_cast(frame), image.width(), image.height(), QImage::Format_ARGB32).rgbSwapped(); + self->m_layers[layer] = image; +} + +void DisplayQt::drawFrame(struct VideoBackend* v) { + QMetaObject::invokeMethod(static_cast(v->user), "update"); +} diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index e8623468f..c1363dede 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau +/* Copyright (c) 2013-2023 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -10,6 +10,9 @@ #include #include +#include +#include + namespace QGBA { class DisplayQt : public Display { @@ -22,6 +25,8 @@ public: bool isDrawing() const override { return m_isDrawing; } bool supportsShaders() const override { return false; } VideoShader* shaders() override { return nullptr; } + QSize contentSize() const override; + VideoBackend* videoBackend() override { return &m_backend; } public slots: void stopDrawing() override; @@ -31,20 +36,38 @@ public slots: void lockAspectRatio(bool lock) override; void lockIntegerScaling(bool lock) override; void interframeBlending(bool enable) override; + void swapInterval(int) override {}; void filter(bool filter) override; void framePosted() override; void setShaders(struct VDir*) override {} void clearShaders() override {} void resizeContext() override; + void setBackgroundImage(const QImage&) override; protected: virtual void paintEvent(QPaintEvent*) override; private: + void redoBounds(); + + static void init(struct VideoBackend*, WHandle); + static void deinit(struct VideoBackend*); + static void setLayerDimensions(struct VideoBackend*, enum VideoLayer, const struct mRectangle*); + static void layerDimensions(const struct VideoBackend*, enum VideoLayer, struct mRectangle*); + static void swap(struct VideoBackend*); + static void clear(struct VideoBackend*); + static void contextResized(struct VideoBackend*, unsigned w, unsigned h); + static void setImageSize(struct VideoBackend*, enum VideoLayer, int w, int h); + static void imageSize(struct VideoBackend*, enum VideoLayer, int* w, int* h); + static void setImage(struct VideoBackend*, enum VideoLayer, const void* frame); + static void drawFrame(struct VideoBackend*); + + VideoBackend m_backend{}; + std::array m_layerDims; + std::array m_layers; bool m_isDrawing = false; - int m_width; - int m_height; - QImage m_backing{nullptr}; + int m_width = -1; + int m_height = -1; QImage m_oldBacking{nullptr}; std::shared_ptr m_context = nullptr; }; diff --git a/src/platform/qt/ForwarderView.cpp b/src/platform/qt/ForwarderView.cpp index 0546e7180..922ef7c16 100644 --- a/src/platform/qt/ForwarderView.cpp +++ b/src/platform/qt/ForwarderView.cpp @@ -15,7 +15,7 @@ using namespace QGBA; ForwarderView::ForwarderView(QWidget* parent) - : QDialog(parent) + : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) { m_ui.setupUi(this); @@ -189,7 +189,7 @@ void ForwarderView::connectBrowseButton(QAbstractButton* button, QLineEdit* line } void ForwarderView::selectImage() { - QString filename = GBAApp::app()->getOpenFileName(this, tr("Select an image"), {}); + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select an image"), tr("Image files (*.png *.jpg *.bmp)")); if (filename.isEmpty()) { return; } diff --git a/src/platform/qt/FrameView.cpp b/src/platform/qt/FrameView.cpp index 1f9d4705d..be2d55d40 100644 --- a/src/platform/qt/FrameView.cpp +++ b/src/platform/qt/FrameView.cpp @@ -548,6 +548,11 @@ void FrameView::newVl() { m_vl->deinit(m_vl); } m_vl = mCoreFindVF(m_currentFrame); + if (!m_vl) { + m_currentFrame->close(m_currentFrame); + m_currentFrame = nullptr; + return; + } m_vl->init(m_vl); m_vl->loadROM(m_vl, m_currentFrame); m_currentFrame = nullptr; @@ -559,7 +564,7 @@ void FrameView::newVl() { } #endif unsigned width, height; - m_vl->desiredVideoDimensions(m_vl, &width, &height); + m_vl->baseVideoSize(m_vl, &width, &height); m_framebuffer = QImage(width, height, QImage::Format_RGBX8888); m_vl->setVideoBuffer(m_vl, reinterpret_cast(m_framebuffer.bits()), width); m_vl->reset(m_vl); diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp index c7c380a7c..e21dbef05 100644 --- a/src/platform/qt/GBAApp.cpp +++ b/src/platform/qt/GBAApp.cpp @@ -169,10 +169,14 @@ void GBAApp::continueAll(const QList& paused) { } } -QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QString& filter) { +QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QString& filter, const QString& path) { QList paused; + QString base(path); + if (base.isNull()) { + base = m_configController->getOption("lastDirectory"); + } pauseAll(&paused); - QString filename = QFileDialog::getOpenFileName(owner, title, m_configController->getOption("lastDirectory"), filter); + QString filename = QFileDialog::getOpenFileName(owner, title, base, filter); continueAll(paused); if (!filename.isEmpty()) { m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath()); @@ -180,10 +184,14 @@ QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QStr return filename; } -QStringList GBAApp::getOpenFileNames(QWidget* owner, const QString& title, const QString& filter) { +QStringList GBAApp::getOpenFileNames(QWidget* owner, const QString& title, const QString& filter, const QString& path) { QList paused; + QString base(path); + if (base.isNull()) { + base = m_configController->getOption("lastDirectory"); + } pauseAll(&paused); - QStringList filenames = QFileDialog::getOpenFileNames(owner, title, m_configController->getOption("lastDirectory"), filter); + QStringList filenames = QFileDialog::getOpenFileNames(owner, title, base, filter); continueAll(paused); if (!filenames.isEmpty()) { m_configController->setOption("lastDirectory", QFileInfo(filenames.at(0)).dir().canonicalPath()); @@ -191,10 +199,14 @@ QStringList GBAApp::getOpenFileNames(QWidget* owner, const QString& title, const return filenames; } -QString GBAApp::getSaveFileName(QWidget* owner, const QString& title, const QString& filter) { +QString GBAApp::getSaveFileName(QWidget* owner, const QString& title, const QString& filter, const QString& path) { QList paused; + QString base(path); + if (base.isNull()) { + base = m_configController->getOption("lastDirectory"); + } pauseAll(&paused); - QString filename = QFileDialog::getSaveFileName(owner, title, m_configController->getOption("lastDirectory"), filter); + QString filename = QFileDialog::getSaveFileName(owner, title, base, filter); continueAll(paused); if (!filename.isEmpty()) { m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath()); diff --git a/src/platform/qt/GBAApp.h b/src/platform/qt/GBAApp.h index 3110b0733..e9907685f 100644 --- a/src/platform/qt/GBAApp.h +++ b/src/platform/qt/GBAApp.h @@ -63,9 +63,9 @@ public: QList windows() { return m_windows; } - QString getOpenFileName(QWidget* owner, const QString& title, const QString& filter = {}); - QStringList getOpenFileNames(QWidget* owner, const QString& title, const QString& filter = {}); - QString getSaveFileName(QWidget* owner, const QString& title, const QString& filter = {}); + QString getOpenFileName(QWidget* owner, const QString& title, const QString& filter = {}, const QString& path = {}); + QStringList getOpenFileNames(QWidget* owner, const QString& title, const QString& filter = {}, const QString& path = {}); + QString getSaveFileName(QWidget* owner, const QString& title, const QString& filter = {}, const QString& path = {}); QString getOpenDirectoryName(QWidget* owner, const QString& title, const QString& path = {}); const NoIntroDB* gameDB() const { return m_db; } diff --git a/src/platform/qt/GBAOverride.cpp b/src/platform/qt/GBAOverride.cpp index 885a5d3ac..57fb2048e 100644 --- a/src/platform/qt/GBAOverride.cpp +++ b/src/platform/qt/GBAOverride.cpp @@ -10,17 +10,6 @@ using namespace QGBA; -void GBAOverride::apply(struct mCore* core) { - if (core->platform(core) != mPLATFORM_GBA) { - return; - } - GBA* gba = static_cast(core->board); - if (!vbaBugCompatSet) { - override.vbaBugCompat = gba->vbaBugCompat; - } - GBAOverrideApply(gba, &override); -} - void GBAOverride::identify(const struct mCore* core) { if (core->platform(core) != mPLATFORM_GBA) { return; @@ -33,3 +22,7 @@ void GBAOverride::identify(const struct mCore* core) { void GBAOverride::save(struct Configuration* config) const { GBAOverrideSave(config, &override); } + +const void* GBAOverride::raw() const { + return &override; +} diff --git a/src/platform/qt/GBAOverride.h b/src/platform/qt/GBAOverride.h index 8b07659fd..477af0014 100644 --- a/src/platform/qt/GBAOverride.h +++ b/src/platform/qt/GBAOverride.h @@ -13,9 +13,9 @@ namespace QGBA { class GBAOverride : public Override { public: - void apply(struct mCore*) override; void identify(const struct mCore*) override; void save(struct Configuration*) const override; + const void* raw() const override; struct GBACartridgeOverride override; bool vbaBugCompatSet; diff --git a/src/platform/qt/GBOverride.cpp b/src/platform/qt/GBOverride.cpp index ed66f327d..224830437 100644 --- a/src/platform/qt/GBOverride.cpp +++ b/src/platform/qt/GBOverride.cpp @@ -11,13 +11,6 @@ using namespace QGBA; -void GBOverride::apply(struct mCore* core) { - if (core->platform(core) != mPLATFORM_GB) { - return; - } - GBOverrideApply(static_cast(core->board), &override); -} - void GBOverride::identify(const struct mCore* core) { if (core->platform(core) != mPLATFORM_GB) { return; @@ -32,3 +25,7 @@ void GBOverride::identify(const struct mCore* core) { void GBOverride::save(struct Configuration* config) const { GBOverrideSave(config, &override); } + +const void* GBOverride::raw() const { + return &override; +} diff --git a/src/platform/qt/GBOverride.h b/src/platform/qt/GBOverride.h index 82c53ca7f..e967211de 100644 --- a/src/platform/qt/GBOverride.h +++ b/src/platform/qt/GBOverride.h @@ -13,9 +13,9 @@ namespace QGBA { class GBOverride : public Override { public: - void apply(struct mCore*) override; void identify(const struct mCore*) override; void save(struct Configuration*) const override; + const void* raw() const override; struct GBCartridgeOverride override; }; diff --git a/src/platform/qt/GDBController.cpp b/src/platform/qt/GDBController.cpp index 5fd2a1768..583ffed62 100644 --- a/src/platform/qt/GDBController.cpp +++ b/src/platform/qt/GDBController.cpp @@ -20,10 +20,6 @@ ushort GDBController::port() { return m_port; } -bool GDBController::isAttached() { - return m_gameController && m_gameController->debugger() == &m_gdbStub.d; -} - void GDBController::setPort(ushort port) { m_port = port; } diff --git a/src/platform/qt/GDBController.h b/src/platform/qt/GDBController.h index d83f00028..6bf0a7132 100644 --- a/src/platform/qt/GDBController.h +++ b/src/platform/qt/GDBController.h @@ -23,7 +23,6 @@ public: public: ushort port(); - bool isAttached(); public slots: void setPort(ushort port); diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index d8b5dd783..21d021edc 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -25,9 +25,11 @@ using namespace QGBA; -InputController::InputController(int playerId, QWidget* topLevel, QObject* parent) +int InputController::s_claimedPlayers = 0; + +InputController::InputController(QWidget* topLevel, QObject* parent) : QObject(parent) - , m_playerId(playerId) + , m_playerId(claimPlayer()) , m_topLevel(topLevel) , m_focusParent(topLevel) { @@ -130,6 +132,7 @@ InputController::InputController(int playerId, QWidget* topLevel, QObject* paren InputController::~InputController() { mInputMapDeinit(&m_inputMap); + freePlayer(m_playerId); } void InputController::addInputDriver(std::shared_ptr driver) { @@ -151,7 +154,7 @@ bool InputController::loadConfiguration(uint32_t type) { if (!mInputMapLoad(&m_inputMap, type, m_config->input())) { return false; } - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver) { return false; } @@ -185,7 +188,7 @@ void InputController::saveConfiguration() { void InputController::saveConfiguration(uint32_t type) { mInputMapSave(&m_inputMap, type, m_config->input()); - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (driver) { driver->saveConfiguration(m_config); } @@ -201,7 +204,7 @@ void InputController::saveProfile(uint32_t type, const QString& profile) { } QString InputController::profileForType(uint32_t type) { - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver) { return {}; } @@ -209,7 +212,7 @@ QString InputController::profileForType(uint32_t type) { } void InputController::setGamepadDriver(uint32_t type) { - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver || !driver->supportsGamepads()) { return; } @@ -220,13 +223,13 @@ QStringList InputController::connectedGamepads(uint32_t type) const { if (!type) { type = m_gamepadDriver; } - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver) { return {}; } QStringList pads; - for (auto pad : driver->connectedGamepads()) { + for (auto& pad : driver->connectedGamepads()) { pads.append(pad->visibleName()); } return pads; @@ -236,7 +239,7 @@ int InputController::gamepadIndex(uint32_t type) const { if (!type) { type = m_gamepadDriver; } - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver) { return -1; } @@ -247,7 +250,7 @@ void InputController::setGamepad(uint32_t type, int index) { if (!type) { type = m_gamepadDriver; } - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver) { return; } @@ -265,7 +268,7 @@ void InputController::setPreferredGamepad(uint32_t type, int index) { if (!type) { type = m_gamepadDriver; } - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver) { return; } @@ -299,7 +302,7 @@ InputMapper InputController::mapper(InputSource* source) { } void InputController::setSensorDriver(uint32_t type) { - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver || !driver->supportsSensors()) { return; } @@ -308,7 +311,7 @@ void InputController::setSensorDriver(uint32_t type) { mRumble* InputController::rumble() { - auto driver = m_inputDrivers.value(m_sensorDriver); + auto& driver = m_inputDrivers.value(m_sensorDriver); if (driver) { return driver->rumble(); } @@ -316,7 +319,7 @@ mRumble* InputController::rumble() { } mRotationSource* InputController::rotationSource() { - auto driver = m_inputDrivers.value(m_sensorDriver); + auto& driver = m_inputDrivers.value(m_sensorDriver); if (driver) { return driver->rotationSource(); } @@ -341,7 +344,7 @@ void InputController::update() { int InputController::pollEvents() { int activeButtons = 0; - for (auto pad : gamepads()) { + for (auto& pad : gamepads()) { InputMapper im(mapper(pad)); activeButtons |= im.mapKeys(pad->currentButtons()); activeButtons |= im.mapAxes(pad->currentAxes()); @@ -356,7 +359,7 @@ int InputController::pollEvents() { } Gamepad* InputController::gamepad(uint32_t type) { - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver) { return nullptr; } @@ -464,7 +467,7 @@ void InputController::testGamepad(uint32_t type) { } } } - for (auto axis : oldAxes) { + for (auto& axis : oldAxes) { GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, false, type, this); clearPendingEvent(event->platformKey()); sendGamepadEvent(event); @@ -550,6 +553,20 @@ bool InputController::hasPendingEvent(int key) const { return m_pendingEvents.contains(key); } +int InputController::claimPlayer() { + for (int i = 0; i < MAX_GBAS; ++i) { + if (!(s_claimedPlayers & (1 << i))) { + s_claimedPlayers |= 1 << i; + return i; + } + } + qFatal("Can't claim 5th player. Please report this bug."); +} + +void InputController::freePlayer(int player) { + s_claimedPlayers &= ~(1 << player); +} + void InputController::stealFocus(QWidget* focus) { m_focusParent = focus; } diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h index 12f4e8915..5b7a01a44 100644 --- a/src/platform/qt/InputController.h +++ b/src/platform/qt/InputController.h @@ -25,7 +25,9 @@ #include #ifdef BUILD_QT_MULTIMEDIA +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) #include "VideoDumper.h" +#endif #include #endif @@ -51,7 +53,7 @@ public: static const uint32_t KEYBOARD = 0x51545F4B; - InputController(int playerId = 0, QWidget* topLevel = nullptr, QObject* parent = nullptr); + InputController(QWidget* topLevel = nullptr, QObject* parent = nullptr); ~InputController(); void addInputDriver(std::shared_ptr); @@ -138,6 +140,9 @@ private: bool hasPendingEvent(int key) const; void sendGamepadEvent(QEvent*); + static int claimPlayer(); + static void freePlayer(int); + Gamepad* gamepad(uint32_t type); QList gamepads(); @@ -165,9 +170,12 @@ private: bool m_cameraActive = false; QByteArray m_cameraDevice; std::unique_ptr m_camera; +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) VideoDumper m_videoDumper; +#endif #endif + static int s_claimedPlayers; mInputMap m_inputMap; ConfigController* m_config = nullptr; int m_playerId; diff --git a/src/platform/qt/InputProfile.cpp b/src/platform/qt/InputProfile.cpp index e8f7f1711..852dbb413 100644 --- a/src/platform/qt/InputProfile.cpp +++ b/src/platform/qt/InputProfile.cpp @@ -8,7 +8,7 @@ #include "input/InputMapper.h" #include "InputController.h" -#include +#include using namespace QGBA; @@ -202,8 +202,8 @@ constexpr InputProfile::InputProfile(const char* name, const InputProfile* InputProfile::findProfile(const QString& name) { for (size_t i = 0; i < sizeof(s_defaultMaps) / sizeof(*s_defaultMaps); ++i) { - QRegExp re(s_defaultMaps[i].m_profileName); - if (re.exactMatch(name)) { + QRegularExpression re(QString("^%1$").arg(s_defaultMaps[i].m_profileName)); + if (re.match(name).hasMatch()) { return &s_defaultMaps[i]; } } diff --git a/src/platform/qt/KeyEditor.cpp b/src/platform/qt/KeyEditor.cpp index e8da70970..7d3ba1990 100644 --- a/src/platform/qt/KeyEditor.cpp +++ b/src/platform/qt/KeyEditor.cpp @@ -8,6 +8,7 @@ #include "input/GamepadAxisEvent.h" #include "input/GamepadButtonEvent.h" #include "ShortcutController.h" +#include "utils.h" #include #include @@ -33,35 +34,7 @@ void KeyEditor::setValue(int key) { if (key < 0) { setText(tr("---")); } else { - QKeySequence seq(key); - switch (key) { -#ifndef Q_OS_MAC - case Qt::Key_Shift: - setText(QCoreApplication::translate("QShortcut", "Shift")); - break; - case Qt::Key_Control: - setText(QCoreApplication::translate("QShortcut", "Control")); - break; - case Qt::Key_Alt: - setText(QCoreApplication::translate("QShortcut", "Alt")); - break; - case Qt::Key_Meta: - setText(QCoreApplication::translate("QShortcut", "Meta")); - break; -#endif - case Qt::Key_Super_L: - setText(tr("Super (L)")); - break; - case Qt::Key_Super_R: - setText(tr("Super (R)")); - break; - case Qt::Key_Menu: - setText(tr("Menu")); - break; - default: - setText(QKeySequence(key).toString(QKeySequence::NativeText)); - break; - } + setText(keyName(key)); } } emit valueChanged(key); diff --git a/src/platform/qt/LogController.h b/src/platform/qt/LogController.h index 548be8b4d..6bd4e5707 100644 --- a/src/platform/qt/LogController.h +++ b/src/platform/qt/LogController.h @@ -79,8 +79,8 @@ public slots: private: mLogFilter m_filter; - bool m_logToFile; - bool m_logToStdout; + bool m_logToFile = false; + bool m_logToStdout = false; std::unique_ptr m_logFile; std::unique_ptr m_logStream; diff --git a/src/platform/qt/MapView.cpp b/src/platform/qt/MapView.cpp index da65abf9d..324f9800a 100644 --- a/src/platform/qt/MapView.cpp +++ b/src/platform/qt/MapView.cpp @@ -9,7 +9,7 @@ #include "GBAApp.h" #include "LogController.h" -#include +#include #include #ifdef M_CORE_GBA #include @@ -42,7 +42,7 @@ MapView::MapView(std::shared_ptr controller, QWidget* parent) #ifdef M_CORE_GBA case mPLATFORM_GBA: m_boundary = 2048; - m_ui.tile->setMaxTile(3096); + m_ui.tile->setMaxTile(3072); m_addressBase = GBA_BASE_VRAM; m_addressWidth = 8; m_ui.bgInfo->addCustomProperty("priority", tr("Priority")); @@ -119,6 +119,9 @@ void MapView::selectMap(int map) { } m_map = map; m_mapStatus.fill({}); + // Different maps can have different max palette counts; set it to + // 0 immediately to avoid tile lookups with state palette IDs break + m_ui.tile->setPalette(0); updateTiles(true); } @@ -184,11 +187,18 @@ void MapView::updateTilesGBA(bool) { frame = GBARegisterDISPCNTGetFrameSelect(io[REG_DISPCNT >> 1]); } } + m_boundary = 1024; + m_ui.tile->setMaxTile(1536); priority = GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + m_map]); if (mode == 0 || (mode == 1 && m_map != 2)) { offset = QString("%1, %2") .arg(io[(REG_BG0HOFS >> 1) + (m_map << 1)]) .arg(io[(REG_BG0VOFS >> 1) + (m_map << 1)]); + + if (!GBARegisterBGCNTIs256Color(io[(REG_BG0CNT >> 1) + m_map])) { + m_boundary = 2048; + m_ui.tile->setMaxTile(3072); + } } else if ((mode > 0 && m_map == 2) || (mode == 2 && m_map == 3)) { int32_t refX = io[(REG_BG2X_LO >> 1) + ((m_map - 2) << 2)]; refX |= io[(REG_BG2X_HI >> 1) + ((m_map - 2) << 2)] << 16; diff --git a/src/platform/qt/MemorySearch.cpp b/src/platform/qt/MemorySearch.cpp index 8589f9cfe..f61fb9e52 100644 --- a/src/platform/qt/MemorySearch.cpp +++ b/src/platform/qt/MemorySearch.cpp @@ -178,7 +178,7 @@ void MemorySearch::refresh() { mCoreMemorySearchResult* result = mCoreMemorySearchResultsGetPointer(&m_results, i); QTableWidgetItem* item = new QTableWidgetItem(QString("%1").arg(result->address, 8, 16, QChar('0'))); m_ui.results->setItem(i, 0, item); - QTableWidgetItem* type; + QTableWidgetItem* type = nullptr; QByteArray string; if (result->type == mCORE_MEMORY_SEARCH_INT && m_ui.numHex->isChecked()) { switch (result->width) { @@ -213,7 +213,12 @@ void MemorySearch::refresh() { string.append(core->rawRead8(core, result->address + i, result->segment)); } item = new QTableWidgetItem(QLatin1String(string)); // TODO + break; + case mCORE_MEMORY_SEARCH_GUESS: + item = nullptr; + break; } + Q_ASSERT(item); } QString divisor; if (result->guessDivisor > 1) { @@ -231,7 +236,12 @@ void MemorySearch::refresh() { break; case mCORE_MEMORY_SEARCH_STRING: type = new QTableWidgetItem("string"); + break; + case mCORE_MEMORY_SEARCH_GUESS: + break; } + Q_ASSERT(type); + m_ui.results->setItem(i, 1, item); m_ui.results->setItem(i, 2, type); m_ui.opDelta->setEnabled(true); diff --git a/src/platform/qt/MultiplayerController.cpp b/src/platform/qt/MultiplayerController.cpp index f44323603..972213f67 100644 --- a/src/platform/qt/MultiplayerController.cpp +++ b/src/platform/qt/MultiplayerController.cpp @@ -6,6 +6,7 @@ #include "MultiplayerController.h" #include "CoreController.h" +#include "LogController.h" #ifdef M_CORE_GBA #include @@ -18,21 +19,10 @@ using namespace QGBA; -#ifdef M_CORE_GB -MultiplayerController::Player::Player(CoreController* coreController, GBSIOLockstepNode* gbNode) +MultiplayerController::Player::Player(CoreController* coreController) : controller(coreController) { - node.gb = gbNode; } -#endif - -#ifdef M_CORE_GBA -MultiplayerController::Player::Player(CoreController* coreController, GBASIOLockstepNode* gbaNode) - : controller(coreController) -{ - node.gba = gbaNode; -} -#endif int MultiplayerController::Player::id() const { switch (controller->platform()) { @@ -67,7 +57,7 @@ MultiplayerController::MultiplayerController() { }; m_lockstep.signal = [](mLockstep* lockstep, unsigned mask) { MultiplayerController* controller = static_cast(lockstep->context); - Player* player = &controller->m_players[0]; + Player* player = controller->player(0); bool woke = false; player->waitMask &= ~mask; if (!player->waitMask && player->awake < 1) { @@ -79,7 +69,7 @@ MultiplayerController::MultiplayerController() { }; m_lockstep.wait = [](mLockstep* lockstep, unsigned mask) { MultiplayerController* controller = static_cast(lockstep->context); - Player* player = &controller->m_players[0]; + Player* player = controller->player(0); bool slept = false; player->waitMask |= mask; if (player->awake > 0) { @@ -214,8 +204,10 @@ MultiplayerController::~MultiplayerController() { } bool MultiplayerController::attachGame(CoreController* controller) { - if (m_lockstep.attached == MAX_GBAS) { - return false; + QList interrupters; + interrupters.append(controller); + for (Player& p : m_pids.values()) { + interrupters.append(p.controller); } if (m_lockstep.attached == 0) { @@ -233,6 +225,9 @@ bool MultiplayerController::attachGame(CoreController* controller) { default: return false; } + m_platform = controller->platform(); + } else if (controller->platform() != m_platform) { + return false; } mCoreThread* thread = controller->thread(); @@ -240,43 +235,79 @@ bool MultiplayerController::attachGame(CoreController* controller) { return false; } + Player player{controller}; switch (controller->platform()) { #ifdef M_CORE_GBA case mPLATFORM_GBA: { + if (m_lockstep.attached >= MAX_GBAS) { + return false; + } + GBA* gba = static_cast(thread->core->board); GBASIOLockstepNode* node = new GBASIOLockstepNode; GBASIOLockstepNodeCreate(node); GBASIOLockstepAttachNode(&m_gbaLockstep, node); - m_players.append({controller, node}); + player.node.gba = node; GBASIOSetDriver(&gba->sio, &node->d, SIO_MULTI); GBASIOSetDriver(&gba->sio, &node->d, SIO_NORMAL_32); - - emit gameAttached(); - return true; + break; } #endif #ifdef M_CORE_GB case mPLATFORM_GB: { + if (m_lockstep.attached >= 2) { + return false; + } + GB* gb = static_cast(thread->core->board); GBSIOLockstepNode* node = new GBSIOLockstepNode; GBSIOLockstepNodeCreate(node); GBSIOLockstepAttachNode(&m_gbLockstep, node); - m_players.append({controller, node}); + player.node.gb = node; GBSIOSetDriver(&gb->sio, &node->d); - - emit gameAttached(); - return true; + break; } #endif default: - break; + return false; } - return false; + QPair path(controller->path(), controller->baseDirectory()); + int claimed = m_claimed[path]; + + int saveId = 0; + mCoreConfigGetIntValue(&controller->thread()->core->config, "savePlayerId", &saveId); + + if (claimed) { + player.saveId = 0; + for (int i = 0; i < MAX_GBAS; ++i) { + if (claimed & (1 << i)) { + continue; + } + player.saveId = i + 1; + break; + } + if (!player.saveId) { + LOG(QT, ERROR) << "Couldn't find available save ID"; + player.saveId = 1; + } + } else if (saveId) { + player.saveId = saveId; + } else { + player.saveId = 1; + } + m_claimed[path] |= 1 << (player.saveId - 1); + + m_pids.insert(m_nextPid, player); + ++m_nextPid; + fixOrder(); + + emit gameAttached(); + return true; } void MultiplayerController::detachGame(CoreController* controller) { @@ -289,8 +320,18 @@ void MultiplayerController::detachGame(CoreController* controller) { } QList interrupters; + int pid = -1; for (int i = 0; i < m_players.count(); ++i) { - interrupters.append(m_players[i].controller); + Player* p = player(i); + if (!p) { + LOG(QT, ERROR) << tr("Trying to detach a multiplayer player that's not attached"); + return; + } + CoreController* playerController = p->controller; + if (playerController == controller) { + pid = m_players[i]; + } + interrupters.append(playerController); } switch (controller->platform()) { #ifdef M_CORE_GBA @@ -322,24 +363,55 @@ void MultiplayerController::detachGame(CoreController* controller) { break; } - for (int i = 0; i < m_players.count(); ++i) { - if (m_players[i].controller == controller) { - m_players.removeAt(i); - break; + // TODO: This might change if we replace the ROM--make sure to handle this properly + QPair path(controller->path(), controller->baseDirectory()); + Player& p = m_pids.find(pid).value(); + if (!p.saveId) { + LOG(QT, ERROR) << "Clearing invalid save ID"; + } else { + m_claimed[path] &= ~(1 << (p.saveId - 1)); + if (!m_claimed[path]) { + m_claimed.remove(path); } } + + m_pids.remove(pid); + if (m_pids.size() == 0) { + m_platform = mPLATFORM_NONE; + } else { + fixOrder(); + } emit gameDetached(); } -int MultiplayerController::playerId(CoreController* controller) { +int MultiplayerController::playerId(CoreController* controller) const { for (int i = 0; i < m_players.count(); ++i) { - if (m_players[i].controller == controller) { + const Player* p = player(i); + if (!p) { + LOG(QT, ERROR) << tr("Trying to get player ID for a multiplayer player that's not attached"); + return -1; + } + if (p->controller == controller) { return i; } } return -1; } +int MultiplayerController::saveId(CoreController* controller) const { + for (int i = 0; i < m_players.count(); ++i) { + const Player* p = player(i); + if (!p) { + LOG(QT, ERROR) << tr("Trying to get save ID for a multiplayer player that's not attached"); + return -1; + } + if (p->controller == controller) { + return p->saveId; + } + } + return -1; +} + int MultiplayerController::attached() { int num; num = m_lockstep.attached; @@ -347,27 +419,52 @@ int MultiplayerController::attached() { } MultiplayerController::Player* MultiplayerController::player(int id) { - Player* player = &m_players[id]; - switch (player->controller->platform()) { + if (id >= m_players.size()) { + return nullptr; + } + int pid = m_players[id]; + auto iter = m_pids.find(pid); + if (iter == m_pids.end()) { + return nullptr; + } + return &iter.value(); +} + +const MultiplayerController::Player* MultiplayerController::player(int id) const { + if (id >= m_players.size()) { + return nullptr; + } + int pid = m_players[id]; + auto iter = m_pids.find(pid); + if (iter == m_pids.end()) { + return nullptr; + } + return &iter.value(); +} + +void MultiplayerController::fixOrder() { + m_players.clear(); + m_players = m_pids.keys(); + std::sort(m_players.begin(), m_players.end()); + switch (m_platform) { #ifdef M_CORE_GBA case mPLATFORM_GBA: - if (player->node.gba->id != id) { - std::sort(m_players.begin(), m_players.end()); - player = &m_players[id]; + for (int pid : m_pids.keys()) { + Player& p = m_pids.find(pid).value(); + GBA* gba = static_cast(p.controller->thread()->core->board); + GBASIOLockstepNode* node = reinterpret_cast(gba->sio.drivers.multiplayer); + m_players[node->id] = pid; } break; #endif #ifdef M_CORE_GB case mPLATFORM_GB: - if (player->node.gb->id != id) { + if (player(0)->node.gb->id == 1) { std::swap(m_players[0], m_players[1]); - player = &m_players[id]; } break; #endif case mPLATFORM_NONE: break; } - - return player; } diff --git a/src/platform/qt/MultiplayerController.h b/src/platform/qt/MultiplayerController.h index 5ad6124db..b5582319f 100644 --- a/src/platform/qt/MultiplayerController.h +++ b/src/platform/qt/MultiplayerController.h @@ -5,10 +5,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #pragma once +#include #include #include #include +#include #include #ifdef M_CORE_GBA #include @@ -37,7 +39,8 @@ public: void detachGame(CoreController*); int attached(); - int playerId(CoreController*); + int playerId(CoreController*) const; + int saveId(CoreController*) const; signals: void gameAttached(); @@ -49,12 +52,7 @@ private: GBASIOLockstepNode* gba; }; struct Player { -#ifdef M_CORE_GB - Player(CoreController* controller, GBSIOLockstepNode* node); -#endif -#ifdef M_CORE_GBA - Player(CoreController* controller, GBASIOLockstepNode* node); -#endif + Player(CoreController* controller); int id() const; bool operator<(const Player&) const; @@ -64,9 +62,12 @@ private: int awake = 1; int32_t cyclesPosted = 0; unsigned waitMask = 0; + int saveId = 1; }; Player* player(int id); + const Player* player(int id) const; + void fixOrder(); union { mLockstep m_lockstep; @@ -77,8 +78,13 @@ private: GBASIOLockstep m_gbaLockstep; #endif }; - QList m_players; + + mPlatform m_platform = mPLATFORM_NONE; + int m_nextPid = 0; + QHash m_pids; + QList m_players; QMutex m_lock; + QHash, int> m_claimed; }; } diff --git a/src/platform/qt/Override.h b/src/platform/qt/Override.h index 2ff209d8c..bf28aa755 100644 --- a/src/platform/qt/Override.h +++ b/src/platform/qt/Override.h @@ -14,9 +14,9 @@ class Override { public: virtual ~Override() {} - virtual void apply(struct mCore*) = 0; virtual void identify(const struct mCore*) = 0; virtual void save(struct Configuration*) const = 0; + virtual const void* raw() const = 0; }; } diff --git a/src/platform/qt/PaletteView.cpp b/src/platform/qt/PaletteView.cpp index 7d70c9741..09eaed347 100644 --- a/src/platform/qt/PaletteView.cpp +++ b/src/platform/qt/PaletteView.cpp @@ -19,7 +19,7 @@ #ifdef M_CORE_GB #include #endif -#include +#include #include using namespace QGBA; @@ -145,9 +145,9 @@ void PaletteView::exportPalette(int start, int length) { return; } if (filename.endsWith(".pal", Qt::CaseInsensitive)) { - exportPaletteRIFF(vf, length, &static_cast(m_controller->thread()->core->board)->video.palette[start]); + mPaletteExportRIFF(vf, length, &static_cast(m_controller->thread()->core->board)->video.palette[start]); } else if (filename.endsWith(".act", Qt::CaseInsensitive)) { - exportPaletteACT(vf, length, &static_cast(m_controller->thread()->core->board)->video.palette[start]); + mPaletteExportACT(vf, length, &static_cast(m_controller->thread()->core->board)->video.palette[start]); } vf->close(vf); } diff --git a/src/platform/qt/ROMInfo.cpp b/src/platform/qt/ROMInfo.cpp index f690982c4..473918f26 100644 --- a/src/platform/qt/ROMInfo.cpp +++ b/src/platform/qt/ROMInfo.cpp @@ -62,4 +62,11 @@ ROMInfo::ROMInfo(std::shared_ptr controller, QWidget* parent) m_ui.crc->setText(tr("(unknown)")); m_ui.name->setText(tr("(unknown)")); } + + QString savePath = controller->savePath(); + if (!savePath.isEmpty()) { + m_ui.savefile->setText(savePath); + } else { + m_ui.savefile->setText(tr("(unknown)")); + } } diff --git a/src/platform/qt/ROMInfo.ui b/src/platform/qt/ROMInfo.ui index c458a7a07..727cff032 100644 --- a/src/platform/qt/ROMInfo.ui +++ b/src/platform/qt/ROMInfo.ui @@ -108,6 +108,20 @@ + + + + Save file: + + + + + + + {SAVEFILE} + + + diff --git a/src/platform/qt/ReportView.cpp b/src/platform/qt/ReportView.cpp index c38c84a03..04944ef5c 100644 --- a/src/platform/qt/ReportView.cpp +++ b/src/platform/qt/ReportView.cpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include "CoreController.h" @@ -127,6 +127,7 @@ void ReportView::generateReport() { swReport << QString("Build architecture: %1").arg(QSysInfo::buildCpuArchitecture()); swReport << QString("Run architecture: %1").arg(QSysInfo::currentCpuArchitecture()); swReport << QString("Qt version: %1").arg(QLatin1String(qVersion())); + swReport << QString("Qt QPA platform: %1").arg(QGuiApplication::platformName()); #ifdef USE_FFMPEG QStringList libavVers; libavVers << QLatin1String(LIBAVCODEC_IDENT); diff --git a/src/platform/qt/SaveConverter.cpp b/src/platform/qt/SaveConverter.cpp index 712396517..e51a3f044 100644 --- a/src/platform/qt/SaveConverter.cpp +++ b/src/platform/qt/SaveConverter.cpp @@ -198,19 +198,24 @@ void SaveConverter::detectFromSize(std::shared_ptr vf) { #ifdef M_CORE_GBA switch (vf->size()) { case GBA_SIZE_SRAM: + case GBA_SIZE_SRAM + 16: m_validSaves.append(AnnotatedSave{SAVEDATA_SRAM, vf}); break; case GBA_SIZE_FLASH512: + case GBA_SIZE_FLASH512 + 16: m_validSaves.append(AnnotatedSave{SAVEDATA_FLASH512, vf}); break; case GBA_SIZE_FLASH1M: + case GBA_SIZE_FLASH1M + 16: m_validSaves.append(AnnotatedSave{SAVEDATA_FLASH1M, vf}); break; case GBA_SIZE_EEPROM: + case GBA_SIZE_EEPROM + 16: m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM, vf, Endian::LITTLE}); m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM, vf, Endian::BIG}); break; case GBA_SIZE_EEPROM512: + case GBA_SIZE_EEPROM512 + 16: m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM512, vf, Endian::LITTLE}); m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM512, vf, Endian::BIG}); break; @@ -478,6 +483,9 @@ SaveConverter::AnnotatedSave::operator QString() const { default: break; } + if ((size & 0xFF) == 0x10) { + typeFormat += QCoreApplication::translate("QGBA::SaveConverter", " + RTC"); + } break; #endif #ifdef M_CORE_GB @@ -615,9 +623,23 @@ QList SaveConverter::AnnotatedSave::possibleConver } break; default: + if (size & 0xFF) { + AnnotatedSave noRtc = same; + noRtc.size &= ~0xFF; + possible.append(noRtc); + } break; } break; +#endif +#ifdef M_CORE_GBA + case mPLATFORM_GBA: + if ((size & 0xFF) == 0x10) { + AnnotatedSave noRtc = same; + noRtc.size &= ~0xFF; + possible.append(noRtc); + } + break; #endif default: break; @@ -650,7 +672,7 @@ QByteArray SaveConverter::AnnotatedSave::convertTo(const SaveConverter::Annotate } converted.resize(target.size); buffer = backing->readAll(); - for (int i = 0; i < size; i += 8) { + for (int i = 0; i < (size & ~0xFF); i += 8) { uint64_t word; const uint64_t* in = reinterpret_cast(buffer.constData()); uint64_t* out = reinterpret_cast(converted.data()); @@ -661,6 +683,9 @@ QByteArray SaveConverter::AnnotatedSave::convertTo(const SaveConverter::Annotate default: break; } + if (endianness == target.endianness && size > target.size) { + converted = backing->read(target.size); + } break; #endif #ifdef M_CORE_GB @@ -711,6 +736,9 @@ QByteArray SaveConverter::AnnotatedSave::convertTo(const SaveConverter::Annotate } break; default: + if (endianness == target.endianness && size > target.size) { + converted = backing->read(target.size); + } break; } break; diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 4eca3cb46..977a149ca 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -51,7 +51,7 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC #ifdef M_CORE_GB m_pageIndex[Page::GB] = 9; - for (auto model : GameBoy::modelList()) { + for (auto& model : GameBoy::modelList()) { m_ui.gbModel->addItem(GameBoy::modelName(model), model); m_ui.sgbModel->addItem(GameBoy::modelName(model), model); m_ui.cgbModel->addItem(GameBoy::modelName(model), model); @@ -143,6 +143,9 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC connect(m_ui.cheatsBrowse, &QAbstractButton::pressed, [this] () { selectPath(m_ui.cheatsPath, m_ui.cheatsSameDir); }); + connect(m_ui.bgImageBrowse, &QAbstractButton::pressed, [this] () { + selectImage(m_ui.bgImage); + }); connect(m_ui.clearCache, &QAbstractButton::pressed, this, &SettingsView::libraryCleared); // TODO: Move to reloadConfig() @@ -350,7 +353,7 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC QLocale englishLocale("en"); m_ui.languages->addItem(englishLocale.nativeLanguageName(), englishLocale); QDir ts(":/translations/"); - for (auto name : ts.entryList()) { + for (auto& name : ts.entryList()) { if (!name.endsWith(".qm") || !name.startsWith(binaryName)) { continue; } @@ -445,6 +448,13 @@ void SettingsView::selectPath(QLineEdit* field, QCheckBox* sameDir) { } } +void SettingsView::selectImage(QLineEdit* field) { + QString path = GBAApp::app()->getOpenFileName(this, tr("Select image"), tr("Image file (*.png *.jpg *.jpeg)")); + if (!path.isNull()) { + field->setText(makePortablePath(path)); + } +} + void SettingsView::updateConfig() { saveSetting("gba.bios", m_ui.gbaBios); saveSetting("gb.bios", m_ui.gbBios); @@ -470,6 +480,7 @@ void SettingsView::updateConfig() { saveSetting("fastForwardMute", m_ui.muteFf); saveSetting("rewindEnable", m_ui.rewind); saveSetting("rewindBufferCapacity", m_ui.rewindCapacity); + saveSetting("rewindBufferInterval", m_ui.rewindBufferInterval); saveSetting("resampleVideo", m_ui.resampleVideo); saveSetting("allowOpposingDirections", m_ui.allowOpposingDirections); saveSetting("suspendScreensaver", m_ui.suspendScreensaver); @@ -501,6 +512,7 @@ void SettingsView::updateConfig() { saveSetting("vbaBugCompat", m_ui.vbaBugCompat); saveSetting("updateAutoCheck", m_ui.updateAutoCheck); saveSetting("showFilenameInLibrary", m_ui.showFilenameInLibrary); + saveSetting("backgroundImage", m_ui.bgImage); if (m_ui.audioBufferSize->currentText().toInt() > 8192) { m_ui.audioBufferSize->setCurrentText("8192"); @@ -697,6 +709,7 @@ void SettingsView::reloadConfig() { loadSetting("fastForwardMute", m_ui.muteFf, m_ui.mute->isChecked()); loadSetting("rewindEnable", m_ui.rewind); loadSetting("rewindBufferCapacity", m_ui.rewindCapacity); + loadSetting("rewindBufferInterval", m_ui.rewindBufferInterval); loadSetting("resampleVideo", m_ui.resampleVideo); loadSetting("allowOpposingDirections", m_ui.allowOpposingDirections); loadSetting("suspendScreensaver", m_ui.suspendScreensaver); @@ -725,6 +738,7 @@ void SettingsView::reloadConfig() { loadSetting("vbaBugCompat", m_ui.vbaBugCompat, true); loadSetting("updateAutoCheck", m_ui.updateAutoCheck); loadSetting("showFilenameInLibrary", m_ui.showFilenameInLibrary); + loadSetting("backgroundImage", m_ui.bgImage); m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt()); diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h index d13a6b453..3543419fc 100644 --- a/src/platform/qt/SettingsView.h +++ b/src/platform/qt/SettingsView.h @@ -71,6 +71,7 @@ public slots: private slots: void selectBios(QLineEdit*); void selectPath(QLineEdit*, QCheckBox*); + void selectImage(QLineEdit*); void updateConfig(); void reloadConfig(); void updateChecked(); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 95f71565b..42e4af5a0 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -95,7 +95,7 @@ - 1 + 0 @@ -907,6 +907,41 @@ + + + + + + + 0 + 0 + + + + + + + + Browse + + + + + + + + + Qt::Horizontal + + + + + + + Custom border: + + + @@ -1160,21 +1195,51 @@ - + + + + Rewind speed: + + + + + + + + + × + + + 1 + + + 10 + + + 1 + + + 1 + + + + + + Qt::Horizontal - + Idle loops: - + @@ -1193,21 +1258,21 @@ - + Preload entire ROM into memory - + Enable Game Boy Player features by default - + Enable VBA bug compatibility in ROM hacks @@ -1836,8 +1901,8 @@ - 30 - 30 + 24 + 24 @@ -1855,8 +1920,8 @@ - 30 - 30 + 24 + 24 @@ -1874,8 +1939,8 @@ - 30 - 30 + 24 + 24 @@ -1893,8 +1958,8 @@ - 30 - 30 + 24 + 24 @@ -1923,8 +1988,8 @@ - 30 - 30 + 24 + 24 @@ -1942,8 +2007,8 @@ - 30 - 30 + 24 + 24 @@ -1961,8 +2026,8 @@ - 30 - 30 + 24 + 24 @@ -1980,8 +2045,8 @@ - 30 - 30 + 24 + 24 @@ -2010,8 +2075,8 @@ - 30 - 30 + 24 + 24 @@ -2029,8 +2094,8 @@ - 30 - 30 + 24 + 24 @@ -2048,8 +2113,8 @@ - 30 - 30 + 24 + 24 @@ -2067,8 +2132,8 @@ - 30 - 30 + 24 + 24 diff --git a/src/platform/qt/ShaderSelector.cpp b/src/platform/qt/ShaderSelector.cpp index 80a8fcd9d..44bca3855 100644 --- a/src/platform/qt/ShaderSelector.cpp +++ b/src/platform/qt/ShaderSelector.cpp @@ -19,8 +19,8 @@ #include #include +#include #include -#include "platform/video-backend.h" #if defined(BUILD_GL) || defined(BUILD_GLES2) @@ -63,7 +63,11 @@ void ShaderSelector::clear() { void ShaderSelector::selectShader() { QDir path(GBAApp::dataDir()); path.cd(QLatin1String("shaders")); +#if !defined(USE_LIBZIP) && !defined(USE_MINIZIP) QString name = GBAApp::app()->getOpenDirectoryName(this, tr("Load shader"), path.absolutePath()); +#else + QString name = GBAApp::app()->getOpenFileName(this, tr("Load shader"), "mGBA Shaders (*.shader)", path.absolutePath()); +#endif if (!name.isNull()) { loadShader(name); refreshShaders(); diff --git a/src/platform/qt/ShortcutController.h b/src/platform/qt/ShortcutController.h index 7eed7e1d7..6e72649b5 100644 --- a/src/platform/qt/ShortcutController.h +++ b/src/platform/qt/ShortcutController.h @@ -57,7 +57,7 @@ private: int m_shortcut = 0; int m_button = -1; int m_axis = -1; - GamepadAxisEvent::Direction m_direction; + GamepadAxisEvent::Direction m_direction = GamepadAxisEvent::NEUTRAL; }; class ShortcutController : public QObject { diff --git a/src/platform/qt/ShortcutModel.cpp b/src/platform/qt/ShortcutModel.cpp index 54fa83fcf..b73982cad 100644 --- a/src/platform/qt/ShortcutModel.cpp +++ b/src/platform/qt/ShortcutModel.cpp @@ -6,6 +6,7 @@ #include "ShortcutModel.h" #include "ShortcutController.h" +#include "utils.h" using namespace QGBA; @@ -33,7 +34,7 @@ QVariant ShortcutModel::data(const QModelIndex& index, int role) const { case 0: return m_controller->visibleName(item->name); case 1: - return shortcut ? QKeySequence(shortcut->shortcut()).toString(QKeySequence::NativeText) : QVariant(); + return shortcut ? keyName(shortcut->shortcut()) : QVariant(); case 2: if (!shortcut) { return QVariant(); @@ -134,4 +135,4 @@ void ShortcutModel::clearMenu(const QString&) { // TODO beginResetModel(); endResetModel(); -} \ No newline at end of file +} diff --git a/src/platform/qt/TileView.cpp b/src/platform/qt/TileView.cpp index 116935e29..7d8d1a71e 100644 --- a/src/platform/qt/TileView.cpp +++ b/src/platform/qt/TileView.cpp @@ -51,7 +51,7 @@ TileView::TileView(std::shared_ptr controller, QWidget* parent) #ifdef M_CORE_GBA case mPLATFORM_GBA: m_ui.tile->setBoundary(2048, 0, 2); - m_ui.tile->setMaxTile(3096); + m_ui.tile->setMaxTile(3072); break; #endif #ifdef M_CORE_GB @@ -76,7 +76,7 @@ TileView::TileView(std::shared_ptr controller, QWidget* parent) #ifdef M_CORE_GBA case mPLATFORM_GBA: m_ui.tile->setBoundary(2048 >> selected, selected, selected + 2); - m_ui.tile->setMaxTile(3096 >> selected); + m_ui.tile->setMaxTile(3072 >> selected); break; #endif #ifdef M_CORE_GB diff --git a/src/platform/qt/VideoProxy.cpp b/src/platform/qt/VideoProxy.cpp index 7c6366229..17168da0e 100644 --- a/src/platform/qt/VideoProxy.cpp +++ b/src/platform/qt/VideoProxy.cpp @@ -12,44 +12,67 @@ using namespace QGBA; VideoProxy::VideoProxy() { - mVideoLoggerRendererCreate(&m_logger.d, false); - m_logger.d.block = true; - m_logger.d.waitOnFlush = true; + mVideoLoggerRendererCreate(&m_logger, false); + m_logger.p = this; + m_logger.block = true; + m_logger.waitOnFlush = true; - m_logger.d.init = &cbind<&VideoProxy::init>; - m_logger.d.reset = &cbind<&VideoProxy::reset>; - m_logger.d.deinit = &cbind<&VideoProxy::deinit>; - m_logger.d.lock = &cbind<&VideoProxy::lock>; - m_logger.d.unlock = &cbind<&VideoProxy::unlock>; - m_logger.d.wait = &cbind<&VideoProxy::wait>; - m_logger.d.wake = &callback::func<&VideoProxy::wake>; + m_logger.init = &cbind<&VideoProxy::init>; + m_logger.reset = &cbind<&VideoProxy::reset>; + m_logger.deinit = &cbind<&VideoProxy::deinit>; + m_logger.lock = &cbind<&VideoProxy::lock>; + m_logger.unlock = &cbind<&VideoProxy::unlock>; + m_logger.wait = &cbind<&VideoProxy::wait>; + m_logger.wake = &callback::func<&VideoProxy::wake>; + RingFIFOInit(&m_dirtyQueue, 0x80000); - m_logger.d.writeData = &callback::func<&VideoProxy::writeData>; - m_logger.d.readData = &callback::func<&VideoProxy::readData>; - m_logger.d.postEvent = &callback::func<&VideoProxy::postEvent>; + m_logger.writeData = &callback::func<&VideoProxy::writeData>; + m_logger.readData = &callback::func<&VideoProxy::readData>; + m_logger.postEvent = &callback::func<&VideoProxy::postEvent>; + + mVideoProxyBackendInit(&m_backend, nullptr); + m_backend.context = this; + m_backend.wakeupCb = [](struct mVideoProxyBackend*, void* context) { + VideoProxy* self = static_cast(context); + QMetaObject::invokeMethod(self, "commandAvailable"); + }; connect(this, &VideoProxy::dataAvailable, this, &VideoProxy::processData); + connect(this, &VideoProxy::commandAvailable, this, &VideoProxy::processCommands); +} + +VideoProxy::~VideoProxy() { + mVideoProxyBackendDeinit(&m_backend); + RingFIFODeinit(&m_dirtyQueue); } void VideoProxy::attach(CoreController* controller) { CoreController::Interrupter interrupter(controller); - controller->thread()->core->videoLogger = &m_logger.d; + controller->thread()->core->videoLogger = &m_logger; } void VideoProxy::detach(CoreController* controller) { CoreController::Interrupter interrupter(controller); - if (controller->thread()->core->videoLogger == &m_logger.d) { + if (controller->thread()->core->videoLogger == &m_logger) { controller->thread()->core->videoLogger = nullptr; } } +void VideoProxy::setProxiedBackend(VideoBackend* backend) { + // TODO: This needs some safety around it + m_backend.backend = backend; +} + void VideoProxy::processData() { - mVideoLoggerRendererRun(&m_logger.d, false); + mVideoLoggerRendererRun(&m_logger, false); m_fromThreadCond.wakeAll(); } +void VideoProxy::processCommands() { + mVideoProxyBackendRun(&m_backend, false); +} + void VideoProxy::init() { - RingFIFOInit(&m_dirtyQueue, 0x80000); } void VideoProxy::reset() { @@ -60,14 +83,13 @@ void VideoProxy::reset() { } void VideoProxy::deinit() { - RingFIFODeinit(&m_dirtyQueue); } bool VideoProxy::writeData(const void* data, size_t length) { while (!RingFIFOWrite(&m_dirtyQueue, data, length)) { if (QThread::currentThread() == thread()) { // We're on the main thread - mVideoLoggerRendererRun(&m_logger.d, false); + mVideoLoggerRendererRun(&m_logger, false); } else { emit dataAvailable(); m_mutex.lock(); @@ -105,7 +127,7 @@ void VideoProxy::postEvent(enum mVideoLoggerEvent event) { void VideoProxy::handleEvent(int event) { m_mutex.lock(); - m_logger.d.handleEvent(&m_logger.d, static_cast(event)); + m_logger.handleEvent(&m_logger, static_cast(event)); m_mutex.unlock(); } @@ -122,7 +144,7 @@ void VideoProxy::wait() { while (RingFIFOSize(&m_dirtyQueue)) { if (QThread::currentThread() == thread()) { // We're on the main thread - mVideoLoggerRendererRun(&m_logger.d, false); + mVideoLoggerRendererRun(&m_logger, false); } else { emit dataAvailable(); m_toThreadCond.wakeAll(); diff --git a/src/platform/qt/VideoProxy.h b/src/platform/qt/VideoProxy.h index 3ba845617..43bb280c3 100644 --- a/src/platform/qt/VideoProxy.h +++ b/src/platform/qt/VideoProxy.h @@ -7,9 +7,12 @@ #include #include +#include +#include #include #include +#include #include namespace QGBA { @@ -21,16 +24,22 @@ Q_OBJECT public: VideoProxy(); + ~VideoProxy(); void attach(CoreController*); void detach(CoreController*); - void setBlocking(bool block) { m_logger.d.waitOnFlush = block; } + void setBlocking(bool block) { m_logger.waitOnFlush = block; } + + VideoBackend* backend() { return &m_backend.d; } + void setProxiedBackend(VideoBackend*); signals: void dataAvailable(); + void commandAvailable(); public slots: void processData(); + void processCommands(); void reset(); void handleEvent(int); @@ -51,22 +60,30 @@ private: using type = T (VideoProxy::*)(A...); template static T func(mVideoLogger* logger, A... args) { - VideoProxy* proxy = reinterpret_cast(logger)->p; + VideoProxy* proxy = static_cast(logger)->p; return (proxy->*F)(args...); } }; template static void cbind(mVideoLogger* logger) { callback::func(logger); } - struct Logger { - mVideoLogger d; + struct Logger : public mVideoLogger { VideoProxy* p; - } m_logger = {{}, this}; + } m_logger; + + struct mVideoProxyBackend m_backend; RingFIFO m_dirtyQueue; QMutex m_mutex; QWaitCondition m_toThreadCond; QWaitCondition m_fromThreadCond; + + QReadWriteLock m_backendInLock; + QReadWriteLock m_backendOutLock; + QQueue m_backendIn; + QQueue m_backendOut; + QWaitCondition m_toBackendThreadCond; + QWaitCondition m_fromBackendThreadCond; }; } diff --git a/src/platform/qt/VideoView.cpp b/src/platform/qt/VideoView.cpp index 3b6382275..b03949c2f 100644 --- a/src/platform/qt/VideoView.cpp +++ b/src/platform/qt/VideoView.cpp @@ -20,6 +20,7 @@ using namespace QGBA; QMap VideoView::s_acodecMap; QMap VideoView::s_vcodecMap; QMap VideoView::s_containerMap; +QMap VideoView::s_extensionMap; bool VideoView::Preset::compatible(const Preset& other) const { if (!other.container.isNull() && !container.isNull() && other.container != container) { @@ -71,6 +72,23 @@ VideoView::VideoView(QWidget* parent) if (s_containerMap.empty()) { s_containerMap["mkv"] = "matroska"; } + if (s_extensionMap.empty()) { + s_extensionMap["matroska"] += ".mkv"; + s_extensionMap["matroska"] += ".mka"; + s_extensionMap["webm"] += ".webm"; + s_extensionMap["avi"] += ".avi"; + s_extensionMap["mp4"] += ".mp4"; + s_extensionMap["mp4"] += ".m4v"; + s_extensionMap["mp4"] += ".m4a"; + + s_extensionMap["flac"] += ".flac"; + s_extensionMap["mpeg"] += ".mpg"; + s_extensionMap["mpeg"] += ".mpeg"; + s_extensionMap["mpegts"] += ".ts"; + s_extensionMap["mp3"] += ".mp3"; + s_extensionMap["ogg"] += ".ogg"; + s_extensionMap["ogv"] += ".ogv"; + } connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &VideoView::close); connect(m_ui.start, &QAbstractButton::clicked, this, &VideoView::startRecording); @@ -195,6 +213,9 @@ void VideoView::setController(std::shared_ptr controller) { } void VideoView::startRecording() { + if (QFileInfo(m_filename).suffix().isEmpty()) { + changeExtension(); + } if (!validateSettings()) { return; } @@ -238,6 +259,7 @@ void VideoView::selectFile() { QString filename = GBAApp::app()->getSaveFileName(this, tr("Select output file")); if (!filename.isEmpty()) { m_ui.filename->setText(filename); + changeExtension(); } } @@ -289,6 +311,7 @@ void VideoView::setContainer(const QString& container) { m_containerCstr = nullptr; m_container = QString(); } + changeExtension(); validateSettings(); uncheckIncompatible(); } @@ -458,6 +481,30 @@ void VideoView::uncheckIncompatible() { } } +void VideoView::changeExtension() { + if (m_filename.isEmpty()) { + return; + } + + if (!s_extensionMap.contains(m_container)) { + return; + } + + QStringList extensions = s_extensionMap.value(m_container); + QString filename = m_filename; + int index = m_filename.lastIndexOf("."); + if (index >= 0) { + if (extensions.contains(filename.mid(index))) { + // This extension is already valid + return; + } + filename.truncate(index); + } + filename += extensions.front(); + + m_ui.filename->setText(filename); +} + QString VideoView::sanitizeCodec(const QString& codec, const QMap& mapping) { QString sanitized = codec.toLower(); sanitized = sanitized.remove(QChar('.')); diff --git a/src/platform/qt/VideoView.h b/src/platform/qt/VideoView.h index 7c2bd99e0..23b5ef034 100644 --- a/src/platform/qt/VideoView.h +++ b/src/platform/qt/VideoView.h @@ -7,6 +7,7 @@ #ifdef USE_FFMPEG +#include #include #include @@ -62,6 +63,8 @@ private slots: void uncheckIncompatible(); void updatePresets(); + void changeExtension(); + private: struct Preset { QString container; @@ -123,6 +126,7 @@ private: static QMap s_acodecMap; static QMap s_vcodecMap; static QMap s_containerMap; + static QMap s_extensionMap; }; } diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 6003b88a3..c0a4231fe 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -92,7 +92,7 @@ Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWi , m_logView(new LogView(&m_log, this)) , m_screenWidget(new WindowBackground()) , m_config(config) - , m_inputController(playerId, this) + , m_inputController(this) , m_shortcutController(new ShortcutController(this)) , m_playerId(playerId) { @@ -206,15 +206,14 @@ void Window::argumentsPassed() { } #ifdef USE_GDB_STUB - if (args->debuggerType == DEBUGGER_GDB) { - if (!m_gdbController) { - m_gdbController = new GDBController(this); - if (m_controller) { - m_gdbController->setController(m_controller); - } - m_gdbController->attach(); - m_gdbController->listen(); - } + if (args->debugGdb) { + gdbOpen(); + } +#endif + +#ifdef USE_DEBUGGERS + if (args->debugCli) { + consoleOpen(); } #endif @@ -258,12 +257,7 @@ void Window::resizeFrame(const QSize& size) { void Window::updateMultiplayerStatus(bool canOpenAnother) { m_multiWindow->setEnabled(canOpenAnother); - if (m_controller) { - MultiplayerController* multiplayer = m_controller->multiplayerController(); - if (multiplayer) { - m_playerId = multiplayer->playerId(m_controller.get()); - } - } + multiplayerChanged(); } void Window::updateMultiplayerActive(bool active) { @@ -413,6 +407,7 @@ void Window::multiplayerChanged() { MultiplayerController* multiplayer = m_controller->multiplayerController(); if (multiplayer) { attached = multiplayer->attached(); + m_playerId = multiplayer->playerId(m_controller.get()); } for (Action* action : m_nonMpActions) { action->setEnabled(attached < 2); @@ -633,6 +628,8 @@ void Window::scriptingOpen() { m_scripting->setController(m_controller); m_display->installEventFilter(m_scripting.get()); } + + m_scripting->setVideoBackend(m_display->videoBackend()); } ScriptingView* view = new ScriptingView(m_scripting.get(), m_config); openView(view); @@ -965,6 +962,12 @@ void Window::gameStopped() { updateTitle(); if (m_pendingClose) { +#ifdef ENABLE_SCRIPTING + std::shared_ptr proxy = m_display->videoProxy(); + if (m_scripting && proxy) { + m_scripting->setVideoBackend(nullptr); + } +#endif m_display.reset(); close(); } @@ -1015,6 +1018,15 @@ void Window::reloadDisplayDriver() { m_display->stopDrawing(); detachWidget(); } +#ifdef ENABLE_SCRIPTING + if (m_scripting) { + m_scripting->setVideoBackend(nullptr); + } +#endif + std::shared_ptr proxy; + if (m_display) { + proxy = m_display->videoProxy(); + } m_display = std::unique_ptr(Display::create(this)); if (!m_display) { LOG(QT, ERROR) << tr("Failed to create an appropriate display device, falling back to software display. " @@ -1053,6 +1065,18 @@ void Window::reloadDisplayDriver() { #elif defined(M_CORE_GBA) m_display->setMinimumSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); #endif + + m_display->setBackgroundImage(QImage{m_config->getOption("backgroundImage")}); + + if (!proxy) { + proxy = std::make_shared(); + } + m_display->setVideoProxy(proxy); +#ifdef ENABLE_SCRIPTING + if (m_scripting) { + m_scripting->setVideoBackend(m_display->videoBackend()); + } +#endif } void Window::reloadAudioDriver() { @@ -1077,12 +1101,7 @@ void Window::changeRenderer() { CoreController::Interrupter interrupter(m_controller); if (m_config->getOption("hwaccelVideo").toInt() && m_display->supportsShaders() && m_controller->supportsFeature(CoreController::Feature::OPENGL)) { - std::shared_ptr proxy = m_display->videoProxy(); - if (!proxy) { - proxy = std::make_shared(); - } - m_display->setVideoProxy(proxy); - proxy->attach(m_controller.get()); + m_display->videoProxy()->attach(m_controller.get()); int fb = m_display->framebufferHandle(); if (fb >= 0) { @@ -1090,11 +1109,7 @@ void Window::changeRenderer() { m_config->updateOption("videoScale"); } } else { - std::shared_ptr proxy = m_display->videoProxy(); - if (proxy) { - proxy->detach(m_controller.get()); - m_display->setVideoProxy({}); - } + m_display->videoProxy()->detach(m_controller.get()); m_controller->setFramebufferHandle(-1); } } @@ -1138,14 +1153,14 @@ void Window::recordFrame() { } void Window::showFPS() { - if (m_frameList.isEmpty()) { - updateTitle(); - return; - } qint64 total = 0; for (qint64 t : m_frameList) { total += t; } + if (!total) { + updateTitle(); + return; + } double fps = (m_frameList.size() * 1e10) / total; m_frameList.clear(); fps = round(fps) / 10.f; @@ -1427,6 +1442,20 @@ void Window::setupMenu(QMenuBar* menubar) { } m_config->updateOption("fastForwardRatio"); + addGameAction(tr("Increase fast forward speed"), "fastForwardUp", [this] { + float newRatio = m_config->getOption("fastForwardRatio", 1.0f).toFloat() + 1.0f; + if (newRatio >= 3.0f) { + m_config->setOption("fastForwardRatio", QVariant(newRatio)); + } + }, "emu"); + + addGameAction(tr("Decrease fast forward speed"), "fastForwardDown", [this] { + float newRatio = m_config->getOption("fastForwardRatio").toFloat() - 1.0f; + if (newRatio >= 2.0f) { + m_config->setOption("fastForwardRatio", QVariant(newRatio)); + } + }, "emu"); + Action* rewindHeld = m_actions.addHeldAction(tr("Rewind (held)"), "holdRewind", [this](bool held) { if (m_controller) { m_controller->setRewinding(held); @@ -1486,8 +1515,8 @@ void Window::setupMenu(QMenuBar* menubar) { Action* setSize = m_frameSizes[i]; showNormal(); QSize size(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); - if (m_controller) { - size = m_controller->screenDimensions(); + if (m_display) { + size = m_display->contentSize(); } size *= i; m_savedScale = i; @@ -1564,7 +1593,8 @@ void Window::setupMenu(QMenuBar* menubar) { m_actions.addSeparator("av"); ConfigOption* mute = m_config->addOption("mute"); - mute->addBoolean(tr("Mute"), &m_actions, "av"); + Action* muteAction = mute->addBoolean(tr("Mute"), &m_actions, "av"); + muteAction->setActive(m_config->getOption("mute").toInt()); mute->connect([this](const QVariant& value) { m_config->setOption("fastForwardMute", static_cast(value.toInt())); reloadConfig(); @@ -1810,6 +1840,11 @@ void Window::setupOptions() { reloadConfig(); }, this); + ConfigOption* rewindBufferInterval = m_config->addOption("rewindBufferInterval"); + rewindBufferInterval->connect([this](const QVariant&) { + reloadConfig(); + }, this); + ConfigOption* allowOpposingDirections = m_config->addOption("allowOpposingDirections"); allowOpposingDirections->connect([this](const QVariant&) { reloadConfig(); @@ -1867,6 +1902,11 @@ void Window::setupOptions() { videoScale->connect([this](const QVariant& value) { if (m_display) { m_display->setVideoScale(value.toInt()); +#ifdef ENABLE_SCRIPTING + if (m_controller && m_scripting) { + m_scripting->updateVideoScale(); + } +#endif } }, this); @@ -1875,6 +1915,13 @@ void Window::setupOptions() { updateTitle(); }, this); + ConfigOption* backgroundImage = m_config->addOption("backgroundImage"); + backgroundImage->connect([this](const QVariant& value) { + if (m_display) { + m_display->setBackgroundImage(QImage{value.toString()}); + } + }, this); + m_config->updateOption("backgroundImage"); } void Window::attachWidget(QWidget* widget) { @@ -2103,6 +2150,8 @@ void Window::setController(CoreController* controller, const QString& fname) { #ifdef ENABLE_SCRIPTING if (m_scripting) { m_scripting->setController(m_controller); + + m_scripting->setVideoBackend(m_display->videoBackend()); } #endif diff --git a/src/platform/qt/main.cpp b/src/platform/qt/main.cpp index c53408e8e..4343168a7 100644 --- a/src/platform/qt/main.cpp +++ b/src/platform/qt/main.cpp @@ -28,6 +28,7 @@ #ifdef QT_STATIC #include #ifdef Q_OS_WIN +Q_IMPORT_PLUGIN(QJpegPlugin); Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); Q_IMPORT_PLUGIN(QWindowsVistaStylePlugin); #ifdef BUILD_QT_MULTIMEDIA diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index d8babf06d..7ab7e2f49 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -19,7 +19,7 @@ #include "scripting/ScriptingTextBuffer.h" #include "scripting/ScriptingTextBufferModel.h" -#include +#include #include #include @@ -51,6 +51,9 @@ ScriptingController::ScriptingController(QObject* parent) m_bufferModel = new ScriptingTextBufferModel(this); QObject::connect(m_bufferModel, &ScriptingTextBufferModel::textBufferCreated, this, &ScriptingController::textBufferCreated); + connect(&m_storageFlush, &QTimer::timeout, this, &ScriptingController::flushStorage); + m_storageFlush.setInterval(5); + mScriptGamepadInit(&m_gamepad); init(); @@ -67,12 +70,16 @@ void ScriptingController::setController(std::shared_ptr controll return; } clearController(); + if (!controller) { + return; + } m_controller = controller; CoreController::Interrupter interrupter(m_controller); m_controller->thread()->scriptContext = &m_scriptContext; if (m_controller->hasStarted()) { mScriptContextAttachCore(&m_scriptContext, m_controller->thread()->core); } + updateVideoScale(); connect(m_controller.get(), &CoreController::stopping, this, &ScriptingController::clearController); } @@ -84,6 +91,10 @@ void ScriptingController::setInputController(InputController* input) { connect(m_inputController, &InputController::updated, this, &ScriptingController::updateGamepad); } +void ScriptingController::setVideoBackend(VideoBackend* backend) { + mScriptCanvasUpdateBackend(&m_scriptContext, backend); +} + bool ScriptingController::loadFile(const QString& path) { VFileDevice vf(path, QIODevice::ReadOnly); if (!vf.isOpen()) { @@ -107,9 +118,11 @@ bool ScriptingController::load(VFileDevice& vf, const QString& name) { emit error(QString::fromUtf8(m_activeEngine->getError(m_activeEngine))); ok = false; } - if (m_controller && m_controller->isPaused()) { + if (m_controller) { m_controller->setSync(true); - m_controller->paused(); + if (m_controller->isPaused()) { + m_controller->paused(); + } } return ok; } @@ -126,6 +139,13 @@ void ScriptingController::clearController() { m_controller.reset(); } +void ScriptingController::updateVideoScale() { + if (!m_controller) { + return; + } + mScriptCanvasSetInternalScale(&m_scriptContext, m_controller->videoScale()); +} + void ScriptingController::reset() { CoreController::Interrupter interrupter(m_controller); m_bufferModel->reset(); @@ -144,6 +164,12 @@ void ScriptingController::runCode(const QString& code) { load(vf, "*prompt"); } +void ScriptingController::flushStorage() { +#ifdef USE_JSON_C + mScriptStorageFlushAll(&m_scriptContext); +#endif +} + bool ScriptingController::eventFilter(QObject* obj, QEvent* ev) { event(obj, ev); return false; @@ -154,7 +180,13 @@ void ScriptingController::event(QObject* obj, QEvent* event) { return; } + CoreController::Interrupter interrupter(m_controller); + switch (event->type()) { + case QEvent::FocusOut: + case QEvent::WindowDeactivate: + mScriptContextClearKeys(&m_scriptContext); + return; case QEvent::KeyPress: case QEvent::KeyRelease: { struct mScriptKeyEvent ev{mSCRIPT_EV_TYPE_KEY}; @@ -289,8 +321,13 @@ void ScriptingController::detachGamepad() { void ScriptingController::init() { mScriptContextInit(&m_scriptContext); mScriptContextAttachStdlib(&m_scriptContext); - mScriptContextAttachSocket(&m_scriptContext); + mScriptContextAttachCanvas(&m_scriptContext); + mScriptContextAttachImage(&m_scriptContext); mScriptContextAttachInput(&m_scriptContext); + mScriptContextAttachSocket(&m_scriptContext); +#ifdef USE_JSON_C + mScriptContextAttachStorage(&m_scriptContext); +#endif mScriptContextRegisterEngines(&m_scriptContext); mScriptContextAttachLogger(&m_scriptContext, &m_logger); @@ -304,6 +341,10 @@ void ScriptingController::init() { if (m_engines.count() == 1) { m_activeEngine = *m_engines.begin(); } + +#ifdef USE_JSON_C + m_storageFlush.start(); +#endif } uint32_t ScriptingController::qtToScriptingKey(const QKeyEvent* event) { diff --git a/src/platform/qt/scripting/ScriptingController.h b/src/platform/qt/scripting/ScriptingController.h index 0e275561d..f2e698d76 100644 --- a/src/platform/qt/scripting/ScriptingController.h +++ b/src/platform/qt/scripting/ScriptingController.h @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -19,6 +20,8 @@ class QKeyEvent; class QTextDocument; +struct VideoBackend; + namespace QGBA { class CoreController; @@ -35,6 +38,7 @@ public: void setController(std::shared_ptr controller); void setInputController(InputController* controller); + void setVideoBackend(VideoBackend* backend); bool loadFile(const QString& path); bool load(VFileDevice& vf, const QString& name); @@ -52,9 +56,12 @@ signals: public slots: void clearController(); + void updateVideoScale(); void reset(); void runCode(const QString& code); + void flushStorage(); + protected: bool eventFilter(QObject*, QEvent*) override; @@ -84,6 +91,8 @@ private: std::shared_ptr m_controller; InputController* m_inputController = nullptr; + + QTimer m_storageFlush; }; } diff --git a/src/platform/qt/scripting/ScriptingTextBuffer.cpp b/src/platform/qt/scripting/ScriptingTextBuffer.cpp index 7a4785d01..fe85eda0e 100644 --- a/src/platform/qt/scripting/ScriptingTextBuffer.cpp +++ b/src/platform/qt/scripting/ScriptingTextBuffer.cpp @@ -6,6 +6,7 @@ #include "ScriptingTextBuffer.h" #include "GBAApp.h" +#include "utils.h" #include #include @@ -205,13 +206,8 @@ void ScriptingTextBuffer::setSize(struct mScriptTextBuffer* buffer, uint32_t col void ScriptingTextBuffer::moveCursor(struct mScriptTextBuffer* buffer, uint32_t x, uint32_t y) { ScriptingTextBuffer* self = static_cast(buffer)->p; - if (x > INT_MAX) { - x = INT_MAX; - } - if (y > INT_MAX) { - y = INT_MAX; - } - QMetaObject::invokeMethod(self, "moveCursor", Q_ARG(QPoint, QPoint(x, y))); + QPoint point(saturateCast(x), saturateCast(y)); + QMetaObject::invokeMethod(self, "moveCursor", Q_ARG(QPoint, point)); } void ScriptingTextBuffer::advance(struct mScriptTextBuffer* buffer, int32_t adv) { diff --git a/src/platform/qt/ts/mgba-pt_BR.ts b/src/platform/qt/ts/mgba-pt_BR.ts index 83dcb1bc6..64863e821 100644 --- a/src/platform/qt/ts/mgba-pt_BR.ts +++ b/src/platform/qt/ts/mgba-pt_BR.ts @@ -56,7 +56,7 @@ O Game Boy Advance é uma marca registrada da Nintendo Co., Ltd. {projectName} is an open-source Game Boy Advance emulator - O {projectName} é um emulador de Game Boy Advance de código fonte aberto + O {projectName} é um emulador de Game Boy Advance de código-fonte aberto diff --git a/src/platform/qt/utils.cpp b/src/platform/qt/utils.cpp index d6da29781..7a9cd3bd7 100644 --- a/src/platform/qt/utils.cpp +++ b/src/platform/qt/utils.cpp @@ -6,6 +6,7 @@ #include "utils.h" #include +#include #include #include "VFileDevice.h" @@ -129,4 +130,27 @@ bool extractMatchingFile(VDir* dir, std::function filter) return false; } +QString keyName(int key) { + switch (key) { +#ifndef Q_OS_MAC + case Qt::Key_Shift: + return QCoreApplication::translate("QShortcut", "Shift"); + case Qt::Key_Control: + return QCoreApplication::translate("QShortcut", "Control"); + case Qt::Key_Alt: + return QCoreApplication::translate("QShortcut", "Alt"); + case Qt::Key_Meta: + return QCoreApplication::translate("QShortcut", "Meta"); +#endif + case Qt::Key_Super_L: + return QObject::tr("Super (L)"); + case Qt::Key_Super_R: + return QObject::tr("Super (R)"); + case Qt::Key_Menu: + return QObject::tr("Menu"); + default: + return QKeySequence(key).toString(QKeySequence::NativeText); + } +} + } diff --git a/src/platform/qt/utils.h b/src/platform/qt/utils.h index 72ce74c30..7f317f499 100644 --- a/src/platform/qt/utils.h +++ b/src/platform/qt/utils.h @@ -72,7 +72,49 @@ constexpr const T& clamp(const T& v, const T& lo, const T& hi) { } #endif +template +constexpr T saturateCast(U value) { + if (std::numeric_limits::is_signed == std::numeric_limits::is_signed) { + if (value > std::numeric_limits::max()) { + return std::numeric_limits::max(); + } + if (value < std::numeric_limits::min()) { + return std::numeric_limits::min(); + } + } else if (std::numeric_limits::is_signed) { + if (value > static_cast(std::numeric_limits::max())) { + std::numeric_limits::max(); + } + } else { + if (value < 0) { + return 0; + } + if (static_cast(value) > std::numeric_limits::max()) { + std::numeric_limits::max(); + } + } + return static_cast(value); +} + +template<> +constexpr unsigned saturateCast(int value) { + if (value < 0) { + return 0; + } + return static_cast(value); +} + +template<> +constexpr int saturateCast(unsigned value) { + if (value > static_cast(std::numeric_limits::max())) { + return std::numeric_limits::max(); + } + return static_cast(value); +} + QString romFilters(bool includeMvl = false); bool extractMatchingFile(VDir* dir, std::function filter); +QString keyName(int key); + } diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c index f0073a904..076429f6b 100644 --- a/src/platform/sdl/gl-common.c +++ b/src/platform/sdl/gl-common.c @@ -5,10 +5,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "main.h" +#include +#include #include +#ifdef USE_PNG +#include +#include +#endif + void mSDLGLDoViewport(int w, int h, struct VideoBackend* v) { - v->resized(v, w, h); + v->contextResized(v, w, h); v->clear(v); v->swap(v); v->clear(v); @@ -24,6 +31,60 @@ void mSDLGLCommonSwap(struct VideoBackend* context) { #endif } +bool mSDLGLCommonLoadBackground(struct VideoBackend* context) { +#ifdef USE_PNG + struct mSDLRenderer* renderer = context->user; + const char* bgImage = mCoreConfigGetValue(&renderer->core->config, "backgroundImage"); + if (!bgImage) { + return false; + } + struct VFile* vf = VFileOpen(bgImage, O_RDONLY); + if (!vf) { + return false; + } + + bool ok = false; + png_structp png = PNGReadOpen(vf, 0); + png_infop info = png_create_info_struct(png); + png_infop end = png_create_info_struct(png); + if (!png || !info || !end) { + goto done; + } + + if (!PNGReadHeader(png, info)) { + goto done; + } + unsigned width = png_get_image_width(png, info); + unsigned height = png_get_image_height(png, info); + uint32_t* pixels = malloc(width * height * 4); + if (!pixels) { + goto done; + } + + if (!PNGReadPixels(png, info, pixels, width, height, width) || !PNGReadFooter(png, end)) { + free(pixels); + goto done; + } + + struct mRectangle dims = { + .width = width, + .height = height + }; + context->setLayerDimensions(context, VIDEO_LAYER_BACKGROUND, &dims); + context->setImage(context, VIDEO_LAYER_BACKGROUND, pixels); + free(pixels); + ok = true; + +done: + PNGReadClose(png, info, end); + vf->close(vf); + return ok; +#else + UNUSED(context); + return false; +#endif +} + bool mSDLGLCommonInit(struct mSDLRenderer* renderer) { #ifndef COLOR_16_BIT SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); @@ -66,3 +127,61 @@ bool mSDLGLCommonInit(struct mSDLRenderer* renderer) { #endif return true; } + +void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user) { + struct mCoreThread* context = user; + SDL_Event event; + struct VideoBackend* v = renderer->backend; + + if (mSDLGLCommonLoadBackground(v)) { + renderer->player.windowUpdated = true; + + struct mRectangle frame; + v->layerDimensions(v, VIDEO_LAYER_IMAGE, &frame); + int i; + for (i = 0; i < VIDEO_LAYER_IMAGE; ++i) { + struct mRectangle dims; + v->layerDimensions(v, i, &dims); + mRectangleCenter(&frame, &dims); + v->setLayerDimensions(v, i, &dims); + } + +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_SetWindowSize(renderer->window, frame.width * renderer->ratio, frame.height * renderer->ratio); +#endif + } + + while (mCoreThreadIsActive(context)) { + while (SDL_PollEvent(&event)) { + mSDLHandleEvent(context, &renderer->player, &event); + // Event handling can change the size of the screen + if (renderer->player.windowUpdated) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); +#else + renderer->viewportWidth = renderer->player.newWidth; + renderer->viewportHeight = renderer->player.newHeight; + mSDLGLCommonInit(renderer); +#endif + mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, v); + renderer->player.windowUpdated = 0; + } + } + renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height); + struct mRectangle dims; + v->layerDimensions(v, VIDEO_LAYER_IMAGE, &dims); + if (renderer->width != dims.width || renderer->height != dims.height) { + renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, renderer->width); + dims.width = renderer->width; + dims.height = renderer->height; + v->setLayerDimensions(v, VIDEO_LAYER_IMAGE, &dims); + } + + if (mCoreSyncWaitFrameStart(&context->impl->sync)) { + v->setImage(v, VIDEO_LAYER_IMAGE, renderer->outputBuffer); + } + mCoreSyncWaitFrameEnd(&context->impl->sync); + v->drawFrame(v); + v->swap(v); + } +} diff --git a/src/platform/sdl/gl-common.h b/src/platform/sdl/gl-common.h index be98d7964..f52cfd633 100644 --- a/src/platform/sdl/gl-common.h +++ b/src/platform/sdl/gl-common.h @@ -15,6 +15,8 @@ struct mSDLRenderer; void mSDLGLDoViewport(int w, int h, struct VideoBackend* v); void mSDLGLCommonSwap(struct VideoBackend* context); bool mSDLGLCommonInit(struct mSDLRenderer* renderer); +void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user); +bool mSDLGLCommonLoadBackground(struct VideoBackend* context); CXX_GUARD_END diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c index 6c1508d33..139d5a36e 100644 --- a/src/platform/sdl/gl-sdl.c +++ b/src/platform/sdl/gl-sdl.c @@ -8,19 +8,17 @@ #include "gl-common.h" #include -#include -#include #include "platform/opengl/gl.h" static bool mSDLGLInit(struct mSDLRenderer* renderer); -static void mSDLGLRunloop(struct mSDLRenderer* renderer, void* user); static void mSDLGLDeinit(struct mSDLRenderer* renderer); void mSDLGLCreate(struct mSDLRenderer* renderer) { renderer->init = mSDLGLInit; renderer->deinit = mSDLGLDeinit; - renderer->runloop = mSDLGLRunloop; + renderer->runloop = mSDLGLCommonRunloop; + renderer->backend = &renderer->gl.d; } bool mSDLGLInit(struct mSDLRenderer* renderer) { @@ -37,48 +35,18 @@ bool mSDLGLInit(struct mSDLRenderer* renderer) { renderer->gl.d.filter = renderer->filter; renderer->gl.d.swap = mSDLGLCommonSwap; renderer->gl.d.init(&renderer->gl.d, 0); - renderer->gl.d.setDimensions(&renderer->gl.d, renderer->width, renderer->height); + struct mRectangle dims = { + .x = 0, + .y = 0, + .width = renderer->width, + .height = renderer->height + }; + renderer->gl.d.setLayerDimensions(&renderer->gl.d, VIDEO_LAYER_IMAGE, &dims); mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, &renderer->gl.d); return true; } -void mSDLGLRunloop(struct mSDLRenderer* renderer, void* user) { - struct mCoreThread* context = user; - SDL_Event event; - struct VideoBackend* v = &renderer->gl.d; - - while (mCoreThreadIsActive(context)) { - while (SDL_PollEvent(&event)) { - mSDLHandleEvent(context, &renderer->player, &event); - // Event handling can change the size of the screen - if (renderer->player.windowUpdated) { -#if SDL_VERSION_ATLEAST(2, 0, 0) - SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); -#else - renderer->viewportWidth = renderer->player.newWidth; - renderer->viewportHeight = renderer->player.newHeight; - mSDLGLCommonInit(renderer); -#endif - mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, v); - renderer->player.windowUpdated = 0; - } - } - renderer->core->desiredVideoDimensions(renderer->core, &renderer->width, &renderer->height); - if (renderer->width != v->width || renderer->height != v->height) { - renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, renderer->width); - v->setDimensions(v, renderer->width, renderer->height); - } - - if (mCoreSyncWaitFrameStart(&context->impl->sync)) { - v->postFrame(v, renderer->outputBuffer); - } - mCoreSyncWaitFrameEnd(&context->impl->sync); - v->drawFrame(v); - v->swap(v); - } -} - void mSDLGLDeinit(struct mSDLRenderer* renderer) { if (renderer->gl.d.deinit) { renderer->gl.d.deinit(&renderer->gl.d); diff --git a/src/platform/sdl/gles2-sdl.c b/src/platform/sdl/gles2-sdl.c index 7237b6273..9bae33b95 100644 --- a/src/platform/sdl/gles2-sdl.c +++ b/src/platform/sdl/gles2-sdl.c @@ -11,20 +11,19 @@ #endif #include -#include #ifdef __linux__ #include #endif static bool mSDLGLES2Init(struct mSDLRenderer* renderer); -static void mSDLGLES2Runloop(struct mSDLRenderer* renderer, void* user); static void mSDLGLES2Deinit(struct mSDLRenderer* renderer); void mSDLGLES2Create(struct mSDLRenderer* renderer) { renderer->init = mSDLGLES2Init; renderer->deinit = mSDLGLES2Deinit; - renderer->runloop = mSDLGLES2Runloop; + renderer->runloop = mSDLGLCommonRunloop; + renderer->backend = &renderer->gl2.d; } bool mSDLGLES2Init(struct mSDLRenderer* renderer) { @@ -50,43 +49,19 @@ bool mSDLGLES2Init(struct mSDLRenderer* renderer) { renderer->gl2.d.swap = mSDLGLCommonSwap; #endif renderer->gl2.d.init(&renderer->gl2.d, 0); - renderer->gl2.d.setDimensions(&renderer->gl2.d, renderer->width, renderer->height); + + struct mRectangle dims = { + .x = 0, + .y = 0, + .width = renderer->width, + .height = renderer->height + }; + renderer->gl2.d.setLayerDimensions(&renderer->gl2.d, VIDEO_LAYER_IMAGE, &dims); mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, &renderer->gl2.d); return true; } -void mSDLGLES2Runloop(struct mSDLRenderer* renderer, void* user) { - struct mCoreThread* context = user; - SDL_Event event; - struct VideoBackend* v = &renderer->gl2.d; - - while (mCoreThreadIsActive(context)) { - while (SDL_PollEvent(&event)) { - mSDLHandleEvent(context, &renderer->player, &event); - // Event handling can change the size of the screen - if (renderer->player.windowUpdated) { -#if SDL_VERSION_ATLEAST(2, 0, 0) - SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); -#else - renderer->viewportWidth = renderer->player.newWidth; - renderer->viewportHeight = renderer->player.newHeight; - mSDLGLCommonInit(renderer); -#endif - mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, v); - renderer->player.windowUpdated = 0; - } - } - - if (mCoreSyncWaitFrameStart(&context->impl->sync)) { - v->postFrame(v, renderer->outputBuffer); - } - mCoreSyncWaitFrameEnd(&context->impl->sync); - v->drawFrame(v); - v->swap(v); - } -} - void mSDLGLES2Deinit(struct mSDLRenderer* renderer) { if (renderer->gl2.d.deinit) { renderer->gl2.d.deinit(&renderer->gl2.d); diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index 96a797986..2d9a74ae8 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -61,6 +61,7 @@ int main(int argc, char** argv) { .useBios = true, .rewindEnable = true, .rewindBufferCapacity = 600, + .rewindBufferInterval = 1, .audioBuffers = 1024, .videoSync = false, .audioSync = true, @@ -107,7 +108,7 @@ int main(int argc, char** argv) { return 1; } - renderer.core->desiredVideoDimensions(renderer.core, &renderer.width, &renderer.height); + renderer.core->baseVideoSize(renderer.core, &renderer.width, &renderer.height); renderer.ratio = graphicsOpts.multiplier; if (renderer.ratio == 0) { renderer.ratio = 1; @@ -242,18 +243,37 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { #endif #ifdef USE_DEBUGGERS - struct mDebugger* debugger = mDebuggerCreate(args->debuggerType, renderer->core); - if (debugger) { + struct mDebugger debugger; + bool hasDebugger = false; + + mDebuggerInit(&debugger); #ifdef USE_EDITLINE - if (args->debuggerType == DEBUGGER_CLI) { - struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; + if (args->debugCli) { + struct mDebuggerModule* module = mDebuggerCreateModule(DEBUGGER_CLI, renderer->core); + if (module) { + struct CLIDebugger* cliDebugger = (struct CLIDebugger*) module; CLIDebuggerAttachBackend(cliDebugger, CLIDebuggerEditLineBackendCreate()); + mDebuggerAttachModule(&debugger, module); + hasDebugger = true; } + } #endif - mDebuggerAttach(debugger, renderer->core); - mDebuggerEnter(debugger, DEBUGGER_ENTER_MANUAL, NULL); + +#ifdef USE_GDB_STUB + if (args->debugGdb) { + struct mDebuggerModule* module = mDebuggerCreateModule(DEBUGGER_GDB, renderer->core); + if (module) { + mDebuggerAttachModule(&debugger, module); + hasDebugger = true; + } + } +#endif + + if (hasDebugger) { + mDebuggerAttach(&debugger, renderer->core); + mDebuggerEnter(&debugger, DEBUGGER_ENTER_MANUAL, NULL); #ifdef ENABLE_SCRIPTING - mScriptBridgeSetDebugger(bridge, debugger); + mScriptBridgeSetDebugger(bridge, &debugger); #endif } #endif @@ -275,7 +295,7 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { if (!didFail) { #if SDL_VERSION_ATLEAST(2, 0, 0) - renderer->core->desiredVideoDimensions(renderer->core, &renderer->width, &renderer->height); + renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height); unsigned width = renderer->width * renderer->ratio; unsigned height = renderer->height * renderer->ratio; if (width != (unsigned) renderer->viewportWidth && height != (unsigned) renderer->viewportHeight) { @@ -321,6 +341,13 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { mScriptBridgeDestroy(bridge); #endif +#ifdef USE_DEBUGGERS + if (hasDebugger) { + renderer->core->detachDebugger(renderer->core); + mDebuggerDeinit(&debugger); + } +#endif + return didFail; } diff --git a/src/platform/sdl/main.h b/src/platform/sdl/main.h index c97e50e33..10135da2f 100644 --- a/src/platform/sdl/main.h +++ b/src/platform/sdl/main.h @@ -76,6 +76,8 @@ struct mSDLRenderer { struct mGLES2Context gl2; #endif + struct VideoBackend* backend; + #ifdef USE_PIXMAN pixman_image_t* pix; pixman_image_t* screenpix; diff --git a/src/platform/sdl/sw-sdl1.c b/src/platform/sdl/sw-sdl1.c index bfe5d2037..cf917fd0a 100644 --- a/src/platform/sdl/sw-sdl1.c +++ b/src/platform/sdl/sw-sdl1.c @@ -28,7 +28,7 @@ bool mSDLSWInit(struct mSDLRenderer* renderer) { SDL_WM_SetCaption(projectName, ""); unsigned width, height; - renderer->core->desiredVideoDimensions(renderer->core, &width, &height); + renderer->core->baseVideoSize(renderer->core, &width, &height); SDL_Surface* surface = SDL_GetVideoSurface(); SDL_LockSurface(surface); diff --git a/src/platform/sdl/sw-sdl2.c b/src/platform/sdl/sw-sdl2.c index 48afc33a6..75ad1c2ea 100644 --- a/src/platform/sdl/sw-sdl2.c +++ b/src/platform/sdl/sw-sdl2.c @@ -21,7 +21,7 @@ void mSDLSWCreate(struct mSDLRenderer* renderer) { bool mSDLSWInit(struct mSDLRenderer* renderer) { unsigned width, height; - renderer->core->desiredVideoDimensions(renderer->core, &width, &height); + renderer->core->baseVideoSize(renderer->core, &width, &height); renderer->window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen)); SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); renderer->player.window = renderer->window; diff --git a/src/platform/switch/gui-font.c b/src/platform/switch/gui-font.c index e164a5e64..16add0128 100644 --- a/src/platform/switch/gui-font.c +++ b/src/platform/switch/gui-font.c @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include -#include +#include #include #include diff --git a/src/platform/switch/main.c b/src/platform/switch/main.c index 14996d5b0..3e891ab3d 100644 --- a/src/platform/switch/main.c +++ b/src/platform/switch/main.c @@ -477,7 +477,7 @@ static void _drawFrame(struct mGUIRunner* runner, bool faded) { } unsigned width, height; - runner->core->desiredVideoDimensions(runner->core, &width, &height); + runner->core->currentVideoSize(runner->core, &width, &height); glActiveTexture(GL_TEXTURE0); if (usePbo) { @@ -530,7 +530,7 @@ static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, un glBindTexture(GL_TEXTURE_2D, screenshotTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - runner->core->desiredVideoDimensions(runner->core, &width, &height); + runner->core->currentVideoSize(runner->core, &width, &height); glDisable(GL_BLEND); bool wasPbo = usePbo; usePbo = false; diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index 28bb07693..01d864bd8 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include #include @@ -731,8 +731,8 @@ static struct VDir* _makeOutDir(const char* testName) { static void _writeImage(struct VFile* vf, const struct CInemaImage* image) { png_structp png = PNGWriteOpen(vf); - png_infop info = PNGWriteHeader(png, image->width, image->height); - if (!PNGWritePixels(png, image->width, image->height, image->stride, image->data)) { + png_infop info = PNGWriteHeader(png, image->width, image->height, mCOLOR_NATIVE); + if (!PNGWritePixels(png, image->width, image->height, image->stride, image->data, mCOLOR_NATIVE)) { CIerr(0, "Could not write output image\n"); } PNGWriteClose(png, info); @@ -1032,7 +1032,7 @@ void CInemaTestRun(struct CInemaTest* test) { return; } struct CInemaImage image; - core->desiredVideoDimensions(core, &image.width, &image.height); + core->baseVideoSize(core, &image.width, &image.height); ssize_t bufferSize = image.width * image.height * BYTES_PER_PIXEL; image.data = malloc(bufferSize); image.stride = image.width; @@ -1075,7 +1075,7 @@ void CInemaTestRun(struct CInemaTest* test) { for (frame = 0; frame < skip; ++frame) { core->runFrame(core); } - core->desiredVideoDimensions(core, &image.width, &image.height); + core->currentVideoSize(core, &image.width, &image.height); #ifdef USE_FFMPEG struct FFmpegDecoder decoder; @@ -1139,7 +1139,7 @@ void CInemaTestRun(struct CInemaTest* test) { break; } CIlog(3, "Test frame: %u\n", frameCounter); - core->desiredVideoDimensions(core, &image.width, &image.height); + core->currentVideoSize(core, &image.width, &image.height); uint8_t* diff = NULL; struct CInemaImage expected = { .data = NULL, @@ -1365,6 +1365,7 @@ static THREAD_ENTRY CInemaJob(void* context) { CIflush(&stream.err, stderr); StringListDeinit(&stream.err.lines); StringListDeinit(&stream.err.partial); + THREAD_EXIT(0); } void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args) { diff --git a/src/platform/video-backend.h b/src/platform/video-backend.h deleted file mode 100644 index 6d714629a..000000000 --- a/src/platform/video-backend.h +++ /dev/null @@ -1,53 +0,0 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef VIDEO_BACKEND_H -#define VIDEO_BACKEND_H - -#include - -CXX_GUARD_START - -#ifdef _WIN32 -#include -typedef HWND WHandle; -#else -typedef void* WHandle; -#endif - -struct VideoBackend { - void (*init)(struct VideoBackend*, WHandle handle); - void (*deinit)(struct VideoBackend*); - void (*setDimensions)(struct VideoBackend*, unsigned width, unsigned height); - void (*swap)(struct VideoBackend*); - void (*clear)(struct VideoBackend*); - void (*resized)(struct VideoBackend*, unsigned w, unsigned h); - void (*postFrame)(struct VideoBackend*, const void* frame); - void (*drawFrame)(struct VideoBackend*); - void (*setMessage)(struct VideoBackend*, const char* message); - void (*clearMessage)(struct VideoBackend*); - - void* user; - unsigned width; - unsigned height; - - bool filter; - bool lockAspectRatio; - bool lockIntegerScaling; - bool interframeBlending; -}; - -struct VideoShader { - const char* name; - const char* author; - const char* description; - void* preprocessShader; - void* passes; - size_t nPasses; -}; - -CXX_GUARD_END - -#endif diff --git a/src/platform/wii/main.c b/src/platform/wii/main.c index cdfe00b2f..db3ed7242 100644 --- a/src/platform/wii/main.c +++ b/src/platform/wii/main.c @@ -1511,7 +1511,7 @@ void _prepareForFrame(struct mGUIRunner* runner) { } void _drawFrame(struct mGUIRunner* runner, bool faded) { - runner->core->desiredVideoDimensions(runner->core, &corew, &coreh); + runner->core->currentVideoSize(runner->core, &corew, &coreh); uint32_t color = 0xFFFFFF3F; if (!faded) { color |= 0xC0; diff --git a/src/platform/windows/vfs-w32.c b/src/platform/windows/vfs-w32.c index bfa2e30a3..b160bc588 100644 --- a/src/platform/windows/vfs-w32.c +++ b/src/platform/windows/vfs-w32.c @@ -151,7 +151,12 @@ bool _vdwDeleteFile(struct VDir* vd, const char* path) { MultiByteToWideChar(CP_UTF8, 0, path, -1, pathw, MAX_PATH); StringCchPrintfW(combined, MAX_PATH, L"%ws\\%ws", dir, pathw); - return DeleteFileW(combined); + DWORD attrs = GetFileAttributesW(combined); + if (attrs & FILE_ATTRIBUTE_DIRECTORY) { + return RemoveDirectoryW(combined); + } else { + return DeleteFileW(combined); + } } const char* _vdweName(struct VDirEntry* vde) { @@ -183,4 +188,4 @@ bool VDirCreate(const char* path) { return true; } return false; -} \ No newline at end of file +} diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index d84659453..0f64514c0 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -1,7 +1,9 @@ include(ExportDirectory) set(SOURCE_FILES + canvas.c context.c input.c + image.c socket.c stdlib.c types.c) @@ -10,13 +12,22 @@ set(TEST_FILES test/classes.c test/types.c) +if(USE_JSON_C) + list(APPEND SOURCE_FILES storage.c) +endif() + if(USE_LUA) list(APPEND SOURCE_FILES engines/lua.c) list(APPEND TEST_FILES test/context.c + test/image.c test/input.c test/lua.c test/stdlib.c) + + if(USE_JSON_C) + list(APPEND TEST_FILES test/storage.c) + endif() endif() source_group("Scripting" FILES ${SOURCE_FILES}) diff --git a/src/script/canvas.c b/src/script/canvas.c new file mode 100644 index 000000000..5e931943d --- /dev/null +++ b/src/script/canvas.c @@ -0,0 +1,294 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +#include +#include +#include + +struct mScriptCanvasContext; +struct mScriptCanvasLayer { + struct VideoBackend* backend; + enum VideoLayer layer; + struct mImage* image; + int x; + int y; + unsigned scale; + bool dirty; + bool sizeDirty; + bool dimsDirty; + bool contentsDirty; +}; + +struct mScriptCanvasContext { + struct mScriptCanvasLayer overlays[VIDEO_LAYER_OVERLAY_COUNT]; + struct VideoBackend* backend; + struct mScriptContext* context; + uint32_t frameCbid; + unsigned scale; +}; + +mSCRIPT_DECLARE_STRUCT(mScriptCanvasContext); +mSCRIPT_DECLARE_STRUCT(mScriptCanvasLayer); + +static void mScriptCanvasLayerDestroy(struct mScriptCanvasLayer* layer); +static void mScriptCanvasLayerUpdate(struct mScriptCanvasLayer* layer); + +static int _getNextAvailableOverlay(struct mScriptCanvasContext* context) { + if (!context->backend) { + return -1; + } + size_t i; + for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) { + int w = -1, h = -1; + context->backend->imageSize(context->backend, VIDEO_LAYER_OVERLAY0 + i, &w, &h); + if (w <= 0 && h <= 0) { + return i; + } + } + return -1; +} + +static void mScriptCanvasContextDeinit(struct mScriptCanvasContext* context) { + size_t i; + for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) { + if (context->overlays[i].image) { + mScriptCanvasLayerDestroy(&context->overlays[i]); + } + } +} + +void mScriptCanvasUpdateBackend(struct mScriptContext* context, struct VideoBackend* backend) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "canvas"); + if (!value) { + return; + } + struct mScriptCanvasContext* canvas = value->value.opaque; + canvas->backend = backend; + size_t i; + for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) { + struct mScriptCanvasLayer* layer = &canvas->overlays[i]; + layer->backend = backend; + layer->dirty = true; + layer->dimsDirty = true; + layer->sizeDirty = true; + layer->contentsDirty = true; + } +} + +void mScriptCanvasSetInternalScale(struct mScriptContext* context, unsigned scale) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "canvas"); + if (!value) { + return; + } + struct mScriptCanvasContext* canvas = value->value.opaque; + if (scale < 1) { + scale = 1; + } + canvas->scale = scale; + size_t i; + for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) { + canvas->overlays[i].scale = scale; + } +} + +static void _mScriptCanvasUpdate(struct mScriptCanvasContext* canvas) { + size_t i; + for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) { + mScriptCanvasLayerUpdate(&canvas->overlays[i]); + } +} + +static unsigned _mScriptCanvasWidth(struct mScriptCanvasContext* canvas) { + if (!canvas->backend) { + return 0; + } + unsigned w, h; + VideoBackendGetFrameSize(canvas->backend, &w, &h); + return w / canvas->scale; +} + +static unsigned _mScriptCanvasHeight(struct mScriptCanvasContext* canvas) { + if (!canvas->backend) { + return 0; + } + unsigned w, h; + VideoBackendGetFrameSize(canvas->backend, &w, &h); + return h / canvas->scale; +} + +static int _mScriptCanvasScreenWidth(struct mScriptCanvasContext* canvas) { + if (!canvas->backend) { + return 0; + } + struct mRectangle dims; + canvas->backend->layerDimensions(canvas->backend, VIDEO_LAYER_IMAGE, &dims); + return dims.width / canvas->scale; +} + +static int _mScriptCanvasScreenHeight(struct mScriptCanvasContext* canvas) { + if (!canvas->backend) { + return 0; + } + struct mRectangle dims; + canvas->backend->layerDimensions(canvas->backend, VIDEO_LAYER_IMAGE, &dims); + return dims.height / canvas->scale; +} + +void mScriptCanvasUpdate(struct mScriptContext* context) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "canvas"); + if (!value) { + return; + } + struct mScriptCanvasContext* canvas = value->value.opaque; + _mScriptCanvasUpdate(canvas); +} + +static struct mScriptValue* mScriptCanvasLayerCreate(struct mScriptCanvasContext* context, int w, int h) { + if (w <= 0 || h <= 0) { + return NULL; + } + int next = _getNextAvailableOverlay(context); + if (next < 0) { + return NULL; + } + + struct mScriptCanvasLayer* layer = &context->overlays[next]; + if (layer->image) { + // This shouldn't exist yet + abort(); + } + + layer->image = mImageCreate(w, h, mCOLOR_ABGR8); + layer->dirty = true; + layer->dimsDirty = true; + layer->sizeDirty = true; + layer->contentsDirty = true; + layer->scale = context->scale; + mScriptCanvasLayerUpdate(layer); + + struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptCanvasLayer)); + value->value.opaque = layer; + return value; +} + +static void mScriptCanvasLayerDestroy(struct mScriptCanvasLayer* layer) { + struct mRectangle frame = {0}; + if (layer->backend) { + layer->backend->setLayerDimensions(layer->backend, layer->layer, &frame); + layer->backend->setImageSize(layer->backend, layer->layer, 0, 0); + } + mImageDestroy(layer->image); + layer->image = NULL; +} + +static void mScriptCanvasLayerUpdate(struct mScriptCanvasLayer* layer) { + if (!layer->dirty || !layer->image || !layer->backend) { + return; + } + + struct VideoBackend* backend = layer->backend; + if (layer->sizeDirty) { + backend->setImageSize(backend, layer->layer, layer->image->width, layer->image->height); + layer->sizeDirty = false; + // Resizing the image invalidates the contents in many backends + layer->contentsDirty = true; + } + if (layer->dimsDirty) { + struct mRectangle frame = { + .x = layer->x * layer->scale, + .y = layer->y * layer->scale, + .width = layer->image->width * layer->scale, + .height = layer->image->height * layer->scale, + }; + backend->setLayerDimensions(backend, layer->layer, &frame); + layer->dimsDirty = false; + } + if (layer->contentsDirty) { + backend->setImage(backend, layer->layer, layer->image->data); + layer->contentsDirty = false; + } + layer->dirty = false; +} + + +static void mScriptCanvasLayerSetPosition(struct mScriptCanvasLayer* layer, int32_t x, int32_t y) { + layer->x = x; + layer->y = y; + layer->dimsDirty = true; + layer->dirty = true; +} + +static void mScriptCanvasLayerInvalidate(struct mScriptCanvasLayer* layer) { + layer->contentsDirty = true; + layer->dirty = true; +} + +void mScriptContextAttachCanvas(struct mScriptContext* context) { + struct mScriptCanvasContext* canvas = calloc(1, sizeof(*canvas)); + canvas->scale = 1; + size_t i; + for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) { + canvas->overlays[i].layer = VIDEO_LAYER_OVERLAY0 + i; + } + struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptCanvasContext)); + value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER; + value->value.opaque = canvas; + + canvas->context = context; + struct mScriptValue* lambda = mScriptObjectBindLambda(value, "update", NULL); + canvas->frameCbid = mScriptContextAddCallback(context, "frame", lambda); + mScriptValueDeref(lambda); + + mScriptContextSetGlobal(context, "canvas", value); + mScriptContextSetDocstring(context, "canvas", "Singleton instance of struct::mScriptCanvasContext"); +} + +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCanvasContext, _deinit, mScriptCanvasContextDeinit, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, W(mScriptCanvasLayer), newLayer, mScriptCanvasLayerCreate, 2, S32, width, S32, height); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCanvasContext, update, _mScriptCanvasUpdate, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, U32, width, _mScriptCanvasWidth, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, U32, height, _mScriptCanvasHeight, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, S32, screenWidth, _mScriptCanvasScreenWidth, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, S32, screenHeight, _mScriptCanvasScreenHeight, 0); + +mSCRIPT_DEFINE_STRUCT(mScriptCanvasContext) + mSCRIPT_DEFINE_CLASS_DOCSTRING( + "A canvas that can be used for drawing images on or around the screen." + ) + mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptCanvasContext) + mSCRIPT_DEFINE_DOCSTRING("Create a new layer of a given size. If multiple layers overlap, the most recently created one takes priority.") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, newLayer) + mSCRIPT_DEFINE_DOCSTRING("Update all layers marked as having pending changes") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, update) + mSCRIPT_DEFINE_DOCSTRING("Get the width of the canvas") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, width) + mSCRIPT_DEFINE_DOCSTRING("Get the height of the canvas") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, height) + mSCRIPT_DEFINE_DOCSTRING("Get the width of the emulated screen") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, screenWidth) + mSCRIPT_DEFINE_DOCSTRING("Get the height of the emulated screen") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, screenHeight) +mSCRIPT_DEFINE_END; + +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCanvasLayer, update, mScriptCanvasLayerInvalidate, 0); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCanvasLayer, setPosition, mScriptCanvasLayerSetPosition, 2, S32, x, S32, y); + +mSCRIPT_DEFINE_STRUCT(mScriptCanvasLayer) + mSCRIPT_DEFINE_CLASS_DOCSTRING( + "An individual layer of a drawable canvas." + ) + mSCRIPT_DEFINE_DOCSTRING("Mark the contents of the layer as needed to be repainted") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasLayer, update) + mSCRIPT_DEFINE_DOCSTRING("Set the position of the layer in the canvas") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasLayer, setPosition) + mSCRIPT_DEFINE_DOCSTRING("The image that has the pixel contents of the image") + mSCRIPT_DEFINE_STRUCT_MEMBER(mScriptCanvasLayer, PS(mImage), image) + mSCRIPT_DEFINE_DOCSTRING("The current x (horizontal) position of this layer") + mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mScriptCanvasLayer, S32, x) + mSCRIPT_DEFINE_DOCSTRING("The current y (vertical) position of this layer") + mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mScriptCanvasLayer, S32, y) +mSCRIPT_DEFINE_END; diff --git a/src/script/context.c b/src/script/context.c index bf5fcfa54..85c3de485 100644 --- a/src/script/context.c +++ b/src/script/context.c @@ -17,8 +17,10 @@ struct mScriptFileInfo { }; struct mScriptCallbackInfo { + struct mScriptValue* fn; const char* callback; - size_t id; + uint32_t id; + bool oneshot; }; static void _engineContextDestroy(void* ctx) { @@ -56,13 +58,28 @@ static void _contextFindForFile(const char* key, void* value, void* user) { } } +static void _freeTable(void* data) { + struct Table* table = data; + + struct TableIterator iter; + if (TableIteratorStart(table, &iter)) { + do { + struct mScriptCallbackInfo* info = TableIteratorGetValue(table, &iter); + mScriptValueDeref(info->fn); + } while (TableIteratorNext(table, &iter)); + } + + TableDeinit(table); + free(table); +} + void mScriptContextInit(struct mScriptContext* context) { HashTableInit(&context->rootScope, 0, (void (*)(void*)) mScriptValueDeref); HashTableInit(&context->engines, 0, _engineContextDestroy); mScriptListInit(&context->refPool, 0); TableInit(&context->weakrefs, 0, (void (*)(void*)) mScriptValueDeref); context->nextWeakref = 1; - HashTableInit(&context->callbacks, 0, (void (*)(void*)) mScriptValueDeref); + HashTableInit(&context->callbacks, 0, _freeTable); TableInit(&context->callbackId, 0, free); context->nextCallbackId = 1; context->constants = NULL; @@ -84,14 +101,6 @@ void mScriptContextFillPool(struct mScriptContext* context, struct mScriptValue* if (value->refs == mSCRIPT_VALUE_UNREF) { return; } - switch (value->type->base) { - case mSCRIPT_TYPE_SINT: - case mSCRIPT_TYPE_UINT: - case mSCRIPT_TYPE_FLOAT: - return; - default: - break; - } struct mScriptValue* poolEntry = mScriptListAppend(&context->refPool); poolEntry->type = mSCRIPT_TYPE_MS_WRAPPER; @@ -212,45 +221,71 @@ void mScriptContextDisownWeakref(struct mScriptContext* context, uint32_t weakre } void mScriptContextTriggerCallback(struct mScriptContext* context, const char* callback, struct mScriptList* args) { - struct mScriptValue* list = HashTableLookup(&context->callbacks, callback); - if (!list) { + struct Table* table = HashTableLookup(&context->callbacks, callback); + if (!table) { return; } - size_t i; - for (i = 0; i < mScriptListSize(list->value.list); ++i) { - struct mScriptFrame frame; - struct mScriptValue* fn = mScriptListGetPointer(list->value.list, i); - if (!fn->type) { - continue; - } - mScriptFrameInit(&frame); - if (args) { - mScriptListCopy(&frame.arguments, args); - } - if (fn->type->base == mSCRIPT_TYPE_WRAPPER) { - fn = mScriptValueUnwrap(fn); - } - mScriptInvoke(fn, &frame); - mScriptFrameDeinit(&frame); + struct TableIterator iter; + if (!TableIteratorStart(table, &iter)) { + return; } + + struct UInt32List oneshots; + UInt32ListInit(&oneshots, 0); + do { + struct mScriptFrame frame; + struct mScriptCallbackInfo* info = TableIteratorGetValue(table, &iter); + struct mScriptValue* fn = mScriptContextAccessWeakref(context, info->fn); + if (fn) { + mScriptFrameInit(&frame); + if (args) { + mScriptListCopy(&frame.arguments, args); + } + mScriptInvoke(fn, &frame); + mScriptFrameDeinit(&frame); + } + + if (info->oneshot) { + *UInt32ListAppend(&oneshots) = info->id; + } + } while (TableIteratorNext(table, &iter)); + + size_t i; + for (i = 0; i < UInt32ListSize(&oneshots); ++i) { + mScriptContextRemoveCallback(context, *UInt32ListGetPointer(&oneshots, i)); + } + UInt32ListDeinit(&oneshots); } -uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) { - if (fn->type->base != mSCRIPT_TYPE_FUNCTION) { +static uint32_t mScriptContextAddCallbackInternal(struct mScriptContext* context, const char* callback, struct mScriptValue* fn, bool oneshot) { + if (fn->type == mSCRIPT_TYPE_MS_WEAKREF) { + struct mScriptValue* weakref = mScriptContextAccessWeakref(context, fn); + if (!weakref) { + return 0; + } + if (weakref->type->base != mSCRIPT_TYPE_FUNCTION) { + return 0; + } + } else if (fn->type->base != mSCRIPT_TYPE_FUNCTION) { return 0; } - struct mScriptValue* list = HashTableLookup(&context->callbacks, callback); - if (!list) { - list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST); - HashTableInsert(&context->callbacks, callback, list); + struct Table* table = HashTableLookup(&context->callbacks, callback); + if (!table) { + table = calloc(1, sizeof(*table)); + TableInit(table, 0, NULL); + HashTableInsert(&context->callbacks, callback, table); } struct mScriptCallbackInfo* info = malloc(sizeof(*info)); // Steal the string from the table key, since it's guaranteed to outlive this struct struct TableIterator iter; HashTableIteratorLookup(&context->callbacks, &iter, callback); info->callback = HashTableIteratorGetKey(&context->callbacks, &iter); - info->id = mScriptListSize(list->value.list); - mScriptValueWrap(fn, mScriptListAppend(list->value.list)); + info->oneshot = oneshot; + if (fn->type->base == mSCRIPT_TYPE_WRAPPER) { + fn = mScriptValueUnwrap(fn); + } + info->fn = fn; + mScriptValueRef(fn); while (true) { uint32_t id = context->nextCallbackId; ++context->nextCallbackId; @@ -258,8 +293,19 @@ uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* c continue; } TableInsert(&context->callbackId, id, info); - return id; + info->id = id; + break; } + TableInsert(table, info->id, info); + return info->id; +} + +uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) { + return mScriptContextAddCallbackInternal(context, callback, fn, false); +} + +uint32_t mScriptContextAddOneshot(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) { + return mScriptContextAddCallbackInternal(context, callback, fn, true); } void mScriptContextRemoveCallback(struct mScriptContext* context, uint32_t cbid) { @@ -267,16 +313,13 @@ void mScriptContextRemoveCallback(struct mScriptContext* context, uint32_t cbid) if (!info) { return; } - struct mScriptValue* list = HashTableLookup(&context->callbacks, info->callback); - if (!list) { + struct Table* table = HashTableLookup(&context->callbacks, info->callback); + if (!table) { return; } - if (info->id >= mScriptListSize(list->value.list)) { - return; - } - struct mScriptValue* fn = mScriptValueUnwrap(mScriptListGetPointer(list->value.list, info->id)); - mScriptValueDeref(fn); - mScriptListGetPointer(list->value.list, info->id)->type = NULL; + mScriptValueDeref(info->fn); + TableRemove(table, cbid); + TableRemove(&context->callbackId, cbid); } void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants) { @@ -324,6 +367,7 @@ void mScriptEngineExportDocNamespace(struct mScriptEngineContext* ctx, const cha struct mScriptValue* key = mScriptStringCreateFromUTF8(values[i].key); mScriptTableInsert(table, key, values[i].value); mScriptValueDeref(key); + mScriptValueDeref(values[i].value); } HashTableInsert(&ctx->docroot, nspace, table); } diff --git a/src/script/docgen.c b/src/script/docgen.c index 65fcc87ce..7e35adbbd 100644 --- a/src/script/docgen.c +++ b/src/script/docgen.c @@ -7,8 +7,7 @@ #include #include #include -#include -#include +#include #include struct mScriptContext context; @@ -187,6 +186,9 @@ void explainClass(struct mScriptTypeClass* cls, int level) { } docstring = NULL; } + if (details->info.member.readonly) { + fprintf(out, "%s readonly: true\n", indent); + } fprintf(out, "%s type: %s\n", indent, details->info.member.type->name); break; case mSCRIPT_CLASS_INIT_END: @@ -468,8 +470,10 @@ int main(int argc, char* argv[]) { mScriptContextInit(&context); mScriptContextAttachStdlib(&context); - mScriptContextAttachSocket(&context); + mScriptContextAttachImage(&context); mScriptContextAttachInput(&context); + mScriptContextAttachSocket(&context); + mScriptContextAttachStorage(&context); mScriptContextSetTextBufferFactory(&context, NULL, NULL); initTypes(); diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index db7a70075..9a5278846 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -36,8 +36,11 @@ static const char* _luaGetError(struct mScriptEngineContext*); static bool _luaCall(struct mScriptFrame*, void* context); +static void _freeFrame(struct mScriptList* frame); +static void _autofreeFrame(struct mScriptContext* context, struct mScriptList* frame); + struct mScriptEngineContextLua; -static bool _luaPushFrame(struct mScriptEngineContextLua*, struct mScriptList*, bool internal); +static bool _luaPushFrame(struct mScriptEngineContextLua*, struct mScriptList*); static bool _luaPopFrame(struct mScriptEngineContextLua*, struct mScriptList*); static bool _luaInvoke(struct mScriptEngineContextLua*, struct mScriptFrame*); @@ -57,6 +60,7 @@ static int _luaGetList(lua_State* lua); static int _luaLenList(lua_State* lua); static int _luaRequireShim(lua_State* lua); +static int _luaPrintShim(lua_State* lua); static const char* _socketLuaSource = "socket = {\n" @@ -98,7 +102,7 @@ static const char* _socketLuaSource = " local cbid = self._nextCallback\n" " self._nextCallback = cbid + 1\n" " self._callbacks[event][cbid] = callback\n" - " return id\n" + " return cbid\n" " end,\n" " remove = function(self, cbid)\n" " for _, group in pairs(self._callbacks) do\n" @@ -131,7 +135,7 @@ static const char* _socketLuaSource = " end,\n" " connect = function(self, address, port)\n" " local status = self._s:connect(address, port)\n" - " return socket._wrap(status)\n" + " return self:_hook(status)\n" " end,\n" " listen = function(self, backlog)\n" " local status = self._s:listen(backlog or 1)\n" @@ -411,6 +415,14 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS lua_getglobal(luaContext->lua, "require"); luaContext->require = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX); + lua_pushliteral(luaContext->lua, "log"); + lua_pushcclosure(luaContext->lua, _luaPrintShim, 1); + lua_setglobal(luaContext->lua, "print"); + + lua_pushliteral(luaContext->lua, "warn"); + lua_pushcclosure(luaContext->lua, _luaPrintShim, 1); + lua_setglobal(luaContext->lua, "warn"); + HashTableInit(&luaContext->d.docroot, 0, (void (*)(void*)) mScriptValueDeref); int status = luaL_dostring(luaContext->lua, _socketLuaSource); @@ -424,6 +436,7 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS lua_getfield(luaContext->lua, -1, "ERRORS"); for (i = 0; i < _mScriptSocketNumErrors; i++) { const struct _mScriptSocketError* err = &_mScriptSocketErrors[i]; + lua_pushinteger(luaContext->lua, err->err); if (err->message) { lua_pushstring(luaContext->lua, err->message); struct mScriptValue* key = mScriptValueAlloc(mSCRIPT_TYPE_MS_S32); @@ -435,7 +448,7 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS } else { lua_pushnil(luaContext->lua); } - lua_seti(luaContext->lua, -2, err->err); + lua_settable(luaContext->lua, -3); } lua_pop(luaContext->lua, 2); @@ -446,7 +459,6 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS mSCRIPT_KV_PAIR(connect, mSCRIPT_VALUE_DOC_FUNCTION(socket_connect)), mSCRIPT_KV_SENTINEL }); - mScriptValueDeref(errors); mScriptEngineSetDocstring(&luaContext->d, "socket", "A basic TCP socket library"); mScriptEngineSetDocstring(&luaContext->d, "socket.ERRORS", "Error strings corresponding to the C.SOCKERR error codes, indexed both by name and by value"); @@ -461,6 +473,16 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS "the connection either succeeds or fails"); } + mScriptEngineExportDocNamespace(&luaContext->d, "script", (struct mScriptKVPair[]) { + mSCRIPT_KV_PAIR(dir, mScriptStringCreateFromASCII("/")), + mSCRIPT_KV_PAIR(path, mScriptStringCreateFromASCII("/lua")), + mSCRIPT_KV_SENTINEL + }); + + mScriptEngineSetDocstring(&luaContext->d, "script", "Information about the currently loaded script"); + mScriptEngineSetDocstring(&luaContext->d, "script.dir", "The path to the directory containing the script"); + mScriptEngineSetDocstring(&luaContext->d, "script.path", "The path of the current script file"); + return &luaContext->d; } @@ -520,6 +542,8 @@ struct mScriptValue* _luaRootScope(struct mScriptEngineContext* ctx) { lua_pop(luaContext->lua, 1); key = _luaCoerce(luaContext, false); mScriptValueWrap(key, mScriptListAppend(list->value.list)); + mScriptValueRef(key); + mScriptContextFillPool(luaContext->d.context, key); } lua_pop(luaContext->lua, 1); @@ -538,11 +562,13 @@ struct mScriptValue* _luaCoerceFunction(struct mScriptEngineContextLua* luaConte return value; } -struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext) { +struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext, struct Table* markedObjects) { struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); bool isList = true; lua_pushnil(luaContext->lua); + + const void* tablePointer; while (lua_next(luaContext->lua, -2) != 0) { struct mScriptValue* value = NULL; int type = lua_type(luaContext->lua, -1); @@ -553,14 +579,20 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext) case LUA_TFUNCTION: value = _luaCoerce(luaContext, true); break; + case LUA_TTABLE: + tablePointer = lua_topointer(luaContext->lua, -1); + // Ensure this table doesn't contain any cycles + if (!HashTableLookupBinary(markedObjects, &tablePointer, sizeof(tablePointer))) { + HashTableInsertBinary(markedObjects, &tablePointer, sizeof(tablePointer), (void*) tablePointer); + value = _luaCoerceTable(luaContext, markedObjects); + } default: - // Don't let values be something that could contain themselves break; } if (!value) { - lua_pop(luaContext->lua, 3); + lua_pop(luaContext->lua, type == LUA_TTABLE ? 2 : 3); mScriptValueDeref(table); - return false; + return NULL; } struct mScriptValue* key = NULL; @@ -606,11 +638,17 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext) } if (i != len + 1) { mScriptValueDeref(list); - mScriptContextFillPool(luaContext->d.context, table); return table; } + for (i = 0; i < mScriptListSize(list->value.list); ++i) { + struct mScriptValue* value = mScriptListGetPointer(list->value.list, i); + if (value->type->base != mSCRIPT_TYPE_WRAPPER) { + continue; + } + value = mScriptValueUnwrap(value); + mScriptValueRef(value); + } mScriptValueDeref(table); - mScriptContextFillPool(luaContext->d.context, list); return list; } @@ -622,6 +660,7 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool size_t size; const void* buffer; + struct Table markedObjects; struct mScriptValue* value = NULL; switch (lua_type(luaContext->lua, -1)) { case LUA_TNIL: @@ -645,7 +684,6 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool case LUA_TSTRING: buffer = lua_tolstring(luaContext->lua, -1, &size); value = mScriptStringCreateFromBytes(buffer, size); - mScriptContextFillPool(luaContext->d.context, value); break; case LUA_TFUNCTION: // This function pops the value internally via luaL_ref @@ -658,19 +696,36 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool if (!pop) { break; } - return _luaCoerceTable(luaContext); + HashTableInit(&markedObjects, 0, NULL); + value = _luaCoerceTable(luaContext, &markedObjects); + HashTableDeinit(&markedObjects); + return value; case LUA_TUSERDATA: if (!lua_getmetatable(luaContext->lua, -1)) { break; } luaL_getmetatable(luaContext->lua, "mSTStruct"); if (!lua_rawequal(luaContext->lua, -1, -2)) { - lua_pop(luaContext->lua, 2); - break; + lua_pop(luaContext->lua, 1); + luaL_getmetatable(luaContext->lua, "mSTList"); + if (!lua_rawequal(luaContext->lua, -1, -2)) { + lua_pop(luaContext->lua, 1); + luaL_getmetatable(luaContext->lua, "mSTTable"); + if (!lua_rawequal(luaContext->lua, -1, -2)) { + lua_pop(luaContext->lua, 2); + break; + } + } } lua_pop(luaContext->lua, 2); value = lua_touserdata(luaContext->lua, -1); value = mScriptContextAccessWeakref(luaContext->d.context, value); + if (value->type->base == mSCRIPT_TYPE_WRAPPER) { + value = mScriptValueUnwrap(value); + } + if (value) { + mScriptValueRef(value); + } break; } if (pop) { @@ -692,6 +747,7 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v lua_pushnil(luaContext->lua); return true; } + mScriptContextFillPool(luaContext->d.context, value); } struct mScriptValue derefPtr; if (value->type->base == mSCRIPT_TYPE_OPAQUE) { @@ -782,6 +838,7 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v if (needsWeakref) { *newValue = mSCRIPT_MAKE(WEAKREF, weakref); } else { + mScriptValueRef(value); mScriptValueWrap(value, newValue); } lua_getfield(luaContext->lua, LUA_REGISTRYINDEX, "mSTList"); @@ -792,6 +849,7 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v if (needsWeakref) { *newValue = mSCRIPT_MAKE(WEAKREF, weakref); } else { + mScriptValueRef(value); mScriptValueWrap(value, newValue); } lua_getfield(luaContext->lua, LUA_REGISTRYINDEX, "mSTTable"); @@ -803,7 +861,6 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v newValue->refs = mSCRIPT_VALUE_UNREF; newValue->type->alloc(newValue); lua_pushcclosure(luaContext->lua, _luaThunk, 1); - mScriptValueDeref(value); break; case mSCRIPT_TYPE_OBJECT: if (!value->value.opaque) { @@ -814,6 +871,7 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v if (needsWeakref) { *newValue = mSCRIPT_MAKE(WEAKREF, weakref); } else { + mScriptValueRef(value); mScriptValueWrap(value, newValue); } lua_getfield(luaContext->lua, LUA_REGISTRYINDEX, "mSTStruct"); @@ -854,7 +912,9 @@ bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFi luaContext->lastError = NULL; } char name[PATH_MAX + 1]; - char dirname[PATH_MAX] = {0}; + char dirname[PATH_MAX]; + name[0] = '\0'; + dirname[0] = '\0'; if (filename) { if (*filename == '*') { snprintf(name, sizeof(name), "=%s", filename + 1); @@ -869,7 +929,11 @@ bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFi lastSlash = lastBackslash; } if (lastSlash) { - strncpy(dirname, filename, lastSlash - filename); + size_t len = lastSlash - filename + 1; + if (sizeof(dirname) < len) { + len = sizeof(dirname); + } + strlcpy(dirname, filename, len); } snprintf(name, sizeof(name), "@%s", filename); } @@ -882,14 +946,43 @@ bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFi #endif switch (ret) { case LUA_OK: + // Create new _ENV + lua_newtable(luaContext->lua); + + // Make the old _ENV the __index in the metatable + lua_newtable(luaContext->lua); + lua_pushliteral(luaContext->lua, "__index"); + lua_getupvalue(luaContext->lua, -4, 1); + lua_rawset(luaContext->lua, -3); + + lua_pushliteral(luaContext->lua, "__newindex"); + lua_getupvalue(luaContext->lua, -4, 1); + lua_rawset(luaContext->lua, -3); + + lua_setmetatable(luaContext->lua, -2); + + lua_pushliteral(luaContext->lua, "script"); + lua_newtable(luaContext->lua); + if (dirname[0]) { - lua_getupvalue(luaContext->lua, -1, 1); lua_pushliteral(luaContext->lua, "require"); lua_pushstring(luaContext->lua, dirname); lua_pushcclosure(luaContext->lua, _luaRequireShim, 1); + lua_rawset(luaContext->lua, -5); + + lua_pushliteral(luaContext->lua, "dir"); + lua_pushstring(luaContext->lua, dirname); lua_rawset(luaContext->lua, -3); - lua_pop(luaContext->lua, 1); } + + if (name[0] == '@') { + lua_pushliteral(luaContext->lua, "path"); + lua_pushstring(luaContext->lua, &name[1]); + lua_rawset(luaContext->lua, -3); + } + + lua_rawset(luaContext->lua, -3); + lua_setupvalue(luaContext->lua, -2, 1); luaContext->func = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX); return true; case LUA_ERRSYNTAX: @@ -914,16 +1007,12 @@ const char* _luaGetError(struct mScriptEngineContext* context) { return luaContext->lastError; } -bool _luaPushFrame(struct mScriptEngineContextLua* luaContext, struct mScriptList* frame, bool internal) { +bool _luaPushFrame(struct mScriptEngineContextLua* luaContext, struct mScriptList* frame) { bool ok = true; if (frame) { size_t i; for (i = 0; i < mScriptListSize(frame); ++i) { struct mScriptValue* value = mScriptListGetPointer(frame, i); - if (internal && value->type->base == mSCRIPT_TYPE_WRAPPER) { - value = mScriptValueUnwrap(value); - mScriptContextFillPool(luaContext->d.context, value); - } if (!_luaWrap(luaContext, value)) { ok = false; break; @@ -947,8 +1036,11 @@ bool _luaPopFrame(struct mScriptEngineContextLua* luaContext, struct mScriptList ok = false; break; } - mScriptValueWrap(value, mScriptListAppend(frame)); - mScriptValueDeref(value); + struct mScriptValue* tail = mScriptListAppend(frame); + mScriptValueWrap(value, tail); + if (tail->type == value->type) { + mScriptValueDeref(value); + } } if (count > i) { lua_pop(luaContext->lua, count - i); @@ -975,6 +1067,26 @@ bool _luaCall(struct mScriptFrame* frame, void* context) { return true; } +void _freeFrame(struct mScriptList* frame) { + size_t i; + for (i = 0; i < mScriptListSize(frame); ++i) { + struct mScriptValue* val = mScriptValueUnwrap(mScriptListGetPointer(frame, i)); + if (val) { + mScriptValueDeref(val); + } + } +} + +void _autofreeFrame(struct mScriptContext* context, struct mScriptList* frame) { + size_t i; + for (i = 0; i < mScriptListSize(frame); ++i) { + struct mScriptValue* val = mScriptValueUnwrap(mScriptListGetPointer(frame, i)); + if (val) { + mScriptContextFillPool(context, val); + } + } +} + bool _luaInvoke(struct mScriptEngineContextLua* luaContext, struct mScriptFrame* frame) { int nargs = 0; if (frame) { @@ -986,7 +1098,7 @@ bool _luaInvoke(struct mScriptEngineContextLua* luaContext, struct mScriptFrame* luaContext->lastError = NULL; } - if (frame && !_luaPushFrame(luaContext, &frame->arguments, false)) { + if (frame && !_luaPushFrame(luaContext, &frame->arguments)) { return false; } @@ -1051,6 +1163,7 @@ int _luaThunk(lua_State* lua) { struct mScriptFrame frame; mScriptFrameInit(&frame); if (!_luaPopFrame(luaContext, &frame.arguments)) { + _freeFrame(&frame.arguments); mScriptContextDrainPool(luaContext->d.context); mScriptFrameDeinit(&frame); luaL_traceback(lua, lua, "Error calling function (translating arguments into runtime)", 1); @@ -1058,19 +1171,21 @@ int _luaThunk(lua_State* lua) { } struct mScriptValue* fn = lua_touserdata(lua, lua_upvalueindex(1)); + _autofreeFrame(luaContext->d.context, &frame.arguments); if (!fn || !mScriptInvoke(fn, &frame)) { + mScriptContextDrainPool(luaContext->d.context); mScriptFrameDeinit(&frame); luaL_traceback(lua, lua, "Error calling function (invoking failed)", 1); return lua_error(lua); } - if (!_luaPushFrame(luaContext, &frame.returnValues, true)) { - mScriptFrameDeinit(&frame); + bool ok = _luaPushFrame(luaContext, &frame.returnValues); + mScriptContextDrainPool(luaContext->d.context); + mScriptFrameDeinit(&frame); + if (!ok) { luaL_traceback(lua, lua, "Error calling function (translating return values from runtime)", 1); return lua_error(lua); } - mScriptContextDrainPool(luaContext->d.context); - mScriptFrameDeinit(&frame); return lua_gettop(luaContext->lua); } @@ -1125,19 +1240,22 @@ int _luaSetObject(lua_State* lua) { strlcpy(key, keyPtr, sizeof(key)); lua_pop(lua, 2); - obj = mScriptContextAccessWeakref(luaContext->d.context, obj); - if (!obj) { - luaL_traceback(lua, lua, "Invalid object", 1); - return lua_error(lua); - } - if (!val) { luaL_traceback(lua, lua, "Error translating value to runtime", 1); return lua_error(lua); } + obj = mScriptContextAccessWeakref(luaContext->d.context, obj); + if (!obj) { + mScriptValueDeref(val); + mScriptContextDrainPool(luaContext->d.context); + luaL_traceback(lua, lua, "Invalid object", 1); + return lua_error(lua); + } + if (!mScriptObjectSet(obj, key, val)) { mScriptValueDeref(val); + mScriptContextDrainPool(luaContext->d.context); char error[MAX_KEY_SIZE + 16]; snprintf(error, sizeof(error), "Invalid key '%s'", key); luaL_traceback(lua, lua, "Invalid key", 1); @@ -1182,7 +1300,10 @@ int _luaGetTable(lua_State* lua) { lua_pop(lua, 2); obj = mScriptContextAccessWeakref(luaContext->d.context, obj); - if (!obj) { + if (obj->type->base == mSCRIPT_TYPE_WRAPPER) { + obj = mScriptValueUnwrap(obj); + } + if (!obj || obj->type != mSCRIPT_TYPE_MS_TABLE) { luaL_traceback(lua, lua, "Invalid table", 1); return lua_error(lua); } @@ -1214,7 +1335,10 @@ int _luaLenTable(lua_State* lua) { lua_pop(lua, 1); obj = mScriptContextAccessWeakref(luaContext->d.context, obj); - if (!obj) { + if (obj->type->base == mSCRIPT_TYPE_WRAPPER) { + obj = mScriptValueUnwrap(obj); + } + if (!obj || obj->type != mSCRIPT_TYPE_MS_TABLE) { luaL_traceback(lua, lua, "Invalid table", 1); return lua_error(lua); } @@ -1250,7 +1374,10 @@ static int _luaNextTable(lua_State* lua) { lua_pop(lua, 2); table = mScriptContextAccessWeakref(luaContext->d.context, table); - if (!table) { + if (table->type->base == mSCRIPT_TYPE_WRAPPER) { + table = mScriptValueUnwrap(table); + } + if (!table || table->type != mSCRIPT_TYPE_MS_TABLE) { luaL_traceback(lua, lua, "Invalid table", 1); return lua_error(lua); } @@ -1410,3 +1537,37 @@ static int _luaRequireShim(lua_State* lua) { int newtop = lua_gettop(luaContext->lua); return newtop - oldtop + 1; } + +static int _luaPrintShim(lua_State* lua) { + int n = lua_gettop(lua); + + lua_getglobal(lua, "console"); + lua_insert(lua, 1); + + // The first upvalue is either "log" or "warn" + lua_getglobal(lua, "console"); + lua_pushvalue(lua, lua_upvalueindex(1)); + lua_gettable(lua, -2); + + lua_insert(lua, 1); + lua_pop(lua, 1); + + // TODO when console:log is variadic and stringifies by itself: + // lua_call(lua, n + 1, 0); + + // Until then, stringify and concatenate: + for (int i = 0; i < n; i++) { + luaL_tolstring(lua, i * 2 + 3, NULL); + lua_replace(lua, i * 2 + 3); + if (i == 0) { + lua_pushliteral(lua, ""); + } else { + lua_pushliteral(lua, "\t"); + } + lua_insert(lua, i * 2 + 3); + } + n = n * 2 - 1; + lua_concat(lua, n + 1); + lua_call(lua, 2, 0); + return 0; +} diff --git a/src/script/image.c b/src/script/image.c new file mode 100644 index 000000000..05ca9bcd4 --- /dev/null +++ b/src/script/image.c @@ -0,0 +1,85 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +static struct mScriptValue* _mImageNew(unsigned width, unsigned height) { + // For various reasons, it's probably a good idea to limit the maximum image size scripts can make + if (width >= 10000 || height >= 10000) { + return NULL; + } + struct mImage* image = mImageCreate(width, height, mCOLOR_ABGR8); + if (!image) { + return NULL; + } + struct mScriptValue* result = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mImage)); + result->value.opaque = image; + result->flags = mSCRIPT_VALUE_FLAG_DEINIT; + return result; +} + +static struct mScriptValue* _mImageLoad(const char* path) { + struct mImage* image = mImageLoad(path); + if (!image) { + return NULL; + } + struct mScriptValue* result = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mImage)); + result->value.opaque = image; + result->flags = mSCRIPT_VALUE_FLAG_DEINIT; + return result; +} +mSCRIPT_DECLARE_STRUCT_C_METHOD(mImage, U32, getPixel, mImageGetPixel, 2, U32, x, U32, y); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mImage, setPixel, mImageSetPixel, 3, U32, x, U32, y, U32, color); +mSCRIPT_DECLARE_STRUCT_C_METHOD_WITH_DEFAULTS(mImage, BOOL, save, mImageSave, 2, CHARP, path, CHARP, format); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mImage, _deinit, mImageDestroy, 0); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mImage, drawImageOpaque, mImageBlit, 3, CS(mImage), image, U32, x, U32, y); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD_WITH_DEFAULTS(mImage, drawImage, mImageCompositeWithAlpha, 4, CS(mImage), image, U32, x, U32, y, F32, alpha); + +mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mImage, save) + mSCRIPT_NO_DEFAULT, + mSCRIPT_CHARP("PNG") +mSCRIPT_DEFINE_DEFAULTS_END; + +mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mImage, drawImage) + mSCRIPT_NO_DEFAULT, + mSCRIPT_NO_DEFAULT, + mSCRIPT_NO_DEFAULT, + mSCRIPT_F32(1.0f) +mSCRIPT_DEFINE_DEFAULTS_END; + +mSCRIPT_DEFINE_STRUCT(mImage) + mSCRIPT_DEFINE_CLASS_DOCSTRING( + "A single, static image." + ) + mSCRIPT_DEFINE_STRUCT_DEINIT(mImage) + mSCRIPT_DEFINE_DOCSTRING("Save the image to a file. Currently, only `PNG` format is supported") + mSCRIPT_DEFINE_STRUCT_METHOD(mImage, save) + mSCRIPT_DEFINE_DOCSTRING("Get the ARGB value of the pixel at a given coordinate") + mSCRIPT_DEFINE_STRUCT_METHOD(mImage, getPixel) + mSCRIPT_DEFINE_DOCSTRING("Set the ARGB value of the pixel at a given coordinate") + mSCRIPT_DEFINE_STRUCT_METHOD(mImage, setPixel) + mSCRIPT_DEFINE_DOCSTRING("Draw another image onto this image without any alpha blending, overwriting what was already there") + mSCRIPT_DEFINE_STRUCT_METHOD(mImage, drawImageOpaque) + mSCRIPT_DEFINE_DOCSTRING("Draw another image onto this image with alpha blending as needed, optionally specifying a coefficient for adjusting the opacity") + mSCRIPT_DEFINE_STRUCT_METHOD(mImage, drawImage) + mSCRIPT_DEFINE_DOCSTRING("The width of the image, in pixels") + mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mImage, U32, width) + mSCRIPT_DEFINE_DOCSTRING("The height of the image, in pixels") + mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mImage, U32, height) +mSCRIPT_DEFINE_END; + +mSCRIPT_BIND_FUNCTION(mImageNew_Binding, W(mImage), _mImageNew, 2, U32, width, U32, height); +mSCRIPT_BIND_FUNCTION(mImageLoad_Binding, W(mImage), _mImageLoad, 1, CHARP, path); + +void mScriptContextAttachImage(struct mScriptContext* context) { + mScriptContextExportNamespace(context, "image", (struct mScriptKVPair[]) { + mSCRIPT_KV_PAIR(new, &mImageNew_Binding), + mSCRIPT_KV_PAIR(load, &mImageLoad_Binding), + mSCRIPT_KV_SENTINEL + }); + mScriptContextSetDocstring(context, "image", "Methods for creating struct::mImage instances"); + mScriptContextSetDocstring(context, "image.new", "Create a new image with the given dimensions"); + mScriptContextSetDocstring(context, "image.load", "Load an image from a path. Currently, only `PNG` format is supported"); +} diff --git a/src/script/input.c b/src/script/input.c index fdb6593cb..31f1f806a 100644 --- a/src/script/input.c +++ b/src/script/input.c @@ -36,17 +36,21 @@ struct mScriptInputContext { static void _mScriptInputDeinit(struct mScriptInputContext*); static bool _mScriptInputIsKeyActive(const struct mScriptInputContext*, struct mScriptValue*); +static struct mScriptValue* _mScriptInputActiveKeys(const struct mScriptInputContext*); mSCRIPT_DECLARE_STRUCT(mScriptInputContext); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptInputContext, _deinit, _mScriptInputDeinit, 0); mSCRIPT_DECLARE_STRUCT_C_METHOD(mScriptInputContext, BOOL, isKeyActive, _mScriptInputIsKeyActive, 1, WRAPPER, key); +mSCRIPT_DECLARE_STRUCT_C_METHOD(mScriptInputContext, WLIST, activeKeys, _mScriptInputActiveKeys, 0); mSCRIPT_DEFINE_STRUCT(mScriptInputContext) mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptInputContext) mSCRIPT_DEFINE_DOCSTRING("Sequence number of the next event to be emitted") mSCRIPT_DEFINE_STRUCT_MEMBER(mScriptInputContext, U64, seq) - mSCRIPT_DEFINE_DOCSTRING("Check if a given keyboard key is currently held. The input can be either the printable character for a key, or the numerical Unicode codepoint") + mSCRIPT_DEFINE_DOCSTRING("Check if a given keyboard key is currently held. The input can be either the printable character for a key, the numerical Unicode codepoint, or a special value from C.KEY") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptInputContext, isKeyActive) + mSCRIPT_DEFINE_DOCSTRING("Get a list of the currently active keys. The values are Unicode codepoints or special key values from C.KEY, not strings, so make sure to convert as needed") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptInputContext, activeKeys) mSCRIPT_DEFINE_DOCSTRING("The currently active gamepad, if any") mSCRIPT_DEFINE_STRUCT_MEMBER(mScriptInputContext, PCS(mScriptGamepad), activeGamepad) mSCRIPT_DEFINE_END; @@ -346,6 +350,19 @@ bool _mScriptInputIsKeyActive(const struct mScriptInputContext* context, struct return down != NULL; } +static struct mScriptValue* _mScriptInputActiveKeys(const struct mScriptInputContext* context) { + struct mScriptValue* list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST); + struct TableIterator iter; + if (!TableIteratorStart(&context->activeKeys, &iter)) { + return list; + } + do { + uint32_t key = TableIteratorGetKey(&context->activeKeys, &iter); + *mScriptListAppend(list->value.list) = mSCRIPT_MAKE_U32(key); + } while (TableIteratorNext(&context->activeKeys, &iter)); + return list; +} + static bool _updateKeys(struct mScriptContext* context, struct mScriptKeyEvent* event) { int offset = 0; switch (event->state) { @@ -410,6 +427,44 @@ void mScriptContextFireEvent(struct mScriptContext* context, struct mScriptEvent mScriptListDeinit(&args); } +void mScriptContextClearKeys(struct mScriptContext* context) { + struct mScriptValue* input = mScriptContextGetGlobal(context, "input"); + if (!input) { + return; + } + struct mScriptInputContext* inputContext = input->value.opaque; + size_t keyCount = TableSize(&inputContext->activeKeys); + uint32_t* keys = calloc(keyCount, sizeof(uint32_t)); + struct TableIterator iter; + size_t i = 0; + if (!TableIteratorStart(&inputContext->activeKeys, &iter)) { + free(keys); + return; + } + do { + keys[i] = TableIteratorGetKey(&inputContext->activeKeys, &iter); + ++i; + } while (TableIteratorNext(&inputContext->activeKeys, &iter)); + + struct mScriptKeyEvent event = { + .d = { + .type = mSCRIPT_EV_TYPE_KEY + }, + .state = mSCRIPT_INPUT_STATE_UP, + .modifiers = 0 + }; + for (i = 0; i < keyCount; ++i) { + event.key = keys[i]; + intptr_t value = (intptr_t) TableLookup(&inputContext->activeKeys, event.key); + if (value > 1) { + TableInsert(&inputContext->activeKeys, event.key, (void*) 1); + } + mScriptContextFireEvent(context, &event.d); + } + + free(keys); +} + int mScriptContextGamepadAttach(struct mScriptContext* context, struct mScriptGamepad* pad) { struct mScriptValue* input = mScriptContextGetGlobal(context, "input"); if (!input) { diff --git a/src/script/socket.c b/src/script/socket.c index 818c758f6..5b2e95b60 100644 --- a/src/script/socket.c +++ b/src/script/socket.c @@ -36,6 +36,7 @@ static const struct _mScriptSocketErrorMapping { #ifndef USE_GETHOSTBYNAME #ifdef _WIN32 { WSATRY_AGAIN, mSCRIPT_SOCKERR_AGAIN }, + { WSAEWOULDBLOCK, mSCRIPT_SOCKERR_AGAIN }, { WSANO_RECOVERY, mSCRIPT_SOCKERR_FAILED }, { WSANO_DATA, mSCRIPT_SOCKERR_NO_DATA }, { WSAHOST_NOT_FOUND, mSCRIPT_SOCKERR_NOT_FOUND }, diff --git a/src/script/stdlib.c b/src/script/stdlib.c index 839673cdb..33ec0b751 100644 --- a/src/script/stdlib.c +++ b/src/script/stdlib.c @@ -3,10 +3,12 @@ * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include +#include #include #include +#include +#include #include #ifdef M_CORE_GBA #include @@ -24,7 +26,14 @@ static uint32_t _mScriptCallbackAdd(struct mScriptCallbackManager* adapter, stru fn = mScriptValueUnwrap(fn); } uint32_t id = mScriptContextAddCallback(adapter->context, name->buffer, fn); - mScriptValueDeref(fn); + return id; +} + +static uint32_t _mScriptCallbackOneshot(struct mScriptCallbackManager* adapter, struct mScriptString* name, struct mScriptValue* fn) { + if (fn->type->base == mSCRIPT_TYPE_WRAPPER) { + fn = mScriptValueUnwrap(fn); + } + uint32_t id = mScriptContextAddOneshot(adapter->context, name->buffer, fn); return id; } @@ -34,6 +43,7 @@ static void _mScriptCallbackRemove(struct mScriptCallbackManager* adapter, uint3 mSCRIPT_DECLARE_STRUCT(mScriptCallbackManager); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCallbackManager, U32, add, _mScriptCallbackAdd, 2, STR, callback, WRAPPER, function); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCallbackManager, U32, oneshot, _mScriptCallbackOneshot, 2, STR, callback, WRAPPER, function); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCallbackManager, remove, _mScriptCallbackRemove, 1, U32, cbid); static uint64_t mScriptMakeBitmask(struct mScriptList* list) { @@ -71,19 +81,22 @@ mSCRIPT_BIND_FUNCTION(mScriptExpandBitmask_Binding, WLIST, mScriptExpandBitmask, mSCRIPT_DEFINE_STRUCT(mScriptCallbackManager) mSCRIPT_DEFINE_CLASS_DOCSTRING( "A global singleton object `callbacks` used for managing callbacks. The following callbacks are defined:\n\n" - "- **alarm**: An in-game alarm went off\n" - "- **crashed**: The emulation crashed\n" - "- **frame**: The emulation finished a frame\n" - "- **keysRead**: The emulation is about to read the key input\n" - "- **reset**: The emulation has been reset\n" - "- **savedataUpdated**: The emulation has just finished modifying save data\n" - "- **sleep**: The emulation has used the sleep feature to enter a low-power mode\n" - "- **shutdown**: The emulation has been powered off\n" - "- **start**: The emulation has started\n" - "- **stop**: The emulation has voluntarily shut down\n" + "- `alarm`: An in-game alarm went off\n" + "- `crashed`: The emulation crashed\n" + "- `frame`: The emulation finished a frame\n" + "- `keysRead`: The emulation is about to read the key input\n" + "- `reset`: The emulation has been reset\n" + "- `rumble`: The state of the rumble motor was changed. This callback is passed a single argument that specifies if it was turned on (true) or off (false)\n" + "- `savedataUpdated`: The emulation has just finished modifying save data\n" + "- `sleep`: The emulation has used the sleep feature to enter a low-power mode\n" + "- `shutdown`: The emulation has been powered off\n" + "- `start`: The emulation has started\n" + "- `stop`: The emulation has voluntarily shut down\n" ) mSCRIPT_DEFINE_DOCSTRING("Add a callback of the named type. The returned id can be used to remove it later") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, add) + mSCRIPT_DEFINE_DOCSTRING("Add a one-shot callback of the named type that will be automatically removed after called. The returned id can be used to remove it early") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, oneshot) mSCRIPT_DEFINE_DOCSTRING("Remove a callback with the previously retuned id") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, remove) mSCRIPT_DEFINE_END; @@ -146,6 +159,15 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) { mSCRIPT_CONSTANT_PAIR(GB_KEY, DOWN), mSCRIPT_KV_SENTINEL }); +#endif +#ifdef USE_DEBUGGERS + mScriptContextExportConstants(context, "WATCHPOINT_TYPE", (struct mScriptKVPair[]) { + mSCRIPT_CONSTANT_PAIR(WATCHPOINT, WRITE), + mSCRIPT_CONSTANT_PAIR(WATCHPOINT, READ), + mSCRIPT_CONSTANT_PAIR(WATCHPOINT, RW), + mSCRIPT_CONSTANT_PAIR(WATCHPOINT, WRITE_CHANGE), + mSCRIPT_KV_SENTINEL + }); #endif mScriptContextSetGlobal(context, "C", context->constants); mScriptContextSetDocstring(context, "C", "A table containing the [exported constants](#constants)"); @@ -158,4 +180,27 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) { mScriptContextSetDocstring(context, "util", "Basic utility library"); mScriptContextSetDocstring(context, "util.makeBitmask", "Compile a list of bit indices into a bitmask"); mScriptContextSetDocstring(context, "util.expandBitmask", "Expand a bitmask into a list of bit indices"); + + struct mScriptValue* systemVersion = mScriptStringCreateFromUTF8(projectVersion); + struct mScriptValue* systemProgram = mScriptStringCreateFromUTF8(projectName); + struct mScriptValue* systemBranch = mScriptStringCreateFromUTF8(gitBranch); + struct mScriptValue* systemCommit = mScriptStringCreateFromUTF8(gitCommit); + struct mScriptValue* systemRevision = mScriptValueAlloc(mSCRIPT_TYPE_MS_S32); + systemRevision->value.s32 = gitRevision; + + mScriptContextExportNamespace(context, "system", (struct mScriptKVPair[]) { + mSCRIPT_KV_PAIR(version, systemVersion), + mSCRIPT_KV_PAIR(program, systemProgram), + mSCRIPT_KV_PAIR(branch, systemBranch), + mSCRIPT_KV_PAIR(commit, systemCommit), + mSCRIPT_KV_PAIR(revision, systemRevision), + mSCRIPT_KV_SENTINEL + }); + + mScriptContextSetDocstring(context, "system", "Information about the system the script is running under"); + mScriptContextSetDocstring(context, "system.version", "The current version of this build of the program"); + mScriptContextSetDocstring(context, "system.program", "The name of the program. Generally this will be \\\"mGBA\\\", but forks may change it to differentiate"); + mScriptContextSetDocstring(context, "system.branch", "The current git branch of this build of the program, if known"); + mScriptContextSetDocstring(context, "system.commit", "The current git commit hash of this build of the program, if known"); + mScriptContextSetDocstring(context, "system.revision", "The current git revision number of this build of the program, or -1 if unknown"); } diff --git a/src/script/storage.c b/src/script/storage.c new file mode 100644 index 000000000..e1299d53e --- /dev/null +++ b/src/script/storage.c @@ -0,0 +1,557 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +#include +#include + +#include +#include + +#define STORAGE_LEN_MAX 64 + +struct mScriptStorageBucket { + char* name; + struct mScriptValue* root; + bool autoflush; + bool dirty; +}; + +struct mScriptStorageContext { + struct Table buckets; +}; + +void mScriptStorageBucketDeinit(void*); +struct mScriptValue* mScriptStorageBucketGet(struct mScriptStorageBucket* bucket, const char* key); +static void mScriptStorageBucketSet(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value); +static void mScriptStorageBucketSetVoid(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value); +static void mScriptStorageBucketSetSInt(struct mScriptStorageBucket* bucket, const char* key, int64_t value); +static void mScriptStorageBucketSetUInt(struct mScriptStorageBucket* bucket, const char* key, uint64_t value); +static void mScriptStorageBucketSetFloat(struct mScriptStorageBucket* bucket, const char* key, double value); +static void mScriptStorageBucketSetBool(struct mScriptStorageBucket* bucket, const char* key, bool value); +static bool mScriptStorageBucketReload(struct mScriptStorageBucket* bucket); +static bool mScriptStorageBucketFlush(struct mScriptStorageBucket* bucket); +static void mScriptStorageBucketEnableAutoFlush(struct mScriptStorageBucket* bucket, bool enable); + +static void mScriptStorageContextDeinit(struct mScriptStorageContext*); +static void mScriptStorageContextFlushAll(struct mScriptStorageContext*); +struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext*, const char* name); + +static bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out); +static struct mScriptValue* mScriptStorageFromJson(struct json_object* json); + +mSCRIPT_DECLARE_STRUCT(mScriptStorageBucket); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, WRAPPER, _get, mScriptStorageBucketGet, 1, CHARP, key); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setSInt, mScriptStorageBucketSetSInt, 2, CHARP, key, S64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setUInt, mScriptStorageBucketSetUInt, 2, CHARP, key, U64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setFloat, mScriptStorageBucketSetFloat, 2, CHARP, key, F64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setBool, mScriptStorageBucketSetBool, 2, CHARP, key, BOOL, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setStr, mScriptStorageBucketSet, 2, CHARP, key, WSTR, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setList, mScriptStorageBucketSet, 2, CHARP, key, WLIST, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setTable, mScriptStorageBucketSet, 2, CHARP, key, WTABLE, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setVoid, mScriptStorageBucketSetVoid, 2, CHARP, key, NUL, value); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, BOOL, reload, mScriptStorageBucketReload, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, BOOL, flush, mScriptStorageBucketFlush, 0); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, enableAutoFlush, mScriptStorageBucketEnableAutoFlush, 1, BOOL, enable); + +mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket) + mSCRIPT_DEFINE_CLASS_DOCSTRING( + "A single 'bucket' of stored data, appropriate for a single script to store its data. " + "Fields can be set directly on the bucket objct, e.g. if you want to store a value called " + "`foo` on a bucket named `bucket`, you can directly assign to it as `bucket.foo = value`, " + "and retrieve it in the same way later. Primitive types (numbers, strings, lists and tables) " + "can be stored in buckets, but complex data types (e.g. a bucket itself) cannot. Data " + "stored in a bucket is periodically flushed to disk and persists between sessions." + ) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setSInt) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setUInt) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setFloat) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setBool) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setStr) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setList) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setTable) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setVoid) + mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(mScriptStorageBucket) + mSCRIPT_DEFINE_DOCSTRING("Reload the state of the bucket from disk") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, reload) + mSCRIPT_DEFINE_DOCSTRING("Flush the bucket to disk manually") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, flush) + mSCRIPT_DEFINE_DOCSTRING( + "Enable or disable the automatic flushing of this bucket. This is good for ensuring buckets " + "don't get flushed in an inconsistent state. It will also disable flushing to disk when the " + "emulator is shut down, so make sure to either manually flush the bucket or re-enable " + "automatic flushing whenever you're done updating it if you do disable it prior." + ) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, enableAutoFlush) +mSCRIPT_DEFINE_END; + +mSCRIPT_DECLARE_STRUCT(mScriptStorageContext); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageContext, _deinit, mScriptStorageContextDeinit, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageContext, S(mScriptStorageBucket), getBucket, mScriptStorageGetBucket, 1, CHARP, key); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageContext, flushAll, mScriptStorageContextFlushAll, 0); + +mSCRIPT_DEFINE_STRUCT(mScriptStorageContext) + mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptStorageContext) + mSCRIPT_DEFINE_DOCSTRING( + "Get a bucket with the given name. Names can contain letters, numbers, " + "underscores and periods. If a given bucket doesn't exist, it is created." + ) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageContext, getBucket) + mSCRIPT_DEFINE_DOCSTRING("Flush all buckets to disk manually") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageContext, flushAll) +mSCRIPT_DEFINE_END; + +struct mScriptValue* mScriptStorageBucketGet(struct mScriptStorageBucket* bucket, const char* key) { + struct mScriptValue* val = mScriptTableLookup(bucket->root, &mSCRIPT_MAKE_CHARP(key)); + if (val) { + mScriptValueRef(val); + } + return val; +} + +void mScriptStorageBucketSet(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value) { + struct mScriptValue* vkey = mScriptStringCreateFromUTF8(key); + if (value->type->base == mSCRIPT_TYPE_WRAPPER) { + value = mScriptValueUnwrap(value); + } + mScriptTableInsert(bucket->root, vkey, value); + mScriptValueDeref(vkey); + bucket->dirty = true; +} + +void mScriptStorageBucketSetVoid(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value) { + UNUSED(value); + struct mScriptValue* vkey = mScriptStringCreateFromUTF8(key); + mScriptTableInsert(bucket->root, vkey, &mScriptValueNull); + mScriptValueDeref(vkey); + bucket->dirty = true; +} + +#define MAKE_SCALAR_SETTER(NAME, TYPE) \ + void mScriptStorageBucketSet ## NAME (struct mScriptStorageBucket* bucket, const char* key, mSCRIPT_TYPE_C_ ## TYPE value) { \ + struct mScriptValue* vkey = mScriptStringCreateFromUTF8(key); \ + struct mScriptValue* vval = mScriptValueAlloc(mSCRIPT_TYPE_MS_ ## TYPE); \ + vval->value.mSCRIPT_TYPE_FIELD_ ## TYPE = value; \ + mScriptTableInsert(bucket->root, vkey, vval); \ + mScriptValueDeref(vkey); \ + mScriptValueDeref(vval); \ + bucket->dirty = true; \ + } + +MAKE_SCALAR_SETTER(SInt, S64) +MAKE_SCALAR_SETTER(UInt, U64) +MAKE_SCALAR_SETTER(Float, F64) +MAKE_SCALAR_SETTER(Bool, BOOL) + +void mScriptStorageGetBucketPath(const char* bucket, char* out) { + mCoreConfigDirectory(out, PATH_MAX); + + strncat(out, PATH_SEP "storage" PATH_SEP, PATH_MAX - 1); +#ifdef _WIN32 + // TODO: Move this to vfs somewhere + WCHAR wout[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, out, -1, wout, MAX_PATH); + CreateDirectoryW(wout, NULL); +#else + mkdir(out, 0755); +#endif + + char suffix[STORAGE_LEN_MAX + 6]; + snprintf(suffix, sizeof(suffix), "%s.json", bucket); + strncat(out, suffix, PATH_MAX - 1); +} + +static struct json_object* _tableToJson(struct mScriptValue* rootVal) { + bool ok = true; + + struct TableIterator iter; + struct json_object* rootObj = json_object_new_object(); + if (mScriptTableIteratorStart(rootVal, &iter)) { + do { + struct mScriptValue* key = mScriptTableIteratorGetKey(rootVal, &iter); + struct mScriptValue* value = mScriptTableIteratorGetValue(rootVal, &iter); + const char* ckey; + if (key->type == mSCRIPT_TYPE_MS_CHARP) { + ckey = key->value.copaque; + } else if (key->type == mSCRIPT_TYPE_MS_STR) { + ckey = key->value.string->buffer; + } else { + ok = false; + break; + } + + struct json_object* obj; + ok = mScriptStorageToJson(value, &obj); + + if (ok) { +#if JSON_C_VERSION_NUM >= (13 << 8) + ok = json_object_object_add(rootObj, ckey, obj) >= 0; +#else + json_object_object_add(rootObj, ckey, obj); +#endif + } + } while (mScriptTableIteratorNext(rootVal, &iter) && ok); + } + if (!ok) { + json_object_put(rootObj); + return NULL; + } + return rootObj; +} + +bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out) { + if (value->type->base == mSCRIPT_TYPE_WRAPPER) { + value = mScriptValueUnwrap(value); + } + + size_t i; + bool ok = true; + struct json_object* obj = NULL; + switch (value->type->base) { + case mSCRIPT_TYPE_SINT: + obj = json_object_new_int64(value->value.s64); + break; + case mSCRIPT_TYPE_UINT: + if (value->type == mSCRIPT_TYPE_MS_BOOL) { + obj = json_object_new_boolean(value->value.u32); + break; + } +#if JSON_C_VERSION_NUM >= (14 << 8) + obj = json_object_new_uint64(value->value.u64); +#else + if (value->value.u64 < (uint64_t) INT64_MAX) { + obj = json_object_new_int64(value->value.u64); + } else { + obj = json_object_new_double(value->value.u64); + } +#endif + break; + case mSCRIPT_TYPE_FLOAT: + obj = json_object_new_double(value->value.f64); + break; + case mSCRIPT_TYPE_STRING: + obj = json_object_new_string_len(value->value.string->buffer, value->value.string->size); + break; + case mSCRIPT_TYPE_LIST: +#if JSON_C_VERSION_NUM >= (15 << 8) + obj = json_object_new_array_ext(mScriptListSize(value->value.list)); +#else + obj = json_object_new_array(); +#endif + for (i = 0; i < mScriptListSize(value->value.list); ++i) { + struct json_object* listObj; + ok = mScriptStorageToJson(mScriptListGetPointer(value->value.list, i), &listObj); + if (!ok) { + break; + } + json_object_array_add(obj, listObj); + } + break; + case mSCRIPT_TYPE_TABLE: + obj = _tableToJson(value); + if (!obj) { + ok = false; + } + break; + case mSCRIPT_TYPE_VOID: + obj = NULL; + break; + default: + ok = false; + break; + } + + if (!ok) { + if (obj) { + json_object_put(obj); + } + *out = NULL; + } else { + *out = obj; + } + return ok; +} + +#ifndef JSON_C_TO_STRING_PRETTY_TAB +#define JSON_C_TO_STRING_PRETTY_TAB 0 +#endif + +static bool _mScriptStorageBucketFlushVF(struct mScriptStorageBucket* bucket, struct VFile* vf) { + struct json_object* rootObj; + bool ok = mScriptStorageToJson(bucket->root, &rootObj); + if (!ok) { + vf->close(vf); + return false; + } + + const char* json = json_object_to_json_string_ext(rootObj, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_PRETTY_TAB); + if (!json) { + json_object_put(rootObj); + vf->close(vf); + return false; + } + + vf->write(vf, json, strlen(json)); + vf->close(vf); + + bucket->dirty = false; + + json_object_put(rootObj); + return true; +} + +bool mScriptStorageBucketFlush(struct mScriptStorageBucket* bucket) { + char path[PATH_MAX]; + mScriptStorageGetBucketPath(bucket->name, path); + struct VFile* vf = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + return false; + } + return _mScriptStorageBucketFlushVF(bucket, vf); +} + +void mScriptStorageBucketEnableAutoFlush(struct mScriptStorageBucket* bucket, bool enable) { + bucket->autoflush = enable; +} + +bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); + if (!value) { + vf->close(vf); + return false; + } + struct mScriptStorageContext* storage = value->value.opaque; + struct mScriptStorageBucket* bucket = mScriptStorageGetBucket(storage, bucketName); + return _mScriptStorageBucketFlushVF(bucket, vf); +} + +bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucketName) { + char path[PATH_MAX]; + mScriptStorageGetBucketPath(bucketName, path); + struct VFile* vf = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + return false; + } + return mScriptStorageSaveBucketVF(context, bucketName, vf); +} + +struct mScriptValue* mScriptStorageFromJson(struct json_object* json) { + enum json_type type = json_object_get_type(json); + struct mScriptValue* value = NULL; + switch (type) { + case json_type_null: + return &mScriptValueNull; + case json_type_int: + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S64); + value->value.s64 = json_object_get_int64(json); + break; + case json_type_double: + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_F64); + value->value.f64 = json_object_get_double(json); + break; + case json_type_boolean: + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_BOOL); + value->value.u32 = json_object_get_boolean(json); + break; + case json_type_string: + value = mScriptStringCreateFromBytes(json_object_get_string(json), json_object_get_string_len(json)); + break; + case json_type_array: + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST); + { + size_t i; + for (i = 0; i < json_object_array_length(json); ++i) { + struct mScriptValue* vval = mScriptStorageFromJson(json_object_array_get_idx(json, i)); + if (!vval) { + mScriptValueDeref(value); + value = NULL; + break; + } + mScriptValueWrap(vval, mScriptListAppend(value->value.list)); + mScriptValueDeref(vval); + } + } + break; + case json_type_object: + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + { + json_object_object_foreach(json, jkey, jval) { + struct mScriptValue* vval = mScriptStorageFromJson(jval); + if (!vval) { + mScriptValueDeref(value); + value = NULL; + break; + } + struct mScriptValue* vkey = mScriptStringCreateFromUTF8(jkey); + mScriptTableInsert(value, vkey, vval); + mScriptValueDeref(vkey); + mScriptValueDeref(vval); + } + } + break; + } + return value; +} + +static struct mScriptValue* _mScriptStorageLoadJson(struct VFile* vf) { + ssize_t size = vf->size(vf); + if (size < 2) { + vf->close(vf); + return NULL; + } + char* json = calloc(1, size + 1); + if (vf->read(vf, json, size) != size) { + vf->close(vf); + return NULL; + } + vf->close(vf); + + struct json_object* obj = json_tokener_parse(json); + free(json); + if (!obj) { + return NULL; + } + + struct mScriptValue* root = mScriptStorageFromJson(obj); + json_object_put(obj); + return root; +} + +bool mScriptStorageBucketReload(struct mScriptStorageBucket* bucket) { + char path[PATH_MAX]; + mScriptStorageGetBucketPath(bucket->name, path); + struct VFile* vf = VFileOpen(path, O_RDONLY); + if (!vf) { + return false; + } + struct mScriptValue* root = _mScriptStorageLoadJson(vf); + if (!root) { + return false; + } + if (bucket->root) { + mScriptValueDeref(bucket->root); + } + bucket->root = root; + + bucket->dirty = false; + + return true; +} + +bool mScriptStorageLoadBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); + if (!value) { + vf->close(vf); + return false; + } + struct mScriptStorageContext* storage = value->value.opaque; + struct mScriptValue* root = _mScriptStorageLoadJson(vf); + if (!root) { + return false; + } + struct mScriptStorageBucket* bucket = mScriptStorageGetBucket(storage, bucketName); + mScriptValueDeref(bucket->root); + bucket->root = root; + + bucket->dirty = false; + + return true; +} + +bool mScriptStorageLoadBucket(struct mScriptContext* context, const char* bucketName) { + char path[PATH_MAX]; + mScriptStorageGetBucketPath(bucketName, path); + struct VFile* vf = VFileOpen(path, O_RDONLY); + if (!vf) { + return false; + } + return mScriptStorageLoadBucketVF(context, bucketName, vf); +} + +void mScriptContextAttachStorage(struct mScriptContext* context) { + struct mScriptStorageContext* storage = calloc(1, sizeof(*storage)); + struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptStorageContext)); + value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER; + value->value.opaque = storage; + + HashTableInit(&storage->buckets, 0, mScriptStorageBucketDeinit); + + mScriptContextSetGlobal(context, "storage", value); + mScriptContextSetDocstring(context, "storage", "Singleton instance of struct::mScriptStorageContext"); +} + +void mScriptStorageFlushAll(struct mScriptContext* context) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); + if (!value) { + return; + } + struct mScriptStorageContext* storage = value->value.opaque; + mScriptStorageContextFlushAll(storage); +} + +void mScriptStorageContextDeinit(struct mScriptStorageContext* storage) { + HashTableDeinit(&storage->buckets); +} + +void mScriptStorageContextFlushAll(struct mScriptStorageContext* storage) { + struct TableIterator iter; + if (HashTableIteratorStart(&storage->buckets, &iter)) { + do { + struct mScriptStorageBucket* bucket = HashTableIteratorGetValue(&storage->buckets, &iter); + if (bucket->autoflush) { + mScriptStorageBucketFlush(bucket); + } + } while (HashTableIteratorNext(&storage->buckets, &iter)); + } +} + +struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext* storage, const char* name) { + if (!name) { + return NULL; + } + + // Check if name is allowed + // Currently only names matching /[0-9A-Za-z][0-9A-Za-z_.]*/ are allowed + size_t i; + for (i = 0; name[i]; ++i) { + if (i >= STORAGE_LEN_MAX) { + return NULL; + } + if (isalnum(name[i])) { + continue; + } + if (name[i] == '_') { + continue; + } + if (i > 0 && name[i] == '.') { + continue; + } + return NULL; + } + struct mScriptStorageBucket* bucket = HashTableLookup(&storage->buckets, name); + if (bucket) { + return bucket; + } + + bucket = calloc(1, sizeof(*bucket)); + bucket->name = strdup(name); + bucket->autoflush = true; + if (!mScriptStorageBucketReload(bucket)) { + bucket->root = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + } + HashTableInsert(&storage->buckets, name, bucket); + return bucket; +} + +void mScriptStorageBucketDeinit(void* data) { + struct mScriptStorageBucket* bucket = data; + if (bucket->dirty) { + mScriptStorageBucketFlush(bucket); + } + mScriptValueDeref(bucket->root); + free(bucket->name); + free(bucket); +} diff --git a/src/script/test/classes.c b/src/script/test/classes.c index 85afb627c..f7c5eb128 100644 --- a/src/script/test/classes.c +++ b/src/script/test/classes.c @@ -5,9 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "util/test/suite.h" -#include -#include -#include +#include struct TestA { int32_t i; @@ -46,6 +44,19 @@ struct TestF { int* ref; }; +struct TestG { + const char* name; + int64_t s; + uint64_t u; + double f; + const char* c; +}; + +struct TestH { + int32_t i; + int32_t j; +}; + static int32_t testAi0(struct TestA* a) { return a->i; } @@ -83,6 +94,26 @@ static void testDeinit(struct TestF* f) { ++*f->ref; } +static void testSetS(struct TestG* g, const char* name, int64_t val) { + g->name = name; + g->s = val; +} + +static void testSetU(struct TestG* g, const char* name, uint64_t val) { + g->name = name; + g->u = val; +} + +static void testSetF(struct TestG* g, const char* name, double val) { + g->name = name; + g->f = val; +} + +static void testSetC(struct TestG* g, const char* name, const char* val) { + g->name = name; + g->c = val; +} + #define MEMBER_A_DOCSTRING "Member a" mSCRIPT_DECLARE_STRUCT(TestA); @@ -157,6 +188,25 @@ mSCRIPT_DEFINE_STRUCT(TestF) mSCRIPT_DEFINE_STRUCT_DEINIT(TestF) mSCRIPT_DEFINE_END; +mSCRIPT_DECLARE_STRUCT(TestG); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestG, setS, testSetS, 2, CHARP, name, S64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestG, setU, testSetU, 2, CHARP, name, U64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestG, setF, testSetF, 2, CHARP, name, F64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestG, setC, testSetC, 2, CHARP, name, CHARP, value); + +mSCRIPT_DEFINE_STRUCT(TestG) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setS) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setU) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setF) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setC) +mSCRIPT_DEFINE_END; + + +mSCRIPT_DEFINE_STRUCT(TestH) + mSCRIPT_DEFINE_STRUCT_MEMBER(TestH, S32, i) + mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(TestH, S32, j) +mSCRIPT_DEFINE_END; + M_TEST_DEFINE(testALayout) { struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestA)->details.cls; assert_false(cls->init); @@ -1032,6 +1082,108 @@ M_TEST_DEFINE(testFDeinit) { assert_false(cls->init); } +M_TEST_DEFINE(testGSet) { + struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestG)->details.cls; + + struct TestG s = { + }; + + assert_int_equal(s.s, 0); + assert_int_equal(s.u, 0); + assert_float_equal(s.f, 0, 0); + assert_null(s.c); + + struct mScriptValue sval = mSCRIPT_MAKE_S(TestG, &s); + struct mScriptValue val; + struct mScriptValue* pval; + + val = mSCRIPT_MAKE_S64(1); + assert_true(mScriptObjectSet(&sval, "a", &val)); + assert_int_equal(s.s, 1); + assert_string_equal(s.name, "a"); + + val = mSCRIPT_MAKE_U64(2); + assert_true(mScriptObjectSet(&sval, "b", &val)); + assert_int_equal(s.u, 2); + assert_string_equal(s.name, "b"); + + val = mSCRIPT_MAKE_F64(1.5); + assert_true(mScriptObjectSet(&sval, "c", &val)); + assert_float_equal(s.f, 1.5, 0); + assert_string_equal(s.name, "c"); + + val = mSCRIPT_MAKE_CHARP("hello"); + assert_true(mScriptObjectSet(&sval, "d", &val)); + assert_string_equal(s.c, "hello"); + assert_string_equal(s.name, "d"); + + val = mSCRIPT_MAKE_S32(3); + assert_true(mScriptObjectSet(&sval, "a", &val)); + assert_int_equal(s.s, 3); + + val = mSCRIPT_MAKE_S16(4); + assert_true(mScriptObjectSet(&sval, "a", &val)); + assert_int_equal(s.s, 4); + + val = mSCRIPT_MAKE_S8(5); + assert_true(mScriptObjectSet(&sval, "a", &val)); + assert_int_equal(s.s, 5); + + val = mSCRIPT_MAKE_BOOL(false); + assert_true(mScriptObjectSet(&sval, "a", &val)); + assert_int_equal(s.u, 0); + + pval = mScriptStringCreateFromASCII("goodbye"); + assert_true(mScriptObjectSet(&sval, "a", pval)); + assert_string_equal(s.c, "goodbye"); + mScriptValueDeref(pval); + + assert_true(cls->init); + mScriptClassDeinit(cls); + assert_false(cls->init); +} + +M_TEST_DEFINE(testHSet) { + struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestH)->details.cls; + + struct TestH s = { + .i = 1, + .j = 2, + }; + + struct mScriptValue sval = mSCRIPT_MAKE_S(TestH, &s); + struct mScriptValue val; + struct mScriptValue compare; + + compare = mSCRIPT_MAKE_S32(1); + assert_true(mScriptObjectGet(&sval, "i", &val)); + assert_true(compare.type->equal(&compare, &val)); + + compare = mSCRIPT_MAKE_S32(2); + assert_true(mScriptObjectGet(&sval, "j", &val)); + assert_true(compare.type->equal(&compare, &val)); + + val = mSCRIPT_MAKE_S32(3); + assert_true(mScriptObjectSet(&sval, "i", &val)); + assert_int_equal(s.i, 3); + + val = mSCRIPT_MAKE_S32(4); + assert_false(mScriptObjectSet(&sval, "j", &val)); + assert_int_equal(s.j, 2); + + compare = mSCRIPT_MAKE_S32(3); + assert_true(mScriptObjectGet(&sval, "i", &val)); + assert_true(compare.type->equal(&compare, &val)); + + compare = mSCRIPT_MAKE_S32(2); + assert_true(mScriptObjectGet(&sval, "j", &val)); + assert_true(compare.type->equal(&compare, &val)); + + assert_true(cls->init); + mScriptClassDeinit(cls); + assert_false(cls->init); +} + M_TEST_SUITE_DEFINE(mScriptClasses, cmocka_unit_test(testALayout), cmocka_unit_test(testASignatures), @@ -1047,4 +1199,6 @@ M_TEST_SUITE_DEFINE(mScriptClasses, cmocka_unit_test(testDSet), cmocka_unit_test(testEGet), cmocka_unit_test(testFDeinit), + cmocka_unit_test(testGSet), + cmocka_unit_test(testHSet), ) diff --git a/src/script/test/context.c b/src/script/test/context.c index 178dc25a7..da3ff7ea8 100644 --- a/src/script/test/context.c +++ b/src/script/test/context.c @@ -5,9 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "util/test/suite.h" -#include -#include -#include +#include M_TEST_DEFINE(weakrefBasic) { struct mScriptContext context; diff --git a/src/script/test/image.c b/src/script/test/image.c new file mode 100644 index 000000000..541f7a210 --- /dev/null +++ b/src/script/test/image.c @@ -0,0 +1,111 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include +#include + +#include "script/test.h" + +#define SETUP_LUA \ + struct mScriptContext context; \ + mScriptContextInit(&context); \ + struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA); \ + mScriptContextAttachStdlib(&context); \ + mScriptContextAttachImage(&context) + +M_TEST_SUITE_SETUP(mScriptImage) { + if (mSCRIPT_ENGINE_LUA->init) { + mSCRIPT_ENGINE_LUA->init(mSCRIPT_ENGINE_LUA); + } + return 0; +} + +M_TEST_SUITE_TEARDOWN(mScriptImage) { + if (mSCRIPT_ENGINE_LUA->deinit) { + mSCRIPT_ENGINE_LUA->deinit(mSCRIPT_ENGINE_LUA); + } + return 0; +} + +M_TEST_DEFINE(members) { + SETUP_LUA; + + TEST_PROGRAM("assert(image)"); + TEST_PROGRAM("assert(image.new)"); + TEST_PROGRAM("assert(image.load)"); + TEST_PROGRAM("im = image.new(1, 1)"); + TEST_PROGRAM("assert(im)"); + TEST_PROGRAM("assert(im.width == 1)"); + TEST_PROGRAM("assert(im.height == 1)"); + TEST_PROGRAM("assert(im.save)"); + TEST_PROGRAM("assert(im.save)"); + TEST_PROGRAM("assert(im.getPixel)"); + TEST_PROGRAM("assert(im.setPixel)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(zeroDim) { + SETUP_LUA; + + TEST_PROGRAM("im = image.new(0, 0)"); + TEST_PROGRAM("assert(not im)"); + TEST_PROGRAM("im = image.new(1, 0)"); + TEST_PROGRAM("assert(not im)"); + TEST_PROGRAM("im = image.new(0, 1)"); + TEST_PROGRAM("assert(not im)"); + TEST_PROGRAM("im = image.new(1, 1)"); + TEST_PROGRAM("assert(im)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(pixelColorDefault) { + SETUP_LUA; + + TEST_PROGRAM("im = image.new(1, 1)"); + TEST_PROGRAM("assert(im:getPixel(0, 0) == 0)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(pixelColorRoundTrip) { + SETUP_LUA; + + TEST_PROGRAM("im = image.new(1, 1)"); + TEST_PROGRAM("im:setPixel(0, 0, 0xFF123456)"); + TEST_PROGRAM("assert(im:getPixel(0, 0) == 0xFF123456)"); + + mScriptContextDeinit(&context); +} + +#ifdef USE_PNG +M_TEST_DEFINE(saveLoadRoundTrip) { + SETUP_LUA; + + unlink("tmp.png"); + TEST_PROGRAM("im = image.new(1, 1)"); + TEST_PROGRAM("im:setPixel(0, 0, 0xFF123456)"); + TEST_PROGRAM("assert(im:save('tmp.png'))"); + TEST_PROGRAM("im = image.load('tmp.png')"); + TEST_PROGRAM("assert(im)"); + TEST_PROGRAM("assert(im:getPixel(0, 0) == 0xFF123456)"); + unlink("tmp.png"); + + mScriptContextDeinit(&context); +} +#endif + +M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptImage, + cmocka_unit_test(members), + cmocka_unit_test(zeroDim), + cmocka_unit_test(pixelColorDefault), + cmocka_unit_test(pixelColorRoundTrip), +#ifdef USE_PNG + cmocka_unit_test(saveLoadRoundTrip), +#endif +) diff --git a/src/script/test/input.c b/src/script/test/input.c index de3ae0e0c..4c689a6ef 100644 --- a/src/script/test/input.c +++ b/src/script/test/input.c @@ -6,9 +6,7 @@ #include "util/test/suite.h" #include -#include -#include -#include +#include #include "script/test.h" @@ -109,6 +107,72 @@ M_TEST_DEFINE(fireKey) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(activeKeys) { + SETUP_LUA; + + TEST_PROGRAM("assert(#input:activeKeys() == 0)"); + + struct mScriptKeyEvent keyEvent = { + .d = { .type = mSCRIPT_EV_TYPE_KEY }, + .state = mSCRIPT_INPUT_STATE_DOWN, + .key = 'a' + }; + mScriptContextFireEvent(&context, &keyEvent.d); + TEST_PROGRAM("assert(#input:activeKeys() == 1)"); + TEST_PROGRAM("assert(input:activeKeys()[1] == string.byte('a'))"); + + keyEvent.state = mSCRIPT_INPUT_STATE_UP; + mScriptContextFireEvent(&context, &keyEvent.d); + + TEST_PROGRAM("assert(#input:activeKeys() == 0)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(clearKeys) { + SETUP_LUA; + + TEST_PROGRAM("assert(not input:isKeyActive('a'))"); + TEST_PROGRAM("assert(not input:isKeyActive('b'))"); + TEST_PROGRAM("assert(#input:activeKeys() == 0)"); + + struct mScriptKeyEvent keyEvent = { + .d = { .type = mSCRIPT_EV_TYPE_KEY }, + .state = mSCRIPT_INPUT_STATE_DOWN, + .key = 'a' + }; + mScriptContextFireEvent(&context, &keyEvent.d); + // This changes it to STATE_HELD, but increments the down counter + mScriptContextFireEvent(&context, &keyEvent.d); + keyEvent.state = mSCRIPT_INPUT_STATE_DOWN; + keyEvent.key = 'b'; + mScriptContextFireEvent(&context, &keyEvent.d); + + TEST_PROGRAM("assert(input:isKeyActive('a'))"); + TEST_PROGRAM("assert(input:isKeyActive('b'))"); + TEST_PROGRAM("assert(#input:activeKeys() == 2)"); + + TEST_PROGRAM( + "up = {}\n" + "function cb(ev)\n" + " assert(ev.type == C.EV_TYPE.KEY)\n" + " assert(ev.state == C.INPUT_STATE.UP)\n" + " table.insert(up, ev.key)\n" + "end\n" + "id = callbacks:add('key', cb)\n" + ); + + mScriptContextClearKeys(&context); + + TEST_PROGRAM("assert(not input:isKeyActive('a'))"); + TEST_PROGRAM("assert(not input:isKeyActive('b'))"); + TEST_PROGRAM("assert(#input:activeKeys() == 0)"); + TEST_PROGRAM("assert(#up == 2)"); + + mScriptContextDeinit(&context); +} + + M_TEST_DEFINE(gamepadExport) { SETUP_LUA; @@ -153,5 +217,7 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptInput, cmocka_unit_test(members), cmocka_unit_test(seq), cmocka_unit_test(fireKey), + cmocka_unit_test(activeKeys), + cmocka_unit_test(clearKeys), cmocka_unit_test(gamepadExport), ) diff --git a/src/script/test/lua.c b/src/script/test/lua.c index 5731b0705..49c5521d7 100644 --- a/src/script/test/lua.c +++ b/src/script/test/lua.c @@ -66,9 +66,14 @@ static int32_t sum(struct mScriptList* list) { return sum; } +static unsigned tableSize(struct Table* table) { + return TableSize(table); +} + mSCRIPT_BIND_FUNCTION(boundIdentityInt, S32, identityInt, 1, S32, a); mSCRIPT_BIND_FUNCTION(boundAddInts, S32, addInts, 2, S32, a, S32, b); mSCRIPT_BIND_FUNCTION(boundSum, S32, sum, 1, LIST, list); +mSCRIPT_BIND_FUNCTION(boundTableSize, U32, tableSize, 1, TABLE, table); mSCRIPT_DECLARE_STRUCT(Test); mSCRIPT_DECLARE_STRUCT_D_METHOD(Test, S32, ifn0, 0); @@ -371,13 +376,51 @@ M_TEST_DEFINE(callCFunc) { assert_true(a.type->equal(&a, val)); mScriptValueDeref(val); + LOAD_PROGRAM("b('a')"); + assert_false(lua->run(lua)); + mScriptContextDeinit(&context); } + +M_TEST_DEFINE(callCTable) { + SETUP_LUA; + + assert_true(lua->setGlobal(lua, "b", &boundTableSize)); + + TEST_PROGRAM("assert(b({}) == 0)"); + assert_null(lua->getError(lua)); + + TEST_PROGRAM("assert(b({[2]=1}) == 1)"); + assert_null(lua->getError(lua)); + + TEST_PROGRAM("assert(b({a=1}) == 1)"); + assert_null(lua->getError(lua)); + + TEST_PROGRAM("assert(b({a={}}) == 1)"); + assert_null(lua->getError(lua)); + + LOAD_PROGRAM( + "a = {}\n" + "a.b = a\n" + "assert(b(a) == 1)\n" + ); + assert_false(lua->run(lua)); + + LOAD_PROGRAM( + "a = {}\n" + "a.b = {}\n" + "a.b.c = a\n" + "assert(b(a) == 1)\n" + ); + assert_false(lua->run(lua)); + + mScriptContextDeinit(&context); +} + M_TEST_DEFINE(globalNull) { SETUP_LUA; struct Test s = {}; - struct mScriptValue* val; struct mScriptValue a; LOAD_PROGRAM("assert(a)"); @@ -764,6 +807,48 @@ M_TEST_DEFINE(linkedList) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(listConvert) { + SETUP_LUA; + + struct mScriptValue* list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST); + + assert_true(lua->setGlobal(lua, "l", list)); + TEST_PROGRAM("assert(l)"); + + struct mScriptValue* val = lua->getGlobal(lua, "l"); + assert_non_null(val); + if (val->type->base == mSCRIPT_TYPE_WRAPPER) { + val = mScriptValueUnwrap(val); + } + assert_ptr_equal(val->type, mSCRIPT_TYPE_MS_LIST); + assert_ptr_equal(val->value.list, list->value.list); + mScriptValueDeref(val); + mScriptValueDeref(list); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(tableConvert) { + SETUP_LUA; + + struct mScriptValue* list = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + + assert_true(lua->setGlobal(lua, "l", list)); + TEST_PROGRAM("assert(l)"); + + struct mScriptValue* val = lua->getGlobal(lua, "l"); + assert_non_null(val); + if (val->type->base == mSCRIPT_TYPE_WRAPPER) { + val = mScriptValueUnwrap(val); + } + assert_ptr_equal(val->type, mSCRIPT_TYPE_MS_TABLE); + assert_ptr_equal(val->value.table, list->value.table); + mScriptValueDeref(val); + mScriptValueDeref(list); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua, cmocka_unit_test(create), cmocka_unit_test(loadGood), @@ -774,6 +859,7 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua, cmocka_unit_test(rootScope), cmocka_unit_test(callLuaFunc), cmocka_unit_test(callCFunc), + cmocka_unit_test(callCTable), cmocka_unit_test(globalNull), cmocka_unit_test(globalStructFieldGet), cmocka_unit_test(globalStructFieldSet), @@ -782,5 +868,7 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua, cmocka_unit_test(tableLookup), cmocka_unit_test(tableIterate), cmocka_unit_test(callList), - cmocka_unit_test(linkedList) + cmocka_unit_test(linkedList), + cmocka_unit_test(listConvert), + cmocka_unit_test(tableConvert), ) diff --git a/src/script/test/stdlib.c b/src/script/test/stdlib.c index 82a4c425b..eda6deb7b 100644 --- a/src/script/test/stdlib.c +++ b/src/script/test/stdlib.c @@ -6,9 +6,7 @@ #include "util/test/suite.h" #include -#include -#include -#include +#include #include "script/test.h" @@ -96,8 +94,85 @@ M_TEST_DEFINE(callbacks) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(oneshot) { + SETUP_LUA; + + TEST_PROGRAM( + "val = 0\n" + "function cb()\n" + " val = val + 1\n" + "end\n" + "id = callbacks:oneshot('test', cb)\n" + "assert(id)" + ); + + TEST_VALUE(S32, "val", 0); + + mScriptContextTriggerCallback(&context, "test", NULL); + TEST_VALUE(S32, "val", 1); + + mScriptContextTriggerCallback(&context, "test", NULL); + TEST_VALUE(S32, "val", 1); + + TEST_PROGRAM( + "id = callbacks:oneshot('test', cb)\n" + "assert(id)\n" + "callbacks:remove(id)" + ); + + mScriptContextTriggerCallback(&context, "test", NULL); + TEST_VALUE(S32, "val", 1); + + mScriptContextDeinit(&context); +} + +static void _tableIncrement(struct mScriptValue* table) { + assert_non_null(table); + struct mScriptValue* value = mScriptTableLookup(table, &mSCRIPT_MAKE_CHARP("key")); + assert_non_null(value); + assert_ptr_equal(value->type, mSCRIPT_TYPE_MS_S32); + ++value->value.s32; +} + +mSCRIPT_BIND_VOID_FUNCTION(tableIncrement, _tableIncrement, 1, WTABLE, table); + +M_TEST_DEFINE(callbackWeakref) { + SETUP_LUA; + + struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + struct mScriptList args; + mScriptListInit(&args, 1); + mScriptValueWrap(table, mScriptListAppend(&args)); + struct mScriptValue* lambda = mScriptLambdaCreate0(&tableIncrement, &args); + mScriptListDeinit(&args); + struct mScriptValue* weakref = mScriptContextMakeWeakref(&context, lambda); + mScriptContextAddCallback(&context, "test", weakref); + + struct mScriptValue* key = mScriptStringCreateFromUTF8("key"); + struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S32); + value->value.s32 = 1; + mScriptTableInsert(table, key, value); + + mScriptContextTriggerCallback(&context, "test", NULL); + assert_int_equal(value->value.s32, 2); + + mScriptContextClearWeakref(&context, weakref->value.u32); + mScriptValueDeref(weakref); + + mScriptContextTriggerCallback(&context, "test", NULL); + assert_int_equal(value->value.s32, 2); + + mScriptValueDeref(table); + mScriptValueDeref(key); + mScriptValueDeref(value); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStdlib, cmocka_unit_test(bitMask), cmocka_unit_test(bitUnmask), cmocka_unit_test(callbacks), + cmocka_unit_test(oneshot), + cmocka_unit_test(callbackWeakref), ) diff --git a/src/script/test/storage.c b/src/script/test/storage.c new file mode 100644 index 000000000..14851318a --- /dev/null +++ b/src/script/test/storage.c @@ -0,0 +1,584 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include +#include + +#include "script/test.h" + +#define SETUP_LUA \ + struct mScriptContext context; \ + mScriptContextInit(&context); \ + struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA); \ + mScriptContextAttachStdlib(&context); \ + mScriptContextAttachStorage(&context); \ + char bucketPath[PATH_MAX]; \ + mScriptStorageGetBucketPath("xtest", bucketPath); \ + remove(bucketPath) + +M_TEST_SUITE_SETUP(mScriptStorage) { + if (mSCRIPT_ENGINE_LUA->init) { + mSCRIPT_ENGINE_LUA->init(mSCRIPT_ENGINE_LUA); + } + return 0; +} + +M_TEST_SUITE_TEARDOWN(mScriptStorage) { + if (mSCRIPT_ENGINE_LUA->deinit) { + mSCRIPT_ENGINE_LUA->deinit(mSCRIPT_ENGINE_LUA); + } + return 0; +} + +M_TEST_DEFINE(basicInt) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = 1"); + TEST_PROGRAM("assert(bucket.a == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicFloat) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = 0.5"); + TEST_PROGRAM("assert(bucket.a == 0.5)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicBool) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = true"); + TEST_PROGRAM("assert(bucket.a == true)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicNil) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = nil"); + TEST_PROGRAM("assert(bucket.a == nil)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = 'hello'"); + TEST_PROGRAM("assert(bucket.a == 'hello')"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicList) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = {1}"); + TEST_PROGRAM("assert(#bucket.a == 1)"); + TEST_PROGRAM("assert(bucket.a[1] == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicTable) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = {['a']=1}"); + TEST_PROGRAM("assert(#bucket.a == 1)"); + TEST_PROGRAM("assert(bucket.a.a == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(nullByteString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = 'a\\x00b'"); + TEST_PROGRAM("assert(bucket.a == 'a\\x00b')"); + TEST_PROGRAM("assert(#bucket.a == 3)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(structured) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM( + "bucket.a = {\n" + " ['a'] = 1,\n" + " ['b'] = {1},\n" + " ['c'] = {\n" + " ['d'] = 1\n" + " }\n" + "}" + ); + TEST_PROGRAM("assert(bucket.a)"); + TEST_PROGRAM("assert(bucket.a.a == 1)"); + TEST_PROGRAM("assert(#bucket.a.b == 1)"); + TEST_PROGRAM("assert(bucket.a.b[1] == 1)"); + TEST_PROGRAM("assert(#bucket.a.c == 1)"); + TEST_PROGRAM("assert(bucket.a.c.d == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(invalidObject) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + LOAD_PROGRAM("bucket.a = bucket"); + assert_false(lua->run(lua)); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeInt) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = 1"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\n\t\"a\":1\n}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeFloat) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = 0.5"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\n\t\"a\":0.5\n}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeBool) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = true"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\n\t\"a\":true\n}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeNil) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = nil"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\n\t\"a\":null\n}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = 'hello'"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\n\t\"a\":\"hello\"\n}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeList) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = {1, 2}"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\n\t\"a\":[\n\t\t1,\n\t\t2\n\t]\n}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeTable) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = {['b']=1}"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\n\t\"a\":{\n\t\t\"b\":1\n\t}\n}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeNullByteString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = 'a\\x00b'"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\n\t\"a\":\"a\\u0000b\"\n}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeInt) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":1}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeFloat) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":0.5}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == 0.5)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeBool) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":true}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == true)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeNil) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":null}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == nil)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":\"hello\"}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == 'hello')"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeList) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":[1,2]}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(#bucket.a == 2)"); + TEST_PROGRAM("assert(bucket.a[1] == 1)"); + TEST_PROGRAM("assert(bucket.a[2] == 2)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeTable) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":{\"b\":1}}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a.b == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeNullByteString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":\"a\\u0000b\"}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == 'a\\x00b')"); + TEST_PROGRAM("assert(bucket.a ~= 'a\\x00c')"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeError) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{a:1}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_false(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(not bucket.a)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(structuredRoundTrip) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM( + "bucket.a = {\n" + " ['a'] = 1,\n" + " ['b'] = {1},\n" + " ['c'] = {\n" + " ['d'] = 1\n" + " }\n" + "}" + ); + + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + + TEST_PROGRAM("bucket.a = nil") + TEST_PROGRAM("assert(not bucket.a)"); + + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + + TEST_PROGRAM("assert(bucket.a)"); + TEST_PROGRAM("assert(bucket.a.a == 1)"); + TEST_PROGRAM("assert(#bucket.a.b == 1)"); + TEST_PROGRAM("assert(bucket.a.b[1] == 1)"); + TEST_PROGRAM("assert(#bucket.a.c == 1)"); + TEST_PROGRAM("assert(bucket.a.c.d == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(autoflush) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + + TEST_PROGRAM("bucket:enableAutoFlush(true)") + TEST_PROGRAM("bucket.a = 1"); + TEST_PROGRAM("storage:flushAll()"); + TEST_PROGRAM("assert(bucket:reload())") + TEST_PROGRAM("assert(bucket.a == 1)"); + + TEST_PROGRAM("bucket:enableAutoFlush(false)") + TEST_PROGRAM("bucket.a = 2"); + TEST_PROGRAM("storage:flushAll()"); + TEST_PROGRAM("assert(bucket:reload())") + TEST_PROGRAM("assert(bucket.a == 1)"); + + TEST_PROGRAM("bucket:enableAutoFlush(false)") + TEST_PROGRAM("bucket.a = 3"); + TEST_PROGRAM("storage:flushAll()"); + TEST_PROGRAM("bucket:enableAutoFlush(true)") + TEST_PROGRAM("storage:flushAll()"); + TEST_PROGRAM("assert(bucket:reload())") + TEST_PROGRAM("assert(bucket.a == 3)"); + + mScriptContextDeinit(&context); +} + +M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, + cmocka_unit_test(basicInt), + cmocka_unit_test(basicFloat), + cmocka_unit_test(basicBool), + cmocka_unit_test(basicNil), + cmocka_unit_test(basicString), + cmocka_unit_test(basicList), + cmocka_unit_test(basicTable), + cmocka_unit_test(nullByteString), + cmocka_unit_test(invalidObject), + cmocka_unit_test(structured), + cmocka_unit_test(serializeInt), + cmocka_unit_test(serializeFloat), + cmocka_unit_test(serializeBool), + cmocka_unit_test(serializeNil), + cmocka_unit_test(serializeString), + cmocka_unit_test(serializeList), + cmocka_unit_test(serializeTable), + cmocka_unit_test(serializeNullByteString), + cmocka_unit_test(deserializeInt), + cmocka_unit_test(deserializeFloat), + cmocka_unit_test(deserializeBool), + cmocka_unit_test(deserializeNil), + cmocka_unit_test(deserializeString), + cmocka_unit_test(deserializeList), + cmocka_unit_test(deserializeTable), + cmocka_unit_test(deserializeNullByteString), + cmocka_unit_test(deserializeError), + cmocka_unit_test(structuredRoundTrip), + cmocka_unit_test(autoflush), +) diff --git a/src/script/test/types.c b/src/script/test/types.c index ae1df5ba0..5c66b3f76 100644 --- a/src/script/test/types.c +++ b/src/script/test/types.c @@ -5,9 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "util/test/suite.h" -#include -#include -#include +#include struct Test { int32_t a; @@ -84,6 +82,10 @@ static bool isNullStruct(struct Test* arg) { return !arg; } +static void increment(struct Test* t) { + ++t->a; +} + mSCRIPT_BIND_FUNCTION(boundVoidOne, S32, voidOne, 0); mSCRIPT_BIND_VOID_FUNCTION(boundDiscard, discard, 1, S32, ignored); mSCRIPT_BIND_FUNCTION(boundIdentityInt, S32, identityInt, 1, S32, in); @@ -96,6 +98,13 @@ mSCRIPT_BIND_FUNCTION(boundIsHello, S32, isHello, 1, CHARP, str); mSCRIPT_BIND_FUNCTION(boundIsSequential, S32, isSequential, 1, LIST, list); mSCRIPT_BIND_FUNCTION(boundIsNullCharp, BOOL, isNullCharp, 1, CHARP, arg); mSCRIPT_BIND_FUNCTION(boundIsNullStruct, BOOL, isNullStruct, 1, S(Test), arg); +mSCRIPT_BIND_FUNCTION_WITH_DEFAULTS(boundAddIntWithDefaults, S32, addInts, 2, S32, a, S32, b); +mSCRIPT_BIND_VOID_FUNCTION(boundIncrement, increment, 1, S(Test), this); + +mSCRIPT_DEFINE_FUNCTION_BINDING_DEFAULTS(boundAddIntWithDefaults) + mSCRIPT_NO_DEFAULT, + mSCRIPT_S32(0) +mSCRIPT_DEFINE_DEFAULTS_END; M_TEST_DEFINE(voidArgs) { struct mScriptFrame frame; @@ -172,6 +181,30 @@ M_TEST_DEFINE(addS32) { mScriptFrameDeinit(&frame); } +M_TEST_DEFINE(addS32Defaults) { + struct mScriptFrame frame; + int32_t val; + + mScriptFrameInit(&frame); + mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.arguments, S32, 2); + assert_true(mScriptInvoke(&boundAddIntWithDefaults, &frame)); + assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_int_equal(val, 3); + mScriptFrameDeinit(&frame); + + mScriptFrameInit(&frame); + mSCRIPT_PUSH(&frame.arguments, S32, 1); + assert_true(mScriptInvoke(&boundAddIntWithDefaults, &frame)); + assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_int_equal(val, 1); + mScriptFrameDeinit(&frame); + + mScriptFrameInit(&frame); + assert_false(mScriptInvoke(&boundAddIntWithDefaults, &frame)); + mScriptFrameDeinit(&frame); +} + M_TEST_DEFINE(subS32) { struct mScriptFrame frame; mScriptFrameInit(&frame); @@ -1308,6 +1341,28 @@ M_TEST_DEFINE(nullStruct) { mScriptFrameDeinit(&frame); } +M_TEST_DEFINE(lambda0) { + struct mScriptList args; + struct Test t = { + .a = 0 + }; + + mScriptListInit(&args, 1); + mSCRIPT_PUSH(&args, S(Test), &t); + struct mScriptValue* fn = mScriptLambdaCreate0(&boundIncrement, &args); + assert_non_null(fn); + mScriptListDeinit(&args); + + struct mScriptFrame frame; + mScriptFrameInit(&frame); + assert_int_equal(t.a, 0); + assert_true(mScriptInvoke(fn, &frame)); + assert_int_equal(t.a, 1); + mScriptFrameDeinit(&frame); + + mScriptValueDeref(fn); +} + M_TEST_SUITE_DEFINE(mScript, cmocka_unit_test(voidArgs), cmocka_unit_test(voidFunc), @@ -1316,6 +1371,7 @@ M_TEST_SUITE_DEFINE(mScript, cmocka_unit_test(identityFunctionF32), cmocka_unit_test(identityFunctionStruct), cmocka_unit_test(addS32), + cmocka_unit_test(addS32Defaults), cmocka_unit_test(subS32), cmocka_unit_test(wrongArgCountLo), cmocka_unit_test(wrongArgCountHi), @@ -1344,4 +1400,5 @@ M_TEST_SUITE_DEFINE(mScript, cmocka_unit_test(invokeList), cmocka_unit_test(nullString), cmocka_unit_test(nullStruct), + cmocka_unit_test(lambda0), ) diff --git a/src/script/types.c b/src/script/types.c index 0237cd4cb..16ef78df4 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -12,6 +12,11 @@ #include #include +struct mScriptLambda { + struct mScriptValue* fn; + struct mScriptList arguments; +}; + static void _allocList(struct mScriptValue*); static void _freeList(struct mScriptValue*); @@ -27,6 +32,10 @@ static bool _stringCast(const struct mScriptValue*, const struct mScriptType*, s static bool _castScalar(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*); static uint32_t _hashScalar(const struct mScriptValue*); +static bool _wstrCast(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*); +static bool _wlistCast(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*); +static bool _wtableCast(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*); + static uint32_t _valHash(const void* val, size_t len, uint32_t seed); static bool _valEqual(const void* a, const void* b); static void* _valRef(void*); @@ -43,6 +52,10 @@ static bool _boolEqual(const struct mScriptValue*, const struct mScriptValue*); static bool _charpEqual(const struct mScriptValue*, const struct mScriptValue*); static bool _stringEqual(const struct mScriptValue*, const struct mScriptValue*); +static void _lambdaAlloc(struct mScriptValue* val); +static void _lambdaFree(struct mScriptValue* val); +static bool _callLambda0(struct mScriptFrame* frame, void* context); + const struct mScriptType mSTVoid = { .base = mSCRIPT_TYPE_VOID, .size = 0, @@ -232,6 +245,7 @@ const struct mScriptType mSTStringWrapper = { .alloc = NULL, .free = NULL, .hash = NULL, + .cast = _wstrCast, }; const struct mScriptType mSTListWrapper = { @@ -241,6 +255,17 @@ const struct mScriptType mSTListWrapper = { .alloc = NULL, .free = NULL, .hash = NULL, + .cast = _wlistCast, +}; + +const struct mScriptType mSTTableWrapper = { + .base = mSCRIPT_TYPE_WRAPPER, + .size = sizeof(struct mScriptValue), + .name = "wrapper table", + .alloc = NULL, + .free = NULL, + .hash = NULL, + .cast = _wtableCast, }; const struct mScriptType mSTWeakref = { @@ -252,6 +277,25 @@ const struct mScriptType mSTWeakref = { .hash = NULL, }; +const struct mScriptType mSTLambda0 = { + .base = mSCRIPT_TYPE_FUNCTION, + .size = sizeof(struct mScriptLambda), + .name = "lambda", + .details = { + .function = { + .parameters = { + .count = 0, + }, + .returnType = { + .count = 0, + }, + }, + }, + .alloc = _lambdaAlloc, + .free = _lambdaFree, + .hash = NULL, +}; + struct mScriptValue mScriptValueNull = { .type = &mSTVoid, .refs = mSCRIPT_VALUE_UNREF @@ -348,22 +392,43 @@ static uint32_t _hashString(const struct mScriptValue* val) { return hash32(buffer, size, 0); } -uint32_t _hashScalar(const struct mScriptValue* val) { - // From https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key - uint32_t x = 0; - switch (val->type->base) { - case mSCRIPT_TYPE_SINT: - x = val->value.s32; - break; - case mSCRIPT_TYPE_UINT: - default: - x = val->value.u32; - break; +bool _wstrCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) { + if (input->type->base != mSCRIPT_TYPE_WRAPPER) { + return false; } - x = ((x >> 16) ^ x) * 0x45D9F3B; - x = ((x >> 16) ^ x) * 0x45D9F3B; - x = (x >> 16) ^ x; - return x; + const struct mScriptValue* unwrapped = mScriptValueUnwrapConst(input); + if (unwrapped->type != mSCRIPT_TYPE_MS_STR) { + return false; + } + memcpy(output, input, sizeof(*output)); + output->type = type; + return true; +} + +bool _wlistCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) { + if (input->type->base != mSCRIPT_TYPE_WRAPPER) { + return false; + } + const struct mScriptValue* unwrapped = mScriptValueUnwrapConst(input); + if (unwrapped->type != mSCRIPT_TYPE_MS_LIST) { + return false; + } + memcpy(output, input, sizeof(*output)); + output->type = type; + return true; +} + +bool _wtableCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) { + if (input->type->base != mSCRIPT_TYPE_WRAPPER) { + return false; + } + const struct mScriptValue* unwrapped = mScriptValueUnwrapConst(input); + if (unwrapped->type != mSCRIPT_TYPE_MS_TABLE) { + return false; + } + memcpy(output, input, sizeof(*output)); + output->type = type; + return true; } #define AS(NAME, TYPE) \ @@ -464,6 +529,16 @@ bool _castScalar(const struct mScriptValue* input, const struct mScriptType* typ return true; } +uint32_t _hashScalar(const struct mScriptValue* val) { + // From https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key + uint32_t x = 0; + _asUInt32(val, &x); + x = ((x >> 16) ^ x) * 0x45D9F3B; + x = ((x >> 16) ^ x) * 0x45D9F3B; + x = (x >> 16) ^ x; + return x; +} + uint32_t _valHash(const void* val, size_t len, uint32_t seed) { UNUSED(len); const struct mScriptValue* value = val; @@ -779,6 +854,32 @@ bool _stringEqual(const struct mScriptValue* a, const struct mScriptValue* b) { return strncmp(valA, valB, lenA) == 0; } +void _lambdaAlloc(struct mScriptValue* value) { + struct mScriptLambda* lambda = calloc(1, sizeof(*lambda)); + struct mScriptFunction* fn = calloc(1, sizeof(*fn)); + fn->context = lambda; + mScriptListInit(&lambda->arguments, 0); + value->value.opaque = fn; +} + +void _lambdaFree(struct mScriptValue* value) { + struct mScriptFunction* fn = value->value.opaque; + struct mScriptLambda* lambda = fn->context; + size_t i; + for (i = 0; i < mScriptListSize(&lambda->arguments); ++i) { + struct mScriptValue* val = mScriptListGetPointer(&lambda->arguments, i); + if (val->type->base != mSCRIPT_TYPE_WRAPPER) { + continue; + } + val = mScriptValueUnwrap(val); + mScriptValueDeref(val); + } + mScriptListDeinit(&lambda->arguments); + mScriptValueDeref(lambda->fn); + free(lambda); + free(fn); +} + struct mScriptValue* mScriptValueAlloc(const struct mScriptType* type) { // TODO: Use an arena instead of just the generic heap struct mScriptValue* val = malloc(sizeof(*val)); @@ -837,7 +938,6 @@ void mScriptValueWrap(struct mScriptValue* value, struct mScriptValue* out) { out->type = mSCRIPT_TYPE_MS_WRAPPER; out->value.opaque = value; - mScriptValueRef(value); } struct mScriptValue* mScriptValueUnwrap(struct mScriptValue* value) { @@ -944,14 +1044,11 @@ bool mScriptTableRemove(struct mScriptValue* table, struct mScriptValue* key) { } struct mScriptValue* mScriptTableLookup(struct mScriptValue* table, struct mScriptValue* key) { - if (table->type->base == mSCRIPT_TYPE_WRAPPER) { - table = mScriptValueUnwrap(table); - } if (table->type != mSCRIPT_TYPE_MS_TABLE) { - return false; + return NULL; } if (!key->type->hash) { - return false; + return NULL; } return HashTableLookupCustom(table->value.table, key); } @@ -1031,6 +1128,44 @@ void mScriptFrameDeinit(struct mScriptFrame* frame) { mScriptListDeinit(&frame->arguments); } +struct mScriptValue* mScriptLambdaCreate0(struct mScriptValue* fn, struct mScriptList* args) { + struct mScriptValue* value = mScriptValueAlloc(&mSTLambda0); + struct mScriptFunction* lfn = value->value.opaque; + struct mScriptLambda* lambda = lfn->context; + lfn->call = _callLambda0; + lambda->fn = fn; + mScriptValueRef(fn); + if (args) { + mScriptListCopy(&lambda->arguments, args); + size_t i; + for (i = 0; i < mScriptListSize(args); ++i) { + struct mScriptValue* val = mScriptListGetPointer(args, i); + if (val->type->base != mSCRIPT_TYPE_WRAPPER) { + continue; + } + val = mScriptValueUnwrap(val); + mScriptValueRef(val); + } + } + return value; +} + +bool _callLambda0(struct mScriptFrame* frame, void* context) { + if (mScriptListSize(&frame->arguments)) { + return false; + } + struct mScriptLambda* lambda = context; + struct mScriptFrame subframe; + mScriptFrameInit(&subframe); + mScriptListCopy(&subframe.arguments, &lambda->arguments); + bool ok = mScriptInvoke(lambda->fn, &subframe); + if (mScriptListSize(&subframe.returnValues)) { + ok = false; + } + mScriptFrameDeinit(&subframe); + return ok; +} + static void _mScriptClassInit(struct mScriptTypeClass* cls, const struct mScriptClassInitDetails* details, bool child) { const char* docstring = NULL; @@ -1097,12 +1232,16 @@ static void _mScriptClassInit(struct mScriptTypeClass* cls, const struct mScript } break; case mSCRIPT_CLASS_INIT_SET: - cls->set = calloc(1, sizeof(*member)); - memcpy(cls->set, &detail->info.member, sizeof(*member)); + member = calloc(1, sizeof(*member)); + memcpy(member, &detail->info.member, sizeof(*member)); if (docstring) { - cls->set->docstring = docstring; + member->docstring = docstring; docstring = NULL; } + if (detail->info.member.type->details.function.parameters.count != 3) { + abort(); + } + HashTableInsert(&cls->setters, detail->info.member.type->details.function.parameters.entries[2]->name, member); break; case mSCRIPT_CLASS_INIT_INTERNAL: cls->internal = true; @@ -1117,11 +1256,11 @@ void mScriptClassInit(struct mScriptTypeClass* cls) { } HashTableInit(&cls->instanceMembers, 0, free); HashTableInit(&cls->castToMembers, 0, NULL); + HashTableInit(&cls->setters, 0, free); cls->alloc = NULL; cls->free = NULL; cls->get = NULL; - cls->set = NULL; _mScriptClassInit(cls, cls->details, false); cls->init = true; @@ -1133,6 +1272,7 @@ void mScriptClassDeinit(struct mScriptTypeClass* cls) { } HashTableDeinit(&cls->instanceMembers); HashTableDeinit(&cls->castToMembers); + HashTableDeinit(&cls->setters); cls->init = false; } @@ -1265,7 +1405,7 @@ bool mScriptObjectGet(struct mScriptValue* obj, const char* member, struct mScri this->type = obj->type; this->refs = mSCRIPT_VALUE_UNREF; this->flags = 0; - this->value.opaque = obj; + this->value.opaque = obj->value.opaque; mSCRIPT_PUSH(&frame.arguments, CHARP, member); if (!mScriptInvoke(&getMember, &frame) || mScriptListSize(&frame.returnValues) != 1) { mScriptFrameDeinit(&frame); @@ -1300,6 +1440,91 @@ bool mScriptObjectGetConst(const struct mScriptValue* obj, const char* member, s return _accessRawMember(m, obj->value.opaque, true, val); } +static struct mScriptClassMember* _findSetter(const struct mScriptTypeClass* cls, const struct mScriptType* type) { + struct mScriptClassMember* m = HashTableLookup(&cls->setters, type->name); + if (m) { + return m; + } + + switch (type->base) { + case mSCRIPT_TYPE_SINT: + if (type->size < 2) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_S16->name); + if (m) { + return m; + } + } + if (type->size < 4) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_S32->name); + if (m) { + return m; + } + } + if (type->size < 8) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_S64->name); + if (m) { + return m; + } + } + break; + case mSCRIPT_TYPE_UINT: + if (type->size < 2) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_U16->name); + if (m) { + return m; + } + } + if (type->size < 4) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_U32->name); + if (m) { + return m; + } + } + if (type->size < 8) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_U64->name); + if (m) { + return m; + } + } + break; + case mSCRIPT_TYPE_FLOAT: + if (type->size < 8) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_F64->name); + if (m) { + return m; + } + } + break; + case mSCRIPT_TYPE_STRING: + if (type == mSCRIPT_TYPE_MS_STR) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_CHARP->name); + if (m) { + return m; + } + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_WSTR->name); + if (m) { + return m; + } + } + break; + case mSCRIPT_TYPE_LIST: + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_WLIST->name); + if (m) { + return m; + } + break; + case mSCRIPT_TYPE_TABLE: + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_WTABLE->name); + if (m) { + return m; + } + break; + default: + break; + } + return NULL; +} + bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScriptValue* val) { if (obj->type->base != mSCRIPT_TYPE_OBJECT || obj->type->isConst) { return false; @@ -1314,6 +1539,32 @@ bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScri struct mScriptClassMember* m = HashTableLookup(&cls->instanceMembers, member); if (!m) { + if (val->type->base == mSCRIPT_TYPE_WRAPPER) { + val = mScriptValueUnwrap(val); + } + struct mScriptValue setMember; + m = _findSetter(cls, val->type); + if (!m || !_accessRawMember(m, obj->value.opaque, obj->type->isConst, &setMember)) { + return false; + } + struct mScriptFrame frame; + mScriptFrameInit(&frame); + struct mScriptValue* this = mScriptListAppend(&frame.arguments); + this->type = obj->type; + this->refs = mSCRIPT_VALUE_UNREF; + this->flags = 0; + this->value.opaque = obj->value.opaque; + mSCRIPT_PUSH(&frame.arguments, CHARP, member); + mScriptValueWrap(val, mScriptListAppend(&frame.arguments)); + if (!mScriptInvoke(&setMember, &frame) || mScriptListSize(&frame.returnValues) != 0) { + mScriptFrameDeinit(&frame); + return false; + } + mScriptFrameDeinit(&frame); + return true; + } + + if (m->readonly) { return false; } @@ -1409,7 +1660,7 @@ void mScriptObjectFree(struct mScriptValue* value) { if (value->type->base != mSCRIPT_TYPE_OBJECT) { return; } - if (value->flags & mSCRIPT_VALUE_FLAG_FREE_BUFFER) { + if (value->flags & (mSCRIPT_VALUE_FLAG_DEINIT | mSCRIPT_VALUE_FLAG_FREE_BUFFER)) { mScriptClassInit(value->type->details.cls); if (value->type->details.cls->free) { struct mScriptValue deinitMember; @@ -1425,10 +1676,46 @@ void mScriptObjectFree(struct mScriptValue* value) { mScriptFrameDeinit(&frame); } } + } + if (value->flags & mSCRIPT_VALUE_FLAG_FREE_BUFFER) { free(value->value.opaque); } } +struct mScriptValue* mScriptObjectBindLambda(struct mScriptValue* obj, const char* member, struct mScriptList* args) { + if (obj->type->base != mSCRIPT_TYPE_OBJECT) { + return false; + } + + struct mScriptTypeClass* cls = obj->type->details.cls; + if (!cls) { + return false; + } + + mScriptClassInit(cls); + + struct mScriptList arguments; + struct mScriptValue fn; + if (!mScriptObjectGetConst(obj, member, &fn)) { + return NULL; + } + + mScriptListInit(&arguments, 0); + mScriptValueWrap(obj, mScriptListAppend(&arguments)); + if (args) { + size_t i; + for (i = 0; i < mScriptListSize(args); ++i) { + memcpy(mScriptListAppend(&arguments), mScriptListGetConstPointer(args, i), sizeof(struct mScriptValue)); + } + } + + struct mScriptValue* value = mScriptValueAlloc(fn.type); + struct mScriptValue* lambda = mScriptLambdaCreate0(value, &arguments); + mScriptValueDeref(value); + mScriptListDeinit(&arguments); + return lambda; +} + bool mScriptPopS32(struct mScriptList* list, int32_t* out) { mSCRIPT_POP(list, S32, val); *out = val; @@ -1478,7 +1765,7 @@ bool mScriptPopPointer(struct mScriptList* list, void** out) { } bool mScriptCast(const struct mScriptType* type, const struct mScriptValue* input, struct mScriptValue* output) { - if (input->type->base == mSCRIPT_TYPE_WRAPPER) { + if (input->type->base == mSCRIPT_TYPE_WRAPPER && type->base != mSCRIPT_TYPE_WRAPPER) { input = mScriptValueUnwrapConst(input); } if (type->cast && type->cast(input, type, output)) { diff --git a/src/sm83/debugger/cli-debugger.c b/src/sm83/debugger/cli-debugger.c index dfc56dc23..fd5a3084d 100644 --- a/src/sm83/debugger/cli-debugger.c +++ b/src/sm83/debugger/cli-debugger.c @@ -26,7 +26,7 @@ static inline void _printFlags(struct CLIDebuggerBackend* be, union FlagRegister } static void _disassemble(struct CLIDebuggerSystem* debugger, struct CLIDebugVector* dv) { - struct SM83Core* cpu = debugger->p->d.core->cpu; + struct SM83Core* cpu = debugger->p->d.p->core->cpu; uint16_t address; int segment = -1; @@ -64,7 +64,7 @@ static inline uint16_t _printLine(struct CLIDebugger* debugger, uint16_t address uint8_t instruction; size_t bytesRemaining = 1; for (bytesRemaining = 1; bytesRemaining; --bytesRemaining) { - instruction = debugger->d.core->rawRead8(debugger->d.core, address, segment); + instruction = debugger->d.p->core->rawRead8(debugger->d.p->core, address, segment); disPtr += snprintf(disPtr, sizeof(disassembly) - (disPtr - disassembly), "%02X", instruction); ++address; bytesRemaining += SM83Decode(instruction, &info); @@ -78,16 +78,16 @@ static inline uint16_t _printLine(struct CLIDebugger* debugger, uint16_t address static void _printStatus(struct CLIDebuggerSystem* debugger) { struct CLIDebuggerBackend* be = debugger->p->backend; - struct SM83Core* cpu = debugger->p->d.core->cpu; + struct SM83Core* cpu = debugger->p->d.p->core->cpu; be->printf(be, "A: %02X F: %02X (AF: %04X)\n", cpu->a, cpu->f.packed, cpu->af); be->printf(be, "B: %02X C: %02X (BC: %04X)\n", cpu->b, cpu->c, cpu->bc); be->printf(be, "D: %02X E: %02X (DE: %04X)\n", cpu->d, cpu->e, cpu->de); be->printf(be, "H: %02X L: %02X (HL: %04X)\n", cpu->h, cpu->l, cpu->hl); be->printf(be, "PC: %04X SP: %04X\n", cpu->pc, cpu->sp); _printFlags(be, cpu->f); - be->printf(be, "T-cycle: %" PRIu64 "\n", mTimingGlobalTime(debugger->p->d.core->timing)); + be->printf(be, "T-cycle: %" PRIu64 "\n", mTimingGlobalTime(debugger->p->d.p->core->timing)); - struct SM83Debugger* platDebugger = (struct SM83Debugger*) debugger->p->d.platform; + struct SM83Debugger* platDebugger = (struct SM83Debugger*) debugger->p->d.p->platform; size_t i; for (i = 0; platDebugger->segments[i].name; ++i) { be->printf(be, "%s%s: %02X", i ? " " : "", platDebugger->segments[i].name, cpu->memory.currentSegment(cpu, platDebugger->segments[i].start)); diff --git a/src/sm83/debugger/debugger.c b/src/sm83/debugger/debugger.c index f6a6f690e..d24a75463 100644 --- a/src/sm83/debugger/debugger.c +++ b/src/sm83/debugger/debugger.c @@ -25,16 +25,18 @@ static struct mBreakpoint* _lookupBreakpoint(struct mBreakpointList* breakpoints return NULL; } -static void _destroyBreakpoint(struct mBreakpoint* breakpoint) { +static void _destroyBreakpoint(struct mDebugger* debugger, struct mBreakpoint* breakpoint) { if (breakpoint->condition) { parseFree(breakpoint->condition); } + TableRemove(&debugger->pointOwner, breakpoint->id); } -static void _destroyWatchpoint(struct mWatchpoint* watchpoint) { +static void _destroyWatchpoint(struct mDebugger* debugger, struct mWatchpoint* watchpoint) { if (watchpoint->condition) { parseFree(watchpoint->condition); } + TableRemove(&debugger->pointOwner, watchpoint->id); } static void SM83DebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { @@ -52,7 +54,9 @@ static void SM83DebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { } struct mDebuggerEntryInfo info = { .address = breakpoint->address, - .pointId = breakpoint->id + .segment = debugger->cpu->memory.currentSegment(debugger->cpu, breakpoint->address), + .pointId = breakpoint->id, + .target = TableLookup(&d->p->pointOwner, breakpoint->id) }; mDebuggerEnter(d->p, DEBUGGER_ENTER_BREAKPOINT, &info); } @@ -62,11 +66,11 @@ static void SM83DebuggerDeinit(struct mDebuggerPlatform* platform); static void SM83DebuggerEnter(struct mDebuggerPlatform* d, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info); -static ssize_t SM83DebuggerSetBreakpoint(struct mDebuggerPlatform*, const struct mBreakpoint*); -static void SM83DebuggerListBreakpoints(struct mDebuggerPlatform*, struct mBreakpointList*); +static ssize_t SM83DebuggerSetBreakpoint(struct mDebuggerPlatform*, struct mDebuggerModule* owner, const struct mBreakpoint*); +static void SM83DebuggerListBreakpoints(struct mDebuggerPlatform*, struct mDebuggerModule* owner, struct mBreakpointList*); static bool SM83DebuggerClearBreakpoint(struct mDebuggerPlatform*, ssize_t id); -static ssize_t SM83DebuggerSetWatchpoint(struct mDebuggerPlatform*, const struct mWatchpoint*); -static void SM83DebuggerListWatchpoints(struct mDebuggerPlatform*, struct mWatchpointList*); +static ssize_t SM83DebuggerSetWatchpoint(struct mDebuggerPlatform*, struct mDebuggerModule* owner, const struct mWatchpoint*); +static void SM83DebuggerListWatchpoints(struct mDebuggerPlatform*, struct mDebuggerModule* owner, struct mWatchpointList*); static void SM83DebuggerCheckBreakpoints(struct mDebuggerPlatform*); static bool SM83DebuggerHasBreakpoints(struct mDebuggerPlatform*); static void SM83DebuggerTrace(struct mDebuggerPlatform*, char* out, size_t* length); @@ -104,12 +108,12 @@ void SM83DebuggerDeinit(struct mDebuggerPlatform* platform) { struct SM83Debugger* debugger = (struct SM83Debugger*) platform; size_t i; for (i = 0; i < mBreakpointListSize(&debugger->breakpoints); ++i) { - _destroyBreakpoint(mBreakpointListGetPointer(&debugger->breakpoints, i)); + _destroyBreakpoint(debugger->d.p, mBreakpointListGetPointer(&debugger->breakpoints, i)); } mBreakpointListDeinit(&debugger->breakpoints); for (i = 0; i < mWatchpointListSize(&debugger->watchpoints); ++i) { - _destroyWatchpoint(mWatchpointListGetPointer(&debugger->watchpoints, i)); + _destroyWatchpoint(debugger->d.p, mWatchpointListGetPointer(&debugger->watchpoints, i)); } mWatchpointListDeinit(&debugger->watchpoints); } @@ -120,20 +124,16 @@ static void SM83DebuggerEnter(struct mDebuggerPlatform* platform, enum mDebugger struct SM83Debugger* debugger = (struct SM83Debugger*) platform; struct SM83Core* cpu = debugger->cpu; cpu->nextEvent = cpu->cycles; - - if (debugger->d.p->entered) { - debugger->d.p->entered(debugger->d.p, reason, info); - } } -static ssize_t SM83DebuggerSetBreakpoint(struct mDebuggerPlatform* d, const struct mBreakpoint* info) { +static ssize_t SM83DebuggerSetBreakpoint(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, const struct mBreakpoint* info) { struct SM83Debugger* debugger = (struct SM83Debugger*) d; struct mBreakpoint* breakpoint = mBreakpointListAppend(&debugger->breakpoints); *breakpoint = *info; breakpoint->id = debugger->nextId; + TableInsert(&debugger->d.p->pointOwner, breakpoint->id, owner); ++debugger->nextId; return breakpoint->id; - } static bool SM83DebuggerClearBreakpoint(struct mDebuggerPlatform* d, ssize_t id) { @@ -144,7 +144,7 @@ static bool SM83DebuggerClearBreakpoint(struct mDebuggerPlatform* d, ssize_t id) for (i = 0; i < mBreakpointListSize(breakpoints); ++i) { struct mBreakpoint* breakpoint = mBreakpointListGetPointer(breakpoints, i); if (breakpoint->id == id) { - _destroyBreakpoint(breakpoint); + _destroyBreakpoint(debugger->d.p, breakpoint); mBreakpointListShift(breakpoints, i, 1); return true; } @@ -154,7 +154,7 @@ static bool SM83DebuggerClearBreakpoint(struct mDebuggerPlatform* d, ssize_t id) for (i = 0; i < mWatchpointListSize(watchpoints); ++i) { struct mWatchpoint* watchpoint = mWatchpointListGetPointer(watchpoints, i); if (watchpoint->id == id) { - _destroyWatchpoint(watchpoint); + _destroyWatchpoint(debugger->d.p, watchpoint); mWatchpointListShift(watchpoints, i, 1); if (!mWatchpointListSize(&debugger->watchpoints)) { SM83DebuggerRemoveMemoryShim(debugger); @@ -170,7 +170,7 @@ static bool SM83DebuggerHasBreakpoints(struct mDebuggerPlatform* d) { return mBreakpointListSize(&debugger->breakpoints) || mWatchpointListSize(&debugger->watchpoints); } -static ssize_t SM83DebuggerSetWatchpoint(struct mDebuggerPlatform* d, const struct mWatchpoint* info) { +static ssize_t SM83DebuggerSetWatchpoint(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, const struct mWatchpoint* info) { struct SM83Debugger* debugger = (struct SM83Debugger*) d; if (!mWatchpointListSize(&debugger->watchpoints)) { SM83DebuggerInstallMemoryShim(debugger); @@ -178,20 +178,43 @@ static ssize_t SM83DebuggerSetWatchpoint(struct mDebuggerPlatform* d, const stru struct mWatchpoint* watchpoint = mWatchpointListAppend(&debugger->watchpoints); *watchpoint = *info; watchpoint->id = debugger->nextId; + TableInsert(&debugger->d.p->pointOwner, watchpoint->id, owner); ++debugger->nextId; return watchpoint->id; } -static void SM83DebuggerListBreakpoints(struct mDebuggerPlatform* d, struct mBreakpointList* list) { +static void SM83DebuggerListBreakpoints(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, struct mBreakpointList* list) { struct SM83Debugger* debugger = (struct SM83Debugger*) d; mBreakpointListClear(list); - mBreakpointListCopy(list, &debugger->breakpoints); + if (owner) { + size_t i; + for (i = 0; i < mBreakpointListSize(&debugger->breakpoints); ++i) { + struct mBreakpoint* point = mBreakpointListGetPointer(&debugger->breakpoints, i); + if (TableLookup(&debugger->d.p->pointOwner, point->id) != owner) { + continue; + } + memcpy(mBreakpointListAppend(list), point, sizeof(*point)); + } + } else { + mBreakpointListCopy(list, &debugger->breakpoints); + } } -static void SM83DebuggerListWatchpoints(struct mDebuggerPlatform* d, struct mWatchpointList* list) { +static void SM83DebuggerListWatchpoints(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, struct mWatchpointList* list) { struct SM83Debugger* debugger = (struct SM83Debugger*) d; mWatchpointListClear(list); - mWatchpointListCopy(list, &debugger->watchpoints); + if (owner) { + size_t i; + for (i = 0; i < mWatchpointListSize(&debugger->watchpoints); ++i) { + struct mWatchpoint* point = mWatchpointListGetPointer(&debugger->watchpoints, i); + if (TableLookup(&debugger->d.p->pointOwner, point->id) != owner) { + continue; + } + memcpy(mWatchpointListAppend(list), point, sizeof(*point)); + } + } else { + mWatchpointListCopy(list, &debugger->watchpoints); + } } static void SM83DebuggerTrace(struct mDebuggerPlatform* d, char* out, size_t* length) { diff --git a/src/sm83/debugger/memory-debugger.c b/src/sm83/debugger/memory-debugger.c index 142ab92c7..23966c9a3 100644 --- a/src/sm83/debugger/memory-debugger.c +++ b/src/sm83/debugger/memory-debugger.c @@ -12,7 +12,7 @@ #include -static bool _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, struct mDebuggerEntryInfo* info, enum mWatchpointType type, uint8_t newValue); +static void _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, enum mWatchpointType type, uint8_t newValue); #define FIND_DEBUGGER(DEBUGGER, CPU) \ do { \ @@ -32,17 +32,14 @@ static bool _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, s static RETURN DebuggerShim_ ## NAME TYPES { \ struct SM83Debugger* debugger; \ FIND_DEBUGGER(debugger, cpu); \ - struct mDebuggerEntryInfo info; \ - if (_checkWatchpoints(debugger, address, &info, WATCHPOINT_ ## RW, VALUE)) { \ - mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); \ - } \ + _checkWatchpoints(debugger, address, WATCHPOINT_ ## RW, VALUE); \ return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \ } CREATE_WATCHPOINT_SHIM(load8, READ, 0, uint8_t, (struct SM83Core* cpu, uint16_t address), address) CREATE_WATCHPOINT_SHIM(store8, WRITE, value, void, (struct SM83Core* cpu, uint16_t address, int8_t value), address, value) -static bool _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, struct mDebuggerEntryInfo* info, enum mWatchpointType type, uint8_t newValue) { +static void _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, enum mWatchpointType type, uint8_t newValue) { struct mWatchpoint* watchpoint; size_t i; for (i = 0; i < mWatchpointListSize(&debugger->watchpoints); ++i) { @@ -59,16 +56,18 @@ static bool _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, s if ((watchpoint->type & WATCHPOINT_CHANGE) && newValue == oldValue) { continue; } - info->type.wp.oldValue = oldValue; - info->type.wp.newValue = newValue; - info->address = address; - info->type.wp.watchType = watchpoint->type; - info->type.wp.accessType = type; - info->pointId = watchpoint->id; - return true; + struct mDebuggerEntryInfo info; + info.type.wp.oldValue = oldValue; + info.type.wp.newValue = newValue; + info.address = address; + info.segment = debugger->originalMemory.currentSegment(debugger->cpu, address); + info.type.wp.watchType = watchpoint->type; + info.type.wp.accessType = type; + info.pointId = watchpoint->id; + info.target = TableLookup(&debugger->d.p->pointOwner, watchpoint->id); + mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); } } - return false; } void SM83DebuggerInstallMemoryShim(struct SM83Debugger* debugger) { diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 9246b20c4..eb82215d7 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -15,12 +15,14 @@ set(SOURCE_FILES ${BASE_SOURCE_FILES} convolve.c elf-read.c - export.c + geometry.c + image.c + image/export.c + image/png-io.c patch.c patch-fast.c patch-ips.c patch-ups.c - png-io.c ring-fifo.c sfo.c text-codec.c) @@ -33,6 +35,9 @@ set(GUI_FILES gui/menu.c) set(TEST_FILES + test/color.c + test/geometry.c + test/image.c test/sfo.c test/string-parser.c test/string-utf8.c diff --git a/src/util/geometry.c b/src/util/geometry.c new file mode 100644 index 000000000..17c523afe --- /dev/null +++ b/src/util/geometry.c @@ -0,0 +1,94 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +void mRectangleUnion(struct mRectangle* dst, const struct mRectangle* add) { + int x0 = dst->x; + int y0 = dst->y; + int x1 = dst->x + dst->width; + int y1 = dst->y + dst->height; + + if (add->x < x0) { + x0 = add->x; + } + if (add->y < y0) { + y0 = add->y; + } + if (add->x + add->width > x1) { + x1 = add->x + add->width; + } + if (add->y + add->height > y1) { + y1 = add->y + add->height; + } + + dst->x = x0; + dst->y = y0; + dst->width = x1 - x0; + dst->height = y1 - y0; +} + +bool mRectangleIntersection(struct mRectangle* dst, const struct mRectangle* add) { + int x[3]; + int y[3]; + + if (dst == add) { + return true; + } + +#define SORT(Z, M) \ + if (dst->Z < add->Z) { \ + Z[0] = dst->Z; \ + Z[1] = add->Z; \ + } else { \ + Z[0] = add->Z; \ + Z[1] = dst->Z; \ + } \ + if (dst->Z + dst->M < add->Z + add->M) { \ + /* dst is entirely before add */ \ + if (dst->Z + dst->M <= add->Z) { \ + return false; \ + } \ + if (dst->Z + dst->M < Z[1]) { \ + Z[2] = Z[1]; \ + Z[1] = dst->Z + dst->M; \ + } else { \ + Z[2] = dst->Z + dst->M; \ + } \ + if (add->Z + add->M < Z[2]) { \ + Z[2] = add->Z + add->M; \ + } \ + } else { \ + /* dst is after before add */ \ + if (dst->Z >= add->Z + add->M) { \ + return false; \ + } \ + if (add->Z + add->M < Z[1]) { \ + Z[2] = Z[1]; \ + Z[1] = add->Z + add->M; \ + } else { \ + Z[2] = add->Z + add->M; \ + } \ + if (dst->Z + dst->M < Z[2]) { \ + Z[2] = dst->Z + dst->M; \ + } \ + } + + SORT(x, width); + SORT(y, height); + +#undef SORT + + dst->x = x[1]; + dst->width = x[2] - x[1]; + dst->y = y[1]; + dst->height = y[2] - y[1]; + return true; +} + +void mRectangleCenter(const struct mRectangle* ref, struct mRectangle* rect) { + rect->x = ref->x + (ref->width - rect->width) / 2; + rect->y = ref->y + (ref->height - rect->height) / 2; +} diff --git a/src/util/image.c b/src/util/image.c new file mode 100644 index 000000000..d68491718 --- /dev/null +++ b/src/util/image.c @@ -0,0 +1,1016 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +#include +#include +#include + +#define PIXEL(IM, X, Y) \ + (void*) (((IM)->stride * (Y) + (X)) * (IM)->depth + (uintptr_t) (IM)->data) + +#define ROW(IM, Y) PIXEL(IM, 0, Y) + +#ifdef __BIG_ENDIAN__ +#define SHIFT_IN(COLOR, DEPTH) \ + if ((DEPTH) < 4) { \ + (COLOR) >>= (32 - 8 * (DEPTH)); \ + } + +#define SHIFT_OUT(COLOR, DEPTH) \ + if ((DEPTH) < 4) { \ + (COLOR) <<= (32 - 8 *(DEPTH)); \ + } +#else +#define SHIFT_IN(COLOR, DEPTH) +#define SHIFT_OUT(COLOR, DEPTH) +#endif + +#define GET_PIXEL(DST, SRC, DEPTH) do { \ + uint32_t _color = 0; \ + memcpy(&_color, (void*) (SRC), (DEPTH)); \ + SHIFT_IN(_color, (DEPTH)); \ + (DST) = _color; \ +} while (0) + +#define PUT_PIXEL(SRC, DST, DEPTH) do { \ + uint32_t _color = (SRC); \ + SHIFT_OUT(_color, (DEPTH)); \ + memcpy((void*) (DST), &_color, (DEPTH)); \ +} while (0); + +struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format) { + return mImageCreateWithStride(width, height, width, format); +} + +struct mImage* mImageCreateWithStride(unsigned width, unsigned height, unsigned stride, enum mColorFormat format) { + if (width < 1 || height < 1) { + return NULL; + } + struct mImage* image = calloc(1, sizeof(struct mImage)); + if (!image) { + return NULL; + } + image->width = width; + image->height = height; + image->stride = stride; + image->format = format; + image->depth = mColorFormatBytes(format); + image->data = calloc(width * height, image->depth); + if (!image->data) { + free(image); + return NULL; + } + if (format == mCOLOR_PAL8) { + image->palette = malloc(1024); + if (!image->palette) { + free(image->data); + free(image); + return NULL; + } + image->palSize = 1; + } + return image; +} + +struct mImage* mImageCreateFromConstBuffer(unsigned width, unsigned height, unsigned stride, enum mColorFormat format, const void* pixels) { + struct mImage* image = mImageCreateWithStride(width, height, stride, format); + if (!image) { + return NULL; + } + memcpy(image->data, pixels, height * stride * image->depth); + return image; +} + +struct mImage* mImageLoad(const char* path) { + struct VFile* vf = VFileOpen(path, O_RDONLY); + if (!vf) { + return NULL; + } + struct mImage* image = mImageLoadVF(vf); + vf->close(vf); + return image; +} + +#ifdef USE_PNG +static struct mImage* mImageLoadPNG(struct VFile* vf) { + png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES); + png_infop info = png_create_info_struct(png); + png_infop end = png_create_info_struct(png); + if (!png || !info || !end) { + PNGReadClose(png, info, end); + return NULL; + } + + if (!PNGReadHeader(png, info)) { + PNGReadClose(png, info, end); + return NULL; + } + unsigned width = png_get_image_width(png, info); + unsigned height = png_get_image_height(png, info); + + struct mImage* image = calloc(1, sizeof(*image)); + if (!image) { + PNGReadClose(png, info, end); + return NULL; + } + bool ok = true; + + image->width = width; + image->height = height; + image->stride = width; + + switch (png_get_channels(png, info)) { + case 3: + image->format = mCOLOR_XBGR8; + image->depth = 4; + image->data = malloc(width * height * 4); + if (!PNGReadPixels(png, info, image->data, width, height, width)) { + ok = false; + } + break; + case 4: + image->format = mCOLOR_ABGR8; + image->depth = 4; + image->data = malloc(width * height * 4); + if (!PNGReadPixelsA(png, info, image->data, width, height, width)) { + ok = false; + } + break; + case 1: + if (png_get_color_type(png, info) == PNG_COLOR_TYPE_GRAY) { + image->format = mCOLOR_L8; + } else { + png_colorp palette; + png_bytep trns; + int count; + int trnsCount = 0; + image->format = mCOLOR_PAL8; + if (png_get_PLTE(png, info, &palette, &count) == 0) { + ok = false; + break; + } + if (count > 256) { + count = 256; +#ifndef NDEBUG + abort(); +#endif + } + image->palette = malloc(1024); + image->palSize = count; + png_get_tRNS(png, info, &trns, &trnsCount, NULL); + + int i; + for (i = 0; i < count; ++i) { + uint32_t color = palette[i].red << 16; + color |= palette[i].green << 8; + color |= palette[i].blue; + + if (i < trnsCount) { + color |= trns[i] << 24; + } else { + color |= 0xFF000000; + } + image->palette[i] = color; + } + } + image->depth = 1; + image->data = malloc(width * height); + if (!PNGReadPixels8(png, info, image->data, width, height, width)) { + ok = false; + } + break; + default: + // Not supported yet + ok = false; + break; + } + + PNGReadClose(png, info, end); + if (!ok) { + mImageDestroy(image); + image = NULL; + } + return image; +} +#endif + +struct mImage* mImageLoadVF(struct VFile* vf) { + vf->seek(vf, 0, SEEK_SET); +#ifdef USE_PNG + if (isPNG(vf)) { + return mImageLoadPNG(vf); + } + vf->seek(vf, 0, SEEK_SET); +#endif + return NULL; +} + +struct mImage* mImageConvertToFormat(const struct mImage* image, enum mColorFormat format) { + if (format == mCOLOR_PAL8) { + // Quantization shouldn't be handled here + return NULL; + } + struct mImage* newImage = calloc(1, sizeof(*newImage)); + newImage->width = image->width; + newImage->height = image->height; + newImage->format = format; + if (format == image->format) { + newImage->depth = image->depth; + newImage->stride = image->stride; + newImage->data = malloc(image->stride * image->height * image->depth); + memcpy(newImage->data, image->data, image->stride * image->height * image->depth); + return newImage; + } + newImage->depth = mColorFormatBytes(format); + newImage->stride = image->width; + newImage->data = malloc(image->width * image->height * newImage->depth); + + // TODO: Implement more specializations, e.g. alpha narrowing/widening, channel swapping + size_t x, y; + for (y = 0; y < newImage->height; ++y) { + uintptr_t src = (uintptr_t) ROW(image, y); + uintptr_t dst = (uintptr_t) ROW(newImage, y); + for (x = 0; x < newImage->width; ++x, src += image->depth, dst += newImage->depth) { + uint32_t color; + GET_PIXEL(color, src, image->depth); + color = mImageColorConvert(color, image, format); + PUT_PIXEL(color, dst, newImage->depth); + } + } + return newImage; +} + +void mImageDestroy(struct mImage* image) { + if (image->palette) { + free(image->palette); + } + free(image->data); + free(image); +} + +bool mImageSave(const struct mImage* image, const char* path, const char* format) { + struct VFile* vf = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + return false; + } + + char extension[PATH_MAX]; + if (!format) { + separatePath(path, NULL, NULL, extension); + format = extension; + } + bool success = mImageSaveVF(image, vf, format); + vf->close(vf); + return success; +} + +#ifdef USE_PNG +bool mImageSavePNG(const struct mImage* image, struct VFile* vf) { + png_structp png = PNGWriteOpen(vf); + png_infop info = NULL; + bool ok = false; + if (png) { + if (image->format == mCOLOR_PAL8) { + info = PNGWriteHeaderPalette(png, image->width, image->height, image->palette, image->palSize); + if (info) { + ok = PNGWritePixelsPalette(png, image->width, image->height, image->stride, image->data); + } + } else { + info = PNGWriteHeader(png, image->width, image->height, image->format); + if (info) { + ok = PNGWritePixels(png, image->width, image->height, image->stride, image->data, image->format); + } + } + PNGWriteClose(png, info); + } + return ok; +} +#endif + +bool mImageSaveVF(const struct mImage* image, struct VFile* vf, const char* format) { +#ifdef USE_PNG + if (strcasecmp(format, "png") == 0) { + return mImageSavePNG(image, vf); + } +#endif + return false; +} + +uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y) { + if (x >= image->width || y >= image->height) { + return 0; + } + const void* pixel = PIXEL(image, x, y); + uint32_t color; + switch (image->depth) { + case 1: + color = *(const uint8_t*) pixel; + break; + case 2: + color = *(const uint16_t*) pixel; + break; + case 4: + color = *(const uint32_t*) pixel; + break; + case 3: +#ifdef __BIG_ENDIAN__ + color = ((const uint8_t*) pixel)[0] << 16; + color |= ((const uint8_t*) pixel)[1] << 8; + color |= ((const uint8_t*) pixel)[2]; +#else + color = ((const uint8_t*) pixel)[0]; + color |= ((const uint8_t*) pixel)[1] << 8; + color |= ((const uint8_t*) pixel)[2] << 16; +#endif + break; + default: + // This should never be reached + abort(); + } + return color; +} + +uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y) { + return mImageColorConvert(mImageGetPixelRaw(image, x, y), image, mCOLOR_ARGB8); +} + +void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color) { + if (x >= image->width || y >= image->height) { + return; + } + void* pixel = PIXEL(image, x, y); + switch (image->depth) { + case 1: + *(uint8_t*) pixel = color; + break; + case 2: + *(uint16_t*) pixel = color; + break; + case 4: + *(uint32_t*) pixel = color; + break; + case 3: +#ifdef __BIG_ENDIAN__ + ((uint8_t*) pixel)[0] = color >> 16; + ((uint8_t*) pixel)[1] = color >> 8; + ((uint8_t*) pixel)[2] = color; +#else + ((uint8_t*) pixel)[0] = color; + ((uint8_t*) pixel)[1] = color >> 8; + ((uint8_t*) pixel)[2] = color >> 16; +#endif + break; + } +} + +void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color) { + mImageSetPixelRaw(image, x, y, mColorConvert(color, mCOLOR_ARGB8, image->format)); +} + +void mImageSetPaletteSize(struct mImage* image, unsigned count) { + if (image->format != mCOLOR_PAL8) { + return; + } + if (count > 256) { + count = 256; + } + image->palSize = count; +} + +void mImageSetPaletteEntry(struct mImage* image, unsigned index, uint32_t color) { + if (image->format != mCOLOR_PAL8) { + return; + } + if (index > 256) { + return; + } + image->palette[index] = color; +} + +#define COMPOSITE_BOUNDS_INIT \ + struct mRectangle dstRect = { \ + .x = 0, \ + .y = 0, \ + .width = image->width, \ + .height = image->height \ + }; \ + struct mRectangle srcRect = { \ + .x = x, \ + .y = y, \ + .width = source->width, \ + .height = source->height \ + }; \ + if (!mRectangleIntersection(&srcRect, &dstRect)) { \ + return; \ + } \ + int srcStartX; \ + int srcStartY; \ + int dstStartX; \ + int dstStartY; \ + if (x < 0) { \ + dstStartX = 0; \ + srcStartX = -x; \ + } else { \ + srcStartX = 0; \ + dstStartX = srcRect.x; \ + } \ + if (y < 0) { \ + dstStartY = 0; \ + srcStartY = -y; \ + } else { \ + srcStartY = 0; \ + dstStartY = srcRect.y; \ + } + +void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y) { + if (image->format == mCOLOR_PAL8) { + // Can't blit to paletted image + return; + } + + COMPOSITE_BOUNDS_INIT; + + for (y = 0; y < srcRect.height; ++y) { + uintptr_t srcPixel = (uintptr_t) PIXEL(source, srcStartX, srcStartY + y); + uintptr_t dstPixel = (uintptr_t) PIXEL(image, dstStartX, dstStartY + y); + for (x = 0; x < srcRect.width; ++x, srcPixel += source->depth, dstPixel += image->depth) { + uint32_t color; + GET_PIXEL(color, srcPixel, source->depth); + color = mImageColorConvert(color, source, image->format); + PUT_PIXEL(color, dstPixel, image->depth); + } + } +} + +void mImageComposite(struct mImage* image, const struct mImage* source, int x, int y) { + if (!mColorFormatHasAlpha(source->format)) { + mImageBlit(image, source, x, y); + return; + } + + if (image->format == mCOLOR_PAL8) { + // Can't blit to paletted image + return; + } + + COMPOSITE_BOUNDS_INIT; + + for (y = 0; y < srcRect.height; ++y) { + uintptr_t srcPixel = (uintptr_t) PIXEL(source, srcStartX, srcStartY + y); + uintptr_t dstPixel = (uintptr_t) PIXEL(image, dstStartX, dstStartY + y); + for (x = 0; x < srcRect.width; ++x, srcPixel += source->depth, dstPixel += image->depth) { + uint32_t color, colorB; + GET_PIXEL(color, srcPixel, source->depth); + color = mImageColorConvert(color, source, mCOLOR_ARGB8); + if (color < 0xFF000000) { + GET_PIXEL(colorB, dstPixel, image->depth); + colorB = mColorConvert(colorB, image->format, mCOLOR_ARGB8); + color = mColorMixARGB8(color, colorB); + } + color = mColorConvert(color, mCOLOR_ARGB8, image->format); + PUT_PIXEL(color, dstPixel, image->depth); + } + } +} + +void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source, int x, int y, float alpha) { + if (alpha >= 1 && alpha < 257.f / 256.f) { + mImageComposite(image, source, x, y); + return; + } + if (image->format == mCOLOR_PAL8) { + // Can't blit to paletted image + return; + } + if (alpha <= 0) { + return; + } + if (alpha > 256) { + // TODO: Add a slow path for alpha > 1, since we need to check saturation only on this path + alpha = 256; + } + + COMPOSITE_BOUNDS_INIT; + + int fixedAlpha = alpha * 0x200; + + for (y = 0; y < srcRect.height; ++y) { + uintptr_t srcPixel = (uintptr_t) PIXEL(source, srcStartX, srcStartY + y); + uintptr_t dstPixel = (uintptr_t) PIXEL(image, dstStartX, dstStartY + y); + for (x = 0; x < srcRect.width; ++x, srcPixel += source->depth, dstPixel += image->depth) { + uint32_t color, colorB; + GET_PIXEL(color, srcPixel, source->depth); + color = mImageColorConvert(color, source, mCOLOR_ARGB8); + uint32_t alpha = (color >> 24) * fixedAlpha; + alpha >>= 9; + if (alpha > 0xFF) { + alpha = 0xFF; + } + color &= 0x00FFFFFF; + color |= alpha << 24; + + GET_PIXEL(colorB, dstPixel, image->depth); + colorB = mColorConvert(colorB, image->format, mCOLOR_ARGB8); + + color = mColorMixARGB8(color, colorB); + color = mColorConvert(color, mCOLOR_ARGB8, image->format); + PUT_PIXEL(color, dstPixel, image->depth); + } + } +} + +#define FILL_BOUNDS_INIT(X, Y, W, H) \ + struct mRectangle dstRect = { \ + .x = 0, \ + .y = 0, \ + .width = painter->backing->width, \ + .height = painter->backing->height \ + }; \ + struct mRectangle srcRect = { \ + .x = (X), \ + .y = (Y), \ + .width = (W), \ + .height = (H) \ + }; \ + if (!mRectangleIntersection(&srcRect, &dstRect)) { \ + return; \ + } \ + int dstStartX; \ + int dstStartY; \ + if ((X) < 0) { \ + dstStartX = 0; \ + } else { \ + dstStartX = srcRect.x; \ + } \ + if ((Y) < 0) { \ + dstStartY = 0; \ + } else { \ + dstStartY = srcRect.y; \ + } + +void mPainterInit(struct mPainter* painter, struct mImage* backing) { + memset(painter, 0, sizeof(*painter)); + painter->backing = backing; +} + +static void mPainterDrawPixel(struct mPainter* painter, unsigned x, unsigned y, uint32_t color) { + if (painter->blend) { + color = mColorMixARGB8(painter->strokeColor, mImageGetPixel(painter->backing, x, y)); + } + mImageSetPixel(painter->backing, x, y, color); +} + +static void mPainterFillRectangle(struct mPainter* painter, int x, int y, int width, int height) { + FILL_BOUNDS_INIT(x, y, width, height); + + if (!painter->blend || painter->fillColor >= 0xFF000000) { + uint32_t color = mColorConvert(painter->fillColor, mCOLOR_ARGB8, painter->backing->format); + for (y = 0; y < srcRect.height; ++y) { + uintptr_t dstPixel = (uintptr_t) PIXEL(painter->backing, dstStartX, dstStartY + y); + for (x = 0; x < srcRect.width; ++x, dstPixel += painter->backing->depth) { + PUT_PIXEL(color, dstPixel, painter->backing->depth); + } + } + } else { + for (y = 0; y < srcRect.height; ++y) { + uintptr_t dstPixel = (uintptr_t) PIXEL(painter->backing, dstStartX, dstStartY + y); + for (x = 0; x < srcRect.width; ++x, dstPixel += painter->backing->depth) { + uint32_t color; + GET_PIXEL(color, dstPixel, painter->backing->depth); + color = mColorConvert(color, painter->backing->format, mCOLOR_ARGB8); + color = mColorMixARGB8(painter->fillColor, color); + color = mColorConvert(color, mCOLOR_ARGB8, painter->backing->format); + PUT_PIXEL(color, dstPixel, painter->backing->depth); + } + } + } +} + +static void mPainterStrokeRectangle(struct mPainter* painter, int x, int y, int width, int height) { + uint32_t fillColor = painter->fillColor; + painter->fillColor = painter->strokeColor; + if (width <= painter->strokeWidth * 2 || height <= painter->strokeWidth * 2) { + mPainterFillRectangle(painter, x, y, width, height); + } else { + int lr = height - painter->strokeWidth; + int tb = width - painter->strokeWidth; + // Top, top-left corner + mPainterFillRectangle(painter, x, y, tb, painter->strokeWidth); + // Left, bottom-left corner + mPainterFillRectangle(painter, x, y + painter->strokeWidth, painter->strokeWidth, lr); + // Bottom, bottom-right corner + mPainterFillRectangle(painter, x + painter->strokeWidth, y + height - painter->strokeWidth, tb, painter->strokeWidth); + // Right, top-right corner + mPainterFillRectangle(painter, x + width - painter->strokeWidth, y, painter->strokeWidth, lr); + } + painter->fillColor = fillColor; +} + +void mPainterDrawRectangle(struct mPainter* painter, int x, int y, int width, int height) { + int interiorW = width - painter->strokeWidth * 2; + int interiorH = height - painter->strokeWidth * 2; + if (painter->fill && interiorW > 0 && interiorH > 0) { + mPainterFillRectangle(painter, x + painter->strokeWidth, y + painter->strokeWidth, interiorW, interiorH); + } + if (painter->strokeWidth) { + mPainterStrokeRectangle(painter, x, y, width, height); + } +} + +void mPainterDrawLine(struct mPainter* painter, int x1, int y1, int x2, int y2) { + if (!painter->strokeWidth) { + return; + } + int dx = x2 - x1; + int dy = y2 - y1; + int x, y; + int xi = 1; + int yi = 1; + int residual; + + int mx = dx; + int my = dy; + if (mx < 0) { + mx = -mx; + } + if (my < 0) { + my = -my; + } + + if (dx < 0) { + xi = -1; + dx = -dx; + } + if (dy < 0) { + yi = -1; + dy = -dy; + } + + unsigned i; + uint32_t color = painter->strokeColor; + + if (mx > my) { + residual = 2 * dy - dx; + y = y1; + for (x = x1; x != x2 + xi; x += xi) { + for (i = 0; i < painter->strokeWidth; ++i) { + mPainterDrawPixel(painter, x, y - painter->strokeWidth / 2 + i, color); + } + if (residual > 0) { + y += yi; + residual -= 2 * dx; + } + residual += 2 * dy; + } + } else { + residual = 2 * dx - dy; + x = x1; + for (y = y1; y != y2 + yi; y += yi) { + for (i = 0; i < painter->strokeWidth; ++i) { + mPainterDrawPixel(painter, x - painter->strokeWidth / 2 + i, y, color); + } + if (residual > 0) { + x += xi; + residual -= 2 * dy; + } + residual += 2 * dx; + } + } + + // TODO: Draw endcaps for widths >2 +} + +static void _drawCircleOctants(struct mPainter* painter, int x, int y, int offx, int offy, int offset, uint32_t color) { + mPainterDrawPixel(painter, x + offy - offset, y + offx - offset, color); + mPainterDrawPixel(painter, x - offy, y + offx - offset, color); + mPainterDrawPixel(painter, x + offy - offset, y - offx, color); + mPainterDrawPixel(painter, x - offy, y - offx, color); + if (offx < offy) { + mPainterDrawPixel(painter, x + offx - offset, y + offy - offset, color); + mPainterDrawPixel(painter, x - offx, y + offy - offset, color); + mPainterDrawPixel(painter, x + offx - offset, y - offy, color); + mPainterDrawPixel(painter, x - offx, y - offy, color); + } +} + +static void _drawCircle2x2(struct mPainter* painter, int x, int y, uint32_t color) { + mPainterDrawPixel(painter, x, y, color); + mPainterDrawPixel(painter, x - 1, y, color); + mPainterDrawPixel(painter, x, y - 1, color); + mPainterDrawPixel(painter, x - 1, y - 1, color); +} + +void mPainterDrawCircle(struct mPainter* painter, int x, int y, int diameter) { + int radius = diameter / 2; + int offset = (diameter ^ 1) & 1; + int stroke = painter->strokeWidth; + int dx = 1; + int residual0 = 1 - radius; + int dy0 = -2 * radius; + int offx = 0; + int offy; + int y0 = radius; + if (stroke > radius) { + // Clamp stroke + stroke = radius; + if (!offset) { + // Draw center dot as stroke + mPainterDrawPixel(painter, x + radius, y + radius, painter->strokeColor); + } + } else if (!offset && painter->fill) { + // Draw center dot as fill + mPainterDrawPixel(painter, x + radius, y + radius, painter->fillColor); + } + + int residual1 = 1 - radius + stroke; + int dy1 = -2 * (radius - stroke); + int y1 = radius - stroke; + int i; + + if (!offset) { + // Draw central axes + for (i = 0; i < stroke; ++i) { + mPainterDrawPixel(painter, x + radius, y + radius * 2 - i, painter->strokeColor); + mPainterDrawPixel(painter, x + radius, y + i, painter->strokeColor); + mPainterDrawPixel(painter, x + radius * 2 - i, y + radius, painter->strokeColor); + mPainterDrawPixel(painter, x + i, y + radius, painter->strokeColor); + } + if (painter->fill) { + for (i = i; i < y1 + 1; ++i) { + mPainterDrawPixel(painter, x + radius, y + radius - i, painter->fillColor); + mPainterDrawPixel(painter, x + radius, y + radius + i, painter->fillColor); + mPainterDrawPixel(painter, x + radius - i, y + radius, painter->fillColor); + mPainterDrawPixel(painter, x + radius + i, y + radius, painter->fillColor); + } + } + } + + while (offx < y0) { + if (residual0 >= 0) { + y0 -= 1; + dy0 += 2; + residual0 += dy0; + } + if (residual1 >= 0) { + y1 -= 1; + dy1 += 2; + residual1 += dy1; + } + offx += 1; + dx += 2; + residual0 += dx; + residual1 += dx; + if (stroke) { + // Fill + if (painter->fill) { + if (offx == 1 && y1 == 0 && offset) { + // Special case for diameter-2 fill + _drawCircle2x2(painter, x + radius, y + radius, painter->fillColor); + } else { + for (offy = 0; offy < y1 + 1; ++offy) { + if (offx > offy) { + continue; + } + _drawCircleOctants(painter, x + radius, y + radius, offx, offy, offset, painter->fillColor); + } + } + } + // Stroke + if (radius == 1 && offset) { + // Special case for diameter-2 stroke + _drawCircle2x2(painter, x + radius, y + radius, painter->strokeColor); + } else { + for (offy = y1 + 1; offy < y0 + 1; ++offy) { + if (offx == 1 && offy == 1 && y1 == 0 && offset) { + // Special case for diameter-2 inner fill + continue; + } + if (offx > offy) { + continue; + } + _drawCircleOctants(painter, x + radius, y + radius, offx, offy, offset, painter->strokeColor); + } + } + } else if (painter->fill) { + if (offx == 1 && y0 == 0 && offset) { + // Special case for diameter-2 fill + _drawCircle2x2(painter, x, y, painter->fillColor); + } else { + for (offy = 0; offy < y0 + 1; ++offy) { + if (offx > offy) { + continue; + } + _drawCircleOctants(painter, x + radius, y + radius, offx, offy, offset, painter->fillColor); + } + } + } + } +} + +uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to) { + if (from == to) { + return color; + } + + int r; + int g; + int b; + int a = 0xFF; + + switch (from) { + case mCOLOR_ARGB8: + a = color >> 24; + // Fall through + case mCOLOR_XRGB8: + case mCOLOR_RGB8: + r = (color >> 16) & 0xFF; + g = (color >> 8) & 0xFF; + b = color & 0xFF; + break; + + case mCOLOR_ABGR8: + a = color >> 24; + // Fall through + case mCOLOR_XBGR8: + case mCOLOR_BGR8: + b = (color >> 16) & 0xFF; + g = (color >> 8) & 0xFF; + r = color & 0xFF; + break; + + case mCOLOR_RGBA8: + a = color & 0xFF; + // Fall through + case mCOLOR_RGBX8: + r = (color >> 24) & 0xFF; + g = (color >> 16) & 0xFF; + b = (color >> 8) & 0xFF; + break; + + case mCOLOR_BGRA8: + a = color & 0xFF; + // Fall through + case mCOLOR_BGRX8: + b = (color >> 24) & 0xFF; + g = (color >> 16) & 0xFF; + r = (color >> 8) & 0xFF; + break; + + case mCOLOR_ARGB5: + a = (color >> 15) * 0xFF; + // Fall through + case mCOLOR_RGB5: + r = (((color >> 10) & 0x1F) * 0x21) >> 2; + g = (((color >> 5) & 0x1F) * 0x21) >> 2; + b = ((color & 0x1F) * 0x21) >> 2; + break; + + case mCOLOR_ABGR5: + a = (color >> 15) * 0xFF; + // Fall through + case mCOLOR_BGR5: + b = (((color >> 10) & 0x1F) * 0x21) >> 2; + g = (((color >> 5) & 0x1F) * 0x21) >> 2; + r = ((color & 0x1F) * 0x21) >> 2; + break; + + case mCOLOR_RGBA5: + a = (color & 1) * 0xFF; + r = (((color >> 11) & 0x1F) * 0x21) >> 2; + g = (((color >> 6) & 0x1F) * 0x21) >> 2; + b = (((color >> 1) & 0x1F) * 0x21) >> 2; + break; + case mCOLOR_BGRA5: + a = (color & 1) * 0xFF; + b = (((color >> 11) & 0x1F) * 0x21) >> 2; + g = (((color >> 6) & 0x1F) * 0x21) >> 2; + r = (((color >> 1) & 0x1F) * 0x21) >> 2; + break; + + case mCOLOR_RGB565: + r = (((color >> 11) & 0x1F) * 0x21) >> 2; + g = (((color >> 5) & 0x3F) * 0x41) >> 4; + b = ((color & 0x1F) * 0x21) >> 2; + break; + case mCOLOR_BGR565: + b = (((color >> 11) & 0x1F) * 0x21) >> 2; + g = (((color >> 5) & 0x3F) * 0x41) >> 4; + r = ((color & 0x1F) * 0x21) >> 2; + break; + + case mCOLOR_L8: + r = color; + g = color; + b = color; + break; + + case mCOLOR_PAL8: + case mCOLOR_ANY: + return 0; + } + + color = 0; + switch (to) { + case mCOLOR_XRGB8: + a = 0xFF; + // Fall through + case mCOLOR_ARGB8: + color |= a << 24; + // Fall through + case mCOLOR_RGB8: + color |= r << 16; + color |= g << 8; + color |= b; + break; + case mCOLOR_XBGR8: + a = 0xFF; + // Fall through + case mCOLOR_ABGR8: + color |= a << 24; + // Fall through + case mCOLOR_BGR8: + color |= b << 16; + color |= g << 8; + color |= r; + break; + case mCOLOR_RGBX8: + a = 0xFF; + // Fall through + case mCOLOR_RGBA8: + color |= a; + color |= r << 24; + color |= g << 16; + color |= b << 8; + break; + case mCOLOR_BGRX8: + a = 0xFF; + // Fall through + case mCOLOR_BGRA8: + color |= a; + color |= b << 24; + color |= g << 16; + color |= r << 8; + break; + case mCOLOR_ARGB5: + color |= (!!a << 15); + // Fall through + case mCOLOR_RGB5: + color |= (r >> 3) << 10; + color |= (g >> 3) << 5; + color |= b >> 3; + break; + case mCOLOR_ABGR5: + color |= (!!a << 15); + // Fall through + case mCOLOR_BGR5: + color |= (b >> 3) << 10; + color |= (g >> 3) << 5; + color |= r >> 3; + break; + case mCOLOR_RGBA5: + color |= !!a; + color |= (r >> 3) << 11; + color |= (g >> 3) << 6; + color |= (b >> 3) << 1; + break; + case mCOLOR_BGRA5: + color |= !!a; + color |= (b >> 3) << 11; + color |= (g >> 3) << 6; + color |= (r >> 3) << 1; + break; + case mCOLOR_RGB565: + color |= (r >> 3) << 11; + color |= (g >> 2) << 5; + color |= b >> 3; + break; + case mCOLOR_BGR565: + color |= (b >> 3) << 11; + color |= (g >> 2) << 5; + color |= r >> 3; + break; + case mCOLOR_L8: + // sRGB primaries in fixed point, roughly fudged to saturate to 0xFFFF + color = (55 * r + 184 * g + 18 * b) >> 8; + break; + case mCOLOR_PAL8: + case mCOLOR_ANY: + return 0; + } + + return color; +} + +uint32_t mImageColorConvert(uint32_t color, const struct mImage* from, enum mColorFormat to) { + if (from->format != mCOLOR_PAL8) { + return mColorConvert(color, from->format, to); + } + if (color < from->palSize) { + color = from->palette[color]; + } + return mColorConvert(color, mCOLOR_ARGB8, to); +} diff --git a/src/util/export.c b/src/util/image/export.c similarity index 86% rename from src/util/export.c rename to src/util/image/export.c index cb138ce5a..2b4aad767 100644 --- a/src/util/export.c +++ b/src/util/image/export.c @@ -1,14 +1,14 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau +/* Copyright (c) 2013-2023 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include +#include #include #include -bool exportPaletteRIFF(struct VFile* vf, size_t entries, const uint16_t* colors) { +bool mPaletteExportRIFF(struct VFile* vf, size_t entries, const uint16_t* colors) { if (entries > 0xFFFF) { return false; } @@ -56,7 +56,7 @@ bool exportPaletteRIFF(struct VFile* vf, size_t entries, const uint16_t* colors) return true; } -bool exportPaletteACT(struct VFile* vf, size_t entries, const uint16_t* colors) { +bool mPaletteExportACT(struct VFile* vf, size_t entries, const uint16_t* colors) { if (entries > 256) { return false; } diff --git a/src/util/image/png-io.c b/src/util/image/png-io.c new file mode 100644 index 000000000..e7df1d64f --- /dev/null +++ b/src/util/image/png-io.c @@ -0,0 +1,727 @@ +/* Copyright (c) 2013-2014 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 + +#ifdef USE_PNG + +#include + +static bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, unsigned entries); + +static void _pngWrite(png_structp png, png_bytep buffer, png_size_t size) { + struct VFile* vf = png_get_io_ptr(png); + size_t written = vf->write(vf, buffer, size); + if (written != size) { + png_error(png, "Could not write PNG"); + } +} + +static void _pngRead(png_structp png, png_bytep buffer, png_size_t size) { + struct VFile* vf = png_get_io_ptr(png); + size_t read = vf->read(vf, buffer, size); + if (read != size) { + png_error(png, "Could not read PNG"); + } +} + +png_structp PNGWriteOpen(struct VFile* source) { + png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); + if (!png) { + return 0; + } + if (setjmp(png_jmpbuf(png))) { + png_destroy_write_struct(&png, 0); + return 0; + } + png_set_write_fn(png, source, _pngWrite, 0); + return png; +} + +static png_infop _pngWriteHeader(png_structp png, unsigned width, unsigned height, const uint32_t* palette, unsigned entries, int type) { + png_infop info = png_create_info_struct(png); + if (!info) { + return NULL; + } + if (setjmp(png_jmpbuf(png))) { + return NULL; + } + png_set_IHDR(png, info, width, height, 8, type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + if (type == PNG_COLOR_TYPE_PALETTE) { + if (!palette) { + return NULL; + } + if (!PNGWritePalette(png, info, palette, entries)) { + return NULL; + } + } + png_write_info(png, info); + return info; +} + +png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height, enum mColorFormat fmt) { + int type; + switch (fmt) { + case mCOLOR_XBGR8: + case mCOLOR_XRGB8: + case mCOLOR_BGRX8: + case mCOLOR_RGBX8: + case mCOLOR_RGB5: + case mCOLOR_BGR5: + case mCOLOR_RGB565: + case mCOLOR_BGR565: + case mCOLOR_RGB8: + case mCOLOR_BGR8: + type = PNG_COLOR_TYPE_RGB; + break; + case mCOLOR_ABGR8: + case mCOLOR_ARGB8: + case mCOLOR_BGRA8: + case mCOLOR_RGBA8: + case mCOLOR_ARGB5: + case mCOLOR_ABGR5: + case mCOLOR_RGBA5: + case mCOLOR_BGRA5: + case mCOLOR_ANY: + type = PNG_COLOR_TYPE_RGB_ALPHA; + break; + case mCOLOR_L8: + type = PNG_COLOR_TYPE_GRAY; + break; + case mCOLOR_PAL8: + type = PNG_COLOR_TYPE_PALETTE; + break; + } + return _pngWriteHeader(png, width, height, NULL, 0, type); +} + +png_infop PNGWriteHeaderPalette(png_structp png, unsigned width, unsigned height, const uint32_t* palette, unsigned entries) { + return _pngWriteHeader(png, width, height, palette, entries, PNG_COLOR_TYPE_PALETTE); +} + +static bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, unsigned entries) { + if (!palette || !entries) { + return false; + } + if (setjmp(png_jmpbuf(png))) { + return false; + } + png_color colors[256]; + png_byte trans[256]; + unsigned i; + for (i = 0; i < entries && i < 256; ++i) { + colors[i].red = palette[i] >> 16; + colors[i].green = palette[i] >> 8; + colors[i].blue = palette[i]; + trans[i] = palette[i] >> 24; + } + png_set_PLTE(png, info, colors, entries); + png_set_tRNS(png, info, trans, entries, NULL); + return true; +} + +static void _convertRowXBGR8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 3] = pixelData[x * 4 + 3]; + row[x * 3 + 1] = pixelData[x * 4 + 2]; + row[x * 3 + 2] = pixelData[x * 4 + 1]; +#else + row[x * 3] = pixelData[x * 4]; + row[x * 3 + 1] = pixelData[x * 4 + 1]; + row[x * 3 + 2] = pixelData[x * 4 + 2]; +#endif + } +} + +static void _convertRowXRGB8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 3] = pixelData[x * 4 + 1]; + row[x * 3 + 1] = pixelData[x * 4 + 2]; + row[x * 3 + 2] = pixelData[x * 4 + 3]; +#else + row[x * 3] = pixelData[x * 4 + 2]; + row[x * 3 + 1] = pixelData[x * 4 + 1]; + row[x * 3 + 2] = pixelData[x * 4]; +#endif + } +} + +static void _convertRowBGRX8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 3] = pixelData[x * 4 + 2]; + row[x * 3 + 1] = pixelData[x * 4 + 1]; + row[x * 3 + 2] = pixelData[x * 4]; +#else + row[x * 3] = pixelData[x * 4 + 1]; + row[x * 3 + 1] = pixelData[x * 4 + 2]; + row[x * 3 + 2] = pixelData[x * 4 + 3]; +#endif + } +} + +static void _convertRowRGBX8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 3] = pixelData[x * 4]; + row[x * 3 + 1] = pixelData[x * 4 + 1]; + row[x * 3 + 2] = pixelData[x * 4 + 2]; +#else + row[x * 3] = pixelData[x * 4 + 3]; + row[x * 3 + 1] = pixelData[x * 4 + 2]; + row[x * 3 + 2] = pixelData[x * 4 + 1]; +#endif + } +} + +static void _convertRowABGR8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 4] = pixelData[x * 4 + 3]; + row[x * 4 + 1] = pixelData[x * 4 + 2]; + row[x * 4 + 2] = pixelData[x * 4 + 1]; + row[x * 4 + 3] = pixelData[x * 4]; +#else + row[x * 4] = pixelData[x * 4]; + row[x * 4 + 1] = pixelData[x * 4 + 1]; + row[x * 4 + 2] = pixelData[x * 4 + 2]; + row[x * 4 + 3] = pixelData[x * 4 + 3]; +#endif + } +} + +static void _convertRowARGB8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 4] = pixelData[x * 4 + 1]; + row[x * 4 + 1] = pixelData[x * 4 + 2]; + row[x * 4 + 2] = pixelData[x * 4 + 3]; + row[x * 4 + 3] = pixelData[x * 4]; +#else + row[x * 4] = pixelData[x * 4 + 2]; + row[x * 4 + 1] = pixelData[x * 4 + 1]; + row[x * 4 + 2] = pixelData[x * 4]; + row[x * 4 + 3] = pixelData[x * 4 + 3]; +#endif + } +} + +static void _convertRowBGRA8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 4] = pixelData[x * 4 + 2]; + row[x * 4 + 1] = pixelData[x * 4 + 1]; + row[x * 4 + 2] = pixelData[x * 4]; + row[x * 4 + 3] = pixelData[x * 4 + 3]; +#else + row[x * 4] = pixelData[x * 4 + 1]; + row[x * 4 + 1] = pixelData[x * 4 + 2]; + row[x * 4 + 2] = pixelData[x * 4 + 3]; + row[x * 4 + 3] = pixelData[x * 4]; +#endif + } +} + +static void _convertRowRGBA8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 4] = pixelData[x * 4]; + row[x * 4 + 1] = pixelData[x * 4 + 1]; + row[x * 4 + 2] = pixelData[x * 4 + 2]; + row[x * 4 + 3] = pixelData[x * 4 + 3]; +#else + row[x * 4] = pixelData[x * 4 + 3]; + row[x * 4 + 1] = pixelData[x * 4 + 2]; + row[x * 4 + 2] = pixelData[x * 4 + 1]; + row[x * 4 + 3] = pixelData[x * 4]; +#endif + } +} + +static void _convertRowRGB5(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { + uint16_t c = ((uint16_t*) pixelData)[x]; + row[x * 3] = (c >> 7) & 0xF8; + row[x * 3 + 1] = (c >> 2) & 0xF8; + row[x * 3 + 2] = (c << 3) & 0xF8; + } +} + +static void _convertRowBGR5(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { + uint16_t c = ((uint16_t*) pixelData)[x]; + row[x * 3] = (c << 3) & 0xF8; + row[x * 3 + 1] = (c >> 2) & 0xF8; + row[x * 3 + 2] = (c >> 7) & 0xF8; + } +} + +static void _convertRowARGB5(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { + uint16_t c = ((uint16_t*) pixelData)[x]; + row[x * 4] = (c >> 7) & 0xF8; + row[x * 4 + 1] = (c >> 2) & 0xF8; + row[x * 4 + 2] = (c << 3) & 0xF8; + row[x * 4 + 3] = (c >> 15) * 0xFF; + } +} + +static void _convertRowABGR5(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { + uint16_t c = ((uint16_t*) pixelData)[x]; + row[x * 4] = (c << 3) & 0xF8; + row[x * 4 + 1] = (c >> 2) & 0xF8; + row[x * 4 + 2] = (c >> 7) & 0xF8; + row[x * 4 + 3] = (c >> 15) * 0xFF; + } +} + +static void _convertRowRGBA5(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { + uint16_t c = ((uint16_t*) pixelData)[x]; + row[x * 4] = (c >> 8) & 0xF8; + row[x * 4 + 1] = (c >> 3) & 0xF8; + row[x * 4 + 2] = (c << 2) & 0xF8; + row[x * 4 + 3] = (c & 1) * 0xFF; + } +} + +static void _convertRowBGRA5(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { + uint16_t c = ((uint16_t*) pixelData)[x]; + row[x * 4] = (c << 2) & 0xF8; + row[x * 4 + 1] = (c >> 3) & 0xF8; + row[x * 4 + 2] = (c >> 8) & 0xF8; + row[x * 4 + 3] = (c & 1) * 0xFF; + } +} + +static void _convertRowRGB565(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { + uint16_t c = ((uint16_t*) pixelData)[x]; + row[x * 3] = (c >> 8) & 0xF8; + row[x * 3 + 1] = (c >> 3) & 0xFC; + row[x * 3 + 2] = (c << 3) & 0xF8; + } +} + +static void _convertRowBGR565(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { + uint16_t c = ((uint16_t*) pixelData)[x]; + row[x * 3] = (c << 3) & 0xF8; + row[x * 3 + 1] = (c >> 3) & 0xFC; + row[x * 3 + 2] = (c >> 8) & 0xF8; + } +} + +static void _convertRowBGR8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 3] = pixelData[x * 3 + 2]; + row[x * 3 + 1] = pixelData[x * 3 + 1]; + row[x * 3 + 2] = pixelData[x * 3]; +#else + row[x * 3] = pixelData[x * 3]; + row[x * 3 + 1] = pixelData[x * 3 + 1]; + row[x * 3 + 2] = pixelData[x * 3 + 2]; +#endif + } +} + +static void _convertRowRGB8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 3] = pixelData[x * 3]; + row[x * 3 + 1] = pixelData[x * 3 + 1]; + row[x * 3 + 2] = pixelData[x * 3 + 2]; +#else + row[x * 3] = pixelData[x * 3 + 2]; + row[x * 3 + 1] = pixelData[x * 3 + 1]; + row[x * 3 + 2] = pixelData[x * 3]; +#endif + } +} + +bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels, enum mColorFormat fmt) { + int depth; + if (fmt == mCOLOR_L8) { + depth = 1; + } else if (mColorFormatHasAlpha(fmt)) { + depth = 4; + } else { + depth = 3; + } + png_bytep row = malloc(sizeof(png_byte) * width * depth); + if (!row) { + return false; + } + const png_byte* pixelData = pixels; + if (setjmp(png_jmpbuf(png))) { + free(row); + return false; + } + const png_byte* pixelRow = pixelData; + stride *= mColorFormatBytes(fmt); + unsigned i; + for (i = 0; i < height; ++i, pixelRow += stride) { + switch (fmt) { + case mCOLOR_XBGR8: + _convertRowXBGR8(row, pixelRow, width); + break; + case mCOLOR_XRGB8: + _convertRowXRGB8(row, pixelRow, width); + break; + case mCOLOR_BGRX8: + _convertRowBGRX8(row, pixelRow, width); + break; + case mCOLOR_RGBX8: + _convertRowRGBX8(row, pixelRow, width); + break; + case mCOLOR_ABGR8: + _convertRowABGR8(row, pixelRow, width); + break; + case mCOLOR_ARGB8: + _convertRowARGB8(row, pixelRow, width); + break; + case mCOLOR_BGRA8: + _convertRowBGRA8(row, pixelRow, width); + break; + case mCOLOR_RGBA8: + _convertRowRGBA8(row, pixelRow, width); + break; + case mCOLOR_RGB5: + _convertRowRGB5(row, pixelRow, width); + break; + case mCOLOR_BGR5: + _convertRowBGR5(row, pixelRow, width); + break; + case mCOLOR_ARGB5: + _convertRowARGB5(row, pixelRow, width); + break; + case mCOLOR_ABGR5: + _convertRowABGR5(row, pixelRow, width); + break; + case mCOLOR_RGBA5: + _convertRowRGBA5(row, pixelRow, width); + break; + case mCOLOR_BGRA5: + _convertRowBGRA5(row, pixelRow, width); + break; + case mCOLOR_RGB565: + _convertRowRGB565(row, pixelRow, width); + break; + case mCOLOR_BGR565: + _convertRowBGR565(row, pixelRow, width); + break; + case mCOLOR_BGR8: + _convertRowBGR8(row, pixelRow, width); + break; + case mCOLOR_RGB8: + _convertRowRGB8(row, pixelRow, width); + break; + case mCOLOR_L8: + case mCOLOR_PAL8: + memcpy(row, pixelRow, width); + break; + case mCOLOR_ANY: + // Invalid value + longjmp(png_jmpbuf(png), 1); + } + png_write_row(png, row); + } + free(row); + return true; +} + +bool PNGWritePixelsPalette(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels) { + UNUSED(width); + const png_byte* pixelData = pixels; + if (setjmp(png_jmpbuf(png))) { + return false; + } + unsigned i; + for (i = 0; i < height; ++i) { + png_write_row(png, &pixelData[stride * i]); + } + return true; +} + +bool PNGWriteCustomChunk(png_structp png, const char* name, size_t size, void* data) { + char realName[5]; + strncpy(realName, name, 4); + realName[0] = tolower((int) realName[0]); + realName[1] = tolower((int) realName[1]); + realName[4] = '\0'; + if (setjmp(png_jmpbuf(png))) { + return false; + } + png_write_chunk(png, (png_bytep) realName, data, size); + return true; +} + +void PNGWriteClose(png_structp png, png_infop info) { + if (!setjmp(png_jmpbuf(png))) { + png_write_end(png, info); + } + png_destroy_write_struct(&png, &info); +} + +bool isPNG(struct VFile* source) { + png_byte header[PNG_HEADER_BYTES]; + source->seek(source, 0, SEEK_SET); + if (source->read(source, header, PNG_HEADER_BYTES) < PNG_HEADER_BYTES) { + return false; + } + return !png_sig_cmp(header, 0, PNG_HEADER_BYTES); +} + +png_structp PNGReadOpen(struct VFile* source, unsigned offset) { + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); + if (!png) { + return 0; + } + if (setjmp(png_jmpbuf(png))) { + png_destroy_read_struct(&png, 0, 0); + return 0; + } + png_set_read_fn(png, source, _pngRead); + png_set_sig_bytes(png, offset); + return png; +} + +bool PNGInstallChunkHandler(png_structp png, void* context, ChunkHandler handler, const char* chunkName) { + if (setjmp(png_jmpbuf(png))) { + return false; + } + png_set_read_user_chunk_fn(png, context, handler); + int len = strlen(chunkName); + int chunks = 0; + char* chunkList = strdup(chunkName); + int i; + for (i = 4; i <= len; i += 5) { + chunkList[i] = '\0'; + ++chunks; + } + png_set_keep_unknown_chunks(png, PNG_HANDLE_CHUNK_ALWAYS, (png_bytep) chunkList, chunks); + free(chunkList); + return true; +} + +bool PNGReadHeader(png_structp png, png_infop info) { + if (setjmp(png_jmpbuf(png))) { + return false; + } + png_read_info(png, info); + return true; +} + +bool PNGIgnorePixels(png_structp png, png_infop info) { + if (setjmp(png_jmpbuf(png))) { + return false; + } + + unsigned height = png_get_image_height(png, info); + unsigned i; + for (i = 0; i < height; ++i) { + png_read_row(png, 0, 0); + } + return true; +} + +bool PNGReadPixels(png_structp png, png_infop info, void* pixels, unsigned width, unsigned height, unsigned stride) { + if (png_get_channels(png, info) != 3) { + return false; + } + + if (setjmp(png_jmpbuf(png))) { + return false; + } + + if (png_get_bit_depth(png, info) == 16) { +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED + png_set_scale_16(png); +#else + png_set_strip_16(png); +#endif + } + + uint8_t* pixelData = pixels; + unsigned pngHeight = png_get_image_height(png, info); + if (height < pngHeight) { + pngHeight = height; + } + + unsigned pngWidth = png_get_image_width(png, info); + if (width < pngWidth) { + pngWidth = width; + } + + unsigned i; + png_bytep row = malloc(png_get_rowbytes(png, info)); + for (i = 0; i < pngHeight; ++i) { + png_read_row(png, row, 0); + unsigned x; + for (x = 0; x < pngWidth; ++x) { +#ifdef COLOR_16_BIT + uint16_t c = row[x * 3 + 2] >> 3; +#ifdef COLOR_5_6_5 + c |= (row[x * 3 + 1] << 3) & 0x7E0; + c |= (row[x * 3] << 8) & 0xF800; +#else + c |= (row[x * 3 + 1] << 2) & 0x3E0; + c |= (row[x * 3] << 7) & 0x7C00; +#endif + ((uint16_t*) pixelData)[stride * i + x] = c; +#else +#if __BIG_ENDIAN__ + pixelData[stride * i * 4 + x * 4 + 3] = row[x * 3]; + pixelData[stride * i * 4 + x * 4 + 2] = row[x * 3 + 1]; + pixelData[stride * i * 4 + x * 4 + 1] = row[x * 3 + 2]; + pixelData[stride * i * 4 + x * 4] = 0xFF; +#else + pixelData[stride * i * 4 + x * 4] = row[x * 3]; + pixelData[stride * i * 4 + x * 4 + 1] = row[x * 3 + 1]; + pixelData[stride * i * 4 + x * 4 + 2] = row[x * 3 + 2]; + pixelData[stride * i * 4 + x * 4 + 3] = 0xFF; +#endif +#endif + } + } + free(row); + return true; +} + +bool PNGReadPixelsA(png_structp png, png_infop info, void* pixels, unsigned width, unsigned height, unsigned stride) { + if (png_get_channels(png, info) != 4) { + return false; + } + + if (setjmp(png_jmpbuf(png))) { + return false; + } + + if (png_get_bit_depth(png, info) == 16) { +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED + png_set_scale_16(png); +#else + png_set_strip_16(png); +#endif + } + + uint8_t* pixelData = pixels; + unsigned pngHeight = png_get_image_height(png, info); + if (height < pngHeight) { + pngHeight = height; + } + + unsigned pngWidth = png_get_image_width(png, info); + if (width < pngWidth) { + pngWidth = width; + } + + unsigned i; + png_bytep row = malloc(png_get_rowbytes(png, info)); + for (i = 0; i < pngHeight; ++i) { + png_read_row(png, row, 0); + unsigned x; + for (x = 0; x < pngWidth; ++x) { +#ifdef COLOR_16_BIT + uint16_t c = row[x * 4 + 2] >> 3; +#ifdef COLOR_5_6_5 + c |= (row[x * 4 + 1] << 3) & 0x7E0; + c |= (row[x * 4] << 8) & 0xF800; +#else + c |= (row[x * 4 + 1] << 2) & 0x3E0; + c |= (row[x * 4] << 7) & 0x7C00; +#endif + ((uint16_t*) pixelData)[stride * i + x] = c; +#else +#if __BIG_ENDIAN__ + pixelData[stride * i * 4 + x * 4 + 3] = row[x * 4]; + pixelData[stride * i * 4 + x * 4 + 2] = row[x * 4 + 1]; + pixelData[stride * i * 4 + x * 4 + 1] = row[x * 4 + 2]; + pixelData[stride * i * 4 + x * 4] = row[x * 4 + 3]; +#else + pixelData[stride * i * 4 + x * 4] = row[x * 4]; + pixelData[stride * i * 4 + x * 4 + 1] = row[x * 4 + 1]; + pixelData[stride * i * 4 + x * 4 + 2] = row[x * 4 + 2]; + pixelData[stride * i * 4 + x * 4 + 3] = row[x * 4 + 3]; +#endif +#endif + } + } + free(row); + return true; +} + +bool PNGReadPixels8(png_structp png, png_infop info, void* pixels, unsigned width, unsigned height, unsigned stride) { + if (png_get_channels(png, info) != 1) { + return false; + } + + if (setjmp(png_jmpbuf(png))) { + return false; + } + + if (png_get_bit_depth(png, info) == 16) { +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED + png_set_scale_16(png); +#else + png_set_strip_16(png); +#endif + } + + uint8_t* pixelData = pixels; + unsigned pngHeight = png_get_image_height(png, info); + if (height < pngHeight) { + pngHeight = height; + } + + unsigned pngWidth = png_get_image_width(png, info); + if (width < pngWidth) { + pngWidth = width; + } + + unsigned i; + for (i = 0; i < pngHeight; ++i) { + png_read_row(png, &pixelData[stride * i], 0); + } + return true; +} + + +bool PNGReadFooter(png_structp png, png_infop end) { + if (setjmp(png_jmpbuf(png))) { + return false; + } + png_read_end(png, end); + return true; +} + +void PNGReadClose(png_structp png, png_infop info, png_infop end) { + png_destroy_read_struct(&png, &info, &end); +} + +#endif diff --git a/src/util/png-io.c b/src/util/png-io.c deleted file mode 100644 index 6c25f399c..000000000 --- a/src/util/png-io.c +++ /dev/null @@ -1,423 +0,0 @@ -/* Copyright (c) 2013-2014 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 - -#ifdef USE_PNG - -#include - -static void _pngWrite(png_structp png, png_bytep buffer, png_size_t size) { - struct VFile* vf = png_get_io_ptr(png); - size_t written = vf->write(vf, buffer, size); - if (written != size) { - png_error(png, "Could not write PNG"); - } -} - -static void _pngRead(png_structp png, png_bytep buffer, png_size_t size) { - struct VFile* vf = png_get_io_ptr(png); - size_t read = vf->read(vf, buffer, size); - if (read != size) { - png_error(png, "Could not read PNG"); - } -} - -png_structp PNGWriteOpen(struct VFile* source) { - png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); - if (!png) { - return 0; - } - if (setjmp(png_jmpbuf(png))) { - png_destroy_write_struct(&png, 0); - return 0; - } - png_set_write_fn(png, source, _pngWrite, 0); - return png; -} - -static png_infop _pngWriteHeader(png_structp png, unsigned width, unsigned height, int type) { - png_infop info = png_create_info_struct(png); - if (!info) { - return 0; - } - if (setjmp(png_jmpbuf(png))) { - return 0; - } - png_set_IHDR(png, info, width, height, 8, type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - png_write_info(png, info); - return info; -} - -png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height) { - return _pngWriteHeader(png, width, height, PNG_COLOR_TYPE_RGB); -} - -png_infop PNGWriteHeaderA(png_structp png, unsigned width, unsigned height) { - return _pngWriteHeader(png, width, height, PNG_COLOR_TYPE_RGB_ALPHA); -} - -png_infop PNGWriteHeader8(png_structp png, unsigned width, unsigned height) { - return _pngWriteHeader(png, width, height, PNG_COLOR_TYPE_PALETTE); -} - -bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, unsigned entries) { - if (!palette || !entries) { - return false; - } - if (setjmp(png_jmpbuf(png))) { - return false; - } - png_color colors[256]; - png_byte trans[256]; - unsigned i; - for (i = 0; i < entries && i < 256; ++i) { - colors[i].red = palette[i]; - colors[i].green = palette[i] >> 8; - colors[i].blue = palette[i] >> 16; - trans[i] = palette[i] >> 24; - } - png_set_PLTE(png, info, colors, entries); - png_set_tRNS(png, info, trans, entries, NULL); - png_write_info(png, info); - return true; -} - -bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels) { - png_bytep row = malloc(sizeof(png_byte) * width * 3); - if (!row) { - return false; - } - const png_byte* pixelData = pixels; - if (setjmp(png_jmpbuf(png))) { - free(row); - return false; - } - unsigned i; - for (i = 0; i < height; ++i) { - unsigned x; - for (x = 0; x < width; ++x) { -#ifdef COLOR_16_BIT - uint16_t c = ((uint16_t*) pixelData)[stride * i + x]; -#ifdef COLOR_5_6_5 - row[x * 3] = (c >> 8) & 0xF8; - row[x * 3 + 1] = (c >> 3) & 0xFC; - row[x * 3 + 2] = (c << 3) & 0xF8; -#else - row[x * 3] = (c >> 7) & 0xF8; - row[x * 3 + 1] = (c >> 2) & 0xF8; - row[x * 3 + 2] = (c << 3) & 0xF8; -#endif -#else -#ifdef __BIG_ENDIAN__ - row[x * 3] = pixelData[stride * i * 4 + x * 4 + 3]; - row[x * 3 + 1] = pixelData[stride * i * 4 + x * 4 + 2]; - row[x * 3 + 2] = pixelData[stride * i * 4 + x * 4 + 1]; -#else - row[x * 3] = pixelData[stride * i * 4 + x * 4]; - row[x * 3 + 1] = pixelData[stride * i * 4 + x * 4 + 1]; - row[x * 3 + 2] = pixelData[stride * i * 4 + x * 4 + 2]; -#endif -#endif - } - png_write_row(png, row); - } - free(row); - return true; -} - -bool PNGWritePixelsA(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels) { - png_bytep row = malloc(sizeof(png_byte) * width * 4); - if (!row) { - return false; - } - const png_byte* pixelData = pixels; - if (setjmp(png_jmpbuf(png))) { - free(row); - return false; - } - unsigned i; - for (i = 0; i < height; ++i) { - unsigned x; - for (x = 0; x < width; ++x) { -#ifdef COLOR_16_BIT - uint16_t c = ((uint16_t*) pixelData)[stride * i + x]; -#ifdef COLOR_5_6_5 - row[x * 4] = (c >> 8) & 0xF8; - row[x * 4 + 1] = (c >> 3) & 0xFC; - row[x * 4 + 2] = (c << 3) & 0xF8; - row[x * 4 + 3] = 0xFF; -#else - row[x * 4] = (c >> 7) & 0xF8; - row[x * 4 + 1] = (c >> 2) & 0xF8; - row[x * 4 + 2] = (c << 3) & 0xF8; - row[x * 4 + 3] = (c >> 15) * 0xFF; -#endif -#else -#ifdef __BIG_ENDIAN__ - row[x * 4] = pixelData[stride * i * 4 + x * 4 + 3]; - row[x * 4 + 1] = pixelData[stride * i * 4 + x * 4 + 2]; - row[x * 4 + 2] = pixelData[stride * i * 4 + x * 4 + 1]; - row[x * 4 + 3] = pixelData[stride * i * 4 + x * 4]; -#else - row[x * 4] = pixelData[stride * i * 4 + x * 4]; - row[x * 4 + 1] = pixelData[stride * i * 4 + x * 4 + 1]; - row[x * 4 + 2] = pixelData[stride * i * 4 + x * 4 + 2]; - row[x * 4 + 3] = pixelData[stride * i * 4 + x * 4 + 3]; -#endif -#endif - } - png_write_row(png, row); - } - free(row); - return true; -} - -bool PNGWritePixels8(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels) { - UNUSED(width); - const png_byte* pixelData = pixels; - if (setjmp(png_jmpbuf(png))) { - return false; - } - unsigned i; - for (i = 0; i < height; ++i) { - png_write_row(png, &pixelData[stride * i]); - } - return true; -} - -bool PNGWriteCustomChunk(png_structp png, const char* name, size_t size, void* data) { - char realName[5]; - strncpy(realName, name, 4); - realName[0] = tolower((int) realName[0]); - realName[1] = tolower((int) realName[1]); - realName[4] = '\0'; - if (setjmp(png_jmpbuf(png))) { - return false; - } - png_write_chunk(png, (png_bytep) realName, data, size); - return true; -} - -void PNGWriteClose(png_structp png, png_infop info) { - if (!setjmp(png_jmpbuf(png))) { - png_write_end(png, info); - } - png_destroy_write_struct(&png, &info); -} - -bool isPNG(struct VFile* source) { - png_byte header[PNG_HEADER_BYTES]; - source->seek(source, 0, SEEK_SET); - if (source->read(source, header, PNG_HEADER_BYTES) < PNG_HEADER_BYTES) { - return false; - } - return !png_sig_cmp(header, 0, PNG_HEADER_BYTES); -} - -png_structp PNGReadOpen(struct VFile* source, unsigned offset) { - png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); - if (!png) { - return 0; - } - if (setjmp(png_jmpbuf(png))) { - png_destroy_read_struct(&png, 0, 0); - return 0; - } - png_set_read_fn(png, source, _pngRead); - png_set_sig_bytes(png, offset); - return png; -} - -bool PNGInstallChunkHandler(png_structp png, void* context, ChunkHandler handler, const char* chunkName) { - if (setjmp(png_jmpbuf(png))) { - return false; - } - png_set_read_user_chunk_fn(png, context, handler); - int len = strlen(chunkName); - int chunks = 0; - char* chunkList = strdup(chunkName); - int i; - for (i = 4; i <= len; i += 5) { - chunkList[i] = '\0'; - ++chunks; - } - png_set_keep_unknown_chunks(png, PNG_HANDLE_CHUNK_ALWAYS, (png_bytep) chunkList, chunks); - free(chunkList); - return true; -} - -bool PNGReadHeader(png_structp png, png_infop info) { - if (setjmp(png_jmpbuf(png))) { - return false; - } - png_read_info(png, info); - return true; -} - -bool PNGIgnorePixels(png_structp png, png_infop info) { - if (setjmp(png_jmpbuf(png))) { - return false; - } - - unsigned height = png_get_image_height(png, info); - unsigned i; - for (i = 0; i < height; ++i) { - png_read_row(png, 0, 0); - } - return true; -} - -bool PNGReadPixels(png_structp png, png_infop info, void* pixels, unsigned width, unsigned height, unsigned stride) { - if (png_get_channels(png, info) != 3) { - return false; - } - - if (setjmp(png_jmpbuf(png))) { - return false; - } - - uint8_t* pixelData = pixels; - unsigned pngHeight = png_get_image_height(png, info); - if (height < pngHeight) { - pngHeight = height; - } - - unsigned pngWidth = png_get_image_width(png, info); - if (width < pngWidth) { - pngWidth = width; - } - - unsigned i; - png_bytep row = malloc(png_get_rowbytes(png, info)); - for (i = 0; i < pngHeight; ++i) { - png_read_row(png, row, 0); - unsigned x; - for (x = 0; x < pngWidth; ++x) { -#ifdef COLOR_16_BIT - uint16_t c = row[x * 3 + 2] >> 3; -#ifdef COLOR_5_6_5 - c |= (row[x * 3 + 1] << 3) & 0x7E0; - c |= (row[x * 3] << 8) & 0xF800; -#else - c |= (row[x * 3 + 1] << 2) & 0x3E0; - c |= (row[x * 3] << 7) & 0x7C00; -#endif - ((uint16_t*) pixelData)[stride * i + x] = c; -#else -#if __BIG_ENDIAN__ - pixelData[stride * i * 4 + x * 4 + 3] = row[x * 3]; - pixelData[stride * i * 4 + x * 4 + 2] = row[x * 3 + 1]; - pixelData[stride * i * 4 + x * 4 + 1] = row[x * 3 + 2]; - pixelData[stride * i * 4 + x * 4] = 0xFF; -#else - pixelData[stride * i * 4 + x * 4] = row[x * 3]; - pixelData[stride * i * 4 + x * 4 + 1] = row[x * 3 + 1]; - pixelData[stride * i * 4 + x * 4 + 2] = row[x * 3 + 2]; - pixelData[stride * i * 4 + x * 4 + 3] = 0xFF; -#endif -#endif - } - } - free(row); - return true; -} - -bool PNGReadPixelsA(png_structp png, png_infop info, void* pixels, unsigned width, unsigned height, unsigned stride) { - if (png_get_channels(png, info) != 4) { - return false; - } - - if (setjmp(png_jmpbuf(png))) { - return false; - } - - uint8_t* pixelData = pixels; - unsigned pngHeight = png_get_image_height(png, info); - if (height < pngHeight) { - pngHeight = height; - } - - unsigned pngWidth = png_get_image_width(png, info); - if (width < pngWidth) { - pngWidth = width; - } - - unsigned i; - png_bytep row = malloc(png_get_rowbytes(png, info)); - for (i = 0; i < pngHeight; ++i) { - png_read_row(png, row, 0); - unsigned x; - for (x = 0; x < pngWidth; ++x) { -#ifdef COLOR_16_BIT - uint16_t c = row[x * 4 + 2] >> 3; -#ifdef COLOR_5_6_5 - c |= (row[x * 4 + 1] << 3) & 0x7E0; - c |= (row[x * 4] << 8) & 0xF800; -#else - c |= (row[x * 4 + 1] << 2) & 0x3E0; - c |= (row[x * 4] << 7) & 0x7C00; -#endif - ((uint16_t*) pixelData)[stride * i + x] = c; -#else -#if __BIG_ENDIAN__ - pixelData[stride * i * 4 + x * 4 + 3] = row[x * 4]; - pixelData[stride * i * 4 + x * 4 + 2] = row[x * 4 + 1]; - pixelData[stride * i * 4 + x * 4 + 1] = row[x * 4 + 2]; - pixelData[stride * i * 4 + x * 4] = row[x * 4 + 3]; -#else - pixelData[stride * i * 4 + x * 4] = row[x * 4]; - pixelData[stride * i * 4 + x * 4 + 1] = row[x * 4 + 1]; - pixelData[stride * i * 4 + x * 4 + 2] = row[x * 4 + 2]; - pixelData[stride * i * 4 + x * 4 + 3] = row[x * 4 + 3]; -#endif -#endif - } - } - free(row); - return true; -} - -bool PNGReadPixels8(png_structp png, png_infop info, void* pixels, unsigned width, unsigned height, unsigned stride) { - if (png_get_channels(png, info) != 1) { - return false; - } - - if (setjmp(png_jmpbuf(png))) { - return false; - } - - uint8_t* pixelData = pixels; - unsigned pngHeight = png_get_image_height(png, info); - if (height < pngHeight) { - pngHeight = height; - } - - unsigned pngWidth = png_get_image_width(png, info); - if (width < pngWidth) { - pngWidth = width; - } - - unsigned i; - for (i = 0; i < pngHeight; ++i) { - png_read_row(png, &pixelData[stride * i], 0); - } - return true; -} - - -bool PNGReadFooter(png_structp png, png_infop end) { - if (setjmp(png_jmpbuf(png))) { - return false; - } - png_read_end(png, end); - return true; -} - -void PNGReadClose(png_structp png, png_infop info, png_infop end) { - png_destroy_read_struct(&png, &info, &end); -} - -#endif diff --git a/src/util/test/color.c b/src/util/test/color.c new file mode 100644 index 000000000..6977795c6 --- /dev/null +++ b/src/util/test/color.c @@ -0,0 +1,173 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include + +M_TEST_DEFINE(channelSwap32) { + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ARGB8, mCOLOR_ABGR8), 0xFFCCBBAA); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ABGR8, mCOLOR_ARGB8), 0xFFCCBBAA); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_XRGB8, mCOLOR_XBGR8), 0xFFCCBBAA); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_XBGR8, mCOLOR_XRGB8), 0xFFCCBBAA); + assert_int_equal(mColorConvert(0xAABBCC, mCOLOR_RGB8, mCOLOR_BGR8), 0xCCBBAA); + assert_int_equal(mColorConvert(0xAABBCC, mCOLOR_BGR8, mCOLOR_RGB8), 0xCCBBAA); + + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ARGB8, mCOLOR_RGBA8), 0xAABBCCFF); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ABGR8, mCOLOR_BGRA8), 0xAABBCCFF); + assert_int_equal(mColorConvert(0xAABBCCFF, mCOLOR_RGBA8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xAABBCCFF, mCOLOR_BGRA8, mCOLOR_ABGR8), 0xFFAABBCC); + + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ARGB8, mCOLOR_BGRA8), 0xCCBBAAFF); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ABGR8, mCOLOR_RGBA8), 0xCCBBAAFF); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_RGBA8, mCOLOR_ABGR8), 0xCCBBAAFF); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_BGRA8, mCOLOR_ARGB8), 0xCCBBAAFF); +} + +M_TEST_DEFINE(channelSwap16) { + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_ARGB5, mCOLOR_ABGR5), 0x83FF); + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_ABGR5, mCOLOR_ARGB5), 0x83FF); + assert_int_equal(mColorConvert(0x7FE0, mCOLOR_RGB5, mCOLOR_BGR5), 0x03FF); + assert_int_equal(mColorConvert(0x7FE0, mCOLOR_BGR5, mCOLOR_RGB5), 0x03FF); + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_RGB565, mCOLOR_BGR565), 0x07FF); + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_BGR565, mCOLOR_RGB565), 0x07FF); + + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_ARGB5, mCOLOR_RGBA5), 0xFFC1); + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_RGBA5, mCOLOR_ARGB5), 0x7FF0); +} + +M_TEST_DEFINE(convertQuantizeOpaque) { + assert_int_equal(mColorConvert(0xA0B0C0, mCOLOR_XRGB8, mCOLOR_RGB5), (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA1B1C1, mCOLOR_XRGB8, mCOLOR_RGB5), (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA2B2C2, mCOLOR_XRGB8, mCOLOR_RGB5), (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA4B4C4, mCOLOR_XRGB8, mCOLOR_RGB5), (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA8B8C8, mCOLOR_XRGB8, mCOLOR_RGB5), (0xA << 11) | (0x1B << 6) | (0x1C << 1) | 1); + + assert_int_equal(mColorConvert(0xA0B0C0, mCOLOR_XRGB8, mCOLOR_BGR5), (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA1B1C1, mCOLOR_XRGB8, mCOLOR_BGR5), (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA2B2C2, mCOLOR_XRGB8, mCOLOR_BGR5), (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA4B4C4, mCOLOR_XRGB8, mCOLOR_BGR5), (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA8B8C8, mCOLOR_XRGB8, mCOLOR_BGR5), (0xC << 11) | (0x1B << 6) | (0x1A << 1) | 1); + + assert_int_equal(mColorConvert(0xA0B0C0, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0xB << 7) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA1B1C1, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0xB << 7) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA2B2C2, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0xB << 7) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA4B4C4, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0xB << 7) | (0x1C << 1)); + assert_int_equal(mColorConvert(0xA8B8C8, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0x1B << 7) | (0x2C << 1) | 1); + assert_int_equal(mColorConvert(0xACBCCC, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0x1B << 7) | (0x3C << 1) | 1); + + assert_int_equal(mColorConvert(0xA0B0C0, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0xB << 7) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA1B1C1, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0xB << 7) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA2B2C2, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0xB << 7) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA4B4C4, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0xB << 7) | (0x1A << 1)); + assert_int_equal(mColorConvert(0xA8B8C8, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0x1B << 7) | (0x2A << 1) | 1); + assert_int_equal(mColorConvert(0xACBCCC, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0x1B << 7) | (0x3A << 1) | 1); +} + +M_TEST_DEFINE(convertQuantizeTransparent) { + assert_int_equal(mColorConvert(0xFFA0B0C0, mCOLOR_ARGB8, mCOLOR_ARGB5), 0x8000 | (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0x00A0B0C0, mCOLOR_ARGB8, mCOLOR_ARGB5), (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0xFEA0B0C0, mCOLOR_ARGB8, mCOLOR_ARGB5), 0x8000 | (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0x01A0B0C0, mCOLOR_ARGB8, mCOLOR_ARGB5), 0x8000 | (0xA << 11) | (0xB << 6) | (0xC << 1)); + + assert_int_equal(mColorConvert(0xFFA0B0C0, mCOLOR_ARGB8, mCOLOR_ABGR5), 0x8000 | (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0x00A0B0C0, mCOLOR_ARGB8, mCOLOR_ABGR5), (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0xFEA0B0C0, mCOLOR_ARGB8, mCOLOR_ABGR5), 0x8000 | (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0x01A0B0C0, mCOLOR_ARGB8, mCOLOR_ABGR5), 0x8000 | (0xC << 11) | (0xB << 6) | (0xA << 1)); + + assert_int_equal(mColorConvert(0xFFA0B0C0, mCOLOR_ARGB8, mCOLOR_RGBA5), 1 | (0xA << 12) | (0xB << 7) | (0xC << 2)); + assert_int_equal(mColorConvert(0x00A0B0C0, mCOLOR_ARGB8, mCOLOR_RGBA5), (0xA << 12) | (0xB << 7) | (0xC << 2)); + assert_int_equal(mColorConvert(0xFEA0B0C0, mCOLOR_ARGB8, mCOLOR_RGBA5), 1 | (0xA << 12) | (0xB << 7) | (0xC << 2)); + assert_int_equal(mColorConvert(0x01A0B0C0, mCOLOR_ARGB8, mCOLOR_RGBA5), 1 | (0xA << 12) | (0xB << 7) | (0xC << 2)); + + assert_int_equal(mColorConvert(0xFFA0B0C0, mCOLOR_ARGB8, mCOLOR_BGRA5), 1 | (0xC << 12) | (0xB << 7) | (0xA << 2)); + assert_int_equal(mColorConvert(0x00A0B0C0, mCOLOR_ARGB8, mCOLOR_BGRA5), (0xC << 12) | (0xB << 7) | (0xA << 2)); + assert_int_equal(mColorConvert(0xFEA0B0C0, mCOLOR_ARGB8, mCOLOR_BGRA5), 1 | (0xC << 12) | (0xB << 7) | (0xA << 2)); + assert_int_equal(mColorConvert(0x01A0B0C0, mCOLOR_ARGB8, mCOLOR_BGRA5), 1 | (0xC << 12) | (0xB << 7) | (0xA << 2)); +} + +M_TEST_DEFINE(convertToOpaque) { + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ARGB8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xFEAABBCC, mCOLOR_ARGB8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x01AABBCC, mCOLOR_ARGB8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x00AABBCC, mCOLOR_ARGB8, mCOLOR_XRGB8), 0xFFAABBCC); + + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ARGB8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0xFEAABBCC, mCOLOR_ARGB8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0x01AABBCC, mCOLOR_ARGB8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0x00AABBCC, mCOLOR_ARGB8, mCOLOR_RGB8), 0xAABBCC); + + assert_int_equal(mColorConvert(0xAABBCCFF, mCOLOR_RGBA8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xAABBCCFE, mCOLOR_RGBA8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xAABBCC01, mCOLOR_RGBA8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xAABBCC00, mCOLOR_RGBA8, mCOLOR_XRGB8), 0xFFAABBCC); + + assert_int_equal(mColorConvert(0xAABBCCFF, mCOLOR_RGBA8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0xAABBCCFE, mCOLOR_RGBA8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0xAABBCC01, mCOLOR_RGBA8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0xAABBCC00, mCOLOR_RGBA8, mCOLOR_RGB8), 0xAABBCC); + + assert_int_equal(mColorConvert(0x7FFF, mCOLOR_ARGB5, mCOLOR_RGB5), 0x7FFF); + assert_int_equal(mColorConvert(0xFFFF, mCOLOR_ARGB5, mCOLOR_RGB5), 0x7FFF); + + assert_int_equal(mColorConvert(0xFFFE, mCOLOR_RGBA5, mCOLOR_RGB5), 0x7FFF); + assert_int_equal(mColorConvert(0xFFFF, mCOLOR_RGBA5, mCOLOR_RGB5), 0x7FFF); +} + +M_TEST_DEFINE(convertToAlpha) { + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_XRGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xFEAABBCC, mCOLOR_XRGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x01AABBCC, mCOLOR_XRGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x00AABBCC, mCOLOR_XRGB8, mCOLOR_ARGB8), 0xFFAABBCC); + + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_RGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xFEAABBCC, mCOLOR_RGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x01AABBCC, mCOLOR_RGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x00AABBCC, mCOLOR_RGB8, mCOLOR_ARGB8), 0xFFAABBCC); + + assert_int_equal(mColorConvert(0x7FFF, mCOLOR_RGB5, mCOLOR_ARGB5), 0xFFFF); + assert_int_equal(mColorConvert(0xFFFF, mCOLOR_RGB5, mCOLOR_ARGB5), 0xFFFF); + + assert_int_equal(mColorConvert(0x7FFF, mCOLOR_RGB5, mCOLOR_RGBA5), 0xFFFF); + assert_int_equal(mColorConvert(0xFFFF, mCOLOR_RGB5, mCOLOR_RGBA5), 0xFFFF); +} + +M_TEST_DEFINE(convertFromGray) { + int i; + for (i = 0; i < 256; ++i) { + assert_int_equal(mColorConvert(i, mCOLOR_L8, mCOLOR_RGB8), (i << 16) | (i << 8) | i); + assert_int_equal(mColorConvert(i, mCOLOR_L8, mCOLOR_ARGB8), 0xFF000000 | (i << 16) | (i << 8) | i); + } +} + +M_TEST_DEFINE(convertToGray) { + int i; + for (i = 0; i < 256; ++i) { + assert_int_equal(mColorConvert((i << 16) | (i << 8) | i, mCOLOR_RGB8, mCOLOR_L8), i); + assert_int_equal(mColorConvert((i << 16) | (i << 8) | i, mCOLOR_ARGB8, mCOLOR_L8), i); + assert_int_equal(mColorConvert(0xFF000000 | (i << 16) | (i << 8) | i, mCOLOR_ARGB8, mCOLOR_L8), i); + } +} + +M_TEST_DEFINE(alphaBlendARGB8) { + assert_int_equal(mColorMixARGB8(0xFF012345, 0xFF987654), 0xFF012345); + assert_int_equal(mColorMixARGB8(0x00012345, 0xFF987654), 0xFF987654); + assert_int_equal(mColorMixARGB8(0x80012345, 0xFF987654), 0xFF4C4C4C); + assert_int_equal(mColorMixARGB8(0x80012345, 0x40987654), 0x9F1F3347); + assert_int_equal(mColorMixARGB8(0x01012345, 0xFF987654), 0xFF977553); + assert_int_equal(mColorMixARGB8(0x01012345, 0xFD987654), 0xFD977553); +} + +M_TEST_SUITE_DEFINE(Color, + cmocka_unit_test(channelSwap32), + cmocka_unit_test(channelSwap16), + cmocka_unit_test(convertQuantizeOpaque), + cmocka_unit_test(convertQuantizeTransparent), + cmocka_unit_test(convertToOpaque), + cmocka_unit_test(convertToAlpha), + cmocka_unit_test(convertFromGray), + cmocka_unit_test(convertToGray), + cmocka_unit_test(alphaBlendARGB8), +) diff --git a/src/util/test/geometry.c b/src/util/test/geometry.c new file mode 100644 index 000000000..419a1b1da --- /dev/null +++ b/src/util/test/geometry.c @@ -0,0 +1,1454 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include + +M_TEST_DEFINE(unionRectOrigin) { + struct mRectangle a = { + .x = 0, + .y = 0, + .width = 1, + .height = 1 + }; + struct mRectangle b = { + .x = 1, + .y = 1, + .width = 1, + .height = 1 + }; + mRectangleUnion(&a, &b); + assert_int_equal(a.x, 0); + assert_int_equal(a.y, 0); + assert_int_equal(a.width, 2); + assert_int_equal(a.height, 2); +} + +M_TEST_DEFINE(unionRectOriginSwapped) { + struct mRectangle a = { + .x = 1, + .y = 1, + .width = 1, + .height = 1 + }; + struct mRectangle b = { + .x = 0, + .y = 0, + .width = 1, + .height = 1 + }; + mRectangleUnion(&a, &b); + assert_int_equal(a.x, 0); + assert_int_equal(a.y, 0); + assert_int_equal(a.width, 2); + assert_int_equal(a.height, 2); +} + +M_TEST_DEFINE(unionRectNonOrigin) { + struct mRectangle a = { + .x = 1, + .y = 1, + .width = 1, + .height = 1 + }; + struct mRectangle b = { + .x = 2, + .y = 2, + .width = 1, + .height = 1 + }; + mRectangleUnion(&a, &b); + assert_int_equal(a.x, 1); + assert_int_equal(a.y, 1); + assert_int_equal(a.width, 2); + assert_int_equal(a.height, 2); +} + +M_TEST_DEFINE(unionRectOverlapping) { + struct mRectangle a = { + .x = 0, + .y = 0, + .width = 2, + .height = 2 + }; + struct mRectangle b = { + .x = 1, + .y = 1, + .width = 2, + .height = 2 + }; + mRectangleUnion(&a, &b); + assert_int_equal(a.x, 0); + assert_int_equal(a.y, 0); + assert_int_equal(a.width, 3); + assert_int_equal(a.height, 3); +} + +M_TEST_DEFINE(unionRectSubRect) { + struct mRectangle a = { + .x = 0, + .y = 0, + .width = 3, + .height = 3 + }; + struct mRectangle b = { + .x = 1, + .y = 1, + .width = 1, + .height = 1 + }; + mRectangleUnion(&a, &b); + assert_int_equal(a.x, 0); + assert_int_equal(a.y, 0); + assert_int_equal(a.width, 3); + assert_int_equal(a.height, 3); +} + +M_TEST_DEFINE(unionRectNegativeOrigin) { + struct mRectangle a = { + .x = -1, + .y = -1, + .width = 1, + .height = 1 + }; + struct mRectangle b = { + .x = 0, + .y = 0, + .width = 1, + .height = 1 + }; + mRectangleUnion(&a, &b); + assert_int_equal(a.x, -1); + assert_int_equal(a.y, -1); + assert_int_equal(a.width, 2); + assert_int_equal(a.height, 2); +} + +M_TEST_DEFINE(intersectRectNWNo) { + /* + * A..... + * ...... + * ..RR.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 0, + .y = 0, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectNWCorner0) { + /* + * ...... + * .A.... + * ..RR.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 1, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectNWCorner1) { + /* + * ...... + * .AA... + * .AXR.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 1, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectNWFullN) { + /* + * ...... + * .AAA.. + * .AXX.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 1, + .width = 3, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectNWFullW) { + /* + * ...... + * .AA... + * .AXR.. + * .AXR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 1, + .width = 2, + .height = 3 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectNNo) { + /* + * ..AA.. + * ...... + * ..RR.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 0, + .width = 2, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectN0) { + /* + * ...... + * ..AA.. + * ..RR.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 1, + .width = 2, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectN1) { + /* + * ...... + * ..AA.. + * ..XX.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 1, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectNFull) { + /* + * ...... + * ..AA.. + * ..XX.. + * ..XX.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 1, + .width = 2, + .height = 3 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectNENo) { + /* + * .....A + * ...... + * ..RR.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 5, + .y = 0, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectNECorner0) { + /* + * ...... + * ....A. + * ..RR.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 4, + .y = 0, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectNECorner1) { + /* + * ...... + * ..,AA. + * ..RXA. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 3, + .y = 1, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 3); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectNEFullN) { + /* + * ...... + * ..AAA. + * ..XXA. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 1, + .width = 3, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectNEFullE) { + /* + * ...... + * ...AA. + * ..RXA. + * ..RXA. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 3, + .y = 1, + .width = 2, + .height = 3 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 3); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectWNo) { + /* + * ...... + * ...... + * A.RR.. + * A.RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 0, + .y = 2, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectW0) { + /* + * ...... + * ...... + * .ARR.. + * .ARR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 2, + .width = 1, + .height = 2 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectW1) { + /* + * ...... + * ...... + * .AXR.. + * .AXR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 2, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectWFull) { + /* + * ...... + * ...... + * .AXX.. + * .AXX.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 2, + .width = 3, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectSWNo) { + /* + * ...... + * ...... + * ..RR.. + * ..RR.. + * ...... + * A..... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 0, + .y = 5, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectSWCorner0) { + /* + * ...... + * ...... + * ..RR.. + * ..RR.. + * .A.... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 4, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectSWCorner1) { + /* + * ...... + * ...... + * ..RR.. + * .AXR.. + * .AA... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 3, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 3); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectSWFullS) { + /* + * ...... + * ...... + * ..RR.. + * .AXX.. + * .AAA.. + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 3, + .width = 3, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 3); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectSWFullW) { + /* + * ...... + * ...... + * .AXR.. + * .AXR.. + * .AA... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 2, + .width = 2, + .height = 3 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectSNo) { + /* + * ...... + * ...... + * ..RR.. + * ..RR.. + * ...... + * ..AA.. + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 5, + .width = 2, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectS0) { + /* + * ...... + * ...... + * ..RR.. + * ..RR.. + * ..AA.. + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 4, + .width = 2, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectS1) { + /* + * ...... + * ...... + * ..RR.. + * ..XX.. + * ..AA.. + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 3, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 3); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectSFull) { + /* + * ...... + * ...... + * ..XX.. + * ..XX.. + * ..AA.. + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 2, + .width = 2, + .height = 3 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectSENo) { + /* + * ...... + * ...... + * ..RR.. + * ..RR.. + * ...... + * .....A + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 5, + .y = 5, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectSECorner0) { + /* + * ...... + * ...... + * ..RR.. + * ..RR.. + * ....A. + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 4, + .y = 4, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectSECorner1) { + /* + * ...... + * ..,... + * ..RR.. + * ..RXA. + * ...AA. + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 3, + .y = 3, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 3); + assert_int_equal(ref.y, 3); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectSEFullS) { + /* + * ...... + * ...... + * ..RR.. + * ..XXA. + * ..AAA. + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 3, + .width = 3, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 3); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectSEFullE) { + /* + * ...... + * ...... + * ..RXA. + * ..RXA. + * ...AA. + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 3, + .y = 2, + .width = 2, + .height = 3 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 3); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectENo) { + /* + * ...... + * ...... + * ..RR.A + * ..RR.A + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 5, + .y = 2, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectE0) { + /* + * ...... + * ...... + * ..RRA. + * ..RRA. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 4, + .y = 2, + .width = 1, + .height = 2 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectE1) { + /* + * ...... + * ...... + * ..RXA. + * ..RXA. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 3, + .y = 2, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 3); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectEFull) { + /* + * ...... + * ...... + * ..XXA. + * ..XXA. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 2, + .width = 3, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectInternalNE) { + /* + * ...... + * ...... + * ..XR.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 2, + .width = 1, + .height = 1 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectInternalNW) { + /* + * ...... + * ...... + * ..RX.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 3, + .y = 2, + .width = 1, + .height = 1 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 3); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectInternalSE) { + /* + * ...... + * ...... + * ..RR.. + * ..XR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 3, + .width = 1, + .height = 1 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 3); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectInternalSW) { + /* + * ...... + * ...... + * ..RR.. + * ..RX.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 3, + .y = 3, + .width = 1, + .height = 1 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 3); + assert_int_equal(ref.y, 3); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectInternalN) { + /* + * ...... + * ...... + * ..XX.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 2, + .width = 2, + .height = 1 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectInternalW) { + /* + * ...... + * ...... + * ..XR.. + * ..XR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 2, + .width = 1, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectInternalS) { + /* + * ...... + * ...... + * ..RR.. + * ..XX.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 3, + .width = 2, + .height = 1 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 3); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectInternalE) { + /* + * ...... + * ...... + * ..RX.. + * ..RX.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 3, + .y = 2, + .width = 1, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 3); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectEqual) { + /* + * ...... + * ...... + * ..XX.. + * ..XX.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(centerRectBasic) { + struct mRectangle ref = { + .x = 0, + .y = 0, + .width = 4, + .height = 4 + }; + struct mRectangle a = { + .x = 0, + .y = 0, + .width = 2, + .height = 2 + }; + mRectangleCenter(&ref, &a); + assert_int_equal(a.x, 1); + assert_int_equal(a.y, 1); +} + +M_TEST_DEFINE(centerRectRoundDown) { + struct mRectangle ref = { + .x = 0, + .y = 0, + .width = 4, + .height = 4 + }; + struct mRectangle a = { + .x = 0, + .y = 0, + .width = 1, + .height = 1 + }; + mRectangleCenter(&ref, &a); + assert_int_equal(a.x, 1); + assert_int_equal(a.y, 1); +} + +M_TEST_DEFINE(centerRectRoundDown2) { + struct mRectangle ref = { + .x = 0, + .y = 0, + .width = 4, + .height = 4 + }; + struct mRectangle a = { + .x = 0, + .y = 0, + .width = 3, + .height = 2 + }; + mRectangleCenter(&ref, &a); + assert_int_equal(a.x, 0); + assert_int_equal(a.y, 1); +} + +M_TEST_DEFINE(centerRectOffset) { + struct mRectangle ref = { + .x = 1, + .y = 1, + .width = 4, + .height = 4 + }; + struct mRectangle a = { + .x = 0, + .y = 0, + .width = 2, + .height = 2 + }; + mRectangleCenter(&ref, &a); + assert_int_equal(a.x, 2); + assert_int_equal(a.y, 2); +} + +M_TEST_SUITE_DEFINE(Geometry, + cmocka_unit_test(unionRectOrigin), + cmocka_unit_test(unionRectOriginSwapped), + cmocka_unit_test(unionRectNonOrigin), + cmocka_unit_test(unionRectOverlapping), + cmocka_unit_test(unionRectSubRect), + cmocka_unit_test(unionRectNegativeOrigin), + cmocka_unit_test(intersectRectNWNo), + cmocka_unit_test(intersectRectNWCorner0), + cmocka_unit_test(intersectRectNWCorner1), + cmocka_unit_test(intersectRectNWFullN), + cmocka_unit_test(intersectRectNWFullW), + cmocka_unit_test(intersectRectNNo), + cmocka_unit_test(intersectRectN0), + cmocka_unit_test(intersectRectN1), + cmocka_unit_test(intersectRectNFull), + cmocka_unit_test(intersectRectNENo), + cmocka_unit_test(intersectRectNECorner0), + cmocka_unit_test(intersectRectNECorner1), + cmocka_unit_test(intersectRectNEFullN), + cmocka_unit_test(intersectRectNEFullE), + cmocka_unit_test(intersectRectWNo), + cmocka_unit_test(intersectRectW0), + cmocka_unit_test(intersectRectW1), + cmocka_unit_test(intersectRectWFull), + cmocka_unit_test(intersectRectSWNo), + cmocka_unit_test(intersectRectSWCorner0), + cmocka_unit_test(intersectRectSWCorner1), + cmocka_unit_test(intersectRectSWFullS), + cmocka_unit_test(intersectRectSWFullW), + cmocka_unit_test(intersectRectSNo), + cmocka_unit_test(intersectRectS0), + cmocka_unit_test(intersectRectS1), + cmocka_unit_test(intersectRectSFull), + cmocka_unit_test(intersectRectSENo), + cmocka_unit_test(intersectRectSECorner0), + cmocka_unit_test(intersectRectSECorner1), + cmocka_unit_test(intersectRectSEFullS), + cmocka_unit_test(intersectRectSEFullE), + cmocka_unit_test(intersectRectENo), + cmocka_unit_test(intersectRectE0), + cmocka_unit_test(intersectRectE1), + cmocka_unit_test(intersectRectEFull), + cmocka_unit_test(intersectRectInternalNE), + cmocka_unit_test(intersectRectInternalNW), + cmocka_unit_test(intersectRectInternalSE), + cmocka_unit_test(intersectRectInternalSW), + cmocka_unit_test(intersectRectInternalN), + cmocka_unit_test(intersectRectInternalW), + cmocka_unit_test(intersectRectInternalS), + cmocka_unit_test(intersectRectInternalE), + cmocka_unit_test(intersectRectEqual), + cmocka_unit_test(centerRectBasic), + cmocka_unit_test(centerRectRoundDown), + cmocka_unit_test(centerRectRoundDown2), + cmocka_unit_test(centerRectOffset), +) diff --git a/src/util/test/image.c b/src/util/test/image.c new file mode 100644 index 000000000..eed41a913 --- /dev/null +++ b/src/util/test/image.c @@ -0,0 +1,1982 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include +#ifdef USE_PNG +#include +#endif +#include + +M_TEST_DEFINE(zeroDim) { + assert_null(mImageCreate(0, 0, mCOLOR_ABGR8)); + assert_null(mImageCreate(1, 0, mCOLOR_ABGR8)); + assert_null(mImageCreate(0, 1, mCOLOR_ABGR8)); + struct mImage* image = mImageCreate(1, 1, mCOLOR_ABGR8); + assert_non_null(image); + mImageDestroy(image); +} + +M_TEST_DEFINE(pitchRead) { + static uint8_t buffer[12] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB + }; + + struct mImage image = { + .data = buffer, + .height = 1 + }; + int i; + + image.depth = 1; + image.width = 12; + image.format = mCOLOR_L8; + + for (i = 0; i < 12; ++i) { + assert_int_equal(mImageGetPixelRaw(&image, i, 0), i); + } + + image.depth = 2; + image.width = 6; + image.format = mCOLOR_RGB5; + + for (i = 0; i < 6; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 2) << 8 | (i * 2 + 1)); +#else + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 2 + 1) << 8 | (i * 2)); +#endif + } + + image.depth = 3; + image.width = 4; + image.format = mCOLOR_RGB8; + + for (i = 0; i < 4; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 3) << 16 | (i * 3 + 1) << 8 | (i * 3 + 2)); +#else + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 3 + 2) << 16 | (i * 3 + 1) << 8 | (i * 3)); +#endif + } + + image.depth = 4; + image.width = 3; + image.format = mCOLOR_ARGB8; + + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 4) << 24 | (i * 4 + 1) << 16 | (i * 4 + 2) << 8 | (i * 4 + 3)); +#else + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 4 + 3) << 24 | (i * 4 + 2) << 16 | (i * 4 + 1) << 8 | (i * 4)); +#endif + } +} + +M_TEST_DEFINE(strideRead) { + static uint8_t buffer[12] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB + }; + + struct mImage image = { + .data = buffer, + .width = 1 + }; + int i; + + image.depth = 1; + image.stride = 1; + image.height = 12; + image.format = mCOLOR_L8; + + for (i = 0; i < 12; ++i) { + assert_int_equal(mImageGetPixelRaw(&image, 0, i), i); + } + + image.depth = 1; + image.stride = 2; + image.height = 6; + image.format = mCOLOR_L8; + + for (i = 0; i < 6; ++i) { + assert_int_equal(mImageGetPixelRaw(&image, 0, i), i * 2); + } + + image.depth = 2; + image.stride = 1; + image.height = 6; + image.format = mCOLOR_RGB5; + + for (i = 0; i < 6; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 2) << 8 | (i * 2 + 1)); +#else + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 2 + 1) << 8 | (i * 2)); +#endif + } + + image.depth = 2; + image.stride = 2; + image.height = 3; + image.format = mCOLOR_RGB5; + + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 4) << 8 | (i * 4 + 1)); +#else + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 4 + 1) << 8 | (i * 4)); +#endif + } + + image.depth = 3; + image.stride = 1; + image.height = 4; + image.format = mCOLOR_RGB8; + + for (i = 0; i < 4; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 3) << 16 | (i * 3 + 1) << 8 | (i * 3 + 2)); +#else + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 3 + 2) << 16 | (i * 3 + 1) << 8 | (i * 3)); +#endif + } + + image.depth = 3; + image.stride = 2; + image.height = 2; + image.format = mCOLOR_RGB8; + + for (i = 0; i < 2; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 6) << 16 | (i * 6 + 1) << 8 | (i * 6 + 2)); +#else + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 6 + 2) << 16 | (i * 6 + 1) << 8 | (i * 6)); +#endif + } + + image.depth = 4; + image.stride = 1; + image.height = 3; + image.format = mCOLOR_ARGB8; + + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 4) << 24 | (i * 4 + 1) << 16 | (i * 4 + 2) << 8 | (i * 4 + 3)); +#else + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 4 + 3) << 24 | (i * 4 + 2) << 16 | (i * 4 + 1) << 8 | (i * 4)); +#endif + } +} + +M_TEST_DEFINE(oobRead) { + static uint8_t buffer[8] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + + struct mImage image = { + .data = buffer, + .width = 1, + .height = 1, + .stride = 1 + }; + + image.depth = 1; + image.format = mCOLOR_L8; + + assert_int_equal(mImageGetPixelRaw(&image, 0, 0), 0xFF); + assert_int_equal(mImageGetPixelRaw(&image, 1, 0), 0); + assert_int_equal(mImageGetPixelRaw(&image, 0, 1), 0); + assert_int_equal(mImageGetPixelRaw(&image, 1, 1), 0); + + image.depth = 2; + image.format = mCOLOR_RGB5; + + assert_int_equal(mImageGetPixelRaw(&image, 0, 0), 0xFFFF); + assert_int_equal(mImageGetPixelRaw(&image, 1, 0), 0); + assert_int_equal(mImageGetPixelRaw(&image, 0, 1), 0); + assert_int_equal(mImageGetPixelRaw(&image, 1, 1), 0); + + image.depth = 3; + image.format = mCOLOR_RGB8; + + assert_int_equal(mImageGetPixelRaw(&image, 0, 0), 0xFFFFFF); + assert_int_equal(mImageGetPixelRaw(&image, 1, 0), 0); + assert_int_equal(mImageGetPixelRaw(&image, 0, 1), 0); + assert_int_equal(mImageGetPixelRaw(&image, 1, 1), 0); + + image.depth = 4; + image.format = mCOLOR_ARGB8; + + assert_int_equal(mImageGetPixelRaw(&image, 0, 0), 0xFFFFFFFF); + assert_int_equal(mImageGetPixelRaw(&image, 1, 0), 0); + assert_int_equal(mImageGetPixelRaw(&image, 0, 1), 0); + assert_int_equal(mImageGetPixelRaw(&image, 1, 1), 0); +} + +M_TEST_DEFINE(pitchWrite) { + static const uint8_t baseline[12] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB + }; + + uint8_t buffer[12]; + + struct mImage image = { + .data = buffer, + .height = 1 + }; + int i; + + image.depth = 1; + image.width = 12; + image.format = mCOLOR_L8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 12; ++i) { + mImageSetPixelRaw(&image, i, 0, i); + } + assert_memory_equal(baseline, buffer, sizeof(baseline)); + + image.depth = 2; + image.width = 6; + image.format = mCOLOR_RGB5; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 6; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, i, 0, (i * 2) << 8 | (i * 2 + 1)); +#else + mImageSetPixelRaw(&image, i, 0, (i * 2 + 1) << 8 | (i * 2)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(baseline)); + + image.depth = 3; + image.width = 4; + image.format = mCOLOR_RGB8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 4; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, i, 0, (i * 3) << 16 | (i * 3 + 1) << 8 | (i * 3 + 2)); +#else + mImageSetPixelRaw(&image, i, 0, (i * 3 + 2) << 16 | (i * 3 + 1) << 8 | (i * 3)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(baseline)); + + image.depth = 4; + image.width = 3; + image.format = mCOLOR_ARGB8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, i, 0, (i * 4) << 24 | (i * 4 + 1) << 16 | (i * 4 + 2) << 8 | (i * 4 + 3)); +#else + mImageSetPixelRaw(&image, i, 0, (i * 4 + 3) << 24 | (i * 4 + 2) << 16 | (i * 4 + 1) << 8 | (i * 4)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(baseline)); +} + +M_TEST_DEFINE(strideWrite) { + static const uint8_t baseline[12] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB + }; + static const uint8_t baseline2x1[12] = { + 0x0, 0x0, 0x2, 0x0, 0x4, 0x0, 0x6, 0x0, 0x8, 0x0, 0xA, 0x0 + }; + static const uint8_t baseline2x2[12] = { + 0x0, 0x1, 0x0, 0x0, 0x4, 0x5, 0x0, 0x0, 0x8, 0x9, 0x0, 0x0 + }; + static const uint8_t baseline3x2[12] = { + 0x0, 0x1, 0x2, 0x0, 0x0, 0x0, 0x6, 0x7, 0x8, 0x0, 0x0, 0x0 + }; + + uint8_t buffer[12]; + + struct mImage image = { + .data = buffer, + .width = 1 + }; + int i; + + image.depth = 1; + image.stride = 1; + image.height = 12; + image.format = mCOLOR_L8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 12; ++i) { + mImageSetPixelRaw(&image, 0, i, i); + } + assert_memory_equal(baseline, buffer, sizeof(buffer)); + + image.depth = 1; + image.stride = 2; + image.height = 6; + image.format = mCOLOR_L8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 6; ++i) { + mImageSetPixelRaw(&image, 0, i, i * 2); + } + assert_memory_equal(baseline2x1, buffer, sizeof(buffer)); + + image.depth = 2; + image.stride = 1; + image.height = 6; + image.format = mCOLOR_RGB5; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 6; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, 0, i, (i * 2) << 8 | (i * 2 + 1)); +#else + mImageSetPixelRaw(&image, 0, i, (i * 2 + 1) << 8 | (i * 2)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(buffer)); + + image.depth = 2; + image.stride = 2; + image.height = 3; + image.format = mCOLOR_RGB5; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, 0, i, (i * 4) << 8 | (i * 4 + 1)); +#else + mImageSetPixelRaw(&image, 0, i, (i * 4 + 1) << 8 | (i * 4)); +#endif + } + assert_memory_equal(baseline2x2, buffer, sizeof(buffer)); + + image.depth = 3; + image.stride = 1; + image.height = 4; + image.format = mCOLOR_RGB8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 4; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, 0, i, (i * 3) << 16 | (i * 3 + 1) << 8 | (i * 3 + 2)); +#else + mImageSetPixelRaw(&image, 0, i, (i * 3 + 2) << 16 | (i * 3 + 1) << 8 | (i * 3)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(buffer)); + + image.depth = 3; + image.stride = 2; + image.height = 2; + image.format = mCOLOR_RGB8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 2; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, 0, i, (i * 6) << 16 | (i * 6 + 1) << 8 | (i * 6 + 2)); +#else + mImageSetPixelRaw(&image, 0, i, (i * 6 + 2) << 16 | (i * 6 + 1) << 8 | (i * 6)); +#endif + } + assert_memory_equal(baseline3x2, buffer, sizeof(buffer)); + + image.depth = 4; + image.stride = 1; + image.height = 3; + image.format = mCOLOR_ARGB8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, 0, i, (i * 4) << 24 | (i * 4 + 1) << 16 | (i * 4 + 2) << 8 | (i * 4 + 3)); +#else + mImageSetPixelRaw(&image, 0, i, (i * 4 + 3) << 24 | (i * 4 + 2) << 16 | (i * 4 + 1) << 8 | (i * 4)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(buffer)); +} + +M_TEST_DEFINE(oobWrite) { + static uint8_t buffer[8]; + + struct mImage image = { + .data = buffer, + .width = 1, + .height = 1, + .stride = 1 + }; + + image.depth = 1; + image.format = mCOLOR_L8; + + memset(buffer, 0, sizeof(buffer)); + mImageSetPixelRaw(&image, 0, 0, 0xFF); + mImageSetPixelRaw(&image, 1, 0, 0); + mImageSetPixelRaw(&image, 0, 1, 0); + mImageSetPixelRaw(&image, 1, 1, 0); + assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF }), sizeof(buffer)); + + image.depth = 2; + image.format = mCOLOR_RGB5; + + memset(buffer, 0, sizeof(buffer)); + mImageSetPixelRaw(&image, 0, 0, 0xFFFF); + mImageSetPixelRaw(&image, 1, 0, 0); + mImageSetPixelRaw(&image, 0, 1, 0); + mImageSetPixelRaw(&image, 1, 1, 0); + assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF, 0xFF }), sizeof(buffer)); + + image.depth = 3; + image.format = mCOLOR_RGB8; + + memset(buffer, 0, sizeof(buffer)); + mImageSetPixelRaw(&image, 0, 0, 0xFFFFFF); + mImageSetPixelRaw(&image, 1, 0, 0); + mImageSetPixelRaw(&image, 0, 1, 0); + mImageSetPixelRaw(&image, 1, 1, 0); + assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF, 0xFF, 0xFF }), sizeof(buffer)); + + image.depth = 4; + image.format = mCOLOR_ARGB8; + + memset(buffer, 0, sizeof(buffer)); + mImageSetPixelRaw(&image, 0, 0, 0xFFFFFFFF); + mImageSetPixelRaw(&image, 1, 0, 0); + mImageSetPixelRaw(&image, 0, 1, 0); + mImageSetPixelRaw(&image, 1, 1, 0); + assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF, 0xFF, 0xFF, 0xFF }), sizeof(buffer)); +} + +M_TEST_DEFINE(paletteAccess) { + struct mImage* image = mImageCreate(1, 1, mCOLOR_PAL8); + mImageSetPaletteSize(image, 1); + + mImageSetPaletteEntry(image, 0, 0xFF00FF00); + mImageSetPixelRaw(image, 0, 0, 0); + assert_int_equal(mImageGetPixelRaw(image, 0, 0), 0); + assert_int_equal(mImageGetPixel(image, 0, 0), 0xFF00FF00); + + mImageSetPaletteEntry(image, 0, 0x01234567); + assert_int_equal(mImageGetPixelRaw(image, 0, 0), 0); + assert_int_equal(mImageGetPixel(image, 0, 0), 0x01234567); + + mImageDestroy(image); +} + +#ifdef USE_PNG +M_TEST_DEFINE(loadPng24) { + const uint8_t data[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, + 0x08, 0x02, 0x00, 0x00, 0x00, 0xfd, 0xd4, 0x9a, 0x73, 0x00, 0x00, 0x00, + 0x12, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x63, 0xf8, 0xff, 0xff, 0x3f, + 0x03, 0x03, 0x03, 0x03, 0x84, 0x00, 0x00, 0x2a, 0xe3, 0x04, 0xfc, 0xe8, + 0x51, 0xc0, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, + 0x42, 0x60, 0x82 + }; + + struct VFile* vf = VFileFromConstMemory(data, sizeof(data)); + struct mImage* image = mImageLoadVF(vf); + vf->close(vf); + + assert_non_null(image); + assert_int_equal(image->width, 2); + assert_int_equal(image->height, 2); + assert_int_equal(image->format, mCOLOR_XBGR8); + + assert_int_equal(mImageGetPixel(image, 0, 0) & 0xFFFFFF, 0xFFFFFF); + assert_int_equal(mImageGetPixel(image, 1, 0) & 0xFFFFFF, 0x000000); + assert_int_equal(mImageGetPixel(image, 0, 1) & 0xFFFFFF, 0xFF0000); + assert_int_equal(mImageGetPixel(image, 1, 1) & 0xFFFFFF, 0x0000FF); + + mImageDestroy(image); +} + +M_TEST_DEFINE(loadPng32) { + const uint8_t data[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x72, 0xb6, 0x0d, 0x24, 0x00, 0x00, 0x00, + 0x1a, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x05, 0xc1, 0x31, 0x01, 0x00, + 0x00, 0x08, 0xc0, 0x20, 0x6c, 0x66, 0x25, 0xfb, 0x1f, 0x13, 0xa6, 0x0a, + 0xa7, 0x5a, 0x78, 0x58, 0x7b, 0x07, 0xac, 0xe9, 0x00, 0x3d, 0x95, 0x00, + 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 + }; + + struct VFile* vf = VFileFromConstMemory(data, sizeof(data)); + struct mImage* image = mImageLoadVF(vf); + vf->close(vf); + + assert_non_null(image); + assert_int_equal(image->width, 2); + assert_int_equal(image->height, 2); + assert_int_equal(image->format, mCOLOR_ABGR8); + + assert_int_equal(mImageGetPixel(image, 0, 0) >> 24, 0xFF); + assert_int_equal(mImageGetPixel(image, 1, 0) >> 24, 0x70); + assert_int_equal(mImageGetPixel(image, 0, 1) >> 24, 0x40); + assert_int_equal(mImageGetPixel(image, 1, 1) >> 24, 0x00); + + mImageDestroy(image); +} + +M_TEST_DEFINE(loadPngPalette) { + const uint8_t data[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, + 0x08, 0x03, 0x00, 0x00, 0x00, 0x45, 0x68, 0xfd, 0x16, 0x00, 0x00, 0x00, + 0x0c, 0x50, 0x4c, 0x54, 0x45, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, + 0xff, 0x00, 0x00, 0x00, 0xff, 0x9b, 0xc0, 0x13, 0xdc, 0x00, 0x00, 0x00, + 0x04, 0x74, 0x52, 0x4e, 0x53, 0x00, 0xc0, 0x80, 0x40, 0x6f, 0x63, 0x29, + 0x01, 0x00, 0x00, 0x00, 0x0e, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x63, + 0x60, 0x60, 0x64, 0x60, 0x62, 0x06, 0x00, 0x00, 0x11, 0x00, 0x07, 0x69, + 0xe2, 0x2a, 0x44, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, + 0x42, 0x60, 0x82 + }; + + struct VFile* vf = VFileFromConstMemory(data, sizeof(data)); + struct mImage* image = mImageLoadVF(vf); + vf->close(vf); + + assert_non_null(image); + assert_int_equal(image->width, 2); + assert_int_equal(image->height, 2); + assert_int_equal(image->format, mCOLOR_PAL8); + + assert_int_equal(mImageGetPixel(image, 0, 0), 0x00000000); + assert_int_equal(mImageGetPixel(image, 1, 0), 0xC0FF0000); + assert_int_equal(mImageGetPixel(image, 0, 1), 0x8000FF00); + assert_int_equal(mImageGetPixel(image, 1, 1), 0x400000FF); + + mImageDestroy(image); +} + +M_TEST_DEFINE(savePngNative) { + struct mImage* image = mImageCreate(1, 1, mCOLOR_ABGR8); + mImageSetPixel(image, 0, 0, 0x01234567); + + struct VFile* vf = VFileMemChunk(NULL, 0); + assert_true(mImageSaveVF(image, vf, "png")); + mImageDestroy(image); + + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + assert_true(isPNG(vf)); + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + + image = mImageLoadVF(vf); + vf->close(vf); + assert_non_null(image); + assert_int_equal(mImageGetPixel(image, 0, 0), 0x01234567); + mImageDestroy(image); +} + +M_TEST_DEFINE(savePngNonNative) { + struct mImage* image = mImageCreate(1, 1, mCOLOR_ARGB8); + mImageSetPixel(image, 0, 0, 0x01234567); + + struct VFile* vf = VFileMemChunk(NULL, 0); + assert_true(mImageSaveVF(image, vf, "png")); + mImageDestroy(image); + + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + assert_true(isPNG(vf)); + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + + image = mImageLoadVF(vf); + vf->close(vf); + assert_non_null(image); + assert_int_equal(image->format, mCOLOR_ABGR8); + assert_int_equal(mImageGetPixel(image, 0, 0), 0x01234567); + mImageDestroy(image); +} + +M_TEST_DEFINE(savePngRoundTrip) { + const enum mColorFormat formats[] = { + mCOLOR_XBGR8, mCOLOR_XRGB8, + mCOLOR_BGRX8, mCOLOR_RGBX8, + mCOLOR_ABGR8, mCOLOR_ARGB8, + mCOLOR_BGRA8, mCOLOR_RGBA8, + mCOLOR_RGB5, mCOLOR_BGR5, + mCOLOR_ARGB5, mCOLOR_ABGR5, + mCOLOR_RGBA5, mCOLOR_BGRA5, + mCOLOR_RGB565, mCOLOR_BGR565, + mCOLOR_RGB8, mCOLOR_BGR8, + 0 + }; + + int i; + for (i = 0; formats[i]; ++i) { + struct mImage* image = mImageCreate(2, 2, formats[i]); + mImageSetPixel(image, 0, 0, 0xFF181008); + mImageSetPixel(image, 1, 0, 0xFF100818); + mImageSetPixel(image, 0, 1, 0xFF081810); + mImageSetPixel(image, 1, 1, 0xFF181008); + assert_int_equal(mImageGetPixel(image, 0, 0), 0xFF181008); + assert_int_equal(mImageGetPixel(image, 1, 0), 0xFF100818); + assert_int_equal(mImageGetPixel(image, 0, 1), 0xFF081810); + assert_int_equal(mImageGetPixel(image, 1, 1), 0xFF181008); + + struct VFile* vf = VFileMemChunk(NULL, 0); + assert_true(mImageSaveVF(image, vf, "png")); + mImageDestroy(image); + + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + assert_true(isPNG(vf)); + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + + image = mImageLoadVF(vf); + vf->close(vf); + assert_non_null(image); + assert_int_equal(mImageGetPixel(image, 0, 0), 0xFF181008); + assert_int_equal(mImageGetPixel(image, 1, 0), 0xFF100818); + assert_int_equal(mImageGetPixel(image, 0, 1), 0xFF081810); + assert_int_equal(mImageGetPixel(image, 1, 1), 0xFF181008); + mImageDestroy(image); + } +} + +M_TEST_DEFINE(savePngL8) { + struct mImage* image = mImageCreate(2, 2, mCOLOR_L8); + mImageSetPixel(image, 0, 0, 0xFF000000); + mImageSetPixel(image, 1, 0, 0xFF555555); + mImageSetPixel(image, 0, 1, 0xFFAAAAAA); + mImageSetPixel(image, 1, 1, 0xFFFFFFFF); + assert_int_equal(mImageGetPixel(image, 0, 0), 0xFF000000); + assert_int_equal(mImageGetPixel(image, 1, 0), 0xFF555555); + assert_int_equal(mImageGetPixel(image, 0, 1), 0xFFAAAAAA); + assert_int_equal(mImageGetPixel(image, 1, 1), 0xFFFFFFFF); + + struct VFile* vf = VFileMemChunk(NULL, 0); + assert_true(mImageSaveVF(image, vf, "png")); + mImageDestroy(image); + + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + assert_true(isPNG(vf)); + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + + image = mImageLoadVF(vf); + vf->close(vf); + assert_non_null(image); + assert_int_equal(mImageGetPixel(image, 0, 0), 0xFF000000); + assert_int_equal(mImageGetPixel(image, 1, 0), 0xFF555555); + assert_int_equal(mImageGetPixel(image, 0, 1), 0xFFAAAAAA); + assert_int_equal(mImageGetPixel(image, 1, 1), 0xFFFFFFFF); + mImageDestroy(image); +} + +M_TEST_DEFINE(savePngPal8) { + struct mImage* image = mImageCreate(2, 2, mCOLOR_PAL8); + mImageSetPaletteSize(image, 4); + mImageSetPaletteEntry(image, 0, 0x00000000); + mImageSetPaletteEntry(image, 1, 0x40FF0000); + mImageSetPaletteEntry(image, 2, 0x8000FF00); + mImageSetPaletteEntry(image, 3, 0xC00000FF); + + mImageSetPixelRaw(image, 0, 0, 0); + mImageSetPixelRaw(image, 1, 0, 1); + mImageSetPixelRaw(image, 0, 1, 2); + mImageSetPixelRaw(image, 1, 1, 3); + assert_int_equal(mImageGetPixel(image, 0, 0), 0x00000000); + assert_int_equal(mImageGetPixel(image, 1, 0), 0x40FF0000); + assert_int_equal(mImageGetPixel(image, 0, 1), 0x8000FF00); + assert_int_equal(mImageGetPixel(image, 1, 1), 0xC00000FF); + + struct VFile* vf = VFileMemChunk(NULL, 0); + assert_true(mImageSaveVF(image, vf, "png")); + mImageDestroy(image); + + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + assert_true(isPNG(vf)); + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + + image = mImageLoadVF(vf); + vf->close(vf); + assert_non_null(image); + assert_int_equal(image->format, mCOLOR_PAL8); + assert_int_equal(mImageGetPixelRaw(image, 0, 0), 0); + assert_int_equal(mImageGetPixelRaw(image, 1, 0), 1); + assert_int_equal(mImageGetPixelRaw(image, 0, 1), 2); + assert_int_equal(mImageGetPixelRaw(image, 1, 1), 3); + assert_int_equal(mImageGetPixel(image, 0, 0), 0x00000000); + assert_int_equal(mImageGetPixel(image, 1, 0), 0x40FF0000); + assert_int_equal(mImageGetPixel(image, 0, 1), 0x8000FF00); + assert_int_equal(mImageGetPixel(image, 1, 1), 0xC00000FF); + mImageDestroy(image); +} +#endif + +M_TEST_DEFINE(convert1x1) { + const enum mColorFormat formats[] = { + mCOLOR_XBGR8, mCOLOR_XRGB8, + mCOLOR_BGRX8, mCOLOR_RGBX8, + mCOLOR_ABGR8, mCOLOR_ARGB8, + mCOLOR_BGRA8, mCOLOR_RGBA8, + mCOLOR_RGB5, mCOLOR_BGR5, + mCOLOR_RGB565, mCOLOR_BGR565, + mCOLOR_ARGB5, mCOLOR_ABGR5, + mCOLOR_RGBA5, mCOLOR_BGRA5, + mCOLOR_RGB8, mCOLOR_BGR8, + mCOLOR_L8, + 0 + }; + + int i, j; + for (i = 0; formats[i]; ++i) { + for (j = 0; formats[j]; ++j) { + struct mImage* src = mImageCreate(1, 1, formats[i]); + mImageSetPixel(src, 0, 0, 0xFF181818); + + struct mImage* dst = mImageConvertToFormat(src, formats[j]); + assert_non_null(dst); + assert_int_equal(dst->format, formats[j]); + assert_int_equal(mImageGetPixel(dst, 0, 0), 0xFF181818); + + mImageDestroy(src); + mImageDestroy(dst); + } + } +} + +M_TEST_DEFINE(convert2x1) { + const enum mColorFormat formats[] = { + mCOLOR_XBGR8, mCOLOR_XRGB8, + mCOLOR_BGRX8, mCOLOR_RGBX8, + mCOLOR_ABGR8, mCOLOR_ARGB8, + mCOLOR_BGRA8, mCOLOR_RGBA8, + mCOLOR_RGB5, mCOLOR_BGR5, + mCOLOR_RGB565, mCOLOR_BGR565, + mCOLOR_ARGB5, mCOLOR_ABGR5, + mCOLOR_RGBA5, mCOLOR_BGRA5, + mCOLOR_RGB8, mCOLOR_BGR8, + mCOLOR_L8, + 0 + }; + + int i, j; + for (i = 0; formats[i]; ++i) { + for (j = 0; formats[j]; ++j) { + struct mImage* src = mImageCreate(2, 1, formats[i]); + mImageSetPixel(src, 0, 0, 0xFF181818); + mImageSetPixel(src, 1, 0, 0xFF101010); + + struct mImage* dst = mImageConvertToFormat(src, formats[j]); + assert_non_null(dst); + assert_int_equal(dst->format, formats[j]); + assert_int_equal(mImageGetPixel(dst, 0, 0), 0xFF181818); + assert_int_equal(mImageGetPixel(dst, 1, 0), 0xFF101010); + + mImageDestroy(src); + mImageDestroy(dst); + } + } +} + +M_TEST_DEFINE(convert1x2) { + const enum mColorFormat formats[] = { + mCOLOR_XBGR8, mCOLOR_XRGB8, + mCOLOR_BGRX8, mCOLOR_RGBX8, + mCOLOR_ABGR8, mCOLOR_ARGB8, + mCOLOR_BGRA8, mCOLOR_RGBA8, + mCOLOR_RGB5, mCOLOR_BGR5, + mCOLOR_RGB565, mCOLOR_BGR565, + mCOLOR_ARGB5, mCOLOR_ABGR5, + mCOLOR_RGBA5, mCOLOR_BGRA5, + mCOLOR_RGB8, mCOLOR_BGR8, + mCOLOR_L8, + 0 + }; + + int i, j; + for (i = 0; formats[i]; ++i) { + for (j = 0; formats[j]; ++j) { + struct mImage* src = calloc(1, sizeof(*src)); + src->width = 1; + src->height = 2; + src->stride = 8; // Use an unusual stride to make sure the right parts get copied + src->format = formats[i]; + src->depth = mColorFormatBytes(src->format); + src->data = calloc(src->stride * src->depth, src->height); + mImageSetPixel(src, 0, 0, 0xFF181818); + mImageSetPixel(src, 0, 1, 0xFF101010); + + struct mImage* dst = mImageConvertToFormat(src, formats[j]); + assert_non_null(dst); + assert_int_equal(dst->format, formats[j]); + assert_int_equal(mImageGetPixel(dst, 0, 0), 0xFF181818); + assert_int_equal(mImageGetPixel(dst, 0, 1), 0xFF101010); + + mImageDestroy(src); + mImageDestroy(dst); + } + } +} + +M_TEST_DEFINE(convert2x2) { + const enum mColorFormat formats[] = { + mCOLOR_XBGR8, mCOLOR_XRGB8, + mCOLOR_BGRX8, mCOLOR_RGBX8, + mCOLOR_ABGR8, mCOLOR_ARGB8, + mCOLOR_BGRA8, mCOLOR_RGBA8, + mCOLOR_RGB5, mCOLOR_BGR5, + mCOLOR_RGB565, mCOLOR_BGR565, + mCOLOR_ARGB5, mCOLOR_ABGR5, + mCOLOR_RGBA5, mCOLOR_BGRA5, + mCOLOR_RGB8, mCOLOR_BGR8, + mCOLOR_L8, + 0 + }; + + int i, j; + for (i = 0; formats[i]; ++i) { + for (j = 0; formats[j]; ++j) { + struct mImage* src = calloc(1, sizeof(*src)); + src->width = 2; + src->height = 2; + src->stride = 8; // Use an unusual stride to make sure the right parts get copied + src->format = formats[i]; + src->depth = mColorFormatBytes(src->format); + src->data = calloc(src->stride * src->depth, src->height); + mImageSetPixel(src, 0, 0, 0xFF181818); + mImageSetPixel(src, 0, 1, 0xFF101010); + mImageSetPixel(src, 1, 0, 0xFF000000); + mImageSetPixel(src, 1, 1, 0xFF080808); + + struct mImage* dst = mImageConvertToFormat(src, formats[j]); + assert_non_null(dst); + assert_int_equal(dst->format, formats[j]); + assert_int_equal(mImageGetPixel(dst, 0, 0), 0xFF181818); + assert_int_equal(mImageGetPixel(dst, 0, 1), 0xFF101010); + assert_int_equal(mImageGetPixel(dst, 1, 0), 0xFF000000); + assert_int_equal(mImageGetPixel(dst, 1, 1), 0xFF080808); + + mImageDestroy(src); + mImageDestroy(dst); + } + } +} + +M_TEST_DEFINE(blitBoundaries) { + static const uint32_t spriteBuffer[4] = { + 0xFF000F00, 0xFF000F01, + 0xFF000F10, 0xFF000F11 + }; + static const uint32_t canvasBuffer[9] = { + 0xFF000000, 0xFF000000, 0xFF000000, + 0xFF000000, 0xFF000000, 0xFF000000, + 0xFF000000, 0xFF000000, 0xFF000000 + }; + + struct mImage* sprite = mImageCreateFromConstBuffer(2, 2, 2, mCOLOR_XRGB8, spriteBuffer); + struct mImage* canvas; + +#define COMPARE(AA, BA, CA, AB, BB, CB, AC, BC, CC) \ + assert_int_equal(mImageGetPixel(canvas, 0, 0), 0xFF000000 | (AA)); \ + assert_int_equal(mImageGetPixel(canvas, 1, 0), 0xFF000000 | (BA)); \ + assert_int_equal(mImageGetPixel(canvas, 2, 0), 0xFF000000 | (CA)); \ + assert_int_equal(mImageGetPixel(canvas, 0, 1), 0xFF000000 | (AB)); \ + assert_int_equal(mImageGetPixel(canvas, 1, 1), 0xFF000000 | (BB)); \ + assert_int_equal(mImageGetPixel(canvas, 2, 1), 0xFF000000 | (CB)); \ + assert_int_equal(mImageGetPixel(canvas, 0, 2), 0xFF000000 | (AC)); \ + assert_int_equal(mImageGetPixel(canvas, 1, 2), 0xFF000000 | (BC)); \ + assert_int_equal(mImageGetPixel(canvas, 2, 2), 0xFF000000 | (CC)) + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -2, -2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -1, -2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 0, -2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 1, -2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 2, -2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 3, -2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -2, -1); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -1, -1); + COMPARE(0xF11, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 0, -1); + COMPARE(0xF10, 0xF11, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 1, -1); + COMPARE(0x000, 0xF10, 0xF11, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 2, -1); + COMPARE(0x000, 0x000, 0xF10, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 3, -1); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -2, 0); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -1, 0); + COMPARE(0xF01, 0x000, 0x000, + 0xF11, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 0, 0); + COMPARE(0xF00, 0xF01, 0x000, + 0xF10, 0xF11, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 1, 0); + COMPARE(0x000, 0xF00, 0xF01, + 0x000, 0xF10, 0xF11, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 2, 0); + COMPARE(0x000, 0x000, 0xF00, + 0x000, 0x000, 0xF10, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 3, 0); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -2, 1); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -1, 1); + COMPARE(0x000, 0x000, 0x000, + 0xF01, 0x000, 0x000, + 0xF11, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 0, 1); + COMPARE(0x000, 0x000, 0x000, + 0xF00, 0xF01, 0x000, + 0xF10, 0xF11, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 1, 1); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0xF00, 0xF01, + 0x000, 0xF10, 0xF11); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 2, 1); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0xF00, + 0x000, 0x000, 0xF10); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 3, 1); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -2, 2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -1, 2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0xF01, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 0, 2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0xF00, 0xF01, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 1, 2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0xF00, 0xF01); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 2, 2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0xF00); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 3, 2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -2, 3); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -1, 3); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 0, 3); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 1, 3); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 2, 3); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 3, 3); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + +#undef COMPARE + mImageDestroy(sprite); +} + +#define COMPARE4(AA, BA, CA, DA, AB, BB, CB, DB, AC, BC, CC, DC, AD, BD, CD, DD) \ + assert_int_equal(mImageGetPixel(image, 0, 0), (AA)); \ + assert_int_equal(mImageGetPixel(image, 1, 0), (BA)); \ + assert_int_equal(mImageGetPixel(image, 2, 0), (CA)); \ + assert_int_equal(mImageGetPixel(image, 3, 0), (DA)); \ + assert_int_equal(mImageGetPixel(image, 0, 1), (AB)); \ + assert_int_equal(mImageGetPixel(image, 1, 1), (BB)); \ + assert_int_equal(mImageGetPixel(image, 2, 1), (CB)); \ + assert_int_equal(mImageGetPixel(image, 3, 1), (DB)); \ + assert_int_equal(mImageGetPixel(image, 0, 2), (AC)); \ + assert_int_equal(mImageGetPixel(image, 1, 2), (BC)); \ + assert_int_equal(mImageGetPixel(image, 2, 2), (CC)); \ + assert_int_equal(mImageGetPixel(image, 3, 2), (DC)); \ + assert_int_equal(mImageGetPixel(image, 0, 3), (AD)); \ + assert_int_equal(mImageGetPixel(image, 1, 3), (BD)); \ + assert_int_equal(mImageGetPixel(image, 2, 3), (CD)); \ + assert_int_equal(mImageGetPixel(image, 3, 3), (DD)) + +#define COMPARE4X(AA, BA, CA, DA, AB, BB, CB, DB, AC, BC, CC, DC, AD, BD, CD, DD) \ + COMPARE4(0xFF000000 | (AA), 0xFF000000 | (BA), 0xFF000000 | (CA), 0xFF000000 | (DA), \ + 0xFF000000 | (AB), 0xFF000000 | (BB), 0xFF000000 | (CB), 0xFF000000 | (DB), \ + 0xFF000000 | (AC), 0xFF000000 | (BC), 0xFF000000 | (CC), 0xFF000000 | (DC), \ + 0xFF000000 | (AD), 0xFF000000 | (BD), 0xFF000000 | (CD), 0xFF000000 | (DD)) + +#define COMPARE3(AA, BA, CA, AB, BB, CB, AC, BC, CC) \ + assert_int_equal(mImageGetPixel(image, 0, 0), (AA)); \ + assert_int_equal(mImageGetPixel(image, 1, 0), (BA)); \ + assert_int_equal(mImageGetPixel(image, 2, 0), (CA)); \ + assert_int_equal(mImageGetPixel(image, 0, 1), (AB)); \ + assert_int_equal(mImageGetPixel(image, 1, 1), (BB)); \ + assert_int_equal(mImageGetPixel(image, 2, 1), (CB)); \ + assert_int_equal(mImageGetPixel(image, 0, 2), (AC)); \ + assert_int_equal(mImageGetPixel(image, 1, 2), (BC)); \ + assert_int_equal(mImageGetPixel(image, 2, 2), (CC)) + +#define COMPARE3X(AA, BA, CA, AB, BB, CB, AC, BC, CC) \ + COMPARE3(0xFF000000 | (AA), 0xFF000000 | (BA), 0xFF000000 | (CA), \ + 0xFF000000 | (AB), 0xFF000000 | (BB), 0xFF000000 | (CB), \ + 0xFF000000 | (AC), 0xFF000000 | (BC), 0xFF000000 | (CC)) + +M_TEST_DEFINE(painterFillRectangle) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 1, 1, 2, 2); + COMPARE4X(0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x0000FF, 0x0000FF, 0x000000, + 0x000000, 0x0000FF, 0x0000FF, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, -1, -1, 2, 2); + COMPARE4X(0x0000FF, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 3, -1, 2, 2); + COMPARE4X(0x000000, 0x000000, 0x000000, 0x0000FF, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, -1, 3, 2, 2); + COMPARE4X(0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x0000FF, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 3, 3, 2, 2); + COMPARE4X(0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x0000FF); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 3, 3); + painter.fillColor = 0xFF00FF00; + mPainterDrawRectangle(&painter, 1, 1, 3, 3); + COMPARE4X(0x0000FF, 0x0000FF, 0x0000FF, 0x000000, + 0x0000FF, 0x00FF00, 0x00FF00, 0x00FF00, + 0x0000FF, 0x00FF00, 0x00FF00, 0x00FF00, + 0x000000, 0x00FF00, 0x00FF00, 0x00FF00); + mImageDestroy(image); +} + +M_TEST_DEFINE(painterFillRectangleBlend) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(3, 3, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0x400000FF; + mPainterDrawRectangle(&painter, 0, 0, 2, 2); + painter.fillColor = 0x40FF0000; + mPainterDrawRectangle(&painter, 1, 1, 2, 2); + COMPARE3(0x400000FF, 0x400000FF, 0x00000000, + 0x400000FF, 0x40FF0000, 0x40FF0000, + 0x00000000, 0x40FF0000, 0x40FF0000); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = true; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0x400000FF; + mPainterDrawRectangle(&painter, 0, 0, 2, 2); + painter.fillColor = 0x40FF0000; + mPainterDrawRectangle(&painter, 1, 1, 2, 2); + COMPARE3(0x400000FF, 0x400000FF, 0x00000000, + 0x400000FF, 0x6F91006D, 0x40FF0000, + 0x00000000, 0x40FF0000, 0x40FF0000); + mImageDestroy(image); +} + +M_TEST_DEFINE(painterStrokeRectangle) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 1, 1, 2, 2); + COMPARE4X(0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x0000FF, 0x0000FF, 0x000000, + 0x000000, 0x0000FF, 0x0000FF, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, -1, -1, 3, 3); + COMPARE4X(0x000000, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 2, -1, 3, 3); + COMPARE4X(0x000000, 0x000000, 0x0000FF, 0x000000, + 0x000000, 0x000000, 0x0000FF, 0x0000FF, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, -1, 2, 3, 3); + COMPARE4X(0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x000000, 0x0000FF, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 2, 2, 3, 3); + COMPARE4X(0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x0000FF, 0x0000FF, + 0x000000, 0x000000, 0x0000FF, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 3, 3); + painter.strokeColor = 0xFF00FF00; + mPainterDrawRectangle(&painter, 1, 1, 3, 3); + COMPARE4X(0x0000FF, 0x0000FF, 0x0000FF, 0x000000, + 0x0000FF, 0x00FF00, 0x00FF00, 0x00FF00, + 0x0000FF, 0x00FF00, 0x0000FF, 0x00FF00, + 0x000000, 0x00FF00, 0x00FF00, 0x00FF00); + mImageDestroy(image); +} + +M_TEST_DEFINE(painterStrokeRectangleWidth) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 4, 4); + COMPARE4X(0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x0000FF, 0x000000, 0x000000, 0x0000FF, + 0x0000FF, 0x000000, 0x000000, 0x0000FF, + 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 4, 1); + COMPARE4X(0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 1, 4); + COMPARE4X(0x0000FF, 0x000000, 0x000000, 0x000000, + 0x0000FF, 0x000000, 0x000000, 0x000000, + 0x0000FF, 0x000000, 0x000000, 0x000000, + 0x0000FF, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 4, 2); + COMPARE4X(0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 2, 4); + COMPARE4X(0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 4, 4); + COMPARE4X(0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 4, 2); + COMPARE4X(0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 2, 4); + COMPARE4X(0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 3; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 4, 2); + COMPARE4X(0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 3; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 2, 4); + COMPARE4X(0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 4; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 1, 1, 2, 2); + COMPARE4X(0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x0000FF, 0x0000FF, 0x000000, + 0x000000, 0x0000FF, 0x0000FF, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); +} + +M_TEST_DEFINE(painterStrokeRectangleBlend) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(4, 4, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0x400000FF; + mPainterDrawRectangle(&painter, 0, 0, 3, 3); + painter.strokeColor = 0x40FF0000; + mPainterDrawRectangle(&painter, 1, 1, 3, 3); + COMPARE4(0x400000FF, 0x400000FF, 0x400000FF, 0x00000000, + 0x400000FF, 0x40FF0000, 0x40FF0000, 0x40FF0000, + 0x400000FF, 0x40FF0000, 0x400000FF, 0x40FF0000, + 0x00000000, 0x40FF0000, 0x40FF0000, 0x40FF0000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = true; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0x400000FF; + mPainterDrawRectangle(&painter, 0, 0, 3, 3); + painter.strokeColor = 0x40FF0000; + mPainterDrawRectangle(&painter, 1, 1, 3, 3); + COMPARE4(0x400000FF, 0x400000FF, 0x400000FF, 0x00000000, + 0x400000FF, 0x40FF0000, 0x6F91006D, 0x40FF0000, + 0x400000FF, 0x6F91006D, 0x400000FF, 0x40FF0000, + 0x00000000, 0x40FF0000, 0x40FF0000, 0x40FF0000); +} + +M_TEST_DEFINE(painterDrawRectangle) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(3, 3, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.fillColor = 0x800000FF; + painter.strokeWidth = 1; + painter.strokeColor = 0x4000FF00; + mPainterDrawRectangle(&painter, 0, 0, 3, 3); + COMPARE3(0x4000FF00, 0x4000FF00, 0x4000FF00, + 0x4000FF00, 0x800000FF, 0x4000FF00, + 0x4000FF00, 0x4000FF00, 0x4000FF00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = true; + painter.fill = true; + painter.fillColor = 0x800000FF; + painter.strokeWidth = 1; + painter.strokeColor = 0x4000FF00; + mPainterDrawRectangle(&painter, 0, 0, 3, 3); + COMPARE3(0x4000FF00, 0x4000FF00, 0x4000FF00, + 0x4000FF00, 0x800000FF, 0x4000FF00, + 0x4000FF00, 0x4000FF00, 0x4000FF00); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.fillColor = 0x800000FF; + painter.strokeWidth = 1; + painter.strokeColor = 0x4000FF00; + mPainterDrawRectangle(&painter, 0, 0, 3, 3); + mPainterDrawRectangle(&painter, 1, 1, 3, 3); + COMPARE4(0x4000FF00, 0x4000FF00, 0x4000FF00, 0x00000000, + 0x4000FF00, 0x4000FF00, 0x4000FF00, 0x4000FF00, + 0x4000FF00, 0x4000FF00, 0x800000FF, 0x4000FF00, + 0x00000000, 0x4000FF00, 0x4000FF00, 0x4000FF00); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = true; + painter.fill = true; + painter.fillColor = 0x800000FF; + painter.strokeWidth = 1; + painter.strokeColor = 0x4000FF00; + mPainterDrawRectangle(&painter, 0, 0, 3, 3); + mPainterDrawRectangle(&painter, 1, 1, 3, 3); + COMPARE4(0x4000FF00, 0x4000FF00, 0x4000FF00, 0x00000000, + 0x4000FF00, 0x9F006698, 0x6F00FF00, 0x4000FF00, + 0x4000FF00, 0x6F00FF00, 0x9F0032CC, 0x4000FF00, + 0x00000000, 0x4000FF00, 0x4000FF00, 0x4000FF00); + mImageDestroy(image); +} + +M_TEST_DEFINE(painterDrawLineOctants) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 0, 2, 2); + COMPARE3X(0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00, + 0x00, 0x00, 0xFF); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 2, 2, 0, 0); + COMPARE3X(0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00, + 0x00, 0x00, 0xFF); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 2, 0, 0, 2); + COMPARE3X(0x00, 0x00, 0xFF, + 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 2, 2, 0); + COMPARE3X(0x00, 0x00, 0xFF, + 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 0, 2, 1); + COMPARE3X(0xFF, 0xFF, 0x00, + 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 2, 1, 0, 0); + COMPARE3X(0xFF, 0x00, 0x00, + 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 0, 1, 2); + COMPARE3X(0xFF, 0x00, 0x00, + 0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 1, 2, 0, 0); + COMPARE3X(0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x00); + mImageDestroy(image); +} + +M_TEST_DEFINE(painterDrawLineWidth) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 0, 3, 3); + COMPARE4X(0xFF, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0xFF, 0xFF); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 3; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 0, 3, 3); + COMPARE4X(0xFF, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0xFF, 0xFF); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 3, 0, 0, 3); + COMPARE4X(0x00, 0x00, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0x00, 0x00, + 0xFF, 0x00, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 3; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 3, 0, 0, 3); + COMPARE4X(0x00, 0x00, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 1, 0, 1, 2); + COMPARE3X(0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 3; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 1, 0, 1, 2); + COMPARE3X(0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 1, 2, 1); + COMPARE3X(0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 3; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 1, 2, 1); + COMPARE3X(0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF); + mImageDestroy(image); +} + +M_TEST_DEFINE(painterDrawLineBlend) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(3, 3, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0x400000FF; + mPainterDrawLine(&painter, 0, 0, 2, 2); + painter.strokeColor = 0x4000FF00; + mPainterDrawLine(&painter, 0, 2, 2, 0); + COMPARE3(0x400000FF, 0x00000000, 0x4000FF00, + 0x00000000, 0x4000FF00, 0x00000000, + 0x4000FF00, 0x00000000, 0x400000FF); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = true; + painter.strokeWidth = 1; + painter.strokeColor = 0x400000FF; + mPainterDrawLine(&painter, 0, 0, 2, 2); + painter.strokeColor = 0x4000FF00; + mPainterDrawLine(&painter, 0, 2, 2, 0); + COMPARE3(0x400000FF, 0x00000000, 0x4000FF00, + 0x00000000, 0x6F00916D, 0x00000000, + 0x4000FF00, 0x00000000, 0x400000FF); + mImageDestroy(image); +} + +M_TEST_DEFINE(painterDrawCircleArea) { + struct mImage* image; + struct mPainter painter; + + int i; + for (i = 4; i < 50; ++i) { + image = mImageCreate(i, i, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawCircle(&painter, 0, 0, i); + + int filled = 0; + + int x, y; + for (y = 0; y < i; ++y) { + for (x = 0; x < i; ++x) { + uint32_t color = mImageGetPixel(image, x, y); + if (color == painter.fillColor) { + ++filled; + } + } + } + float area = i * i; + assert_float_equal(filled / area, M_PI / 4, 0.12); + } +} + +M_TEST_DEFINE(painterDrawCircleCircumference) { + struct mImage* image; + struct mPainter painter; + + int i; + for (i = 25; i < 100; ++i) { + image = mImageCreate(i, i, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawCircle(&painter, 0, 0, i); + + int filled = 0; + + int x, y; + for (y = 0; y < i; ++y) { + for (x = 0; x < i; ++x) { + uint32_t color = mImageGetPixel(image, x, y); + if (color == painter.strokeColor) { + ++filled; + } + } + } + assert_float_equal(filled / (float) i, M_PI, M_PI * 0.11); + } +} + + +M_TEST_DEFINE(painterDrawCircleOffset) { + struct mImage* image; + struct mPainter painter; + + int i; + for (i = 4; i < 20; ++i) { + image = mImageCreate(i * 2, i * 2, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawCircle(&painter, 0, 0, i); + mPainterDrawCircle(&painter, i, 0, i); + mPainterDrawCircle(&painter, 0, i, i); + mPainterDrawCircle(&painter, i, i, i); + + int x, y; + for (y = 0; y < i; ++y) { + for (x = 0; x < i; ++x) { + uint32_t color = mImageGetPixel(image, x, y); + assert_int_equal(color, mImageGetPixel(image, x + i, y)); + assert_int_equal(color, mImageGetPixel(image, x, y + i)); + assert_int_equal(color, mImageGetPixel(image, x + i, y + i)); + } + } + } +} + +#undef COMPARE3X +#undef COMPARE3 +#undef COMPARE4X +#undef COMPARE4 + +M_TEST_SUITE_DEFINE(Image, + cmocka_unit_test(zeroDim), + cmocka_unit_test(pitchRead), + cmocka_unit_test(strideRead), + cmocka_unit_test(oobRead), + cmocka_unit_test(pitchWrite), + cmocka_unit_test(strideWrite), + cmocka_unit_test(oobWrite), + cmocka_unit_test(paletteAccess), +#ifdef USE_PNG + cmocka_unit_test(loadPng24), + cmocka_unit_test(loadPng32), + cmocka_unit_test(loadPngPalette), + cmocka_unit_test(savePngNative), + cmocka_unit_test(savePngNonNative), + cmocka_unit_test(savePngRoundTrip), + cmocka_unit_test(savePngL8), + cmocka_unit_test(savePngPal8), +#endif + cmocka_unit_test(convert1x1), + cmocka_unit_test(convert2x1), + cmocka_unit_test(convert1x2), + cmocka_unit_test(convert2x2), + cmocka_unit_test(blitBoundaries), + cmocka_unit_test(painterFillRectangle), + cmocka_unit_test(painterFillRectangleBlend), + cmocka_unit_test(painterStrokeRectangle), + cmocka_unit_test(painterStrokeRectangleWidth), + cmocka_unit_test(painterStrokeRectangleBlend), + cmocka_unit_test(painterDrawRectangle), + cmocka_unit_test(painterDrawLineOctants), + cmocka_unit_test(painterDrawLineWidth), + cmocka_unit_test(painterDrawLineBlend), + cmocka_unit_test(painterDrawCircleArea), + cmocka_unit_test(painterDrawCircleCircumference), + cmocka_unit_test(painterDrawCircleOffset), +) diff --git a/src/util/test/vfs.c b/src/util/test/vfs.c index cc623d1b0..4fd995275 100644 --- a/src/util/test/vfs.c +++ b/src/util/test/vfs.c @@ -84,7 +84,7 @@ M_TEST_DEFINE(resizeMem) { } M_TEST_DEFINE(resizeConstMem) { - uint8_t bytes[32]; + uint8_t bytes[32] = {0}; struct VFile* vf = VFileFromConstMemory(bytes, 32); assert_non_null(vf); assert_int_equal(vf->size(vf), 32); @@ -96,7 +96,7 @@ M_TEST_DEFINE(resizeConstMem) { } M_TEST_DEFINE(resizeMemChunk) { - uint8_t bytes[32]; + uint8_t bytes[32] = {0}; struct VFile* vf = VFileMemChunk(bytes, 32); assert_non_null(vf); assert_int_equal(vf->size(vf), 32); diff --git a/src/util/vfs.c b/src/util/vfs.c index 28d366c53..d99f5020f 100644 --- a/src/util/vfs.c +++ b/src/util/vfs.c @@ -13,6 +13,10 @@ #ifdef __3DS__ #include #endif +#ifdef _WIN32 +#include +#include +#endif struct VFile* VFileOpen(const char* path, int flags) { #ifdef USE_VFS_FILE @@ -207,6 +211,41 @@ void separatePath(const char* path, char* dirname, char* basename, char* extensi } } +bool isAbsolute(const char* path) { + // XXX: Is this robust? +#ifdef _WIN32 + WCHAR wpath[PATH_MAX]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, PATH_MAX); + return !PathIsRelativeW(wpath); +#else + return path[0] == '/'; +#endif +} + +void makeAbsolute(const char* path, const char* base, char* out) { + if (isAbsolute(path)) { + strncpy(out, path, PATH_MAX); + return; + } + + char buf[PATH_MAX]; + snprintf(buf, sizeof(buf), "%s" PATH_SEP "%s", base, path); +#ifdef _WIN32 + WCHAR wbuf[PATH_MAX]; + WCHAR wout[PATH_MAX]; + MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, PATH_MAX); + if (GetFullPathNameW(wbuf, PATH_MAX, wout, NULL)) { + WideCharToMultiByte(CP_UTF8, 0, wout, -1, out, PATH_MAX, 0, 0); + return; + } +#elif defined(HAVE_REALPATH) + if (realpath(buf, out)) { + return; + } +#endif + strncpy(out, buf, PATH_MAX); +} + struct VFile* VDirFindFirst(struct VDir* dir, bool (*filter)(struct VFile*)) { dir->rewind(dir); struct VDirEntry* dirent = dir->listNext(dir); diff --git a/src/util/vfs/vfs-dirent.c b/src/util/vfs/vfs-dirent.c index 610482313..806148b89 100644 --- a/src/util/vfs/vfs-dirent.c +++ b/src/util/vfs/vfs-dirent.c @@ -133,7 +133,7 @@ bool _vdDeleteFile(struct VDir* vd, const char* path) { char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); sprintf(combined, "%s%s%s", dir, PATH_SEP, path); - bool ret = !unlink(combined); + bool ret = !remove(combined); free(combined); return ret; } diff --git a/src/util/vfs/vfs-file.c b/src/util/vfs/vfs-file.c index d3adc3177..4a63e53f8 100644 --- a/src/util/vfs/vfs-file.c +++ b/src/util/vfs/vfs-file.c @@ -151,7 +151,9 @@ static bool _vffSync(struct VFile* vf, void* buffer, size_t size) { fseek(vff->file, 0, SEEK_SET); size_t res = fwrite(buffer, size, 1, vff->file); fseek(vff->file, pos, SEEK_SET); - return res == 1; + if (res != 1) { + return false; + } } return fflush(vff->file) == 0; } diff --git a/src/util/vfs/vfs-zip.c b/src/util/vfs/vfs-zip.c index 2f8234b94..65e1c1e32 100644 --- a/src/util/vfs/vfs-zip.c +++ b/src/util/vfs/vfs-zip.c @@ -309,6 +309,9 @@ ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size) { if (!vfz->buffer) { vfz->bufferSize = BLOCK_SIZE; vfz->buffer = malloc(BLOCK_SIZE); + if (vfz->readSize) { + abort(); + } } while (bytesRead < size) { @@ -714,7 +717,7 @@ struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode) { } } - struct VFileZip* vfz = malloc(sizeof(struct VFileZip)); + struct VFileZip* vfz = calloc(1, sizeof(struct VFileZip)); vfz->uz = vdz->uz; vfz->z = vdz->z; vfz->buffer = 0;