mirror of https://github.com/mgba-emu/mgba.git
Compare commits
59 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
daf3ce76e7 | |
![]() |
3dfa8bc547 | |
![]() |
81f55ac4ba | |
![]() |
ce2bdd225b | |
![]() |
efa7f97012 | |
![]() |
a92d02c756 | |
![]() |
a499b09e48 | |
![]() |
0511a2cfd5 | |
![]() |
3d1fbcefb0 | |
![]() |
f19c39946f | |
![]() |
0cb0c185ff | |
![]() |
1984d042d1 | |
![]() |
1d82abedcc | |
![]() |
9b0b4db0d7 | |
![]() |
244f0e362a | |
![]() |
c01dfa3f83 | |
![]() |
5080a29341 | |
![]() |
f8675a9004 | |
![]() |
745aab78d1 | |
![]() |
248dbba88d | |
![]() |
3426f953da | |
![]() |
e443b61c21 | |
![]() |
2594015853 | |
![]() |
8aa9c4503a | |
![]() |
2226b454fd | |
![]() |
7950fac9e5 | |
![]() |
b0e12c3392 | |
![]() |
46d31796df | |
![]() |
d0e1a5d5d2 | |
![]() |
3b0e0c7178 | |
![]() |
3e98fe68a0 | |
![]() |
7db12fae96 | |
![]() |
717f4ec493 | |
![]() |
803914b30e | |
![]() |
d5a6098448 | |
![]() |
8891abaca8 | |
![]() |
ae41898206 | |
![]() |
6d2e81d246 | |
![]() |
9324fedee4 | |
![]() |
c05d08759a | |
![]() |
e9df5bc961 | |
![]() |
99ae62695e | |
![]() |
2f1937f953 | |
![]() |
1dcd40a5e9 | |
![]() |
3c5db2b7a1 | |
![]() |
7ed38b0a6d | |
![]() |
630386e569 | |
![]() |
35cec4858f | |
![]() |
0de0347bf1 | |
![]() |
3186482281 | |
![]() |
0e613d296a | |
![]() |
1c06cbd8d3 | |
![]() |
12c6aaa213 | |
![]() |
65181a737b | |
![]() |
51c405011f | |
![]() |
0a845c4e66 | |
![]() |
c818d506a7 | |
![]() |
3827f2b91f | |
![]() |
767c6a039c |
105
CHANGES
105
CHANGES
|
@ -1,14 +1,55 @@
|
|||
0.2.0: (Future)
|
||||
0.2.1: (2015-05-13)
|
||||
Bugfixes:
|
||||
- All: Fix sanitize-deb script not cleaning up after itself
|
||||
- All: Fix dependencies for libavcodec on Debian-derived platforms
|
||||
- ARM7: Handle writeback for PC in addressing modes 2 and 3
|
||||
- ARM7: Make illegal instruction decoding consistent between ARM and Thumb
|
||||
- ARM7: Fix ARM multiply instructions when PC is a destination register
|
||||
- Debugger: Fix use-after-free in breakpoint clearing code
|
||||
- Debugger: Fix boundary conditions in tab completion
|
||||
- GBA: Fix timers not updating timing when writing to only the reload register
|
||||
- GBA: Fix rewind boundary conditions
|
||||
- GBA: Add initial I/O register settings for background matrix registers
|
||||
- GBA: Fix hang when loading a savestate if sync to video is enabled
|
||||
- GBA: Handle out-of-bounds I/O access
|
||||
- GBA: Fix bounds-checking on EEPROM access
|
||||
- GBA Audio: FIFOs should not poll DMAs that are not scheduled for audio
|
||||
- GBA BIOS: Initialize a variable that may be uninitialized in very rare cases
|
||||
- GBA Memory: Allow SRAM to be 64kB
|
||||
- GBA Memory: Fix 32-bit loads from unaddress cartridge space
|
||||
- GBA Memory: Fix jumping to invalid memory when switching from Thumb to ARM
|
||||
- GBA Video: Fix second frame mode 5
|
||||
- Perf: Fix race condition if a game crashes immediately on start
|
||||
- Qt: Fix Display object leak when closing a window
|
||||
- Qt: Fix .deb dependencies
|
||||
- Qt: Fix "QOpenGLContext::swapBuffers() called with non-exposed window" warning
|
||||
- Qt: Fix window not regaining focus after exiting savestate window
|
||||
- Qt: Fix regression where video would not record if the game had already started
|
||||
- Qt: Fix potential crash if a gamepad causes focus to change
|
||||
- Qt: Fix controller axis querying
|
||||
- Qt: Fix multiplayer windows opening as the wrong size
|
||||
- Qt: Fix controllers sometimes not loading the right profile
|
||||
- SDL: Fix boundary conditions for joystick adjustments
|
||||
- SDL: Allocate properly sized input maps
|
||||
- SDL: Fix potential build issues when Qt and SDL2 are in use
|
||||
- Util: Fix resource leak in UTF-8 handling code
|
||||
- Util: Fix a null-pointer issue when attempting to delete a key
|
||||
- VFS: Fix resource leaks if some allocations fail
|
||||
- Video: Fix an issue with very long filenames
|
||||
Misc:
|
||||
- GBA Memory: Soft-crash if jumping past the end of a ROM
|
||||
- Qt: Show multiplayer numbers in window title
|
||||
- Qt: Solar sensor can have shortcuts set
|
||||
|
||||
0.2.0: (2015-04-03)
|
||||
Features:
|
||||
- Support for gamepad axes, e.g. analog sticks or triggers
|
||||
- Add scale presets for up to 6x
|
||||
- Debugger: Add CLI "frame", frame advance command
|
||||
- Settings window
|
||||
- Bilinear resampling option
|
||||
- Add option to skip BIOS start screen
|
||||
- List of recently opened games
|
||||
- Support for games using the Solar Sensor
|
||||
- Debugger: Add CLI functions for writing to memory
|
||||
- Better audio resampling via blip-buf
|
||||
- Game Pak overrides dialog for setting savetype and sensor values
|
||||
- Support for games using the tilt sensor
|
||||
|
@ -23,9 +64,7 @@ Features:
|
|||
- Support loading 7-Zip files
|
||||
- Drag and drop game loading
|
||||
- Cheat code support
|
||||
- Debugger: Add CLI functions for examining memory regions
|
||||
- Runtime configurable audio driver
|
||||
- Debugger: Add CLI function for writing a register
|
||||
- Libretro core for use with RetroArch and other front-ends
|
||||
- Controller profiles for setting different bindings for different controllers
|
||||
- Ability to lock aspect ratio
|
||||
|
@ -33,59 +72,65 @@ Features:
|
|||
- Ability to switch which game controller is in use per instance
|
||||
- Ability to prevent opposing directional input
|
||||
- Warning dialog if an unimplemented BIOS feature is called
|
||||
- Debugger: Add CLI "frame", frame advance command
|
||||
- Debugger: Add CLI functions for writing to memory
|
||||
- Debugger: Add CLI functions for examining memory regions
|
||||
- Debugger: Add CLI function for writing a register
|
||||
Bugfixes:
|
||||
- ARM7: Extend prefetch by one stage
|
||||
- ARM7: Fix cycle counting for loads
|
||||
- Debugger: Disassembly now lists PSR bitmasks (fixes #191)
|
||||
- GBA: Fix savestate loading of DISPSTAT and WAITCNT registers
|
||||
- GBA: Initialize gba.sync to null
|
||||
- GBA: Fix timer initialization
|
||||
- GBA Audio: Support 16-bit writes to FIFO audio
|
||||
- GBA Audio: Audio buffer sizes are now correct sizes for both sample rates
|
||||
- GBA BIOS: Fix BIOS prefetch after returning from an IRQ
|
||||
- GBA BIOS: Fix BIOS prefetch after reset
|
||||
- GBA Memory: Fix alignment of open bus 8- and 16-bit loads
|
||||
- GBA Thread: Fix possible hang when loading an archive
|
||||
- Perf: Fix crash when the GBA thread fails to start
|
||||
- SDL: Properly clean up if a game doesn't launch
|
||||
- Debugger: Disassembly now lists PSR bitmasks (fixes #191)
|
||||
- GBA BIOS: Prevent CpuSet and CpuFastSet from using BIOS addresses as a source (fixes #184)
|
||||
- GBA BIOS: Fix BIOS decompression routines with invalid source addresses
|
||||
- GBA Memory: Fix alignment of open bus 8- and 16-bit loads
|
||||
- GBA Memory: Fix I cycles that had been moved to ARM7 core
|
||||
- GBA Memory: Fix cycle counting for 32-bit load/stores
|
||||
- GBA RR: Fix fallthrough error when reading tags from a movie
|
||||
- GBA Thread: Fix possible hang when loading an archive
|
||||
- GBA Thread: Fix possible deadlock in video sync
|
||||
- GBA: Fix savestate loading of DISPSTAT and WAITCNT registers
|
||||
- Perf: Fix crash when the GBA thread fails to start
|
||||
- Qt: Fix crash starting a GDB stub if a game isn't loaded
|
||||
- Qt: Fix crash when adjusting settings after closing a game
|
||||
- Qt: Fix crash when starting GDB stub after closing a game
|
||||
- Qt: Fix patch loading while a game is running
|
||||
- Util: Fix sockets on Windows
|
||||
- Qt: Fix crash when loading a game after stopping GDB server
|
||||
- GBA BIOS: Fix BIOS decompression routines with invalid source addresses
|
||||
- GBA: Initialize gba.sync to null
|
||||
- GBA: Fix timer initialization
|
||||
- GBA Memory: Fix I cycles that had been moved to ARM7 core
|
||||
- GBA Memory: Fix cycle counting for 32-bit load/stores
|
||||
- ARM7: Fix cycle counting for loads
|
||||
- Qt: Pause game while open file dialogs are open (fixes #6 on GitHub)
|
||||
- Qt: Fix crash when attempting to pause if a game is not running
|
||||
- SDL: Properly clean up if a game doesn't launch
|
||||
- Util: Fix sockets on Windows
|
||||
Misc:
|
||||
- GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples
|
||||
- GBA Memory: Simplify memory API and use fixed bus width
|
||||
- GBA Video: Start video at the last scanline instead of the first
|
||||
- All: Enable link-time optimization
|
||||
- Debugger: Watchpoints now work on STM/LDM instructions
|
||||
- GBA: Improve accuracy of event timing
|
||||
- Debugger: Clean up GDB stub network interfacing
|
||||
- Debugger: Simplify debugger state machine to play nicer with the GBA thread loop
|
||||
- Debugger: Merge Thumb BL instructions when disassembling
|
||||
- Debugger: Clean up debugger interface, removing obsolete state (fixes #67)
|
||||
- Debugger: Watchpoints now report address watched (fixes #68)
|
||||
- Debugger: Add support for soft breakpoints
|
||||
- Debugger: Make I/O register names be addresses instead of values
|
||||
- Debugger: Rename read/write commands
|
||||
- GBA: Improve accuracy of event timing
|
||||
- GBA: Add API for getting Configuration structs for overrides and input
|
||||
- GBA: Refactor gba-sensors and gba-gpio into gba-hardware
|
||||
- GBA: Refactor gba directory, dropping gba- prefix and making supervisor directory
|
||||
- Debugger: Add support for soft breakpoints
|
||||
- Util: Use proper locale for reading and writing float values
|
||||
- Debugger: Make I/O register names be addresses instead of values
|
||||
- Debugger: Rename read/write commands
|
||||
- Qt: Optimize logo drawing
|
||||
- Qt: Move frame upload back onto main thread
|
||||
- All: Enable link-time optimization
|
||||
- GBA Thread: Make GBASyncWaitFrameStart time out
|
||||
- GBA: Move A/V stream interface into core
|
||||
- GBA: Savestates now take into account savedata state machines (fixes #109)
|
||||
- GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples
|
||||
- GBA Memory: Simplify memory API and use fixed bus width
|
||||
- GBA Thread: Make GBASyncWaitFrameStart time out
|
||||
- GBA Video: Start video at the last scanline instead of the first
|
||||
- Qt: Optimize logo drawing
|
||||
- Qt: Move frame upload back onto main thread
|
||||
- Qt: Remember window position
|
||||
- Qt: Double-clicking on the window toggles full screen
|
||||
- Util: Use proper locale for reading and writing float values
|
||||
|
||||
0.1.1: (2015-01-24)
|
||||
Bugfixes:
|
||||
|
|
|
@ -72,7 +72,7 @@ endfunction()
|
|||
# Version information
|
||||
set(LIB_VERSION_MAJOR 0)
|
||||
set(LIB_VERSION_MINOR 2)
|
||||
set(LIB_VERSION_PATCH 0)
|
||||
set(LIB_VERSION_PATCH 1)
|
||||
set(LIB_VERSION_ABI 0.2)
|
||||
set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH})
|
||||
|
||||
|
@ -224,7 +224,7 @@ if(USE_FFMPEG)
|
|||
string(REGEX MATCH "^[0-9]+" LIBAVUTIL_VERSION_MAJOR ${libavutil_VERSION})
|
||||
string(REGEX MATCH "^[0-9]+" LIBSWSCALE_VERSION_MAJOR ${libswscale_VERSION})
|
||||
list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES})
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR},libavformat${LIBAVFORMAT_VERSION_MAJOR},libavresample${LIBAVRESAMPLE_VERSION_MAJOR},libavutil${LIBAVUTIL_VERSION_MAJOR},libswscale${LIBSWSCALE_VERSION_MAJOR}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR}|libavcodec-extra-${LIBAVCODEC_VERSION_MAJOR},libavformat${LIBAVFORMAT_VERSION_MAJOR},libavresample${LIBAVRESAMPLE_VERSION_MAJOR},libavutil${LIBAVUTIL_VERSION_MAJOR},libswscale${LIBSWSCALE_VERSION_MAJOR}")
|
||||
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libavcodec-extra")
|
||||
endif()
|
||||
|
||||
|
|
36
README.md
36
README.md
|
@ -3,7 +3,7 @@ mGBA
|
|||
|
||||
mGBA is a new emulator for running Game Boy Advance games. It aims to be faster and more accurate than many existing Game Boy Advance emulators, as well as adding features that other emulators lack.
|
||||
|
||||
Up-to-date news and downloads can be found at [endrift.com/mgba](https://endrift.com/mgba/).
|
||||
Up-to-date news and downloads can be found at [mgba.io](http://mgba.io/).
|
||||
|
||||

|
||||
|
||||
|
@ -13,11 +13,12 @@ Features
|
|||
- Near full Game Boy Advance hardware support[<sup>[1]</sup>](#missing).
|
||||
- Fast emulation. Known to run at full speed even on low end hardware, such as netbooks.
|
||||
- Qt and SDL ports for a heavy-weight and a light-weight frontend.
|
||||
- Local (same computer) link cable support.
|
||||
- Save type detection, even for flash memory size[<sup>[2]</sup>](#flashdetect).
|
||||
- Real-time clock support, even without configuration.
|
||||
- A built-in BIOS implementation, and ability to load external BIOS files.
|
||||
- Turbo/fast-forward support by holding Tab.
|
||||
- Frameskip, configurable up to 9.
|
||||
- Frameskip, configurable up to 10.
|
||||
- Screenshot support.
|
||||
- Cheat code support.
|
||||
- 9 savestate slots. Savestates are also viewable as screenshots.
|
||||
|
@ -30,12 +31,13 @@ Features
|
|||
|
||||
### Planned features
|
||||
|
||||
- Local and networked multiplayer link cable support ([Bug #1](https://endrift.com/mgba/bugs/show_bug.cgi?id=1)).
|
||||
- Dolphin/JOY bus link cable support ([Bug #73](https://endrift.com/mgba/bugs/show_bug.cgi?id=73)).
|
||||
- Networked multiplayer link cable support ([Bug #1](http://mgba.io/b/1)).
|
||||
- Dolphin/JOY bus link cable support ([Bug #73](http://mgba.io/b/73)).
|
||||
- Re-recording support for tool-assist runs. ([Bugzilla keyword "TASBlocker"](https://endrift.com/mgba/bugs/buglist.cgi?quicksearch=TASBlocker))
|
||||
- Lua support for scripting ([Bug #62](https://endrift.com/mgba/bugs/show_bug.cgi?id=62)).
|
||||
- A comprehensive debug suite ([Bug #132](https://endrift.com/mgba/bugs/show_bug.cgi?id=132)).
|
||||
- libretro core for RetroArch and OpenEmu ([Bug #86](https://endrift.com/mgba/bugs/show_bug.cgi?id=86)).
|
||||
- Lua support for scripting ([Bug #62](http://mgba.io/b/62)).
|
||||
- A comprehensive debug suite ([Bug #132](http://mgba.io/b/132)).
|
||||
- OpenEmu core.
|
||||
- e-Reader support. ([Bug #171](http://mgba.io/b/171))
|
||||
|
||||
|
||||
Supported Platforms
|
||||
|
@ -46,7 +48,7 @@ Supported Platforms
|
|||
- Linux
|
||||
- FreeBSD
|
||||
|
||||
Other Unix-like platforms work as well, but are untested.
|
||||
Other Unix-like platforms, such as OpenBSD, are known to work as well, but are untested and not fully supported.
|
||||
|
||||
### System requirements
|
||||
|
||||
|
@ -72,7 +74,7 @@ Controls are configurable in the menu. The default gamepad controls are mapped s
|
|||
Compiling
|
||||
---------
|
||||
|
||||
Compiling requires using CMake 2.8.11 or newer. To use CMake to build on a Unix-based system, the recommended commands are as follows:
|
||||
Compiling requires using CMake 2.8.11 or newer. GCC and Clang are both known to work to compile mGBA, but Visual Studio 2013 and older are known not to work. To use CMake to build on a Unix-based system, the recommended commands are as follows:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
|
@ -99,18 +101,18 @@ Footnotes
|
|||
|
||||
<a name="missing">[1]</a> Currently missing features are
|
||||
|
||||
- OBJ window for modes 3, 4 and 5 ([Bug #5](https://endrift.com/mgba/bugs/show_bug.cgi?id=5))
|
||||
- Mosaic for transformed OBJs ([Bug #9](https://endrift.com/mgba/bugs/show_bug.cgi?id=9))
|
||||
- BIOS call RegisterRamReset is partially stubbed out ([Bug #141](https://endrift.com/mgba/bugs/show_bug.cgi?id=141))
|
||||
- Audio channel reset flags ([Bug #142](https://endrift.com/mgba/bugs/show_bug.cgi?id=142))
|
||||
- Game Pak prefetch ([Bug #195](https://endrift.com/mgba/bugs/show_bug.cgi?id=195))
|
||||
- BIOS call Stop, for entering sleep mode ([Bug #199](https://endrift.com/mgba/bugs/show_bug.cgi?id=199))
|
||||
- OBJ window for modes 3, 4 and 5 ([Bug #5](http://mgba.io/b/5))
|
||||
- Mosaic for transformed OBJs ([Bug #9](http://mgba.io/b/9))
|
||||
- BIOS call RegisterRamReset is partially stubbed out ([Bug #141](http://mgba.io/b/141))
|
||||
- Audio channel reset flags ([Bug #142](http://mgba.io/b/142))
|
||||
- Game Pak prefetch ([Bug #195](http://mgba.io/b/195))
|
||||
- BIOS call Stop, for entering sleep mode ([Bug #199](http://mgba.io/b/199))
|
||||
|
||||
<a name="flashdetect">[2]</a> Flash memory size detection does not work in some cases, and may require overrides, which are not yet user configurable. Filing a bug is recommended if such a case is encountered.
|
||||
<a name="flashdetect">[2]</a> Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered.
|
||||
|
||||
<a name="osxver">[3]</a> 10.7 is only needed for the Qt port. The SDL port is known to work on 10.6, and may work on older.
|
||||
|
||||
[downloads]: https://endrift.com/mgba/downloads.html
|
||||
[downloads]: http://mgba.io/downloads.html
|
||||
[source]: https://github.com/mgba-emu/mgba/
|
||||
|
||||
Copyright
|
||||
|
|
|
@ -380,8 +380,12 @@ DEFINE_DECODER_ARM(MRC, ILL, info->operandFormat = ARM_OPERAND_NONE;)
|
|||
|
||||
// Begin miscellaneous definitions
|
||||
|
||||
DEFINE_DECODER_ARM(BKPT, BKPT, info->operandFormat = ARM_OPERAND_NONE;) // Not strictly in ARMv4T, but here for convenience
|
||||
DEFINE_DECODER_ARM(ILL, ILL, info->operandFormat = ARM_OPERAND_NONE;) // Illegal opcode
|
||||
DEFINE_DECODER_ARM(BKPT, BKPT,
|
||||
info->operandFormat = ARM_OPERAND_NONE;
|
||||
info->traps = 1;) // Not strictly in ARMv4T, but here for convenience
|
||||
DEFINE_DECODER_ARM(ILL, ILL,
|
||||
info->operandFormat = ARM_OPERAND_NONE;
|
||||
info->traps = 1;) // Illegal opcode
|
||||
|
||||
DEFINE_DECODER_ARM(MSR, MSR,
|
||||
info->affectsCPSR = 1;
|
||||
|
|
|
@ -281,8 +281,13 @@ DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(POPR, ARM_SP, LDM, ARM_MEMORY_INCREMENT_AFTE
|
|||
DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(PUSH, ARM_SP, STM, ARM_MEMORY_DECREMENT_BEFORE, 0)
|
||||
DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(PUSHR, ARM_SP, STM, ARM_MEMORY_DECREMENT_BEFORE, 1 << ARM_LR)
|
||||
|
||||
DEFINE_THUMB_DECODER(ILL, ILL, info->traps = 1;)
|
||||
DEFINE_THUMB_DECODER(BKPT, BKPT, info->traps = 1;)
|
||||
DEFINE_THUMB_DECODER(ILL, ILL,
|
||||
info->operandFormat = ARM_OPERAND_NONE;
|
||||
info->traps = 1;)
|
||||
|
||||
DEFINE_THUMB_DECODER(BKPT, BKPT,
|
||||
info->operandFormat = ARM_OPERAND_NONE;
|
||||
info->traps = 1;)
|
||||
|
||||
DEFINE_THUMB_DECODER(B, B,
|
||||
int16_t immediate = (opcode & 0x07FF) << 5;
|
||||
|
|
|
@ -233,7 +233,12 @@ static inline void _immediate(struct ARMCore* cpu, uint32_t opcode) {
|
|||
#define ADDR_MODE_2_RM (cpu->gprs[rm])
|
||||
#define ADDR_MODE_2_IMMEDIATE (opcode & 0x00000FFF)
|
||||
#define ADDR_MODE_2_INDEX(U_OP, M) (cpu->gprs[rn] U_OP M)
|
||||
#define ADDR_MODE_2_WRITEBACK(ADDR) (cpu->gprs[rn] = ADDR)
|
||||
#define ADDR_MODE_2_WRITEBACK(ADDR) \
|
||||
cpu->gprs[rn] = ADDR; \
|
||||
if (UNLIKELY(rn == ARM_PC)) { \
|
||||
ARM_WRITE_PC; \
|
||||
}
|
||||
|
||||
#define ADDR_MODE_2_LSL (cpu->gprs[rm] << ADDR_MODE_2_I)
|
||||
#define ADDR_MODE_2_LSR (ADDR_MODE_2_I_TEST ? ((uint32_t) cpu->gprs[rm]) >> ADDR_MODE_2_I : 0)
|
||||
#define ADDR_MODE_2_ASR (ADDR_MODE_2_I_TEST ? ((int32_t) cpu->gprs[rm]) >> ADDR_MODE_2_I : ((int32_t) cpu->gprs[rm]) >> 31)
|
||||
|
@ -322,13 +327,12 @@ static inline void _immediate(struct ARMCore* cpu, uint32_t opcode) {
|
|||
int rdHi = (opcode >> 16) & 0xF; \
|
||||
int rs = (opcode >> 8) & 0xF; \
|
||||
int rm = opcode & 0xF; \
|
||||
UNUSED(rdHi); \
|
||||
if (rdHi == ARM_PC || rd == ARM_PC) { \
|
||||
return; \
|
||||
} \
|
||||
ARM_WAIT_MUL(cpu->gprs[rs]); \
|
||||
BODY; \
|
||||
S_BODY; \
|
||||
if (rd == ARM_PC) { \
|
||||
ARM_WRITE_PC; \
|
||||
})
|
||||
S_BODY;)
|
||||
|
||||
#define DEFINE_MULTIPLY_INSTRUCTION_ARM(NAME, BODY, S_BODY) \
|
||||
DEFINE_MULTIPLY_INSTRUCTION_EX_ARM(NAME, BODY, ) \
|
||||
|
|
|
@ -816,7 +816,7 @@ static unsigned char _tabComplete(EditLine* elstate, int ch) {
|
|||
}
|
||||
|
||||
const char* commandPtr;
|
||||
int cmd = 0, len = 0;
|
||||
size_t cmd = 0, len = 0;
|
||||
const char* name = 0;
|
||||
for (commandPtr = li->buffer; commandPtr <= li->cursor; ++commandPtr, ++len) {
|
||||
for (; (name = _debuggerCommands[cmd].name); ++cmd) {
|
||||
|
@ -832,7 +832,7 @@ static unsigned char _tabComplete(EditLine* elstate, int ch) {
|
|||
if (!name) {
|
||||
return CC_ERROR;
|
||||
}
|
||||
if (_debuggerCommands[cmd + 1].name && name[len - 2] == _debuggerCommands[cmd + 1].name[len - 2]) {
|
||||
if (_debuggerCommands[cmd + 1].name && strlen(_debuggerCommands[cmd + 1].name) >= len - 1 && name[len - 2] == _debuggerCommands[cmd + 1].name[len - 2]) {
|
||||
--len;
|
||||
const char* next = 0;
|
||||
int i;
|
||||
|
@ -842,6 +842,9 @@ static unsigned char _tabComplete(EditLine* elstate, int ch) {
|
|||
}
|
||||
next = _debuggerCommands[i].name;
|
||||
}
|
||||
if (!next) {
|
||||
return CC_ERROR;
|
||||
}
|
||||
|
||||
for (; name[len]; ++len) {
|
||||
if (name[len] != next[len]) {
|
||||
|
|
|
@ -149,11 +149,14 @@ bool ARMDebuggerSetSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t add
|
|||
void ARMDebuggerClearBreakpoint(struct ARMDebugger* debugger, uint32_t address) {
|
||||
struct DebugBreakpoint** previous = &debugger->breakpoints;
|
||||
struct DebugBreakpoint* breakpoint;
|
||||
for (; (breakpoint = *previous); previous = &breakpoint->next) {
|
||||
struct DebugBreakpoint** next;
|
||||
while ((breakpoint = *previous)) {
|
||||
next = &breakpoint->next;
|
||||
if (breakpoint->address == address) {
|
||||
*previous = breakpoint->next;
|
||||
*previous = *next;
|
||||
free(breakpoint);
|
||||
}
|
||||
previous = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,12 +172,15 @@ void ARMDebuggerSetWatchpoint(struct ARMDebugger* debugger, uint32_t address) {
|
|||
|
||||
void ARMDebuggerClearWatchpoint(struct ARMDebugger* debugger, uint32_t address) {
|
||||
struct DebugWatchpoint** previous = &debugger->watchpoints;
|
||||
struct DebugWatchpoint* breakpoint;
|
||||
for (; (breakpoint = *previous); previous = &breakpoint->next) {
|
||||
if (breakpoint->address == address) {
|
||||
*previous = breakpoint->next;
|
||||
free(breakpoint);
|
||||
struct DebugWatchpoint* watchpoint;
|
||||
struct DebugWatchpoint** next;
|
||||
while ((watchpoint = *previous)) {
|
||||
next = &watchpoint->next;
|
||||
if (watchpoint->address == address) {
|
||||
*previous = *next;
|
||||
free(watchpoint);
|
||||
}
|
||||
previous = next;
|
||||
}
|
||||
if (!debugger->watchpoints) {
|
||||
ARMDebuggerRemoveMemoryShim(debugger);
|
||||
|
|
|
@ -464,6 +464,8 @@ void GDBStubCreate(struct GDBStub* stub) {
|
|||
stub->d.custom = _gdbStubPoll;
|
||||
stub->d.log = 0;
|
||||
stub->untilPoll = GDB_STUB_INTERVAL;
|
||||
stub->lineAck = GDB_ACK_PENDING;
|
||||
stub->shouldBlock = false;
|
||||
}
|
||||
|
||||
bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAddress) {
|
||||
|
|
|
@ -514,11 +514,15 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) {
|
|||
GBALog(audio->p, GBA_LOG_ERROR, "Bad FIFO write to address 0x%03x", fifoId);
|
||||
return;
|
||||
}
|
||||
if (CircleBufferSize(&channel->fifo) <= 4 * sizeof(int32_t)) {
|
||||
if (CircleBufferSize(&channel->fifo) <= 4 * sizeof(int32_t) && channel->dmaSource > 0) {
|
||||
struct GBADMA* dma = &audio->p->memory.dma[channel->dmaSource];
|
||||
dma->nextCount = 4;
|
||||
dma->nextEvent = 0;
|
||||
GBAMemoryUpdateDMAs(audio->p, -cycles);
|
||||
if (GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_CUSTOM) {
|
||||
dma->nextCount = 4;
|
||||
dma->nextEvent = 0;
|
||||
GBAMemoryUpdateDMAs(audio->p, -cycles);
|
||||
} else {
|
||||
channel->dmaSource = 0;
|
||||
}
|
||||
}
|
||||
CircleBufferRead8(&channel->fifo, &channel->sample);
|
||||
}
|
||||
|
|
|
@ -331,7 +331,7 @@ static void _unLz77(struct GBA* gba, int width) {
|
|||
uint32_t disp;
|
||||
int bytes;
|
||||
int byte;
|
||||
int halfword;
|
||||
int halfword = 0;
|
||||
while (remaining > 0) {
|
||||
if (blocksRemaining) {
|
||||
if (blockheader & 0x80) {
|
||||
|
|
|
@ -433,6 +433,7 @@ void GBATimerUpdateRegister(struct GBA* gba, int timer) {
|
|||
|
||||
void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t reload) {
|
||||
gba->timers[timer].reload = reload;
|
||||
gba->timers[timer].overflowInterval = (0x10000 - gba->timers[timer].reload) << gba->timers[timer].prescaleBits;
|
||||
}
|
||||
|
||||
void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) {
|
||||
|
|
|
@ -95,7 +95,7 @@ static struct GBAInputMapImpl* _guaranteeMap(struct GBAInputMap* map, uint32_t t
|
|||
map->numMaps = 1;
|
||||
impl = &map->maps[0];
|
||||
impl->type = type;
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey));
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(int));
|
||||
TableInit(&impl->axes, 2, free);
|
||||
} else {
|
||||
impl = _lookupMap(map, type);
|
||||
|
@ -110,7 +110,7 @@ static struct GBAInputMapImpl* _guaranteeMap(struct GBAInputMap* map, uint32_t t
|
|||
}
|
||||
if (impl) {
|
||||
impl->type = type;
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey));
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(int));
|
||||
} else {
|
||||
map->maps = realloc(map->maps, sizeof(*map->maps) * map->numMaps * 2);
|
||||
for (m = map->numMaps * 2 - 1; m > map->numMaps; --m) {
|
||||
|
@ -120,7 +120,7 @@ static struct GBAInputMapImpl* _guaranteeMap(struct GBAInputMap* map, uint32_t t
|
|||
map->numMaps *= 2;
|
||||
impl = &map->maps[m];
|
||||
impl->type = type;
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey));
|
||||
impl->map = calloc(GBA_KEY_MAX, sizeof(int));
|
||||
}
|
||||
TableInit(&impl->axes, 2, free);
|
||||
}
|
||||
|
|
12
src/gba/io.c
12
src/gba/io.c
|
@ -286,6 +286,10 @@ void GBAIOInit(struct GBA* gba) {
|
|||
gba->memory.io[REG_RCNT >> 1] = RCNT_INITIAL;
|
||||
gba->memory.io[REG_KEYINPUT >> 1] = 0x3FF;
|
||||
gba->memory.io[REG_SOUNDBIAS >> 1] = 0x200;
|
||||
gba->memory.io[REG_BG2PA >> 1] = 0x100;
|
||||
gba->memory.io[REG_BG2PD >> 1] = 0x100;
|
||||
gba->memory.io[REG_BG3PA >> 1] = 0x100;
|
||||
gba->memory.io[REG_BG3PD >> 1] = 0x100;
|
||||
}
|
||||
|
||||
void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
||||
|
@ -494,6 +498,10 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
|||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_STUB, "Stub I/O register write: %03x", address);
|
||||
if (address >= REG_MAX) {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Write to unused I/O register: %03X", address);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -648,6 +656,10 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
|||
break;
|
||||
default:
|
||||
GBALog(gba, GBA_LOG_STUB, "Stub I/O register read: %03x", address);
|
||||
if (address >= REG_MAX) {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Read from unused I/O register: %03X", address);
|
||||
return 0; // TODO: Reuse LOAD_BAD
|
||||
}
|
||||
break;
|
||||
}
|
||||
return gba->memory.io[address >> 1];
|
||||
|
|
|
@ -225,7 +225,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
|||
}
|
||||
|
||||
gba->lastJump = address;
|
||||
if (newRegion == memory->activeRegion) {
|
||||
if (newRegion == memory->activeRegion && (newRegion < REGION_CART0 || (address & (SIZE_CART0 - 1)) < memory->romSize)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -233,33 +233,37 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
|||
memory->biosPrefetch = cpu->prefetch[1];
|
||||
}
|
||||
memory->activeRegion = newRegion;
|
||||
switch (address & ~OFFSET_MASK) {
|
||||
case BASE_BIOS:
|
||||
switch (newRegion) {
|
||||
case REGION_BIOS:
|
||||
cpu->memory.activeRegion = memory->bios;
|
||||
cpu->memory.activeMask = SIZE_BIOS - 1;
|
||||
break;
|
||||
case BASE_WORKING_RAM:
|
||||
case REGION_WORKING_RAM:
|
||||
cpu->memory.activeRegion = memory->wram;
|
||||
cpu->memory.activeMask = SIZE_WORKING_RAM - 1;
|
||||
break;
|
||||
case BASE_WORKING_IRAM:
|
||||
case REGION_WORKING_IRAM:
|
||||
cpu->memory.activeRegion = memory->iwram;
|
||||
cpu->memory.activeMask = SIZE_WORKING_IRAM - 1;
|
||||
break;
|
||||
case BASE_VRAM:
|
||||
case REGION_VRAM:
|
||||
cpu->memory.activeRegion = (uint32_t*) gba->video.renderer->vram;
|
||||
cpu->memory.activeMask = 0x0000FFFF;
|
||||
break;
|
||||
case BASE_CART0:
|
||||
case BASE_CART0_EX:
|
||||
case BASE_CART1:
|
||||
case BASE_CART1_EX:
|
||||
case BASE_CART2:
|
||||
case BASE_CART2_EX:
|
||||
case REGION_CART0:
|
||||
case REGION_CART0_EX:
|
||||
case REGION_CART1:
|
||||
case REGION_CART1_EX:
|
||||
case REGION_CART2:
|
||||
case REGION_CART2_EX:
|
||||
cpu->memory.activeRegion = memory->rom;
|
||||
cpu->memory.activeMask = SIZE_CART0 - 1;
|
||||
break;
|
||||
if ((address & (SIZE_CART0 - 1)) < memory->romSize) {
|
||||
break;
|
||||
}
|
||||
// Fall through
|
||||
default:
|
||||
memory->activeRegion = 0;
|
||||
cpu->memory.activeRegion = _deadbeef;
|
||||
cpu->memory.activeMask = 0;
|
||||
GBALog(gba, GBA_LOG_FATAL, "Jumped to invalid address");
|
||||
|
@ -324,7 +328,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
|
|||
} else { \
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Out of bounds ROM Load32: 0x%08X", address); \
|
||||
value = (address >> 1) & 0xFFFF; \
|
||||
value |= value << 16; \
|
||||
value |= ((address + 2) >> 1) << 16; \
|
||||
}
|
||||
|
||||
#define LOAD_SRAM \
|
||||
|
|
|
@ -61,7 +61,7 @@ enum {
|
|||
SIZE_CART0 = 0x02000000,
|
||||
SIZE_CART1 = 0x02000000,
|
||||
SIZE_CART2 = 0x02000000,
|
||||
SIZE_CART_SRAM = 0x00008000,
|
||||
SIZE_CART_SRAM = 0x00010000,
|
||||
SIZE_CART_FLASH512 = 0x00010000,
|
||||
SIZE_CART_FLASH1M = 0x00020000,
|
||||
SIZE_CART_EEPROM = 0x00002000
|
||||
|
|
|
@ -1608,7 +1608,7 @@ static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
BACKGROUND_BITMAP_ITERATE(160, 128);
|
||||
|
||||
if (!mosaicWait) {
|
||||
LOAD_16(color, (offset + (localX >> 8) + (localY >> 8) * 160) << 1, renderer->d.vram);
|
||||
LOAD_16(color, offset + (localX >> 8) * 2 + (localY >> 8) * 320, renderer->d.vram);
|
||||
#ifndef COLOR_16_BIT
|
||||
unsigned color32 = 0;
|
||||
color32 |= (color << 9) & 0xF80000;
|
||||
|
|
|
@ -345,12 +345,14 @@ void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32
|
|||
savedata->writeAddress |= (value & 0x1) << 6;
|
||||
} else if (writeSize == 1) {
|
||||
savedata->command = EEPROM_COMMAND_NULL;
|
||||
} else {
|
||||
} else if ((savedata->writeAddress >> 3) < SIZE_CART_EEPROM) {
|
||||
uint8_t current = savedata->data[savedata->writeAddress >> 3];
|
||||
current &= ~(1 << (0x7 - (savedata->writeAddress & 0x7)));
|
||||
current |= (value & 0x1) << (0x7 - (savedata->writeAddress & 0x7));
|
||||
savedata->data[savedata->writeAddress >> 3] = current;
|
||||
++savedata->writeAddress;
|
||||
} else {
|
||||
GBALog(0, GBA_LOG_GAME_ERROR, "Writing beyond end of EEPROM: %08X", (savedata->writeAddress >> 3));
|
||||
}
|
||||
break;
|
||||
case EEPROM_COMMAND_READ_PENDING:
|
||||
|
@ -375,7 +377,12 @@ uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata) {
|
|||
--savedata->readBitsRemaining;
|
||||
if (savedata->readBitsRemaining < 64) {
|
||||
int step = 63 - savedata->readBitsRemaining;
|
||||
uint8_t data = savedata->data[(savedata->readAddress + step) >> 3] >> (0x7 - (step & 0x7));
|
||||
uint32_t address = (savedata->readAddress + step) >> 3;
|
||||
if (address >= SIZE_CART_EEPROM) {
|
||||
GBALog(0, GBA_LOG_GAME_ERROR, "Reading beyond end of EEPROM: %08X", address);
|
||||
return 0xFF;
|
||||
}
|
||||
uint8_t data = savedata->data[address] >> (0x7 - (step & 0x7));
|
||||
if (!savedata->readBitsRemaining) {
|
||||
savedata->command = EEPROM_COMMAND_NULL;
|
||||
}
|
||||
|
|
|
@ -178,7 +178,10 @@ static bool _loadPNGState(struct GBA* gba, struct VFile* vf) {
|
|||
PNGReadFooter(png, end);
|
||||
PNGReadClose(png, info, end);
|
||||
gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, pixels);
|
||||
bool videoFrameWait = gba->sync->videoFrameWait;
|
||||
gba->sync->videoFrameWait = false;
|
||||
GBASyncPostFrame(gba->sync);
|
||||
gba->sync->videoFrameWait = videoFrameWait;
|
||||
|
||||
free(pixels);
|
||||
return true;
|
||||
|
@ -291,14 +294,14 @@ void GBARewind(struct GBAThread* thread, int nStates) {
|
|||
}
|
||||
int offset = thread->rewindBufferWriteOffset - nStates;
|
||||
if (offset < 0) {
|
||||
offset += thread->rewindBufferSize;
|
||||
offset += thread->rewindBufferCapacity;
|
||||
}
|
||||
struct GBASerializedState* state = thread->rewindBuffer[offset];
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
thread->rewindBufferSize -= nStates - 1;
|
||||
thread->rewindBufferWriteOffset = (offset + 1) % thread->rewindBufferCapacity;
|
||||
thread->rewindBufferSize -= nStates;
|
||||
thread->rewindBufferWriteOffset = offset;
|
||||
GBADeserialize(thread->gba, state);
|
||||
}
|
||||
|
||||
|
|
|
@ -145,7 +145,7 @@ bool GBAOverrideFind(const struct Configuration* config, struct GBACartridgeOver
|
|||
override->savetype = SAVEDATA_AUTODETECT;
|
||||
override->hardware = HW_NONE;
|
||||
override->idleLoop = IDLE_LOOP_NONE;
|
||||
bool found;
|
||||
bool found = false;
|
||||
|
||||
if (override->id[0] == 'F') {
|
||||
// Classic NES Series
|
||||
|
|
|
@ -202,7 +202,8 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
|||
avformat_alloc_output_context2(&encoder->context, oformat, 0, outfile);
|
||||
#else
|
||||
encoder->context = avformat_alloc_context();
|
||||
strncpy(encoder->context->filename, outfile, sizeof(encoder->context->filename));
|
||||
strncpy(encoder->context->filename, outfile, sizeof(encoder->context->filename) - 1);
|
||||
encoder->context->filename[sizeof(encoder->context->filename) - 1] = '\0';
|
||||
encoder->context->oformat = oformat;
|
||||
#endif
|
||||
|
||||
|
|
|
@ -95,7 +95,14 @@ int main(int argc, char** argv) {
|
|||
if (!didStart) {
|
||||
goto cleanup;
|
||||
}
|
||||
GBAThreadInterrupt(&context);
|
||||
if (GBAThreadHasCrashed(&context)) {
|
||||
GBAThreadJoin(&context);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
GBAGetGameCode(context.gba, gameCode);
|
||||
GBAThreadContinue(&context);
|
||||
|
||||
int frames = perfOpts.frames;
|
||||
if (!frames) {
|
||||
|
|
|
@ -17,12 +17,13 @@ AudioDevice::AudioDevice(QObject* parent)
|
|||
: QIODevice(parent)
|
||||
, m_context(nullptr)
|
||||
, m_drift(0)
|
||||
, m_ratio(1.f)
|
||||
{
|
||||
setOpenMode(ReadOnly);
|
||||
}
|
||||
|
||||
void AudioDevice::setFormat(const QAudioFormat& format) {
|
||||
if (!GBAThreadIsActive(m_context)) {
|
||||
if (!m_context || !GBAThreadIsActive(m_context)) {
|
||||
return;
|
||||
}
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_NN
|
||||
|
|
|
@ -48,6 +48,8 @@ AudioProcessor* AudioProcessor::create() {
|
|||
|
||||
AudioProcessor::AudioProcessor(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_context(nullptr)
|
||||
, m_samples(GBA_AUDIO_SAMPLES)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,10 @@ void AudioProcessorQt::setInput(GBAThread* input) {
|
|||
}
|
||||
|
||||
void AudioProcessorQt::start() {
|
||||
if (!input()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_device) {
|
||||
m_device = new AudioDevice(this);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,10 @@ AudioProcessorSDL::~AudioProcessorSDL() {
|
|||
}
|
||||
|
||||
void AudioProcessorSDL::start() {
|
||||
if (!input()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_audio.thread) {
|
||||
GBASDLResumeAudio(&m_audio);
|
||||
} else {
|
||||
|
|
|
@ -4,7 +4,10 @@ enable_language(CXX)
|
|||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++11")
|
||||
|
||||
if(APPLE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7 -stdlib=libc++")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7")
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(PLATFORM_SRC)
|
||||
|
@ -74,7 +77,7 @@ qt5_wrap_ui(UI_FILES
|
|||
VideoView.ui)
|
||||
|
||||
set(QT_LIBRARIES)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5,libqt5opengl5" PARENT_SCOPE)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5,libqt5opengl5")
|
||||
|
||||
set(AUDIO_SRC)
|
||||
if(BUILD_SDL)
|
||||
|
@ -87,7 +90,7 @@ if(Qt5Multimedia_FOUND)
|
|||
AudioDevice.cpp)
|
||||
list(APPEND QT_LIBRARIES Qt5::Multimedia)
|
||||
add_definitions(-DBUILD_QT_MULTIMEDIA)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5multimedia5" PARENT_SCOPE)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5multimedia5")
|
||||
endif()
|
||||
|
||||
if(NOT AUDIO_SRC)
|
||||
|
@ -115,6 +118,7 @@ set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CM
|
|||
|
||||
list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::OpenGL)
|
||||
target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES})
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}" PARENT_SCOPE)
|
||||
|
||||
install(TARGETS ${BINARY_NAME}-qt
|
||||
RUNTIME DESTINATION bin COMPONENT ${BINARY_NAME}-qt
|
||||
|
|
|
@ -32,6 +32,9 @@ Display::Display(QGLFormat format, QWidget* parent)
|
|||
: QGLWidget(format, parent)
|
||||
, m_painter(nullptr)
|
||||
, m_started(false)
|
||||
, m_lockAspectRatio(false)
|
||||
, m_filter(false)
|
||||
, m_context(nullptr)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||
setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
|
||||
|
@ -129,7 +132,6 @@ void Display::screenshot() {
|
|||
void Display::initializeGL() {
|
||||
glClearColor(0, 0, 0, 0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
swapBuffers();
|
||||
}
|
||||
|
||||
void Display::resizeEvent(QResizeEvent* event) {
|
||||
|
@ -146,6 +148,7 @@ Painter::Painter(Display* parent)
|
|||
: m_gl(parent)
|
||||
, m_lockAspectRatio(false)
|
||||
, m_filter(false)
|
||||
, m_context(nullptr)
|
||||
{
|
||||
m_size = parent->size();
|
||||
}
|
||||
|
|
|
@ -334,7 +334,7 @@ bool GameController::isPaused() {
|
|||
}
|
||||
|
||||
void GameController::setPaused(bool paused) {
|
||||
if (paused == GBAThreadIsPaused(&m_threadContext)) {
|
||||
if (!m_gameOpen || paused == GBAThreadIsPaused(&m_threadContext)) {
|
||||
return;
|
||||
}
|
||||
if (paused) {
|
||||
|
@ -500,6 +500,10 @@ void GameController::setTurbo(bool set, bool forced) {
|
|||
if (m_turboForced && !forced) {
|
||||
return;
|
||||
}
|
||||
if (m_turbo == set && m_turboForced == forced) {
|
||||
// Don't interrupt the thread if we don't need to
|
||||
return;
|
||||
}
|
||||
m_turbo = set;
|
||||
m_turboForced = set && forced;
|
||||
threadInterrupt();
|
||||
|
@ -511,12 +515,18 @@ void GameController::setTurbo(bool set, bool forced) {
|
|||
void GameController::setAVStream(GBAAVStream* stream) {
|
||||
threadInterrupt();
|
||||
m_threadContext.stream = stream;
|
||||
if (m_gameOpen) {
|
||||
m_threadContext.gba->stream = stream;
|
||||
}
|
||||
threadContinue();
|
||||
}
|
||||
|
||||
void GameController::clearAVStream() {
|
||||
threadInterrupt();
|
||||
m_threadContext.stream = nullptr;
|
||||
if (m_gameOpen) {
|
||||
m_threadContext.gba->stream = nullptr;
|
||||
}
|
||||
threadContinue();
|
||||
}
|
||||
|
||||
|
|
|
@ -90,6 +90,11 @@ void InputController::setConfiguration(ConfigController* config) {
|
|||
|
||||
void InputController::loadConfiguration(uint32_t type) {
|
||||
GBAInputMapLoad(&m_inputMap, type, m_config->input());
|
||||
#ifdef BUILD_SDL
|
||||
if (m_playerAttached) {
|
||||
GBASDLPlayerLoadConfig(&m_sdlPlayer, m_config->input());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void InputController::loadProfile(uint32_t type, const QString& profile) {
|
||||
|
@ -281,13 +286,18 @@ void InputController::testGamepad() {
|
|||
GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, this);
|
||||
if (newlyAboveThreshold) {
|
||||
postPendingEvent(event->gbaKey());
|
||||
QApplication::sendEvent(QApplication::focusWidget(), event);
|
||||
if (!event->isAccepted()) {
|
||||
clearPendingEvent(event->gbaKey());
|
||||
}
|
||||
} else if (oldAxes.contains(axis)) {
|
||||
clearPendingEvent(event->gbaKey());
|
||||
QApplication::sendEvent(QApplication::focusWidget(), event);
|
||||
}
|
||||
QApplication::sendEvent(QApplication::focusWidget(), event);
|
||||
}
|
||||
|
||||
if (!QApplication::focusWidget()) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeButtons.subtract(oldButtons);
|
||||
|
|
|
@ -15,6 +15,7 @@ using namespace QGBA;
|
|||
KeyEditor::KeyEditor(QWidget* parent)
|
||||
: QLineEdit(parent)
|
||||
, m_direction(GamepadAxisEvent::NEUTRAL)
|
||||
, m_button(false)
|
||||
{
|
||||
setAlignment(Qt::AlignCenter);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,12 @@ LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent)
|
|||
m_slots[i]->installEventFilter(this);
|
||||
connect(m_slots[i], &QAbstractButton::clicked, this, [this, i]() { triggerState(i + 1); });
|
||||
}
|
||||
|
||||
QAction* escape = new QAction(this);
|
||||
escape->connect(escape, SIGNAL(triggered()), this, SLOT(close()));
|
||||
escape->setShortcut(QKeySequence("Esc"));
|
||||
escape->setShortcutContext(Qt::WidgetWithChildrenShortcut);
|
||||
addAction(escape);
|
||||
}
|
||||
|
||||
void LoadSaveState::setMode(LoadSave mode) {
|
||||
|
@ -80,9 +86,6 @@ bool LoadSaveState::eventFilter(QObject* object, QEvent* event) {
|
|||
case Qt::Key_9:
|
||||
triggerState(static_cast<QKeyEvent*>(event)->key() - Qt::Key_1 + 1);
|
||||
break;
|
||||
case Qt::Key_Escape:
|
||||
close();
|
||||
break;
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Return:
|
||||
triggerState(m_currentFocus + 1);
|
||||
|
|
|
@ -47,7 +47,6 @@ private:
|
|||
|
||||
Ui::LoadSaveState m_ui;
|
||||
GameController* m_controller;
|
||||
InputController* m_inputController;
|
||||
SavestateButton* m_slots[NUM_SLOTS];
|
||||
LoadSave m_mode;
|
||||
|
||||
|
|
|
@ -12,6 +12,9 @@ using namespace QGBA;
|
|||
|
||||
LogView::LogView(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_logLevel(0)
|
||||
, m_lines(0)
|
||||
, m_lineLimit(DEFAULT_LINE_LIMIT)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
connect(m_ui.levelDebug, SIGNAL(toggled(bool)), this, SLOT(setLevelDebug(bool)));
|
||||
|
@ -24,8 +27,6 @@ LogView::LogView(QWidget* parent)
|
|||
connect(m_ui.levelSWI, SIGNAL(toggled(bool)), this, SLOT(setLevelSWI(bool)));
|
||||
connect(m_ui.clear, SIGNAL(clicked()), this, SLOT(clear()));
|
||||
connect(m_ui.maxLines, SIGNAL(valueChanged(int)), this, SLOT(setMaxLines(int)));
|
||||
m_logLevel = 0;
|
||||
m_lines = 0;
|
||||
m_ui.maxLines->setValue(DEFAULT_LINE_LIMIT);
|
||||
}
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
|
|||
|
||||
QGLFormat format(QGLFormat(QGL::Rgba | QGL::DoubleBuffer));
|
||||
format.setSwapInterval(1);
|
||||
m_display = new Display(format);
|
||||
m_display = new Display(format, this);
|
||||
|
||||
m_logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio());
|
||||
m_logo = m_logo; // Free memory left over in old pixmap
|
||||
|
@ -196,7 +196,14 @@ void Window::saveConfig() {
|
|||
}
|
||||
|
||||
void Window::selectROM() {
|
||||
bool doPause = m_controller->isLoaded() && !m_controller->isPaused();
|
||||
if (doPause) {
|
||||
m_controller->setPaused(true);
|
||||
}
|
||||
QString filename = QFileDialog::getOpenFileName(this, tr("Select ROM"), m_config->getQtOption("lastDirectory").toString(), tr("Game Boy Advance ROMs (*.gba *.zip *.rom *.bin)"));
|
||||
if (doPause) {
|
||||
m_controller->setPaused(false);
|
||||
}
|
||||
if (!filename.isEmpty()) {
|
||||
m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path());
|
||||
m_controller->loadGame(filename);
|
||||
|
@ -204,7 +211,14 @@ void Window::selectROM() {
|
|||
}
|
||||
|
||||
void Window::selectBIOS() {
|
||||
bool doPause = m_controller->isLoaded() && !m_controller->isPaused();
|
||||
if (doPause) {
|
||||
m_controller->setPaused(true);
|
||||
}
|
||||
QString filename = QFileDialog::getOpenFileName(this, tr("Select BIOS"), m_config->getQtOption("lastDirectory").toString());
|
||||
if (doPause) {
|
||||
m_controller->setPaused(false);
|
||||
}
|
||||
if (!filename.isEmpty()) {
|
||||
m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path());
|
||||
m_config->setOption("bios", filename);
|
||||
|
@ -216,7 +230,14 @@ void Window::selectBIOS() {
|
|||
}
|
||||
|
||||
void Window::selectPatch() {
|
||||
bool doPause = m_controller->isLoaded() && !m_controller->isPaused();
|
||||
if (doPause) {
|
||||
m_controller->setPaused(true);
|
||||
}
|
||||
QString filename = QFileDialog::getOpenFileName(this, tr("Select patch"), m_config->getQtOption("lastDirectory").toString(), tr("Patches (*.ips *.ups *.bps)"));
|
||||
if (doPause) {
|
||||
m_controller->setPaused(false);
|
||||
}
|
||||
if (!filename.isEmpty()) {
|
||||
m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path());
|
||||
m_controller->loadPatch(filename);
|
||||
|
@ -497,8 +518,14 @@ void Window::recordFrame() {
|
|||
}
|
||||
|
||||
void Window::showFPS() {
|
||||
char title[13] = { '\0' };
|
||||
GBAGetGameTitle(m_controller->thread()->gba, title);
|
||||
char gameTitle[13] = { '\0' };
|
||||
GBAGetGameTitle(m_controller->thread()->gba, gameTitle);
|
||||
|
||||
QString title(gameTitle);
|
||||
std::shared_ptr<MultiplayerController> multiplayer = m_controller->multiplayerController();
|
||||
if (multiplayer && multiplayer->attached() > 1) {
|
||||
title += tr(" - Player %1 of %2").arg(m_playerId + 1).arg(multiplayer->attached());
|
||||
}
|
||||
if (m_frameList.isEmpty()) {
|
||||
setWindowTitle(tr(PROJECT_NAME " - %1").arg(title));
|
||||
return;
|
||||
|
@ -520,7 +547,7 @@ void Window::openStateWindow(LoadSave ls) {
|
|||
connect(m_stateWindow, &LoadSaveState::closed, [this]() {
|
||||
m_screenWidget->layout()->removeWidget(m_stateWindow);
|
||||
m_stateWindow = nullptr;
|
||||
setFocus();
|
||||
QMetaObject::invokeMethod(this, "setFocus", Qt::QueuedConnection);
|
||||
});
|
||||
if (!wasPaused) {
|
||||
m_controller->setPaused(true);
|
||||
|
@ -585,9 +612,14 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
}
|
||||
Window* w2 = new Window(m_config, multiplayer->attached());
|
||||
w2->setAttribute(Qt::WA_DeleteOnClose);
|
||||
#ifndef Q_OS_MAC
|
||||
w2->show();
|
||||
#endif
|
||||
w2->loadConfig();
|
||||
w2->controller()->setMultiplayerController(multiplayer);
|
||||
#ifdef Q_OS_MAC
|
||||
w2->show();
|
||||
#endif
|
||||
});
|
||||
addControlledAction(fileMenu, multiWindow, "multiWindow");
|
||||
|
||||
|
@ -662,6 +694,26 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
}, this);
|
||||
m_config->updateOption("audioSync");
|
||||
|
||||
emulationMenu->addSeparator();
|
||||
|
||||
QMenu* solarMenu = emulationMenu->addMenu(tr("Solar sensor"));
|
||||
m_shortcutController->addMenu(solarMenu);
|
||||
QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu);
|
||||
connect(solarIncrease, SIGNAL(triggered()), m_controller, SLOT(increaseLuminanceLevel()));
|
||||
addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel");
|
||||
|
||||
QAction* solarDecrease = new QAction(tr("Decrease solar level"), solarMenu);
|
||||
connect(solarDecrease, SIGNAL(triggered()), m_controller, SLOT(decreaseLuminanceLevel()));
|
||||
addControlledAction(solarMenu, solarDecrease, "decreaseLuminanceLevel");
|
||||
|
||||
QAction* maxSolar = new QAction(tr("Brightest solar level"), solarMenu);
|
||||
connect(maxSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(10); });
|
||||
addControlledAction(solarMenu, maxSolar, "maxLuminanceLevel");
|
||||
|
||||
QAction* minSolar = new QAction(tr("Darkest solar level"), solarMenu);
|
||||
connect(minSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(0); });
|
||||
addControlledAction(solarMenu, minSolar, "minLuminanceLevel");
|
||||
|
||||
QMenu* avMenu = menubar->addMenu(tr("Audio/&Video"));
|
||||
m_shortcutController->addMenu(avMenu);
|
||||
QMenu* frameMenu = avMenu->addMenu(tr("Frame size"));
|
||||
|
@ -672,7 +724,7 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
showNormal();
|
||||
resizeFrame(VIDEO_HORIZONTAL_PIXELS * i, VIDEO_VERTICAL_PIXELS * i);
|
||||
});
|
||||
addControlledAction(frameMenu, setSize, tr("frame%1x").arg(QString::number(i)));
|
||||
addControlledAction(frameMenu, setSize, QString("frame%1x").arg(QString::number(i)));
|
||||
}
|
||||
addControlledAction(frameMenu, frameMenu->addAction(tr("Fullscreen"), this, SLOT(toggleFullScreen()), QKeySequence("Ctrl+F")), "fullscreen");
|
||||
|
||||
|
@ -716,7 +768,7 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
|
||||
avMenu->addSeparator();
|
||||
|
||||
QMenu* target = avMenu->addMenu("FPS target");
|
||||
QMenu* target = avMenu->addMenu(tr("FPS target"));
|
||||
ConfigOption* fpsTargetOption = m_config->addOption("fpsTarget");
|
||||
fpsTargetOption->connect([this](const QVariant& value) {
|
||||
emit fpsTargetChanged(value.toInt());
|
||||
|
@ -780,23 +832,6 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
addControlledAction(toolsMenu, gdbWindow, "gdbWindow");
|
||||
#endif
|
||||
|
||||
QMenu* solarMenu = toolsMenu->addMenu(tr("Solar sensor"));
|
||||
QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu);
|
||||
connect(solarIncrease, SIGNAL(triggered()), m_controller, SLOT(increaseLuminanceLevel()));
|
||||
addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel");
|
||||
|
||||
QAction* solarDecrease = new QAction(tr("Decrease solar level"), solarMenu);
|
||||
connect(solarDecrease, SIGNAL(triggered()), m_controller, SLOT(decreaseLuminanceLevel()));
|
||||
addControlledAction(solarMenu, solarDecrease, "decreaseLuminanceLevel");
|
||||
|
||||
QAction* maxSolar = new QAction(tr("Brightest solar level"), solarMenu);
|
||||
connect(maxSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(10); });
|
||||
addControlledAction(solarMenu, maxSolar, "maxLuminanceLevel");
|
||||
|
||||
QAction* minSolar = new QAction(tr("Darkest solar level"), solarMenu);
|
||||
connect(minSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(0); });
|
||||
addControlledAction(solarMenu, minSolar, "minLuminanceLevel");
|
||||
|
||||
toolsMenu->addSeparator();
|
||||
addControlledAction(toolsMenu, toolsMenu->addAction(tr("Settings..."), this, SLOT(openSettingsWindow())), "settings");
|
||||
addControlledAction(toolsMenu, toolsMenu->addAction(tr("Edit shortcuts..."), this, SLOT(openShortcutWindow())), "shortcuts");
|
||||
|
@ -898,6 +933,7 @@ void Window::updateMRU() {
|
|||
QAction* Window::addControlledAction(QMenu* menu, QAction* action, const QString& name) {
|
||||
m_shortcutController->addAction(menu, action, name);
|
||||
menu->addAction(action);
|
||||
action->setShortcutContext(Qt::WidgetShortcut);
|
||||
addAction(action);
|
||||
return action;
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ include_directories(${CMAKE_SOURCE_DIR}/src/platform/sdl ${PIXMAN-1_INCLUDE_DIR}
|
|||
|
||||
set(SDL_INCLUDE_DIR "${SDL_INCLUDE_DIR}" PARENT_SCOPE)
|
||||
set(SDL_LIBRARY "${SDL_LIBRARY}" PARENT_SCOPE)
|
||||
set(SDLMAIN_LIBRARY "${SDLMAIN_LIBRARY}" PARENT_SCOPE)
|
||||
|
||||
set(MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/main.c)
|
||||
|
||||
|
|
|
@ -190,7 +190,7 @@ void GBASDLPlayerLoadConfig(struct GBASDLPlayer* context, const struct Configura
|
|||
}
|
||||
|
||||
void GBASDLPlayerChangeJoystick(struct GBASDLEvents* events, struct GBASDLPlayer* player, size_t index) {
|
||||
if (player->playerId > MAX_PLAYERS || index >= events->nJoysticks) {
|
||||
if (player->playerId >= MAX_PLAYERS || index >= events->nJoysticks) {
|
||||
return;
|
||||
}
|
||||
events->joysticksClaimed[player->playerId] = index;
|
||||
|
|
|
@ -53,10 +53,14 @@ void ConfigurationSetValue(struct Configuration* configuration, const char* sect
|
|||
struct Table* currentSection = &configuration->root;
|
||||
if (section) {
|
||||
currentSection = HashTableLookup(&configuration->sections, section);
|
||||
if (!currentSection && value) {
|
||||
currentSection = malloc(sizeof(*currentSection));
|
||||
HashTableInit(currentSection, 0, _sectionDeinit);
|
||||
HashTableInsert(&configuration->sections, section, currentSection);
|
||||
if (!currentSection) {
|
||||
if (value) {
|
||||
currentSection = malloc(sizeof(*currentSection));
|
||||
HashTableInit(currentSection, 0, _sectionDeinit);
|
||||
HashTableInsert(&configuration->sections, section, currentSection);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value) {
|
||||
|
|
|
@ -178,13 +178,14 @@ char* utf16to8(const uint16_t* utf16, size_t length) {
|
|||
offset = utf8 + bytes;
|
||||
} else if (utf8Length >= utf8TotalBytes) {
|
||||
char* newUTF8 = realloc(utf8, utf8TotalBytes * 2);
|
||||
offset = offset - utf8 + newUTF8;
|
||||
if (newUTF8 != utf8) {
|
||||
free(utf8);
|
||||
}
|
||||
if (!newUTF8) {
|
||||
return 0;
|
||||
}
|
||||
offset = offset - utf8 + newUTF8;
|
||||
utf8 = newUTF8;
|
||||
memcpy(offset, buffer, bytes);
|
||||
offset += bytes;
|
||||
}
|
||||
|
|
|
@ -197,6 +197,7 @@ struct VDir* VDirOpen(const char* path) {
|
|||
|
||||
struct VDirDE* vd = malloc(sizeof(struct VDirDE));
|
||||
if (!vd) {
|
||||
closedir(de);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -65,12 +65,12 @@ static struct VFile* _vd7zOpenFile(struct VDir* vd, const char* path, int mode);
|
|||
static const char* _vde7zName(struct VDirEntry* vde);
|
||||
|
||||
struct VDir* VDirOpen7z(const char* path, int flags) {
|
||||
struct VDir7z* vd = malloc(sizeof(struct VDir7z));
|
||||
|
||||
if (flags & O_WRONLY || flags & O_CREAT) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct VDir7z* vd = malloc(sizeof(struct VDir7z));
|
||||
|
||||
// What does any of this mean, Igor?
|
||||
if (InFile_Open(&vd->archiveStream.file, path)) {
|
||||
free(vd);
|
||||
|
|
|
@ -57,5 +57,6 @@ while [ $# -gt 0 ]; do
|
|||
sed -i~ "/^[^:]*: $/d" deb-temp/DEBIAN/control
|
||||
rm deb-temp/DEBIAN/control~
|
||||
dpkg-deb -b deb-temp $DEB
|
||||
rm -rf deb-temp
|
||||
shift
|
||||
done
|
||||
|
|
Loading…
Reference in New Issue