mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' into feature/input-revamp
This commit is contained in:
commit
1de4c8dc2e
254
CHANGES
254
CHANGES
|
@ -1,177 +1,197 @@
|
|||
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
|
||||
|
|
|
@ -19,6 +19,7 @@ set(USE_SQLITE3 ON CACHE BOOL "Whether or not to enable SQLite3 support")
|
|||
set(M_CORE_GBA ON CACHE BOOL "Build Game Boy Advance core")
|
||||
set(M_CORE_GB ON CACHE BOOL "Build Game Boy core")
|
||||
set(USE_LZMA ON CACHE BOOL "Whether or not to enable 7-Zip support")
|
||||
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")
|
||||
|
@ -255,6 +256,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()
|
||||
|
@ -349,6 +354,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)
|
||||
|
@ -391,6 +397,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
|
||||
|
@ -595,6 +602,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)
|
||||
|
@ -642,6 +661,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})
|
||||
|
@ -718,7 +741,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/mgba-16.png DESTINATION share/icons/hicolor/16x16/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-24.png DESTINATION share/icons/hicolor/24x24/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/mgba-32.png DESTINATION share/icons/hicolor/32x32/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})
|
||||
|
@ -754,6 +777,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})
|
||||
|
@ -826,11 +854,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})
|
||||
|
@ -846,7 +869,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})
|
||||
|
|
|
@ -31,6 +31,7 @@ struct mCoreRewindContext {
|
|||
Thread thread;
|
||||
Condition cond;
|
||||
Mutex mutex;
|
||||
bool ready;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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*);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -70,7 +70,7 @@ static void GBATimerUpdate(struct GBA* gba, int timerId, uint32_t cyclesLate) {
|
|||
}
|
||||
}
|
||||
|
||||
if (timerId < 4) {
|
||||
if (timerId < 3) {
|
||||
struct GBATimer* nextTimer = &gba->timers[timerId + 1];
|
||||
if (GBATimerFlagsIsCountUp(nextTimer->flags)) { // TODO: Does this increment while disabled?
|
||||
++gba->memory.io[(REG_TM1CNT_LO >> 1) + (timerId << 1)];
|
||||
|
@ -143,10 +143,6 @@ void GBATimerUpdateRegister(struct GBA* gba, int timer, int32_t cyclesLate) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (gba->memory.lastPrefetchedPc > (uint32_t) gba->cpu->gprs[ARM_PC]) {
|
||||
cyclesLate -= ((gba->memory.lastPrefetchedPc - gba->cpu->gprs[ARM_PC]) * gba->cpu->memory.activeSeqCycles16) / WORD_SIZE_THUMB;
|
||||
}
|
||||
|
||||
int prescaleBits = GBATimerFlagsGetPrescaleBits(currentTimer->flags);
|
||||
int32_t currentTime = mTimingCurrentTime(&gba->timing) - cyclesLate;
|
||||
int32_t tickMask = (1 << prescaleBits) - 1;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=["mgba"],
|
||||
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")
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,12 +264,12 @@ 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)
|
||||
if(DESKTOP_FILE_INSTALL)
|
||||
install(CODE "execute_process(COMMAND ${DESKTOP_FILE_INSTALL} \"${CMAKE_SOURCE_DIR}/res/mgba-qt.desktop\" --dir \"\$ENV{DESTDIR}\${CMAKE_INSTALL_FULL_DATADIR}/applications/\")")
|
||||
install(CODE "execute_process(COMMAND ${DESKTOP_FILE_INSTALL} \"${CMAKE_SOURCE_DIR}/res/mgba-qt.desktop\" --dir \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/applications/\")")
|
||||
endif()
|
||||
endif()
|
||||
if(UNIX)
|
||||
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
|
@ -333,9 +333,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();
|
||||
|
@ -349,7 +349,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);
|
||||
|
|
|
@ -50,7 +50,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
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -706,14 +704,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -724,7 +722,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;
|
||||
}
|
||||
}
|
||||
|
@ -852,8 +850,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -874,9 +874,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());
|
||||
|
@ -994,22 +996,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());
|
||||
|
@ -1019,24 +1024,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) {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "Display.h"
|
||||
#include "GBAApp.h"
|
||||
#include "InputController.h"
|
||||
#include "ShaderSelector.h"
|
||||
#include "ShortcutView.h"
|
||||
|
||||
#include <mgba/core/serialize.h>
|
||||
|
@ -149,7 +150,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"));
|
||||
|
@ -171,6 +172,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()) {
|
||||
|
@ -300,6 +319,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);
|
||||
|
|
|
@ -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*);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/>
|
||||
|
|
|
@ -79,7 +79,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
|
||||
|
@ -178,7 +180,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);
|
||||
|
@ -284,6 +285,7 @@ void Window::loadConfig() {
|
|||
enterFullScreen();
|
||||
}
|
||||
|
||||
#if defined(BUILD_GL) || defined(BUILD_GLES)
|
||||
if (opts->shader) {
|
||||
struct VDir* shader = VDirOpen(opts->shader);
|
||||
if (shader) {
|
||||
|
@ -292,6 +294,7 @@ void Window::loadConfig() {
|
|||
shader->close(shader);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
m_mruFiles = m_config->getMRU();
|
||||
updateMRU();
|
||||
|
@ -469,6 +472,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);
|
||||
|
@ -691,14 +699,10 @@ 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);
|
||||
emit startDrawing(context);
|
||||
for (QAction* action : m_gameActions) {
|
||||
action->setDisabled(false);
|
||||
}
|
||||
|
@ -805,6 +809,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() {
|
||||
|
@ -813,6 +818,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) {
|
||||
|
@ -1273,13 +1279,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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>")
|
||||
|
|
|
@ -23,13 +23,6 @@
|
|||
<source>{projectName} would like to thank the following patrons from Patreon:</source>
|
||||
<translation>{projectName} möchte den folgenden Unterstützern auf Patreon danken:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../AboutScreen.ui" line="86"/>
|
||||
<source>© 2013 – 2016 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0
|
||||
Game Boy Advance is a registered trademark of Nintendo Co., Ltd.</source>
|
||||
<translation>© 2013 – 2016 Jeffrey Pfau, lizenziert unter der Mozilla Public License, Version 2.0
|
||||
Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../AboutScreen.ui" line="190"/>
|
||||
<source>{patrons}</source>
|
||||
|
@ -40,6 +33,13 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.</tran
|
|||
<source>{projectVersion}</source>
|
||||
<translation>{projectVersion}</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../AboutScreen.ui" line="86"/>
|
||||
<source>© 2013 – 2017 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0
|
||||
Game Boy Advance is a registered trademark of Nintendo Co., Ltd.</source>
|
||||
<translation>© 2013 – 2017 Jeffrey Pfau, lizenziert unter der Mozilla Public License, Version 2.0
|
||||
Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../AboutScreen.ui" line="155"/>
|
||||
<source>{logo}</source>
|
||||
|
@ -458,8 +458,8 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.</tran
|
|||
<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"/>
|
||||
|
@ -1169,28 +1169,28 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.</tran
|
|||
<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>
|
||||
|
@ -3280,7 +3280,7 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.</tran
|
|||
<message>
|
||||
<location filename="../Window.cpp" line="1105"/>
|
||||
<source>Sh&utdown</source>
|
||||
<translation>B&eenden</translation>
|
||||
<translation>Schli&eßen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../Window.cpp" line="1111"/>
|
||||
|
@ -3910,7 +3910,7 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.</tran
|
|||
<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
|
@ -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/mgba.6 DESTINATION ${MANDIR}/man6 COMPONENT ${BINARY_NAME}-sdl)
|
||||
endif()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -2,9 +2,9 @@ if(NOT PROJECT_NAME)
|
|||
set(PROJECT_NAME "mGBA")
|
||||
endif()
|
||||
set(LIB_VERSION_MAJOR 0)
|
||||
set(LIB_VERSION_MINOR 6)
|
||||
set(LIB_VERSION_MINOR 7)
|
||||
set(LIB_VERSION_PATCH 0)
|
||||
set(LIB_VERSION_ABI 0.6)
|
||||
set(LIB_VERSION_ABI 0.7)
|
||||
set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH})
|
||||
set(SUMMARY "${PROJECT_NAME} Game Boy Advance Emulator")
|
||||
|
||||
|
|
Loading…
Reference in New Issue