diff --git a/CHANGES b/CHANGES index db620fc67..55ce1fd69 100644 --- a/CHANGES +++ b/CHANGES @@ -21,8 +21,10 @@ Features: - Debugger: Segment/bank support - GB: Symbol table support - GB MBC: Add MBC1 multicart support - - GBA: Implement keypad interrupts + - Implement keypad interrupts - LR35902: Watchpoints + - Memory search + - Debugger: Execution tracing Bugfixes: - LR35902: Fix core never exiting with certain event patterns - GB Timer: Improve DIV reset behavior @@ -64,6 +66,13 @@ Bugfixes: - LR35902: Fix decoding LD r, $imm and 0-valued immediates (fixes mgba.io/i/735) - GB: Fix STAT blocking - GB MBC: Fix swapping carts not detect new MBC + - GB Timer: Fix DIV batching if TAC changes + - GB Video: Reset renderer when loading state + - GBA BIOS: Fix INT_MIN/-1 crash + - GBA Savedata: Update and fix Sharkport importing (fixes mgba.io/i/658) + - OpenGL: Fix some shaders causing offset graphics + - Qt: Fix game unpausing after frame advancing and refocusing + - GB Timer: Fix sub-M-cycle DIV reset timing and edge triggering Misc: - SDL: Remove scancode key input - GBA Video: Clean up unused timers @@ -124,6 +133,15 @@ Misc: - Util: Tune patch-fast extent sizes - Qt: Relax hard dependency on OpenGL - GB Video: Improved video timings + - Core: List memory segments in the core + - Core: Move savestate creation time to extdata + - Debugger: Add mDebuggerRunFrame convenience function + - GBA Memory: Remove unused prefetch cruft + - GB: Trust ROM header for number of SRAM banks (fixes mgba.io/i/726) + - Core: Config values can now be hexadecimal + - GB: Reset with initial state of DIV register + - GB MBC: New MBC7 implementation + - Qt: Better highlight active key in control binding 0.5.2: (2016-12-31) Bugfixes: diff --git a/CMakeLists.txt b/CMakeLists.txt index e40465c6e..7504ae4da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -155,8 +155,6 @@ add_custom_target(version-info ALL include(${CMAKE_CURRENT_SOURCE_DIR}/version.cmake) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/version.c.in ${CMAKE_CURRENT_BINARY_DIR}/version.c) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/flags.h.in ${CMAKE_CURRENT_BINARY_DIR}/flags.h) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/flags.h DESTINATION include/mgba COMPONENT lib${BINARY_NAME}) list(APPEND UTIL_SRC ${CMAKE_CURRENT_BINARY_DIR}/version.c) source_group("Generated sources" FILES ${CMAKE_CURRENT_BINARY_DIR}/version.c) @@ -200,6 +198,7 @@ if(WIN32) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) endif() elseif(UNIX) + set(USE_PTHREADS ON) add_definitions(-DUSE_PTHREADS) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") @@ -844,6 +843,10 @@ if(BUILD_EXAMPLE) endif() endif() +message(STATUS ${USE_PTHREADS}) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/flags.h.in ${CMAKE_CURRENT_BINARY_DIR}/flags.h) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/flags.h DESTINATION include/mgba COMPONENT lib${BINARY_NAME}) + # Packaging set(CPACK_PACKAGE_VERSION ${VERSION_STRING}) set(CPACK_PACKAGE_VERSION_MAJOR ${LIB_VERSION_MAJOR}) diff --git a/README.md b/README.md index 61c69c8ac..3b52e1573 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Features - Remappable controls for both keyboards and gamepads. - Loading from ZIP and 7z files. - IPS, UPS and BPS patch support. -- Game debugging via a command-line interface (not available with Qt port) and GDB remote support, compatible with IDA Pro. +- Game debugging via a command-line interface and GDB remote support, compatible with IDA Pro. - Configurable emulation rewinding. - Support for loading and exporting GameShark and Action Replay snapshots. - Cores available for RetroArch/Libretro and OpenEmu. @@ -94,6 +94,16 @@ Compiling requires using CMake 2.8.11 or newer. GCC and Clang are both known to This will build and install mGBA into `/usr/bin` and `/usr/lib`. Dependencies that are installed will be automatically detected, and features that are disabled if the dependencies are not found will be shown after running the `cmake` command after warnings about being unable to find them. +If you are on macOS, the steps are a little different. Assuming you are using the homebrew package manager, the recommended commands to obtain the dependencies and build are: + + brew install cmake ffmpeg imagemagick libzip qt5 sdl2 libedit + mkdir build + cd build + cmake -DCMAKE_PREFIX_PATH=`brew --prefix qt5` .. + make + +Note that you should not do a `make install` on macOS, as it will not work properly. + #### Windows developer building To build on Windows for development, using MSYS2 is recommended. Follow the installation steps found on their [website](https://msys2.github.io). Make sure you're running the 32-bit version ("MSYS2 MinGW 32-bit") (or the 64-bit version "MSYS2 MinGW 64-bit" if you want to build for x86_64) and run this additional command (including the braces) to install the needed dependencies (please note that this involves downloading over 500MiB of packages, so it will take a long time): @@ -133,7 +143,7 @@ mGBA has no hard dependencies, however, the following optional dependencies are - ImageMagick: for GIF recording. - SQLite3: for game databases. -Both libpng and zlib are included with the emulator, so they do not need to be externally compiled first. +SQLite3, libpng, and zlib are included with the emulator, so they do not need to be externally compiled first. Footnotes --------- @@ -145,7 +155,7 @@ Footnotes [2] Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered. -[3] 10.7 is only needed for the Qt port. The SDL port is known to work on 10.6, and may work on older. +[3] 10.7 is only needed for the Qt port. The SDL port is known to work on 10.5, and may work on older. [downloads]: http://mgba.io/downloads.html [source]: https://github.com/mgba-emu/mgba/ @@ -153,7 +163,7 @@ Footnotes Copyright --------- -mGBA is Copyright © 2013 – 2016 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 – 2017 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/include/mgba/core/core.h b/include/mgba/core/core.h index 5507a54a0..17c9de9f3 100644 --- a/include/mgba/core/core.h +++ b/include/mgba/core/core.h @@ -19,8 +19,7 @@ CXX_GUARD_START #endif #include #ifdef USE_DEBUGGERS -// TODO: Fix layering violation -#include +#include #endif enum mPlatform { @@ -132,6 +131,9 @@ struct mCore { void (*rawWrite16)(struct mCore*, uint32_t address, int segment, uint16_t); void (*rawWrite32)(struct mCore*, uint32_t address, int segment, uint32_t); + size_t (*listMemoryBlocks)(const struct mCore*, const struct mCoreMemoryBlock**); + void* (*getMemoryBlock)(struct mCore*, size_t id, size_t* sizeOut); + #ifdef USE_DEBUGGERS bool (*supportsDebuggerType)(struct mCore*, enum mDebuggerType); struct mDebuggerPlatform* (*debuggerPlatform)(struct mCore*); diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h index 7f0515a60..44c7e13d5 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -31,6 +31,10 @@ typedef uint32_t color_t; #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_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)) + struct blip_t; struct mCoreCallbacks { @@ -112,6 +116,27 @@ struct mCoreChannelInfo { const char* visibleType; }; +enum mCoreMemoryBlockFlags { + mCORE_MEMORY_READ = 0x01, + mCORE_MEMORY_WRITE = 0x02, + mCORE_MEMORY_RW = 0x03, + mCORE_MEMORY_MAPPED = 0x10, + mCORE_MEMORY_VIRTUAL = 0x20, +}; + +struct mCoreMemoryBlock { + size_t id; + const char* internalName; + const char* shortName; + const char* longName; + uint32_t start; + uint32_t end; + uint32_t size; + uint32_t flags; + uint16_t maxSegment; + uint32_t segmentStart; +}; + CXX_GUARD_END #endif diff --git a/include/mgba/core/library.h b/include/mgba/core/library.h index 1408f24d3..d55ad4094 100644 --- a/include/mgba/core/library.h +++ b/include/mgba/core/library.h @@ -36,6 +36,7 @@ void mLibraryDestroy(struct mLibrary*); struct VDir; struct VFile; void mLibraryLoadDirectory(struct mLibrary* library, const char* base); +void mLibraryClear(struct mLibrary* library); size_t mLibraryCount(struct mLibrary* library, const struct mLibraryEntry* constraints); size_t mLibraryGetEntries(struct mLibrary* library, struct mLibraryListing* out, size_t numEntries, size_t offset, const struct mLibraryEntry* constraints); diff --git a/include/mgba/core/mem-search.h b/include/mgba/core/mem-search.h new file mode 100644 index 000000000..57084b4ae --- /dev/null +++ b/include/mgba/core/mem-search.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2013-2017 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 CORE_MEM_SEARCH_H +#define CORE_MEM_SEARCH_H + +#include + +CXX_GUARD_START + +#include + +enum mCoreMemorySearchType { + mCORE_MEMORY_SEARCH_32, + mCORE_MEMORY_SEARCH_16, + mCORE_MEMORY_SEARCH_8, + mCORE_MEMORY_SEARCH_STRING, + mCORE_MEMORY_SEARCH_GUESS, +}; + +struct mCoreMemorySearchParams { + int memoryFlags; + enum mCoreMemorySearchType type; + union { + const char* valueStr; + uint32_t value32; + uint32_t value16; + uint32_t value8; + }; +}; + +struct mCoreMemorySearchResult { + uint32_t address; + int segment; + uint64_t guessDivisor; + enum mCoreMemorySearchType type; +}; + +DECLARE_VECTOR(mCoreMemorySearchResults, struct mCoreMemorySearchResult); + +struct mCore; +void mCoreMemorySearch(struct mCore* core, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit); +void mCoreMemorySearchRepeat(struct mCore* core, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* inout); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/core/serialize.h b/include/mgba/core/serialize.h index 0951fc4c3..afbf8de54 100644 --- a/include/mgba/core/serialize.h +++ b/include/mgba/core/serialize.h @@ -16,6 +16,7 @@ enum mStateExtdataTag { EXTDATA_SAVEDATA = 2, EXTDATA_CHEATS = 3, EXTDATA_RTC = 4, + EXTDATA_META_TIME = 0x101, EXTDATA_MAX }; @@ -23,6 +24,7 @@ enum mStateExtdataTag { #define SAVESTATE_SAVEDATA 2 #define SAVESTATE_CHEATS 4 #define SAVESTATE_RTC 8 +#define SAVESTATE_METADATA 16 struct mStateExtdataItem { int32_t size; diff --git a/include/mgba/core/timing.h b/include/mgba/core/timing.h index 19de59dc7..634631bed 100644 --- a/include/mgba/core/timing.h +++ b/include/mgba/core/timing.h @@ -38,6 +38,7 @@ bool mTimingIsScheduled(const struct mTiming* timing, const struct mTimingEvent* int32_t mTimingTick(struct mTiming* timing, int32_t cycles); int32_t mTimingCurrentTime(const struct mTiming* timing); int32_t mTimingNextEvent(struct mTiming* timing); +int32_t mTimingUntil(const struct mTiming* timing, const struct mTimingEvent*); CXX_GUARD_END diff --git a/include/mgba/internal/debugger/debugger.h b/include/mgba/debugger/debugger.h similarity index 92% rename from include/mgba/internal/debugger/debugger.h rename to include/mgba/debugger/debugger.h index d73afd39e..bef61c208 100644 --- a/include/mgba/internal/debugger/debugger.h +++ b/include/mgba/debugger/debugger.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2017 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 @@ -12,7 +12,6 @@ CXX_GUARD_START #include #include -#include mLOG_DECLARE_CATEGORY(DEBUGGER); @@ -53,14 +52,6 @@ enum mDebuggerEntryReason { DEBUGGER_ENTER_ILLEGAL_OP }; -struct mDebugWatchpoint { - uint32_t address; - enum mWatchpointType type; -}; - -extern const char* ERROR_MISSING_ARGS; -extern const char* ERROR_OVERFLOW; - struct mDebuggerEntryInfo { uint32_t address; union { @@ -92,6 +83,7 @@ struct mDebuggerPlatform { void (*setWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); void (*clearWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment); void (*checkBreakpoints)(struct mDebuggerPlatform*); + void (*trace)(struct mDebuggerPlatform*, char* out, size_t* length); }; struct mDebuggerSymbols; @@ -112,6 +104,7 @@ struct mDebugger { struct mDebugger* mDebuggerCreate(enum mDebuggerType type, struct mCore*); void mDebuggerAttach(struct mDebugger*, struct mCore*); void mDebuggerRun(struct mDebugger*); +void mDebuggerRunFrame(struct mDebugger*); void mDebuggerEnter(struct mDebugger*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*); CXX_GUARD_END diff --git a/include/mgba/feature/commandline.h b/include/mgba/feature/commandline.h index b47442c21..854afdb77 100644 --- a/include/mgba/feature/commandline.h +++ b/include/mgba/feature/commandline.h @@ -12,7 +12,7 @@ CXX_GUARD_START #include -#include +#include struct mArguments { char* fname; diff --git a/include/mgba/gb/interface.h b/include/mgba/gb/interface.h index 03b836865..ef0771bca 100644 --- a/include/mgba/gb/interface.h +++ b/include/mgba/gb/interface.h @@ -30,6 +30,7 @@ enum GBMemoryBankControllerType { GB_MMM01 = 0x10, GB_HuC1 = 0x11, GB_HuC3 = 0x12, + GB_POCKETCAM = 0x13, GB_MBC3_RTC = 0x103, GB_MBC5_RUMBLE = 0x105 }; diff --git a/include/mgba/internal/arm/debugger/debugger.h b/include/mgba/internal/arm/debugger/debugger.h index d5a05e6fd..d35dbed13 100644 --- a/include/mgba/internal/arm/debugger/debugger.h +++ b/include/mgba/internal/arm/debugger/debugger.h @@ -10,9 +10,10 @@ CXX_GUARD_START -#include +#include #include +#include struct ARMDebugBreakpoint { uint32_t address; diff --git a/include/mgba/internal/debugger/cli-debugger.h b/include/mgba/internal/debugger/cli-debugger.h index 2f86590ee..6eb28b1dc 100644 --- a/include/mgba/internal/debugger/cli-debugger.h +++ b/include/mgba/internal/debugger/cli-debugger.h @@ -10,7 +10,10 @@ CXX_GUARD_START -#include +#include + +extern const char* ERROR_MISSING_ARGS; +extern const char* ERROR_OVERFLOW; struct CLIDebugger; diff --git a/include/mgba/internal/debugger/gdb-stub.h b/include/mgba/internal/debugger/gdb-stub.h index 9e6be2239..5743834a2 100644 --- a/include/mgba/internal/debugger/gdb-stub.h +++ b/include/mgba/internal/debugger/gdb-stub.h @@ -10,7 +10,7 @@ CXX_GUARD_START -#include +#include #include diff --git a/include/mgba/internal/debugger/parser.h b/include/mgba/internal/debugger/parser.h index c5ab01446..178254b69 100644 --- a/include/mgba/internal/debugger/parser.h +++ b/include/mgba/internal/debugger/parser.h @@ -10,7 +10,7 @@ CXX_GUARD_START -#include +#include enum LexState { LEX_ERROR = -1, diff --git a/include/mgba/internal/gb/gb.h b/include/mgba/internal/gb/gb.h index 05362c7bd..37050e613 100644 --- a/include/mgba/internal/gb/gb.h +++ b/include/mgba/internal/gb/gb.h @@ -139,6 +139,8 @@ bool GBIsROM(struct VFile* vf); void GBGetGameTitle(const struct GB* gba, char* out); void GBGetGameCode(const struct GB* gba, char* out); +void GBTestKeypadIRQ(struct GB* gb); + void GBFrameEnded(struct GB* gb); CXX_GUARD_END diff --git a/include/mgba/internal/gb/mbc.h b/include/mgba/internal/gb/mbc.h index 6242343e8..477364967 100644 --- a/include/mgba/internal/gb/mbc.h +++ b/include/mgba/internal/gb/mbc.h @@ -36,7 +36,6 @@ struct GBMBCRTCSaveBuffer { void GBMBCRTCRead(struct GB* gb); void GBMBCRTCWrite(struct GB* gb); -uint8_t GBMBC7Read(struct GBMemory*, uint16_t address); void GBMBC7Write(struct GBMemory*, uint16_t address, uint8_t value); CXX_GUARD_END diff --git a/include/mgba/internal/gb/memory.h b/include/mgba/internal/gb/memory.h index 11f1e929f..d28338cff 100644 --- a/include/mgba/internal/gb/memory.h +++ b/include/mgba/internal/gb/memory.h @@ -63,24 +63,27 @@ enum { }; struct GBMemory; -typedef void (*GBMemoryBankController)(struct GB*, uint16_t address, uint8_t value); +typedef void (*GBMemoryBankControllerWrite)(struct GB*, uint16_t address, uint8_t value); +typedef uint8_t (*GBMemoryBankControllerRead)(struct GBMemory*, uint16_t address); DECL_BITFIELD(GBMBC7Field, uint8_t); -DECL_BIT(GBMBC7Field, SK, 6); DECL_BIT(GBMBC7Field, CS, 7); -DECL_BIT(GBMBC7Field, IO, 1); +DECL_BIT(GBMBC7Field, CLK, 6); +DECL_BIT(GBMBC7Field, DI, 1); +DECL_BIT(GBMBC7Field, DO, 0); enum GBMBC7MachineState { - GBMBC7_STATE_NULL = -1, GBMBC7_STATE_IDLE = 0, GBMBC7_STATE_READ_COMMAND = 1, - GBMBC7_STATE_READ_ADDRESS = 2, - GBMBC7_STATE_COMMAND_0 = 3, - GBMBC7_STATE_COMMAND_SR_WRITE = 4, - GBMBC7_STATE_COMMAND_SR_READ = 5, - GBMBC7_STATE_COMMAND_SR_FILL = 6, - GBMBC7_STATE_READ = 7, - GBMBC7_STATE_WRITE = 8, + GBMBC7_STATE_DO = 2, + + GBMBC7_STATE_EEPROM_EWDS = 0x10, + GBMBC7_STATE_EEPROM_WRAL = 0x11, + GBMBC7_STATE_EEPROM_ERAL = 0x12, + GBMBC7_STATE_EEPROM_EWEN = 0x13, + GBMBC7_STATE_EEPROM_WRITE = 0x14, + GBMBC7_STATE_EEPROM_READ = 0x18, + GBMBC7_STATE_EEPROM_ERASE = 0x1C, }; struct GBMBC1State { @@ -90,17 +93,23 @@ struct GBMBC1State { struct GBMBC7State { enum GBMBC7MachineState state; - uint32_t sr; + uint16_t sr; uint8_t address; bool writable; int srBits; - int command; - GBMBC7Field field; + uint8_t access; + uint8_t latch; + GBMBC7Field eeprom; +}; + +struct GBPocketCamState { + bool registersActive; }; union GBMBCState { struct GBMBC1State mbc1; struct GBMBC7State mbc7; + struct GBPocketCamState pocketCam; }; struct mRotationSource; @@ -109,7 +118,8 @@ struct GBMemory { uint8_t* romBase; uint8_t* romBank; enum GBMemoryBankControllerType mbcType; - GBMemoryBankController mbc; + GBMemoryBankControllerWrite mbcWrite; + GBMemoryBankControllerRead mbcRead; union GBMBCState mbcState; int currentBank; diff --git a/include/mgba/internal/gb/overrides.h b/include/mgba/internal/gb/overrides.h index fd787163c..44d012183 100644 --- a/include/mgba/internal/gb/overrides.h +++ b/include/mgba/internal/gb/overrides.h @@ -16,6 +16,8 @@ struct GBCartridgeOverride { int headerCrc32; enum GBModel model; enum GBMemoryBankControllerType mbc; + + uint32_t gbColors[4]; }; struct Configuration; diff --git a/include/mgba/internal/gb/serialize.h b/include/mgba/internal/gb/serialize.h index e370c997b..c55f97600 100644 --- a/include/mgba/internal/gb/serialize.h +++ b/include/mgba/internal/gb/serialize.h @@ -154,8 +154,7 @@ mLOG_DECLARE_CATEGORY(GB_STATE); * | bit 4: Is HDMA active? * | bits 5 - 7: Active RTC register * | 0x00196 - 0x00197: Reserved (leave zero) - * 0x00198 - 0x0019F: Savestate creation time (usec since 1970) - * 0x001A0 - 0x0025F: Reserved (leave zero) + * 0x00198 - 0x0025F: Reserved (leave zero) * 0x00260 - 0x002FF: OAM * 0x00300 - 0x0037F: I/O memory * 0x00380 - 0x003FE: HRAM @@ -354,9 +353,7 @@ struct GBSerializedState { uint16_t reserved; } memory; - uint64_t creationUsec; - - uint32_t reserved[48]; + uint32_t reserved[50]; uint8_t oam[GB_SIZE_OAM]; diff --git a/include/mgba/internal/gb/video.h b/include/mgba/internal/gb/video.h index 60534636b..440a90ccf 100644 --- a/include/mgba/internal/gb/video.h +++ b/include/mgba/internal/gb/video.h @@ -144,7 +144,7 @@ void GBVideoWriteLYC(struct GBVideo* video, uint8_t value); void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value); void GBVideoSwitchBank(struct GBVideo* video, uint8_t value); -void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint16_t color); +void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint32_t color); struct GBSerializedState; void GBVideoSerialize(const struct GBVideo* video, struct GBSerializedState* state); diff --git a/include/mgba/internal/gba/memory.h b/include/mgba/internal/gba/memory.h index 9507f0e29..1e5c89890 100644 --- a/include/mgba/internal/gba/memory.h +++ b/include/mgba/internal/gba/memory.h @@ -132,10 +132,6 @@ struct GBAMemory { char waitstatesSeq16[256]; char waitstatesNonseq32[256]; char waitstatesNonseq16[256]; - char waitstatesPrefetchSeq32[16]; - char waitstatesPrefetchSeq16[16]; - char waitstatesPrefetchNonseq32[16]; - char waitstatesPrefetchNonseq16[16]; int activeRegion; bool prefetch; uint32_t lastPrefetchedPc; diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index 14bddf6e2..4f24198dd 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -190,8 +190,7 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | 0x002F8 - 0x002FB: CPU prefecth (decode slot) * | 0x002FC - 0x002FF: CPU prefetch (fetch slot) * 0x00300 - 0x00303: Associated movie stream ID for record/replay (or 0 if no stream) - * 0x00304 - 0x0030F: Reserved (leave zero) - * 0x00310 - 0x00317: Savestate creation time (usec since 1970) + * 0x00304 - 0x00317: Savestate creation time (usec since 1970) * 0x00318 - 0x0031B: Last prefetched program counter * 0x0031C - 0x0031F: Miscellaneous flags * | bit 0: Is CPU halted? @@ -312,9 +311,7 @@ struct GBASerializedState { uint32_t cpuPrefetch[2]; uint32_t associatedStreamId; - uint32_t reservedRr[3]; - - uint64_t creationUsec; + uint32_t reservedRr[5]; uint32_t lastPrefetchedPc; GBASerializedMiscFlags miscFlags; diff --git a/include/mgba/internal/lr35902/debugger/debugger.h b/include/mgba/internal/lr35902/debugger/debugger.h index 85712ee96..340e13aac 100644 --- a/include/mgba/internal/lr35902/debugger/debugger.h +++ b/include/mgba/internal/lr35902/debugger/debugger.h @@ -10,9 +10,10 @@ CXX_GUARD_START -#include +#include #include +#include struct LR35902DebugBreakpoint { diff --git a/res/shaders/agb001.shader/manifest.ini b/res/shaders/agb001.shader/manifest.ini index 62280af9f..4c1a8f5b3 100644 --- a/res/shaders/agb001.shader/manifest.ini +++ b/res/shaders/agb001.shader/manifest.ini @@ -7,5 +7,5 @@ passes=1 [pass.0] fragmentShader=agb001.fs blend=1 -width=960 -height=640 +width=-4 +height=-4 diff --git a/res/shaders/ags001.shader/manifest.ini b/res/shaders/ags001.shader/manifest.ini index 85398cce6..2d5b7c491 100644 --- a/res/shaders/ags001.shader/manifest.ini +++ b/res/shaders/ags001.shader/manifest.ini @@ -7,13 +7,13 @@ passes=2 [pass.0] fragmentShader=ags001.fs blend=1 -width=960 -height=640 +width=-4 +height=-4 [pass.1] fragmentShader=ags001-light.fs -width=960 -height=640 +width=-4 +height=-4 [pass.1.uniform.lightBrightness] type=float diff --git a/res/shaders/fish.shader/fish.fs b/res/shaders/fish.shader/fish.fs index 7d750fd7b..96a1b5f46 100644 --- a/res/shaders/fish.shader/fish.fs +++ b/res/shaders/fish.shader/fish.fs @@ -23,19 +23,15 @@ THE SOFTWARE. */ -precision highp float; - varying vec2 texCoord; uniform sampler2D tex; uniform vec2 texSize; uniform float similarity_threshold; -#define screen_res 240,160 - vec4 texel_fetch(sampler2D t, ivec2 c) // because GLSL TexelFetch is not supported { - return texture2D(tex, (2 * vec2(c) + vec2(1,1)) / (2 * vec2(screen_res)) ); + return texture2D(tex, (2 * vec2(c) + vec2(1,1)) / (2 * texSize) ); } float pixel_brightness(vec4 pixel) @@ -140,7 +136,7 @@ vec4 interpolate_diagonal(vec4 a, vec4 b, vec4 c, vec4 d) void main() { - ivec2 pixel_coords2 = ivec2(texCoord * vec2(screen_res) * 2); + ivec2 pixel_coords2 = ivec2(texCoord * texSize * 2); ivec2 pixel_coords = pixel_coords2 / 2; bool x_even = mod(pixel_coords2.x,2) == 0; diff --git a/res/shaders/lcd.shader/lcd.fs b/res/shaders/lcd.shader/lcd.fs new file mode 100644 index 000000000..ceedd8eb7 --- /dev/null +++ b/res/shaders/lcd.shader/lcd.fs @@ -0,0 +1,40 @@ +/* + LCD Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + 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. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float boundBrightness; + +void main() +{ + vec4 color = texture2D(tex, texCoord); + + if (int(mod(texCoord.s * texSize.x * 3.0, 3.0)) == 0 || + int(mod(texCoord.t * texSize.y * 3.0, 3.0)) == 0) + { + color.rgb *= vec3(1.0, 1.0, 1.0) * boundBrightness; + } + + gl_FragColor = color; +} diff --git a/res/shaders/lcd.shader/manifest.ini b/res/shaders/lcd.shader/manifest.ini new file mode 100644 index 000000000..81061d8b0 --- /dev/null +++ b/res/shaders/lcd.shader/manifest.ini @@ -0,0 +1,18 @@ +[shader] +name=LCD +author=Dominus Iniquitatis +description=Simple LCD emulation. +passes=1 + +[pass.0] +fragmentShader=lcd.fs +blend=1 +width=-3 +height=-3 + +[pass.0.uniform.boundBrightness] +type=float +readableName=Bound brightness +default=0.9 +min=0.0 +max=1.0 diff --git a/res/shaders/motion_blur.shader/manifest.ini b/res/shaders/motion_blur.shader/manifest.ini new file mode 100644 index 000000000..5f485a041 --- /dev/null +++ b/res/shaders/motion_blur.shader/manifest.ini @@ -0,0 +1,18 @@ +[shader] +name=Motion Blur +author=Dominus Iniquitatis +description=Simple motion blur. +passes=1 + +[pass.0] +fragmentShader=motion_blur.fs +blend=1 +width=-1 +height=-1 + +[pass.0.uniform.amount] +type=float +readableName=Amount +default=0.3 +min=0.0 +max=1.0 diff --git a/res/shaders/motion_blur.shader/motion_blur.fs b/res/shaders/motion_blur.shader/motion_blur.fs new file mode 100644 index 000000000..850107355 --- /dev/null +++ b/res/shaders/motion_blur.shader/motion_blur.fs @@ -0,0 +1,35 @@ +/* + Motion Blur Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + 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. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float amount; + +void main() +{ + vec4 color = texture2D(tex, texCoord); + color.a = 1.0 - amount; + + gl_FragColor = color; +} diff --git a/res/shaders/scanlines.shader/manifest.ini b/res/shaders/scanlines.shader/manifest.ini new file mode 100644 index 000000000..8df4604d1 --- /dev/null +++ b/res/shaders/scanlines.shader/manifest.ini @@ -0,0 +1,18 @@ +[shader] +name=Scanlines +author=Dominus Iniquitatis +description=Simple scanlines. +passes=1 + +[pass.0] +fragmentShader=scanlines.fs +blend=1 +width=-2 +height=-2 + +[pass.0.uniform.lineBrightness] +type=float +readableName=Line brightness +default=0.5 +min=0.0 +max=1.0 diff --git a/res/shaders/scanlines.shader/scanlines.fs b/res/shaders/scanlines.shader/scanlines.fs new file mode 100644 index 000000000..4f25fd0f6 --- /dev/null +++ b/res/shaders/scanlines.shader/scanlines.fs @@ -0,0 +1,39 @@ +/* + Scanlines Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + 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. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float lineBrightness; + +void main() +{ + vec4 color = texture2D(tex, texCoord); + + if (int(mod(texCoord.t * texSize.y * 2.0, 2.0)) == 0) + { + color.rgb *= vec3(1.0, 1.0, 1.0) * lineBrightness; + } + + gl_FragColor = color; +} diff --git a/res/shaders/soften.shader/manifest.ini b/res/shaders/soften.shader/manifest.ini new file mode 100644 index 000000000..6c2312796 --- /dev/null +++ b/res/shaders/soften.shader/manifest.ini @@ -0,0 +1,18 @@ +[shader] +name=Soften +author=Dominus Iniquitatis +description=Soft image blurring. +passes=1 + +[pass.0] +fragmentShader=soften.fs +blend=1 +width=-1 +height=-1 + +[pass.0.uniform.amount] +type=float +readableName=Amount +default=0.5 +min=0.0 +max=1.0 diff --git a/res/shaders/soften.shader/soften.fs b/res/shaders/soften.shader/soften.fs new file mode 100644 index 000000000..41cfa1083 --- /dev/null +++ b/res/shaders/soften.shader/soften.fs @@ -0,0 +1,64 @@ +/* + Soften Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + 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. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float amount; + +vec2 GetTexelSize() +{ + return vec2(1.0 / texSize.x, 1.0 / texSize.y); +} + +void main() +{ + vec4 color = texture2D(tex, texCoord); + + vec4 northColor = texture2D(tex, texCoord + vec2(0.0, GetTexelSize().y)); + vec4 southColor = texture2D(tex, texCoord - vec2(0.0, GetTexelSize().y)); + vec4 eastColor = texture2D(tex, texCoord + vec2(GetTexelSize().x, 0.0)); + vec4 westColor = texture2D(tex, texCoord - vec2(GetTexelSize().x, 0.0)); + + if (abs(length(color) - length(northColor)) > 0.0) + { + color = mix(color, northColor, amount / 4.0); + } + + if (abs(length(color) - length(southColor)) > 0.0) + { + color = mix(color, southColor, amount / 4.0); + } + + if (abs(length(color) - length(eastColor)) > 0.0) + { + color = mix(color, eastColor, amount / 4.0); + } + + if (abs(length(color) - length(westColor)) > 0.0) + { + color = mix(color, westColor, amount / 4.0); + } + + gl_FragColor = color; +} diff --git a/res/shaders/vba_pixelate.shader/manifest.ini b/res/shaders/vba_pixelate.shader/manifest.ini new file mode 100644 index 000000000..ae5d58d1b --- /dev/null +++ b/res/shaders/vba_pixelate.shader/manifest.ini @@ -0,0 +1,18 @@ +[shader] +name=VBA Pixelate +author=Dominus Iniquitatis +description=VisualBoyAdvance-style pixelation. +passes=1 + +[pass.0] +fragmentShader=vba_pixelate.fs +blend=1 +width=-2 +height=-2 + +[pass.0.uniform.boundBrightness] +type=float +readableName=Bound brightness +default=0.5 +min=0.0 +max=1.0 diff --git a/res/shaders/vba_pixelate.shader/vba_pixelate.fs b/res/shaders/vba_pixelate.shader/vba_pixelate.fs new file mode 100644 index 000000000..c676a3afd --- /dev/null +++ b/res/shaders/vba_pixelate.shader/vba_pixelate.fs @@ -0,0 +1,40 @@ +/* + VBA Pixelate Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + 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. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float boundBrightness; + +void main() +{ + vec4 color = texture2D(tex, texCoord); + + if (int(mod(texCoord.s * texSize.x * 2.0, 2.0)) == 0 || + int(mod(texCoord.t * texSize.y * 2.0, 2.0)) == 0) + { + color.rgb *= vec3(1.0, 1.0, 1.0) * boundBrightness; + } + + gl_FragColor = color; +} diff --git a/res/shaders/vignette.shader/manifest.ini b/res/shaders/vignette.shader/manifest.ini new file mode 100644 index 000000000..f486dbc44 --- /dev/null +++ b/res/shaders/vignette.shader/manifest.ini @@ -0,0 +1,18 @@ +[shader] +name=Vignette +author=Dominus Iniquitatis +description=Configurable vignette effect. +passes=1 + +[pass.0] +fragmentShader=vignette.fs +blend=1 +width=-1 +height=-1 + +[pass.0.uniform.intensity] +type=float +readableName=Intensity +default=1.0 +min=0.0 +max=1.0 diff --git a/res/shaders/vignette.shader/vignette.fs b/res/shaders/vignette.shader/vignette.fs new file mode 100644 index 000000000..fb6e11c3c --- /dev/null +++ b/res/shaders/vignette.shader/vignette.fs @@ -0,0 +1,35 @@ +/* + Vignette Shader + + Copyright (C) 2017 Dominus Iniquitatis - zerosaiko@gmail.com + + 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. +*/ + +uniform sampler2D tex; +uniform vec2 texSize; +varying vec2 texCoord; + +uniform float intensity; + +void main() +{ + vec4 color = texture2D(tex, texCoord); + color = mix(color, vec4(0.0, 0.0, 0.0, 1.0), length(texCoord - 0.5) * intensity); + + gl_FragColor = color; +} diff --git a/res/shaders/wiiu.shader/manifest.ini b/res/shaders/wiiu.shader/manifest.ini index 2b92c1d74..e69751ed3 100644 --- a/res/shaders/wiiu.shader/manifest.ini +++ b/res/shaders/wiiu.shader/manifest.ini @@ -7,5 +7,5 @@ passes=1 [pass.0] fragmentShader=wiiu.fs blend=1 -width=960 -height=640 +width=-4 +height=-4 diff --git a/src/arm/debugger/debugger.c b/src/arm/debugger/debugger.c index 03ae0e90e..fde61a8a8 100644 --- a/src/arm/debugger/debugger.c +++ b/src/arm/debugger/debugger.c @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -54,6 +55,7 @@ static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t address static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment); static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform*); static bool ARMDebuggerHasBreakpoints(struct mDebuggerPlatform*); +static void ARMDebuggerTrace(struct mDebuggerPlatform*, char* out, size_t* length); struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void) { struct mDebuggerPlatform* platform = (struct mDebuggerPlatform*) malloc(sizeof(struct ARMDebugger)); @@ -66,6 +68,7 @@ struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void) { platform->clearWatchpoint = ARMDebuggerClearWatchpoint; platform->checkBreakpoints = ARMDebuggerCheckBreakpoints; platform->hasBreakpoints = ARMDebuggerHasBreakpoints; + platform->trace = ARMDebuggerTrace; return platform; } @@ -207,3 +210,39 @@ static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform* d, uint32_t add ARMDebuggerRemoveMemoryShim(debugger); } } + +static void ARMDebuggerTrace(struct mDebuggerPlatform* d, char* out, size_t* length) { + struct ARMDebugger* debugger = (struct ARMDebugger*) d; + struct ARMCore* cpu = debugger->cpu; + + char disassembly[64]; + + struct ARMInstructionInfo info; + if (cpu->executionMode == MODE_ARM) { + uint32_t instruction = cpu->prefetch[0]; + sprintf(disassembly, "%08X: ", instruction); + ARMDecodeARM(instruction, &info); + ARMDisassemble(&info, cpu->gprs[ARM_PC], disassembly + strlen("00000000: "), sizeof(disassembly) - strlen("00000000: ")); + } else { + struct ARMInstructionInfo info2; + struct ARMInstructionInfo combined; + uint16_t instruction = cpu->prefetch[0]; + uint16_t instruction2 = cpu->prefetch[1]; + ARMDecodeThumb(instruction, &info); + ARMDecodeThumb(instruction2, &info2); + if (ARMDecodeThumbCombine(&info, &info2, &combined)) { + sprintf(disassembly, "%04X%04X: ", instruction, instruction2); + ARMDisassemble(&combined, cpu->gprs[ARM_PC], disassembly + strlen("00000000: "), sizeof(disassembly) - strlen("00000000: ")); + } else { + sprintf(disassembly, " %04X: ", instruction); + ARMDisassemble(&info, cpu->gprs[ARM_PC], disassembly + strlen("00000000: "), sizeof(disassembly) - strlen("00000000: ")); + } + } + + *length = snprintf(out, *length, "%08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X cpsr: %08X | %s", + cpu->gprs[0], cpu->gprs[1], cpu->gprs[2], cpu->gprs[3], + cpu->gprs[4], cpu->gprs[5], cpu->gprs[6], cpu->gprs[7], + cpu->gprs[8], cpu->gprs[9], cpu->gprs[10], cpu->gprs[11], + cpu->gprs[12], cpu->gprs[13], cpu->gprs[14], cpu->gprs[15], + cpu->cpsr.packed, disassembly); +} diff --git a/src/core/config.c b/src/core/config.c index cbb6f32ee..950607469 100644 --- a/src/core/config.c +++ b/src/core/config.c @@ -86,6 +86,9 @@ static bool _lookupIntValue(const struct mCoreConfig* config, const char* key, i } char* end; long value = strtol(charValue, &end, 10); + if (end == &charValue[1] && *end == 'x') { + value = strtol(charValue, &end, 16); + } if (*end) { return false; } diff --git a/src/core/library.c b/src/core/library.c index d230f60a6..b16afe3ce 100644 --- a/src/core/library.c +++ b/src/core/library.c @@ -339,6 +339,16 @@ static void _mLibraryDeleteEntry(struct mLibrary* library, struct mLibraryEntry* sqlite3_step(library->insertPath); } +void mLibraryClear(struct mLibrary* library) { + int result = sqlite3_exec(library->db, + " BEGIN TRANSACTION;" + "\n DELETE FROM roots;" + "\n DELETE FROM roms;" + "\n DELETE FROM paths;" + "\n COMMIT;" + "\n VACUUM;", NULL, NULL, NULL); +} + size_t mLibraryCount(struct mLibrary* library, const struct mLibraryEntry* constraints) { sqlite3_clear_bindings(library->count); sqlite3_reset(library->count); diff --git a/src/core/mem-search.c b/src/core/mem-search.c new file mode 100644 index 000000000..5cf74140f --- /dev/null +++ b/src/core/mem-search.c @@ -0,0 +1,496 @@ +/* Copyright (c) 2013-2017 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 + +DEFINE_VECTOR(mCoreMemorySearchResults, struct mCoreMemorySearchResult); + +static size_t _search32(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint32_t value32, struct mCoreMemorySearchResults* out, size_t limit) { + const uint32_t* mem32 = mem; + size_t found = 0; + uint32_t start = block->start; + uint32_t end = size; // TODO: Segments + size_t i; + // TODO: Big endian + for (i = 0; (!limit || found < limit) && i < end; i += 16) { + int mask = 0; + mask |= (mem32[(i >> 2) + 0] == value32) << 0; + mask |= (mem32[(i >> 2) + 1] == value32) << 1; + mask |= (mem32[(i >> 2) + 2] == value32) << 2; + mask |= (mem32[(i >> 2) + 3] == value32) << 3; + if (!mask) { + continue; + } + if ((mask & 1) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i; + res->type = mCORE_MEMORY_SEARCH_32; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 2) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 4; + res->type = mCORE_MEMORY_SEARCH_32; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 4) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 8; + res->type = mCORE_MEMORY_SEARCH_32; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 8) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 12; + res->type = mCORE_MEMORY_SEARCH_32; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + } + // TODO: last 12 bytes + return found; +} + +static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint16_t value16, struct mCoreMemorySearchResults* out, size_t limit) { + const uint16_t* mem16 = mem; + size_t found = 0; + uint32_t start = block->start; + uint32_t end = size; // TODO: Segments + size_t i; + // TODO: Big endian + for (i = 0; (!limit || found < limit) && i < end; i += 16) { + int mask = 0; + mask |= (mem16[(i >> 1) + 0] == value16) << 0; + mask |= (mem16[(i >> 1) + 1] == value16) << 1; + mask |= (mem16[(i >> 1) + 2] == value16) << 2; + mask |= (mem16[(i >> 1) + 3] == value16) << 3; + mask |= (mem16[(i >> 1) + 4] == value16) << 4; + mask |= (mem16[(i >> 1) + 5] == value16) << 5; + mask |= (mem16[(i >> 1) + 6] == value16) << 6; + mask |= (mem16[(i >> 1) + 7] == value16) << 7; + if (!mask) { + continue; + } + if ((mask & 1) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i; + res->type = mCORE_MEMORY_SEARCH_16; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 2) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 2; + res->type = mCORE_MEMORY_SEARCH_16; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 4) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 4; + res->type = mCORE_MEMORY_SEARCH_16; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 8) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 6; + res->type = mCORE_MEMORY_SEARCH_16; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 16) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 8; + res->type = mCORE_MEMORY_SEARCH_16; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 32) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 10; + res->type = mCORE_MEMORY_SEARCH_16; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 64) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 12; + res->type = mCORE_MEMORY_SEARCH_16; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 128) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 14; + res->type = mCORE_MEMORY_SEARCH_16; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + } + // TODO: last 14 bytes + return found; +} + +static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint8_t value8, struct mCoreMemorySearchResults* out, size_t limit) { + const uint8_t* mem8 = mem; + size_t found = 0; + uint32_t start = block->start; + uint32_t end = size; // TODO: Segments + size_t i; + for (i = 0; (!limit || found < limit) && i < end; i += 8) { + int mask = 0; + mask |= (mem8[i + 0] == value8) << 0; + mask |= (mem8[i + 1] == value8) << 1; + mask |= (mem8[i + 2] == value8) << 2; + mask |= (mem8[i + 3] == value8) << 3; + mask |= (mem8[i + 4] == value8) << 4; + mask |= (mem8[i + 5] == value8) << 5; + mask |= (mem8[i + 6] == value8) << 6; + mask |= (mem8[i + 7] == value8) << 7; + if (!mask) { + continue; + } + if ((mask & 1) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i; + res->type = mCORE_MEMORY_SEARCH_8; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 2) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 1; + res->type = mCORE_MEMORY_SEARCH_8; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 4) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 2; + res->type = mCORE_MEMORY_SEARCH_8; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 8) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 3; + res->type = mCORE_MEMORY_SEARCH_8; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 16) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 4; + res->type = mCORE_MEMORY_SEARCH_8; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 32) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 5; + res->type = mCORE_MEMORY_SEARCH_8; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 64) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 6; + res->type = mCORE_MEMORY_SEARCH_8; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + if ((mask & 128) && (!limit || found < limit)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i + 7; + res->type = mCORE_MEMORY_SEARCH_8; + res->segment = -1; // TODO + res->guessDivisor = 1; + ++found; + } + } + // TODO: last 7 bytes + return found; +} + +static size_t _searchStr(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const char* valueStr, struct mCoreMemorySearchResults* out, size_t limit) { + const char* memStr = mem; + size_t found = 0; + size_t len = strlen(valueStr); + uint32_t start = block->start; + uint32_t end = size; // TODO: Segments + size_t i; + for (i = 0; (!limit || found < limit) && i < end - len; ++i) { + if (!strncmp(valueStr, &memStr[i], len)) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); + res->address = start + i; + res->type = mCORE_MEMORY_SEARCH_STRING; + res->segment = -1; // TODO + ++found; + } + } + return found; +} + +static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const char* valueStr, struct mCoreMemorySearchResults* out, size_t limit) { + // TODO: As str + + char* end; + uint64_t value; + + size_t found = 0; + + struct mCoreMemorySearchResults tmp; + mCoreMemorySearchResultsInit(&tmp, 0); + + // Decimal: + value = strtoull(valueStr, &end, 10); + if (end && !end[0]) { + if (value > 0x10000) { + found += _search32(mem, size, block, value, out, limit ? limit - found : 0); + } else if (value > 0x100) { + found += _search16(mem, size, block, value, out, limit ? limit - found : 0); + } else { + found += _search8(mem, size, block, value, out, limit ? limit - found : 0); + } + + uint32_t divisor = 1; + while (value && !(value % 10)) { + mCoreMemorySearchResultsClear(&tmp); + value /= 10; + divisor *= 10; + + if (value > 0x10000) { + found += _search32(mem, size, block, value, &tmp, limit ? limit - found : 0); + } else if (value > 0x100) { + found += _search16(mem, size, block, value, &tmp, limit ? limit - found : 0); + } else { + found += _search8(mem, size, block, value, &tmp, limit ? limit - found : 0); + } + size_t i; + for (i = 0; i < mCoreMemorySearchResultsSize(&tmp); ++i) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsGetPointer(&tmp, i); + res->guessDivisor = divisor; + *mCoreMemorySearchResultsAppend(out) = *res; + } + } + } + + // Hex: + value = strtoull(valueStr, &end, 16); + if (end && !end[0]) { + if (value > 0x10000) { + found += _search32(mem, size, block, value, out, limit ? limit - found : 0); + } else if (value > 0x100) { + found += _search16(mem, size, block, value, out, limit ? limit - found : 0); + } else { + found += _search8(mem, size, block, value, out, limit ? limit - found : 0); + } + + uint32_t divisor = 1; + while (value && !(value & 0xF)) { + mCoreMemorySearchResultsClear(&tmp); + value >>= 4; + divisor <<= 4; + + if (value > 0x10000) { + found += _search32(mem, size, block, value, &tmp, limit ? limit - found : 0); + } else if (value > 0x100) { + found += _search16(mem, size, block, value, &tmp, limit ? limit - found : 0); + } else { + found += _search8(mem, size, block, value, &tmp, limit ? limit - found : 0); + } + size_t i; + for (i = 0; i < mCoreMemorySearchResultsSize(&tmp); ++i) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsGetPointer(&tmp, i); + res->guessDivisor = divisor; + *mCoreMemorySearchResultsAppend(out) = *res; + } + } + } + + mCoreMemorySearchResultsDeinit(&tmp); + return found; +} + +static size_t _search(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit) { + switch (params->type) { + case mCORE_MEMORY_SEARCH_32: + return _search32(mem, size, block, params->value32, out, limit); + case mCORE_MEMORY_SEARCH_16: + return _search16(mem, size, block, params->value16, out, limit); + case mCORE_MEMORY_SEARCH_8: + return _search8(mem, size, block, params->value8, out, limit); + case mCORE_MEMORY_SEARCH_STRING: + return _searchStr(mem, size, block, params->valueStr, out, limit); + case mCORE_MEMORY_SEARCH_GUESS: + return _searchGuess(mem, size, block, params->valueStr, out, limit); + } +} + +void mCoreMemorySearch(struct mCore* core, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit) { + const struct mCoreMemoryBlock* blocks; + size_t nBlocks = core->listMemoryBlocks(core, &blocks); + size_t found = 0; + + size_t b; + for (b = 0; (!limit || found < limit) && b < nBlocks; ++b) { + size_t size; + const struct mCoreMemoryBlock* block = &blocks[b]; + if (!(block->flags & params->memoryFlags)) { + continue; + } + void* mem = core->getMemoryBlock(core, block->id, &size); + if (!mem) { + continue; + } + if (size > block->end - block->start) { + size = block->end - block->start; // TOOD: Segments + } + found += _search(mem, size, block, params, out, limit ? limit - found : 0); + } +} + +bool _testGuess(struct mCore* core, const struct mCoreMemorySearchResult* res, const struct mCoreMemorySearchParams* params) { + uint64_t value; + char* end; + + value = strtoull(params->valueStr, &end, 10); + if (end) { + if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor == value) { + return true; + } + if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor == value) { + return true; + } + if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor == value) { + return true; + } + } + + value = strtoull(params->valueStr, &end, 16); + if (end) { + if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor == value) { + return true; + } + if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor == value) { + return true; + } + if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor == value) { + return true; + } + } + return false; +} + +void mCoreMemorySearchRepeat(struct mCore* core, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* inout) { + size_t i; + for (i = 0; i < mCoreMemorySearchResultsSize(inout); ++i) { + struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsGetPointer(inout, i); + switch (res->type) { + case mCORE_MEMORY_SEARCH_8: + switch (params->type) { + case mCORE_MEMORY_SEARCH_8: + if (core->rawRead8(core, res->address, res->segment) != params->value8) { + mCoreMemorySearchResultsShift(inout, i, 1); + --i; + } + break; + case mCORE_MEMORY_SEARCH_16: + if (core->rawRead8(core, res->address, res->segment) != params->value16) { + mCoreMemorySearchResultsShift(inout, i, 1); + --i; + } + break; + case mCORE_MEMORY_SEARCH_32: + if (core->rawRead32(core, res->address, res->segment) != params->value32) { + mCoreMemorySearchResultsShift(inout, i, 1); + --i; + } + break; + case mCORE_MEMORY_SEARCH_GUESS: + if (!_testGuess(core, res, params)) { + mCoreMemorySearchResultsShift(inout, i, 1); + --i; + } + break; + default: + break; + } + break; + case mCORE_MEMORY_SEARCH_16: + switch (params->type) { + case mCORE_MEMORY_SEARCH_16: + if (core->rawRead16(core, res->address, res->segment) != params->value16) { + mCoreMemorySearchResultsShift(inout, i, 1); + --i; + } + break; + case mCORE_MEMORY_SEARCH_32: + if (core->rawRead32(core, res->address, res->segment) != params->value32) { + mCoreMemorySearchResultsShift(inout, i, 1); + --i; + } + break; + case mCORE_MEMORY_SEARCH_GUESS: + if (!_testGuess(core, res, params)) { + mCoreMemorySearchResultsShift(inout, i, 1); + --i; + } + break; + default: + break; + } + break; + case mCORE_MEMORY_SEARCH_32: + switch (params->type) { + case mCORE_MEMORY_SEARCH_32: + if (core->rawRead32(core, res->address, res->segment) != params->value32) { + mCoreMemorySearchResultsShift(inout, i, 1); + --i; + } + break; + case mCORE_MEMORY_SEARCH_GUESS: + if (!_testGuess(core, res, params)) { + mCoreMemorySearchResultsShift(inout, i, 1); + --i; + } + break; + default: + break; + } + break; + case mCORE_MEMORY_SEARCH_STRING: + case mCORE_MEMORY_SEARCH_GUESS: + // TOOD + break; + } + } +} diff --git a/src/core/serialize.c b/src/core/serialize.c index bd6abd112..9efca53f7 100644 --- a/src/core/serialize.c +++ b/src/core/serialize.c @@ -303,6 +303,36 @@ bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) { struct mStateExtdata extdata; mStateExtdataInit(&extdata); size_t stateSize = core->stateSize(core); + + if (flags & SAVESTATE_METADATA) { + uint64_t creationUsec; +#ifndef _MSC_VER + struct timeval tv; + if (!gettimeofday(&tv, 0)) { + uint64_t usec = tv.tv_usec; + usec += tv.tv_sec * 1000000LL; + STORE_64LE(usec, 0, &creationUsec); + } +#else + struct timespec ts; + if (timespec_get(&ts, TIME_UTC)) { + uint64_t usec = ts.tv_nsec / 1000; + usec += ts.tv_sec * 1000000LL; + STORE_64LE(usec, 0, &creationUsec); + } +#endif + else { + creationUsec = 0; + } + + struct mStateExtdataItem item = { + .size = sizeof(creationUsec), + .data = &creationUsec, + .clean = NULL + }; + mStateExtdataPut(&extdata, EXTDATA_META_TIME, &item); + } + if (flags & SAVESTATE_SAVEDATA) { void* sram = NULL; size_t size = core->savedataClone(core, &sram); diff --git a/src/core/timing.c b/src/core/timing.c index 11b808a93..1d22a93cb 100644 --- a/src/core/timing.c +++ b/src/core/timing.c @@ -89,5 +89,9 @@ int32_t mTimingNextEvent(struct mTiming* timing) { if (!next) { return INT_MAX; } - return next->when - timing->masterCycles; + return next->when - timing->masterCycles - *timing->relativeCycles; +} + +int32_t mTimingUntil(const struct mTiming* timing, const struct mTimingEvent* event) { + return event->when - timing->masterCycles - *timing->relativeCycles; } diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index 70cff2a5f..cc911d8c7 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -44,6 +44,7 @@ static void _clearBreakpoint(struct CLIDebugger*, struct CLIDebugVector*); static void _setWatchpoint(struct CLIDebugger*, struct CLIDebugVector*); static void _setReadWatchpoint(struct CLIDebugger*, struct CLIDebugVector*); static void _setWriteWatchpoint(struct CLIDebugger*, struct CLIDebugVector*); +static void _trace(struct CLIDebugger*, struct CLIDebugVector*); static void _writeByte(struct CLIDebugger*, struct CLIDebugVector*); static void _writeHalfword(struct CLIDebugger*, struct CLIDebugVector*); static void _writeWord(struct CLIDebugger*, struct CLIDebugVector*); @@ -80,6 +81,7 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = { { "r/2", _readHalfword, CLIDVParse, "Read a halfword from a specified offset" }, { "r/4", _readWord, CLIDVParse, "Read a word from a specified offset" }, { "status", _printStatus, 0, "Print the current status" }, + { "trace", _trace, CLIDVParse, "Trace a fixed number of instructions" }, { "w", _setWatchpoint, CLIDVParse, "Set a watchpoint" }, { "w/1", _writeByte, CLIDVParse, "Write a byte at a specified offset" }, { "w/2", _writeHalfword, CLIDVParse, "Write a halfword at a specified offset" }, @@ -469,6 +471,27 @@ static void _clearBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector } } +static void _trace(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || dv->type != CLIDV_INT_TYPE) { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); + return; + } + + char trace[1024]; + trace[sizeof(trace) - 1] = '\0'; + + int i; + for (i = 0; i < dv->intValue; ++i) { + debugger->d.core->step(debugger->d.core); + size_t traceSize = sizeof(trace) - 1; + debugger->d.platform->trace(debugger->d.platform, trace, &traceSize); + if (traceSize + 1 < sizeof(trace)) { + trace[traceSize + 1] = '\0'; + } + debugger->backend->printf(debugger->backend, "%s\n", trace); + } +} + static void _printStatus(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); debugger->system->printStatus(debugger->system); diff --git a/src/debugger/debugger.c b/src/debugger/debugger.c index bf7b9d99c..bac157f7c 100644 --- a/src/debugger/debugger.c +++ b/src/debugger/debugger.c @@ -3,7 +3,7 @@ * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include +#include #include @@ -97,6 +97,13 @@ void mDebuggerRun(struct mDebugger* debugger) { } } +void mDebuggerRunFrame(struct mDebugger* debugger) { + int32_t frame = debugger->core->frameCounter(debugger->core); + do { + mDebuggerRun(debugger); + } while (debugger->core->frameCounter(debugger->core) == frame); +} + void mDebuggerEnter(struct mDebugger* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) { debugger->state = DEBUGGER_PAUSED; if (debugger->platform->entered) { diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index ad59ae893..0f997f395 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -431,7 +431,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { runner->core->reset(runner->core); break; case RUNNER_SAVE_STATE: - mCoreSaveState(runner->core, ((int) item->data) >> 16, SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA); + mCoreSaveState(runner->core, ((int) item->data) >> 16, SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA); break; case RUNNER_LOAD_STATE: mCoreLoadState(runner->core, ((int) item->data) >> 16, SAVESTATE_SCREENSHOT | SAVESTATE_RTC); diff --git a/src/gb/audio.c b/src/gb/audio.c index 8fbca6d9b..3b384cdf8 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -337,6 +337,7 @@ void GBAudioWriteNR34(struct GBAudio* audio, uint8_t value) { } audio->ch3.window = 0; } + mTimingDeschedule(audio->timing, &audio->ch3Fade); mTimingDeschedule(audio->timing, &audio->ch3Event); if (audio->playingCh3) { audio->ch3.readable = audio->style != GB_AUDIO_DMG; @@ -554,7 +555,7 @@ void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate) { if (audio->ch2.envelope.dead == 2) { mTimingDeschedule(timing, &audio->ch2Event); } - _updateSquareSample(&audio->ch1); + _updateSquareSample(&audio->ch2); } } @@ -708,7 +709,7 @@ bool _writeEnvelope(struct GBAudioEnvelope* envelope, uint8_t value) { } static void _updateSquareSample(struct GBAudioSquareChannel* ch) { - ch->sample = (ch->control.hi * ch->envelope.currentVolume - 8) * 0x10; + ch->sample = (ch->control.hi * 2 - 1) * ch->envelope.currentVolume * 0x8; } static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch) { @@ -863,6 +864,7 @@ static void _updateChannel3(struct mTiming* timing, void* user, uint32_t cyclesL ch->sample *= volume * 4; audio->ch3.readable = true; if (audio->style == GB_AUDIO_DMG) { + mTimingDeschedule(audio->timing, &audio->ch3Fade); mTimingSchedule(timing, &audio->ch3Fade, 2 - cyclesLate); } int cycles = 2 * (2048 - ch->rate); diff --git a/src/gb/core.c b/src/gb/core.c index 339eaa477..018517837 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -56,6 +56,28 @@ const static struct LR35902Segment _GBCSegments[] = { { 0 } }; +const static struct mCoreMemoryBlock _GBMemoryBlocks[] = { + { -1, "mem", "All", "All", 0, 0x10000, 0x10000, mCORE_MEMORY_VIRTUAL }, + { GB_REGION_CART_BANK0, "cart0", "ROM Bank", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_SIZE_CART_BANK0 * 2, 0x800000, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED, 511 }, + { GB_REGION_VRAM, "vram", "VRAM", "Video RAM (8kiB)", GB_BASE_VRAM, GB_BASE_VRAM + GB_SIZE_VRAM, GB_SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { GB_REGION_EXTERNAL_RAM, "sram", "SRAM", "External RAM (8kiB)", GB_BASE_EXTERNAL_RAM, GB_BASE_EXTERNAL_RAM + GB_SIZE_EXTERNAL_RAM, GB_SIZE_EXTERNAL_RAM * 4, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 3 }, + { GB_REGION_WORKING_RAM_BANK0, "wram", "WRAM", "Working RAM (8kiB)", GB_BASE_WORKING_RAM_BANK0, GB_BASE_WORKING_RAM_BANK0 + GB_SIZE_WORKING_RAM_BANK0 * 2 , GB_SIZE_WORKING_RAM_BANK0 * 2, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { GB_BASE_OAM, "oam", "OAM", "OBJ Attribute Memory", GB_BASE_OAM, GB_BASE_OAM + GB_SIZE_OAM, GB_SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { GB_BASE_IO, "io", "MMIO", "Memory-Mapped I/O", GB_BASE_IO, GB_BASE_IO + GB_SIZE_IO, GB_SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { 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 }, +}; + +const static struct mCoreMemoryBlock _GBCMemoryBlocks[] = { + { -1, "mem", "All", "All", 0, 0x10000, 0x10000, mCORE_MEMORY_VIRTUAL }, + { GB_REGION_CART_BANK0, "cart0", "ROM Bank", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_SIZE_CART_BANK0 * 2, 0x800000, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED, 511 }, + { GB_REGION_VRAM, "vram", "VRAM", "Video RAM (8kiB)", GB_BASE_VRAM, GB_BASE_VRAM + GB_SIZE_VRAM, GB_SIZE_VRAM * 2, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 1 }, + { GB_REGION_EXTERNAL_RAM, "sram", "SRAM", "External RAM (8kiB)", GB_BASE_EXTERNAL_RAM, GB_BASE_EXTERNAL_RAM + GB_SIZE_EXTERNAL_RAM, GB_SIZE_EXTERNAL_RAM * 4, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 3 }, + { GB_REGION_WORKING_RAM_BANK0, "wram", "WRAM", "Working RAM (8kiB)", GB_BASE_WORKING_RAM_BANK0, GB_BASE_WORKING_RAM_BANK0 + GB_SIZE_WORKING_RAM_BANK0 * 2, GB_SIZE_WORKING_RAM_BANK0 * 8, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 7 }, + { GB_BASE_OAM, "oam", "OAM", "OBJ Attribute Memory", GB_BASE_OAM, GB_BASE_OAM + GB_SIZE_OAM, GB_SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { GB_BASE_IO, "io", "MMIO", "Memory-Mapped I/O", GB_BASE_IO, GB_BASE_IO + GB_SIZE_IO, GB_SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { 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 }, +}; + struct mVideoLogContext; struct GBCore { struct mCore d; @@ -419,11 +441,13 @@ static bool _GBCoreSaveState(struct mCore* core, void* state) { static void _GBCoreSetKeys(struct mCore* core, uint32_t keys) { struct GBCore* gbcore = (struct GBCore*) core; gbcore->keys = keys; + GBTestKeypadIRQ(core->board); } static void _GBCoreAddKeys(struct mCore* core, uint32_t keys) { struct GBCore* gbcore = (struct GBCore*) core; gbcore->keys |= keys; + GBTestKeypadIRQ(core->board); } static void _GBCoreClearKeys(struct mCore* core, uint32_t keys) { @@ -539,6 +563,48 @@ static void _GBCoreRawWrite32(struct mCore* core, uint32_t address, int segment, GBPatch8(cpu, address + 3, value >> 24, NULL, segment); } +size_t _GBListMemoryBlocks(const struct mCore* core, const struct mCoreMemoryBlock** blocks) { + const struct GB* gb = core->board; + switch (gb->model) { + case GB_MODEL_DMG: + case GB_MODEL_SGB: + default: + *blocks = _GBMemoryBlocks; + return sizeof(_GBMemoryBlocks) / sizeof(*_GBMemoryBlocks); + case GB_MODEL_CGB: + case GB_MODEL_AGB: + *blocks = _GBCMemoryBlocks; + return sizeof(_GBCMemoryBlocks) / sizeof(*_GBCMemoryBlocks); + } +} + +void* _GBGetMemoryBlock(struct mCore* core, size_t id, size_t* sizeOut) { + struct GB* gb = core->board; + bool isCgb = gb->model >= GB_MODEL_CGB; + switch (id) { + default: + return NULL; + case GB_REGION_CART_BANK0: + *sizeOut = gb->memory.romSize; + return gb->memory.rom; + case GB_REGION_VRAM: + *sizeOut = GB_SIZE_WORKING_RAM_BANK0 * (isCgb ? 1 : 2); + return gb->video.vram; + case GB_REGION_EXTERNAL_RAM: + *sizeOut = gb->sramSize; + return gb->memory.sram; + case GB_REGION_WORKING_RAM_BANK0: + *sizeOut = GB_SIZE_VRAM * (isCgb ? 8 : 2); + return gb->memory.wram; + case GB_BASE_OAM: + *sizeOut = GB_SIZE_OAM; + return gb->video.oam.raw; + case GB_BASE_HRAM: + *sizeOut = GB_SIZE_HRAM; + return gb->memory.hram; + } +} + #ifdef USE_DEBUGGERS static bool _GBCoreSupportsDebuggerType(struct mCore* core, enum mDebuggerType type) { UNUSED(core); @@ -771,6 +837,8 @@ struct mCore* GBCoreCreate(void) { core->rawWrite8 = _GBCoreRawWrite8; core->rawWrite16 = _GBCoreRawWrite16; core->rawWrite32 = _GBCoreRawWrite32; + core->listMemoryBlocks = _GBListMemoryBlocks; + core->getMemoryBlock = _GBGetMemoryBlock; #ifdef USE_DEBUGGERS core->supportsDebuggerType = _GBCoreSupportsDebuggerType; core->debuggerPlatform = _GBCoreDebuggerPlatform; diff --git a/src/gb/debugger/cli.c b/src/gb/debugger/cli.c index 809961290..2f59293d1 100644 --- a/src/gb/debugger/cli.c +++ b/src/gb/debugger/cli.c @@ -118,5 +118,5 @@ static void _save(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger->system; - mCoreSaveState(gbDebugger->core, dv->intValue, SAVESTATE_SCREENSHOT | SAVESTATE_RTC); + mCoreSaveState(gbDebugger->core, dv->intValue, SAVESTATE_SCREENSHOT | SAVESTATE_RTC | SAVESTATE_METADATA); } diff --git a/src/gb/gb.c b/src/gb/gb.c index a97fe97ef..7338701cc 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -433,6 +433,7 @@ void GBReset(struct LR35902Core* cpu) { cpu->e = 0xD8; cpu->h = 1; cpu->l = 0x4D; + gb->timer.internalDiv = 0x2AF3; break; case GB_MODEL_AGB: cpu->b = 1; @@ -444,6 +445,7 @@ void GBReset(struct LR35902Core* cpu) { cpu->e = 0x08; cpu->h = 0; cpu->l = 0x7C; + gb->timer.internalDiv = 0x7A8; break; } @@ -710,4 +712,6 @@ void GBFrameEnded(struct GB* gb) { mCheatRefresh(device, cheats); } } + + GBTestKeypadIRQ(gb); } diff --git a/src/gb/io.c b/src/gb/io.c index 8b4102c0d..3be656499 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -564,6 +564,13 @@ uint8_t GBIORead(struct GB* gb, unsigned address) { return gb->memory.io[address] | _registerMask[address]; } +void GBTestKeypadIRQ(struct GB* gb) { + if (_readKeys(gb)) { + gb->memory.io[REG_IF] |= (1 << GB_IRQ_KEYPAD); + GBUpdateIRQs(gb); + } +} + struct GBSerializedState; void GBIOSerialize(const struct GB* gb, struct GBSerializedState* state) { memcpy(state->io, gb->memory.io, GB_SIZE_IO); diff --git a/src/gb/mbc.c b/src/gb/mbc.c index 0710f10c0..87fae1510 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -28,6 +28,10 @@ static void _GBMBC5(struct GB*, uint16_t address, uint8_t value); static void _GBMBC6(struct GB*, uint16_t address, uint8_t value); static void _GBMBC7(struct GB*, uint16_t address, uint8_t value); static void _GBHuC3(struct GB*, uint16_t address, uint8_t value); +static void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value); + +static uint8_t _GBMBC7Read(struct GBMemory*, uint16_t address); +static uint8_t _GBPocketCamRead(struct GBMemory*, uint16_t address); void GBMBCSwitchBank(struct GB* gb, int bank) { size_t bankStart = bank * GB_SIZE_CART_BANK0; @@ -75,7 +79,11 @@ static bool _isMulticart(const uint8_t* mem) { void GBMBCSwitchSramBank(struct GB* gb, int bank) { size_t bankStart = bank * GB_SIZE_EXTERNAL_RAM; - GBResizeSram(gb, (bank + 1) * GB_SIZE_EXTERNAL_RAM); + if (bankStart + GB_SIZE_EXTERNAL_RAM > gb->sramSize) { + mLOG(GB_MBC, GAME_ERROR, "Attempting to switch to an invalid RAM bank: %0X", bank); + bankStart &= (gb->sramSize - 1); + bank = bankStart / GB_SIZE_EXTERNAL_RAM; + } gb->memory.sramBank = &gb->memory.sram[bankStart]; gb->memory.sramCurrentBank = bank; } @@ -148,6 +156,12 @@ void GBMBCInit(struct GB* gb) { case 0x22: gb->memory.mbcType = GB_MBC7; break; + case 0xFC: + gb->memory.mbcType = GB_POCKETCAM; + break; + case 0xFD: + gb->memory.mbcType = GB_HuC1; + break; case 0xFE: gb->memory.mbcType = GB_HuC3; break; @@ -156,51 +170,57 @@ void GBMBCInit(struct GB* gb) { } else { gb->memory.mbcType = GB_MBC_NONE; } + gb->memory.mbcRead = NULL; switch (gb->memory.mbcType) { case GB_MBC_NONE: - gb->memory.mbc = _GBMBCNone; + gb->memory.mbcWrite = _GBMBCNone; break; case GB_MBC1: - gb->memory.mbc = _GBMBC1; + gb->memory.mbcWrite = _GBMBC1; break; case GB_MBC2: - gb->memory.mbc = _GBMBC2; + gb->memory.mbcWrite = _GBMBC2; gb->sramSize = 0x200; break; case GB_MBC3: - gb->memory.mbc = _GBMBC3; + gb->memory.mbcWrite = _GBMBC3; break; default: mLOG(GB_MBC, WARN, "Unknown MBC type: %02X", cart->type); // Fall through case GB_MBC5: - gb->memory.mbc = _GBMBC5; + gb->memory.mbcWrite = _GBMBC5; break; case GB_MBC6: mLOG(GB_MBC, WARN, "unimplemented MBC: MBC6"); - gb->memory.mbc = _GBMBC6; + gb->memory.mbcWrite = _GBMBC6; break; case GB_MBC7: - gb->memory.mbc = _GBMBC7; - gb->sramSize = GB_SIZE_EXTERNAL_RAM; + gb->memory.mbcWrite = _GBMBC7; + gb->memory.mbcRead = _GBMBC7Read; + gb->sramSize = 0x100; break; case GB_MMM01: mLOG(GB_MBC, WARN, "unimplemented MBC: MMM01"); - gb->memory.mbc = _GBMBC1; + gb->memory.mbcWrite = _GBMBC1; break; case GB_HuC1: mLOG(GB_MBC, WARN, "unimplemented MBC: HuC-1"); - gb->memory.mbc = _GBMBC1; + gb->memory.mbcWrite = _GBMBC1; break; case GB_HuC3: - gb->memory.mbc = _GBHuC3; + gb->memory.mbcWrite = _GBHuC3; break; case GB_MBC3_RTC: memset(gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs)); - gb->memory.mbc = _GBMBC3; + gb->memory.mbcWrite = _GBMBC3; break; case GB_MBC5_RUMBLE: - gb->memory.mbc = _GBMBC5; + gb->memory.mbcWrite = _GBMBC5; + break; + case GB_POCKETCAM: + gb->memory.mbcWrite = _GBPocketCam; + gb->memory.mbcRead = _GBPocketCamRead; break; } @@ -350,7 +370,8 @@ void _GBMBC2(struct GB* gb, uint16_t address, uint8_t value) { // TODO mLOG(GB_MBC, STUB, "MBC2 unknown address: %04X:%02X", address, value); break; - }} + } +} void _GBMBC3(struct GB* gb, uint16_t address, uint8_t value) { struct GBMemory* memory = &gb->memory; @@ -451,12 +472,25 @@ void _GBMBC6(struct GB* gb, uint16_t address, uint8_t value) { void _GBMBC7(struct GB* gb, uint16_t address, uint8_t value) { int bank = value & 0x7F; switch (address >> 13) { + case 0x0: + switch (value) { + default: + case 0: + gb->memory.mbcState.mbc7.access = 0; + break; + case 0xA: + gb->memory.mbcState.mbc7.access |= 1; + break; + } + break; case 0x1: GBMBCSwitchBank(gb, bank); break; case 0x2: - if (value < 0x10) { - GBMBCSwitchSramBank(gb, value); + if (value == 0x40) { + gb->memory.mbcState.mbc7.access |= 2; + } else { + gb->memory.mbcState.mbc7.access &= ~2; } break; default: @@ -466,19 +500,17 @@ void _GBMBC7(struct GB* gb, uint16_t address, uint8_t value) { } } -uint8_t GBMBC7Read(struct GBMemory* memory, uint16_t address) { +uint8_t _GBMBC7Read(struct GBMemory* memory, uint16_t address) { struct GBMBC7State* mbc7 = &memory->mbcState.mbc7; + if (mbc7->access != 3) { + return 0xFF; + } switch (address & 0xF0) { - case 0x00: - case 0x10: - case 0x60: - case 0x70: - return 0; case 0x20: if (memory->rotation && memory->rotation->readTiltX) { int32_t x = -memory->rotation->readTiltX(memory->rotation); x >>= 21; - x += 2047; + x += 0x81D0; return x; } return 0xFF; @@ -486,7 +518,7 @@ uint8_t GBMBC7Read(struct GBMemory* memory, uint16_t address) { if (memory->rotation && memory->rotation->readTiltX) { int32_t x = -memory->rotation->readTiltX(memory->rotation); x >>= 21; - x += 2047; + x += 0x81D0; return x >> 8; } return 7; @@ -494,7 +526,7 @@ uint8_t GBMBC7Read(struct GBMemory* memory, uint16_t address) { if (memory->rotation && memory->rotation->readTiltY) { int32_t y = -memory->rotation->readTiltY(memory->rotation); y >>= 21; - y += 2047; + y += 0x81D0; return y; } return 0xFF; @@ -502,144 +534,142 @@ uint8_t GBMBC7Read(struct GBMemory* memory, uint16_t address) { if (memory->rotation && memory->rotation->readTiltY) { int32_t y = -memory->rotation->readTiltY(memory->rotation); y >>= 21; - y += 2047; + y += 0x81D0; return y >> 8; } return 7; + case 0x60: + return 0; case 0x80: - return (mbc7->sr >> 16) & 1; + return mbc7->eeprom; default: return 0xFF; } } void GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t value) { - if ((address & 0xF0) != 0x80) { + struct GBMBC7State* mbc7 = &memory->mbcState.mbc7; + if (mbc7->access != 3) { return; } - struct GBMBC7State* mbc7 = &memory->mbcState.mbc7; - GBMBC7Field old = memory->mbcState.mbc7.field; - mbc7->field = GBMBC7FieldClearIO(value); - if (!GBMBC7FieldIsCS(old) && GBMBC7FieldIsCS(value)) { - if (mbc7->state == GBMBC7_STATE_WRITE) { - if (mbc7->writable) { - memory->sramBank[mbc7->address * 2] = mbc7->sr >> 8; - memory->sramBank[mbc7->address * 2 + 1] = mbc7->sr; - } - mbc7->sr = 0x1FFFF; - mbc7->state = GBMBC7_STATE_NULL; - } else { - mbc7->state = GBMBC7_STATE_IDLE; + switch (address & 0xF0) { + case 0x00: + mbc7->latch = (value & 0x55) == 0x55; + return; + case 0x10: + mbc7->latch |= (value & 0xAA); + if (mbc7->latch == 0xFF && memory->rotation && memory->rotation->sample) { + memory->rotation->sample(memory->rotation); } + mbc7->latch = 0; + return; + default: + mLOG(GB_MBC, STUB, "MBC7 unknown register: %04X:%02X", address, value); + return; + case 0x80: + break; } - if (!GBMBC7FieldIsSK(old) && GBMBC7FieldIsSK(value)) { - if (mbc7->state > GBMBC7_STATE_IDLE && mbc7->state != GBMBC7_STATE_READ) { + GBMBC7Field old = memory->mbcState.mbc7.eeprom; + value = GBMBC7FieldFillDO(value); // Hi-Z + if (!GBMBC7FieldIsCS(old) && GBMBC7FieldIsCS(value)) { + mbc7->state = GBMBC7_STATE_IDLE; + } + if (!GBMBC7FieldIsCLK(old) && GBMBC7FieldIsCLK(value)) { + if (mbc7->state == GBMBC7_STATE_READ_COMMAND || mbc7->state == GBMBC7_STATE_EEPROM_WRITE || mbc7->state == GBMBC7_STATE_EEPROM_WRAL) { mbc7->sr <<= 1; - mbc7->sr |= GBMBC7FieldGetIO(value); + mbc7->sr |= GBMBC7FieldGetDI(value); ++mbc7->srBits; } switch (mbc7->state) { case GBMBC7_STATE_IDLE: - if (GBMBC7FieldIsIO(value)) { + if (GBMBC7FieldIsDI(value)) { mbc7->state = GBMBC7_STATE_READ_COMMAND; mbc7->srBits = 0; mbc7->sr = 0; } break; case GBMBC7_STATE_READ_COMMAND: - if (mbc7->srBits == 2) { - mbc7->state = GBMBC7_STATE_READ_ADDRESS; - mbc7->srBits = 0; - mbc7->command = mbc7->sr; - } - break; - case GBMBC7_STATE_READ_ADDRESS: - if (mbc7->srBits == 8) { - mbc7->state = GBMBC7_STATE_COMMAND_0 + mbc7->command; - mbc7->srBits = 0; - mbc7->address = mbc7->sr; - if (mbc7->state == GBMBC7_STATE_COMMAND_0) { - switch (mbc7->address >> 6) { - case 0: - mbc7->writable = false; - mbc7->state = GBMBC7_STATE_NULL; - break; - case 3: - mbc7->writable = true; - mbc7->state = GBMBC7_STATE_NULL; - break; - } + if (mbc7->srBits == 10) { + mbc7->state = 0x10 | (mbc7->sr >> 6); + if (mbc7->state & 0xC) { + mbc7->state &= ~0x3; } - } - break; - case GBMBC7_STATE_COMMAND_0: - if (mbc7->srBits == 16) { - switch (mbc7->address >> 6) { - case 0: - mbc7->writable = false; - mbc7->state = GBMBC7_STATE_NULL; - break; - case 1: - mbc7->state = GBMBC7_STATE_WRITE; - if (mbc7->writable) { - int i; - for (i = 0; i < 256; ++i) { - memory->sramBank[i * 2] = mbc7->sr >> 8; - memory->sramBank[i * 2 + 1] = mbc7->sr; - } - } - break; - case 2: - mbc7->state = GBMBC7_STATE_WRITE; - if (mbc7->writable) { - int i; - for (i = 0; i < 256; ++i) { - memory->sramBank[i * 2] = 0xFF; - memory->sramBank[i * 2 + 1] = 0xFF; - } - } - break; - case 3: - mbc7->writable = true; - mbc7->state = GBMBC7_STATE_NULL; - break; - } - } - break; - case GBMBC7_STATE_COMMAND_SR_WRITE: - if (mbc7->srBits == 16) { mbc7->srBits = 0; - mbc7->state = GBMBC7_STATE_WRITE; + mbc7->address = mbc7->sr & 0x7F; } break; - case GBMBC7_STATE_COMMAND_SR_READ: - if (mbc7->srBits == 1) { - mbc7->sr = memory->sramBank[mbc7->address * 2] << 8; - mbc7->sr |= memory->sramBank[mbc7->address * 2 + 1]; - mbc7->srBits = 0; - mbc7->state = GBMBC7_STATE_READ; - } - break; - case GBMBC7_STATE_COMMAND_SR_FILL: - if (mbc7->srBits == 16) { - mbc7->sr = 0xFFFF; - mbc7->srBits = 0; - mbc7->state = GBMBC7_STATE_WRITE; + case GBMBC7_STATE_DO: + value = GBMBC7FieldSetDO(value, mbc7->sr >> 15); + mbc7->sr <<= 1; + --mbc7->srBits; + if (!mbc7->srBits) { + mbc7->state = GBMBC7_STATE_IDLE; } break; default: break; } - } else if (GBMBC7FieldIsSK(old) && !GBMBC7FieldIsSK(value)) { - if (mbc7->state == GBMBC7_STATE_READ) { - mbc7->sr <<= 1; - ++mbc7->srBits; + switch (mbc7->state) { + case GBMBC7_STATE_EEPROM_EWEN: + mbc7->writable = true; + mbc7->state = GBMBC7_STATE_IDLE; + break; + case GBMBC7_STATE_EEPROM_EWDS: + mbc7->writable = false; + mbc7->state = GBMBC7_STATE_IDLE; + break; + case GBMBC7_STATE_EEPROM_WRITE: if (mbc7->srBits == 16) { - mbc7->srBits = 0; - mbc7->state = GBMBC7_STATE_NULL; + if (mbc7->writable) { + memory->sram[mbc7->address * 2] = mbc7->sr >> 8; + memory->sram[mbc7->address * 2 + 1] = mbc7->sr; + } + mbc7->state = GBMBC7_STATE_IDLE; } + break; + case GBMBC7_STATE_EEPROM_ERASE: + if (mbc7->writable) { + memory->sram[mbc7->address * 2] = 0xFF; + memory->sram[mbc7->address * 2 + 1] = 0xFF; + } + mbc7->state = GBMBC7_STATE_IDLE; + break; + case GBMBC7_STATE_EEPROM_READ: + mbc7->srBits = 16; + mbc7->sr = memory->sram[mbc7->address * 2] << 8; + mbc7->sr |= memory->sram[mbc7->address * 2 + 1]; + mbc7->state = GBMBC7_STATE_DO; + value = GBMBC7FieldClearDO(value); + break; + case GBMBC7_STATE_EEPROM_WRAL: + if (mbc7->srBits == 16) { + if (mbc7->writable) { + int i; + for (i = 0; i < 128; ++i) { + memory->sram[i * 2] = mbc7->sr >> 8; + memory->sram[i * 2 + 1] = mbc7->sr; + } + } + mbc7->state = GBMBC7_STATE_IDLE; + } + break; + case GBMBC7_STATE_EEPROM_ERAL: + if (mbc7->writable) { + int i; + for (i = 0; i < 128; ++i) { + memory->sram[i * 2] = 0xFF; + memory->sram[i * 2 + 1] = 0xFF; + } + } + mbc7->state = GBMBC7_STATE_IDLE; + break; + default: + break; } + } else if (GBMBC7FieldIsCS(value) && GBMBC7FieldIsCLK(old) && !GBMBC7FieldIsCLK(value)) { + value = GBMBC7FieldSetDO(value, GBMBC7FieldGetDO(old)); } + mbc7->eeprom = value; } void _GBHuC3(struct GB* gb, uint16_t address, uint8_t value) { @@ -674,6 +704,52 @@ void _GBHuC3(struct GB* gb, uint16_t address, uint8_t value) { } } +void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value) { + struct GBMemory* memory = &gb->memory; + int bank = value & 0x3F; + switch (address >> 13) { + case 0x0: + switch (value) { + case 0: + memory->sramAccess = false; + break; + case 0xA: + memory->sramAccess = true; + GBMBCSwitchSramBank(gb, memory->sramCurrentBank); + break; + default: + // TODO + mLOG(GB_MBC, STUB, "Pocket Cam unknown value %02X", value); + break; + } + break; + case 0x1: + GBMBCSwitchBank(gb, bank); + break; + case 0x2: + if (value < 0x10) { + GBMBCSwitchSramBank(gb, value); + memory->mbcState.pocketCam.registersActive = false; + } else { + memory->mbcState.pocketCam.registersActive = true; + } + break; + default: + mLOG(GB_MBC, STUB, "Pocket Cam unknown address: %04X:%02X", address, value); + break; + } +} + +uint8_t _GBPocketCamRead(struct GBMemory* memory, uint16_t address) { + if (memory->mbcState.pocketCam.registersActive) { + return 0; + } + if (!memory->sramAccess) { + return 0xFF; + } + return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; +} + void GBMBCRTCRead(struct GB* gb) { struct GBMBCRTCSaveBuffer rtcBuffer; struct VFile* vf = gb->sramVf; diff --git a/src/gb/memory.c b/src/gb/memory.c index 8484941b7..c424f5a92 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -99,7 +99,8 @@ void GBMemoryInit(struct GB* gb) { gb->memory.romSize = 0; gb->memory.sram = 0; gb->memory.mbcType = GB_MBC_AUTODETECT; - gb->memory.mbc = 0; + gb->memory.mbcRead = NULL; + gb->memory.mbcWrite = NULL; gb->memory.rtc = NULL; @@ -215,10 +216,10 @@ uint8_t GBLoad8(struct LR35902Core* cpu, uint16_t address) { case GB_REGION_EXTERNAL_RAM + 1: if (memory->rtcAccess) { return memory->rtcRegs[memory->activeRtcReg]; + } else if (memory->mbcRead) { + return memory->mbcRead(memory, address); } else if (memory->sramAccess) { return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; - } else if (memory->mbcType == GB_MBC7) { - return GBMBC7Read(memory, address); } else if (memory->mbcType == GB_HuC3) { return 0x01; // TODO: Is this supposed to be the current SRAM bank? } @@ -274,7 +275,7 @@ void GBStore8(struct LR35902Core* cpu, uint16_t address, int8_t value) { case GB_REGION_CART_BANK1 + 1: case GB_REGION_CART_BANK1 + 2: case GB_REGION_CART_BANK1 + 3: - memory->mbc(gb, address, value); + memory->mbcWrite(gb, address, value); cpu->memory.setActiveRegion(cpu, cpu->pc); return; case GB_REGION_VRAM: @@ -391,8 +392,8 @@ uint8_t GBView8(struct LR35902Core* cpu, uint16_t address, int segment) { } else { return 0xFF; } - } else if (memory->mbcType == GB_MBC7) { - return GBMBC7Read(memory, address); + } else if (memory->mbcRead) { + return memory->mbcRead(memory, address); } else if (memory->mbcType == GB_HuC3) { return 0x01; // TODO: Is this supposed to be the current SRAM bank? } @@ -436,6 +437,7 @@ void GBMemoryDMA(struct GB* gb, uint16_t base) { if (base > 0xF100) { return; } + mTimingDeschedule(&gb->timing, &gb->memory.dmaEvent); mTimingSchedule(&gb->timing, &gb->memory.dmaEvent, 8); if (gb->cpu->cycles + 8 < gb->cpu->nextEvent) { gb->cpu->nextEvent = gb->cpu->cycles + 8; diff --git a/src/gb/overrides.c b/src/gb/overrides.c index 70d11cd22..d8265800b 100644 --- a/src/gb/overrides.c +++ b/src/gb/overrides.c @@ -13,7 +13,7 @@ static const struct GBCartridgeOverride _overrides[] = { // None yet - { 0, 0, 0 } + { 0, 0, 0, { 0 } } }; bool GBOverrideFind(const struct Configuration* config, struct GBCartridgeOverride* override) { @@ -35,6 +35,12 @@ bool GBOverrideFind(const struct Configuration* config, struct GBCartridgeOverri snprintf(sectionName, sizeof(sectionName), "gb.override.%08X", override->headerCrc32); const char* model = ConfigurationGetValue(config, sectionName, "model"); const char* mbc = ConfigurationGetValue(config, sectionName, "mbc"); + const char* pal[4] = { + ConfigurationGetValue(config, sectionName, "pal[0]"), + ConfigurationGetValue(config, sectionName, "pal[1]"), + ConfigurationGetValue(config, sectionName, "pal[2]"), + ConfigurationGetValue(config, sectionName, "pal[3]") + }; if (model) { if (strcasecmp(model, "DMG") == 0) { @@ -63,6 +69,21 @@ bool GBOverrideFind(const struct Configuration* config, struct GBCartridgeOverri found = true; } } + + if (pal[0] && pal[1] && pal[2] && pal[3]) { + int i; + for (i = 0; i < 4; ++i) { + char* end; + unsigned long value = strtoul(pal[i], &end, 10); + if (end == &pal[i][1] && *end == 'x') { + value = strtoul(pal[i], &end, 16); + } + if (*end) { + continue; + } + override->gbColors[i] = value; + } + } } return found; } @@ -89,6 +110,12 @@ void GBOverrideSave(struct Configuration* config, const struct GBCartridgeOverri } ConfigurationSetValue(config, sectionName, "model", model); + if (override->gbColors[0] | override->gbColors[1] | override->gbColors[2] | override->gbColors[3]) { + ConfigurationSetIntValue(config, sectionName, "pal[0]", override->gbColors[0]); + ConfigurationSetIntValue(config, sectionName, "pal[1]", override->gbColors[1]); + ConfigurationSetIntValue(config, sectionName, "pal[2]", override->gbColors[2]); + ConfigurationSetIntValue(config, sectionName, "pal[3]", override->gbColors[3]); + } if (override->mbc != GB_MBC_AUTODETECT) { ConfigurationSetIntValue(config, sectionName, "mbc", override->mbc); } else { @@ -105,6 +132,13 @@ void GBOverrideApply(struct GB* gb, const struct GBCartridgeOverride* override) gb->memory.mbcType = override->mbc; GBMBCInit(gb); } + + if (override->gbColors[0] | override->gbColors[1] | override->gbColors[2] | override->gbColors[3]) { + GBVideoSetPalette(&gb->video, 0, override->gbColors[0]); + GBVideoSetPalette(&gb->video, 1, override->gbColors[1]); + GBVideoSetPalette(&gb->video, 2, override->gbColors[2]); + GBVideoSetPalette(&gb->video, 3, override->gbColors[3]); + } } void GBOverrideApplyDefaults(struct GB* gb) { diff --git a/src/gb/renderers/software.c b/src/gb/renderers/software.c index 1474a86a5..2d2dbe787 100644 --- a/src/gb/renderers/software.c +++ b/src/gb/renderers/software.c @@ -67,8 +67,6 @@ static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer, enum G softwareRenderer->currentWy = 0; softwareRenderer->wx = 0; softwareRenderer->model = model; - - _clearScreen(softwareRenderer); } static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer) { diff --git a/src/gb/serialize.c b/src/gb/serialize.c index 1cacc4224..d9f192092 100644 --- a/src/gb/serialize.c +++ b/src/gb/serialize.c @@ -59,25 +59,6 @@ void GBSerialize(struct GB* gb, struct GBSerializedState* state) { GBVideoSerialize(&gb->video, state); GBTimerSerialize(&gb->timer, state); GBAudioSerialize(&gb->audio, state); - -#ifndef _MSC_VER - struct timeval tv; - if (!gettimeofday(&tv, 0)) { - uint64_t usec = tv.tv_usec; - usec += tv.tv_sec * 1000000LL; - STORE_64LE(usec, 0, &state->creationUsec); - } -#else - struct timespec ts; - if (timespec_get(&ts, TIME_UTC)) { - uint64_t usec = ts.tv_nsec / 1000; - usec += ts.tv_sec * 1000000LL; - STORE_64LE(usec, 0, &state->creationUsec); - } -#endif - else { - state->creationUsec = 0; - } } bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) { @@ -188,8 +169,8 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) { } GBMemoryDeserialize(gb, state); - GBIODeserialize(gb, state); GBVideoDeserialize(&gb->video, state); + GBIODeserialize(gb, state); GBTimerDeserialize(&gb->timer, state); GBAudioDeserialize(&gb->audio, state); diff --git a/src/gb/timer.c b/src/gb/timer.c index e5bda7113..3729e313f 100644 --- a/src/gb/timer.c +++ b/src/gb/timer.c @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include #include #include #include @@ -18,22 +19,26 @@ void _GBTimerIRQ(struct mTiming* timing, void* context, uint32_t cyclesLate) { GBUpdateIRQs(timer->p); } -void _GBTimerIncrement(struct mTiming* timing, void* context, uint32_t cyclesLate) { - struct GBTimer* timer = context; - timer->nextDiv += cyclesLate; - while (timer->nextDiv > 0) { +static void _GBTimerDivIncrement(struct GBTimer* timer, uint32_t cyclesLate) { + while (timer->nextDiv >= GB_DMG_DIV_PERIOD) { timer->nextDiv -= GB_DMG_DIV_PERIOD; // Make sure to trigger when the correct bit is a falling edge if (timer->timaPeriod > 0 && (timer->internalDiv & (timer->timaPeriod - 1)) == timer->timaPeriod - 1) { ++timer->p->memory.io[REG_TIMA]; if (!timer->p->memory.io[REG_TIMA]) { - mTimingSchedule(timing, &timer->irq, 4 - cyclesLate); + mTimingSchedule(&timer->p->timing, &timer->irq, 4 - cyclesLate); } } ++timer->internalDiv; timer->p->memory.io[REG_DIV] = timer->internalDiv >> 4; } +} + +void _GBTimerUpdate(struct mTiming* timing, void* context, uint32_t cyclesLate) { + struct GBTimer* timer = context; + timer->nextDiv += cyclesLate; + _GBTimerDivIncrement(timer, cyclesLate); // Batch div increments int divsToGo = 16 - (timer->internalDiv & 15); int timaToGo = INT_MAX; @@ -50,7 +55,7 @@ void _GBTimerIncrement(struct mTiming* timing, void* context, uint32_t cyclesLat void GBTimerReset(struct GBTimer* timer) { timer->event.context = timer; timer->event.name = "GB Timer"; - timer->event.callback = _GBTimerIncrement; + timer->event.callback = _GBTimerUpdate; timer->event.priority = 0x20; timer->irq.context = timer; timer->irq.name = "GB Timer IRQ"; @@ -63,11 +68,19 @@ void GBTimerReset(struct GBTimer* timer) { } void GBTimerDivReset(struct GBTimer* timer) { + timer->nextDiv -= mTimingUntil(&timer->p->timing, &timer->event); + mTimingDeschedule(&timer->p->timing, &timer->event); + _GBTimerDivIncrement(timer, (timer->p->cpu->executionState + 1) & 3); + if (timer->internalDiv & (timer->timaPeriod >> 1)) { + ++timer->p->memory.io[REG_TIMA]; + if (!timer->p->memory.io[REG_TIMA]) { + mTimingSchedule(&timer->p->timing, &timer->irq, 4 - (timer->p->cpu->executionState + 1) & 3); + } + } timer->p->memory.io[REG_DIV] = 0; timer->internalDiv = 0; timer->nextDiv = GB_DMG_DIV_PERIOD; - mTimingDeschedule(&timer->p->timing, &timer->event); - mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv); + mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv - ((timer->p->cpu->executionState + 1) & 3)); } uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) { @@ -86,6 +99,12 @@ uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) { timer->timaPeriod = 256 >> 4; break; } + + timer->nextDiv -= mTimingUntil(&timer->p->timing, &timer->event); + mTimingDeschedule(&timer->p->timing, &timer->event); + _GBTimerDivIncrement(timer, (timer->p->cpu->executionState + 1) & 3); + timer->nextDiv += GB_DMG_DIV_PERIOD; + mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv); } else { timer->timaPeriod = 0; } diff --git a/src/gb/video.c b/src/gb/video.c index 8794a953c..b495a96b1 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -185,9 +185,6 @@ void _endMode1(struct mTiming* timing, void* context, uint32_t cyclesLate) { next = GB_VIDEO_MODE_2_LENGTH + (video->p->memory.io[REG_SCX] & 7); video->mode = 2; video->modeEvent.callback = _endMode2; - if (video->p->memory.mbcType == GB_MBC7 && video->p->memory.rotation && video->p->memory.rotation->sample) { - video->p->memory.rotation->sample(video->p->memory.rotation); - } } else if (video->ly == GB_VIDEO_VERTICAL_TOTAL_PIXELS) { video->p->memory.io[REG_LY] = 0; next = GB_VIDEO_HORIZONTAL_LENGTH - 8; @@ -471,11 +468,11 @@ void GBVideoSwitchBank(struct GBVideo* video, uint8_t value) { video->vramCurrentBank = value; } -void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint16_t color) { +void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint32_t color) { if (index >= 4) { return; } - video->dmgPalette[index] = color; + video->dmgPalette[index] = M_RGB8_TO_RGB5(color); } static void GBVideoDummyRendererInit(struct GBVideoRenderer* renderer, enum GBModel model) { @@ -628,4 +625,7 @@ void GBVideoDeserialize(struct GBVideo* video, const struct GBSerializedState* s _cleanOAM(video, video->ly); GBVideoSwitchBank(video, video->vramCurrentBank); + + video->renderer->deinit(video->renderer); + video->renderer->init(video->renderer, video->p->model); } diff --git a/src/gba/bios.c b/src/gba/bios.c index 199ffab8f..ae3b23ed2 100644 --- a/src/gba/bios.c +++ b/src/gba/bios.c @@ -256,18 +256,23 @@ static void _MidiKey2Freq(struct GBA* gba) { static void _Div(struct GBA* gba, int32_t num, int32_t denom) { struct ARMCore* cpu = gba->cpu; - if (denom != 0) { + if (denom != 0 && (denom != -1 || num != INT32_MIN)) { div_t result = div(num, denom); cpu->gprs[0] = result.quot; cpu->gprs[1] = result.rem; cpu->gprs[3] = abs(result.quot); - } else { + } else if (denom == 0) { mLOG(GBA_BIOS, GAME_ERROR, "Attempting to divide %i by zero!", num); // If abs(num) > 1, this should hang, but that would be painful to // emulate in HLE, and no game will get into a state where it hangs... cpu->gprs[0] = (num < 0) ? -1 : 1; cpu->gprs[1] = num; cpu->gprs[3] = 1; + } else { + mLOG(GBA_BIOS, GAME_ERROR, "Attempting to divide INT_MIN by -1!"); + cpu->gprs[0] = INT32_MIN; + cpu->gprs[1] = 0; + cpu->gprs[3] = INT32_MIN; } } diff --git a/src/gba/core.c b/src/gba/core.c index 2064ee2d0..4c212598f 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -45,6 +45,80 @@ const static struct mCoreChannelInfo _GBAAudioChannels[] = { { 5, "chB", "FIFO Channel B", NULL }, }; +const static struct mCoreMemoryBlock _GBAMemoryBlocks[] = { + { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL }, + { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_IRAM, "iwram", "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, BASE_WORKING_IRAM + SIZE_WORKING_IRAM, SIZE_WORKING_IRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_IO, "io", "MMIO", "Memory-Mapped I/O", BASE_IO, BASE_IO + SIZE_IO, SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_PALETTE_RAM, "palette", "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, BASE_PALETTE_RAM + SIZE_PALETTE_RAM, SIZE_PALETTE_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_VRAM, "vram", "VRAM", "Video RAM (96kiB)", BASE_VRAM, BASE_VRAM + SIZE_VRAM, SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_OAM, "oam", "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, BASE_OAM + SIZE_OAM, SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_CART0, "cart0", "ROM", "Game Pak (32MiB)", BASE_CART0, BASE_CART0 + SIZE_CART0, SIZE_CART0, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART1, "cart1", "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, BASE_CART1 + SIZE_CART1, SIZE_CART1, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, +}; + +const static struct mCoreMemoryBlock _GBAMemoryBlocksSRAM[] = { + { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL }, + { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_IRAM, "iwram", "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, BASE_WORKING_IRAM + SIZE_WORKING_IRAM, SIZE_WORKING_IRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_IO, "io", "MMIO", "Memory-Mapped I/O", BASE_IO, BASE_IO + SIZE_IO, SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_PALETTE_RAM, "palette", "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, BASE_PALETTE_RAM + SIZE_PALETTE_RAM, SIZE_PALETTE_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_VRAM, "vram", "VRAM", "Video RAM (96kiB)", BASE_VRAM, BASE_VRAM + SIZE_VRAM, SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_OAM, "oam", "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, BASE_OAM + SIZE_OAM, SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_CART0, "cart0", "ROM", "Game Pak (32MiB)", BASE_CART0, BASE_CART0 + SIZE_CART0, SIZE_CART0, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART1, "cart1", "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, BASE_CART1 + SIZE_CART1, SIZE_CART1, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART_SRAM, "sram", "SRAM", "Static RAM (64kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_SRAM, SIZE_CART_SRAM, true }, +}; + +const static struct mCoreMemoryBlock _GBAMemoryBlocksFlash512[] = { + { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL }, + { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_IRAM, "iwram", "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, BASE_WORKING_IRAM + SIZE_WORKING_IRAM, SIZE_WORKING_IRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_IO, "io", "MMIO", "Memory-Mapped I/O", BASE_IO, BASE_IO + SIZE_IO, SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_PALETTE_RAM, "palette", "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, BASE_PALETTE_RAM + SIZE_PALETTE_RAM, SIZE_PALETTE_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_VRAM, "vram", "VRAM", "Video RAM (96kiB)", BASE_VRAM, BASE_VRAM + SIZE_VRAM, SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_OAM, "oam", "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, BASE_OAM + SIZE_OAM, SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_CART0, "cart0", "ROM", "Game Pak (32MiB)", BASE_CART0, BASE_CART0 + SIZE_CART0, SIZE_CART0, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART1, "cart1", "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, BASE_CART1 + SIZE_CART1, SIZE_CART1, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART_SRAM, "sram", "Flash", "Flash Memory (64kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_FLASH512, SIZE_CART_FLASH512, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, +}; + +const static struct mCoreMemoryBlock _GBAMemoryBlocksFlash1M[] = { + { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL }, + { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_IRAM, "iwram", "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, BASE_WORKING_IRAM + SIZE_WORKING_IRAM, SIZE_WORKING_IRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_IO, "io", "MMIO", "Memory-Mapped I/O", BASE_IO, BASE_IO + SIZE_IO, SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_PALETTE_RAM, "palette", "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, BASE_PALETTE_RAM + SIZE_PALETTE_RAM, SIZE_PALETTE_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_VRAM, "vram", "VRAM", "Video RAM (96kiB)", BASE_VRAM, BASE_VRAM + SIZE_VRAM, SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_OAM, "oam", "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, BASE_OAM + SIZE_OAM, SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_CART0, "cart0", "ROM", "Game Pak (32MiB)", BASE_CART0, BASE_CART0 + SIZE_CART0, SIZE_CART0, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART1, "cart1", "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, BASE_CART1 + SIZE_CART1, SIZE_CART1, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART_SRAM, "sram", "Flash", "Flash Memory (64kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_FLASH512, SIZE_CART_FLASH1M, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 1 }, +}; + +const static struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = { + { -1, "mem", "All", "All", 0, 0x10000000, 0x10000000, mCORE_MEMORY_VIRTUAL }, + { REGION_BIOS, "bios", "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS, SIZE_BIOS, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_RAM, "wram", "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, BASE_WORKING_RAM + SIZE_WORKING_RAM, SIZE_WORKING_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_WORKING_IRAM, "iwram", "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, BASE_WORKING_IRAM + SIZE_WORKING_IRAM, SIZE_WORKING_IRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_IO, "io", "MMIO", "Memory-Mapped I/O", BASE_IO, BASE_IO + SIZE_IO, SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_PALETTE_RAM, "palette", "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, BASE_PALETTE_RAM + SIZE_PALETTE_RAM, SIZE_PALETTE_RAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_VRAM, "vram", "VRAM", "Video RAM (96kiB)", BASE_VRAM, BASE_VRAM + SIZE_VRAM, SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_OAM, "oam", "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, BASE_OAM + SIZE_OAM, SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, + { REGION_CART0, "cart0", "ROM", "Game Pak (32MiB)", BASE_CART0, BASE_CART0 + SIZE_CART0, SIZE_CART0, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART1, "cart1", "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, BASE_CART1 + SIZE_CART1, SIZE_CART1, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED }, + { REGION_CART_SRAM_MIRROR, "eeprom", "EEPROM", "EEPROM (8kiB)", 0, SIZE_CART_EEPROM, SIZE_CART_EEPROM, mCORE_MEMORY_RW }, +}; + struct mVideoLogContext; struct GBACore { struct mCore d; @@ -528,6 +602,67 @@ static void _GBACoreRawWrite32(struct mCore* core, uint32_t address, int segment GBAPatch32(cpu, address, value, NULL); } +size_t _GBAListMemoryBlocks(const struct mCore* core, const struct mCoreMemoryBlock** blocks) { + const struct GBA* gba = core->board; + switch (gba->memory.savedata.type) { + case SAVEDATA_SRAM: + *blocks = _GBAMemoryBlocksSRAM; + return sizeof(_GBAMemoryBlocksSRAM) / sizeof(*_GBAMemoryBlocksSRAM); + case SAVEDATA_FLASH512: + *blocks = _GBAMemoryBlocksFlash512; + return sizeof(_GBAMemoryBlocksFlash512) / sizeof(*_GBAMemoryBlocksFlash512); + case SAVEDATA_FLASH1M: + *blocks = _GBAMemoryBlocksFlash1M; + return sizeof(_GBAMemoryBlocksFlash1M) / sizeof(*_GBAMemoryBlocksFlash1M); + case SAVEDATA_EEPROM: + *blocks = _GBAMemoryBlocksEEPROM; + return sizeof(_GBAMemoryBlocksEEPROM) / sizeof(*_GBAMemoryBlocksEEPROM); + default: + *blocks = _GBAMemoryBlocks; + return sizeof(_GBAMemoryBlocks) / sizeof(*_GBAMemoryBlocks); + } +} + +void* _GBAGetMemoryBlock(struct mCore* core, size_t id, size_t* sizeOut) { + struct GBA* gba = core->board; + switch (id) { + default: + return NULL; + case REGION_BIOS: + *sizeOut = SIZE_BIOS; + return gba->memory.bios; + case REGION_WORKING_RAM: + *sizeOut = SIZE_WORKING_RAM; + return gba->memory.wram; + case REGION_WORKING_IRAM: + *sizeOut = SIZE_WORKING_IRAM; + return gba->memory.iwram; + case REGION_PALETTE_RAM: + *sizeOut = SIZE_PALETTE_RAM; + return gba->video.palette; + case REGION_VRAM: + *sizeOut = SIZE_VRAM; + return gba->video.vram; + case REGION_OAM: + *sizeOut = SIZE_OAM; + return gba->video.oam.raw; + case REGION_CART0: + case REGION_CART1: + case REGION_CART2: + *sizeOut = gba->memory.romSize; + return gba->memory.rom; + case REGION_CART_SRAM: + if (gba->memory.savedata.type == SAVEDATA_FLASH1M) { + *sizeOut = SIZE_CART_FLASH1M; + return gba->memory.savedata.currentBank; + } + // Fall through + case REGION_CART_SRAM_MIRROR: + *sizeOut = GBASavedataSize(&gba->memory.savedata); + return gba->memory.savedata.data; + } +} + #ifdef USE_DEBUGGERS static bool _GBACoreSupportsDebuggerType(struct mCore* core, enum mDebuggerType type) { UNUSED(core); @@ -757,6 +892,8 @@ struct mCore* GBACoreCreate(void) { core->rawWrite8 = _GBACoreRawWrite8; core->rawWrite16 = _GBACoreRawWrite16; core->rawWrite32 = _GBACoreRawWrite32; + core->listMemoryBlocks = _GBAListMemoryBlocks; + core->getMemoryBlock = _GBAGetMemoryBlock; #ifdef USE_DEBUGGERS core->supportsDebuggerType = _GBACoreSupportsDebuggerType; core->debuggerPlatform = _GBACoreDebuggerPlatform; diff --git a/src/gba/debugger/cli.c b/src/gba/debugger/cli.c index 44ae46917..4208d9021 100644 --- a/src/gba/debugger/cli.c +++ b/src/gba/debugger/cli.c @@ -119,5 +119,5 @@ static void _save(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system; - mCoreSaveState(gbaDebugger->core, dv->intValue, SAVESTATE_SCREENSHOT | SAVESTATE_RTC); + mCoreSaveState(gbaDebugger->core, dv->intValue, SAVESTATE_SCREENSHOT | SAVESTATE_RTC | SAVESTATE_METADATA); } diff --git a/src/gba/memory.c b/src/gba/memory.c index db4a30122..be35f4104 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -59,12 +59,8 @@ void GBAMemoryInit(struct GBA* gba) { for (i = 0; i < 16; ++i) { gba->memory.waitstatesNonseq16[i] = GBA_BASE_WAITSTATES[i]; gba->memory.waitstatesSeq16[i] = GBA_BASE_WAITSTATES_SEQ[i]; - gba->memory.waitstatesPrefetchNonseq16[i] = GBA_BASE_WAITSTATES[i]; - gba->memory.waitstatesPrefetchSeq16[i] = GBA_BASE_WAITSTATES_SEQ[i]; gba->memory.waitstatesNonseq32[i] = GBA_BASE_WAITSTATES_32[i]; gba->memory.waitstatesSeq32[i] = GBA_BASE_WAITSTATES_SEQ_32[i]; - gba->memory.waitstatesPrefetchNonseq32[i] = GBA_BASE_WAITSTATES_32[i]; - gba->memory.waitstatesPrefetchSeq32[i] = GBA_BASE_WAITSTATES_SEQ_32[i]; } for (; i < 256; ++i) { gba->memory.waitstatesNonseq16[i] = 0; @@ -1505,33 +1501,32 @@ int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait) { previousLoads = dist; } - int32_t s = cpu->memory.activeSeqCycles16 + 1; - int32_t n2s = cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16 + 1; + int32_t s = cpu->memory.activeSeqCycles16; + int32_t n2s = cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16; // Figure out how many sequential loads we can jam in int32_t stall = s; int32_t loads = 1; - if (stall > wait && !previousLoads) { - // We might need to stall a bit extra if we haven't finished the first S cycle - wait = stall; - } else { - while (stall < wait) { + if (stall < wait) { + int32_t maxLoads = 8 - previousLoads; + while (stall < wait && loads < maxLoads) { stall += s; ++loads; } - if (loads + previousLoads > 8) { - loads = 8 - previousLoads; - } } + if (stall > wait) { + // The wait cannot take less time than the prefetch stalls + wait = stall; + } + // This instruction used to have an N, convert it to an S. wait -= n2s; - // TODO: Invalidate prefetch on branch - memory->lastPrefetchedPc = cpu->gprs[ARM_PC] + WORD_SIZE_THUMB * loads; + memory->lastPrefetchedPc = cpu->gprs[ARM_PC] + WORD_SIZE_THUMB * (loads + previousLoads - 1); // The next |loads|S waitstates disappear entirely, so long as they're all in a row - cpu->cycles -= (s - 1) * loads; + cpu->cycles -= stall; return wait; } diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 663fcc44c..5917fa5f1 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -69,25 +69,6 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) { GBAAudioSerialize(&gba->audio, state); GBASavedataSerialize(&gba->memory.savedata, state); - -#ifndef _MSC_VER - struct timeval tv; - if (!gettimeofday(&tv, 0)) { - uint64_t usec = tv.tv_usec; - usec += tv.tv_sec * 1000000LL; - STORE_64(usec, 0, &state->creationUsec); - } -#else - struct timespec ts; - if (timespec_get(&ts, TIME_UTC)) { - uint64_t usec = ts.tv_nsec / 1000; - usec += ts.tv_sec * 1000000LL; - STORE_64(usec, 0, &state->creationUsec); - } -#endif - else { - state->creationUsec = 0; - } state->associatedStreamId = 0; if (gba->rr) { gba->rr->stateSaved(gba->rr, state); diff --git a/src/gba/sharkport.c b/src/gba/sharkport.c index 4ada2360c..d9ca483c9 100644 --- a/src/gba/sharkport.c +++ b/src/gba/sharkport.c @@ -115,24 +115,14 @@ bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChec uint32_t copySize = size - 0x1C; switch (gba->memory.savedata.type) { - case SAVEDATA_SRAM: - if (copySize > SIZE_CART_SRAM) { - copySize = SIZE_CART_SRAM; - } - break; case SAVEDATA_FLASH512: if (copySize > SIZE_CART_FLASH512) { GBASavedataForceType(&gba->memory.savedata, SAVEDATA_FLASH1M, gba->memory.savedata.realisticTiming); } // Fall through - case SAVEDATA_FLASH1M: - if (copySize > SIZE_CART_FLASH1M) { - copySize = SIZE_CART_FLASH1M; - } - break; - case SAVEDATA_EEPROM: - if (copySize > SIZE_CART_EEPROM) { - copySize = SAVEDATA_EEPROM; + default: + if (copySize > GBASavedataSize(&gba->memory.savedata)) { + copySize = GBASavedataSize(&gba->memory.savedata); } break; case SAVEDATA_FORCE_NONE: @@ -141,6 +131,7 @@ bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChec } memcpy(gba->memory.savedata.data, &payload[0x1C], copySize); + gba->memory.savedata.vf && gba->memory.savedata.vf->sync(gba->memory.savedata.vf, gba->memory.savedata.data, size); free(payload); return true; diff --git a/src/gba/timer.c b/src/gba/timer.c index c62f64506..03af4fe34 100644 --- a/src/gba/timer.c +++ b/src/gba/timer.c @@ -83,13 +83,13 @@ void GBATimerInit(struct GBA* gba) { void GBATimerUpdateRegister(struct GBA* gba, int timer) { struct GBATimer* currentTimer = &gba->timers[timer]; if (GBATimerFlagsIsEnable(currentTimer->flags) && !GBATimerFlagsIsCountUp(currentTimer->flags)) { - int32_t prefetchSkew = 0; - if (gba->memory.lastPrefetchedPc >= (uint32_t) gba->cpu->gprs[ARM_PC]) { - prefetchSkew = (gba->memory.lastPrefetchedPc - gba->cpu->gprs[ARM_PC]) * (gba->cpu->memory.activeSeqCycles16 + 1) / WORD_SIZE_THUMB; + int32_t prefetchSkew = -2; + if (gba->memory.lastPrefetchedPc > (uint32_t) gba->cpu->gprs[ARM_PC]) { + prefetchSkew += ((gba->memory.lastPrefetchedPc - gba->cpu->gprs[ARM_PC]) * gba->cpu->memory.activeSeqCycles16) / WORD_SIZE_THUMB; } // Reading this takes two cycles (1N+1I), so let's remove them preemptively int32_t diff = gba->cpu->cycles - (currentTimer->lastEvent - gba->timing.masterCycles); - gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((diff - 2 + prefetchSkew) >> GBATimerFlagsGetPrescaleBits(currentTimer->flags)); + gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((diff + prefetchSkew) >> GBATimerFlagsGetPrescaleBits(currentTimer->flags)); } } diff --git a/src/lr35902/debugger/debugger.c b/src/lr35902/debugger/debugger.c index 5d3f3a6ce..ae6345b79 100644 --- a/src/lr35902/debugger/debugger.c +++ b/src/lr35902/debugger/debugger.c @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -48,6 +49,7 @@ static void LR35902DebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t add static void LR35902DebuggerClearWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment); static void LR35902DebuggerCheckBreakpoints(struct mDebuggerPlatform*); static bool LR35902DebuggerHasBreakpoints(struct mDebuggerPlatform*); +static void LR35902DebuggerTrace(struct mDebuggerPlatform*, char* out, size_t* length); struct mDebuggerPlatform* LR35902DebuggerPlatformCreate(void) { struct mDebuggerPlatform* platform = (struct mDebuggerPlatform*) malloc(sizeof(struct LR35902Debugger)); @@ -60,6 +62,7 @@ struct mDebuggerPlatform* LR35902DebuggerPlatformCreate(void) { platform->clearWatchpoint = LR35902DebuggerClearWatchpoint; platform->checkBreakpoints = LR35902DebuggerCheckBreakpoints; platform->hasBreakpoints = LR35902DebuggerHasBreakpoints; + platform->trace = LR35902DebuggerTrace; return platform; } @@ -137,3 +140,31 @@ static void LR35902DebuggerClearWatchpoint(struct mDebuggerPlatform* d, uint32_t LR35902DebuggerRemoveMemoryShim(debugger); } } + +static void LR35902DebuggerTrace(struct mDebuggerPlatform* d, char* out, size_t* length) { + struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; + struct LR35902Core* cpu = debugger->cpu; + + char disassembly[64]; + + struct LR35902InstructionInfo info = {{0}}; + char* disPtr = disassembly; + uint8_t instruction; + uint16_t address = cpu->pc; + size_t bytesRemaining = 1; + for (bytesRemaining = 1; bytesRemaining; --bytesRemaining) { + instruction = debugger->d.p->core->rawRead8(debugger->d.p->core, address, -1); + disPtr += snprintf(disPtr, sizeof(disassembly) - (disPtr - disassembly), "%02X", instruction); + ++address; + bytesRemaining += LR35902Decode(instruction, &info); + }; + disPtr[0] = ':'; + disPtr[1] = ' '; + disPtr += 2; + LR35902Disassemble(&info, disPtr, sizeof(disassembly) - (disPtr - disassembly)); + + *length = snprintf(out, *length, "A: %02X F: %02X B: %02X C: %02X D: %02X E: %02X H: %02X L: %02X SP: %04X PC: %04X | %s", + cpu->a, cpu->f.packed, cpu->b, cpu->c, + cpu->d, cpu->e, cpu->h, cpu->l, + cpu->sp, cpu->pc, disassembly); +} diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 47842273e..2dfa7448b 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -175,7 +175,7 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h) drawW -= drawW % v->width; drawH -= drawH % v->height; } - glViewport(0, 0, v->width, v->height); + glViewport(0, 0, w, h); glClearColor(0.f, 0.f, 0.f, 1.f); glClear(GL_COLOR_BUFFER_BIT); glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH); @@ -203,13 +203,13 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { int drawH = shader->height; int padW = 0; int padH = 0; - if (!shader->width) { + if (!drawW) { drawW = viewport[2]; padW = viewport[0]; } else if (shader->width < 0) { drawW = context->d.width * -shader->width; } - if (!shader->height) { + if (!drawH) { drawH = viewport[3]; padH = viewport[1]; } else if (shader->height < 0) { @@ -234,7 +234,7 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glUseProgram(shader->program); glUniform1i(shader->texLocation, 0); - glUniform2f(shader->texSizeLocation, context->d.width, context->d.height); + glUniform2f(shader->texSizeLocation, context->d.width - padW, context->d.height - padH); glVertexAttribPointer(shader->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, _vertices); glEnableVertexAttribArray(shader->positionLocation); size_t u; @@ -290,7 +290,6 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { } glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindTexture(GL_TEXTURE_2D, shader->tex); - glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); } void mGLES2ContextDrawFrame(struct VideoBackend* v) { @@ -298,12 +297,17 @@ void mGLES2ContextDrawFrame(struct VideoBackend* 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); size_t n; for (n = 0; n < context->nShaders; ++n) { + glViewport(0, 0, viewport[2], viewport[3]); _drawShader(context, &context->shaders[n]); } + glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); _drawShader(context, &context->finalShader); glBindFramebuffer(GL_FRAMEBUFFER, 0); glUseProgram(0); diff --git a/src/platform/python/CMakeLists.txt b/src/platform/python/CMakeLists.txt index 7b7605e7d..ad0d7536a 100644 --- a/src/platform/python/CMakeLists.txt +++ b/src/platform/python/CMakeLists.txt @@ -1,12 +1,12 @@ find_program(PYTHON python) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) - get_property(INCLUDE_DIRECTORIES DIRECTORY PROPERTY INCLUDE_DIRECTORIES) set(INCLUDE_FLAGS) foreach(DIR IN LISTS INCLUDE_DIRECTORIES) list(APPEND INCLUDE_FLAGS "-I${DIR}") endforeach() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) add_custom_command(OUTPUT build/lib/${BINARY_NAME}/__init__.py COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON} ${CMAKE_CURRENT_BINARY_DIR}/setup.py build --build-base ${CMAKE_CURRENT_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/src/platform/python/_builder.h b/src/platform/python/_builder.h index 8820a8c08..7d7703bb5 100644 --- a/src/platform/python/_builder.h +++ b/src/platform/python/_builder.h @@ -28,7 +28,9 @@ void free(void*); #include "flags.h" #include +#include #include +#include #define PYEXPORT extern "Python+C" #include "platform/python/log.h" diff --git a/src/platform/python/_builder.py b/src/platform/python/_builder.py index ccf35a880..f93766b22 100644 --- a/src/platform/python/_builder.py +++ b/src/platform/python/_builder.py @@ -21,7 +21,9 @@ ffi.set_source("mgba._pylib", """ #include #include #include +#include #include +#include #include #include #include diff --git a/src/platform/python/mgba/__init__.py b/src/platform/python/mgba/__init__.py index aab19b4d2..a8c0ddce4 100644 --- a/src/platform/python/mgba/__init__.py +++ b/src/platform/python/mgba/__init__.py @@ -5,6 +5,8 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. from ._pylib import ffi, lib +from collections import namedtuple + def createCallback(structName, cbName, funcName=None): funcName = funcName or "_py{}{}".format(structName, cbName[0].upper() + cbName[1:]) fullStruct = "struct {}*".format(structName) @@ -13,3 +15,19 @@ def createCallback(structName, cbName, funcName=None): return getattr(ffi.from_handle(h.pyobj), cbName)(*args) return ffi.def_extern(name=funcName)(cb) + +version = ffi.string(lib.projectVersion).decode('utf-8') + +GitInfo = namedtuple("GitInfo", "commit commitShort branch revision") + +git = {} +if lib.gitCommit and lib.gitCommit != "(unknown)": + git['commit'] = ffi.string(lib.gitCommit).decode('utf-8') +if lib.gitCommitShort and lib.gitCommitShort != "(unknown)": + git['commitShort'] = ffi.string(lib.gitCommitShort).decode('utf-8') +if lib.gitBranch and lib.gitBranch != "(unknown)": + git['branch'] = ffi.string(lib.gitBranch).decode('utf-8') +if lib.gitRevision > 0: + git['revision'] = lib.gitRevision + +git = GitInfo(**git) diff --git a/src/platform/python/mgba/image.py b/src/platform/python/mgba/image.py index f76945b32..948edeeb4 100644 --- a/src/platform/python/mgba/image.py +++ b/src/platform/python/mgba/image.py @@ -6,64 +6,74 @@ from ._pylib import ffi, lib from . import png +try: + import PIL.Image as PImage +except ImportError: + pass + class Image: - def __init__(self, width, height, stride=0): - self.width = width - self.height = height - self.stride = stride - self.constitute() + def __init__(self, width, height, stride=0): + self.width = width + self.height = height + self.stride = stride + self.constitute() - def constitute(self): - if self.stride <= 0: - self.stride = self.width - self.buffer = ffi.new("color_t[{}]".format(self.stride * self.height)) + def constitute(self): + if self.stride <= 0: + self.stride = self.width + self.buffer = ffi.new("color_t[{}]".format(self.stride * self.height)) - def savePNG(self, f): - p = png.PNG(f) - success = p.writeHeader(self) - success = success and p.writePixels(self) - p.writeClose() - return success + def savePNG(self, f): + p = png.PNG(f) + success = p.writeHeader(self) + success = success and p.writePixels(self) + p.writeClose() + return success + + if 'PImage' in globals(): + def toPIL(self): + return PImage.frombytes("RGBX", (self.width, self.height), ffi.buffer(self.buffer), "raw", + "RGBX", self.stride * 4) def u16ToU32(c): - r = c & 0x1F - g = (c >> 5) & 0x1F - b = (c >> 10) & 0x1F - a = (c >> 15) & 1 - abgr = r << 3 - abgr |= g << 11 - abgr |= b << 19 - abgr |= (a * 0xFF) << 24 - return abgr + r = c & 0x1F + g = (c >> 5) & 0x1F + b = (c >> 10) & 0x1F + a = (c >> 15) & 1 + abgr = r << 3 + abgr |= g << 11 + abgr |= b << 19 + abgr |= (a * 0xFF) << 24 + return abgr def u32ToU16(c): - r = (c >> 3) & 0x1F - g = (c >> 11) & 0x1F - b = (c >> 19) & 0x1F - a = c >> 31 - abgr = r - abgr |= g << 5 - abgr |= b << 10 - abgr |= a << 15 - return abgr + r = (c >> 3) & 0x1F + g = (c >> 11) & 0x1F + b = (c >> 19) & 0x1F + a = c >> 31 + abgr = r + abgr |= g << 5 + abgr |= b << 10 + abgr |= a << 15 + return abgr if ffi.sizeof("color_t") == 2: - def colorToU16(c): - return c + def colorToU16(c): + return c - colorToU32 = u16ToU32 + colorToU32 = u16ToU32 - def u16ToColor(c): - return c + def u16ToColor(c): + return c - u32ToColor = u32ToU16 + u32ToColor = u32ToU16 else: - def colorToU32(c): - return c + def colorToU32(c): + return c - colorToU16 = u32ToU16 + colorToU16 = u32ToU16 - def u32ToColor(c): - return c + def u32ToColor(c): + return c - u16ToColor = u16ToU32 + u16ToColor = u16ToU32 diff --git a/src/platform/python/mgba/log.py b/src/platform/python/mgba/log.py index 4514e67a9..168627a0e 100644 --- a/src/platform/python/mgba/log.py +++ b/src/platform/python/mgba/log.py @@ -8,29 +8,33 @@ from . import createCallback createCallback("mLoggerPy", "log", "_pyLog") +defaultLogger = None + def installDefault(logger): - lib.mLogSetDefaultLogger(logger._native) + global defaultLogger + defaultLogger = logger + lib.mLogSetDefaultLogger(logger._native) class Logger(object): - FATAL = lib.mLOG_FATAL - DEBUG = lib.mLOG_DEBUG - INFO = lib.mLOG_INFO - WARN = lib.mLOG_WARN - ERROR = lib.mLOG_ERROR - STUB = lib.mLOG_STUB - GAME_ERROR = lib.mLOG_GAME_ERROR + FATAL = lib.mLOG_FATAL + DEBUG = lib.mLOG_DEBUG + INFO = lib.mLOG_INFO + WARN = lib.mLOG_WARN + ERROR = lib.mLOG_ERROR + STUB = lib.mLOG_STUB + GAME_ERROR = lib.mLOG_GAME_ERROR - def __init__(self): - self._handle = ffi.new_handle(self) - self._native = ffi.gc(lib.mLoggerPythonCreate(self._handle), lib.free) + def __init__(self): + self._handle = ffi.new_handle(self) + self._native = ffi.gc(lib.mLoggerPythonCreate(self._handle), lib.free) - @staticmethod - def categoryName(category): - return ffi.string(lib.mLogCategoryName(category)).decode('UTF-8') + @staticmethod + def categoryName(category): + return ffi.string(lib.mLogCategoryName(category)).decode('UTF-8') - def log(self, category, level, message): - print("{}: {}".format(self.categoryName(category), message)) + def log(self, category, level, message): + print("{}: {}".format(self.categoryName(category), message)) class NullLogger(Logger): - def log(self, category, level, message): - pass + def log(self, category, level, message): + pass diff --git a/src/platform/python/mgba/memory.py b/src/platform/python/mgba/memory.py index a7f52e22e..c59f5a792 100644 --- a/src/platform/python/mgba/memory.py +++ b/src/platform/python/mgba/memory.py @@ -67,10 +67,53 @@ class MemoryView(object): self._addrCheck(address) self._rawWrite(self._core, self._base + address, segment, value & self._mask) + +class MemorySearchResult(object): + def __init__(self, memory, result): + self.address = result.address + self.segment = result.segment + self.guessDivisor = result.guessDivisor + self.type = result.type + + if result.type == Memory.SEARCH_8: + self._memory = memory.u8 + elif result.type == Memory.SEARCH_16: + self._memory = memory.u16 + elif result.type == Memory.SEARCH_32: + self._memory = memory.u32 + elif result.type == Memory.SEARCH_STRING: + self._memory = memory.u8 + else: + raise ValueError("Unknown type: %X" % result.type) + + @property + def value(self): + if self.type == Memory.SEARCH_STRING: + raise ValueError + return self._memory[self.address] * self.guessDivisor + + @value.setter + def value(self, v): + if self.type == Memory.SEARCH_STRING: + raise IndexError + self._memory[self.address] = v // self.guessDivisor + + class Memory(object): + SEARCH_32 = lib.mCORE_MEMORY_SEARCH_32 + SEARCH_16 = lib.mCORE_MEMORY_SEARCH_16 + SEARCH_8 = lib.mCORE_MEMORY_SEARCH_8 + SEARCH_STRING = lib.mCORE_MEMORY_SEARCH_STRING + SEARCH_GUESS = lib.mCORE_MEMORY_SEARCH_GUESS + + READ = lib.mCORE_MEMORY_READ + WRITE = lib.mCORE_MEMORY_READ + RW = lib.mCORE_MEMORY_RW + def __init__(self, core, size, base=0): self.size = size self.base = base + self._core = core self.u8 = MemoryView(core, 1, size, base, "u") self.u16 = MemoryView(core, 2, size, base, "u") @@ -81,3 +124,32 @@ class Memory(object): def __len__(self): return self._size + + def search(self, value, type=SEARCH_GUESS, flags=RW, limit=10000, old_results=[]): + results = ffi.new("struct mCoreMemorySearchResults*") + lib.mCoreMemorySearchResultsInit(results, len(old_results)) + params = ffi.new("struct mCoreMemorySearchParams*") + params.memoryFlags = flags + params.type = type + if type == self.SEARCH_8: + params.value8 = int(value) + elif type == self.SEARCH_16: + params.value16 = int(value) + elif type == self.SEARCH_32: + params.value32 = int(value) + else: + params.valueStr = ffi.new("char[]", str(value).encode("ascii")) + + for result in old_results: + r = lib.mCoreMemorySearchResultsAppend(results) + r.address = result.address + r.segment = result.segment + r.guessDivisor = result.guessDivisor + r.type = result.type + if old_results: + lib.mCoreMemorySearchRepeat(self._core, params, results) + else: + lib.mCoreMemorySearch(self._core, params, results, limit) + new_results = [MemorySearchResult(self, lib.mCoreMemorySearchResultsGetPointer(results, i)) for i in range(lib.mCoreMemorySearchResultsSize(results))] + lib.mCoreMemorySearchResultsDeinit(results) + return new_results diff --git a/src/platform/python/setup.py.in b/src/platform/python/setup.py.in index 31ce6cd52..04c1c0c29 100644 --- a/src/platform/python/setup.py.in +++ b/src/platform/python/setup.py.in @@ -1,5 +1,10 @@ from setuptools import setup import re +import os + +os.environ["BINDIR"] = "${CMAKE_BINARY_DIR}" +os.environ["CPPFLAGS"] = " ".join([d for d in "${INCLUDE_FLAGS}".split(";") if d]) +os.chdir("${CMAKE_CURRENT_SOURCE_DIR}") classifiers = [ "Programming Language :: C", @@ -18,6 +23,7 @@ setup(name="${BINARY_NAME}", packages=["mgba"], setup_requires=['cffi>=1.6'], install_requires=['cffi>=1.6', 'cached-property'], + extras_require={'pil': ['Pillow>=2.3']}, cffi_modules=["_builder.py:ffi"], license="MPL 2.0", classifiers=classifiers diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 86e1af59d..2569d5b14 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -85,6 +85,7 @@ set(SOURCE_FILES LogController.cpp LogView.cpp MemoryModel.cpp + MemorySearch.cpp MemoryView.cpp MessagePainter.cpp MultiplayerController.cpp @@ -115,6 +116,7 @@ set(UI_FILES IOViewer.ui LoadSaveState.ui LogView.ui + MemorySearch.ui MemoryView.ui ObjView.ui OverrideView.ui diff --git a/src/platform/qt/GBAKeyEditor.cpp b/src/platform/qt/GBAKeyEditor.cpp index 0b58760eb..064bd0f58 100644 --- a/src/platform/qt/GBAKeyEditor.cpp +++ b/src/platform/qt/GBAKeyEditor.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "GBAKeyEditor.h" +#include #include #include #include @@ -182,10 +183,20 @@ bool GBAKeyEditor::event(QEvent* event) { } bool GBAKeyEditor::eventFilter(QObject* obj, QEvent* event) { + KeyEditor* keyEditor = static_cast(obj); + if (event->type() == QEvent::FocusOut) { + keyEditor->setPalette(QApplication::palette(keyEditor)); + } if (event->type() != QEvent::FocusIn) { return false; } - findFocus(static_cast(obj)); + + QPalette palette = keyEditor->palette(); + palette.setBrush(keyEditor->backgroundRole(), palette.highlight()); + palette.setBrush(keyEditor->foregroundRole(), palette.highlightedText()); + keyEditor->setPalette(palette); + + findFocus(keyEditor); return true; } diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 0b48ac302..ef1b3dddb 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -40,7 +40,7 @@ using namespace std; GameController::GameController(QObject* parent) : QObject(parent) , m_audioProcessor(AudioProcessor::create()) - , m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC) + , m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC | SAVESTATE_METADATA) , m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC) { #ifdef M_CORE_GBA @@ -162,6 +162,7 @@ GameController::GameController(QObject* parent) default: break; } + mTileCacheDeinit(controller->m_tileCache.get()); controller->m_tileCache.reset(); } @@ -696,6 +697,7 @@ void GameController::threadContinue() { void GameController::frameAdvance() { if (m_pauseAfterFrame.testAndSetRelaxed(false, true)) { setPaused(false); + m_wasPaused = true; } } diff --git a/src/platform/qt/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp index 40777000f..a381c3d22 100644 --- a/src/platform/qt/LoadSaveState.cpp +++ b/src/platform/qt/LoadSaveState.cpp @@ -188,7 +188,7 @@ void LoadSaveState::loadState(int slot) { return; } - QDateTime creation/*(QDateTime::fromMSecsSinceEpoch(state->creationUsec / 1000LL))*/; // TODO + QDateTime creation; QImage stateImage; unsigned width, height; @@ -198,6 +198,12 @@ void LoadSaveState::loadState(int slot) { stateImage = QImage((uchar*) item.data, width, height, QImage::Format_ARGB32).rgbSwapped(); } + if (mStateExtdataGet(&extdata, EXTDATA_META_TIME, &item) && item.size == sizeof(uint64_t)) { + uint64_t creationUsec; + LOAD_64LE(creationUsec, 0, item.data); + creation = QDateTime::fromMSecsSinceEpoch(creationUsec / 1000LL); + } + if (!stateImage.isNull()) { QPixmap statePixmap; statePixmap.convertFromImage(stateImage); diff --git a/src/platform/qt/MemorySearch.cpp b/src/platform/qt/MemorySearch.cpp new file mode 100644 index 000000000..0f7df5ece --- /dev/null +++ b/src/platform/qt/MemorySearch.cpp @@ -0,0 +1,204 @@ +/* Copyright (c) 2013-2017 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 "MemorySearch.h" + +#include + +#include "GameController.h" +#include "MemoryView.h" + +using namespace QGBA; + +MemorySearch::MemorySearch(GameController* controller, QWidget* parent) + : QWidget(parent) + , m_controller(controller) +{ + m_ui.setupUi(this); + + mCoreMemorySearchResultsInit(&m_results, 0); + connect(m_ui.search, &QPushButton::clicked, this, &MemorySearch::search); + connect(m_ui.searchWithin, &QPushButton::clicked, this, &MemorySearch::searchWithin); + connect(m_ui.refresh, &QPushButton::clicked, this, &MemorySearch::refresh); + connect(m_ui.numHex, &QPushButton::clicked, this, &MemorySearch::refresh); + connect(m_ui.numDec, &QPushButton::clicked, this, &MemorySearch::refresh); + connect(m_ui.viewMem, &QPushButton::clicked, this, &MemorySearch::openMemory); +} + +MemorySearch::~MemorySearch() { + mCoreMemorySearchResultsDeinit(&m_results); +} + +bool MemorySearch::createParams(mCoreMemorySearchParams* params) { + params->memoryFlags = mCORE_MEMORY_RW; + mCore* core = m_controller->thread()->core; + + QByteArray string; + bool ok = false; + if (m_ui.typeNum->isChecked()) { + if (m_ui.bits8->isChecked()) { + params->type = mCORE_MEMORY_SEARCH_8; + } + if (m_ui.bits16->isChecked()) { + params->type = mCORE_MEMORY_SEARCH_16; + } + if (m_ui.bits32->isChecked()) { + params->type = mCORE_MEMORY_SEARCH_32; + } + if (m_ui.numHex->isChecked()) { + uint32_t v = m_ui.value->text().toUInt(&ok, 16); + if (ok) { + switch (params->type) { + case mCORE_MEMORY_SEARCH_8: + ok = v < 0x100; + params->value8 = v; + break; + case mCORE_MEMORY_SEARCH_16: + ok = v < 0x10000; + params->value16 = v; + break; + case mCORE_MEMORY_SEARCH_32: + params->value32 = v; + break; + default: + ok = false; + } + } + } + if (m_ui.numDec->isChecked()) { + uint32_t v = m_ui.value->text().toUInt(&ok, 10); + if (ok) { + switch (params->type) { + case mCORE_MEMORY_SEARCH_8: + ok = v < 0x100; + params->value8 = v; + break; + case mCORE_MEMORY_SEARCH_16: + ok = v < 0x10000; + params->value16 = v; + break; + case mCORE_MEMORY_SEARCH_32: + params->value32 = v; + break; + default: + ok = false; + } + } + } + if (m_ui.numGuess->isChecked()) { + params->type = mCORE_MEMORY_SEARCH_GUESS; + m_string = m_ui.value->text().toLocal8Bit(); + params->valueStr = m_string.constData(); + ok = true; + } + } + if (m_ui.typeStr->isChecked()) { + params->type = mCORE_MEMORY_SEARCH_STRING; + m_string = m_ui.value->text().toLocal8Bit(); + params->valueStr = m_string.constData(); + ok = true; + } + return ok; +} + +void MemorySearch::search() { + mCoreMemorySearchResultsClear(&m_results); + + mCoreMemorySearchParams params; + + GameController::Interrupter interrupter(m_controller); + if (!m_controller->isLoaded()) { + return; + } + mCore* core = m_controller->thread()->core; + + if (createParams(¶ms)) { + mCoreMemorySearch(core, ¶ms, &m_results, LIMIT); + } + + refresh(); +} + +void MemorySearch::searchWithin() { + mCoreMemorySearchParams params; + + GameController::Interrupter interrupter(m_controller); + if (!m_controller->isLoaded()) { + return; + } + mCore* core = m_controller->thread()->core; + + if (createParams(¶ms)) { + mCoreMemorySearchRepeat(core, ¶ms, &m_results); + } + + refresh(); +} + +void MemorySearch::refresh() { + GameController::Interrupter interrupter(m_controller); + if (!m_controller->isLoaded()) { + return; + } + mCore* core = m_controller->thread()->core; + + m_ui.results->clearContents(); + m_ui.results->setRowCount(mCoreMemorySearchResultsSize(&m_results)); + for (size_t i = 0; i < mCoreMemorySearchResultsSize(&m_results); ++i) { + 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); + if (m_ui.numHex->isChecked()) { + switch (result->type) { + case mCORE_MEMORY_SEARCH_8: + item = new QTableWidgetItem(QString("%1").arg(core->rawRead8(core, result->address, result->segment), 2, 16, QChar('0'))); + break; + case mCORE_MEMORY_SEARCH_16: + item = new QTableWidgetItem(QString("%1").arg(core->rawRead16(core, result->address, result->segment), 4, 16, QChar('0'))); + break; + case mCORE_MEMORY_SEARCH_GUESS: + case mCORE_MEMORY_SEARCH_32: + item = new QTableWidgetItem(QString("%1").arg(core->rawRead32(core, result->address, result->segment), 8, 16, QChar('0'))); + break; + case mCORE_MEMORY_SEARCH_STRING: + item = new QTableWidgetItem("?"); // TODO + } + } else { + switch (result->type) { + case mCORE_MEMORY_SEARCH_8: + item = new QTableWidgetItem(QString::number(core->rawRead8(core, result->address, result->segment))); + break; + case mCORE_MEMORY_SEARCH_16: + item = new QTableWidgetItem(QString::number(core->rawRead16(core, result->address, result->segment))); + break; + case mCORE_MEMORY_SEARCH_GUESS: + case mCORE_MEMORY_SEARCH_32: + item = new QTableWidgetItem(QString::number(core->rawRead32(core, result->address, result->segment))); + break; + case mCORE_MEMORY_SEARCH_STRING: + item = new QTableWidgetItem("?"); // TODO + } + } + m_ui.results->setItem(i, 1, item); + } + m_ui.results->sortItems(0); +} + +void MemorySearch::openMemory() { + auto items = m_ui.results->selectedItems(); + if (items.empty()) { + return; + } + QTableWidgetItem* item = items[0]; + uint32_t address = item->text().toUInt(nullptr, 16); + + MemoryView* memView = new MemoryView(m_controller); + memView->jumpToAddress(address); + + connect(m_controller, &GameController::gameStopped, memView, &QWidget::close); + memView->setAttribute(Qt::WA_DeleteOnClose); + memView->show(); +} diff --git a/src/platform/qt/MemorySearch.h b/src/platform/qt/MemorySearch.h new file mode 100644 index 000000000..65f365f44 --- /dev/null +++ b/src/platform/qt/MemorySearch.h @@ -0,0 +1,47 @@ +/* Copyright (c) 2013-2017 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 QGBA_MEMORY_SEARCH +#define QGBA_MEMORY_SEARCH + +#include "ui_MemorySearch.h" + +#include + +namespace QGBA { + +class GameController; + +class MemorySearch : public QWidget { +Q_OBJECT + +public: + static constexpr size_t LIMIT = 10000; + + MemorySearch(GameController* controller, QWidget* parent = nullptr); + ~MemorySearch(); + +public slots: + void refresh(); + void search(); + void searchWithin(); + +private slots: + void openMemory(); + +private: + bool createParams(mCoreMemorySearchParams*); + + Ui::MemorySearch m_ui; + + GameController* m_controller; + + mCoreMemorySearchResults m_results; + QByteArray m_string; +}; + +} + +#endif diff --git a/src/platform/qt/MemorySearch.ui b/src/platform/qt/MemorySearch.ui new file mode 100644 index 000000000..ccd7a05e9 --- /dev/null +++ b/src/platform/qt/MemorySearch.ui @@ -0,0 +1,223 @@ + + + MemorySearch + + + + 0 + 0 + 631 + 378 + + + + + 540 + 241 + + + + Form + + + + + + + 1 + 0 + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SelectRows + + + false + + + false + + + + Address + + + + + Current Value + + + + + Type + + + + + + + + + + Value + + + + + + + + + + Type + + + + + + + Numeric + + + true + + + type + + + + + + + Text + + + type + + + + + + + Width + + + + + + + 1 Byte (8-bit) + + + width + + + + + + + 2 Bytes (16-bit) + + + width + + + + + + + 4 Bytes (32-bit) + + + true + + + width + + + + + + + Number type + + + + + + + Hexadecimal + + + true + + + + + + + Decimal + + + + + + + Guess + + + + + + + + + QDialogButtonBox::Close + + + + + + + + + Search + + + + + + + Search Within + + + + + + + Open in Memory Viewer + + + + + + + Refresh + + + + + + + + + + + + + + + diff --git a/src/platform/qt/MemoryView.cpp b/src/platform/qt/MemoryView.cpp index b343060fa..543dbe6bf 100644 --- a/src/platform/qt/MemoryView.cpp +++ b/src/platform/qt/MemoryView.cpp @@ -9,53 +9,9 @@ #include "GameController.h" #include -#ifdef M_CORE_GBA -#include -#endif -#ifdef M_CORE_GB -#include -#endif using namespace QGBA; -struct IndexInfo { - const char* name; - const char* longName; - uint32_t base; - uint32_t size; - int maxSegment; -}; -#ifdef M_CORE_GBA -const static struct IndexInfo indexInfoGBA[] = { - { "All", "All", 0, 0x10000000 }, - { "BIOS", "BIOS (16kiB)", BASE_BIOS, SIZE_BIOS }, - { "EWRAM", "Working RAM (256kiB)", BASE_WORKING_RAM, SIZE_WORKING_RAM }, - { "IWRAM", "Internal Working RAM (32kiB)", BASE_WORKING_IRAM, SIZE_WORKING_IRAM }, - { "MMIO", "Memory-Mapped I/O", BASE_IO, SIZE_IO }, - { "Palette", "Palette RAM (1kiB)", BASE_PALETTE_RAM, SIZE_PALETTE_RAM }, - { "VRAM", "Video RAM (96kiB)", BASE_VRAM, SIZE_VRAM }, - { "OAM", "OBJ Attribute Memory (1kiB)", BASE_OAM, SIZE_OAM }, - { "ROM", "Game Pak (32MiB)", BASE_CART0, SIZE_CART0 }, - { "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, SIZE_CART1 }, - { "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, SIZE_CART2 }, - { "SRAM", "Static RAM (64kiB)", BASE_CART_SRAM, SIZE_CART_SRAM }, - { nullptr, nullptr, 0, 0, 0 } -}; -#endif -#ifdef M_CORE_GB -const static struct IndexInfo indexInfoGB[] = { - { "All", "All", 0, 0x10000 }, - { "ROM", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_SIZE_CART_BANK0 * 2, 511 }, - { "VRAM", "Video RAM (8kiB)", GB_BASE_VRAM, GB_SIZE_VRAM, 1 }, - { "SRAM", "External RAM (8kiB)", GB_BASE_EXTERNAL_RAM, GB_SIZE_EXTERNAL_RAM, 3 }, - { "WRAM", "Working RAM (8kiB)", GB_BASE_WORKING_RAM_BANK0, GB_SIZE_WORKING_RAM_BANK0 * 2, 7 }, - { "OAM", "OBJ Attribute Memory", GB_BASE_OAM, GB_SIZE_OAM }, - { "IO", "Memory-Mapped I/O", GB_BASE_IO, GB_SIZE_IO }, - { "HRAM", "High RAM", GB_BASE_HRAM, GB_SIZE_HRAM }, - { nullptr, nullptr, 0, 0, 0 } -}; -#endif - MemoryView::MemoryView(GameController* controller, QWidget* parent) : QWidget(parent) , m_controller(controller) @@ -65,21 +21,8 @@ MemoryView::MemoryView(GameController* controller, QWidget* parent) m_ui.hexfield->setController(controller); mCore* core = m_controller->thread()->core; - const IndexInfo* info = nullptr; - switch (core->platform(core)) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: - info = indexInfoGBA; - break; -#endif -#ifdef M_CORE_GB - case PLATFORM_GB: - info = indexInfoGB; - break; -#endif - default: - break; - } + const mCoreMemoryBlock* info; + size_t nBlocks = core->listMemoryBlocks(core, &info); connect(m_ui.regions, static_cast(&QComboBox::currentIndexChanged), this, &MemoryView::setIndex); @@ -87,7 +30,10 @@ MemoryView::MemoryView(GameController* controller, QWidget* parent) this, &MemoryView::setSegment); if (info) { - for (size_t i = 0; info[i].name; ++i) { + for (size_t i = 0; i < nBlocks; ++i) { + if (!(info[i].flags & (mCORE_MEMORY_MAPPED | mCORE_MEMORY_VIRTUAL))) { + continue; + } m_ui.regions->addItem(tr(info[i].longName)); } } @@ -116,44 +62,22 @@ MemoryView::MemoryView(GameController* controller, QWidget* parent) void MemoryView::setIndex(int index) { mCore* core = m_controller->thread()->core; - IndexInfo info; - switch (core->platform(core)) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: - info = indexInfoGBA[index]; - break; -#endif -#ifdef M_CORE_GB - case PLATFORM_GB: - info = indexInfoGB[index]; - break; -#endif - default: - return; - } + const mCoreMemoryBlock* blocks; + size_t nBlocks = core->listMemoryBlocks(core, &blocks); + const mCoreMemoryBlock& info = blocks[index]; + m_ui.segments->setValue(-1); m_ui.segments->setVisible(info.maxSegment > 0); m_ui.segments->setMaximum(info.maxSegment); - m_ui.hexfield->setRegion(info.base, info.size, info.name); + m_ui.hexfield->setRegion(info.start, info.end - info.start, info.shortName); } void MemoryView::setSegment(int segment) { mCore* core = m_controller->thread()->core; - IndexInfo info; - switch (core->platform(core)) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: - info = indexInfoGBA[m_ui.regions->currentIndex()]; - break; -#endif -#ifdef M_CORE_GB - case PLATFORM_GB: - info = indexInfoGB[m_ui.regions->currentIndex()]; - break; -#endif - default: - return; - } + const mCoreMemoryBlock* blocks; + size_t nBlocks = core->listMemoryBlocks(core, &blocks); + const mCoreMemoryBlock& info = blocks[m_ui.regions->currentIndex()]; + m_ui.hexfield->setSegment(info.maxSegment < segment ? info.maxSegment : segment); } diff --git a/src/platform/qt/MemoryView.h b/src/platform/qt/MemoryView.h index 9882ce32b..04a492ca9 100644 --- a/src/platform/qt/MemoryView.h +++ b/src/platform/qt/MemoryView.h @@ -22,6 +22,7 @@ public: public slots: void update(); + void jumpToAddress(uint32_t address) { m_ui.hexfield->jumpToAddress(address); } private slots: void setIndex(int); diff --git a/src/platform/qt/ObjView.cpp b/src/platform/qt/ObjView.cpp index 0fa19df8c..35fb9be0b 100644 --- a/src/platform/qt/ObjView.cpp +++ b/src/platform/qt/ObjView.cpp @@ -68,6 +68,7 @@ void ObjView::translateIndex(int index) { #ifdef M_CORE_GBA void ObjView::updateTilesGBA(bool force) { + m_ui.objId->setMaximum(127); const GBA* gba = static_cast(m_controller->thread()->core->board); const GBAObj* obj = &gba->video.oam.obj[m_objId]; @@ -172,6 +173,7 @@ void ObjView::updateTilesGBA(bool force) { #ifdef M_CORE_GB void ObjView::updateTilesGB(bool force) { + m_ui.objId->setMaximum(39); const GB* gb = static_cast(m_controller->thread()->core->board); const GBObj* obj = &gb->video.oam.obj[m_objId]; diff --git a/src/platform/qt/OverrideView.cpp b/src/platform/qt/OverrideView.cpp index 42f90f5eb..6b7652f81 100644 --- a/src/platform/qt/OverrideView.cpp +++ b/src/platform/qt/OverrideView.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "OverrideView.h" +#include #include #include "ConfigController.h" @@ -79,6 +80,21 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config, connect(m_ui.gbModel, &QComboBox::currentTextChanged, this, &OverrideView::updateOverrides); connect(m_ui.mbc, &QComboBox::currentTextChanged, this, &OverrideView::updateOverrides); + QPalette palette = m_ui.color0->palette(); + palette.setColor(backgroundRole(), QColor(0xF8, 0xF8, 0xF8)); + m_ui.color0->setPalette(palette); + palette.setColor(backgroundRole(), QColor(0xA8, 0xA8, 0xA8)); + m_ui.color1->setPalette(palette); + palette.setColor(backgroundRole(), QColor(0x50, 0x50, 0x50)); + m_ui.color2->setPalette(palette); + palette.setColor(backgroundRole(), QColor(0x00, 0x00, 0x00)); + m_ui.color3->setPalette(palette); + + m_ui.color0->installEventFilter(this); + m_ui.color1->installEventFilter(this); + m_ui.color2->installEventFilter(this); + m_ui.color3->installEventFilter(this); + connect(m_ui.tabWidget, &QTabWidget::currentChanged, this, &OverrideView::updateOverrides); #ifndef M_CORE_GBA m_ui.tabWidget->removeTab(m_ui.tabWidget->indexOf(m_ui.tabGBA)); @@ -96,6 +112,42 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config, } } +bool OverrideView::eventFilter(QObject* obj, QEvent* event) { +#ifdef M_CORE_GB + if (event->type() != QEvent::MouseButtonRelease) { + return false; + } + int colorId; + if (obj == m_ui.color0) { + colorId = 0; + } else if (obj == m_ui.color1) { + colorId = 1; + } else if (obj == m_ui.color2) { + colorId = 2; + } else if (obj == m_ui.color3) { + colorId = 3; + } else { + return false; + } + + QWidget* swatch = static_cast(obj); + + QColorDialog* colorPicker = new QColorDialog; + colorPicker->setAttribute(Qt::WA_DeleteOnClose); + colorPicker->open(); + connect(colorPicker, &QColorDialog::colorSelected, [this, swatch, colorId](const QColor& color) { + QPalette palette = swatch->palette(); + palette.setColor(backgroundRole(), color); + swatch->setPalette(palette); + m_gbColors[colorId] = color.rgb(); + updateOverrides(); + }); + return true; +#else + return false; +#endif +} + void OverrideView::saveOverride() { if (!m_config) { return; @@ -155,7 +207,13 @@ void OverrideView::updateOverrides() { GBOverride* gb = new GBOverride; gb->override.mbc = s_mbcList[m_ui.mbc->currentIndex()]; gb->override.model = s_gbModelList[m_ui.gbModel->currentIndex()]; - if (gb->override.mbc != GB_MBC_AUTODETECT || gb->override.model != GB_MODEL_AUTODETECT) { + gb->override.gbColors[0] = m_gbColors[0]; + gb->override.gbColors[1] = m_gbColors[1]; + gb->override.gbColors[2] = m_gbColors[2]; + gb->override.gbColors[3] = m_gbColors[3]; + bool hasOverride = gb->override.mbc != GB_MBC_AUTODETECT || gb->override.model != GB_MODEL_AUTODETECT; + hasOverride = hasOverride || (m_gbColors[0] | m_gbColors[1] | m_gbColors[2] | m_gbColors[3]); + if (hasOverride) { m_controller->setOverride(gb); } else { m_controller->clearOverride(); diff --git a/src/platform/qt/OverrideView.h b/src/platform/qt/OverrideView.h index c7f3fad99..3d1c8a3aa 100644 --- a/src/platform/qt/OverrideView.h +++ b/src/platform/qt/OverrideView.h @@ -36,6 +36,9 @@ private slots: void gameStarted(mCoreThread*); void gameStopped(); +protected: + bool eventFilter(QObject* obj, QEvent* event) override; + private: Ui::OverrideView m_ui; @@ -43,6 +46,8 @@ private: ConfigController* m_config; #ifdef M_CORE_GB + uint32_t m_gbColors[4]{}; + static QList s_gbModelList; static QList s_mbcList; #endif diff --git a/src/platform/qt/OverrideView.ui b/src/platform/qt/OverrideView.ui index 2d383ad47..3d8238cd8 100644 --- a/src/platform/qt/OverrideView.ui +++ b/src/platform/qt/OverrideView.ui @@ -6,8 +6,8 @@ 0 0 - 443 - 282 + 444 + 284 @@ -326,6 +326,93 @@ + + + + Colors + + + + + + + + + + 30 + 30 + + + + true + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + 30 + 30 + + + + true + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + 30 + 30 + + + + true + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + 30 + 30 + + + + true + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 161568fa6..1ea0ca131 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -88,6 +88,7 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC m_ui.patchPath->setText(path); } }); + connect(m_ui.clearCache, &QAbstractButton::pressed, this, &SettingsView::libraryCleared); // TODO: Move to reloadConfig() QVariant audioDriver = m_controller->getQtOption("audioDriver"); @@ -212,7 +213,7 @@ void SettingsView::updateConfig() { loadState |= m_ui.loadStateCheats->isChecked() ? SAVESTATE_CHEATS : 0; saveSetting("loadStateExtdata", loadState); - int saveState = SAVESTATE_RTC; + int saveState = SAVESTATE_RTC | SAVESTATE_METADATA; saveState |= m_ui.saveStateScreenshot->isChecked() ? SAVESTATE_SCREENSHOT : 0; saveState |= m_ui.saveStateSave->isChecked() ? SAVESTATE_SAVEDATA : 0; saveState |= m_ui.saveStateCheats->isChecked() ? SAVESTATE_CHEATS : 0; @@ -299,7 +300,7 @@ void SettingsView::reloadConfig() { int saveState = loadSetting("saveStateExtdata").toInt(&ok); if (!ok) { - saveState = SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC; + saveState = SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC | SAVESTATE_METADATA; } m_ui.saveStateScreenshot->setChecked(saveState & SAVESTATE_SCREENSHOT); m_ui.saveStateSave->setChecked(saveState & SAVESTATE_SAVEDATA); diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h index 511e2346c..c292b4a0f 100644 --- a/src/platform/qt/SettingsView.h +++ b/src/platform/qt/SettingsView.h @@ -29,6 +29,7 @@ signals: void audioDriverChanged(); void displayDriverChanged(); void pathsChanged(); + void libraryCleared(); private slots: void selectBios(QLineEdit*); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 8fca6efaf..6f27a1fd5 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -431,9 +431,6 @@ - - false - Clear cache diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 750bcff79..74904cc3b 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -35,6 +35,7 @@ #include "LoadSaveState.h" #include "LogView.h" #include "MultiplayerController.h" +#include "MemorySearch.h" #include "MemoryView.h" #include "OverrideView.h" #include "ObjView.h" @@ -468,6 +469,7 @@ void Window::openSettingsWindow() { connect(settingsWindow, &SettingsView::audioDriverChanged, m_controller, &GameController::reloadAudioDriver); connect(settingsWindow, &SettingsView::displayDriverChanged, this, &Window::mustRestart); connect(settingsWindow, &SettingsView::pathsChanged, this, &Window::reloadConfig); + connect(settingsWindow, &SettingsView::libraryCleared, m_libraryView, &LibraryController::clear); openView(settingsWindow); } @@ -1405,6 +1407,11 @@ void Window::setupMenu(QMenuBar* menubar) { m_gameActions.append(memoryView); addControlledAction(toolsMenu, memoryView, "memoryView"); + QAction* memorySearch = new QAction(tr("Search memory..."), toolsMenu); + connect(memorySearch, &QAction::triggered, openTView()); + m_gameActions.append(memorySearch); + addControlledAction(toolsMenu, memorySearch, "memorySearch"); + #ifdef M_CORE_GBA QAction* ioViewer = new QAction(tr("View &I/O registers..."), toolsMenu); connect(ioViewer, &QAction::triggered, openTView()); diff --git a/src/platform/qt/library/LibraryController.cpp b/src/platform/qt/library/LibraryController.cpp index ecb8a1fab..bb7acac36 100644 --- a/src/platform/qt/library/LibraryController.cpp +++ b/src/platform/qt/library/LibraryController.cpp @@ -24,7 +24,7 @@ void AbstractGameList::addEntries(QList items) { } void AbstractGameList::removeEntries(QList items) { for (LibraryEntryRef o : items) { - addEntry(o); + removeEntry(o); } } @@ -130,6 +130,20 @@ void LibraryController::addDirectory(const QString& dir) { m_loaderThread.start(); } +void LibraryController::clear() { + if (!m_library) { + if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { + m_library = m_loaderThread.m_library; + m_loaderThread.m_library = nullptr; + } else { + return; + } + } + + mLibraryClear(m_library); + refresh(); +} + void LibraryController::refresh() { if (!m_library) { if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { @@ -181,7 +195,7 @@ void LibraryController::refresh() { } void LibraryController::selectLastBootedGame() { - if (!m_config) { + if (!m_config || m_config->getMRU().isEmpty()) { return; } const QString lastfile = m_config->getMRU().first(); diff --git a/src/platform/qt/library/LibraryController.h b/src/platform/qt/library/LibraryController.h index 3cc82c8b5..7a7799b5b 100644 --- a/src/platform/qt/library/LibraryController.h +++ b/src/platform/qt/library/LibraryController.h @@ -100,6 +100,9 @@ public: void addDirectory(const QString& dir); +public slots: + void clear(); + signals: void startGame(); void doneLoading(); diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index 896d473ca..38ada79a5 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include #include