mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' into feature/input-revamp
This commit is contained in:
commit
ab07c280fe
20
CHANGES
20
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:
|
||||
|
|
|
@ -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})
|
||||
|
|
18
README.md
18
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
|
|||
|
||||
<a name="flashdetect">[2]</a> 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.
|
||||
|
||||
<a name="osxver">[3]</a> 10.7 is only needed for the Qt port. The SDL port is known to work on 10.6, and may work on older.
|
||||
<a name="osxver">[3]</a> 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:
|
||||
|
||||
|
|
|
@ -19,8 +19,7 @@ CXX_GUARD_START
|
|||
#endif
|
||||
#include <mgba/core/interface.h>
|
||||
#ifdef USE_DEBUGGERS
|
||||
// TODO: Fix layering violation
|
||||
#include <mgba/internal/debugger/debugger.h>
|
||||
#include <mgba/debugger/debugger.h>
|
||||
#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*);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 <mgba-util/common.h>
|
||||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba-util/vector.h>
|
||||
|
||||
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
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 <mgba/core/cpu.h>
|
||||
#include <mgba/core/log.h>
|
||||
#include <mgba-util/vector.h>
|
||||
|
||||
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
|
|
@ -12,7 +12,7 @@ CXX_GUARD_START
|
|||
|
||||
#include <mgba-util/table.h>
|
||||
|
||||
#include <mgba/internal/debugger/debugger.h>
|
||||
#include <mgba/debugger/debugger.h>
|
||||
|
||||
struct mArguments {
|
||||
char* fname;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -10,9 +10,10 @@
|
|||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/internal/debugger/debugger.h>
|
||||
#include <mgba/debugger/debugger.h>
|
||||
|
||||
#include <mgba/internal/arm/arm.h>
|
||||
#include <mgba-util/vector.h>
|
||||
|
||||
struct ARMDebugBreakpoint {
|
||||
uint32_t address;
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/internal/debugger/debugger.h>
|
||||
#include <mgba/debugger/debugger.h>
|
||||
|
||||
extern const char* ERROR_MISSING_ARGS;
|
||||
extern const char* ERROR_OVERFLOW;
|
||||
|
||||
struct CLIDebugger;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/internal/debugger/debugger.h>
|
||||
#include <mgba/debugger/debugger.h>
|
||||
|
||||
#include <mgba-util/socket.h>
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/internal/debugger/debugger.h>
|
||||
#include <mgba/debugger/debugger.h>
|
||||
|
||||
enum LexState {
|
||||
LEX_ERROR = -1,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ struct GBCartridgeOverride {
|
|||
int headerCrc32;
|
||||
enum GBModel model;
|
||||
enum GBMemoryBankControllerType mbc;
|
||||
|
||||
uint32_t gbColors[4];
|
||||
};
|
||||
|
||||
struct Configuration;
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -10,9 +10,10 @@
|
|||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/internal/debugger/debugger.h>
|
||||
#include <mgba/debugger/debugger.h>
|
||||
|
||||
#include <mgba/internal/lr35902/lr35902.h>
|
||||
#include <mgba-util/vector.h>
|
||||
|
||||
|
||||
struct LR35902DebugBreakpoint {
|
||||
|
|
|
@ -7,5 +7,5 @@ passes=1
|
|||
[pass.0]
|
||||
fragmentShader=agb001.fs
|
||||
blend=1
|
||||
width=960
|
||||
height=640
|
||||
width=-4
|
||||
height=-4
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -7,5 +7,5 @@ passes=1
|
|||
[pass.0]
|
||||
fragmentShader=wiiu.fs
|
||||
blend=1
|
||||
width=960
|
||||
height=640
|
||||
width=-4
|
||||
height=-4
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/internal/arm/arm.h>
|
||||
#include <mgba/internal/arm/decoder.h>
|
||||
#include <mgba/internal/arm/isa-inlines.h>
|
||||
#include <mgba/internal/arm/debugger/memory-debugger.h>
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 <mgba/core/mem-search.h>
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/core/interface.h>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 <mgba/internal/debugger/debugger.h>
|
||||
#include <mgba/debugger/debugger.h>
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
328
src/gb/mbc.c
328
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;
|
||||
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->sr = 0x1FFFF;
|
||||
mbc7->state = GBMBC7_STATE_NULL;
|
||||
} else {
|
||||
mbc7->latch = 0;
|
||||
return;
|
||||
default:
|
||||
mLOG(GB_MBC, STUB, "MBC7 unknown register: %04X:%02X", address, value);
|
||||
return;
|
||||
case 0x80:
|
||||
break;
|
||||
}
|
||||
GBMBC7Field old = memory->mbcState.mbc7.eeprom;
|
||||
value = GBMBC7FieldFillDO(value); // Hi-Z
|
||||
if (!GBMBC7FieldIsCS(old) && GBMBC7FieldIsCS(value)) {
|
||||
mbc7->state = GBMBC7_STATE_IDLE;
|
||||
}
|
||||
}
|
||||
if (!GBMBC7FieldIsSK(old) && GBMBC7FieldIsSK(value)) {
|
||||
if (mbc7->state > GBMBC7_STATE_IDLE && mbc7->state != GBMBC7_STATE_READ) {
|
||||
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;
|
||||
if (mbc7->srBits == 10) {
|
||||
mbc7->state = 0x10 | (mbc7->sr >> 6);
|
||||
if (mbc7->state & 0xC) {
|
||||
mbc7->state &= ~0x3;
|
||||
}
|
||||
mbc7->srBits = 0;
|
||||
mbc7->command = mbc7->sr;
|
||||
mbc7->address = mbc7->sr & 0x7F;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba/internal/gb/timer.h>
|
||||
|
||||
#include <mgba/internal/lr35902/lr35902.h>
|
||||
#include <mgba/internal/gb/gb.h>
|
||||
#include <mgba/internal/gb/io.h>
|
||||
#include <mgba/internal/gb/serialize.h>
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
137
src/gba/core.c
137
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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <mgba/internal/lr35902/debugger/debugger.h>
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/internal/lr35902/decoder.h>
|
||||
#include <mgba/internal/lr35902/lr35902.h>
|
||||
#include <mgba/internal/lr35902/debugger/memory-debugger.h>
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -28,7 +28,9 @@ void free(void*);
|
|||
#include "flags.h"
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/core/mem-search.h>
|
||||
#include <mgba/core/tile-cache.h>
|
||||
#include <mgba/core/version.h>
|
||||
|
||||
#define PYEXPORT extern "Python+C"
|
||||
#include "platform/python/log.h"
|
||||
|
|
|
@ -21,7 +21,9 @@ ffi.set_source("mgba._pylib", """
|
|||
#include <mgba-util/common.h>
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/core/log.h>
|
||||
#include <mgba/core/mem-search.h>
|
||||
#include <mgba/core/tile-cache.h>
|
||||
#include <mgba/core/version.h>
|
||||
#include <mgba/internal/arm/arm.h>
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/internal/gba/input.h>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
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
|
||||
|
@ -25,6 +30,11 @@ class Image:
|
|||
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
|
||||
|
|
|
@ -8,7 +8,11 @@ from . import createCallback
|
|||
|
||||
createCallback("mLoggerPy", "log", "_pyLog")
|
||||
|
||||
defaultLogger = None
|
||||
|
||||
def installDefault(logger):
|
||||
global defaultLogger
|
||||
defaultLogger = logger
|
||||
lib.mLogSetDefaultLogger(logger._native)
|
||||
|
||||
class Logger(object):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "GBAKeyEditor.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QComboBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPaintEvent>
|
||||
|
@ -182,10 +183,20 @@ bool GBAKeyEditor::event(QEvent* event) {
|
|||
}
|
||||
|
||||
bool GBAKeyEditor::eventFilter(QObject* obj, QEvent* event) {
|
||||
KeyEditor* keyEditor = static_cast<KeyEditor*>(obj);
|
||||
if (event->type() == QEvent::FocusOut) {
|
||||
keyEditor->setPalette(QApplication::palette(keyEditor));
|
||||
}
|
||||
if (event->type() != QEvent::FocusIn) {
|
||||
return false;
|
||||
}
|
||||
findFocus(static_cast<KeyEditor*>(obj));
|
||||
|
||||
QPalette palette = keyEditor->palette();
|
||||
palette.setBrush(keyEditor->backgroundRole(), palette.highlight());
|
||||
palette.setBrush(keyEditor->foregroundRole(), palette.highlightedText());
|
||||
keyEditor->setPalette(palette);
|
||||
|
||||
findFocus(keyEditor);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 <mgba/core/core.h>
|
||||
|
||||
#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();
|
||||
}
|
|
@ -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 <mgba/core/mem-search.h>
|
||||
|
||||
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
|
|
@ -0,0 +1,223 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MemorySearch</class>
|
||||
<widget class="QWidget" name="MemorySearch">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>631</width>
|
||||
<height>378</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>540</width>
|
||||
<height>241</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<widget class="QTableWidget" name="results">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="showGrid">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Address</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Current Value</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Type</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Value</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="value"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QRadioButton" name="typeNum">
|
||||
<property name="text">
|
||||
<string>Numeric</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">type</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QRadioButton" name="typeStr">
|
||||
<property name="text">
|
||||
<string>Text</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">type</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Width</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QRadioButton" name="bits8">
|
||||
<property name="text">
|
||||
<string>1 Byte (8-bit)</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">width</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QRadioButton" name="bits16">
|
||||
<property name="text">
|
||||
<string>2 Bytes (16-bit)</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">width</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QRadioButton" name="bits32">
|
||||
<property name="text">
|
||||
<string>4 Bytes (32-bit)</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">width</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Number type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QRadioButton" name="numHex">
|
||||
<property name="text">
|
||||
<string>Hexadecimal</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QRadioButton" name="numDec">
|
||||
<property name="text">
|
||||
<string>Decimal</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QRadioButton" name="numGuess">
|
||||
<property name="text">
|
||||
<string>Guess</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="search">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="searchWithin">
|
||||
<property name="text">
|
||||
<string>Search Within</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="viewMem">
|
||||
<property name="text">
|
||||
<string>Open in Memory Viewer</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="refresh">
|
||||
<property name="text">
|
||||
<string>Refresh</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<buttongroups>
|
||||
<buttongroup name="type"/>
|
||||
<buttongroup name="width"/>
|
||||
<buttongroup name="numType"/>
|
||||
</buttongroups>
|
||||
</ui>
|
|
@ -9,53 +9,9 @@
|
|||
#include "GameController.h"
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#ifdef M_CORE_GBA
|
||||
#include <mgba/internal/gba/memory.h>
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
#include <mgba/internal/gb/memory.h>
|
||||
#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<void (QComboBox::*)(int)>(&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);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ public:
|
|||
|
||||
public slots:
|
||||
void update();
|
||||
void jumpToAddress(uint32_t address) { m_ui.hexfield->jumpToAddress(address); }
|
||||
|
||||
private slots:
|
||||
void setIndex(int);
|
||||
|
|
|
@ -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<const GBA*>(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<const GB*>(m_controller->thread()->core->board);
|
||||
const GBObj* obj = &gb->video.oam.obj[m_objId];
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "OverrideView.h"
|
||||
|
||||
#include <QColorDialog>
|
||||
#include <QPushButton>
|
||||
|
||||
#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<QWidget*>(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();
|
||||
|
|
|
@ -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<enum GBModel> s_gbModelList;
|
||||
static QList<enum GBMemoryBankControllerType> s_mbcList;
|
||||
#endif
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>443</width>
|
||||
<height>282</height>
|
||||
<width>444</width>
|
||||
<height>284</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -326,6 +326,93 @@
|
|||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Colors</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QFrame" name="color0">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="color1">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="color2">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="color3">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -29,6 +29,7 @@ signals:
|
|||
void audioDriverChanged();
|
||||
void displayDriverChanged();
|
||||
void pathsChanged();
|
||||
void libraryCleared();
|
||||
|
||||
private slots:
|
||||
void selectBios(QLineEdit*);
|
||||
|
|
|
@ -431,9 +431,6 @@
|
|||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="clearCache">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear cache</string>
|
||||
</property>
|
||||
|
|
|
@ -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<MemorySearch>());
|
||||
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<IOViewer>());
|
||||
|
|
|
@ -24,7 +24,7 @@ void AbstractGameList::addEntries(QList<LibraryEntryRef> items) {
|
|||
}
|
||||
void AbstractGameList::removeEntries(QList<LibraryEntryRef> 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();
|
||||
|
|
|
@ -100,6 +100,9 @@ public:
|
|||
|
||||
void addDirectory(const QString& dir);
|
||||
|
||||
public slots:
|
||||
void clear();
|
||||
|
||||
signals:
|
||||
void startGame();
|
||||
void doneLoading();
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include <mgba/core/input.h>
|
||||
#include <mgba/core/serialize.h>
|
||||
#include <mgba/core/thread.h>
|
||||
#include <mgba/internal/debugger/debugger.h>
|
||||
#include <mgba/debugger/debugger.h>
|
||||
#include <mgba/internal/gba/input.h>
|
||||
#include <mgba-util/configuration.h>
|
||||
#include <mgba-util/formatting.h>
|
||||
|
|
Loading…
Reference in New Issue