Merge branch 'feature/input-revamp' into medusa

This commit is contained in:
Vicki Pfau 2017-07-12 20:43:13 -07:00
commit fb035eb0a3
66 changed files with 2652 additions and 1559 deletions

254
CHANGES
View File

@ -8,179 +8,199 @@ Misc:
- DS GX: Clean up and unify texture mapping
0.7.0: (Future)
Bugfixes:
- GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749)
Misc:
- GBA Timer: Use global cycles for timers
- GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722)
- All: Make FIXED_ROM_BUFFER an option instead of 3DS-only
0.6.0: (Future)
Features:
- GBA: Support printing debug strings from inside a game
- GBA: Better cheat type autodetection
- GB: Tile viewer
- Library view
- Sprite viewer
- Debugging console
- Improved memory viewer
- GB: LR35902/GB-Z80 disassembler
- Configuration of gamepad hats
- Qt: Spanish translation (by Kevin López)
- Add option for whether rewinding restores save games
- Qt: German translation (by Lothar Serra Mari)
- Savestates now contain any RTC override data
- Memory search
- Command line ability to override configuration values
- Add option to allow preloading the entire ROM before running
- GB: Video/audio channel enabling/disabling
- Add option for whether rewinding restores save games
- Savestates now contain any RTC override data
- Add option to lock video to integer scaling
- Video log recording for testing and bug reporting
- Library view
- Debugger: Segment/bank support
- LR35902: Watchpoints
- LR35902/GB-Z80 disassembler
- GB: Tile viewer
- GB: Video/audio channel enabling/disabling
- GB: Symbol table support
- GB MBC: Add MBC1 multicart support
- GBA: Support printing debug strings from inside a game
- GBA: Better cheat type autodetection
- Implement keypad interrupts
- LR35902: Watchpoints
- Memory search
- Configuration of gamepad hats
- Video log recording for testing and bug reporting
- Debugger: Segment/bank support
- Debugger: Execution tracing
- Qt: German translation (by Lothar Serra Mari)
- Qt: Spanish translation (by Kevin López)
- Qt: Italian translation (by theheroGAC)
Bugfixes:
- LR35902: Fix core never exiting with certain event patterns
- GB Timer: Improve DIV reset behavior
- GBA Memory: Improve initial skipped BIOS state
- GBA BIOS: Implement BitUnPack
- ARM7: Fix MLA/*MULL/*MLAL timing
- GBA: Fix multiboot ROM loading
- Libretro: Fix saving in GB games (fixes mgba.io/i/486)
- LR35902: Fix pc overflowing current region off-by-one
- GB MBC: Fix ROM bank overflows getting set to bank 0
- Qt: Fix timing issues on high refresh rate monitors
- GBA Savedata: Fix savedata unmasking (fixes mgba.io/i/441)
- Util: Fix overflow when loading invalid UPS patches
- Tools: Fix recurring multiple times over the same library
- GBA I/O: Handle audio registers specially when deserializing
- Util: Fix highest-fd socket not being returned by SocketAccept
- Qt: Fix linking after some windows have been closed
- GBA Video: Fix wrong palette on 256-color sprites in OBJWIN
- Windows: Fix VDir.rewind
- SDL: Fix game crash check
- SDL: Fix race condition with audio thread when starting
- GB: Fix flickering when screen is strobed quickly
- FFmpeg: Fix overflow and general issues with audio encoding
- Qt: Fix crash when changing audio settings after a game is closed
- GBA BIOS: Fix ArcTan sign in HLE BIOS
- GBA BIOS: Fix ArcTan2 sign in HLE BIOS (fixes mgba.io/i/689)
- GBA Video: Don't update background scanline params in mode 0 (fixes mgba.io/i/377)
- Qt: Ensure CLI backend is attached when submitting commands (fixes mgba.io/i/662)
- Core: Fix crash with rewind if savestates shrink
- Test: Fix crash when loading invalid file
- GBA Hardware: Fix crash if a savestate lies about game hardware
- Test: Fix crash when fuzzing fails to load a file
- GBA: Fix multiboot loading resulting in too small WRAM
- Test: Don't rely on core for frames elapsed
- Test: Fix crash when loading invalid file
- GBA Hardware: Fix crash if a savestate lies about game hardware
- Test: Fix crash when fuzzing fails to load a file
- Qt: Disable "New multiplayer window" when MAX_GBAS is reached (fixes mgba.io/i/107)
- 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
- Core: Fix interrupting a thread while on the thread (fixes mgba.io/i/692)
- Core: Fix directory sets crashing on close if base isn't properly detached
- FFmpeg: Fix overflow and general issues with audio encoding
- GB: Fix flickering when screen is strobed quickly
- GB: Fix STAT blocking
- GB MBC: Fix ROM bank overflows getting set to bank 0
- GB MBC: Fix swapping carts not detect new MBC
- GB Timer: Improve DIV reset behavior
- GB Timer: Fix DIV batching if TAC changes
- GB Video: Reset renderer when loading state
- GBA: Fix multiboot ROM loading
- GBA: Fix multiboot loading resulting in too small WRAM
- GBA BIOS: Implement BitUnPack
- GBA BIOS: Fix ArcTan sign in HLE BIOS
- GBA BIOS: Fix ArcTan2 sign in HLE BIOS (fixes mgba.io/i/689)
- GBA BIOS: Fix INT_MIN/-1 crash
- GBA Hardware: Fix crash if a savestate lies about game hardware
- GBA I/O: Handle audio registers specially when deserializing
- GBA Memory: Improve initial skipped BIOS state
- GBA Savedata: Fix savedata unmasking (fixes mgba.io/i/441)
- GBA Savedata: Update and fix Sharkport importing (fixes mgba.io/i/658)
- GBA Video: Fix wrong palette on 256-color sprites in OBJWIN
- GBA Video: Don't update background scanline params in mode 0 (fixes mgba.io/i/377)
- Libretro: Fix saving in GB games (fixes mgba.io/i/486)
- LR35902: Fix core never exiting with certain event patterns
- LR35902: Fix pc overflowing current region off-by-one
- LR35902: Fix decoding LD r, $imm and 0-valued immediates (fixes mgba.io/i/735)
- OpenGL: Fix some shaders causing offset graphics
- GB Timer: Fix sub-M-cycle DIV reset timing and edge triggering
- Qt: Fix window icon being stretched
- Qt: Fix data directory path
- Qt: Fix timing issues on high refresh rate monitors
- Qt: Fix linking after some windows have been closed
- Qt: Fix crash when changing audio settings after a game is closed
- Qt: Ensure CLI backend is attached when submitting commands (fixes mgba.io/i/662)
- Qt: Disable "New multiplayer window" when MAX_GBAS is reached (fixes mgba.io/i/107)
- Qt: Fix game unpausing after frame advancing and refocusing
- SDL: Fix game crash check
- SDL: Fix race condition with audio thread when starting
- SDL: Fix showing version number
- Test: Fix crash when loading invalid file
- Test: Fix crash when fuzzing fails to load a file
- Test: Don't rely on core for frames elapsed
- Test: Fix crash when loading invalid file
- Test: Fix crash when fuzzing fails to load a file
- Tools: Fix recurring multiple times over the same library
- Util: Fix overflow when loading invalid UPS patches
- Util: Fix highest-fd socket not being returned by SocketAccept
- Windows: Fix VDir.rewind
Misc:
- SDL: Remove scancode key input
- GBA Video: Clean up unused timers
- Test: Add a basic test suite
- GBA Video: Allow multiple handles into the same tile cache
- VFS: Call msync when syncing mapped data
- GBA Video, GB Video: Colors are now fully scaled
- VFS: Allow truncating memory chunk VFiles
- Debugger: Modularize CLI debugger
- Core: Clean up some thread state checks
- Debugger: Make building with debugging aspects optional
- GBA Memory: Support for Mo Jie Qi Bing by Vast Fame (taizou)
- GBA Memory: Support reading/writing POSTFLG
- Util: Add size counting to Table
- Qt: Move last directory setting from qt.ini to config.ini
- All: Add C++ header guards
- All: Move time.h include to common.h
- 3DS, PSP2, Wii: Last directory loaded is saved
- CMake: Add ability to just print version string
- Core: New, faster event timing subsystem
- Core: Clean up some thread state checks
- Core: Add generic checksum function
- Core: Cores can now have multiple sets of callbacks
- Core: Restore sleep callback
- Core: Move rewind diffing to its own thread
- Core: Ability to enumerate and modify video and audio channels
- Core: List memory segments in the core
- Core: Move savestate creation time to extdata
- Core: Config values can now be hexadecimal
- Core: Improved threading interrupted detection
- Debugger: Modularize CLI debugger
- Debugger: Make building with debugging aspects optional
- Debugger: Add functions for read- or write-only watchpoints
- Debugger: Make attaching a backend idempotent
- Debugger: Add mDebuggerRunFrame convenience function
- Feature: Move game database from flatfile to SQLite3
- Feature: Support ImageMagick 7
- Feature: Make -l option explicit
- FFmpeg: Return false if a file fails to open
- FFmpeg: Force MP4 files to YUV420P
- GB: Trust ROM header for number of SRAM banks (fixes mgba.io/i/726)
- GB: Reset with initial state of DIV register
- GB MBC: New MBC7 implementation
- GB Audio: Simplify envelope code
- GB Audio: Improve initial envelope samples
- Debugger: Add functions for read- or write-only watchpoints
- GB Audio: Start implementing "zombie" audio (fixes mgba.io/i/389)
- GB Video: Improved video timings
- GBA: Ignore invalid opcodes used by the Wii U VC emulator
- GBA, GB: ROM is now unloaded if a patch is applied
- GBA DMA: Refactor DMA out of memory.c
- GBA DMA: Move DMAs to using absolute timing
- All: Add C++ header guards
- GBA I/O: Clear JOYSTAT RECV flag when reading JOY_RECV registers
- GBA I/O: Set JOYSTAT TRANS flag when writing JOY_TRANS registers
- GBA Memory: Support for Mo Jie Qi Bing by Vast Fame (taizou)
- GBA Memory: Support reading/writing POSTFLG
- GBA Memory: Remove unused prefetch cruft
- GBA Timer: Improve accuracy of timers
- GBA Video: Clean up unused timers
- GBA Video: Allow multiple handles into the same tile cache
- GBA Video, GB Video: Colors are now fully scaled
- GBA Video: Optimize when BLD* registers are written frequently
- OpenGL: Add xBR-lv2 shader
- Qt: Move last directory setting from qt.ini to config.ini
- Qt: Improved HiDPI support
- Qt: Expose configuration directory
- Feature: Move game database from flatfile to SQLite3
- GB Audio: Start implementing "zombie" audio (fixes mgba.io/i/389)
- VFS: Fix some minor VFile issues with FILEs
- Core: Add generic checksum function
- Feature: Support ImageMagick 7
- All: Move time.h include to common.h
- CMake: Add ability to just print version string
- Qt: Merge "Save" and "OK" buttons in shader options
- SDL: Automatically map controllers when plugged in
- Qt: Automatically load controller profile when plugged in
- OpenGL: Add xBR-lv2 shader
- GBA, GB: ROM is now unloaded if a patch is applied
- Util: Add 8-bit PNG write support
- Qt: Rename "Resample video" option to "Bilinear filtering"
- GBA Video: Optimize when BLD* registers are written frequently
- Core: Cores can now have multiple sets of callbacks
- GBA: Ignore invalid opcodes used by the Wii U VC emulator
- Qt: Remove audio thread
- Qt: Remove audio buffer sizing in AudioProcessorQt
- Qt: Re-enable QtMultimedia on Windows
- FFmpeg: Return false if a file fails to open
- FFmpeg: Force MP4 files to YUV420P
- Qt: Make "Mute" able to be bound to a key
- Core: Restore sleep callback
- Qt: Add .gb/.gbc files to the extension list in Info.plist
- Feature: Make -l option explicit
- Core: Ability to enumerate and modify video and audio channels
- Debugger: Make attaching a backend idempotent
- Qt: Relax hard dependency on OpenGL
- Qt: Better highlight active key in control binding
- SDL: Remove scancode key input
- SDL: Automatically map controllers when plugged in
- Test: Add a basic test suite
- Util: Add size counting to Table
- Util: Add 8-bit PNG write support
- Util: Tune patch-fast extent sizes
- VFS: Call msync when syncing mapped data
- VFS: Allow truncating memory chunk VFiles
- VFS: Fix some minor VFile issues with FILEs
- VFS: Optimize expanding in-memory files
- VFS: Add VFileFIFO for operating on circle buffers
- Core: Move rewind diffing to its own thread
- 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
- Core: Improved threading interrupted detection
- GBA Timer: Improve accuracy of timers
0.6 beta 2: (Future)
Changes from beta 1:
Features:
- Qt: Italian translation (by theheroGAC)
- Qt: Updated German translation
Bugfixes:
- GB Audio: Fix incorrect channel 4 iteration
- GB Audio: Fix zombie mode bit masking
- GB Serialize: Fix timer serialization
- GB Video: Fix LYC regression
- GBA SIO: Improve SIO Normal dummy driver (fixes mgba.io/i/520)
- GBA Timer: Fix count-up timing overflowing timer 3
- PSP2: Use custom localtime_r since newlib version is broken (fixes mgba.io/i/560)
- Qt: Fix memory search close button (fixes mgba.io/i/769)
- Qt: Fix window icon being stretched
- Qt: Fix initial window size (fixes mgba.io/i/766)
- Qt: Fix data directory path
- Qt: Fix controls not saving on non-SDL builds
- GB Video: Fix LYC regression
- Qt: Fix translation initialization (fixes mgba.io/i/776)
- PSP2: Use custom localtime_r since newlib version is broken (fixes mgba.io/i/560)
- Qt: Fix patch loading while a game is running
- Qt: Fix shader selector on Ubuntu (fixes mgba.io/i/767)
- Core: Fix rewinding getting out of sync (fixes mgba.io/i/791)
- Qt: Fix GL-less build
- Qt: Fix Software renderer not handling alpha bits properly
Misc:
- Qt: Add language selector
- GB Serialize: Add MBC state serialization
- GBA Memory: Call crash callbacks regardless of if hard crash is enabled
- GBA Timer: Improve accuracy of timers
- Qt: Minor test fixes
- PSP2: Update toolchain to use vita.cmake
- Qt: Add language selector
- Qt: Minor test fixes
- Qt: Move shader settings into main settings window
- Qt: Dismiss game crashing/failing dialogs when a new game loads
- Qt: Properly ship Qt translations
- SDL: Remove writing back obtained samples (fixes mgba.io/i/768)
0.6 beta 1: (2017-06-29)
- Initial beta for 0.6

View File

@ -20,6 +20,7 @@ set(M_CORE_GBA ON CACHE BOOL "Build Game Boy Advance core")
set(M_CORE_GB ON CACHE BOOL "Build Game Boy core")
set(M_CORE_DS ON CACHE BOOL "Build DS core")
set(USE_LZMA ON CACHE BOOL "Whether or not to enable 7-Zip support")
set(ENABLE_SCRIPTING ON CACHE BOOL "Whether or not to enable scripting support")
set(BUILD_QT ON CACHE BOOL "Build Qt frontend")
set(BUILD_SDL ON CACHE BOOL "Build SDL frontend")
set(BUILD_LIBRETRO OFF CACHE BOOL "Build libretro core")
@ -260,6 +261,10 @@ if(DEFINED 3DS OR DEFINED PSP2 OR DEFINED WII)
set(USE_SQLITE3 OFF)
endif()
if(DEFINED 3DS OR DEFINED WII)
add_definitions(-DFIXED_ROM_BUFFER)
endif()
if(NOT M_CORE_GBA)
set(USE_GDB_STUB OFF)
endif()
@ -354,6 +359,7 @@ endif()
# Feature dependencies
set(FEATURE_DEFINES)
set(FEATURES)
set(ENABLES)
if(CMAKE_SYSTEM_NAME MATCHES .*BSD)
set(LIBEDIT_LIBRARIES -ledit)
if (CMAKE_SYSTEM_NAME STREQUAL OpenBSD)
@ -396,6 +402,7 @@ find_feature(USE_MAGICK "MagickWand")
find_feature(USE_EPOXY "epoxy")
find_feature(USE_CMOCKA "cmocka")
find_feature(USE_SQLITE3 "sqlite3")
find_feature(ENABLE_PYTHON "PythonLibs")
# Features
set(DEBUGGER_SRC
@ -613,6 +620,18 @@ if(USE_SQLITE3)
list(APPEND FEATURE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/feature/sqlite3/no-intro.c")
endif()
if(ENABLE_SCRIPTING)
list(APPEND ENABLES SCRIPTING)
if(BUILD_PYTHON)
find_program(PYTHON python)
include(FindPythonLibs)
list(APPEND DEPENDENCY_LIB ${PYTHON_LIBRARIES})
include_directories(AFTER ${PYTHON_INCLUDE_DIRS})
list(APPEND ENABLES PYTHON)
endif()
endif()
set(TEST_SRC ${CORE_TEST_SRC})
if(M_CORE_GB)
add_definitions(-DM_CORE_GB)
@ -673,6 +692,10 @@ foreach(FEATURE IN LISTS FEATURES)
list(APPEND FEATURE_DEFINES "USE_${FEATURE}")
endforeach()
foreach(ENABLE IN LISTS ENABLES)
list(APPEND FEATURE_DEFINES "ENABLE_${ENABLE}")
endforeach()
source_group("Virtual files" FILES ${CORE_VFS_SRC} ${VFS_SRC})
source_group("Extra features" FILES ${FEATURE_SRC})
source_group("Third-party code" FILES ${THIRD_PARTY_SRC})
@ -749,7 +772,7 @@ if(NOT SKIP_LIBRARY)
target_link_libraries(${BINARY_NAME} ${DEBUGGER_LIB} ${DEPENDENCY_LIB} ${OS_LIB})
install(TARGETS ${BINARY_NAME} LIBRARY DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME} NAMELINK_SKIP ARCHIVE DESTINATION ${LIBDIR} RUNTIME DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME})
if(UNIX AND NOT APPLE)
if(UNIX AND NOT APPLE AND NOT HAIKU)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/${BINARY_NAME}-16.png DESTINATION share/icons/hicolor/16x16/apps RENAME ${BINARY_NAME}.png COMPONENT lib${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/${BINARY_NAME}-24.png DESTINATION share/icons/hicolor/24x24/apps RENAME ${BINARY_NAME}.png COMPONENT lib${BINARY_NAME})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/${BINARY_NAME}-32.png DESTINATION share/icons/hicolor/32x32/apps RENAME ${BINARY_NAME}.png COMPONENT lib${BINARY_NAME})
@ -785,6 +808,11 @@ if(DISABLE_FRONTENDS)
set(BUILD_QT OFF)
endif()
if(BUILD_PYTHON)
enable_testing()
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/python ${CMAKE_CURRENT_BINARY_DIR}/python)
endif()
if(BUILD_LIBRETRO)
file(GLOB RETRO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/libretro/*.c)
add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC})
@ -857,11 +885,6 @@ if(BUILD_SUITE)
add_test(${BINARY_NAME}-suite ${BINARY_NAME}-suite)
endif()
if(BUILD_PYTHON)
enable_testing()
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/python ${CMAKE_CURRENT_BINARY_DIR}/python)
endif()
if(BUILD_EXAMPLE)
add_executable(${BINARY_NAME}-example-server ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/example/client-server/server.c)
target_link_libraries(${BINARY_NAME}-example-server ${BINARY_NAME})
@ -877,7 +900,7 @@ if(BUILD_EXAMPLE)
endif()
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})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/flags.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mgba COMPONENT lib${BINARY_NAME})
# Packaging
set(CPACK_PACKAGE_VERSION ${VERSION_STRING})

View File

@ -31,6 +31,7 @@ struct mCoreRewindContext {
Thread thread;
Condition cond;
Mutex mutex;
bool ready;
#endif
};

View File

@ -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 M_SCRIPTING_H
#define M_SCRIPTING_H
#include <mgba-util/common.h>
CXX_GUARD_START
#ifdef USE_DEBUGGERS
#include <mgba/debugger/debugger.h>
#endif
struct mScriptBridge;
struct VFile;
struct mScriptEngine {
const char* (*name)(struct mScriptEngine*);
bool (*init)(struct mScriptEngine*, struct mScriptBridge*);
void (*deinit)(struct mScriptEngine*);
bool (*isScript)(struct mScriptEngine*, const char* name, struct VFile* vf);
bool (*loadScript)(struct mScriptEngine*, const char* name, struct VFile* vf);
void (*run)(struct mScriptEngine*);
#ifdef USE_DEBUGGERS
void (*debuggerEntered)(struct mScriptEngine*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*);
#endif
};
struct mScriptBridge* mScriptBridgeCreate(void);
void mScriptBridgeDestroy(struct mScriptBridge*);
void mScriptBridgeInstallEngine(struct mScriptBridge*, struct mScriptEngine*);
#ifdef USE_DEBUGGERS
void mScriptBridgeSetDebugger(struct mScriptBridge*, struct mDebugger*);
struct mDebugger* mScriptBridgeGetDebugger(struct mScriptBridge*);
void mScriptBridgeDebuggerEntered(struct mScriptBridge*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*);
#endif
void mScriptBridgeRun(struct mScriptBridge*);
bool mScriptBridgeLoadScript(struct mScriptBridge*, const char* name);
CXX_GUARD_END
#endif

View File

@ -11,15 +11,40 @@
CXX_GUARD_START
#include <mgba/core/log.h>
#include <mgba/core/rewind.h>
#include <mgba/core/sync.h>
#include <mgba-util/threading.h>
struct mCoreThread;
struct mCore;
typedef void (*ThreadCallback)(struct mCoreThread* threadContext);
struct mCoreThread;
struct mThreadLogger {
struct mLogger d;
struct mCoreThread* p;
};
struct mCoreThreadInternal;
struct mCoreThread {
// Input
struct mCore* core;
struct mThreadLogger logger;
ThreadCallback startCallback;
ThreadCallback resetCallback;
ThreadCallback cleanCallback;
ThreadCallback frameCallback;
ThreadCallback sleepCallback;
void* userData;
void (*run)(struct mCoreThread*);
struct mCoreThreadInternal* impl;
};
#ifndef OPAQUE_THREADING
#include <mgba/core/rewind.h>
#include <mgba/core/sync.h>
#include <mgba-util/threading.h>
enum mCoreThreadState {
THREAD_INITIALIZED = -1,
THREAD_RUNNING = 0,
@ -37,17 +62,7 @@ enum mCoreThreadState {
THREAD_CRASHED
};
struct mCoreThread;
struct mThreadLogger {
struct mLogger d;
struct mCoreThread* p;
};
struct mCoreThread {
// Input
struct mCore* core;
// Threading state
struct mCoreThreadInternal {
Thread thread;
enum mCoreThreadState state;
@ -57,19 +72,12 @@ struct mCoreThread {
int interruptDepth;
bool frameWasOn;
struct mThreadLogger logger;
ThreadCallback startCallback;
ThreadCallback resetCallback;
ThreadCallback cleanCallback;
ThreadCallback frameCallback;
ThreadCallback sleepCallback;
void* userData;
void (*run)(struct mCoreThread*);
struct mCoreSync sync;
struct mCoreRewindContext rewind;
};
#endif
bool mCoreThreadStart(struct mCoreThread* threadContext);
bool mCoreThreadHasStarted(struct mCoreThread* threadContext);
bool mCoreThreadHasExited(struct mCoreThread* threadContext);

View File

@ -92,6 +92,7 @@ struct mDebugger {
struct mDebuggerPlatform* platform;
enum mDebuggerState state;
struct mCore* core;
struct mScriptBridge* bridge;
void (*init)(struct mDebugger*);
void (*deinit)(struct mDebugger*);

View File

@ -18,6 +18,7 @@ struct GB;
struct GBMemory;
void GBMBCInit(struct GB* gb);
void GBMBCSwitchBank(struct GB* gb, int bank);
void GBMBCSwitchBank0(struct GB* gb, int bank);
void GBMBCSwitchSramBank(struct GB* gb, int bank);
struct GBMBCRTCSaveBuffer {

View File

@ -145,7 +145,7 @@ mLOG_DECLARE_CATEGORY(GB_STATE);
* | 0x0017C - 0x0017D: HDMA remaining
* | 0x0017E: DMA remaining
* | 0x0017F - 0x00183: RTC registers
* | 0x00184 - 0x00193: MBC state (TODO)
* | 0x00184 - 0x00193: MBC state
* | 0x00194 - 0x00195: Flags
* | bit 0: SRAM accessable
* | bit 1: RTC accessible
@ -331,18 +331,21 @@ struct GBSerializedState {
union {
struct {
uint32_t mode;
uint8_t mode;
uint8_t multicartStride;
} mbc1;
struct {
uint64_t lastLatch;
} rtc;
struct {
int8_t machineState;
GBMBC7Field field;
int8_t address;
uint8_t state;
GBMBC7Field eeprom;
uint8_t address;
uint8_t access;
uint8_t latch;
uint8_t srBits;
uint32_t sr;
GBSerializedMBC7Flags flags;
uint16_t sr;
uint32_t writable;
} mbc7;
struct {
uint8_t reserved[16];

View File

@ -30,6 +30,7 @@ void mCoreRewindContextInit(struct mCoreRewindContext* context, size_t entries,
context->stateFlags = SAVESTATE_SAVEDATA;
#ifndef DISABLE_THREADING
context->onThread = onThread;
context->ready = false;
if (onThread) {
MutexInit(&context->mutex);
ConditionInit(&context->cond);
@ -73,6 +74,7 @@ void mCoreRewindAppend(struct mCoreRewindContext* context, struct mCore* core) {
context->currentState = nextState;
#ifndef DISABLE_THREADING
if (context->onThread) {
context->ready = true;
ConditionWake(&context->cond);
MutexUnlock(&context->mutex);
return;
@ -121,6 +123,12 @@ bool mCoreRewindRestore(struct mCoreRewindContext* context, struct mCore* core)
}
--context->size;
mCoreLoadStateNamed(core, context->previousState, context->stateFlags);
if (context->current == 0) {
context->current = mCoreRewindPatchesSize(&context->patchMemory);
}
--context->current;
struct PatchFast* patch = mCoreRewindPatchesGetPointer(&context->patchMemory, context->current);
size_t size2 = context->previousState->size(context->previousState);
size_t size = context->currentState->size(context->currentState);
@ -129,18 +137,12 @@ bool mCoreRewindRestore(struct mCoreRewindContext* context, struct mCore* core)
}
void* current = context->currentState->map(context->currentState, size, MAP_READ);
void* previous = context->previousState->map(context->previousState, size, MAP_WRITE);
patch->d.applyPatch(&patch->d, current, size, previous, size);
patch->d.applyPatch(&patch->d, previous, size, current, size);
context->currentState->unmap(context->currentState, current, size);
context->previousState->unmap(context->previousState, previous, size);
mCoreLoadStateNamed(core, context->previousState, context->stateFlags);
struct VFile* nextState = context->previousState;
context->previousState = context->currentState;
context->currentState = nextState;
if (context->current == 0) {
context->current = mCoreRewindPatchesSize(&context->patchMemory);
}
--context->current;
#ifndef DISABLE_THREADING
if (context->onThread) {
MutexUnlock(&context->mutex);
@ -154,13 +156,14 @@ THREAD_ENTRY _rewindThread(void* context) {
struct mCoreRewindContext* rewindContext = context;
ThreadSetName("Rewind Diff Thread");
MutexLock(&rewindContext->mutex);
struct VFile* state = rewindContext->currentState;
while (rewindContext->onThread) {
if (rewindContext->currentState != state) {
_rewindDiff(rewindContext);
state = rewindContext->currentState;
while (!rewindContext->ready && rewindContext->onThread) {
ConditionWait(&rewindContext->cond, &rewindContext->mutex);
}
ConditionWait(&rewindContext->cond, &rewindContext->mutex);
if (rewindContext->ready) {
_rewindDiff(rewindContext);
}
rewindContext->ready = false;
}
MutexUnlock(&rewindContext->mutex);
return 0;

113
src/core/scripting.c Normal file
View File

@ -0,0 +1,113 @@
/* 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/scripting.h>
#include <mgba-util/table.h>
#include <mgba-util/vfs.h>
struct mScriptBridge {
struct Table engines;
struct mDebugger* debugger;
};
struct mScriptInfo {
const char* name;
struct VFile* vf;
bool success;
};
static void _seDeinit(void* value) {
struct mScriptEngine* se = value;
se->deinit(se);
}
static void _seTryLoad(const char* key, void* value, void* user) {
UNUSED(key);
struct mScriptEngine* se = value;
struct mScriptInfo* si = user;
if (!si->success && se->isScript(se, si->name, si->vf)) {
si->success = se->loadScript(se, si->name, si->vf);
}
}
static void _seRun(const char* key, void* value, void* user) {
UNUSED(key);
UNUSED(user);
struct mScriptEngine* se = value;
se->run(se);
}
#ifdef USE_DEBUGGERS
struct mScriptDebuggerEntry {
enum mDebuggerEntryReason reason;
struct mDebuggerEntryInfo* info;
};
static void _seDebuggerEnter(const char* key, void* value, void* user) {
UNUSED(key);
struct mScriptEngine* se = value;
struct mScriptDebuggerEntry* entry = user;
se->debuggerEntered(se, entry->reason, entry->info);
}
#endif
struct mScriptBridge* mScriptBridgeCreate(void) {
struct mScriptBridge* sb = malloc(sizeof(*sb));
HashTableInit(&sb->engines, 0, _seDeinit);
sb->debugger = NULL;
return sb;
}
void mScriptBridgeDestroy(struct mScriptBridge* sb) {
HashTableDeinit(&sb->engines);
free(sb);
}
void mScriptBridgeInstallEngine(struct mScriptBridge* sb, struct mScriptEngine* se) {
if (!se->init(se, sb)) {
return;
}
const char* name = se->name(se);
HashTableInsert(&sb->engines, name, se);
}
#ifdef USE_DEBUGGERS
void mScriptBridgeSetDebugger(struct mScriptBridge* sb, struct mDebugger* debugger) {
sb->debugger = debugger;
debugger->bridge = sb;
}
struct mDebugger* mScriptBridgeGetDebugger(struct mScriptBridge* sb) {
return sb->debugger;
}
void mScriptBridgeDebuggerEntered(struct mScriptBridge* sb, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) {
struct mScriptDebuggerEntry entry = {
.reason = reason,
.info = info
};
HashTableEnumerate(&sb->engines, _seDebuggerEnter, &entry);
}
#endif
void mScriptBridgeRun(struct mScriptBridge* sb) {
HashTableEnumerate(&sb->engines, _seRun, NULL);
}
bool mScriptBridgeLoadScript(struct mScriptBridge* sb, const char* name) {
struct VFile* vf = VFileOpen(name, O_RDONLY);
if (!vf) {
return false;
}
struct mScriptInfo info = {
.name = name,
.vf = vf,
.success = false
};
HashTableEnumerate(&sb->engines, _seTryLoad, &info);
vf->close(vf);
return info.success;
}

View File

@ -38,7 +38,7 @@ static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
static void _mCoreLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args);
static void _changeState(struct mCoreThread* threadContext, enum mCoreThreadState newState, bool broadcast) {
static void _changeState(struct mCoreThreadInternal* threadContext, enum mCoreThreadState newState, bool broadcast) {
MutexLock(&threadContext->stateMutex);
threadContext->state = newState;
if (broadcast) {
@ -47,13 +47,13 @@ static void _changeState(struct mCoreThread* threadContext, enum mCoreThreadStat
MutexUnlock(&threadContext->stateMutex);
}
static void _waitOnInterrupt(struct mCoreThread* threadContext) {
static void _waitOnInterrupt(struct mCoreThreadInternal* threadContext) {
while (threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) {
ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
}
}
static void _waitUntilNotState(struct mCoreThread* threadContext, enum mCoreThreadState oldState) {
static void _waitUntilNotState(struct mCoreThreadInternal* threadContext, enum mCoreThreadState oldState) {
MutexLock(&threadContext->sync.videoFrameMutex);
bool videoFrameWait = threadContext->sync.videoFrameWait;
threadContext->sync.videoFrameWait = false;
@ -81,7 +81,7 @@ static void _waitUntilNotState(struct mCoreThread* threadContext, enum mCoreThre
MutexUnlock(&threadContext->sync.videoFrameMutex);
}
static void _pauseThread(struct mCoreThread* threadContext) {
static void _pauseThread(struct mCoreThreadInternal* threadContext) {
threadContext->state = THREAD_PAUSING;
_waitUntilNotState(threadContext, THREAD_PAUSING);
}
@ -92,11 +92,11 @@ void _frameStarted(void* context) {
return;
}
if (thread->core->opts.rewindEnable && thread->core->opts.rewindBufferCapacity > 0) {
if (thread->state != THREAD_REWINDING) {
mCoreRewindAppend(&thread->rewind, thread->core);
} else if (thread->state == THREAD_REWINDING) {
if (!mCoreRewindRestore(&thread->rewind, thread->core)) {
mCoreRewindAppend(&thread->rewind, thread->core);
if (thread->impl->state != THREAD_REWINDING) {
mCoreRewindAppend(&thread->impl->rewind, thread->core);
} else if (thread->impl->state == THREAD_REWINDING) {
if (!mCoreRewindRestore(&thread->impl->rewind, thread->core)) {
mCoreRewindAppend(&thread->impl->rewind, thread->core);
}
}
}
@ -117,7 +117,7 @@ void _crashed(void* context) {
if (!thread) {
return;
}
_changeState(thread, THREAD_CRASHED, true);
_changeState(thread->impl, THREAD_CRASHED, true);
}
void _coreSleep(void* context) {
@ -157,7 +157,7 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
.context = threadContext
};
core->addCoreCallbacks(core, &callbacks);
core->setSync(core, &threadContext->sync);
core->setSync(core, &threadContext->impl->sync);
core->reset(core);
struct mLogFilter filter;
@ -168,11 +168,11 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
}
if (core->opts.rewindEnable && core->opts.rewindBufferCapacity > 0) {
mCoreRewindContextInit(&threadContext->rewind, core->opts.rewindBufferCapacity, true);
threadContext->rewind.stateFlags = core->opts.rewindSave ? SAVESTATE_SAVEDATA : 0;
mCoreRewindContextInit(&threadContext->impl->rewind, core->opts.rewindBufferCapacity, true);
threadContext->impl->rewind.stateFlags = core->opts.rewindSave ? SAVESTATE_SAVEDATA : 0;
}
_changeState(threadContext, THREAD_RUNNING, true);
_changeState(threadContext->impl, THREAD_RUNNING, true);
if (threadContext->startCallback) {
threadContext->startCallback(threadContext);
@ -181,49 +181,50 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
threadContext->resetCallback(threadContext);
}
while (threadContext->state < THREAD_EXITING) {
struct mCoreThreadInternal* impl = threadContext->impl;
while (impl->state < THREAD_EXITING) {
#ifdef USE_DEBUGGERS
struct mDebugger* debugger = core->debugger;
if (debugger) {
mDebuggerRun(debugger);
if (debugger->state == DEBUGGER_SHUTDOWN) {
_changeState(threadContext, THREAD_EXITING, false);
_changeState(impl, THREAD_EXITING, false);
}
} else
#endif
{
while (threadContext->state <= THREAD_MAX_RUNNING) {
while (impl->state <= THREAD_MAX_RUNNING) {
core->runLoop(core);
}
}
int resetScheduled = 0;
MutexLock(&threadContext->stateMutex);
while (threadContext->state > THREAD_MAX_RUNNING && threadContext->state < THREAD_EXITING) {
if (threadContext->state == THREAD_PAUSING) {
threadContext->state = THREAD_PAUSED;
ConditionWake(&threadContext->stateCond);
MutexLock(&impl->stateMutex);
while (impl->state > THREAD_MAX_RUNNING && impl->state < THREAD_EXITING) {
if (impl->state == THREAD_PAUSING) {
impl->state = THREAD_PAUSED;
ConditionWake(&impl->stateCond);
}
if (threadContext->state == THREAD_INTERRUPTING) {
threadContext->state = THREAD_INTERRUPTED;
ConditionWake(&threadContext->stateCond);
if (impl->state == THREAD_INTERRUPTING) {
impl->state = THREAD_INTERRUPTED;
ConditionWake(&impl->stateCond);
}
if (threadContext->state == THREAD_RUN_ON) {
if (impl->state == THREAD_RUN_ON) {
if (threadContext->run) {
threadContext->run(threadContext);
}
threadContext->state = threadContext->savedState;
ConditionWake(&threadContext->stateCond);
impl->state = impl->savedState;
ConditionWake(&impl->stateCond);
}
if (threadContext->state == THREAD_RESETING) {
threadContext->state = THREAD_RUNNING;
if (impl->state == THREAD_RESETING) {
impl->state = THREAD_RUNNING;
resetScheduled = 1;
}
while (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_WAITING) {
ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
while (impl->state == THREAD_PAUSED || impl->state == THREAD_INTERRUPTED || impl->state == THREAD_WAITING) {
ConditionWait(&impl->stateCond, &impl->stateMutex);
}
}
MutexUnlock(&threadContext->stateMutex);
MutexUnlock(&impl->stateMutex);
if (resetScheduled) {
core->reset(core);
if (threadContext->resetCallback) {
@ -232,12 +233,12 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
}
}
while (threadContext->state < THREAD_SHUTDOWN) {
_changeState(threadContext, THREAD_SHUTDOWN, false);
while (impl->state < THREAD_SHUTDOWN) {
_changeState(impl, THREAD_SHUTDOWN, false);
}
if (core->opts.rewindEnable) {
mCoreRewindContextDeinit(&threadContext->rewind);
mCoreRewindContextDeinit(&impl->rewind);
}
if (threadContext->cleanCallback) {
@ -251,27 +252,28 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
}
bool mCoreThreadStart(struct mCoreThread* threadContext) {
threadContext->state = THREAD_INITIALIZED;
threadContext->impl = malloc(sizeof(*threadContext->impl));
threadContext->impl->state = THREAD_INITIALIZED;
threadContext->logger.p = threadContext;
if (!threadContext->logger.d.log) {
threadContext->logger.d.log = _mCoreLog;
threadContext->logger.d.filter = NULL;
}
if (!threadContext->sync.fpsTarget) {
threadContext->sync.fpsTarget = _defaultFPSTarget;
if (!threadContext->impl->sync.fpsTarget) {
threadContext->impl->sync.fpsTarget = _defaultFPSTarget;
}
MutexInit(&threadContext->stateMutex);
ConditionInit(&threadContext->stateCond);
MutexInit(&threadContext->impl->stateMutex);
ConditionInit(&threadContext->impl->stateCond);
MutexInit(&threadContext->sync.videoFrameMutex);
ConditionInit(&threadContext->sync.videoFrameAvailableCond);
ConditionInit(&threadContext->sync.videoFrameRequiredCond);
MutexInit(&threadContext->sync.audioBufferMutex);
ConditionInit(&threadContext->sync.audioRequiredCond);
MutexInit(&threadContext->impl->sync.videoFrameMutex);
ConditionInit(&threadContext->impl->sync.videoFrameAvailableCond);
ConditionInit(&threadContext->impl->sync.videoFrameRequiredCond);
MutexInit(&threadContext->impl->sync.audioBufferMutex);
ConditionInit(&threadContext->impl->sync.audioRequiredCond);
threadContext->interruptDepth = 0;
threadContext->impl->interruptDepth = 0;
#ifdef USE_PTHREADS
sigset_t signals;
@ -281,271 +283,289 @@ bool mCoreThreadStart(struct mCoreThread* threadContext) {
pthread_sigmask(SIG_BLOCK, &signals, 0);
#endif
threadContext->sync.audioWait = threadContext->core->opts.audioSync;
threadContext->sync.videoFrameWait = threadContext->core->opts.videoSync;
threadContext->sync.fpsTarget = threadContext->core->opts.fpsTarget;
threadContext->impl->sync.audioWait = threadContext->core->opts.audioSync;
threadContext->impl->sync.videoFrameWait = threadContext->core->opts.videoSync;
threadContext->impl->sync.fpsTarget = threadContext->core->opts.fpsTarget;
MutexLock(&threadContext->stateMutex);
ThreadCreate(&threadContext->thread, _mCoreThreadRun, threadContext);
while (threadContext->state < THREAD_RUNNING) {
ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
MutexLock(&threadContext->impl->stateMutex);
ThreadCreate(&threadContext->impl->thread, _mCoreThreadRun, threadContext);
while (threadContext->impl->state < THREAD_RUNNING) {
ConditionWait(&threadContext->impl->stateCond, &threadContext->impl->stateMutex);
}
MutexUnlock(&threadContext->stateMutex);
MutexUnlock(&threadContext->impl->stateMutex);
return true;
}
bool mCoreThreadHasStarted(struct mCoreThread* threadContext) {
if (!threadContext->impl) {
return false;
}
bool hasStarted;
MutexLock(&threadContext->stateMutex);
hasStarted = threadContext->state > THREAD_INITIALIZED;
MutexUnlock(&threadContext->stateMutex);
MutexLock(&threadContext->impl->stateMutex);
hasStarted = threadContext->impl->state > THREAD_INITIALIZED;
MutexUnlock(&threadContext->impl->stateMutex);
return hasStarted;
}
bool mCoreThreadHasExited(struct mCoreThread* threadContext) {
if (!threadContext->impl) {
return false;
}
bool hasExited;
MutexLock(&threadContext->stateMutex);
hasExited = threadContext->state > THREAD_EXITING;
MutexUnlock(&threadContext->stateMutex);
MutexLock(&threadContext->impl->stateMutex);
hasExited = threadContext->impl->state > THREAD_EXITING;
MutexUnlock(&threadContext->impl->stateMutex);
return hasExited;
}
bool mCoreThreadHasCrashed(struct mCoreThread* threadContext) {
if (!threadContext->impl) {
return false;
}
bool hasExited;
MutexLock(&threadContext->stateMutex);
hasExited = threadContext->state == THREAD_CRASHED;
MutexUnlock(&threadContext->stateMutex);
MutexLock(&threadContext->impl->stateMutex);
hasExited = threadContext->impl->state == THREAD_CRASHED;
MutexUnlock(&threadContext->impl->stateMutex);
return hasExited;
}
void mCoreThreadMarkCrashed(struct mCoreThread* threadContext) {
MutexLock(&threadContext->stateMutex);
threadContext->state = THREAD_CRASHED;
MutexUnlock(&threadContext->stateMutex);
MutexLock(&threadContext->impl->stateMutex);
threadContext->impl->state = THREAD_CRASHED;
MutexUnlock(&threadContext->impl->stateMutex);
}
void mCoreThreadEnd(struct mCoreThread* threadContext) {
MutexLock(&threadContext->stateMutex);
_waitOnInterrupt(threadContext);
threadContext->state = THREAD_EXITING;
ConditionWake(&threadContext->stateCond);
MutexUnlock(&threadContext->stateMutex);
MutexLock(&threadContext->sync.audioBufferMutex);
threadContext->sync.audioWait = 0;
ConditionWake(&threadContext->sync.audioRequiredCond);
MutexUnlock(&threadContext->sync.audioBufferMutex);
MutexLock(&threadContext->impl->stateMutex);
_waitOnInterrupt(threadContext->impl);
threadContext->impl->state = THREAD_EXITING;
ConditionWake(&threadContext->impl->stateCond);
MutexUnlock(&threadContext->impl->stateMutex);
MutexLock(&threadContext->impl->sync.audioBufferMutex);
threadContext->impl->sync.audioWait = 0;
ConditionWake(&threadContext->impl->sync.audioRequiredCond);
MutexUnlock(&threadContext->impl->sync.audioBufferMutex);
MutexLock(&threadContext->sync.videoFrameMutex);
threadContext->sync.videoFrameWait = false;
threadContext->sync.videoFrameOn = false;
ConditionWake(&threadContext->sync.videoFrameRequiredCond);
ConditionWake(&threadContext->sync.videoFrameAvailableCond);
MutexUnlock(&threadContext->sync.videoFrameMutex);
MutexLock(&threadContext->impl->sync.videoFrameMutex);
threadContext->impl->sync.videoFrameWait = false;
threadContext->impl->sync.videoFrameOn = false;
ConditionWake(&threadContext->impl->sync.videoFrameRequiredCond);
ConditionWake(&threadContext->impl->sync.videoFrameAvailableCond);
MutexUnlock(&threadContext->impl->sync.videoFrameMutex);
}
void mCoreThreadReset(struct mCoreThread* threadContext) {
MutexLock(&threadContext->stateMutex);
if (threadContext->state == THREAD_INTERRUPTED || threadContext->state == THREAD_INTERRUPTING) {
threadContext->savedState = THREAD_RESETING;
MutexLock(&threadContext->impl->stateMutex);
if (threadContext->impl->state == THREAD_INTERRUPTED || threadContext->impl->state == THREAD_INTERRUPTING) {
threadContext->impl->savedState = THREAD_RESETING;
} else {
threadContext->state = THREAD_RESETING;
threadContext->impl->state = THREAD_RESETING;
}
ConditionWake(&threadContext->stateCond);
MutexUnlock(&threadContext->stateMutex);
ConditionWake(&threadContext->impl->stateCond);
MutexUnlock(&threadContext->impl->stateMutex);
}
void mCoreThreadJoin(struct mCoreThread* threadContext) {
ThreadJoin(threadContext->thread);
if (!threadContext->impl) {
return;
}
ThreadJoin(threadContext->impl->thread);
MutexDeinit(&threadContext->stateMutex);
ConditionDeinit(&threadContext->stateCond);
MutexDeinit(&threadContext->impl->stateMutex);
ConditionDeinit(&threadContext->impl->stateCond);
MutexDeinit(&threadContext->sync.videoFrameMutex);
ConditionWake(&threadContext->sync.videoFrameAvailableCond);
ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
ConditionWake(&threadContext->sync.videoFrameRequiredCond);
ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
MutexDeinit(&threadContext->impl->sync.videoFrameMutex);
ConditionWake(&threadContext->impl->sync.videoFrameAvailableCond);
ConditionDeinit(&threadContext->impl->sync.videoFrameAvailableCond);
ConditionWake(&threadContext->impl->sync.videoFrameRequiredCond);
ConditionDeinit(&threadContext->impl->sync.videoFrameRequiredCond);
ConditionWake(&threadContext->sync.audioRequiredCond);
ConditionDeinit(&threadContext->sync.audioRequiredCond);
MutexDeinit(&threadContext->sync.audioBufferMutex);
ConditionWake(&threadContext->impl->sync.audioRequiredCond);
ConditionDeinit(&threadContext->impl->sync.audioRequiredCond);
MutexDeinit(&threadContext->impl->sync.audioBufferMutex);
free(threadContext->impl);
threadContext->impl = NULL;
}
bool mCoreThreadIsActive(struct mCoreThread* threadContext) {
return threadContext->state >= THREAD_RUNNING && threadContext->state < THREAD_EXITING;
if (!threadContext->impl) {
return false;
}
return threadContext->impl->state >= THREAD_RUNNING && threadContext->impl->state < THREAD_EXITING;
}
void mCoreThreadInterrupt(struct mCoreThread* threadContext) {
if (!threadContext) {
return;
}
MutexLock(&threadContext->stateMutex);
++threadContext->interruptDepth;
if (threadContext->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) {
MutexUnlock(&threadContext->stateMutex);
MutexLock(&threadContext->impl->stateMutex);
++threadContext->impl->interruptDepth;
if (threadContext->impl->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) {
MutexUnlock(&threadContext->impl->stateMutex);
return;
}
threadContext->savedState = threadContext->state;
_waitOnInterrupt(threadContext);
threadContext->state = THREAD_INTERRUPTING;
ConditionWake(&threadContext->stateCond);
_waitUntilNotState(threadContext, THREAD_INTERRUPTING);
MutexUnlock(&threadContext->stateMutex);
threadContext->impl->savedState = threadContext->impl->state;
_waitOnInterrupt(threadContext->impl);
threadContext->impl->state = THREAD_INTERRUPTING;
ConditionWake(&threadContext->impl->stateCond);
_waitUntilNotState(threadContext->impl, THREAD_INTERRUPTING);
MutexUnlock(&threadContext->impl->stateMutex);
}
void mCoreThreadInterruptFromThread(struct mCoreThread* threadContext) {
if (!threadContext) {
return;
}
MutexLock(&threadContext->stateMutex);
++threadContext->interruptDepth;
if (threadContext->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) {
if (threadContext->state == THREAD_INTERRUPTING) {
threadContext->state = THREAD_INTERRUPTED;
MutexLock(&threadContext->impl->stateMutex);
++threadContext->impl->interruptDepth;
if (threadContext->impl->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) {
if (threadContext->impl->state == THREAD_INTERRUPTING) {
threadContext->impl->state = THREAD_INTERRUPTED;
}
MutexUnlock(&threadContext->stateMutex);
MutexUnlock(&threadContext->impl->stateMutex);
return;
}
threadContext->savedState = threadContext->state;
threadContext->state = THREAD_INTERRUPTED;
ConditionWake(&threadContext->stateCond);
MutexUnlock(&threadContext->stateMutex);
threadContext->impl->savedState = threadContext->impl->state;
threadContext->impl->state = THREAD_INTERRUPTING;
ConditionWake(&threadContext->impl->stateCond);
MutexUnlock(&threadContext->impl->stateMutex);
}
void mCoreThreadContinue(struct mCoreThread* threadContext) {
if (!threadContext) {
return;
}
MutexLock(&threadContext->stateMutex);
--threadContext->interruptDepth;
if (threadContext->interruptDepth < 1 && mCoreThreadIsActive(threadContext)) {
threadContext->state = threadContext->savedState;
ConditionWake(&threadContext->stateCond);
MutexLock(&threadContext->impl->stateMutex);
--threadContext->impl->interruptDepth;
if (threadContext->impl->interruptDepth < 1 && mCoreThreadIsActive(threadContext)) {
threadContext->impl->state = threadContext->impl->savedState;
ConditionWake(&threadContext->impl->stateCond);
}
MutexUnlock(&threadContext->stateMutex);
MutexUnlock(&threadContext->impl->stateMutex);
}
void mCoreThreadRunFunction(struct mCoreThread* threadContext, void (*run)(struct mCoreThread*)) {
MutexLock(&threadContext->stateMutex);
MutexLock(&threadContext->impl->stateMutex);
threadContext->run = run;
_waitOnInterrupt(threadContext);
threadContext->savedState = threadContext->state;
threadContext->state = THREAD_RUN_ON;
ConditionWake(&threadContext->stateCond);
_waitUntilNotState(threadContext, THREAD_RUN_ON);
MutexUnlock(&threadContext->stateMutex);
_waitOnInterrupt(threadContext->impl);
threadContext->impl->savedState = threadContext->impl->state;
threadContext->impl->state = THREAD_RUN_ON;
ConditionWake(&threadContext->impl->stateCond);
_waitUntilNotState(threadContext->impl, THREAD_RUN_ON);
MutexUnlock(&threadContext->impl->stateMutex);
}
void mCoreThreadPause(struct mCoreThread* threadContext) {
bool frameOn = threadContext->sync.videoFrameOn;
MutexLock(&threadContext->stateMutex);
_waitOnInterrupt(threadContext);
if (threadContext->state == THREAD_RUNNING) {
_pauseThread(threadContext);
threadContext->frameWasOn = frameOn;
bool frameOn = threadContext->impl->sync.videoFrameOn;
MutexLock(&threadContext->impl->stateMutex);
_waitOnInterrupt(threadContext->impl);
if (threadContext->impl->state == THREAD_RUNNING) {
_pauseThread(threadContext->impl);
threadContext->impl->frameWasOn = frameOn;
frameOn = false;
}
MutexUnlock(&threadContext->stateMutex);
MutexUnlock(&threadContext->impl->stateMutex);
mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
mCoreSyncSetVideoSync(&threadContext->impl->sync, frameOn);
}
void mCoreThreadUnpause(struct mCoreThread* threadContext) {
bool frameOn = threadContext->sync.videoFrameOn;
MutexLock(&threadContext->stateMutex);
_waitOnInterrupt(threadContext);
if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
threadContext->state = THREAD_RUNNING;
ConditionWake(&threadContext->stateCond);
frameOn = threadContext->frameWasOn;
bool frameOn = threadContext->impl->sync.videoFrameOn;
MutexLock(&threadContext->impl->stateMutex);
_waitOnInterrupt(threadContext->impl);
if (threadContext->impl->state == THREAD_PAUSED || threadContext->impl->state == THREAD_PAUSING) {
threadContext->impl->state = THREAD_RUNNING;
ConditionWake(&threadContext->impl->stateCond);
frameOn = threadContext->impl->frameWasOn;
}
MutexUnlock(&threadContext->stateMutex);
MutexUnlock(&threadContext->impl->stateMutex);
mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
mCoreSyncSetVideoSync(&threadContext->impl->sync, frameOn);
}
bool mCoreThreadIsPaused(struct mCoreThread* threadContext) {
bool isPaused;
MutexLock(&threadContext->stateMutex);
if (threadContext->interruptDepth) {
isPaused = threadContext->savedState == THREAD_PAUSED;
MutexLock(&threadContext->impl->stateMutex);
if (threadContext->impl->interruptDepth) {
isPaused = threadContext->impl->savedState == THREAD_PAUSED;
} else {
isPaused = threadContext->state == THREAD_PAUSED;
isPaused = threadContext->impl->state == THREAD_PAUSED;
}
MutexUnlock(&threadContext->stateMutex);
MutexUnlock(&threadContext->impl->stateMutex);
return isPaused;
}
void mCoreThreadTogglePause(struct mCoreThread* threadContext) {
bool frameOn = threadContext->sync.videoFrameOn;
MutexLock(&threadContext->stateMutex);
_waitOnInterrupt(threadContext);
if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
threadContext->state = THREAD_RUNNING;
ConditionWake(&threadContext->stateCond);
frameOn = threadContext->frameWasOn;
} else if (threadContext->state == THREAD_RUNNING) {
_pauseThread(threadContext);
threadContext->frameWasOn = frameOn;
bool frameOn = threadContext->impl->sync.videoFrameOn;
MutexLock(&threadContext->impl->stateMutex);
_waitOnInterrupt(threadContext->impl);
if (threadContext->impl->state == THREAD_PAUSED || threadContext->impl->state == THREAD_PAUSING) {
threadContext->impl->state = THREAD_RUNNING;
ConditionWake(&threadContext->impl->stateCond);
frameOn = threadContext->impl->frameWasOn;
} else if (threadContext->impl->state == THREAD_RUNNING) {
_pauseThread(threadContext->impl);
threadContext->impl->frameWasOn = frameOn;
frameOn = false;
}
MutexUnlock(&threadContext->stateMutex);
MutexUnlock(&threadContext->impl->stateMutex);
mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
mCoreSyncSetVideoSync(&threadContext->impl->sync, frameOn);
}
void mCoreThreadPauseFromThread(struct mCoreThread* threadContext) {
bool frameOn = true;
MutexLock(&threadContext->stateMutex);
if (threadContext->state == THREAD_RUNNING || (threadContext->interruptDepth && threadContext->savedState == THREAD_RUNNING)) {
threadContext->state = THREAD_PAUSING;
MutexLock(&threadContext->impl->stateMutex);
if (threadContext->impl->state == THREAD_RUNNING || (threadContext->impl->interruptDepth && threadContext->impl->savedState == THREAD_RUNNING)) {
threadContext->impl->state = THREAD_PAUSING;
frameOn = false;
}
MutexUnlock(&threadContext->stateMutex);
MutexUnlock(&threadContext->impl->stateMutex);
mCoreSyncSetVideoSync(&threadContext->sync, frameOn);
mCoreSyncSetVideoSync(&threadContext->impl->sync, frameOn);
}
void mCoreThreadSetRewinding(struct mCoreThread* threadContext, bool rewinding) {
MutexLock(&threadContext->stateMutex);
if (rewinding && (threadContext->state == THREAD_REWINDING || (threadContext->interruptDepth && threadContext->savedState == THREAD_REWINDING))) {
MutexUnlock(&threadContext->stateMutex);
MutexLock(&threadContext->impl->stateMutex);
if (rewinding && (threadContext->impl->state == THREAD_REWINDING || (threadContext->impl->interruptDepth && threadContext->impl->savedState == THREAD_REWINDING))) {
MutexUnlock(&threadContext->impl->stateMutex);
return;
}
if (!rewinding && ((!threadContext->interruptDepth && threadContext->state != THREAD_REWINDING) || (threadContext->interruptDepth && threadContext->savedState != THREAD_REWINDING))) {
MutexUnlock(&threadContext->stateMutex);
if (!rewinding && ((!threadContext->impl->interruptDepth && threadContext->impl->state != THREAD_REWINDING) || (threadContext->impl->interruptDepth && threadContext->impl->savedState != THREAD_REWINDING))) {
MutexUnlock(&threadContext->impl->stateMutex);
return;
}
_waitOnInterrupt(threadContext);
if (rewinding && threadContext->state == THREAD_RUNNING) {
threadContext->state = THREAD_REWINDING;
_waitOnInterrupt(threadContext->impl);
if (rewinding && threadContext->impl->state == THREAD_RUNNING) {
threadContext->impl->state = THREAD_REWINDING;
}
if (!rewinding && threadContext->state == THREAD_REWINDING) {
threadContext->state = THREAD_RUNNING;
if (!rewinding && threadContext->impl->state == THREAD_REWINDING) {
threadContext->impl->state = THREAD_RUNNING;
}
MutexUnlock(&threadContext->stateMutex);
MutexUnlock(&threadContext->impl->stateMutex);
}
void mCoreThreadWaitFromThread(struct mCoreThread* threadContext) {
MutexLock(&threadContext->stateMutex);
if (threadContext->interruptDepth && threadContext->savedState == THREAD_RUNNING) {
threadContext->savedState = THREAD_WAITING;
} else if (threadContext->state == THREAD_RUNNING) {
threadContext->state = THREAD_WAITING;
MutexLock(&threadContext->impl->stateMutex);
if (threadContext->impl->interruptDepth && threadContext->impl->savedState == THREAD_RUNNING) {
threadContext->impl->savedState = THREAD_WAITING;
} else if (threadContext->impl->state == THREAD_RUNNING) {
threadContext->impl->state = THREAD_WAITING;
}
MutexUnlock(&threadContext->stateMutex);
MutexUnlock(&threadContext->impl->stateMutex);
}
void mCoreThreadStopWaiting(struct mCoreThread* threadContext) {
MutexLock(&threadContext->stateMutex);
if (threadContext->interruptDepth && threadContext->savedState == THREAD_WAITING) {
threadContext->savedState = THREAD_RUNNING;
} else if (threadContext->state == THREAD_WAITING) {
threadContext->state = THREAD_RUNNING;
ConditionWake(&threadContext->stateCond);
MutexLock(&threadContext->impl->stateMutex);
if (threadContext->impl->interruptDepth && threadContext->impl->savedState == THREAD_WAITING) {
threadContext->impl->savedState = THREAD_RUNNING;
} else if (threadContext->impl->state == THREAD_WAITING) {
threadContext->impl->state = THREAD_RUNNING;
ConditionWake(&threadContext->impl->stateCond);
}
MutexUnlock(&threadContext->stateMutex);
MutexUnlock(&threadContext->impl->stateMutex);
}
#ifdef USE_PTHREADS

View File

@ -12,6 +12,10 @@
#include <mgba/internal/debugger/parser.h>
#include <mgba-util/string.h>
#if ENABLE_SCRIPTING
#include <mgba/core/scripting.h>
#endif
#if !defined(NDEBUG) && !defined(_WIN32)
#include <signal.h>
#endif
@ -51,6 +55,9 @@ static void _writeWord(struct CLIDebugger*, struct CLIDebugVector*);
static void _dumpByte(struct CLIDebugger*, struct CLIDebugVector*);
static void _dumpHalfword(struct CLIDebugger*, struct CLIDebugVector*);
static void _dumpWord(struct CLIDebugger*, struct CLIDebugVector*);
#ifdef ENABLE_SCRIPTING
static void _source(struct CLIDebugger*, struct CLIDebugVector*);
#endif
static struct CLIDebuggerCommandSummary _debuggerCommands[] = {
{ "b", _setBreakpoint, CLIDVParse, "Set a breakpoint" },
@ -92,6 +99,9 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = {
{ "x/1", _dumpByte, CLIDVParse, "Examine bytes at a specified offset" },
{ "x/2", _dumpHalfword, CLIDVParse, "Examine halfwords at a specified offset" },
{ "x/4", _dumpWord, CLIDVParse, "Examine words at a specified offset" },
#ifdef ENABLE_SCRIPTING
{ "source", _source, CLIDVStringParse, "Load a script" },
#endif
#if !defined(NDEBUG) && !defined(_WIN32)
{ "!", _breakInto, 0, "Break into attached debugger (for developers)" },
#endif
@ -411,6 +421,20 @@ static void _dumpWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
}
}
#ifdef ENABLE_SCRIPTING
static void _source(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
if (!dv) {
debugger->backend->printf(debugger->backend, "Needs a filename\n");
return;
}
if (debugger->d.bridge && mScriptBridgeLoadScript(debugger->d.bridge, dv->charValue)) {
mScriptBridgeRun(debugger->d.bridge);
} else {
debugger->backend->printf(debugger->backend, "Failed to load script\n");
}
}
#endif
static void _setBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
if (!dv || dv->type != CLIDV_INT_TYPE) {
debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS);

View File

@ -13,6 +13,10 @@
#include <mgba/internal/debugger/gdb-stub.h>
#endif
#if ENABLE_SCRIPTING
#include <mgba/core/scripting.h>
#endif
const uint32_t DEBUGGER_ID = 0xDEADBEEF;
mLOG_DEFINE_CATEGORY(DEBUGGER, "Debugger", "core.debugger");
@ -34,6 +38,7 @@ struct mDebugger* mDebuggerCreate(enum mDebuggerType type, struct mCore* core) {
};
union DebugUnion* debugger = malloc(sizeof(union DebugUnion));
memset(debugger, 0, sizeof(*debugger));
switch (type) {
case DEBUGGER_CLI:
@ -109,6 +114,11 @@ void mDebuggerEnter(struct mDebugger* debugger, enum mDebuggerEntryReason reason
if (debugger->platform->entered) {
debugger->platform->entered(debugger->platform, reason, info);
}
#ifdef ENABLE_SCRIPTING
if (debugger->bridge) {
mScriptBridgeDebuggerEntered(debugger->bridge, reason, info);
}
#endif
}
static void mDebuggerInit(void* cpu, struct mCPUComponent* component) {

View File

@ -620,8 +620,9 @@ void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) {
}
}
*left = sampleLeft * (1 + audio->volumeLeft);
*right = sampleRight * (1 + audio->volumeRight);
int dcOffset = audio->style == GB_AUDIO_GBA ? 0 : 0x1FC;
*left = (sampleLeft - dcOffset) * (1 + audio->volumeLeft);
*right = (sampleRight - dcOffset) * (1 + audio->volumeRight);
}
static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
@ -702,6 +703,7 @@ bool _writeEnvelope(struct GBAudioEnvelope* envelope, uint8_t value) {
if (!envelope->stepTime) {
// TODO: Improve "zombie" mode
++envelope->currentVolume;
envelope->currentVolume &= 0xF;
}
_updateEnvelopeDead(envelope);
envelope->nextStep = envelope->stepTime;
@ -709,7 +711,7 @@ bool _writeEnvelope(struct GBAudioEnvelope* envelope, uint8_t value) {
}
static void _updateSquareSample(struct GBAudioSquareChannel* ch) {
ch->sample = (ch->control.hi * 2 - 1) * ch->envelope.currentVolume * 0x8;
ch->sample = ch->control.hi * ch->envelope.currentVolume * 0x8;
}
static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch) {
@ -860,8 +862,7 @@ static void _updateChannel3(struct mTiming* timing, void* user, uint32_t cyclesL
ch->sample = bitsCarry >> 4;
break;
}
ch->sample -= 8;
ch->sample *= volume * 4;
ch->sample *= volume * 2;
audio->ch3.readable = true;
if (audio->style == GB_AUDIO_DMG) {
mTimingDeschedule(audio->timing, &audio->ch3Fade);
@ -888,12 +889,12 @@ static void _updateChannel4(struct mTiming* timing, void* user, uint32_t cyclesL
do {
int lsb = ch->lfsr & 1;
ch->sample = lsb * 0x10 - 0x8;
ch->sample = lsb * 0x8;
ch->sample *= ch->envelope.currentVolume;
ch->lfsr >>= 1;
ch->lfsr ^= (lsb * 0x60) << (ch->power ? 0 : 8);
cycles += baseCycles;
} while (cycles < audio->sampleInterval);
} while (cycles + baseCycles < audio->sampleInterval);
mTimingSchedule(timing, &audio->ch4Event, cycles - cyclesLate);
}

View File

@ -42,7 +42,7 @@ static void GBStop(struct LR35902Core* cpu);
static void _enableInterrupts(struct mTiming* timing, void* user, uint32_t cyclesLate);
#ifdef _3DS
#ifdef FIXED_ROM_BUFFER
extern uint32_t* romBuffer;
extern size_t romBufferSize;
#endif
@ -109,7 +109,7 @@ bool GBLoadROM(struct GB* gb, struct VFile* vf) {
gb->pristineRomSize = vf->size(vf);
vf->seek(vf, 0, SEEK_SET);
gb->isPristine = true;
#ifdef _3DS
#ifdef FIXED_ROM_BUFFER
if (gb->pristineRomSize <= romBufferSize) {
gb->memory.rom = romBuffer;
vf->read(vf, romBuffer, gb->pristineRomSize);
@ -277,7 +277,7 @@ void GBUnloadROM(struct GB* gb) {
}
if (gb->romVf) {
#ifndef _3DS
#ifndef FIXED_ROM_BUFFER
gb->romVf->unmap(gb->romVf, gb->memory.rom, gb->pristineRomSize);
#endif
gb->romVf->close(gb->romVf);
@ -326,7 +326,7 @@ void GBApplyPatch(struct GB* gb, struct Patch* patch) {
return;
}
if (gb->romVf) {
#ifndef _3DS
#ifndef FIXED_ROM_BUFFER
gb->romVf->unmap(gb->romVf, gb->memory.rom, gb->pristineRomSize);
#endif
gb->romVf->close(gb->romVf);

View File

@ -50,7 +50,7 @@ void GBMBCSwitchBank(struct GB* gb, int bank) {
}
}
static void _switchBank0(struct GB* gb, int bank) {
void GBMBCSwitchBank0(struct GB* gb, int bank) {
size_t bankStart = bank * GB_SIZE_CART_BANK0 << gb->memory.mbcState.mbc1.multicartStride;
if (bankStart + GB_SIZE_CART_BANK0 > gb->memory.romSize) {
mLOG(GB_MBC, GAME_ERROR, "Attempting to switch to an invalid ROM bank: %0X", bank);
@ -320,7 +320,7 @@ void _GBMBC1(struct GB* gb, uint16_t address, uint8_t value) {
case 0x2:
bank &= 3;
if (memory->mbcState.mbc1.mode) {
_switchBank0(gb, bank);
GBMBCSwitchBank0(gb, bank);
GBMBCSwitchSramBank(gb, bank);
}
GBMBCSwitchBank(gb, (bank << memory->mbcState.mbc1.multicartStride) | (memory->currentBank & (stride - 1)));
@ -328,9 +328,9 @@ void _GBMBC1(struct GB* gb, uint16_t address, uint8_t value) {
case 0x3:
memory->mbcState.mbc1.mode = value & 1;
if (memory->mbcState.mbc1.mode) {
_switchBank0(gb, memory->currentBank >> memory->mbcState.mbc1.multicartStride);
GBMBCSwitchBank0(gb, memory->currentBank >> memory->mbcState.mbc1.multicartStride);
} else {
_switchBank0(gb, 0);
GBMBCSwitchBank0(gb, 0);
GBMBCSwitchSramBank(gb, 0);
}
break;

View File

@ -629,6 +629,28 @@ void GBMemorySerialize(const struct GB* gb, struct GBSerializedState* state) {
flags = GBSerializedMemoryFlagsSetIsHdma(flags, memory->isHdma);
flags = GBSerializedMemoryFlagsSetActiveRtcReg(flags, memory->activeRtcReg);
STORE_16LE(flags, 0, &state->memory.flags);
switch (memory->mbcType) {
case GB_MBC1:
state->memory.mbc1.mode = memory->mbcState.mbc1.mode;
state->memory.mbc1.multicartStride = memory->mbcState.mbc1.multicartStride;
break;
case GB_MBC3_RTC:
STORE_64LE(gb->memory.rtcLastLatch, 0, &state->memory.rtc.lastLatch);
break;
case GB_MBC7:
state->memory.mbc7.state = memory->mbcState.mbc7.state;
state->memory.mbc7.eeprom = memory->mbcState.mbc7.eeprom;
state->memory.mbc7.address = memory->mbcState.mbc7.address;
state->memory.mbc7.access = memory->mbcState.mbc7.access;
state->memory.mbc7.latch = memory->mbcState.mbc7.latch;
state->memory.mbc7.srBits = memory->mbcState.mbc7.srBits;
STORE_16LE(memory->mbcState.mbc7.sr, 0, &state->memory.mbc7.sr);
STORE_32LE(memory->mbcState.mbc7.writable, 0, &state->memory.mbc7.writable);
break;
default:
break;
}
}
void GBMemoryDeserialize(struct GB* gb, const struct GBSerializedState* state) {
@ -671,6 +693,32 @@ void GBMemoryDeserialize(struct GB* gb, const struct GBSerializedState* state) {
memory->ime = GBSerializedMemoryFlagsGetIme(flags);
memory->isHdma = GBSerializedMemoryFlagsGetIsHdma(flags);
memory->activeRtcReg = GBSerializedMemoryFlagsGetActiveRtcReg(flags);
switch (memory->mbcType) {
case GB_MBC1:
memory->mbcState.mbc1.mode = state->memory.mbc1.mode;
memory->mbcState.mbc1.multicartStride = state->memory.mbc1.multicartStride;
if (memory->mbcState.mbc1.mode) {
GBMBCSwitchBank0(gb, memory->currentBank >> memory->mbcState.mbc1.multicartStride);
}
break;
case GB_MBC3_RTC:
// TODO?
//LOAD_64LE(gb->memory.rtcLastLatch, 0, &state->memory.rtc.lastLatch);
break;
case GB_MBC7:
memory->mbcState.mbc7.state = state->memory.mbc7.state;
memory->mbcState.mbc7.eeprom = state->memory.mbc7.eeprom;
memory->mbcState.mbc7.address = state->memory.mbc7.address & 0x7F;
memory->mbcState.mbc7.access = state->memory.mbc7.access;
memory->mbcState.mbc7.latch = state->memory.mbc7.latch;
memory->mbcState.mbc7.srBits = state->memory.mbc7.srBits;
LOAD_16LE(memory->mbcState.mbc7.sr, 0, &state->memory.mbc7.sr);
LOAD_32LE(memory->mbcState.mbc7.writable, 0, &state->memory.mbc7.writable);
break;
default:
break;
}
}
void _pristineCow(struct GB* gb) {

View File

@ -118,7 +118,7 @@ void GBTimerSerialize(const struct GBTimer* timer, struct GBSerializedState* sta
STORE_32LE(timer->event.when - mTimingCurrentTime(&timer->p->timing), 0, &state->timer.nextEvent);
STORE_32LE(timer->irq.when - mTimingCurrentTime(&timer->p->timing), 0, &state->timer.nextIRQ);
GBSerializedTimerFlags flags = GBSerializedTimerFlagsSetIrqPending(0, mTimingIsScheduled(&timer->p->timing, &timer->irq));
STORE_32LE(flags, 0, &state->timer.flags);
state->timer.flags = flags;
}
void GBTimerDeserialize(struct GBTimer* timer, const struct GBSerializedState* state) {
@ -130,8 +130,7 @@ void GBTimerDeserialize(struct GBTimer* timer, const struct GBSerializedState* s
LOAD_32LE(when, 0, &state->timer.nextEvent);
mTimingSchedule(&timer->p->timing, &timer->event, when);
GBSerializedTimerFlags flags;
LOAD_32LE(flags, 0, &state->timer.flags);
GBSerializedTimerFlags flags = state->timer.flags;
if (GBSerializedTimerFlagsIsIrqPending(flags)) {
LOAD_32LE(when, 0, &state->timer.nextIRQ);

View File

@ -42,7 +42,7 @@ static bool _setSoftwareBreakpoint(struct ARMDebugger*, uint32_t address, enum E
static bool _clearSoftwareBreakpoint(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t opcode);
#ifdef _3DS
#ifdef FIXED_ROM_BUFFER
extern uint32_t* romBuffer;
extern size_t romBufferSize;
#endif
@ -120,7 +120,7 @@ void GBAUnloadROM(struct GBA* gba) {
}
if (gba->romVf) {
#ifndef _3DS
#ifndef FIXED_ROM_BUFFER
gba->romVf->unmap(gba->romVf, gba->memory.rom, gba->pristineRomSize);
#endif
gba->romVf->close(gba->romVf);
@ -323,7 +323,7 @@ bool GBALoadROM(struct GBA* gba, struct VFile* vf) {
gba->pristineRomSize = SIZE_CART0;
}
gba->isPristine = true;
#ifdef _3DS
#ifdef FIXED_ROM_BUFFER
if (gba->pristineRomSize <= romBufferSize) {
gba->memory.rom = romBuffer;
vf->read(vf, romBuffer, gba->pristineRomSize);
@ -342,6 +342,16 @@ bool GBALoadROM(struct GBA* gba, struct VFile* vf) {
gba->romCrc32 = doCrc32(gba->memory.rom, gba->memory.romSize);
GBAHardwareInit(&gba->memory.hw, &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]);
GBAVFameDetect(&gba->memory.vfame, gba->memory.rom, gba->memory.romSize);
if (popcount32(gba->memory.romSize) != 1) {
// This ROM is either a bad dump or homebrew. Emulate flash cart behavior.
#ifndef FIXED_ROM_BUFFER
void* newRom = anonymousMemoryMap(SIZE_CART0);
memcpy(newRom, gba->memory.rom, gba->pristineRomSize);
gba->memory.rom = newRom;
#endif
gba->memory.romSize = SIZE_CART0;
gba->isPristine = false;
}
// TODO: error check
return true;
}
@ -394,7 +404,7 @@ void GBAApplyPatch(struct GBA* gba, struct Patch* patch) {
return;
}
if (gba->romVf) {
#ifndef _3DS
#ifndef FIXED_ROM_BUFFER
gba->romVf->unmap(gba->romVf, gba->memory.rom, gba->pristineRomSize);
#endif
gba->romVf->close(gba->romVf);

View File

@ -709,16 +709,16 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
switch (address) {
// Reading this takes two cycles (1N+1I), so let's remove them preemptively
case REG_TM0CNT_LO:
GBATimerUpdateRegister(gba, 0, 2);
GBATimerUpdateRegister(gba, 0, 4);
break;
case REG_TM1CNT_LO:
GBATimerUpdateRegister(gba, 1, 2);
GBATimerUpdateRegister(gba, 1, 4);
break;
case REG_TM2CNT_LO:
GBATimerUpdateRegister(gba, 2, 2);
GBATimerUpdateRegister(gba, 2, 4);
break;
case REG_TM3CNT_LO:
GBATimerUpdateRegister(gba, 3, 2);
GBATimerUpdateRegister(gba, 3, 4);
break;
case REG_KEYINPUT:

View File

@ -297,10 +297,8 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
memory->activeRegion = -1;
cpu->memory.activeRegion = _deadbeef;
cpu->memory.activeMask = 0;
if (gba->yankedRomSize || !gba->hardCrash) {
mLOG(GBA_MEM, GAME_ERROR, "Jumped to invalid address: %08X", address);
} else if (mCoreCallbacksListSize(&gba->coreCallbacks)) {
mLOG(GBA_MEM, GAME_ERROR, "Jumped to invalid address: %08X", address);
if (!gba->yankedRomSize && mCoreCallbacksListSize(&gba->coreCallbacks)) {
size_t c;
for (c = 0; c < mCoreCallbacksListSize(&gba->coreCallbacks); ++c) {
struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&gba->coreCallbacks, c);
@ -308,6 +306,10 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
callbacks->coreCrashed(callbacks->context);
}
}
}
if (gba->yankedRomSize || !gba->hardCrash) {
mLOG(GBA_MEM, GAME_ERROR, "Jumped to invalid address: %08X", address);
} else {
mLOG(GBA_MEM, FATAL, "Jumped to invalid address: %08X", address);
}
@ -1551,7 +1553,7 @@ void _pristineCow(struct GBA* gba) {
gba->cpu->memory.activeRegion = newRom;
}
if (gba->romVf) {
#ifndef _3DS
#ifndef FIXED_ROM_BUFFER
gba->romVf->unmap(gba->romVf, gba->memory.rom, gba->memory.romSize);
#endif
gba->romVf->close(gba->romVf);

View File

@ -155,11 +155,13 @@ void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) {
case SIO_NORMAL_8:
case SIO_NORMAL_32:
value |= 0x0004;
if ((value & 0x4080) == 0x4080) {
// TODO: Test this on hardware to see if this is correct
GBARaiseIRQ(sio->p, IRQ_SIO);
if ((value & 0x0081) == 0x0081) {
if (value & 0x4000) {
// TODO: Test this on hardware to see if this is correct
GBARaiseIRQ(sio->p, IRQ_SIO);
}
value &= ~0x0080;
}
value &= ~0x0080;
break;
default:
// TODO

View File

@ -6,12 +6,33 @@ foreach(DIR IN LISTS INCLUDE_DIRECTORIES)
list(APPEND INCLUDE_FLAGS "-I${DIR}")
endforeach()
include(FindPythonLibs)
list(APPEND DEPENDENCY_LIB ${PYTHON_LIBRARIES})
include_directories(AFTER ${PYTHON_INCLUDE_DIRS})
file(GLOB PYTHON_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
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}
DEPENDS ${BINARY_NAME}
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/setup.py
DEPENDS ${PYTHON_HEADERS}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py)
add_custom_target(${BINARY_NAME}-py ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/build/lib/${BINARY_NAME}/__init__.py)
add_custom_command(OUTPUT lib.c
COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON} ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py
DEPENDS ${PYTHON_HEADERS}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py)
set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/lib.c PROPERTIES GENERATED ON)
file(GLOB PYTHON_SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.c)
add_library(${BINARY_NAME}-pylib STATIC ${CMAKE_CURRENT_BINARY_DIR}/lib.c ${PYTHON_SRC})
add_dependencies(${BINARY_NAME}-pylib ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py)
set_target_properties(${BINARY_NAME}-pylib PROPERTIES INCLUDE_DIRECTORIES "${CMAKE_BINARY_DIR};${INCLUDE_DIRECTORIES}")
set_target_properties(${BINARY_NAME}-pylib PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}")
set(PYTHON_LIBRARY ${BINARY_NAME}-pylib PARENT_SCOPE)
add_custom_target(${BINARY_NAME}-py ALL DEPENDS ${BINARY_NAME}-pylib ${CMAKE_CURRENT_BINARY_DIR}/build/lib/${BINARY_NAME}/__init__.py)

View File

@ -1,5 +1,6 @@
#define COMMON_H
#define PNG_H
#define OPAQUE_THREADING
#define _SYS_TIME_H
#define _SYS_TIME_H_
#define _TIME_H
@ -30,9 +31,11 @@ void free(void*);
#include <mgba/core/core.h>
#include <mgba/core/mem-search.h>
#include <mgba/core/tile-cache.h>
#include <mgba/core/thread.h>
#include <mgba/core/version.h>
#define PYEXPORT extern "Python+C"
#include "platform/python/core.h"
#include "platform/python/log.h"
#include "platform/python/sio.h"
#include "platform/python/vfs-py.h"
@ -53,3 +56,6 @@ void free(void*);
#include <mgba/internal/gba/input.h>
#include <mgba/internal/gb/renderers/tile-cache.h>
#endif
#ifdef USE_DEBUGGERS
#include <mgba/debugger/debugger.h>
#endif

View File

@ -18,12 +18,15 @@ cppflags.extend(["-I" + incdir, "-I" + srcdir, "-I" + bindir])
ffi.set_source("mgba._pylib", """
#include "flags.h"
#define OPAQUE_THREADING
#include <mgba-util/common.h>
#include <mgba/core/core.h>
#include <mgba/core/log.h>
#include <mgba/core/mem-search.h>
#include <mgba/core/thread.h>
#include <mgba/core/tile-cache.h>
#include <mgba/core/version.h>
#include <mgba/debugger/debugger.h>
#include <mgba/internal/arm/arm.h>
#include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/input.h>
@ -35,6 +38,7 @@ ffi.set_source("mgba._pylib", """
#include <mgba-util/vfs.h>
#define PYEXPORT
#include "platform/python/core.h"
#include "platform/python/log.h"
#include "platform/python/sio.h"
#include "platform/python/vfs-py.h"
@ -43,7 +47,7 @@ ffi.set_source("mgba._pylib", """
extra_compile_args=cppflags,
libraries=["medusa-emu"],
library_dirs=[bindir],
sources=[os.path.join(pydir, path) for path in ["vfs-py.c", "log.c", "sio.c"]])
sources=[os.path.join(pydir, path) for path in ["vfs-py.c", "core.c", "log.c", "sio.c"]])
preprocessed = subprocess.check_output(cpp + ["-fno-inline", "-P"] + cppflags + [os.path.join(pydir, "_builder.h")], universal_newlines=True)
@ -55,5 +59,59 @@ for line in preprocessed.splitlines():
lines.append(line)
ffi.cdef('\n'.join(lines))
preprocessed = subprocess.check_output(cpp + ["-fno-inline", "-P"] + cppflags + [os.path.join(pydir, "lib.h")], universal_newlines=True)
lines = []
for line in preprocessed.splitlines():
line = line.strip()
if line.startswith('#'):
continue
lines.append(line)
ffi.embedding_api('\n'.join(lines))
ffi.embedding_init_code("""
from mgba._pylib import ffi
debugger = None
pendingCode = []
@ffi.def_extern()
def mPythonSetDebugger(_debugger):
from mgba.debugger import NativeDebugger
global debugger
if debugger and debugger._native == _debugger:
return
debugger = _debugger and NativeDebugger(_debugger)
@ffi.def_extern()
def mPythonLoadScript(name, vf):
from mgba.vfs import VFile
vf = VFile(vf)
name = ffi.string(name)
source = vf.readAll().decode('utf-8')
try:
code = compile(source, name, 'exec')
pendingCode.append(code)
except:
return False
return True
@ffi.def_extern()
def mPythonRunPending():
global pendingCode
for code in pendingCode:
exec(code)
pendingCode = []
@ffi.def_extern()
def mPythonDebuggerEntered(reason, info):
global debugger
if not debugger:
return
if info == ffi.NULL:
info = None
for cb in debugger._cbs:
cb(reason, info)
""")
if __name__ == "__main__":
ffi.compile()
ffi.emit_c_code("lib.c")

View File

@ -0,0 +1,19 @@
/* Copyright (c) 2013-2016 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 "core.h"
#include <mgba/core/core.h>
struct mCoreCallbacks* mCorePythonCallbackCreate(void* pyobj) {
struct mCoreCallbacks* callbacks = malloc(sizeof(*callbacks));
callbacks->videoFrameStarted = _mCorePythonCallbacksVideoFrameStarted;
callbacks->videoFrameEnded = _mCorePythonCallbacksVideoFrameEnded;
callbacks->coreCrashed = _mCorePythonCallbacksCoreCrashed;
callbacks->sleep = _mCorePythonCallbacksSleep;
callbacks->context = pyobj;
return callbacks;
}

View File

@ -0,0 +1,15 @@
/* 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-util/common.h>
#include "pycommon.h"
struct mCoreCallbacks* mCorePythonCallbackCreate(void* pyobj);
PYEXPORT void _mCorePythonCallbacksVideoFrameStarted(void* user);
PYEXPORT void _mCorePythonCallbacksVideoFrameEnded(void* user);
PYEXPORT void _mCorePythonCallbacksCoreCrashed(void* user);
PYEXPORT void _mCorePythonCallbacksSleep(void* user);

View File

@ -0,0 +1,103 @@
/* Copyright (c) 2013-2016 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 "engine.h"
#include <mgba/core/scripting.h>
#include <mgba-util/string.h>
#include <mgba-util/vfs.h>
#ifdef USE_DEBUGGERS
#include <mgba/debugger/debugger.h>
#endif
#include "lib.h"
static const char* mPythonScriptEngineName(struct mScriptEngine*);
static bool mPythonScriptEngineInit(struct mScriptEngine*, struct mScriptBridge*);
static void mPythonScriptEngineDeinit(struct mScriptEngine*);
static bool mPythonScriptEngineIsScript(struct mScriptEngine*, const char* name, struct VFile* vf);
static bool mPythonScriptEngineLoadScript(struct mScriptEngine*, const char* name, struct VFile* vf);
static void mPythonScriptEngineRun(struct mScriptEngine*);
#ifdef USE_DEBUGGERS
static void mPythonScriptDebuggerEntered(struct mScriptEngine*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*);
#endif
struct mPythonScriptEngine {
struct mScriptEngine d;
struct mScriptBridge* sb;
};
struct mPythonScriptEngine* mPythonCreateScriptEngine(void) {
struct mPythonScriptEngine* engine = malloc(sizeof(*engine));
engine->d.name = mPythonScriptEngineName;
engine->d.init = mPythonScriptEngineInit;
engine->d.deinit = mPythonScriptEngineDeinit;
engine->d.isScript = mPythonScriptEngineIsScript;
engine->d.loadScript = mPythonScriptEngineLoadScript;
engine->d.run = mPythonScriptEngineRun;
#ifdef USE_DEBUGGERS
engine->d.debuggerEntered = mPythonScriptDebuggerEntered;
#endif
engine->sb = NULL;
return engine;
}
void mPythonSetup(struct mScriptBridge* sb) {
struct mPythonScriptEngine* se = mPythonCreateScriptEngine();
mScriptBridgeInstallEngine(sb, &se->d);
}
const char* mPythonScriptEngineName(struct mScriptEngine* se) {
UNUSED(se);
return "python";
}
bool mPythonScriptEngineInit(struct mScriptEngine* se, struct mScriptBridge* sb) {
struct mPythonScriptEngine* engine = (struct mPythonScriptEngine*) se;
engine->sb = sb;
return true;
}
void mPythonScriptEngineDeinit(struct mScriptEngine* se) {
struct mPythonScriptEngine* engine = (struct mPythonScriptEngine*) se;
free(se);
}
bool mPythonScriptEngineIsScript(struct mScriptEngine* se, const char* name, struct VFile* vf) {
UNUSED(se);
UNUSED(vf);
return endswith(name, ".py");
}
bool mPythonScriptEngineLoadScript(struct mScriptEngine* se, const char* name, struct VFile* vf) {
struct mPythonScriptEngine* engine = (struct mPythonScriptEngine*) se;
return mPythonLoadScript(name, vf);
}
void mPythonScriptEngineRun(struct mScriptEngine* se) {
struct mPythonScriptEngine* engine = (struct mPythonScriptEngine*) se;
struct mDebugger* debugger = mScriptBridgeGetDebugger(engine->sb);
if (debugger) {
mPythonSetDebugger(debugger);
}
mPythonRunPending();
}
#ifdef USE_DEBUGGERS
void mPythonScriptDebuggerEntered(struct mScriptEngine* se, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) {
struct mPythonScriptEngine* engine = (struct mPythonScriptEngine*) se;
struct mDebugger* debugger = mScriptBridgeGetDebugger(engine->sb);
if (!debugger) {
return;
}
mPythonDebuggerEntered(reason, info);
}
#endif

View File

@ -0,0 +1,20 @@
/* 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 PYTHON_ENGINE_H
#define PYTHON_ENGINE_H
#include <mgba-util/common.h>
CXX_GUARD_START
struct mScriptBridge;
struct mPythonScriptEngine;
struct mPythonScriptEngine* mPythonCreateScriptEngine(void);
void mPythonSetup(struct mScriptBridge* sb);
CXX_GUARD_END
#endif

11
src/platform/python/lib.h Normal file
View File

@ -0,0 +1,11 @@
#include "flags.h"
struct VFile;
extern bool mPythonLoadScript(const char*, struct VFile*);
extern void mPythonRunPending();
#ifdef USE_DEBUGGERS
extern void mPythonSetDebugger(struct mDebugger*);
extern void mPythonDebuggerEntered(enum mDebuggerEntryReason, struct mDebuggerEntryInfo*);
#endif

View File

@ -3,14 +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/core/log.h>
struct mLoggerPy {
struct mLogger d;
void* pyobj;
};
void _pyLog(void* logger, int category, enum mLogLevel level, const char* message);
#include "log.h"
static void _pyLogShim(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
struct mLoggerPy* pylogger = (struct mLoggerPy*) logger;

View File

@ -5,6 +5,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/core/log.h>
#include "pycommon.h"
struct mLoggerPy {
struct mLogger d;
void* pyobj;

View File

@ -4,7 +4,7 @@
# 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/.
from ._pylib import ffi, lib
from . import tile
from . import tile, createCallback
from cached_property import cached_property
def find(path):
@ -38,10 +38,65 @@ def needsReset(f):
return f(self, *args, **kwargs)
return wrapper
def protected(f):
def wrapper(self, *args, **kwargs):
if self._protected:
raise RuntimeError("Core is protected")
return f(self, *args, **kwargs)
return wrapper
@ffi.def_extern()
def _mCorePythonCallbacksVideoFrameStarted(user):
context = ffi.from_handle(user)
context._videoFrameStarted()
@ffi.def_extern()
def _mCorePythonCallbacksVideoFrameEnded(user):
context = ffi.from_handle(user)
context._videoFrameEnded()
@ffi.def_extern()
def _mCorePythonCallbacksCoreCrashed(user):
context = ffi.from_handle(user)
context._coreCrashed()
@ffi.def_extern()
def _mCorePythonCallbacksSleep(user):
context = ffi.from_handle(user)
context._sleep()
class CoreCallbacks(object):
def __init__(self):
self._handle = ffi.new_handle(self)
self.videoFrameStarted = []
self.videoFrameEnded = []
self.coreCrashed = []
self.sleep = []
self.context = lib.mCorePythonCallbackCreate(self._handle)
def _videoFrameStarted(self):
for cb in self.videoFrameStarted:
cb()
def _videoFrameEnded(self):
for cb in self.videoFrameEnded:
cb()
def _coreCrashed(self):
for cb in self.coreCrashed:
cb()
def _sleep(self):
for cb in self.sleep:
cb()
class Core(object):
def __init__(self, native):
self._core = native
self._wasReset = False
self._protected = False
self._callbacks = CoreCallbacks()
self._core.addCoreCallbacks(self._core, self._callbacks.context)
@cached_property
def tiles(self):
@ -51,17 +106,22 @@ class Core(object):
def _init(cls, native):
core = ffi.gc(native, native.deinit)
success = bool(core.init(core))
lib.mCoreInitConfig(core, ffi.NULL)
if not success:
raise RuntimeError("Failed to initialize core")
return cls._detect(core)
def _deinit(self):
self._core.deinit(self._core)
@classmethod
def _detect(cls, core):
if hasattr(cls, 'PLATFORM_GBA') and core.platform(core) == cls.PLATFORM_GBA:
return GBA(core)
if hasattr(cls, 'PLATFORM_GB') and core.platform(core) == cls.PLATFORM_GB:
return GB(core)
return Core(core)
def _deinit(self):
self._core.deinit(self._core)
def loadFile(self, path):
return bool(lib.mCoreLoadFile(self._core, path.encode('UTF-8')))
@ -103,10 +163,12 @@ class Core(object):
self._wasReset = True
@needsReset
@protected
def runFrame(self):
self._core.runFrame(self._core)
@needsReset
@protected
def runLoop(self):
self._core.runLoop(self._core)
@ -132,26 +194,66 @@ class Core(object):
def clearKeys(self, *args, **kwargs):
self._core.clearKeys(self._core, self._keysToInt(*args, **kwargs))
@property
@needsReset
def frameCounter(self):
return self._core.frameCounter(self._core)
@property
def frameCycles(self):
return self._core.frameCycles(self._core)
@property
def frequency(self):
return self._core.frequency(self._core)
def getGameTitle(self):
@property
def gameTitle(self):
title = ffi.new("char[16]")
self._core.getGameTitle(self._core, title)
return ffi.string(title, 16).decode("ascii")
def getGameCode(self):
@property
def gameCode(self):
code = ffi.new("char[12]")
self._core.getGameCode(self._core, code)
return ffi.string(code, 12).decode("ascii")
def addFrameCallback(self, cb):
self._callbacks.videoFrameEnded.append(cb)
class ICoreOwner(object):
def claim(self):
raise NotImplementedError
def release(self):
raise NotImplementedError
def __enter__(self):
self.core = self.claim()
self.core._protected = True
return self.core
def __exit__(self, type, value, traceback):
self.core._protected = False
self.release()
class IRunner(object):
def pause(self):
raise NotImplementedError
def unpause(self):
raise NotImplementedError
def useCore(self):
raise NotImplementedError
def isRunning(self):
raise NotImplementedError
def isPaused(self):
raise NotImplementedError
if hasattr(lib, 'PLATFORM_GBA'):
from .gba import GBA
Core.PLATFORM_GBA = lib.PLATFORM_GBA

View File

@ -0,0 +1,80 @@
# 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/.
from ._pylib import ffi, lib
from .core import IRunner, ICoreOwner, Core
class DebuggerCoreOwner(ICoreOwner):
def __init__(self, debugger):
self.debugger = debugger
self.wasPaused = False
def claim(self):
if self.debugger.isRunning():
self.wasPaused = True
self.debugger.pause()
return self.debugger._core
def release(self):
if self.wasPaused:
self.debugger.unpause()
class NativeDebugger(IRunner):
WATCHPOINT_WRITE = lib.WATCHPOINT_WRITE
WATCHPOINT_READ = lib.WATCHPOINT_READ
WATCHPOINT_RW = lib.WATCHPOINT_RW
BREAKPOINT_HARDWARE = lib.BREAKPOINT_HARDWARE
BREAKPOINT_SOFTWARE = lib.BREAKPOINT_SOFTWARE
ENTER_MANUAL = lib.DEBUGGER_ENTER_MANUAL
ENTER_ATTACHED = lib.DEBUGGER_ENTER_ATTACHED
ENTER_BREAKPOINT = lib.DEBUGGER_ENTER_BREAKPOINT
ENTER_WATCHPOINT = lib.DEBUGGER_ENTER_WATCHPOINT
ENTER_ILLEGAL_OP = lib.DEBUGGER_ENTER_ILLEGAL_OP
def __init__(self, native):
self._native = native
self._cbs = []
self._core = Core._detect(native.core)
self._core._wasReset = True
def pause(self):
lib.mDebuggerEnter(self._native, lib.DEBUGGER_ENTER_MANUAL, ffi.NULL)
def unpause(self):
self._native.state = lib.DEBUGGER_RUNNING
def isRunning(self):
return self._native.state == lib.DEBUGGER_RUNNING
def isPaused(self):
return self._native.state in (lib.DEBUGGER_PAUSED, lib.DEBUGGER_CUSTOM)
def useCore(self):
return DebuggerCoreOwner(self)
def setBreakpoint(self, address):
if not self._native.platform.setBreakpoint:
raise RuntimeError("Platform does not support breakpoints")
self._native.platform.setBreakpoint(self._native.platform, address)
def clearBreakpoint(self, address):
if not self._native.platform.setBreakpoint:
raise RuntimeError("Platform does not support breakpoints")
self._native.platform.clearBreakpoint(self._native.platform, address)
def setWatchpoint(self, address):
if not self._native.platform.setWatchpoint:
raise RuntimeError("Platform does not support watchpoints")
self._native.platform.setWatchpoint(self._native.platform, address)
def clearWatchpoint(self, address):
if not self._native.platform.clearWatchpoint:
raise RuntimeError("Platform does not support watchpoints")
self._native.platform.clearWatchpoint(self._native.platform, address)
def addCallback(self, cb):
self._cbs.append(cb)

View File

@ -0,0 +1,58 @@
# 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/.
from ._pylib import ffi, lib
from .core import IRunner, ICoreOwner, Core
class ThreadCoreOwner(ICoreOwner):
def __init__(self, thread):
self.thread = thread
def claim(self):
if not self.thread.isRunning():
raise ValueError
lib.mCoreThreadInterrupt(self.thread._native)
return self.thread._core
def release(self):
lib.mCoreThreadContinue(self.thread._native)
class Thread(IRunner):
def __init__(self, native=None):
if native:
self._native = native
self._core = Core(native.core)
self._core._wasReset = lib.mCoreThreadHasStarted(self._native)
else:
self._native = ffi.new("struct mCoreThread*")
def start(self, core):
if lib.mCoreThreadHasStarted(self._native):
raise ValueError
self._core = core
self._native.core = core._core
lib.mCoreThreadStart(self._native)
self._core._wasReset = lib.mCoreThreadHasStarted(self._native)
def end(self):
if not lib.mCoreThreadHasStarted(self._native):
raise ValueError
lib.mCoreThreadEnd(self._native)
lib.mCoreThreadJoin(self._native)
def pause(self):
lib.mCoreThreadPause(self._native)
def unpause(self):
lib.mCoreThreadUnpause(self._native)
def isRunning(self):
return bool(lib.mCoreThreadIsActive(self._native))
def isPaused(self):
return bool(lib.mCoreThreadIsPaused(self._native))
def useCore(self):
return ThreadCoreOwner(self)

View File

@ -108,6 +108,13 @@ class VFile:
def read(self, buffer, size):
return self.handle.read(self.handle, buffer, size)
def readAll(self, size=0):
if not size:
size = self.size()
buffer = ffi.new("char[%i]" % size)
size = self.handle.read(self.handle, buffer, size)
return ffi.unpack(buffer, size)
def readline(self, buffer, size):
return self.handle.readline(self.handle, buffer, size)

View File

@ -0,0 +1,15 @@
/* 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 PYTHON_COMMON_H
#define PYTHON_COMMON_H
#include <mgba-util/common.h>
#ifndef PYEXPORT
#define PYEXPORT extern
#endif
#endif

View File

@ -3,22 +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-util/vfs.h>
struct VFilePy {
struct VFile d;
void* fileobj;
};
bool _vfpClose(struct VFile* vf);
off_t _vfpSeek(struct VFile* vf, off_t offset, int whence);
ssize_t _vfpRead(struct VFile* vf, void* buffer, size_t size);
ssize_t _vfpWrite(struct VFile* vf, const void* buffer, size_t size);
void* _vfpMap(struct VFile* vf, size_t size, int flags);
void _vfpUnmap(struct VFile* vf, void* memory, size_t size);
void _vfpTruncate(struct VFile* vf, size_t size);
ssize_t _vfpSize(struct VFile* vf);
bool _vfpSync(struct VFile* vf, const void* buffer, size_t size);
#include "vfs-py.h"
struct VFile* VFileFromPython(void* fileobj) {
if (!fileobj) {

View File

@ -3,9 +3,10 @@
* 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-util/vfs.h>
#include "pycommon.h"
struct VFilePy {
struct VFile d;
void* fileobj;

View File

@ -26,13 +26,13 @@ void AudioDevice::setFormat(const QAudioFormat& format) {
LOG(QT, INFO) << tr("Can't set format of context-less audio device");
return;
}
double fauxClock = GBAAudioCalculateRatio(1, m_context->sync.fpsTarget, 1);
mCoreSyncLockAudio(&m_context->sync);
double fauxClock = GBAAudioCalculateRatio(1, m_context->impl->sync.fpsTarget, 1);
mCoreSyncLockAudio(&m_context->impl->sync);
blip_set_rates(m_context->core->getAudioChannel(m_context->core, 0),
m_context->core->frequency(m_context->core), format.sampleRate() * fauxClock);
blip_set_rates(m_context->core->getAudioChannel(m_context->core, 1),
m_context->core->frequency(m_context->core), format.sampleRate() * fauxClock);
mCoreSyncUnlockAudio(&m_context->sync);
mCoreSyncUnlockAudio(&m_context->impl->sync);
}
void AudioDevice::setInput(mCoreThread* input) {
@ -49,14 +49,14 @@ qint64 AudioDevice::readData(char* data, qint64 maxSize) {
return 0;
}
mCoreSyncLockAudio(&m_context->sync);
mCoreSyncLockAudio(&m_context->impl->sync);
int available = blip_samples_avail(m_context->core->getAudioChannel(m_context->core, 0));
if (available > maxSize / sizeof(GBAStereoSample)) {
available = maxSize / sizeof(GBAStereoSample);
}
blip_read_samples(m_context->core->getAudioChannel(m_context->core, 0), &reinterpret_cast<GBAStereoSample*>(data)->left, available, true);
blip_read_samples(m_context->core->getAudioChannel(m_context->core, 1), &reinterpret_cast<GBAStereoSample*>(data)->right, available, true);
mCoreSyncConsumeAudio(&m_context->sync);
mCoreSyncConsumeAudio(&m_context->impl->sync);
return available * sizeof(GBAStereoSample);
}

View File

@ -214,7 +214,9 @@ if(NOT DEFINED DATADIR)
set(DATADIR ${CMAKE_INSTALL_DATADIR}/${BINARY_NAME})
endif()
endif()
install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/shaders DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt)
if(BUILD_GL OR BUILD_GLES2)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/shaders DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt)
endif()
install(FILES ${CMAKE_SOURCE_DIR}/res/nointro.dat DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt)
if(NOT WIN32 AND NOT APPLE)
list(APPEND QT_DEFINES DATADIR="${CMAKE_INSTALL_PREFIX}/${DATADIR}")
@ -230,8 +232,20 @@ if(Qt5LinguistTools_FOUND)
else()
qt5_add_translation(TRANSLATION_FILES ${TS_FILES})
endif()
set(QT_QM_FILES)
if(QT_STATIC)
get_target_property(QT_CORE_LOCATION Qt5::Core LOCATION)
get_filename_component(QT_CORE_LOCATION ${QT_CORE_LOCATION} DIRECTORY)
get_filename_component(QT_QM_LOCATION "${QT_CORE_LOCATION}/../translations" ABSOLUTE)
foreach(TS ${TS_FILES})
get_filename_component(TS ${TS} NAME)
string(REGEX REPLACE "${BINARY_NAME}-(.*).ts$" "qtbase_\\1.qm" QT_QM "${TS}")
list(APPEND QT_QM_FILES "${QT_QM_LOCATION}/${QT_QM}")
endforeach()
list(APPEND TRANSLATION_FILES ${QT_QM_FILES})
endif()
add_custom_command(OUTPUT ${TRANSLATION_QRC}
COMMAND ${CMAKE_COMMAND} -DTRANSLATION_QRC:FILEPATH="${TRANSLATION_QRC}" -DQM_BASE="${CMAKE_CURRENT_BINARY_DIR}" -P "${CMAKE_CURRENT_SOURCE_DIR}/ts.cmake"
COMMAND ${CMAKE_COMMAND} -DTRANSLATION_QRC:FILEPATH="${TRANSLATION_QRC}" -DQM_BASE="${CMAKE_CURRENT_BINARY_DIR}" "-DTRANSLATION_FILES='${TRANSLATION_FILES}'" -P "${CMAKE_CURRENT_SOURCE_DIR}/ts.cmake"
DEPENDS ${TRANSLATION_FILES})
qt5_add_resources(TRANSLATION_RESOURCES ${TRANSLATION_QRC})
list(APPEND RESOURCES ${TRANSLATION_RESOURCES})
@ -250,7 +264,7 @@ target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${BINARY_NAME} ${QT_
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}" PARENT_SCOPE)
install(TARGETS ${BINARY_NAME}-qt
RUNTIME DESTINATION bin COMPONENT ${BINARY_NAME}-qt
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-qt
BUNDLE DESTINATION Applications COMPONENT ${BINARY_NAME}-qt)
if(UNIX AND NOT APPLE)
find_program(DESKTOP_FILE_INSTALL desktop-file-install)
@ -265,7 +279,6 @@ if(APPLE OR WIN32)
set_target_properties(${BINARY_NAME}-qt PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
endif()
if(APPLE)
message(STATUS ${CMAKE_SYSTEM_NAME})
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
get_target_property(QTCOCOA Qt5::QCocoaIntegrationPlugin LOCATION)
get_target_property(COREAUDIO Qt5::CoreAudioPlugin LOCATION)

View File

@ -69,7 +69,7 @@ void DisplayGL::startDrawing(mCoreThread* thread) {
m_painter->moveToThread(m_drawThread);
connect(m_drawThread, &QThread::started, m_painter, &PainterGL::start);
m_drawThread->start();
mCoreSyncSetVideoSync(&m_context->sync, false);
mCoreSyncSetVideoSync(&m_context->impl->sync, false);
lockAspectRatio(isAspectRatioLocked());
lockIntegerScaling(isIntegerScalingLocked());
@ -338,9 +338,9 @@ void PainterGL::draw() {
return;
}
if (mCoreSyncWaitFrameStart(&m_context->sync) || !m_queue.isEmpty()) {
if (mCoreSyncWaitFrameStart(&m_context->impl->sync) || !m_queue.isEmpty()) {
dequeue();
mCoreSyncWaitFrameEnd(&m_context->sync);
mCoreSyncWaitFrameEnd(&m_context->impl->sync);
m_painter.begin(m_gl->context()->device());
performDraw();
m_painter.end();
@ -354,7 +354,7 @@ void PainterGL::draw() {
m_delayTimer.restart();
}
} else {
mCoreSyncWaitFrameEnd(&m_context->sync);
mCoreSyncWaitFrameEnd(&m_context->impl->sync);
}
if (!m_queue.isEmpty()) {
QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection);

View File

@ -51,7 +51,8 @@ void DisplayQt::framePosted(const uint32_t* buffer) {
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), m_width, m_height, QImage::Format_RGB555);
#endif
#else
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), m_width, m_height, QImage::Format_RGB32);
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), m_width, m_height, QImage::Format_ARGB32);
m_backing = m_backing.convertToFormat(QImage::Format_RGB32);
#endif
}

View File

@ -79,7 +79,7 @@ GameController::GameController(QObject* parent)
default:
break;
}
controller->m_fpsTarget = context->sync.fpsTarget;
controller->m_fpsTarget = context->impl->sync.fpsTarget;
if (controller->m_override) {
controller->m_override->identify(context->core);
@ -126,7 +126,6 @@ GameController::GameController(QObject* parent)
if (controller->m_multiplayer) {
controller->m_multiplayer->detachGame(controller);
}
controller->m_patch = QString();
controller->clearOverride();
controller->endVideoLog();
@ -300,8 +299,8 @@ void GameController::setConfig(const mCoreConfig* config) {
if (isLoaded()) {
Interrupter interrupter(this);
mCoreLoadForeignConfig(m_threadContext.core, config);
m_audioSync = m_threadContext.sync.audioWait;
m_videoSync = m_threadContext.sync.videoFrameWait;
m_audioSync = m_threadContext.impl->sync.audioWait;
m_videoSync = m_threadContext.impl->sync.videoFrameWait;
m_audioProcessor->setInput(&m_threadContext);
}
}
@ -411,13 +410,6 @@ void GameController::openGame(bool biosOnly) {
m_pauseAfterFrame = false;
if (m_turbo) {
m_threadContext.sync.videoFrameWait = false;
m_threadContext.sync.audioWait = false;
} else {
m_threadContext.sync.videoFrameWait = m_videoSync;
m_threadContext.sync.audioWait = m_audioSync;
}
m_threadContext.core->init(m_threadContext.core);
mCoreInitConfig(m_threadContext.core, nullptr);
@ -474,6 +466,7 @@ void GameController::openGame(bool biosOnly) {
m_threadContext.core->loadPatch(m_threadContext.core, patch);
}
patch->close(patch);
m_patch = QString();
} else {
mCoreAutoloadPatch(m_threadContext.core);
}
@ -483,6 +476,13 @@ void GameController::openGame(bool biosOnly) {
if (!mCoreThreadStart(&m_threadContext)) {
emit gameFailed();
}
if (m_turbo) {
m_threadContext.impl->sync.videoFrameWait = false;
m_threadContext.impl->sync.audioWait = false;
} else {
m_threadContext.impl->sync.videoFrameWait = m_videoSync;
m_threadContext.impl->sync.audioWait = m_audioSync;
}
}
void GameController::loadBIOS(int platform, const QString& path) {
@ -543,12 +543,10 @@ void GameController::replaceGame(const QString& path) {
}
void GameController::loadPatch(const QString& path) {
m_patch = path;
if (m_gameOpen) {
closeGame();
m_patch = path;
openGame();
} else {
m_patch = path;
}
}
@ -713,14 +711,14 @@ void GameController::setRewind(bool enable, int capacity, bool rewindSave) {
if (m_gameOpen) {
Interrupter interrupter(this);
if (m_threadContext.core->opts.rewindEnable && m_threadContext.core->opts.rewindBufferCapacity > 0) {
mCoreRewindContextDeinit(&m_threadContext.rewind);
mCoreRewindContextDeinit(&m_threadContext.impl->rewind);
}
m_threadContext.core->opts.rewindEnable = enable;
m_threadContext.core->opts.rewindBufferCapacity = capacity;
m_threadContext.core->opts.rewindSave = rewindSave;
if (enable && capacity > 0) {
mCoreRewindContextInit(&m_threadContext.rewind, capacity, true);
m_threadContext.rewind.stateFlags = rewindSave ? SAVESTATE_SAVEDATA : 0;
mCoreRewindContextInit(&m_threadContext.impl->rewind, capacity, true);
m_threadContext.impl->rewind.stateFlags = rewindSave ? SAVESTATE_SAVEDATA : 0;
}
}
}
@ -731,7 +729,7 @@ void GameController::rewind(int states) {
states = INT_MAX;
}
for (int i = 0; i < states; ++i) {
if (!mCoreRewindRestore(&m_threadContext.rewind, m_threadContext.core)) {
if (!mCoreRewindRestore(&m_threadContext.impl->rewind, m_threadContext.core)) {
break;
}
}
@ -873,8 +871,10 @@ void GameController::startAudio() {
// Don't freeze!
m_audioSync = false;
m_videoSync = true;
m_threadContext.sync.audioWait = false;
m_threadContext.sync.videoFrameWait = true;
if (isLoaded()) {
m_threadContext.impl->sync.audioWait = false;
m_threadContext.impl->sync.videoFrameWait = true;
}
}
}
@ -895,9 +895,11 @@ void GameController::setVideoLayerEnabled(int layer, bool enable) {
void GameController::setFPSTarget(float fps) {
Interrupter interrupter(this);
m_fpsTarget = fps;
m_threadContext.sync.fpsTarget = fps;
if (m_turbo && m_turboSpeed > 0) {
m_threadContext.sync.fpsTarget *= m_turboSpeed;
if (isLoaded()) {
m_threadContext.impl->sync.fpsTarget = fps;
if (m_turbo && m_turboSpeed > 0) {
m_threadContext.impl->sync.fpsTarget *= m_turboSpeed;
}
}
if (m_audioProcessor) {
redoSamples(m_audioProcessor->getBufferSamples());
@ -1015,22 +1017,25 @@ void GameController::setTurboSpeed(float ratio) {
void GameController::enableTurbo() {
Interrupter interrupter(this);
if (!isLoaded()) {
return;
}
bool shouldRedoSamples = false;
if (!m_turbo) {
shouldRedoSamples = m_threadContext.sync.fpsTarget != m_fpsTarget;
m_threadContext.sync.fpsTarget = m_fpsTarget;
m_threadContext.sync.audioWait = m_audioSync;
m_threadContext.sync.videoFrameWait = m_videoSync;
shouldRedoSamples = m_threadContext.impl->sync.fpsTarget != m_fpsTarget;
m_threadContext.impl->sync.fpsTarget = m_fpsTarget;
m_threadContext.impl->sync.audioWait = m_audioSync;
m_threadContext.impl->sync.videoFrameWait = m_videoSync;
} else if (m_turboSpeed <= 0) {
shouldRedoSamples = m_threadContext.sync.fpsTarget != m_fpsTarget;
m_threadContext.sync.fpsTarget = m_fpsTarget;
m_threadContext.sync.audioWait = false;
m_threadContext.sync.videoFrameWait = false;
shouldRedoSamples = m_threadContext.impl->sync.fpsTarget != m_fpsTarget;
m_threadContext.impl->sync.fpsTarget = m_fpsTarget;
m_threadContext.impl->sync.audioWait = false;
m_threadContext.impl->sync.videoFrameWait = false;
} else {
shouldRedoSamples = m_threadContext.sync.fpsTarget != m_fpsTarget * m_turboSpeed;
m_threadContext.sync.fpsTarget = m_fpsTarget * m_turboSpeed;
m_threadContext.sync.audioWait = true;
m_threadContext.sync.videoFrameWait = false;
shouldRedoSamples = m_threadContext.impl->sync.fpsTarget != m_fpsTarget * m_turboSpeed;
m_threadContext.impl->sync.fpsTarget = m_fpsTarget * m_turboSpeed;
m_threadContext.impl->sync.audioWait = true;
m_threadContext.impl->sync.videoFrameWait = false;
}
if (m_audioProcessor && shouldRedoSamples) {
redoSamples(m_audioProcessor->getBufferSamples());
@ -1040,24 +1045,30 @@ void GameController::enableTurbo() {
void GameController::setSync(bool enable) {
m_turbo = false;
m_turboForced = false;
if (!enable) {
m_threadContext.sync.audioWait = false;
m_threadContext.sync.videoFrameWait = false;
} else {
m_threadContext.sync.audioWait = m_audioSync;
m_threadContext.sync.videoFrameWait = m_videoSync;
if (isLoaded()) {
if (!enable) {
m_threadContext.impl->sync.audioWait = false;
m_threadContext.impl->sync.videoFrameWait = false;
} else {
m_threadContext.impl->sync.audioWait = m_audioSync;
m_threadContext.impl->sync.videoFrameWait = m_videoSync;
}
}
m_sync = enable;
}
void GameController::setAudioSync(bool enable) {
m_audioSync = enable;
m_threadContext.sync.audioWait = enable;
if (isLoaded()) {
m_threadContext.impl->sync.audioWait = enable;
}
}
void GameController::setVideoSync(bool enable) {
m_videoSync = enable;
m_threadContext.sync.videoFrameWait = enable;
if (isLoaded()) {
m_threadContext.impl->sync.videoFrameWait = enable;
}
}
void GameController::setAVStream(mAVStream* stream) {

View File

@ -20,7 +20,7 @@ GamepadAxisEvent::GamepadAxisEvent(int axis, Direction direction, bool isNew, in
, m_key(GBA_KEY_NONE)
{
ignore();
if (controller) {
if (controller && controller->map()) {
m_key = static_cast<GBAKey>(mInputMapAxis(controller->map(), type, axis, direction * INT_MAX));
}
}

View File

@ -19,7 +19,7 @@ GamepadButtonEvent::GamepadButtonEvent(QEvent::Type pressType, int button, int t
, m_key(GBA_KEY_NONE)
{
ignore();
if (controller) {
if (controller && controller->map()) {
m_key = static_cast<GBAKey>(mInputMapKey(controller->map(), type, button));
}
}

View File

@ -20,7 +20,7 @@ GamepadHatEvent::GamepadHatEvent(QEvent::Type pressType, int hatId, Direction di
, m_key(GBA_KEY_NONE)
{
ignore();
if (controller) {
if (controller && controller->map()) {
m_key = static_cast<GBAKey>(mInputMapHat(controller->map(), type, hatId, direction));
}
}

View File

@ -10,6 +10,7 @@
#include "Display.h"
#include "GBAApp.h"
#include "InputController.h"
#include "ShaderSelector.h"
#include "ShortcutView.h"
#include <mgba/core/serialize.h>
@ -158,7 +159,7 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
m_ui.languages->setItemData(0, QLocale("en"));
QDir ts(":/translations/");
for (auto name : ts.entryList()) {
if (!name.endsWith(".qm")) {
if (!name.endsWith(".qm") || !name.startsWith(binaryName)) {
continue;
}
QLocale locale(name.remove(QString("%0-").arg(binaryName)).remove(".qm"));
@ -180,6 +181,24 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
m_ui.tabs->addItem(tr("Shortcuts"));
}
SettingsView::~SettingsView() {
#if defined(BUILD_GL) || defined(BUILD_GLES)
if (m_shader) {
m_ui.stackedWidget->removeWidget(m_shader);
m_shader->setParent(nullptr);
}
#endif
}
void SettingsView::setShaderSelector(ShaderSelector* shaderSelector) {
#if defined(BUILD_GL) || defined(BUILD_GLES)
m_shader = shaderSelector;
m_ui.stackedWidget->addWidget(m_shader);
m_ui.tabs->addItem(tr("Shaders"));
connect(m_ui.buttonBox, &QDialogButtonBox::accepted, m_shader, &ShaderSelector::saved);
#endif
}
void SettingsView::selectBios(QLineEdit* bios) {
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select BIOS"));
if (!filename.isEmpty()) {
@ -315,6 +334,8 @@ void SettingsView::reloadConfig() {
loadSetting("showLibrary", m_ui.showLibrary);
loadSetting("preload", m_ui.preload);
m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt());
double fastForwardRatio = loadSetting("fastForwardRatio").toDouble();
if (fastForwardRatio <= 0) {
m_ui.fastForwardUnbounded->setChecked(true);

View File

@ -18,12 +18,16 @@ class ConfigController;
class InputController;
class InputIndex;
class ShortcutView;
class ShaderSelector;
class SettingsView : public QDialog {
Q_OBJECT
public:
SettingsView(ConfigController* controller, InputController* inputController, QWidget* parent = nullptr);
~SettingsView();
void setShaderSelector(ShaderSelector* shaderSelector);
signals:
void biosLoaded(int platform, const QString&);
@ -45,6 +49,7 @@ private:
InputController* m_input;
ShortcutView* m_shortcutView;
ShortcutView* m_keyView;
ShaderSelector* m_shader = nullptr;
void saveSetting(const char* key, const QAbstractButton*);
void saveSetting(const char* key, const QComboBox*);

View File

@ -21,6 +21,8 @@
#include <mgba-util/vfs.h>
#include "platform/video-backend.h"
#if defined(BUILD_GL) || defined(BUILD_GLES)
#if !defined(_WIN32) || defined(USE_EPOXY)
#include "platform/opengl/gles2.h"
#endif
@ -39,6 +41,9 @@ ShaderSelector::ShaderSelector(Display* display, ConfigController* config, QWidg
connect(m_ui.load, &QAbstractButton::clicked, this, &ShaderSelector::selectShader);
connect(m_ui.unload, &QAbstractButton::clicked, this, &ShaderSelector::clearShader);
connect(m_ui.buttonBox, &QDialogButtonBox::clicked, this, &ShaderSelector::buttonPressed);
connect(this, &ShaderSelector::saved, [this]() {
m_config->setOption("shader", m_shaderPath);
});
}
ShaderSelector::~ShaderSelector() {
@ -59,7 +64,7 @@ void ShaderSelector::clear() {
void ShaderSelector::selectShader() {
QString path(GBAApp::dataDir());
path += QLatin1String("/shaders");
QFileDialog dialog(nullptr, tr("Load shader"), path, tr("%1 Shader (%.shader)").arg(projectName));
QFileDialog dialog(nullptr, tr("Load shader"), path);
dialog.setFileMode(QFileDialog::Directory);
dialog.exec();
QStringList names = dialog.selectedFiles();
@ -86,7 +91,6 @@ void ShaderSelector::clearShader() {
m_display->clearShaders();
refreshShaders();
m_shaderPath = "";
m_config->setOption("shader", nullptr);
}
void ShaderSelector::refreshShaders() {
@ -115,6 +119,10 @@ void ShaderSelector::refreshShaders() {
disconnect(this, &ShaderSelector::reset, 0, 0);
disconnect(this, &ShaderSelector::resetToDefault, 0, 0);
connect(this, &ShaderSelector::saved, [this]() {
m_config->setOption("shader", m_shaderPath);
});
#if !defined(_WIN32) || defined(USE_EPOXY)
if (m_shaders->preprocessShader) {
m_ui.passes->addTab(makePage(static_cast<mGLES2Shader*>(m_shaders->preprocessShader), "default", 0), tr("Preprocessing"));
@ -264,7 +272,6 @@ void ShaderSelector::buttonPressed(QAbstractButton* button) {
emit reset();
break;
case QDialogButtonBox::Ok:
m_config->setOption("shader", m_shaderPath);
emit saved();
close();
break;
@ -275,3 +282,5 @@ void ShaderSelector::buttonPressed(QAbstractButton* button) {
break;
}
}
#endif

View File

@ -6,6 +6,8 @@
#ifndef QGBA_SHADER_SELECTOR_H
#define QGBA_SHADER_SELECTOR_H
#if defined(BUILD_GL) || defined(BUILD_GLES)
#include <QDialog>
#include "ui_ShaderSelector.h"
@ -56,3 +58,5 @@ private:
}
#endif
#endif

View File

@ -81,33 +81,36 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QPushButton" name="unload">
<property name="text">
<string>Unload Shader</string>
</property>
</widget>
</item>
<item>
<item row="0" column="1">
<widget class="QPushButton" name="load">
<property name="text">
<string>Load New Shader</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Reset|QDialogButtonBox::RestoreDefaults</set>
</property>
<property name="centerButtons">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok|QDialogButtonBox::Reset|QDialogButtonBox::RestoreDefaults</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>

View File

@ -100,7 +100,9 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
updateTitle();
m_display = Display::create(this);
#if defined(BUILD_GL) || defined(BUILD_GLES)
m_shaderView = new ShaderSelector(m_display, m_config);
#endif
m_logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio());
m_logo = m_logo; // Free memory left over in old pixmap
@ -199,7 +201,6 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
connect(this, &Window::shutdown, m_display, &Display::stopDrawing);
connect(this, &Window::shutdown, m_controller, &GameController::closeGame);
connect(this, &Window::shutdown, m_logView, &QWidget::hide);
connect(this, &Window::shutdown, m_shaderView, &QWidget::hide);
connect(this, &Window::audioBufferSamplesChanged, m_controller, &GameController::setAudioBufferSamples);
connect(this, &Window::sampleRateChanged, m_controller, &GameController::setAudioSampleRate);
connect(this, &Window::fpsTargetChanged, m_controller, &GameController::setFPSTarget);
@ -308,6 +309,7 @@ void Window::loadConfig() {
enterFullScreen();
}
#if defined(BUILD_GL) || defined(BUILD_GLES)
if (opts->shader) {
struct VDir* shader = VDirOpen(opts->shader);
if (shader) {
@ -316,6 +318,7 @@ void Window::loadConfig() {
shader->close(shader);
}
}
#endif
m_mruFiles = m_config->getMRU();
updateMRU();
@ -509,6 +512,11 @@ void Window::exportSharkport() {
void Window::openSettingsWindow() {
SettingsView* settingsWindow = new SettingsView(m_config, &m_inputController);
#if defined(BUILD_GL) || defined(BUILD_GLES)
if (m_display->supportsShaders()) {
settingsWindow->setShaderSelector(m_shaderView);
}
#endif
connect(settingsWindow, &SettingsView::biosLoaded, m_controller, &GameController::loadBIOS);
connect(settingsWindow, &SettingsView::audioDriverChanged, m_controller, &GameController::reloadAudioDriver);
connect(settingsWindow, &SettingsView::displayDriverChanged, this, &Window::mustRestart);
@ -763,14 +771,9 @@ void Window::toggleFullScreen() {
}
void Window::gameStarted(mCoreThread* context, const QString& fname) {
MutexLock(&context->stateMutex);
if (context->state < THREAD_EXITING) {
emit startDrawing(context);
} else {
MutexUnlock(&context->stateMutex);
if (!mCoreThreadIsActive(context)) {
return;
}
MutexUnlock(&context->stateMutex);
int platform = 1 << context->core->platform(context->core);
#ifdef M_CORE_DS
if ((platform & SUPPORT_DS) && (!m_config->getOption("useBios").toInt() || m_config->getOption("ds.bios7").isNull() || m_config->getOption("ds.bios9").isNull() || m_config->getOption("ds.firmware").isNull())) {
@ -783,6 +786,7 @@ void Window::gameStarted(mCoreThread* context, const QString& fname) {
return;
}
#endif
emit startDrawing(context);
for (QAction* action : m_gameActions) {
action->setDisabled(false);
}
@ -887,6 +891,7 @@ void Window::gameCrashed(const QString& errorMessage) {
QMessageBox::Ok, this, Qt::Sheet);
crash->setAttribute(Qt::WA_DeleteOnClose);
crash->show();
connect(m_controller, &GameController::gameStarted, crash, &QWidget::close);
}
void Window::gameFailed() {
@ -895,6 +900,7 @@ void Window::gameFailed() {
QMessageBox::Ok, this, Qt::Sheet);
fail->setAttribute(Qt::WA_DeleteOnClose);
fail->show();
connect(m_controller, &GameController::gameStarted, fail, &QWidget::close);
}
void Window::unimplementedBiosCall(int call) {
@ -1365,13 +1371,6 @@ void Window::setupMenu(QMenuBar* menubar) {
}
m_config->updateOption("frameskip");
QAction* shaderView = new QAction(tr("Shader options..."), avMenu);
connect(shaderView, &QAction::triggered, m_shaderView, &QWidget::show);
if (!m_display->supportsShaders()) {
shaderView->setEnabled(false);
}
addControlledAction(avMenu, shaderView, "shaderSelector");
avMenu->addSeparator();
ConfigOption* mute = m_config->addOption("mute");

View File

@ -375,6 +375,9 @@ void InputController::updateJoysticks() {
}
const mInputMap* InputController::map() {
if (!m_activeKeyInfo) {
return nullptr;
}
return &m_inputMap;
}

View File

@ -120,8 +120,14 @@ QVariant InputModel::headerData(int section, Qt::Orientation orientation, int ro
QModelIndex InputModel::index(int row, int column, const QModelIndex& parent) const {
if (parent.isValid()) {
InputModelItem* p = static_cast<InputModelItem*>(parent.internalPointer());
if (row >= m_tree[p->obj].count()) {
return QModelIndex();
}
return createIndex(row, column, const_cast<InputModelItem*>(&m_tree[p->obj][row]));
}
if (row >= m_topLevelMenus.count()) {
return QModelIndex();
}
return createIndex(row, column, const_cast<InputModelItem*>(&m_topLevelMenus[row]));
}

View File

@ -30,7 +30,7 @@ Q_IMPORT_PLUGIN(QWindowsAudioPlugin);
using namespace QGBA;
int main(int argc, char* argv[]) {
#ifdef BUILD_SDL
#if defined(BUILD_SDL) && SDL_VERSION_ATLEAST(2, 0, 0)
SDL_SetMainReady();
#endif
@ -58,6 +58,12 @@ int main(int argc, char* argv[]) {
qtTranslator.load(locale, "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath));
application.installTranslator(&qtTranslator);
#ifdef QT_STATIC
QTranslator qtStaticTranslator;
qtStaticTranslator.load(locale, "qtbase", "_", ":/translations/");
application.installTranslator(&qtStaticTranslator);
#endif
QTranslator langTranslator;
langTranslator.load(locale, binaryName, "-", ":/translations/");
application.installTranslator(&langTranslator);

View File

@ -1,7 +1,6 @@
file(GLOB TRANSLATION_FILES "${QM_BASE}/*.qm")
file(WRITE ${TRANSLATION_QRC} "<RCC>\n\t<qresource prefix=\"/translations/\">\n")
foreach(TS ${TRANSLATION_FILES})
get_filename_component(TS_BASE "${TS}" NAME)
file(APPEND ${TRANSLATION_QRC} "\t\t<file>${TS_BASE}</file>\n")
file(APPEND ${TRANSLATION_QRC} "\t\t<file alias=\"${TS_BASE}\">${TS}</file>\n")
endforeach()
file(APPEND ${TRANSLATION_QRC} "\t</qresource>\n</RCC>")

View File

@ -40,9 +40,9 @@
</message>
<message>
<location filename="../AboutScreen.ui" line="86"/>
<source>© 2013 2016 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0
<source>© 2013 2017 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0
Game Boy and Game Boy Advance are registered trademarks of Nintendo Co., Ltd.</source>
<translation>© 2013 2016 Jeffrey Pfau, lizenziert unter der Mozilla Public License, Version 2.0
<translation>© 2013 2017 Jeffrey Pfau, lizenziert unter der Mozilla Public License, Version 2.0
Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., Ltd.</translation>
</message>
<message>
@ -458,8 +458,8 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L
<name>MemorySearch</name>
<message>
<location filename="../MemorySearch.ui" line="20"/>
<source>Form</source>
<translation>Eingabemaske</translation>
<source>Memory Search</source>
<translation>Speicher durchsuchen</translation>
</message>
<message>
<location filename="../MemorySearch.ui" line="45"/>
@ -1146,28 +1146,28 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L
<context>
<name>QGBA::GameController</name>
<message>
<location filename="../GameController.cpp" line="351"/>
<location filename="../GameController.cpp" line="535"/>
<location filename="../GameController.cpp" line="352"/>
<location filename="../GameController.cpp" line="536"/>
<source>Failed to open game file: %1</source>
<translation>Fehler beim Öffnen der Spieldatei: %1</translation>
</message>
<message>
<location filename="../GameController.cpp" line="507"/>
<location filename="../GameController.cpp" line="508"/>
<source>Failed to open save file: %1</source>
<translation>Fehler beim Öffnen der Speicherdatei: %1</translation>
</message>
<message>
<location filename="../GameController.cpp" line="564"/>
<location filename="../GameController.cpp" line="565"/>
<source>Failed to open snapshot file for reading: %1</source>
<translation>Konnte Snapshot-Datei %1 nicht zum Lesen öffnen</translation>
</message>
<message>
<location filename="../GameController.cpp" line="584"/>
<location filename="../GameController.cpp" line="585"/>
<source>Failed to open snapshot file for writing: %1</source>
<translation>Konnte Snapshot-Datei %1 nicht zum Schreiben öffnen</translation>
</message>
<message>
<location filename="../GameController.cpp" line="850"/>
<location filename="../GameController.cpp" line="851"/>
<source>Failed to start audio processor</source>
<translation>Fehler beim Starten des Audio-Prozessors</translation>
</message>
@ -3285,7 +3285,7 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L
<message>
<location filename="../Window.cpp" line="1105"/>
<source>Sh&amp;utdown</source>
<translation>B&amp;eenden</translation>
<translation>Schli&amp;en</translation>
</message>
<message>
<location filename="../Window.cpp" line="1111"/>
@ -3935,7 +3935,7 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L
<message>
<location filename="../SettingsView.ui" line="173"/>
<source>Sample rate:</source>
<translation>Sample-Rate:</translation>
<translation>Abtastrate:</translation>
</message>
<message>
<location filename="../SettingsView.ui" line="185"/>

File diff suppressed because it is too large Load Diff

View File

@ -86,11 +86,17 @@ else()
endif()
endif()
if(ENABLE_SCRIPTING)
if(BUILD_PYTHON)
list(APPEND PLATFORM_LIBRARY "${PYTHON_LIBRARY}")
endif()
endif()
add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC})
set_target_properties(${BINARY_NAME}-sdl PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES}")
target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY})
set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME})
install(TARGETS ${BINARY_NAME}-sdl DESTINATION bin COMPONENT ${BINARY_NAME}-sdl)
install(TARGETS ${BINARY_NAME}-sdl DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-sdl)
if(UNIX)
install(FILES ${CMAKE_SOURCE_DIR}/doc/${BINARY_NAME}.6 DESTINATION ${MANDIR}/man6 COMPONENT ${BINARY_NAME}-sdl)
endif()

View File

@ -53,7 +53,7 @@ void mSDLGLRunloop(struct mSDLRenderer* renderer, void* user) {
SDL_Event event;
struct VideoBackend* v = &renderer->gl.d;
while (context->state < THREAD_EXITING) {
while (mCoreThreadIsActive(context)) {
while (SDL_PollEvent(&event)) {
mSDLHandleEvent(context, &renderer->player, &event);
#if SDL_VERSION_ATLEAST(2, 0, 0)
@ -66,10 +66,10 @@ void mSDLGLRunloop(struct mSDLRenderer* renderer, void* user) {
#endif
}
if (mCoreSyncWaitFrameStart(&context->sync)) {
if (mCoreSyncWaitFrameStart(&context->impl->sync)) {
v->postFrame(v, renderer->outputBuffer);
}
mCoreSyncWaitFrameEnd(&context->sync);
mCoreSyncWaitFrameEnd(&context->impl->sync);
v->drawFrame(v);
v->swap(v);
}

View File

@ -13,6 +13,13 @@
#ifdef USE_EDITLINE
#include "feature/editline/cli-el-backend.h"
#endif
#ifdef ENABLE_SCRIPTING
#include <mgba/core/scripting.h>
#ifdef ENABLE_PYTHON
#include "platform/python/engine.h"
#endif
#endif
#include <mgba/core/core.h>
#include <mgba/core/config.h>
@ -56,7 +63,7 @@ int main(int argc, char** argv) {
initParserForGraphics(&subparser, &graphicsOpts);
bool parsed = parseArguments(&args, argc, argv, &subparser);
if (!args.fname) {
if (!args.fname && !args.showVersion) {
parsed = false;
}
if (!parsed || args.showHelp) {
@ -159,6 +166,13 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) {
return 1;
}
mCoreAutoloadSave(renderer->core);
#ifdef ENABLE_SCRIPTING
struct mScriptBridge* bridge = mScriptBridgeCreate();
#ifdef ENABLE_PYTHON
mPythonSetup(bridge);
#endif
#endif
#ifdef USE_DEBUGGERS
struct mDebugger* debugger = mDebuggerCreate(args->debuggerType, renderer->core);
if (debugger) {
@ -171,6 +185,9 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) {
mDebuggerAttach(debugger, renderer->core);
mDebuggerEnter(debugger, DEBUGGER_ENTER_MANUAL, NULL);
}
#ifdef ENABLE_SCRIPTING
mScriptBridgeSetDebugger(bridge, debugger);
#endif
#endif
if (args->patch) {
@ -212,6 +229,11 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) {
printf("Could not run game. Are you sure the file exists and is a compatible game?\n");
}
renderer->core->unloadROM(renderer->core);
#ifdef ENABLE_SCRIPTING
mScriptBridgeDestroy(bridge);
#endif
return didFail;
}

View File

@ -40,12 +40,11 @@ bool mSDLInitAudio(struct mSDLAudio* context, struct mCoreThread* threadContext)
mLOG(SDL_AUDIO, ERROR, "Could not open SDL sound system");
return false;
}
context->samples = context->obtainedSpec.samples;
context->core = 0;
if (threadContext) {
context->core = threadContext->core;
context->sync = &threadContext->sync;
context->sync = &threadContext->impl->sync;
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_PauseAudioDevice(context->deviceId, 0);

View File

@ -416,7 +416,7 @@ static void _mSDLHandleKeypress(struct mCoreThread* context, struct mSDLPlayer*
return;
}
if (event->keysym.sym == SDLK_TAB) {
context->sync.audioWait = event->type != SDL_KEYDOWN;
context->impl->sync.audioWait = event->type != SDL_KEYDOWN;
return;
}
if (event->keysym.sym == SDLK_BACKQUOTE) {

View File

@ -24,6 +24,7 @@
#include <mgba-util/gui/file-select.h>
#include <mgba-util/gui/font.h>
#include <mgba-util/gui/menu.h>
#include <mgba-util/memory.h>
#include <mgba-util/vfs.h>
#define GCN1_INPUT 0x47434E31
@ -114,6 +115,9 @@ static bool frameLimiter = true;
static int scaleFactor;
static unsigned corew, coreh;
uint32_t* romBuffer;
size_t romBufferSize;
static void* framebuffer[2] = { 0, 0 };
static int whichFb = 0;
@ -242,6 +246,10 @@ int main(int argc, char* argv[]) {
AUDIO_RegisterDMACallback(_audioDMA);
memset(audioBuffer, 0, sizeof(audioBuffer));
#ifdef FIXED_ROM_BUFFER
romBufferSize = SIZE_CART0;
romBuffer = anonymousMemoryMap(romBufferSize);
#endif
#if !defined(COLOR_16_BIT) && !defined(COLOR_5_6_5)
#error This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
@ -510,6 +518,10 @@ int main(int argc, char* argv[]) {
}
mGUIDeinit(&runner);
#ifdef FIXED_ROM_BUFFER
mappedMemoryFree(romBuffer, romBufferSize);
#endif
free(fifo);
free(texmem);
free(rescaleTexmem);