diff --git a/CHANGES b/CHANGES
index 95bd66cb1..1be8e51ba 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,14 +1,46 @@
-0.2.0: (Future)
+0.3.0: (Future)
+Features:
+ - Ability to hide individual background layers, or OBJs
+ - Ability to mute individual audio channels
+ - Palette viewer
+ - Volume control
+ - More shortcuts are editable (e.g. quick save/load, solar sensor)
+ - Rewind now shows the frame after rewinding
+ - Import/Export of GameShark/Action Replay snapshots
+ - Add "Step backwards" item for single increment rewind
+ - Deadzone estimation for game controllers
+ - Analog inputs can be used for shortcuts
+Bugfixes:
+ - GBA: Fix timers not updating timing when writing to only the reload register
+ - All: Fix sanitize-deb script not cleaning up after itself
+ - Qt: Fix Display object leak when closing a window
+ - Qt: Fix .deb dependencies
+ - GBA Audio: FIFOs should not poll DMAs that are not scheduled for audio
+ - Qt: Fix "QOpenGLContext::swapBuffers() called with non-exposed window" warning
+ - ARM7: Fix SWI and IRQ timings
+ - GBA Audio: Force audio FIFOs to 32-bit
+ - GBA Memory: Ensure changing the timing of a DMA reschedules it
+ - Qt: Fix window not regaining focus after exiting savestate window
+ - Qt: Fix regression where video would not record if the game had already started
+ - GBA: Fix rewind boundary conditions
+ - GBA: Add initial I/O register settings for background matrix registers
+ - Qt: Fix potential crash if a gamepad causes focus to change
+ - GBA Memory: Allow SRAM to be 64kB
+ - Qt: Fix controller axis querying
+ - GBA Memory: Improve Thumb open bus behavior
+ - GBA Memory: Fix 32-bit loads from unaddress cartridge space
+Misc:
+ - Qt: Show multiplayer numbers in window title
+
+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 +55,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,53 +63,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
+ - 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:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fafca9711..9e87c7d1e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,18 +18,19 @@ set(BUILD_STATIC OFF CACHE BOOL "Build a static library")
set(BUILD_SHARED ON CACHE BOOL "Build a shared library")
file(GLOB ARM_SRC ${CMAKE_SOURCE_DIR}/src/arm/*.c)
file(GLOB GBA_SRC ${CMAKE_SOURCE_DIR}/src/gba/*.c)
+file(GLOB GBA_CHEATS_SRC ${CMAKE_SOURCE_DIR}/src/gba/cheats/*.c)
file(GLOB GBA_RR_SRC ${CMAKE_SOURCE_DIR}/src/gba/rr/*.c)
file(GLOB GBA_SV_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/*.c)
file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs])
-file(GLOB VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/*.c)
file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/video-software.c)
file(GLOB SIO_SRC ${CMAKE_SOURCE_DIR}/src/gba/sio/*.c)
file(GLOB THIRD_PARTY_SRC ${CMAKE_SOURCE_DIR}/src/third-party/inih/*.c)
list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c)
+set(VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-mem.c)
source_group("ARM core" FILES ${ARM_SRC})
source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC} ${SIO_SRC})
-source_group("GBA supervisor" FILES ${GBA_SV_SRC} ${GBA_RR_SRC})
-source_group("Utilities" FILES ${UTIL_SRC} ${VFS_SRC}})
+source_group("GBA supervisor" FILES ${GBA_CHEATS_SRC} ${GBA_SV_SRC} ${GBA_RR_SRC})
+source_group("Utilities" FILES ${UTIL_SRC})
include_directories(${CMAKE_SOURCE_DIR}/src/arm)
include_directories(${CMAKE_SOURCE_DIR}/src)
@@ -71,9 +72,9 @@ endfunction()
# Version information
set(LIB_VERSION_MAJOR 0)
-set(LIB_VERSION_MINOR 2)
+set(LIB_VERSION_MINOR 3)
set(LIB_VERSION_PATCH 0)
-set(LIB_VERSION_ABI 0.2)
+set(LIB_VERSION_ABI 0.3)
set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH})
# Advanced settings
@@ -117,6 +118,7 @@ if(WIN32)
set(WIN32_VERSION "${LIB_VERSION_MAJOR},${LIB_VERSION_MINOR},${LIB_VERSION_PATCH}")
add_definitions(-D_WIN32_WINNT=0x0600)
list(APPEND OS_LIB ws2_32)
+ list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c)
file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/windows/*.c)
source_group("Windows-specific code" FILES ${OS_SRC})
else()
@@ -129,6 +131,7 @@ else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
endif()
+ list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c)
file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/posix/*.c)
source_group("POSIX-specific code" FILES ${OS_SRC})
endif()
@@ -262,12 +265,14 @@ if(USE_LIBZIP)
link_directories(${LIBZIP_LIBRARY_DIRS})
list(APPEND DEPENDENCY_LIB ${LIBZIP_LIBRARIES})
list(APPEND FEATURES LIBZIP)
+ list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-zip.c)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libzip2")
endif()
if (USE_LZMA)
include_directories(AFTER ${CMAKE_SOURCE_DIR}/third-party/lzma)
add_definitions(-D_7ZIP_PPMD_SUPPPORT)
+ list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-lzma.c)
set(LZMA_SRC
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zAlloc.c
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zArcIn.c
@@ -299,6 +304,7 @@ endforeach()
set(CORE_SRC
${ARM_SRC}
${GBA_SRC}
+ ${GBA_CHEATS_SRC}
${GBA_RR_SRC}
${GBA_SV_SRC}
${DEBUGGER_SRC}
diff --git a/README.md b/README.md
index 5de6c3b08..4953cf912 100644
--- a/README.md
+++ b/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,6 +13,7 @@ Features
- Near full Game Boy Advance hardware support[[1]](#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[[2]](#flashdetect).
- Real-time clock support, even without configuration.
- A built-in BIOS implementation, and ability to load external BIOS files.
@@ -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
[1] 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))
-[2] 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.
+[2] Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered.
[3] 10.7 is only needed for the Qt port. The SDL port is known to work on 10.6, and may work on older.
-[downloads]: https://endrift.com/mgba/downloads.html
+[downloads]: http://mgba.io/downloads.html
[source]: https://github.com/mgba-emu/mgba/
Copyright
diff --git a/src/arm/arm.c b/src/arm/arm.c
index 05cb65eef..b75274aa3 100644
--- a/src/arm/arm.c
+++ b/src/arm/arm.c
@@ -164,10 +164,10 @@ void ARMRaiseIRQ(struct ARMCore* cpu) {
cpu->gprs[ARM_PC] = BASE_IRQ;
int currentCycles = 0;
ARM_WRITE_PC;
- cpu->memory.setActiveRegion(cpu, cpu->gprs[ARM_PC]);
_ARMSetMode(cpu, MODE_ARM);
cpu->spsr = cpsr;
cpu->cpsr.i = 1;
+ cpu->cycles += currentCycles;
}
void ARMRaiseSWI(struct ARMCore* cpu) {
@@ -184,10 +184,10 @@ void ARMRaiseSWI(struct ARMCore* cpu) {
cpu->gprs[ARM_PC] = BASE_SWI;
int currentCycles = 0;
ARM_WRITE_PC;
- cpu->memory.setActiveRegion(cpu, cpu->gprs[ARM_PC]);
_ARMSetMode(cpu, MODE_ARM);
cpu->spsr = cpsr;
cpu->cpsr.i = 1;
+ cpu->cycles += currentCycles;
}
static inline void ARMStep(struct ARMCore* cpu) {
diff --git a/src/arm/isa-arm.c b/src/arm/isa-arm.c
index 6b0506af4..fe691b1d9 100644
--- a/src/arm/isa-arm.c
+++ b/src/arm/isa-arm.c
@@ -254,7 +254,7 @@ static inline void _immediate(struct ARMCore* cpu, uint32_t opcode) {
#define ADDR_MODE_4_WRITEBACK_STM cpu->gprs[rn] = address;
#define ARM_LOAD_POST_BODY \
- ++currentCycles; \
+ currentCycles += 1 + cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32; \
if (rd == ARM_PC) { \
ARM_WRITE_PC; \
}
@@ -562,14 +562,14 @@ DEFINE_LOAD_STORE_T_INSTRUCTION_ARM(STRT,
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_ARM(LDM,
load,
- ++currentCycles;
+ currentCycles += 1 + cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32;
if (rs & 0x8000) {
ARM_WRITE_PC;
})
DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_ARM(STM,
store,
- currentCycles += cpu->memory.activeNonseqCycles32 - cpu->memory.activeSeqCycles32)
+ ARM_STORE_POST_BODY;)
DEFINE_INSTRUCTION_ARM(SWP,
int rm = opcode & 0xF;
diff --git a/src/arm/isa-thumb.c b/src/arm/isa-thumb.c
index 3c12072fa..ef9ad3eca 100644
--- a/src/arm/isa-thumb.c
+++ b/src/arm/isa-thumb.c
@@ -41,7 +41,8 @@
#define THUMB_PREFETCH_CYCLES (1 + cpu->memory.activeSeqCycles16)
-#define THUMB_LOAD_POST_BODY ++currentCycles;
+#define THUMB_LOAD_POST_BODY \
+ currentCycles += 1 + cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16;
#define THUMB_STORE_POST_BODY \
currentCycles += cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16;
diff --git a/src/gba/audio.c b/src/gba/audio.c
index dce744a45..f0e06c117 100644
--- a/src/gba/audio.c
+++ b/src/gba/audio.c
@@ -14,6 +14,7 @@
const unsigned GBA_AUDIO_SAMPLES = 2048;
const unsigned BLIP_BUFFER_SIZE = 0x4000;
const unsigned GBA_AUDIO_FIFO_SIZE = 8 * sizeof(int32_t);
+const int GBA_AUDIO_VOLUME_MAX = 0x100;
#define SWEEP_CYCLES (GBA_ARM7TDMI_FREQUENCY / 128)
static bool _writeEnvelope(struct GBAAudioEnvelope* envelope, uint16_t value);
@@ -41,6 +42,14 @@ void GBAAudioInit(struct GBAAudio* audio, size_t samples) {
#endif
CircleBufferInit(&audio->chA.fifo, GBA_AUDIO_FIFO_SIZE);
CircleBufferInit(&audio->chB.fifo, GBA_AUDIO_FIFO_SIZE);
+
+ audio->forceDisableCh[0] = false;
+ audio->forceDisableCh[1] = false;
+ audio->forceDisableCh[2] = false;
+ audio->forceDisableCh[3] = false;
+ audio->forceDisableChA = false;
+ audio->forceDisableChB = false;
+ audio->masterVolume = GBA_AUDIO_VOLUME_MAX;
}
void GBAAudioReset(struct GBAAudio* audio) {
@@ -480,30 +489,6 @@ void GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value) {
}
}
-void GBAAudioWriteFIFO16(struct GBAAudio* audio, int address, uint16_t value) {
- struct CircleBuffer* fifo;
- switch (address) {
- case REG_FIFO_A_LO:
- case REG_FIFO_A_HI:
- fifo = &audio->chA.fifo;
- break;
- case REG_FIFO_B_LO:
- case REG_FIFO_B_HI:
- fifo = &audio->chB.fifo;
- break;
- default:
- GBALog(audio->p, GBA_LOG_ERROR, "Bad FIFO write to address 0x%03x", address);
- return;
- }
- int i;
- for (i = 0; i < 2; ++i) {
- while (!CircleBufferWrite8(fifo, value >> (8 * i))) {
- int8_t dummy;
- CircleBufferRead8(fifo, &dummy);
- }
- }
-}
-
void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) {
struct GBAAudioFIFO* channel;
if (fifoId == 0) {
@@ -514,11 +499,16 @@ 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;
+ dma->reg = GBADMARegisterSetWidth(dma->reg, 1);
+ GBAMemoryUpdateDMAs(audio->p, -cycles);
+ } else {
+ channel->dmaSource = 0;
+ }
}
CircleBufferRead8(&channel->fifo, &channel->sample);
}
@@ -738,7 +728,7 @@ static int _applyBias(struct GBAAudio* audio, int sample) {
} else if (sample < 0) {
sample = 0;
}
- return (sample - GBARegisterSOUNDBIASGetBias(audio->soundbias)) << 5;
+ return ((sample - GBARegisterSOUNDBIASGetBias(audio->soundbias)) * audio->masterVolume) >> 3;
}
static void _sample(struct GBAAudio* audio) {
@@ -746,55 +736,67 @@ static void _sample(struct GBAAudio* audio) {
int16_t sampleRight = 0;
int psgShift = 5 - audio->volume;
- if (audio->ch1Left) {
- sampleLeft += audio->ch1.sample;
+ if (audio->playingCh1 && !audio->forceDisableCh[0]) {
+ if (audio->ch1Left) {
+ sampleLeft += audio->ch1.sample;
+ }
+
+ if (audio->ch1Right) {
+ sampleRight += audio->ch1.sample;
+ }
}
- if (audio->ch1Right) {
- sampleRight += audio->ch1.sample;
+ if (audio->playingCh2 && !audio->forceDisableCh[1]) {
+ if (audio->ch2Left) {
+ sampleLeft += audio->ch2.sample;
+ }
+
+ if (audio->ch2Right) {
+ sampleRight += audio->ch2.sample;
+ }
}
- if (audio->ch2Left) {
- sampleLeft += audio->ch2.sample;
+ if (audio->playingCh3 && !audio->forceDisableCh[2]) {
+ if (audio->ch3Left) {
+ sampleLeft += audio->ch3.sample;
+ }
+
+ if (audio->ch3Right) {
+ sampleRight += audio->ch3.sample;
+ }
}
- if (audio->ch2Right) {
- sampleRight += audio->ch2.sample;
- }
+ if (audio->playingCh4 && !audio->forceDisableCh[3]) {
+ if (audio->ch4Left) {
+ sampleLeft += audio->ch4.sample;
+ }
- if (audio->ch3Left) {
- sampleLeft += audio->ch3.sample;
- }
-
- if (audio->ch3Right) {
- sampleRight += audio->ch3.sample;
- }
-
- if (audio->ch4Left) {
- sampleLeft += audio->ch4.sample;
- }
-
- if (audio->ch4Right) {
- sampleRight += audio->ch4.sample;
+ if (audio->ch4Right) {
+ sampleRight += audio->ch4.sample;
+ }
}
sampleLeft = (sampleLeft * (1 + audio->volumeLeft)) >> psgShift;
sampleRight = (sampleRight * (1 + audio->volumeRight)) >> psgShift;
- if (audio->chALeft) {
- sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA;
+ if (!audio->forceDisableChA) {
+ if (audio->chALeft) {
+ sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA;
+ }
+
+ if (audio->chARight) {
+ sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA;
+ }
}
- if (audio->chARight) {
- sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA;
- }
+ if (!audio->forceDisableChB) {
+ if (audio->chBLeft) {
+ sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB;
+ }
- if (audio->chBLeft) {
- sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB;
- }
-
- if (audio->chBRight) {
- sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB;
+ if (audio->chBRight) {
+ sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB;
+ }
}
sampleLeft = _applyBias(audio, sampleLeft);
diff --git a/src/gba/audio.h b/src/gba/audio.h
index 2aa5953e5..dc16d4631 100644
--- a/src/gba/audio.h
+++ b/src/gba/audio.h
@@ -11,6 +11,9 @@
#include "util/circle-buffer.h"
+#define RESAMPLE_NN 0
+#define RESAMPLE_BLIP_BUF 2
+
#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
#include "third-party/blip_buf/blip_buf.h"
#endif
@@ -18,9 +21,7 @@
struct GBADMA;
extern const unsigned GBA_AUDIO_SAMPLES;
-
-#define RESAMPLE_NN 0
-#define RESAMPLE_BLIP_BUF 2
+extern const int GBA_AUDIO_VOLUME_MAX;
DECL_BITFIELD(GBAAudioRegisterEnvelope, uint16_t);
DECL_BITS(GBAAudioRegisterEnvelope, Length, 0, 6);
@@ -236,6 +237,11 @@ struct GBAAudio {
int32_t nextSample;
int32_t sampleInterval;
+
+ bool forceDisableCh[4];
+ bool forceDisableChA;
+ bool forceDisableChB;
+ int masterVolume;
};
struct GBAStereoSample {
@@ -268,7 +274,6 @@ void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value);
void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value);
void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value);
-void GBAAudioWriteFIFO16(struct GBAAudio* audio, int address, uint16_t value);
void GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value);
void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles);
diff --git a/src/gba/cheats.c b/src/gba/cheats.c
index d5584d9d5..ee7820174 100644
--- a/src/gba/cheats.c
+++ b/src/gba/cheats.c
@@ -5,8 +5,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "cheats.h"
+#include "gba/cheats/gameshark.h"
+#include "gba/cheats/parv3.h"
#include "gba/gba.h"
-#include "gba/io.h"
+#include "util/string.h"
#include "util/vfs.h"
#define MAX_LINE_LENGTH 128
@@ -17,85 +19,6 @@ DEFINE_VECTOR(GBACheatList, struct GBACheat);
DEFINE_VECTOR(GBACheatSets, struct GBACheatSet*);
DEFINE_VECTOR(StringList, char*);
-static const uint32_t _gsa1S[4] = { 0x09F4FBBD, 0x9681884A, 0x352027E9, 0xF3DEE5A7 };
-static const uint32_t _par3S[4] = { 0x7AA9648F, 0x7FAE6994, 0xC0EFAAD5, 0x42712C57 };
-
-static const uint8_t _gsa1T1[256] = {
- 0x31, 0x1C, 0x23, 0xE5, 0x89, 0x8E, 0xA1, 0x37, 0x74, 0x6D, 0x67, 0xFC, 0x1F, 0xC0, 0xB1, 0x94,
- 0x3B, 0x05, 0x56, 0x86, 0x00, 0x24, 0xF0, 0x17, 0x72, 0xA2, 0x3D, 0x1B, 0xE3, 0x17, 0xC5, 0x0B,
- 0xB9, 0xE2, 0xBD, 0x58, 0x71, 0x1B, 0x2C, 0xFF, 0xE4, 0xC9, 0x4C, 0x5E, 0xC9, 0x55, 0x33, 0x45,
- 0x7C, 0x3F, 0xB2, 0x51, 0xFE, 0x10, 0x7E, 0x75, 0x3C, 0x90, 0x8D, 0xDA, 0x94, 0x38, 0xC3, 0xE9,
- 0x95, 0xEA, 0xCE, 0xA6, 0x06, 0xE0, 0x4F, 0x3F, 0x2A, 0xE3, 0x3A, 0xE4, 0x43, 0xBD, 0x7F, 0xDA,
- 0x55, 0xF0, 0xEA, 0xCB, 0x2C, 0xA8, 0x47, 0x61, 0xA0, 0xEF, 0xCB, 0x13, 0x18, 0x20, 0xAF, 0x3E,
- 0x4D, 0x9E, 0x1E, 0x77, 0x51, 0xC5, 0x51, 0x20, 0xCF, 0x21, 0xF9, 0x39, 0x94, 0xDE, 0xDD, 0x79,
- 0x4E, 0x80, 0xC4, 0x9D, 0x94, 0xD5, 0x95, 0x01, 0x27, 0x27, 0xBD, 0x6D, 0x78, 0xB5, 0xD1, 0x31,
- 0x6A, 0x65, 0x74, 0x74, 0x58, 0xB3, 0x7C, 0xC9, 0x5A, 0xED, 0x50, 0x03, 0xC4, 0xA2, 0x94, 0x4B,
- 0xF0, 0x58, 0x09, 0x6F, 0x3E, 0x7D, 0xAE, 0x7D, 0x58, 0xA0, 0x2C, 0x91, 0xBB, 0xE1, 0x70, 0xEB,
- 0x73, 0xA6, 0x9A, 0x44, 0x25, 0x90, 0x16, 0x62, 0x53, 0xAE, 0x08, 0xEB, 0xDC, 0xF0, 0xEE, 0x77,
- 0xC2, 0xDE, 0x81, 0xE8, 0x30, 0x89, 0xDB, 0xFE, 0xBC, 0xC2, 0xDF, 0x26, 0xE9, 0x8B, 0xD6, 0x93,
- 0xF0, 0xCB, 0x56, 0x90, 0xC0, 0x46, 0x68, 0x15, 0x43, 0xCB, 0xE9, 0x98, 0xE3, 0xAF, 0x31, 0x25,
- 0x4D, 0x7B, 0xF3, 0xB1, 0x74, 0xE2, 0x64, 0xAC, 0xD9, 0xF6, 0xA0, 0xD5, 0x0B, 0x9B, 0x49, 0x52,
- 0x69, 0x3B, 0x71, 0x00, 0x2F, 0xBB, 0xBA, 0x08, 0xB1, 0xAE, 0xBB, 0xB3, 0xE1, 0xC9, 0xA6, 0x7F,
- 0x17, 0x97, 0x28, 0x72, 0x12, 0x6E, 0x91, 0xAE, 0x3A, 0xA2, 0x35, 0x46, 0x27, 0xF8, 0x12, 0x50
-};
-
-static const uint8_t _gsa1T2[256] = {
- 0xD8, 0x65, 0x04, 0xC2, 0x65, 0xD5, 0xB0, 0x0C, 0xDF, 0x9D, 0xF0, 0xC3, 0x9A, 0x17, 0xC9, 0xA6,
- 0xE1, 0xAC, 0x0D, 0x14, 0x2F, 0x3C, 0x2C, 0x87, 0xA2, 0xBF, 0x4D, 0x5F, 0xAC, 0x2D, 0x9D, 0xE1,
- 0x0C, 0x9C, 0xE7, 0x7F, 0xFC, 0xA8, 0x66, 0x59, 0xAC, 0x18, 0xD7, 0x05, 0xF0, 0xBF, 0xD1, 0x8B,
- 0x35, 0x9F, 0x59, 0xB4, 0xBA, 0x55, 0xB2, 0x85, 0xFD, 0xB1, 0x72, 0x06, 0x73, 0xA4, 0xDB, 0x48,
- 0x7B, 0x5F, 0x67, 0xA5, 0x95, 0xB9, 0xA5, 0x4A, 0xCF, 0xD1, 0x44, 0xF3, 0x81, 0xF5, 0x6D, 0xF6,
- 0x3A, 0xC3, 0x57, 0x83, 0xFA, 0x8E, 0x15, 0x2A, 0xA2, 0x04, 0xB2, 0x9D, 0xA8, 0x0D, 0x7F, 0xB8,
- 0x0F, 0xF6, 0xAC, 0xBE, 0x97, 0xCE, 0x16, 0xE6, 0x31, 0x10, 0x60, 0x16, 0xB5, 0x83, 0x45, 0xEE,
- 0xD7, 0x5F, 0x2C, 0x08, 0x58, 0xB1, 0xFD, 0x7E, 0x79, 0x00, 0x34, 0xAD, 0xB5, 0x31, 0x34, 0x39,
- 0xAF, 0xA8, 0xDD, 0x52, 0x6A, 0xB0, 0x60, 0x35, 0xB8, 0x1D, 0x52, 0xF5, 0xF5, 0x30, 0x00, 0x7B,
- 0xF4, 0xBA, 0x03, 0xCB, 0x3A, 0x84, 0x14, 0x8A, 0x6A, 0xEF, 0x21, 0xBD, 0x01, 0xD8, 0xA0, 0xD4,
- 0x43, 0xBE, 0x23, 0xE7, 0x76, 0x27, 0x2C, 0x3F, 0x4D, 0x3F, 0x43, 0x18, 0xA7, 0xC3, 0x47, 0xA5,
- 0x7A, 0x1D, 0x02, 0x55, 0x09, 0xD1, 0xFF, 0x55, 0x5E, 0x17, 0xA0, 0x56, 0xF4, 0xC9, 0x6B, 0x90,
- 0xB4, 0x80, 0xA5, 0x07, 0x22, 0xFB, 0x22, 0x0D, 0xD9, 0xC0, 0x5B, 0x08, 0x35, 0x05, 0xC1, 0x75,
- 0x4F, 0xD0, 0x51, 0x2D, 0x2E, 0x5E, 0x69, 0xE7, 0x3B, 0xC2, 0xDA, 0xFF, 0xF6, 0xCE, 0x3E, 0x76,
- 0xE8, 0x36, 0x8C, 0x39, 0xD8, 0xF3, 0xE9, 0xA6, 0x42, 0xE6, 0xC1, 0x4C, 0x05, 0xBE, 0x17, 0xF2,
- 0x5C, 0x1B, 0x19, 0xDB, 0x0F, 0xF3, 0xF8, 0x49, 0xEB, 0x36, 0xF6, 0x40, 0x6F, 0xAD, 0xC1, 0x8C
-};
-
-static const uint8_t _par3T1[256] = {
- 0xD0, 0xFF, 0xBA, 0xE5, 0xC1, 0xC7, 0xDB, 0x5B, 0x16, 0xE3, 0x6E, 0x26, 0x62, 0x31, 0x2E, 0x2A,
- 0xD1, 0xBB, 0x4A, 0xE6, 0xAE, 0x2F, 0x0A, 0x90, 0x29, 0x90, 0xB6, 0x67, 0x58, 0x2A, 0xB4, 0x45,
- 0x7B, 0xCB, 0xF0, 0x73, 0x84, 0x30, 0x81, 0xC2, 0xD7, 0xBE, 0x89, 0xD7, 0x4E, 0x73, 0x5C, 0xC7,
- 0x80, 0x1B, 0xE5, 0xE4, 0x43, 0xC7, 0x46, 0xD6, 0x6F, 0x7B, 0xBF, 0xED, 0xE5, 0x27, 0xD1, 0xB5,
- 0xD0, 0xD8, 0xA3, 0xCB, 0x2B, 0x30, 0xA4, 0xF0, 0x84, 0x14, 0x72, 0x5C, 0xFF, 0xA4, 0xFB, 0x54,
- 0x9D, 0x70, 0xE2, 0xFF, 0xBE, 0xE8, 0x24, 0x76, 0xE5, 0x15, 0xFB, 0x1A, 0xBC, 0x87, 0x02, 0x2A,
- 0x58, 0x8F, 0x9A, 0x95, 0xBD, 0xAE, 0x8D, 0x0C, 0xA5, 0x4C, 0xF2, 0x5C, 0x7D, 0xAD, 0x51, 0xFB,
- 0xB1, 0x22, 0x07, 0xE0, 0x29, 0x7C, 0xEB, 0x98, 0x14, 0xC6, 0x31, 0x97, 0xE4, 0x34, 0x8F, 0xCC,
- 0x99, 0x56, 0x9F, 0x78, 0x43, 0x91, 0x85, 0x3F, 0xC2, 0xD0, 0xD1, 0x80, 0xD1, 0x77, 0xA7, 0xE2,
- 0x43, 0x99, 0x1D, 0x2F, 0x8B, 0x6A, 0xE4, 0x66, 0x82, 0xF7, 0x2B, 0x0B, 0x65, 0x14, 0xC0, 0xC2,
- 0x1D, 0x96, 0x78, 0x1C, 0xC4, 0xC3, 0xD2, 0xB1, 0x64, 0x07, 0xD7, 0x6F, 0x02, 0xE9, 0x44, 0x31,
- 0xDB, 0x3C, 0xEB, 0x93, 0xED, 0x9A, 0x57, 0x05, 0xB9, 0x0E, 0xAF, 0x1F, 0x48, 0x11, 0xDC, 0x35,
- 0x6C, 0xB8, 0xEE, 0x2A, 0x48, 0x2B, 0xBC, 0x89, 0x12, 0x59, 0xCB, 0xD1, 0x18, 0xEA, 0x72, 0x11,
- 0x01, 0x75, 0x3B, 0xB5, 0x56, 0xF4, 0x8B, 0xA0, 0x41, 0x75, 0x86, 0x7B, 0x94, 0x12, 0x2D, 0x4C,
- 0x0C, 0x22, 0xC9, 0x4A, 0xD8, 0xB1, 0x8D, 0xF0, 0x55, 0x2E, 0x77, 0x50, 0x1C, 0x64, 0x77, 0xAA,
- 0x3E, 0xAC, 0xD3, 0x3D, 0xCE, 0x60, 0xCA, 0x5D, 0xA0, 0x92, 0x78, 0xC6, 0x51, 0xFE, 0xF9, 0x30
-};
-
-static const uint8_t _par3T2[256] = {
- 0xAA, 0xAF, 0xF0, 0x72, 0x90, 0xF7, 0x71, 0x27, 0x06, 0x11, 0xEB, 0x9C, 0x37, 0x12, 0x72, 0xAA,
- 0x65, 0xBC, 0x0D, 0x4A, 0x76, 0xF6, 0x5C, 0xAA, 0xB0, 0x7A, 0x7D, 0x81, 0xC1, 0xCE, 0x2F, 0x9F,
- 0x02, 0x75, 0x38, 0xC8, 0xFC, 0x66, 0x05, 0xC2, 0x2C, 0xBD, 0x91, 0xAD, 0x03, 0xB1, 0x88, 0x93,
- 0x31, 0xC6, 0xAB, 0x40, 0x23, 0x43, 0x76, 0x54, 0xCA, 0xE7, 0x00, 0x96, 0x9F, 0xD8, 0x24, 0x8B,
- 0xE4, 0xDC, 0xDE, 0x48, 0x2C, 0xCB, 0xF7, 0x84, 0x1D, 0x45, 0xE5, 0xF1, 0x75, 0xA0, 0xED, 0xCD,
- 0x4B, 0x24, 0x8A, 0xB3, 0x98, 0x7B, 0x12, 0xB8, 0xF5, 0x63, 0x97, 0xB3, 0xA6, 0xA6, 0x0B, 0xDC,
- 0xD8, 0x4C, 0xA8, 0x99, 0x27, 0x0F, 0x8F, 0x94, 0x63, 0x0F, 0xB0, 0x11, 0x94, 0xC7, 0xE9, 0x7F,
- 0x3B, 0x40, 0x72, 0x4C, 0xDB, 0x84, 0x78, 0xFE, 0xB8, 0x56, 0x08, 0x80, 0xDF, 0x20, 0x2F, 0xB9,
- 0x66, 0x2D, 0x60, 0x63, 0xF5, 0x18, 0x15, 0x1B, 0x86, 0x85, 0xB9, 0xB4, 0x68, 0x0E, 0xC6, 0xD1,
- 0x8A, 0x81, 0x2B, 0xB3, 0xF6, 0x48, 0xF0, 0x4F, 0x9C, 0x28, 0x1C, 0xA4, 0x51, 0x2F, 0xD7, 0x4B,
- 0x17, 0xE7, 0xCC, 0x50, 0x9F, 0xD0, 0xD1, 0x40, 0x0C, 0x0D, 0xCA, 0x83, 0xFA, 0x5E, 0xCA, 0xEC,
- 0xBF, 0x4E, 0x7C, 0x8F, 0xF0, 0xAE, 0xC2, 0xD3, 0x28, 0x41, 0x9B, 0xC8, 0x04, 0xB9, 0x4A, 0xBA,
- 0x72, 0xE2, 0xB5, 0x06, 0x2C, 0x1E, 0x0B, 0x2C, 0x7F, 0x11, 0xA9, 0x26, 0x51, 0x9D, 0x3F, 0xF8,
- 0x62, 0x11, 0x2E, 0x89, 0xD2, 0x9D, 0x35, 0xB1, 0xE4, 0x0A, 0x4D, 0x93, 0x01, 0xA7, 0xD1, 0x2D,
- 0x00, 0x87, 0xE2, 0x2D, 0xA4, 0xE9, 0x0A, 0x06, 0x66, 0xF8, 0x1F, 0x44, 0x75, 0xB5, 0x6B, 0x1C,
- 0xFC, 0x31, 0x09, 0x48, 0xA3, 0xFF, 0x92, 0x12, 0x58, 0xE9, 0xFA, 0xAE, 0x4F, 0xE2, 0xB4, 0xCC
-};
-
static int32_t _readMem(struct ARMCore* cpu, uint32_t address, int width) {
switch (width) {
case 1:
@@ -122,225 +45,10 @@ static void _writeMem(struct ARMCore* cpu, uint32_t address, int width, int32_t
}
}
-static int _hexDigit(char digit) {
- switch (digit) {
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- return digit - '0';
-
- case 'a':
- case 'b':
- case 'c':
- case 'd':
- case 'e':
- case 'f':
- return digit - 'a' + 10;
-
- case 'A':
- case 'B':
- case 'C':
- case 'D':
- case 'E':
- case 'F':
- return digit - 'A' + 10;
-
- default:
- return -1;
- }
-}
-
-static const char* _hex32(const char* line, uint32_t* out) {
- uint32_t value = 0;
- int i;
- for (i = 0; i < 8; ++i, ++line) {
- char digit = *line;
- value <<= 4;
- int nybble = _hexDigit(digit);
- if (nybble < 0) {
- return 0;
- }
- value |= nybble;
- }
- *out = value;
- return line;
-}
-
-static const char* _hex16(const char* line, uint16_t* out) {
- uint16_t value = 0;
- *out = 0;
- int i;
- for (i = 0; i < 4; ++i, ++line) {
- char digit = *line;
- value <<= 4;
- int nybble = _hexDigit(digit);
- if (nybble < 0) {
- return 0;
- }
- value |= nybble;
- }
- *out = value;
- return line;
-}
-
-static void _registerLine(struct GBACheatSet* cheats, const char* line) {
+void GBACheatRegisterLine(struct GBACheatSet* cheats, const char* line) {
*StringListAppend(&cheats->lines) = strdup(line);
}
-// http://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm
-static void _decryptGameShark(uint32_t* op1, uint32_t* op2, const uint32_t* seeds) {
- uint32_t sum = 0xC6EF3720;
- int i;
- for (i = 0; i < 32; ++i) {
- *op2 -= ((*op1 << 4) + seeds[2]) ^ (*op1 + sum) ^ ((*op1 >> 5) + seeds[3]);
- *op1 -= ((*op2 << 4) + seeds[0]) ^ (*op2 + sum) ^ ((*op2 >> 5) + seeds[1]);
- sum -= 0x9E3779B9;
- }
-}
-
-static void _reseedGameShark(uint32_t* seeds, uint16_t params, const uint8_t* t1, const uint8_t* t2) {
- int x, y;
- int s0 = params >> 8;
- int s1 = params & 0xFF;
- for (y = 0; y < 4; ++y) {
- for (x = 0; x < 4; ++x) {
- uint8_t z = t1[(s0 + x) & 0xFF] + t2[(s1 + y) & 0xFF];
- seeds[y] <<= 8;
- seeds[y] |= z;
- }
- }
-}
-
-static void _setGameSharkVersion(struct GBACheatSet* cheats, int version) {
- cheats->gsaVersion = 1;
- switch (version) {
- case 1:
- memcpy(cheats->gsaSeeds, _gsa1S, 4 * sizeof(uint32_t));
- break;
- case 3:
- memcpy(cheats->gsaSeeds, _par3S, 4 * sizeof(uint32_t));
- break;
- }
-}
-
-static bool _addGSA1(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) {
- enum GBAGameSharkType type = op1 >> 28;
- struct GBACheat* cheat = 0;
-
- if (cheats->incompleteCheat) {
- if (cheats->remainingAddresses > 0) {
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_ASSIGN;
- cheat->width = 4;
- cheat->address = op1;
- cheat->operand = cheats->incompleteCheat->operand;
- cheat->repeat = 1;
- --cheats->remainingAddresses;
- }
- if (cheats->remainingAddresses > 0) {
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_ASSIGN;
- cheat->width = 4;
- cheat->address = op2;
- cheat->operand = cheats->incompleteCheat->operand;
- cheat->repeat = 1;
- --cheats->remainingAddresses;
- }
- if (cheats->remainingAddresses == 0) {
- cheats->incompleteCheat = 0;
- }
- return true;
- }
-
- switch (type) {
- case GSA_ASSIGN_1:
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_ASSIGN;
- cheat->width = 1;
- cheat->address = op1 & 0x0FFFFFFF;
- break;
- case GSA_ASSIGN_2:
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_ASSIGN;
- cheat->width = 2;
- cheat->address = op1 & 0x0FFFFFFF;
- break;
- case GSA_ASSIGN_4:
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_ASSIGN;
- cheat->width = 4;
- cheat->address = op1 & 0x0FFFFFFF;
- break;
- case GSA_ASSIGN_LIST:
- cheats->remainingAddresses = (op1 & 0xFFFF) - 1;
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_ASSIGN;
- cheat->width = 4;
- cheat->address = op2;
- cheats->incompleteCheat = cheat;
- break;
- case GSA_PATCH:
- cheats->romPatches[0].address = (op1 & 0xFFFFFF) << 1;
- cheats->romPatches[0].newValue = op2;
- cheats->romPatches[0].applied = false;
- cheats->romPatches[0].exists = true;
- return true;
- case GSA_BUTTON:
- // TODO: Implement button
- return false;
- case GSA_IF_EQ:
- if (op1 == 0xDEADFACE) {
- _reseedGameShark(cheats->gsaSeeds, op2, _gsa1T1, _gsa1T2);
- return true;
- }
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_IF_EQ;
- cheat->width = 2;
- cheat->address = op1 & 0x0FFFFFFF;
- break;
- case GSA_IF_EQ_RANGE:
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_IF_EQ;
- cheat->width = 2;
- cheat->address = op2 & 0x0FFFFFFF;
- cheat->operand = op1 & 0xFFFF;
- cheat->repeat = (op1 >> 16) & 0xFF;
- return true;
- case GSA_HOOK:
- if (cheats->hook) {
- return false;
- }
- cheats->hook = malloc(sizeof(*cheats->hook));
- cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1));
- cheats->hook->mode = MODE_THUMB;
- cheats->hook->refs = 1;
- cheats->hook->reentries = 0;
- return true;
- default:
- return false;
- }
- cheat->operand = op2;
- cheat->repeat = 1;
- return true;
-}
-
-static bool _addPAR3(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) {
- // TODO
- UNUSED(cheats);
- UNUSED(op1);
- UNUSED(op2);
- UNUSED(_par3T1);
- UNUSED(_par3T2);
- return false;
-}
-
static void _addBreakpoint(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
if (!device->p || !cheats->hook) {
return;
@@ -415,6 +123,8 @@ void GBACheatSetInit(struct GBACheatSet* set, const char* name) {
GBACheatListInit(&set->list, 4);
StringListInit(&set->lines, 4);
set->incompleteCheat = 0;
+ set->incompletePatch = 0;
+ set->currentBlock = 0;
set->gsaVersion = 0;
set->remainingAddresses = 0;
set->hook = 0;
@@ -476,201 +186,35 @@ void GBACheatRemoveSet(struct GBACheatDevice* device, struct GBACheatSet* cheats
_removeBreakpoint(device, cheats);
}
-bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t op2) {
- char line[14] = "XXXXXXXX XXXX";
- snprintf(line, sizeof(line), "%08X %04X", op1, op2);
- _registerLine(cheats, line);
-
- enum GBACodeBreakerType type = op1 >> 28;
- struct GBACheat* cheat = 0;
-
- if (cheats->incompleteCheat) {
- cheats->incompleteCheat->repeat = op1 & 0xFFFF;
- cheats->incompleteCheat->addressOffset = op2;
- cheats->incompleteCheat->operandOffset = 0;
- cheats->incompleteCheat = 0;
- return true;
- }
-
- switch (type) {
- case CB_GAME_ID:
- // TODO: Run checksum
- return true;
- case CB_HOOK:
- if (cheats->hook) {
- return false;
- }
- cheats->hook = malloc(sizeof(*cheats->hook));
- cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1));
- cheats->hook->mode = MODE_THUMB;
- cheats->hook->refs = 1;
- cheats->hook->reentries = 0;
- return true;
- case CB_OR_2:
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_OR;
- cheat->width = 2;
- break;
- case CB_ASSIGN_1:
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_ASSIGN;
- cheat->width = 1;
- break;
- case CB_FILL:
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_ASSIGN;
- cheat->width = 2;
- cheats->incompleteCheat = cheat;
- break;
- case CB_FILL_8:
- GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2);
- return false;
- case CB_AND_2:
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_AND;
- cheat->width = 2;
- break;
- case CB_IF_EQ:
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_IF_EQ;
- cheat->width = 2;
- break;
- case CB_ASSIGN_2:
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_ASSIGN;
- cheat->width = 2;
- break;
- case CB_ENCRYPT:
- GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker encryption not supported");
- return false;
- case CB_IF_NE:
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_IF_NE;
- cheat->width = 2;
- break;
- case CB_IF_GT:
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_IF_GT;
- cheat->width = 2;
- break;
- case CB_IF_LT:
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_IF_LT;
- cheat->width = 2;
- break;
- case CB_IF_SPECIAL:
- switch (op1 & 0x0FFFFFFF) {
- case 0x20:
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_IF_AND;
- cheat->width = 2;
- cheat->address = BASE_IO | REG_JOYSTAT;
- cheat->operand = op2;
- cheat->repeat = 1;
- return true;
- default:
- GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2);
- return false;
- }
- case CB_ADD_2:
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_ADD;
- cheat->width = 2;
- break;
- case CB_IF_AND:
- cheat = GBACheatListAppend(&cheats->list);
- cheat->type = CHEAT_IF_AND;
- cheat->width = 2;
- break;
- }
-
- cheat->address = op1 & 0x0FFFFFFF;
- cheat->operand = op2;
- cheat->repeat = 1;
- return true;
-}
-
-bool GBACheatAddCodeBreakerLine(struct GBACheatSet* cheats, const char* line) {
- uint32_t op1;
- uint16_t op2;
- line = _hex32(line, &op1);
- if (!line) {
- return false;
- }
- while (*line == ' ') {
- ++line;
- }
- line = _hex16(line, &op2);
- if (!line) {
- return false;
- }
- return GBACheatAddCodeBreaker(cheats, op1, op2);
-}
-
-bool GBACheatAddGameShark(struct GBACheatSet* set, uint32_t op1, uint32_t op2) {
- uint32_t o1 = op1;
- uint32_t o2 = op2;
- char line[18] = "XXXXXXXX XXXXXXXX";
- snprintf(line, sizeof(line), "%08X %08X", op1, op2);
- _registerLine(set, line);
-
- switch (set->gsaVersion) {
- case 0:
- _setGameSharkVersion(set, 1);
- // Fall through
- case 1:
- _decryptGameShark(&o1, &o2, set->gsaSeeds);
- return _addGSA1(set, o1, o2);
- }
- return false;
-}
-
-bool GBACheatAddGameSharkLine(struct GBACheatSet* cheats, const char* line) {
- uint32_t op1;
- uint32_t op2;
- line = _hex32(line, &op1);
- if (!line) {
- return false;
- }
- while (*line == ' ') {
- ++line;
- }
- line = _hex32(line, &op2);
- if (!line) {
- return false;
- }
- return GBACheatAddGameShark(cheats, op1, op2);
-}
-
bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_t op2) {
uint32_t o1 = op1;
uint32_t o2 = op2;
char line[18] = "XXXXXXXX XXXXXXXX";
snprintf(line, sizeof(line), "%08X %08X", op1, op2);
- _registerLine(set, line);
+ GBACheatRegisterLine(set, line);
switch (set->gsaVersion) {
case 0:
// Try to detect GameShark version
- _decryptGameShark(&o1, &o2, _gsa1S);
+ GBACheatDecryptGameShark(&o1, &o2, GBACheatGameSharkSeeds);
if ((o1 & 0xF0000000) == 0xF0000000 && !(o2 & 0xFFFFFCFE)) {
- _setGameSharkVersion(set, 1);
- return _addGSA1(set, o1, o2);
+ GBACheatSetGameSharkVersion(set, 1);
+ return GBACheatAddGameSharkRaw(set, o1, o2);
}
o1 = op1;
o2 = op2;
- _decryptGameShark(&o1, &o2, _par3S);
+ GBACheatDecryptGameShark(&o1, &o2, GBACheatProActionReplaySeeds);
if ((o1 & 0xFE000000) == 0xC4000000 && !(o2 & 0xFFFF0000)) {
- _setGameSharkVersion(set, 3);
- return _addPAR3(set, o1, o2);
+ GBACheatSetGameSharkVersion(set, 3);
+ return GBACheatAddProActionReplayRaw(set, o1, o2);
}
break;
case 1:
- _decryptGameShark(&o1, &o2, set->gsaSeeds);
- return _addGSA1(set, o1, o2);
+ GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds);
+ return GBACheatAddGameSharkRaw(set, o1, o2);
case 3:
- _decryptGameShark(&o1, &o2, set->gsaSeeds);
- return _addPAR3(set, o1, o2);
+ GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds);
+ return GBACheatAddProActionReplayRaw(set, o1, o2);
}
return false;
}
@@ -678,14 +222,14 @@ bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_t op2)
bool GBACheatAutodetectLine(struct GBACheatSet* cheats, const char* line) {
uint32_t op1;
uint32_t op2;
- line = _hex32(line, &op1);
+ line = hex32(line, &op1);
if (!line) {
return false;
}
while (*line == ' ') {
++line;
}
- line = _hex32(line, &op2);
+ line = hex32(line, &op2);
if (!line) {
return false;
}
@@ -726,7 +270,7 @@ bool GBACheatParseFile(struct GBACheatDevice* device, struct VFile* vf) {
if (set && !reset) {
GBACheatSetCopyProperties(newSet, set);
} else {
- _setGameSharkVersion(newSet, gsaVersion);
+ GBACheatSetGameSharkVersion(newSet, gsaVersion);
}
reset = false;
set = newSet;
@@ -755,7 +299,7 @@ bool GBACheatParseFile(struct GBACheatDevice* device, struct VFile* vf) {
GBACheatSetInit(set, 0);
set->enabled = !nextDisabled;
nextDisabled = false;
- _setGameSharkVersion(set, gsaVersion);
+ GBACheatSetGameSharkVersion(set, gsaVersion);
}
GBACheatAddLine(set, cheat);
break;
@@ -819,21 +363,21 @@ bool GBACheatAddLine(struct GBACheatSet* cheats, const char* line) {
uint32_t op1;
uint16_t op2;
uint16_t op3;
- line = _hex32(line, &op1);
+ line = hex32(line, &op1);
if (!line) {
return false;
}
while (isspace(line[0])) {
++line;
}
- line = _hex16(line, &op2);
+ line = hex16(line, &op2);
if (!line) {
return false;
}
if (!line[0] || isspace(line[0])) {
return GBACheatAddCodeBreaker(cheats, op1, op2);
}
- line = _hex16(line, &op3);
+ line = hex16(line, &op3);
if (!line) {
return false;
}
@@ -849,6 +393,7 @@ void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats)
}
bool condition = true;
int conditionRemaining = 0;
+ int negativeConditionRemaining = 0;
_patchROM(device, cheats);
size_t nCodes = GBACheatListSize(&cheats->list);
@@ -859,6 +404,13 @@ void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats)
if (!condition) {
continue;
}
+ } else if (negativeConditionRemaining > 0) {
+ conditionRemaining = negativeConditionRemaining - 1;
+ negativeConditionRemaining = 0;
+ condition = !condition;
+ if (!condition) {
+ continue;
+ }
} else {
condition = true;
}
@@ -874,6 +426,11 @@ void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats)
value = operand;
performAssignment = true;
break;
+ case CHEAT_ASSIGN_INDIRECT:
+ value = operand;
+ address = _readMem(device->p->cpu, address + cheat->addressOffset, 4);
+ performAssignment = true;
+ break;
case CHEAT_AND:
value = _readMem(device->p->cpu, address, cheat->width) & operand;
performAssignment = true;
@@ -889,34 +446,42 @@ void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats)
case CHEAT_IF_EQ:
condition = _readMem(device->p->cpu, address, cheat->width) == operand;
conditionRemaining = cheat->repeat;
+ negativeConditionRemaining = cheat->negativeRepeat;
break;
case CHEAT_IF_NE:
condition = _readMem(device->p->cpu, address, cheat->width) != operand;
conditionRemaining = cheat->repeat;
+ negativeConditionRemaining = cheat->negativeRepeat;
break;
case CHEAT_IF_LT:
condition = _readMem(device->p->cpu, address, cheat->width) < operand;
conditionRemaining = cheat->repeat;
+ negativeConditionRemaining = cheat->negativeRepeat;
break;
case CHEAT_IF_GT:
condition = _readMem(device->p->cpu, address, cheat->width) > operand;
conditionRemaining = cheat->repeat;
+ negativeConditionRemaining = cheat->negativeRepeat;
break;
case CHEAT_IF_ULT:
condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) < (uint32_t) operand;
conditionRemaining = cheat->repeat;
+ negativeConditionRemaining = cheat->negativeRepeat;
break;
case CHEAT_IF_UGT:
condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) > (uint32_t) operand;
conditionRemaining = cheat->repeat;
+ negativeConditionRemaining = cheat->negativeRepeat;
break;
case CHEAT_IF_AND:
condition = _readMem(device->p->cpu, address, cheat->width) & operand;
conditionRemaining = cheat->repeat;
+ negativeConditionRemaining = cheat->negativeRepeat;
break;
case CHEAT_IF_LAND:
condition = _readMem(device->p->cpu, address, cheat->width) && operand;
conditionRemaining = cheat->repeat;
+ negativeConditionRemaining = cheat->negativeRepeat;
break;
}
diff --git a/src/gba/cheats.h b/src/gba/cheats.h
index 3acbcebeb..61290c2d7 100644
--- a/src/gba/cheats.h
+++ b/src/gba/cheats.h
@@ -15,6 +15,7 @@
enum GBACheatType {
CHEAT_ASSIGN,
+ CHEAT_ASSIGN_INDIRECT,
CHEAT_AND,
CHEAT_ADD,
CHEAT_OR,
@@ -85,6 +86,11 @@ enum GBAActionReplay3Action {
};
enum GBAActionReplay3Base {
+ PAR3_BASE_ASSIGN = 0x00000000,
+ PAR3_BASE_INDIRECT = 0x40000000,
+ PAR3_BASE_ADD = 0x80000000,
+ PAR3_BASE_OTHER = 0xC0000000,
+
PAR3_BASE_ASSIGN_1 = 0x00000000,
PAR3_BASE_ASSIGN_2 = 0x02000000,
PAR3_BASE_ASSIGN_4 = 0x04000000,
@@ -119,7 +125,10 @@ enum GBAActionReplay3Other {
enum {
PAR3_COND = 0x38000000,
PAR3_WIDTH = 0x06000000,
- PAR3_ACTION = 0xC0000000
+ PAR3_ACTION = 0xC0000000,
+ PAR3_BASE = 0xC0000000,
+
+ PAR3_WIDTH_BASE = 25
};
struct GBACheat {
@@ -128,6 +137,7 @@ struct GBACheat {
uint32_t address;
uint32_t operand;
uint32_t repeat;
+ uint32_t negativeRepeat;
int32_t addressOffset;
int32_t operandOffset;
@@ -148,8 +158,6 @@ struct GBACheatSet {
struct GBACheatHook* hook;
struct GBACheatList list;
- struct GBACheat* incompleteCheat;
-
struct GBACheatPatch {
uint32_t address;
int16_t newValue;
@@ -158,6 +166,10 @@ struct GBACheatSet {
bool exists;
} romPatches[MAX_ROM_PATCHES];
+ struct GBACheat* incompleteCheat;
+ struct GBACheatPatch* incompletePatch;
+ struct GBACheat* currentBlock;
+
int gsaVersion;
uint32_t gsaSeeds[4];
int remainingAddresses;
@@ -196,6 +208,9 @@ bool GBACheatAddCodeBreakerLine(struct GBACheatSet*, const char* line);
bool GBACheatAddGameShark(struct GBACheatSet*, uint32_t op1, uint32_t op2);
bool GBACheatAddGameSharkLine(struct GBACheatSet*, const char* line);
+bool GBACheatAddProActionReplay(struct GBACheatSet*, uint32_t op1, uint32_t op2);
+bool GBACheatAddProActionReplayLine(struct GBACheatSet*, const char* line);
+
bool GBACheatAddAutodetect(struct GBACheatSet*, uint32_t op1, uint32_t op2);
bool GBACheatAddAutodetectLine(struct GBACheatSet*, const char* line);
@@ -206,4 +221,4 @@ bool GBACheatAddLine(struct GBACheatSet*, const char* line);
void GBACheatRefresh(struct GBACheatDevice*, struct GBACheatSet*);
-#endif
\ No newline at end of file
+#endif
diff --git a/src/gba/cheats/cheats-private.h b/src/gba/cheats/cheats-private.h
new file mode 100644
index 000000000..ca74d8348
--- /dev/null
+++ b/src/gba/cheats/cheats-private.h
@@ -0,0 +1,13 @@
+/* Copyright (c) 2013-2015 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 GBA_CHEATS_PRIVATE_H
+#define GBA_CHEATS_PRIVATE_H
+
+#include "gba/cheats.h"
+
+void GBACheatRegisterLine(struct GBACheatSet* set, const char* line);
+
+#endif
diff --git a/src/gba/cheats/codebreaker.c b/src/gba/cheats/codebreaker.c
new file mode 100644
index 000000000..74bbe4dfd
--- /dev/null
+++ b/src/gba/cheats/codebreaker.c
@@ -0,0 +1,143 @@
+/* Copyright (c) 2013-2015 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 "gba/cheats.h"
+
+#include "gba/cheats/cheats-private.h"
+#include "gba/gba.h"
+#include "gba/io.h"
+#include "util/string.h"
+
+bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t op2) {
+ char line[14] = "XXXXXXXX XXXX";
+ snprintf(line, sizeof(line), "%08X %04X", op1, op2);
+ GBACheatRegisterLine(cheats, line);
+
+ enum GBACodeBreakerType type = op1 >> 28;
+ struct GBACheat* cheat = 0;
+
+ if (cheats->incompleteCheat) {
+ cheats->incompleteCheat->repeat = op1 & 0xFFFF;
+ cheats->incompleteCheat->addressOffset = op2;
+ cheats->incompleteCheat->operandOffset = 0;
+ cheats->incompleteCheat = 0;
+ return true;
+ }
+
+ switch (type) {
+ case CB_GAME_ID:
+ // TODO: Run checksum
+ return true;
+ case CB_HOOK:
+ if (cheats->hook) {
+ return false;
+ }
+ cheats->hook = malloc(sizeof(*cheats->hook));
+ cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1));
+ cheats->hook->mode = MODE_THUMB;
+ cheats->hook->refs = 1;
+ cheats->hook->reentries = 0;
+ return true;
+ case CB_OR_2:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_OR;
+ cheat->width = 2;
+ break;
+ case CB_ASSIGN_1:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_ASSIGN;
+ cheat->width = 1;
+ break;
+ case CB_FILL:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_ASSIGN;
+ cheat->width = 2;
+ cheats->incompleteCheat = cheat;
+ break;
+ case CB_FILL_8:
+ GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2);
+ return false;
+ case CB_AND_2:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_AND;
+ cheat->width = 2;
+ break;
+ case CB_IF_EQ:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_IF_EQ;
+ cheat->width = 2;
+ break;
+ case CB_ASSIGN_2:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_ASSIGN;
+ cheat->width = 2;
+ break;
+ case CB_ENCRYPT:
+ GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker encryption not supported");
+ return false;
+ case CB_IF_NE:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_IF_NE;
+ cheat->width = 2;
+ break;
+ case CB_IF_GT:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_IF_GT;
+ cheat->width = 2;
+ break;
+ case CB_IF_LT:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_IF_LT;
+ cheat->width = 2;
+ break;
+ case CB_IF_SPECIAL:
+ switch (op1 & 0x0FFFFFFF) {
+ case 0x20:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_IF_AND;
+ cheat->width = 2;
+ cheat->address = BASE_IO | REG_JOYSTAT;
+ cheat->operand = op2;
+ cheat->repeat = 1;
+ return true;
+ default:
+ GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2);
+ return false;
+ }
+ case CB_ADD_2:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_ADD;
+ cheat->width = 2;
+ break;
+ case CB_IF_AND:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_IF_AND;
+ cheat->width = 2;
+ break;
+ }
+
+ cheat->address = op1 & 0x0FFFFFFF;
+ cheat->operand = op2;
+ cheat->repeat = 1;
+ cheat->negativeRepeat = 0;
+ return true;
+}
+
+bool GBACheatAddCodeBreakerLine(struct GBACheatSet* cheats, const char* line) {
+ uint32_t op1;
+ uint16_t op2;
+ line = hex32(line, &op1);
+ if (!line) {
+ return false;
+ }
+ while (*line == ' ') {
+ ++line;
+ }
+ line = hex16(line, &op2);
+ if (!line) {
+ return false;
+ }
+ return GBACheatAddCodeBreaker(cheats, op1, op2);
+}
diff --git a/src/gba/cheats/gameshark.c b/src/gba/cheats/gameshark.c
new file mode 100644
index 000000000..e9bb8221d
--- /dev/null
+++ b/src/gba/cheats/gameshark.c
@@ -0,0 +1,224 @@
+/* Copyright (c) 2013-2015 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 "gameshark.h"
+
+#include "gba/cheats/cheats-private.h"
+#include "gba/cheats/parv3.h"
+#include "gba/gba.h"
+#include "util/string.h"
+
+const uint32_t GBACheatGameSharkSeeds[4] = { 0x09F4FBBD, 0x9681884A, 0x352027E9, 0xF3DEE5A7 };
+
+static const uint8_t _gsa1T1[256] = {
+ 0x31, 0x1C, 0x23, 0xE5, 0x89, 0x8E, 0xA1, 0x37, 0x74, 0x6D, 0x67, 0xFC, 0x1F, 0xC0, 0xB1, 0x94,
+ 0x3B, 0x05, 0x56, 0x86, 0x00, 0x24, 0xF0, 0x17, 0x72, 0xA2, 0x3D, 0x1B, 0xE3, 0x17, 0xC5, 0x0B,
+ 0xB9, 0xE2, 0xBD, 0x58, 0x71, 0x1B, 0x2C, 0xFF, 0xE4, 0xC9, 0x4C, 0x5E, 0xC9, 0x55, 0x33, 0x45,
+ 0x7C, 0x3F, 0xB2, 0x51, 0xFE, 0x10, 0x7E, 0x75, 0x3C, 0x90, 0x8D, 0xDA, 0x94, 0x38, 0xC3, 0xE9,
+ 0x95, 0xEA, 0xCE, 0xA6, 0x06, 0xE0, 0x4F, 0x3F, 0x2A, 0xE3, 0x3A, 0xE4, 0x43, 0xBD, 0x7F, 0xDA,
+ 0x55, 0xF0, 0xEA, 0xCB, 0x2C, 0xA8, 0x47, 0x61, 0xA0, 0xEF, 0xCB, 0x13, 0x18, 0x20, 0xAF, 0x3E,
+ 0x4D, 0x9E, 0x1E, 0x77, 0x51, 0xC5, 0x51, 0x20, 0xCF, 0x21, 0xF9, 0x39, 0x94, 0xDE, 0xDD, 0x79,
+ 0x4E, 0x80, 0xC4, 0x9D, 0x94, 0xD5, 0x95, 0x01, 0x27, 0x27, 0xBD, 0x6D, 0x78, 0xB5, 0xD1, 0x31,
+ 0x6A, 0x65, 0x74, 0x74, 0x58, 0xB3, 0x7C, 0xC9, 0x5A, 0xED, 0x50, 0x03, 0xC4, 0xA2, 0x94, 0x4B,
+ 0xF0, 0x58, 0x09, 0x6F, 0x3E, 0x7D, 0xAE, 0x7D, 0x58, 0xA0, 0x2C, 0x91, 0xBB, 0xE1, 0x70, 0xEB,
+ 0x73, 0xA6, 0x9A, 0x44, 0x25, 0x90, 0x16, 0x62, 0x53, 0xAE, 0x08, 0xEB, 0xDC, 0xF0, 0xEE, 0x77,
+ 0xC2, 0xDE, 0x81, 0xE8, 0x30, 0x89, 0xDB, 0xFE, 0xBC, 0xC2, 0xDF, 0x26, 0xE9, 0x8B, 0xD6, 0x93,
+ 0xF0, 0xCB, 0x56, 0x90, 0xC0, 0x46, 0x68, 0x15, 0x43, 0xCB, 0xE9, 0x98, 0xE3, 0xAF, 0x31, 0x25,
+ 0x4D, 0x7B, 0xF3, 0xB1, 0x74, 0xE2, 0x64, 0xAC, 0xD9, 0xF6, 0xA0, 0xD5, 0x0B, 0x9B, 0x49, 0x52,
+ 0x69, 0x3B, 0x71, 0x00, 0x2F, 0xBB, 0xBA, 0x08, 0xB1, 0xAE, 0xBB, 0xB3, 0xE1, 0xC9, 0xA6, 0x7F,
+ 0x17, 0x97, 0x28, 0x72, 0x12, 0x6E, 0x91, 0xAE, 0x3A, 0xA2, 0x35, 0x46, 0x27, 0xF8, 0x12, 0x50
+};
+
+static const uint8_t _gsa1T2[256] = {
+ 0xD8, 0x65, 0x04, 0xC2, 0x65, 0xD5, 0xB0, 0x0C, 0xDF, 0x9D, 0xF0, 0xC3, 0x9A, 0x17, 0xC9, 0xA6,
+ 0xE1, 0xAC, 0x0D, 0x14, 0x2F, 0x3C, 0x2C, 0x87, 0xA2, 0xBF, 0x4D, 0x5F, 0xAC, 0x2D, 0x9D, 0xE1,
+ 0x0C, 0x9C, 0xE7, 0x7F, 0xFC, 0xA8, 0x66, 0x59, 0xAC, 0x18, 0xD7, 0x05, 0xF0, 0xBF, 0xD1, 0x8B,
+ 0x35, 0x9F, 0x59, 0xB4, 0xBA, 0x55, 0xB2, 0x85, 0xFD, 0xB1, 0x72, 0x06, 0x73, 0xA4, 0xDB, 0x48,
+ 0x7B, 0x5F, 0x67, 0xA5, 0x95, 0xB9, 0xA5, 0x4A, 0xCF, 0xD1, 0x44, 0xF3, 0x81, 0xF5, 0x6D, 0xF6,
+ 0x3A, 0xC3, 0x57, 0x83, 0xFA, 0x8E, 0x15, 0x2A, 0xA2, 0x04, 0xB2, 0x9D, 0xA8, 0x0D, 0x7F, 0xB8,
+ 0x0F, 0xF6, 0xAC, 0xBE, 0x97, 0xCE, 0x16, 0xE6, 0x31, 0x10, 0x60, 0x16, 0xB5, 0x83, 0x45, 0xEE,
+ 0xD7, 0x5F, 0x2C, 0x08, 0x58, 0xB1, 0xFD, 0x7E, 0x79, 0x00, 0x34, 0xAD, 0xB5, 0x31, 0x34, 0x39,
+ 0xAF, 0xA8, 0xDD, 0x52, 0x6A, 0xB0, 0x60, 0x35, 0xB8, 0x1D, 0x52, 0xF5, 0xF5, 0x30, 0x00, 0x7B,
+ 0xF4, 0xBA, 0x03, 0xCB, 0x3A, 0x84, 0x14, 0x8A, 0x6A, 0xEF, 0x21, 0xBD, 0x01, 0xD8, 0xA0, 0xD4,
+ 0x43, 0xBE, 0x23, 0xE7, 0x76, 0x27, 0x2C, 0x3F, 0x4D, 0x3F, 0x43, 0x18, 0xA7, 0xC3, 0x47, 0xA5,
+ 0x7A, 0x1D, 0x02, 0x55, 0x09, 0xD1, 0xFF, 0x55, 0x5E, 0x17, 0xA0, 0x56, 0xF4, 0xC9, 0x6B, 0x90,
+ 0xB4, 0x80, 0xA5, 0x07, 0x22, 0xFB, 0x22, 0x0D, 0xD9, 0xC0, 0x5B, 0x08, 0x35, 0x05, 0xC1, 0x75,
+ 0x4F, 0xD0, 0x51, 0x2D, 0x2E, 0x5E, 0x69, 0xE7, 0x3B, 0xC2, 0xDA, 0xFF, 0xF6, 0xCE, 0x3E, 0x76,
+ 0xE8, 0x36, 0x8C, 0x39, 0xD8, 0xF3, 0xE9, 0xA6, 0x42, 0xE6, 0xC1, 0x4C, 0x05, 0xBE, 0x17, 0xF2,
+ 0x5C, 0x1B, 0x19, 0xDB, 0x0F, 0xF3, 0xF8, 0x49, 0xEB, 0x36, 0xF6, 0x40, 0x6F, 0xAD, 0xC1, 0x8C
+};
+
+// http://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm
+void GBACheatDecryptGameShark(uint32_t* op1, uint32_t* op2, const uint32_t* seeds) {
+ uint32_t sum = 0xC6EF3720;
+ int i;
+ for (i = 0; i < 32; ++i) {
+ *op2 -= ((*op1 << 4) + seeds[2]) ^ (*op1 + sum) ^ ((*op1 >> 5) + seeds[3]);
+ *op1 -= ((*op2 << 4) + seeds[0]) ^ (*op2 + sum) ^ ((*op2 >> 5) + seeds[1]);
+ sum -= 0x9E3779B9;
+ }
+}
+
+void GBACheatReseedGameShark(uint32_t* seeds, uint16_t params, const uint8_t* t1, const uint8_t* t2) {
+ int x, y;
+ int s0 = params >> 8;
+ int s1 = params & 0xFF;
+ for (y = 0; y < 4; ++y) {
+ for (x = 0; x < 4; ++x) {
+ uint8_t z = t1[(s0 + x) & 0xFF] + t2[(s1 + y) & 0xFF];
+ seeds[y] <<= 8;
+ seeds[y] |= z;
+ }
+ }
+}
+
+void GBACheatSetGameSharkVersion(struct GBACheatSet* cheats, int version) {
+ cheats->gsaVersion = 1;
+ switch (version) {
+ case 1:
+ memcpy(cheats->gsaSeeds, GBACheatGameSharkSeeds, 4 * sizeof(uint32_t));
+ break;
+ case 3:
+ memcpy(cheats->gsaSeeds, GBACheatProActionReplaySeeds, 4 * sizeof(uint32_t));
+ break;
+ }
+}
+
+bool GBACheatAddGameSharkRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) {
+ enum GBAGameSharkType type = op1 >> 28;
+ struct GBACheat* cheat = 0;
+
+ if (cheats->incompleteCheat) {
+ if (cheats->remainingAddresses > 0) {
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_ASSIGN;
+ cheat->width = 4;
+ cheat->address = op1;
+ cheat->operand = cheats->incompleteCheat->operand;
+ cheat->repeat = 1;
+ --cheats->remainingAddresses;
+ }
+ if (cheats->remainingAddresses > 0) {
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_ASSIGN;
+ cheat->width = 4;
+ cheat->address = op2;
+ cheat->operand = cheats->incompleteCheat->operand;
+ cheat->repeat = 1;
+ --cheats->remainingAddresses;
+ }
+ if (cheats->remainingAddresses == 0) {
+ cheats->incompleteCheat = 0;
+ }
+ return true;
+ }
+
+ switch (type) {
+ case GSA_ASSIGN_1:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_ASSIGN;
+ cheat->width = 1;
+ cheat->address = op1 & 0x0FFFFFFF;
+ break;
+ case GSA_ASSIGN_2:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_ASSIGN;
+ cheat->width = 2;
+ cheat->address = op1 & 0x0FFFFFFF;
+ break;
+ case GSA_ASSIGN_4:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_ASSIGN;
+ cheat->width = 4;
+ cheat->address = op1 & 0x0FFFFFFF;
+ break;
+ case GSA_ASSIGN_LIST:
+ cheats->remainingAddresses = (op1 & 0xFFFF) - 1;
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_ASSIGN;
+ cheat->width = 4;
+ cheat->address = op2;
+ cheats->incompleteCheat = cheat;
+ break;
+ case GSA_PATCH:
+ cheats->romPatches[0].address = (op1 & 0xFFFFFF) << 1;
+ cheats->romPatches[0].newValue = op2;
+ cheats->romPatches[0].applied = false;
+ cheats->romPatches[0].exists = true;
+ return true;
+ case GSA_BUTTON:
+ // TODO: Implement button
+ GBALog(0, GBA_LOG_STUB, "GameShark button unimplemented");
+ return false;
+ case GSA_IF_EQ:
+ if (op1 == 0xDEADFACE) {
+ GBACheatReseedGameShark(cheats->gsaSeeds, op2, _gsa1T1, _gsa1T2);
+ return true;
+ }
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_IF_EQ;
+ cheat->width = 2;
+ cheat->address = op1 & 0x0FFFFFFF;
+ break;
+ case GSA_IF_EQ_RANGE:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->type = CHEAT_IF_EQ;
+ cheat->width = 2;
+ cheat->address = op2 & 0x0FFFFFFF;
+ cheat->operand = op1 & 0xFFFF;
+ cheat->repeat = (op1 >> 16) & 0xFF;
+ return true;
+ case GSA_HOOK:
+ if (cheats->hook) {
+ return false;
+ }
+ cheats->hook = malloc(sizeof(*cheats->hook));
+ cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1));
+ cheats->hook->mode = MODE_THUMB;
+ cheats->hook->refs = 1;
+ cheats->hook->reentries = 0;
+ return true;
+ default:
+ return false;
+ }
+ cheat->operand = op2;
+ cheat->repeat = 1;
+ return true;
+}
+
+bool GBACheatAddGameShark(struct GBACheatSet* set, uint32_t op1, uint32_t op2) {
+ uint32_t o1 = op1;
+ uint32_t o2 = op2;
+ char line[18] = "XXXXXXXX XXXXXXXX";
+ snprintf(line, sizeof(line), "%08X %08X", op1, op2);
+ GBACheatRegisterLine(set, line);
+
+ switch (set->gsaVersion) {
+ case 0:
+ GBACheatSetGameSharkVersion(set, 1);
+ // Fall through
+ case 1:
+ GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds);
+ return GBACheatAddGameSharkRaw(set, o1, o2);
+ }
+ return false;
+}
+
+bool GBACheatAddGameSharkLine(struct GBACheatSet* cheats, const char* line) {
+ uint32_t op1;
+ uint32_t op2;
+ line = hex32(line, &op1);
+ if (!line) {
+ return false;
+ }
+ while (*line == ' ') {
+ ++line;
+ }
+ line = hex32(line, &op2);
+ if (!line) {
+ return false;
+ }
+ return GBACheatAddGameShark(cheats, op1, op2);
+}
diff --git a/src/gba/cheats/gameshark.h b/src/gba/cheats/gameshark.h
new file mode 100644
index 000000000..efac542ce
--- /dev/null
+++ b/src/gba/cheats/gameshark.h
@@ -0,0 +1,18 @@
+/* Copyright (c) 2013-2015 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 GBA_CHEATS_GAMESHARK_H
+#define GBA_CHEATS_GAMESHARK_H
+
+#include "gba/cheats.h"
+
+extern const uint32_t GBACheatGameSharkSeeds[4];
+
+void GBACheatDecryptGameShark(uint32_t* op1, uint32_t* op2, const uint32_t* seeds);
+void GBACheatReseedGameShark(uint32_t* seeds, uint16_t params, const uint8_t* t1, const uint8_t* t2);
+void GBACheatSetGameSharkVersion(struct GBACheatSet* cheats, int version);
+bool GBACheatAddGameSharkRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2);
+
+#endif
diff --git a/src/gba/cheats/parv3.c b/src/gba/cheats/parv3.c
new file mode 100644
index 000000000..62693a2af
--- /dev/null
+++ b/src/gba/cheats/parv3.c
@@ -0,0 +1,324 @@
+/* Copyright (c) 2013-2015 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 "parv3.h"
+
+#include "gba/cheats/cheats-private.h"
+#include "gba/cheats/gameshark.h"
+#include "gba/gba.h"
+#include "util/string.h"
+
+const uint32_t GBACheatProActionReplaySeeds[4] = { 0x7AA9648F, 0x7FAE6994, 0xC0EFAAD5, 0x42712C57 };
+
+static const uint8_t _par3T1[256] = {
+ 0xD0, 0xFF, 0xBA, 0xE5, 0xC1, 0xC7, 0xDB, 0x5B, 0x16, 0xE3, 0x6E, 0x26, 0x62, 0x31, 0x2E, 0x2A,
+ 0xD1, 0xBB, 0x4A, 0xE6, 0xAE, 0x2F, 0x0A, 0x90, 0x29, 0x90, 0xB6, 0x67, 0x58, 0x2A, 0xB4, 0x45,
+ 0x7B, 0xCB, 0xF0, 0x73, 0x84, 0x30, 0x81, 0xC2, 0xD7, 0xBE, 0x89, 0xD7, 0x4E, 0x73, 0x5C, 0xC7,
+ 0x80, 0x1B, 0xE5, 0xE4, 0x43, 0xC7, 0x46, 0xD6, 0x6F, 0x7B, 0xBF, 0xED, 0xE5, 0x27, 0xD1, 0xB5,
+ 0xD0, 0xD8, 0xA3, 0xCB, 0x2B, 0x30, 0xA4, 0xF0, 0x84, 0x14, 0x72, 0x5C, 0xFF, 0xA4, 0xFB, 0x54,
+ 0x9D, 0x70, 0xE2, 0xFF, 0xBE, 0xE8, 0x24, 0x76, 0xE5, 0x15, 0xFB, 0x1A, 0xBC, 0x87, 0x02, 0x2A,
+ 0x58, 0x8F, 0x9A, 0x95, 0xBD, 0xAE, 0x8D, 0x0C, 0xA5, 0x4C, 0xF2, 0x5C, 0x7D, 0xAD, 0x51, 0xFB,
+ 0xB1, 0x22, 0x07, 0xE0, 0x29, 0x7C, 0xEB, 0x98, 0x14, 0xC6, 0x31, 0x97, 0xE4, 0x34, 0x8F, 0xCC,
+ 0x99, 0x56, 0x9F, 0x78, 0x43, 0x91, 0x85, 0x3F, 0xC2, 0xD0, 0xD1, 0x80, 0xD1, 0x77, 0xA7, 0xE2,
+ 0x43, 0x99, 0x1D, 0x2F, 0x8B, 0x6A, 0xE4, 0x66, 0x82, 0xF7, 0x2B, 0x0B, 0x65, 0x14, 0xC0, 0xC2,
+ 0x1D, 0x96, 0x78, 0x1C, 0xC4, 0xC3, 0xD2, 0xB1, 0x64, 0x07, 0xD7, 0x6F, 0x02, 0xE9, 0x44, 0x31,
+ 0xDB, 0x3C, 0xEB, 0x93, 0xED, 0x9A, 0x57, 0x05, 0xB9, 0x0E, 0xAF, 0x1F, 0x48, 0x11, 0xDC, 0x35,
+ 0x6C, 0xB8, 0xEE, 0x2A, 0x48, 0x2B, 0xBC, 0x89, 0x12, 0x59, 0xCB, 0xD1, 0x18, 0xEA, 0x72, 0x11,
+ 0x01, 0x75, 0x3B, 0xB5, 0x56, 0xF4, 0x8B, 0xA0, 0x41, 0x75, 0x86, 0x7B, 0x94, 0x12, 0x2D, 0x4C,
+ 0x0C, 0x22, 0xC9, 0x4A, 0xD8, 0xB1, 0x8D, 0xF0, 0x55, 0x2E, 0x77, 0x50, 0x1C, 0x64, 0x77, 0xAA,
+ 0x3E, 0xAC, 0xD3, 0x3D, 0xCE, 0x60, 0xCA, 0x5D, 0xA0, 0x92, 0x78, 0xC6, 0x51, 0xFE, 0xF9, 0x30
+};
+
+static const uint8_t _par3T2[256] = {
+ 0xAA, 0xAF, 0xF0, 0x72, 0x90, 0xF7, 0x71, 0x27, 0x06, 0x11, 0xEB, 0x9C, 0x37, 0x12, 0x72, 0xAA,
+ 0x65, 0xBC, 0x0D, 0x4A, 0x76, 0xF6, 0x5C, 0xAA, 0xB0, 0x7A, 0x7D, 0x81, 0xC1, 0xCE, 0x2F, 0x9F,
+ 0x02, 0x75, 0x38, 0xC8, 0xFC, 0x66, 0x05, 0xC2, 0x2C, 0xBD, 0x91, 0xAD, 0x03, 0xB1, 0x88, 0x93,
+ 0x31, 0xC6, 0xAB, 0x40, 0x23, 0x43, 0x76, 0x54, 0xCA, 0xE7, 0x00, 0x96, 0x9F, 0xD8, 0x24, 0x8B,
+ 0xE4, 0xDC, 0xDE, 0x48, 0x2C, 0xCB, 0xF7, 0x84, 0x1D, 0x45, 0xE5, 0xF1, 0x75, 0xA0, 0xED, 0xCD,
+ 0x4B, 0x24, 0x8A, 0xB3, 0x98, 0x7B, 0x12, 0xB8, 0xF5, 0x63, 0x97, 0xB3, 0xA6, 0xA6, 0x0B, 0xDC,
+ 0xD8, 0x4C, 0xA8, 0x99, 0x27, 0x0F, 0x8F, 0x94, 0x63, 0x0F, 0xB0, 0x11, 0x94, 0xC7, 0xE9, 0x7F,
+ 0x3B, 0x40, 0x72, 0x4C, 0xDB, 0x84, 0x78, 0xFE, 0xB8, 0x56, 0x08, 0x80, 0xDF, 0x20, 0x2F, 0xB9,
+ 0x66, 0x2D, 0x60, 0x63, 0xF5, 0x18, 0x15, 0x1B, 0x86, 0x85, 0xB9, 0xB4, 0x68, 0x0E, 0xC6, 0xD1,
+ 0x8A, 0x81, 0x2B, 0xB3, 0xF6, 0x48, 0xF0, 0x4F, 0x9C, 0x28, 0x1C, 0xA4, 0x51, 0x2F, 0xD7, 0x4B,
+ 0x17, 0xE7, 0xCC, 0x50, 0x9F, 0xD0, 0xD1, 0x40, 0x0C, 0x0D, 0xCA, 0x83, 0xFA, 0x5E, 0xCA, 0xEC,
+ 0xBF, 0x4E, 0x7C, 0x8F, 0xF0, 0xAE, 0xC2, 0xD3, 0x28, 0x41, 0x9B, 0xC8, 0x04, 0xB9, 0x4A, 0xBA,
+ 0x72, 0xE2, 0xB5, 0x06, 0x2C, 0x1E, 0x0B, 0x2C, 0x7F, 0x11, 0xA9, 0x26, 0x51, 0x9D, 0x3F, 0xF8,
+ 0x62, 0x11, 0x2E, 0x89, 0xD2, 0x9D, 0x35, 0xB1, 0xE4, 0x0A, 0x4D, 0x93, 0x01, 0xA7, 0xD1, 0x2D,
+ 0x00, 0x87, 0xE2, 0x2D, 0xA4, 0xE9, 0x0A, 0x06, 0x66, 0xF8, 0x1F, 0x44, 0x75, 0xB5, 0x6B, 0x1C,
+ 0xFC, 0x31, 0x09, 0x48, 0xA3, 0xFF, 0x92, 0x12, 0x58, 0xE9, 0xFA, 0xAE, 0x4F, 0xE2, 0xB4, 0xCC
+};
+static uint32_t _parAddr(uint32_t x) {
+ return (x & 0xFFFFF) | ((x << 4) & 0x0F000000);
+}
+
+static void _parEndBlock(struct GBACheatSet* cheats) {
+ size_t size = GBACheatListSize(&cheats->list) - GBACheatListIndex(&cheats->list, cheats->currentBlock);
+ if (cheats->currentBlock->repeat) {
+ cheats->currentBlock->negativeRepeat = size - cheats->currentBlock->repeat;
+ } else {
+ cheats->currentBlock->repeat = size;
+ }
+ cheats->currentBlock = 0;
+}
+
+static void _parElseBlock(struct GBACheatSet* cheats) {
+ size_t size = GBACheatListSize(&cheats->list) - GBACheatListIndex(&cheats->list, cheats->currentBlock);
+ cheats->currentBlock->repeat = size;
+}
+
+static bool _addPAR3Cond(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) {
+ enum GBAActionReplay3Condition condition = op1 & PAR3_COND;
+ int width = 1 << ((op1 & PAR3_WIDTH) >> PAR3_WIDTH_BASE);
+ if (width > 4) {
+ // TODO: Always false conditions
+ return false;
+ }
+ if ((op1 & PAR3_ACTION) == PAR3_ACTION_DISABLE) {
+ // TODO: Codes that disable
+ return false;
+ }
+
+ struct GBACheat* cheat = GBACheatListAppend(&cheats->list);
+ cheat->address = _parAddr(op1);
+ cheat->width = width;
+ cheat->operand = op2 & (0xFFFFFFFFU >> ((4 - width) * 8));
+ cheat->addressOffset = 0;
+ cheat->operandOffset = 0;
+
+ switch (op1 & PAR3_ACTION) {
+ case PAR3_ACTION_NEXT:
+ cheat->repeat = 1;
+ cheat->negativeRepeat = 0;
+ break;
+ case PAR3_ACTION_NEXT_TWO:
+ cheat->repeat = 2;
+ cheat->negativeRepeat = 0;
+ break;
+ case PAR3_ACTION_BLOCK:
+ cheat->repeat = 0;
+ cheat->negativeRepeat = 0;
+ if (cheats->currentBlock) {
+ _parEndBlock(cheats);
+ }
+ cheats->currentBlock = cheat;
+ break;
+ }
+
+ switch (condition) {
+ case PAR3_COND_OTHER:
+ // We shouldn't be able to get here
+ GBALog(0, GBA_LOG_ERROR, "Unexpectedly created 'other' PARv3 code");
+ cheat->type = CHEAT_IF_LAND;
+ cheat->operand = 0;
+ break;
+ case PAR3_COND_EQ:
+ cheat->type = CHEAT_IF_EQ;
+ break;
+ case PAR3_COND_NE:
+ cheat->type = CHEAT_IF_NE;
+ break;
+ case PAR3_COND_LT:
+ cheat->type = CHEAT_IF_LT;
+ break;
+ case PAR3_COND_GT:
+ cheat->type = CHEAT_IF_GT;
+ break;
+ case PAR3_COND_ULT:
+ cheat->type = CHEAT_IF_ULT;
+ break;
+ case PAR3_COND_UGT:
+ cheat->type = CHEAT_IF_UGT;
+ break;
+ case PAR3_COND_LAND:
+ cheat->type = CHEAT_IF_LAND;
+ break;
+ }
+ return true;
+}
+
+static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) {
+ struct GBACheat* cheat;
+ switch (op2 & 0xFF000000) {
+ case PAR3_OTHER_SLOWDOWN:
+ // TODO: Slowdown
+ return false;
+ case PAR3_OTHER_BUTTON_1:
+ case PAR3_OTHER_BUTTON_2:
+ case PAR3_OTHER_BUTTON_4:
+ // TODO: Button
+ GBALog(0, GBA_LOG_STUB, "GameShark button unimplemented");
+ return false;
+ // TODO: Fix overriding existing patches
+ case PAR3_OTHER_PATCH_1:
+ cheats->romPatches[0].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1);
+ cheats->romPatches[0].applied = false;
+ cheats->romPatches[0].exists = true;
+ cheats->incompletePatch = &cheats->romPatches[0];
+ break;
+ case PAR3_OTHER_PATCH_2:
+ cheats->romPatches[1].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1);
+ cheats->romPatches[1].applied = false;
+ cheats->romPatches[1].exists = true;
+ cheats->incompletePatch = &cheats->romPatches[1];
+ break;
+ case PAR3_OTHER_PATCH_3:
+ cheats->romPatches[2].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1);
+ cheats->romPatches[2].applied = false;
+ cheats->romPatches[2].exists = true;
+ cheats->incompletePatch = &cheats->romPatches[2];
+ break;
+ case PAR3_OTHER_PATCH_4:
+ cheats->romPatches[3].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1);
+ cheats->romPatches[3].applied = false;
+ cheats->romPatches[3].exists = true;
+ cheats->incompletePatch = &cheats->romPatches[3];
+ break;
+ case PAR3_OTHER_ENDIF:
+ if (cheats->currentBlock) {
+ _parEndBlock(cheats);
+ return true;
+ }
+ return false;
+ case PAR3_OTHER_ELSE:
+ if (cheats->currentBlock) {
+ _parElseBlock(cheats);
+ return true;
+ }
+ return false;
+ case PAR3_OTHER_FILL_1:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->address = _parAddr(op2);
+ cheat->width = 1;
+ cheats->incompleteCheat = cheat;
+ break;
+ case PAR3_OTHER_FILL_2:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->address = _parAddr(op2);
+ cheat->width = 2;
+ cheats->incompleteCheat = cheat;
+ break;
+ case PAR3_OTHER_FILL_4:
+ cheat = GBACheatListAppend(&cheats->list);
+ cheat->address = _parAddr(op2);
+ cheat->width = 3;
+ cheats->incompleteCheat = cheat;
+ break;
+ }
+ return true;
+}
+
+bool GBACheatAddProActionReplayRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) {
+ if (cheats->incompletePatch) {
+ cheats->incompletePatch->newValue = op1;
+ cheats->incompletePatch = 0;
+ return true;
+ }
+ if (cheats->incompleteCheat) {
+ cheats->incompleteCheat->operand = op1 & (0xFFFFFFFFU >> ((4 - cheats->incompleteCheat->width) * 8));
+ cheats->incompleteCheat->addressOffset = op2 >> 24;
+ cheats->incompleteCheat->repeat = (op2 >> 16) & 0xFF;
+ cheats->incompleteCheat->addressOffset = (op2 & 0xFFFF) * cheats->incompleteCheat->width;
+ cheats->incompleteCheat = 0;
+ return true;
+ }
+
+ if (op2 == 0x001DC0DE) {
+ return true;
+ }
+
+ switch (op1) {
+ case 0x00000000:
+ return _addPAR3Special(cheats, op2);
+ case 0xDEADFACE:
+ GBACheatReseedGameShark(cheats->gsaSeeds, op2, _par3T1, _par3T2);
+ return true;
+ }
+
+ if (op1 >> 24 == 0xC4) {
+ if (cheats->hook) {
+ return false;
+ }
+ cheats->hook = malloc(sizeof(*cheats->hook));
+ cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1));
+ cheats->hook->mode = MODE_THUMB;
+ cheats->hook->refs = 1;
+ cheats->hook->reentries = 0;
+ return true;
+ }
+
+ if (op1 & PAR3_COND) {
+ return _addPAR3Cond(cheats, op1, op2);
+ }
+
+ int width = 1 << ((op1 & PAR3_WIDTH) >> PAR3_WIDTH_BASE);
+ struct GBACheat* cheat = GBACheatListAppend(&cheats->list);
+ cheat->address = _parAddr(op1);
+ cheat->operandOffset = 0;
+ cheat->addressOffset = 0;
+ cheat->repeat = 1;
+
+ switch (op1 & PAR3_BASE) {
+ case PAR3_BASE_ASSIGN:
+ cheat->type = CHEAT_ASSIGN;
+ cheat->addressOffset = width;
+ if (width < 4) {
+ cheat->repeat = (op2 >> (width * 8)) + 1;
+ }
+ break;
+ case PAR3_BASE_INDIRECT:
+ cheat->type = CHEAT_ASSIGN_INDIRECT;
+ if (width < 4) {
+ cheat->addressOffset = (op2 >> (width * 8)) * width;
+ }
+ break;
+ case PAR3_BASE_ADD:
+ cheat->type = CHEAT_ADD;
+ break;
+ case PAR3_BASE_OTHER:
+ width = ((op1 >> 24) & 1) + 1;
+ cheat->type = CHEAT_ASSIGN;
+ cheat->address = BASE_IO | (op1 & OFFSET_MASK);
+ break;
+ }
+
+ cheat->width = width;
+ cheat->operand = op2 & (0xFFFFFFFFU >> ((4 - width) * 8));
+ return true;
+}
+
+bool GBACheatAddProActionReplay(struct GBACheatSet* set, uint32_t op1, uint32_t op2) {
+ uint32_t o1 = op1;
+ uint32_t o2 = op2;
+ char line[18] = "XXXXXXXX XXXXXXXX";
+ snprintf(line, sizeof(line), "%08X %08X", op1, op2);
+ GBACheatRegisterLine(set, line);
+
+ switch (set->gsaVersion) {
+ case 0:
+ GBACheatSetGameSharkVersion(set, 3);
+ // Fall through
+ case 1:
+ GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds);
+ return GBACheatAddProActionReplayRaw(set, o1, o2);
+ }
+ return false;
+}
+
+bool GBACheatAddProActionReplayLine(struct GBACheatSet* cheats, const char* line) {
+ uint32_t op1;
+ uint32_t op2;
+ line = hex32(line, &op1);
+ if (!line) {
+ return false;
+ }
+ while (*line == ' ') {
+ ++line;
+ }
+ line = hex32(line, &op2);
+ if (!line) {
+ return false;
+ }
+ return GBACheatAddProActionReplay(cheats, op1, op2);
+}
diff --git a/src/gba/cheats/parv3.h b/src/gba/cheats/parv3.h
new file mode 100644
index 000000000..474cf367d
--- /dev/null
+++ b/src/gba/cheats/parv3.h
@@ -0,0 +1,15 @@
+/* Copyright (c) 2013-2015 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 GBA_CHEATS_PARV3_H
+#define GBA_CHEATS_PARV3_H
+
+#include "gba/cheats.h"
+
+extern const uint32_t GBACheatProActionReplaySeeds[4];
+
+bool GBACheatAddProActionReplayRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2);
+
+#endif
diff --git a/src/gba/gba.c b/src/gba/gba.c
index 97b4668fe..fbf38f06a 100644
--- a/src/gba/gba.c
+++ b/src/gba/gba.c
@@ -87,8 +87,12 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
gba->idleOptimization = IDLE_LOOP_REMOVE;
gba->idleLoop = IDLE_LOOP_NONE;
gba->lastJump = 0;
+ gba->haltPending = false;
gba->idleDetectionStep = 0;
gba->idleDetectionFailures = 0;
+
+ gba->realisticTiming = false;
+
gba->performingDMA = false;
}
@@ -430,6 +434,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) {
@@ -464,7 +469,7 @@ void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) {
}
gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->reload;
currentTimer->oldReload = currentTimer->reload;
- currentTimer->lastEvent = 0;
+ currentTimer->lastEvent = gba->cpu->cycles;
gba->timersEnabled |= 1 << timer;
} else if (wasEnabled && !currentTimer->enable) {
if (!currentTimer->countUp) {
diff --git a/src/gba/gba.h b/src/gba/gba.h
index 3594b4ab8..ec5b3f2f5 100644
--- a/src/gba/gba.h
+++ b/src/gba/gba.h
@@ -157,10 +157,13 @@ struct GBA {
enum GBAIdleLoopOptimization idleOptimization;
uint32_t idleLoop;
uint32_t lastJump;
+ bool haltPending;
int idleDetectionStep;
int idleDetectionFailures;
int32_t cachedRegisters[16];
bool taintedRegisters[16];
+
+ bool realisticTiming;
};
struct GBACartridge {
diff --git a/src/gba/io.c b/src/gba/io.c
index 8d31458a0..95ebef115 100644
--- a/src/gba/io.c
+++ b/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) {
@@ -366,23 +370,14 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16));
break;
- // TODO: Confirm this behavior on real hardware
case REG_FIFO_A_LO:
case REG_FIFO_B_LO:
- if (gba->performingDMA) {
- GBAAudioWriteFIFO16(&gba->audio, address, value);
- } else {
- GBAIOWrite32(gba, address, (gba->memory.io[(address >> 1) + 1] << 16) | value);
- }
+ GBAIOWrite32(gba, address, (gba->memory.io[(address >> 1) + 1] << 16) | value);
break;
case REG_FIFO_A_HI:
case REG_FIFO_B_HI:
- if (gba->performingDMA) {
- GBAAudioWriteFIFO16(&gba->audio, address, value);
- } else {
- GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16));
- }
+ GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16));
break;
// DMA
@@ -573,7 +568,7 @@ void GBAIOWrite32(struct GBA* gba, uint32_t address, uint32_t value) {
}
uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
- gba->lastJump = -1; // IO reads need to invalidate detected idle loops
+ gba->haltPending = false; // IO reads need to invalidate detected idle loops
switch (address) {
case REG_TM0CNT_LO:
GBATimerUpdateRegister(gba, 0);
diff --git a/src/gba/memory.c b/src/gba/memory.c
index 3ea0b7ece..24e6cfb4e 100644
--- a/src/gba/memory.c
+++ b/src/gba/memory.c
@@ -17,6 +17,7 @@
#define IDLE_LOOP_THRESHOLD 10000
static uint32_t _popcount32(unsigned bits);
+static void _pristineCow(struct GBA* gba);
static uint32_t _deadbeef[2] = { 0xDEADBEEF, 0xFEEDFACE };
static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t region);
@@ -197,8 +198,13 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
int newRegion = address >> BASE_OFFSET;
if (gba->idleOptimization >= IDLE_LOOP_REMOVE && memory->activeRegion != REGION_BIOS) {
- if (address == gba->lastJump && address == gba->idleLoop) {
- GBAHalt(gba);
+ if (address == gba->idleLoop) {
+ if (gba->haltPending) {
+ gba->haltPending = false;
+ GBAHalt(gba);
+ } else {
+ gba->haltPending = true;
+ }
} else if (gba->idleOptimization >= IDLE_LOOP_DETECT && newRegion == memory->activeRegion) {
if (address == gba->lastJump) {
switch (gba->idleDetectionStep) {
@@ -274,13 +280,30 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
}
#define LOAD_BAD \
- GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load32: 0x%08X", address); \
if (gba->performingDMA) { \
value = gba->bus; \
} else { \
value = cpu->prefetch[1]; \
if (cpu->executionMode == MODE_THUMB) { \
- value |= value << 16; \
+ /* http://ngemu.com/threads/gba-open-bus.170809/ */ \
+ switch (cpu->gprs[ARM_PC] >> BASE_OFFSET) { \
+ case REGION_BIOS: \
+ case REGION_OAM: \
+ /* This isn't right half the time, but we don't have $+6 handy */ \
+ value <<= 16; \
+ value |= cpu->prefetch[0]; \
+ break; \
+ case REGION_WORKING_IRAM: \
+ /* This doesn't handle prefetch clobbering */ \
+ if (cpu->gprs[ARM_PC] & 2) { \
+ value |= cpu->prefetch[0] << 16; \
+ } else { \
+ value <<= 16; \
+ value |= cpu->prefetch[0]; \
+ } \
+ default: \
+ value |= value << 16; \
+ } \
} \
}
@@ -293,6 +316,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
value = memory->biosPrefetch; \
} \
} else { \
+ GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load32: 0x%08X", address); \
LOAD_BAD; \
}
@@ -324,7 +348,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 \
@@ -375,12 +399,13 @@ uint32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
LOAD_SRAM;
break;
default:
+ GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load32: 0x%08X", address);
LOAD_BAD;
break;
}
if (cycleCounter) {
- *cycleCounter += 2 + wait;
+ *cycleCounter += 1 + wait;
}
// Unaligned 32-bit loads are "rotated" so they make some semblance of sense
int rotate = (address & 3) << 3;
@@ -404,15 +429,9 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
}
} else {
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address);
- if (gba->performingDMA) {
- LOAD_16(value, address & 2, &gba->bus);
- } else {
- uint32_t prefetch = cpu->prefetch[1];
- if (cpu->executionMode == MODE_THUMB) {
- prefetch |= prefetch << 16;
- }
- LOAD_16(value, address & 2, &prefetch);
- }
+ LOAD_BAD;
+ uint32_t v2 = value;
+ LOAD_16(value, address & 2, &v2);
}
break;
case REGION_WORKING_RAM:
@@ -470,20 +489,14 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
break;
default:
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address);
- if (gba->performingDMA) {
- LOAD_16(value, address & 2, &gba->bus);
- } else {
- uint32_t prefetch = cpu->prefetch[1];
- if (cpu->executionMode == MODE_THUMB) {
- prefetch |= prefetch << 16;
- }
- LOAD_16(value, address & 2, &prefetch);
- }
+ LOAD_BAD;
+ uint32_t v2 = value;
+ LOAD_16(value, address & 2, &v2);
break;
}
if (cycleCounter) {
- *cycleCounter += 2 + wait;
+ *cycleCounter += 1 + wait;
}
// Unaligned 16-bit loads are "unpredictable", but the GBA rotates them, so we have to, too.
int rotate = (address & 1) << 3;
@@ -493,49 +506,42 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
struct GBA* gba = (struct GBA*) cpu->master;
struct GBAMemory* memory = &gba->memory;
- uint8_t value = 0;
+ uint32_t value = 0;
int wait = 0;
switch (address >> BASE_OFFSET) {
case REGION_BIOS:
if (address < SIZE_BIOS) {
if (memory->activeRegion == REGION_BIOS) {
- value = ((int8_t*) memory->bios)[address];
+ value = ((uint8_t*) memory->bios)[address];
} else {
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad BIOS Load8: 0x%08X", address);
value = ((uint8_t*) &memory->biosPrefetch)[address & 3];
}
} else {
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address);
- if (gba->performingDMA) {
- value = ((uint8_t*) &gba->bus)[address & 3];
- } else {
- uint32_t prefetch = cpu->prefetch[1];
- if (cpu->executionMode == MODE_THUMB) {
- prefetch |= prefetch << 16;
- }
- value = ((uint8_t*) &prefetch)[address & 3];
- }
+ LOAD_BAD;
+ value = ((uint8_t*) &value)[address & 3];
}
break;
case REGION_WORKING_RAM:
- value = ((int8_t*) memory->wram)[address & (SIZE_WORKING_RAM - 1)];
+ value = ((uint8_t*) memory->wram)[address & (SIZE_WORKING_RAM - 1)];
wait = memory->waitstatesNonseq16[REGION_WORKING_RAM];
break;
case REGION_WORKING_IRAM:
- value = ((int8_t*) memory->iwram)[address & (SIZE_WORKING_IRAM - 1)];
+ value = ((uint8_t*) memory->iwram)[address & (SIZE_WORKING_IRAM - 1)];
break;
case REGION_IO:
value = (GBAIORead(gba, address & 0xFFFE) >> ((address & 0x0001) << 3)) & 0xFF;
break;
case REGION_PALETTE_RAM:
- value = ((int8_t*) gba->video.palette)[address & (SIZE_PALETTE_RAM - 1)];
+ value = ((uint8_t*) gba->video.palette)[address & (SIZE_PALETTE_RAM - 1)];
break;
case REGION_VRAM:
if ((address & 0x0001FFFF) < SIZE_VRAM) {
- value = ((int8_t*) gba->video.renderer->vram)[address & 0x0001FFFF];
+ value = ((uint8_t*) gba->video.renderer->vram)[address & 0x0001FFFF];
} else {
- value = ((int8_t*) gba->video.renderer->vram)[address & 0x00017FFF];
+ value = ((uint8_t*) gba->video.renderer->vram)[address & 0x00017FFF];
}
break;
case REGION_OAM:
@@ -549,7 +555,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
case REGION_CART2_EX:
wait = memory->waitstatesNonseq16[address >> BASE_OFFSET];
if ((address & (SIZE_CART0 - 1)) < memory->romSize) {
- value = ((int8_t*) memory->rom)[address & (SIZE_CART0 - 1)];
+ value = ((uint8_t*) memory->rom)[address & (SIZE_CART0 - 1)];
} else {
GBALog(gba, GBA_LOG_GAME_ERROR, "Out of bounds ROM Load8: 0x%08X", address);
value = (address >> 1) & 0xFF; \
@@ -572,23 +578,17 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
GBALog(gba, GBA_LOG_GAME_ERROR, "Reading from non-existent SRAM: 0x%08X", address);
value = 0xFF;
}
+ value &= 0xFF;
break;
default:
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address);
- if (gba->performingDMA) {
- value = ((uint8_t*) &gba->bus)[address & 3];
- } else {
- uint32_t prefetch = cpu->prefetch[1];
- if (cpu->executionMode == MODE_THUMB) {
- prefetch |= prefetch << 16;
- }
- value = ((uint8_t*) &prefetch)[address & 3];
- }
+ LOAD_BAD;
+ value = ((uint8_t*) &value)[address & 3];
break;
}
if (cycleCounter) {
- *cycleCounter += 2 + wait;
+ *cycleCounter += 1 + wait;
}
return value;
}
@@ -623,6 +623,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
gba->video.renderer->writeOAM(gba->video.renderer, ((address & (SIZE_OAM - 4)) >> 1) + 1);
#define STORE_CART \
+ wait += waitstatesRegion[address >> BASE_OFFSET]; \
GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Store32: 0x%08X", address);
#define STORE_SRAM \
@@ -777,7 +778,7 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo
if (memory->savedata.type == SAVEDATA_AUTODETECT) {
if (address == SAVEDATA_FLASH_BASE) {
GBALog(gba, GBA_LOG_INFO, "Detected Flash savegame");
- GBASavedataInitFlash(&memory->savedata);
+ GBASavedataInitFlash(&memory->savedata, gba->realisticTiming);
} else {
GBALog(gba, GBA_LOG_INFO, "Detected SRAM savegame");
GBASavedataInitSRAM(&memory->savedata);
@@ -848,12 +849,12 @@ void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* o
case REGION_CART1_EX:
case REGION_CART2:
case REGION_CART2_EX:
+ _pristineCow(gba);
if ((address & (SIZE_CART0 - 1)) < gba->memory.romSize) {
- LOAD_32(oldValue, address & (SIZE_CART0 - 1), gba->memory.rom);
- STORE_32(value, address & (SIZE_CART0 - 1), gba->memory.rom);
- } else {
- GBALog(gba, GBA_LOG_WARN, "Bad memory Patch32: 0x%08X", address);
+ gba->memory.romSize = (address & (SIZE_CART0 - 4)) + 4;
}
+ LOAD_32(oldValue, address & (SIZE_CART0 - 1), gba->memory.rom);
+ STORE_32(value, address & (SIZE_CART0 - 1), gba->memory.rom);
break;
case REGION_CART_SRAM:
case REGION_CART_SRAM_MIRROR:
@@ -915,12 +916,12 @@ void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* o
case REGION_CART1_EX:
case REGION_CART2:
case REGION_CART2_EX:
+ _pristineCow(gba);
if ((address & (SIZE_CART0 - 1)) < gba->memory.romSize) {
- LOAD_16(oldValue, address & (SIZE_CART0 - 1), gba->memory.rom);
- STORE_16(value, address & (SIZE_CART0 - 1), gba->memory.rom);
- } else {
- GBALog(gba, GBA_LOG_WARN, "Bad memory Patch16: 0x%08X", address);
+ gba->memory.romSize = (address & (SIZE_CART0 - 2)) + 2;
}
+ LOAD_16(oldValue, address & (SIZE_CART0 - 1), gba->memory.rom);
+ STORE_16(value, address & (SIZE_CART0 - 1), gba->memory.rom);
break;
case REGION_CART_SRAM:
case REGION_CART_SRAM_MIRROR:
@@ -1180,9 +1181,9 @@ void GBAAdjustWaitstates(struct GBA* gba, uint16_t parameters) {
memory->waitstatesSeq16[REGION_CART1] = memory->waitstatesSeq16[REGION_CART1_EX] = GBA_ROM_WAITSTATES_SEQ[ws1seq + 2];
memory->waitstatesSeq16[REGION_CART2] = memory->waitstatesSeq16[REGION_CART2_EX] = GBA_ROM_WAITSTATES_SEQ[ws2seq + 4];
- memory->waitstatesNonseq32[REGION_CART0] = memory->waitstatesNonseq32[REGION_CART0_EX] = memory->waitstatesSeq16[REGION_CART0] + 1 + memory->waitstatesSeq16[REGION_CART0];
- memory->waitstatesNonseq32[REGION_CART1] = memory->waitstatesNonseq32[REGION_CART1_EX] = memory->waitstatesSeq16[REGION_CART1] + 1 + memory->waitstatesSeq16[REGION_CART1];
- memory->waitstatesNonseq32[REGION_CART2] = memory->waitstatesNonseq32[REGION_CART2_EX] = memory->waitstatesSeq16[REGION_CART2] + 1 + memory->waitstatesSeq16[REGION_CART2];
+ memory->waitstatesNonseq32[REGION_CART0] = memory->waitstatesNonseq32[REGION_CART0_EX] = memory->waitstatesNonseq16[REGION_CART0] + 1 + memory->waitstatesSeq16[REGION_CART0];
+ memory->waitstatesNonseq32[REGION_CART1] = memory->waitstatesNonseq32[REGION_CART1_EX] = memory->waitstatesNonseq16[REGION_CART1] + 1 + memory->waitstatesSeq16[REGION_CART1];
+ memory->waitstatesNonseq32[REGION_CART2] = memory->waitstatesNonseq32[REGION_CART2_EX] = memory->waitstatesNonseq16[REGION_CART2] + 1 + memory->waitstatesSeq16[REGION_CART2];
memory->waitstatesSeq32[REGION_CART0] = memory->waitstatesSeq32[REGION_CART0_EX] = 2 * memory->waitstatesSeq16[REGION_CART0] + 1;
memory->waitstatesSeq32[REGION_CART1] = memory->waitstatesSeq32[REGION_CART1_EX] = 2 * memory->waitstatesSeq16[REGION_CART1] + 1;
@@ -1251,6 +1252,12 @@ uint16_t GBAMemoryWriteDMACNT_HI(struct GBA* gba, int dma, uint16_t control) {
struct GBAMemory* memory = &gba->memory;
struct GBADMA* currentDma = &memory->dma[dma];
int wasEnabled = GBADMARegisterIsEnable(currentDma->reg);
+ int oldTiming = GBADMARegisterGetTiming(currentDma->reg);
+ int newTiming = GBADMARegisterGetTiming(control);
+ // This is probably a huge hack...verify what this does on hardware
+ if (oldTiming && oldTiming != DMA_TIMING_CUSTOM && oldTiming != newTiming) {
+ wasEnabled = false;
+ }
currentDma->reg = control;
if (GBADMARegisterIsDRQ(currentDma->reg)) {
@@ -1476,3 +1483,12 @@ uint32_t _popcount32(unsigned bits) {
bits = (bits & 0x33333333) + ((bits >> 2) & 0x33333333);
return (((bits + (bits >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
}
+
+void _pristineCow(struct GBA* gba) {
+ if (gba->memory.rom != gba->pristineRom) {
+ return;
+ }
+ gba->memory.rom = anonymousMemoryMap(SIZE_CART0);
+ memcpy(gba->memory.rom, gba->pristineRom, gba->memory.romSize);
+ memset(((uint8_t*) gba->memory.rom) + gba->memory.romSize, 0xFF, SIZE_CART0 - gba->memory.romSize);
+}
diff --git a/src/gba/memory.h b/src/gba/memory.h
index db044d35b..ef39f42db 100644
--- a/src/gba/memory.h
+++ b/src/gba/memory.h
@@ -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
diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c
index eb8a1d193..54a409724 100644
--- a/src/gba/renderers/video-software.c
+++ b/src/gba/renderers/video-software.c
@@ -87,6 +87,12 @@ void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
renderer->d.getPixels = GBAVideoSoftwareRendererGetPixels;
renderer->d.putPixels = GBAVideoSoftwareRendererPutPixels;
+
+ renderer->d.disableBG[0] = false;
+ renderer->d.disableBG[1] = false;
+ renderer->d.disableBG[2] = false;
+ renderer->d.disableBG[3] = false;
+ renderer->d.disableOBJ = false;
}
static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
@@ -522,6 +528,8 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
softwareRenderer->windows[0].control.packed = 0xFF;
}
+ GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer);
+
int w;
x = 0;
for (w = 0; w < softwareRenderer->nWindows; ++w) {
@@ -599,10 +607,10 @@ static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer,
}
static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
- renderer->bg[0].enabled = GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt);
- renderer->bg[1].enabled = GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt);
- renderer->bg[2].enabled = GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt);
- renderer->bg[3].enabled = GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt);
+ renderer->bg[0].enabled = GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt) && !renderer->d.disableBG[0];
+ renderer->bg[1].enabled = GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt) && !renderer->d.disableBG[1];
+ renderer->bg[2].enabled = GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt) && !renderer->d.disableBG[2];
+ renderer->bg[3].enabled = GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt) && !renderer->d.disableBG[3];
}
static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) {
@@ -691,7 +699,7 @@ static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) {
int w;
renderer->end = 0;
int spriteLayers = 0;
- if (GBARegisterDISPCNTIsObjEnable(renderer->dispcnt)) {
+ if (GBARegisterDISPCNTIsObjEnable(renderer->dispcnt) && !renderer->d.disableOBJ) {
if (renderer->oamDirty) {
_cleanOAM(renderer);
}
diff --git a/src/gba/savedata.c b/src/gba/savedata.c
index bc9a34851..0d1c82475 100644
--- a/src/gba/savedata.c
+++ b/src/gba/savedata.c
@@ -14,6 +14,8 @@
#include
#include
+#define FLASH_SETTLE_CYCLES 18000
+
static void _flashSwitchBank(struct GBASavedata* savedata, int bank);
static void _flashErase(struct GBASavedata* savedata);
static void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart);
@@ -113,7 +115,7 @@ bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out) {
return true;
}
-void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type) {
+void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming) {
if (savedata->type != SAVEDATA_AUTODETECT) {
struct VFile* vf = savedata->vf;
GBASavedataDeinit(savedata);
@@ -123,7 +125,7 @@ void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type)
case SAVEDATA_FLASH512:
case SAVEDATA_FLASH1M:
savedata->type = type;
- GBASavedataInitFlash(savedata);
+ GBASavedataInitFlash(savedata, realisticTiming);
break;
case SAVEDATA_EEPROM:
GBASavedataInitEEPROM(savedata);
@@ -139,7 +141,7 @@ void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type)
}
}
-void GBASavedataInitFlash(struct GBASavedata* savedata) {
+void GBASavedataInitFlash(struct GBASavedata* savedata, bool realisticTiming) {
if (savedata->type == SAVEDATA_AUTODETECT) {
savedata->type = SAVEDATA_FLASH512;
}
@@ -162,6 +164,8 @@ void GBASavedataInitFlash(struct GBASavedata* savedata) {
}
savedata->currentBank = savedata->data;
+ savedata->dust = 0;
+ savedata->realisticTiming = realisticTiming;
if (end < SIZE_CART_FLASH512) {
memset(&savedata->data[end], 0xFF, flashSize - end);
}
@@ -226,6 +230,10 @@ uint8_t GBASavedataReadFlash(struct GBASavedata* savedata, uint16_t address) {
}
}
}
+ if (savedata->dust > 0 && (address >> 12) == savedata->settling) {
+ --savedata->dust;
+ return 0x5F;
+ }
return savedata->currentBank[address];
}
@@ -384,6 +392,8 @@ void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializ
state->savedata.readBitsRemaining = savedata->readBitsRemaining;
state->savedata.readAddress = savedata->readAddress;
state->savedata.writeAddress = savedata->writeAddress;
+ state->savedata.settlingSector = savedata->settling;
+ state->savedata.settlingDust = savedata->dust;
UNUSED(includeData); // TODO
}
@@ -393,13 +403,16 @@ void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerial
return;
}
if (savedata->type != state->savedata.type) {
- GBASavedataForceType(savedata, state->savedata.type);
+ GBASavedataForceType(savedata, state->savedata.type, savedata->realisticTiming);
}
savedata->command = state->savedata.command;
savedata->flashState = state->savedata.flashState;
savedata->readBitsRemaining = state->savedata.readBitsRemaining;
savedata->readAddress = state->savedata.readAddress;
savedata->writeAddress = state->savedata.writeAddress;
+ savedata->settling = state->savedata.settlingSector;
+ savedata->dust = state->savedata.settlingDust;
+
if (savedata->type == SAVEDATA_FLASH1M) {
_flashSwitchBank(savedata, state->savedata.flashBank);
}
@@ -434,5 +447,9 @@ void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart) {
if (savedata->type == SAVEDATA_FLASH1M) {
GBALog(0, GBA_LOG_DEBUG, "Performing unknown sector-size erase at 0x%04x", sectorStart);
}
+ savedata->settling = sectorStart >> 12;
+ if (savedata->realisticTiming) {
+ savedata->dust = FLASH_SETTLE_CYCLES;
+ }
memset(&savedata->currentBank[sectorStart & ~(size - 1)], 0xFF, size);
}
diff --git a/src/gba/savedata.h b/src/gba/savedata.h
index d1614283a..a3030eb03 100644
--- a/src/gba/savedata.h
+++ b/src/gba/savedata.h
@@ -73,6 +73,10 @@ struct GBASavedata {
uint8_t* currentBank;
+ bool realisticTiming;
+ unsigned settling;
+ int dust;
+
enum FlashStateMachine flashState;
};
@@ -82,9 +86,9 @@ void GBASavedataDeinit(struct GBASavedata* savedata);
void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf);
void GBASavedataUnmask(struct GBASavedata* savedata);
bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out);
-void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type);
+void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming);
-void GBASavedataInitFlash(struct GBASavedata* savedata);
+void GBASavedataInitFlash(struct GBASavedata* savedata, bool realisticTiming);
void GBASavedataInitEEPROM(struct GBASavedata* savedata);
void GBASavedataInitSRAM(struct GBASavedata* savedata);
diff --git a/src/gba/serialize.c b/src/gba/serialize.c
index 2395ee635..f6574d64c 100644
--- a/src/gba/serialize.c
+++ b/src/gba/serialize.c
@@ -256,6 +256,18 @@ void GBARecordFrame(struct GBAThread* thread) {
thread->rewindBuffer[offset] = state;
}
GBASerialize(thread->gba, state);
+
+ if (thread->rewindScreenBuffer) {
+ unsigned stride;
+ uint8_t* pixels = 0;
+ thread->gba->video.renderer->getPixels(thread->gba->video.renderer, &stride, (void*) &pixels);
+ if (pixels) {
+ size_t y;
+ for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) {
+ memcpy(&thread->rewindScreenBuffer[(offset * VIDEO_VERTICAL_PIXELS + y) * VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL], &pixels[y * stride * BYTES_PER_PIXEL], VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
+ }
+ }
+ }
thread->rewindBufferSize = thread->rewindBufferSize == thread->rewindBufferCapacity ? thread->rewindBufferCapacity : thread->rewindBufferSize + 1;
thread->rewindBufferWriteOffset = (offset + 1) % thread->rewindBufferCapacity;
}
@@ -273,12 +285,15 @@ void GBARewindSettingsChanged(struct GBAThread* threadContext, int newCapacity,
GBADeallocateState(threadContext->rewindBuffer[i]);
}
free(threadContext->rewindBuffer);
+ free(threadContext->rewindScreenBuffer);
}
threadContext->rewindBufferCapacity = newCapacity;
if (threadContext->rewindBufferCapacity > 0) {
threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(struct GBASerializedState*));
+ threadContext->rewindScreenBuffer = calloc(threadContext->rewindBufferCapacity, VIDEO_VERTICAL_PIXELS * VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
} else {
threadContext->rewindBuffer = 0;
+ threadContext->rewindScreenBuffer = 0;
}
}
@@ -291,15 +306,18 @@ 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);
+ if (thread->rewindScreenBuffer) {
+ thread->gba->video.renderer->putPixels(thread->gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, &thread->rewindScreenBuffer[offset * VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL]);
+ }
}
void GBARewindAll(struct GBAThread* thread) {
diff --git a/src/gba/serialize.h b/src/gba/serialize.h
index 5a2d9a34b..3bb416655 100644
--- a/src/gba/serialize.h
+++ b/src/gba/serialize.h
@@ -162,8 +162,9 @@ extern const uint32_t GBA_SAVESTATE_MAGIC;
* | 0x002E3 - 0x002E3: Reserved
* | 0x002E4 - 0x002E7: EEPROM read bits remaining
* | 0x002E8 - 0x002EB: EEPROM read address
- * | 0x002EC - 0x002EBF EEPROM write address
- * 0x002F0 - 0x002F3: Reserved (leave zero)
+ * | 0x002EC - 0x002EF: EEPROM write address
+ * | 0x002F0 - 0x002F1: Flash settling sector
+ * | 0x002F2 - 0x002F3: Flash settling remaining
* 0x002F4 - 0x002FF: Prefetch
* | 0x002F4 - 0x002F7: GBA BIOS bus prefetch
* | 0x002F8 - 0x002FB: CPU prefecth (decode slot)
@@ -297,10 +298,10 @@ struct GBASerializedState {
int32_t readBitsRemaining;
uint32_t readAddress;
uint32_t writeAddress;
+ uint16_t settlingSector;
+ uint16_t settlingDust;
} savedata;
- uint32_t reservedPadding;
-
uint32_t biosPrefetch;
uint32_t cpuPrefetch[2];
diff --git a/src/gba/sharkport.c b/src/gba/sharkport.c
new file mode 100644
index 000000000..3c9707abc
--- /dev/null
+++ b/src/gba/sharkport.c
@@ -0,0 +1,260 @@
+/* Copyright (c) 2013-2015 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 "sharkport.h"
+
+#include "gba/gba.h"
+#include "util/vfs.h"
+
+static const char* const SHARKPORT_HEADER = "SharkPortSave";
+
+bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChecksum) {
+ union {
+ char c[0x1C];
+ int32_t i;
+ } buffer;
+ if (vf->read(vf, &buffer.i, 4) < 4) {
+ return false;
+ }
+ int32_t size;
+ LOAD_32(size, 0, &buffer.i);
+ if (size != (int32_t) strlen(SHARKPORT_HEADER)) {
+ return false;
+ }
+ if (vf->read(vf, buffer.c, size) < size) {
+ return false;
+ }
+ if (memcmp(SHARKPORT_HEADER, buffer.c, size) != 0) {
+ return false;
+ }
+ if (vf->read(vf, &buffer.i, 4) < 4) {
+ return false;
+ }
+ LOAD_32(size, 0, &buffer.i);
+ if (size != 0x000F0000) {
+ // What is this value?
+ return false;
+ }
+
+ // Skip first three fields
+ if (vf->read(vf, &buffer.i, 4) < 4) {
+ return false;
+ }
+ LOAD_32(size, 0, &buffer.i);
+ if (vf->seek(vf, size, SEEK_CUR) < 0) {
+ return false;
+ }
+
+ if (vf->read(vf, &buffer.i, 4) < 4) {
+ return false;
+ }
+ LOAD_32(size, 0, &buffer.i);
+ if (vf->seek(vf, size, SEEK_CUR) < 0) {
+ return false;
+ }
+
+ if (vf->read(vf, &buffer.i, 4) < 4) {
+ return false;
+ }
+ LOAD_32(size, 0, &buffer.i);
+ if (vf->seek(vf, size, SEEK_CUR) < 0) {
+ return false;
+ }
+
+ // Read payload
+ if (vf->read(vf, &buffer.i, 4) < 4) {
+ return false;
+ }
+ LOAD_32(size, 0, &buffer.i);
+ if (size < 0x1C || size > SIZE_CART_FLASH1M + 0x1C) {
+ return false;
+ }
+ char* payload = malloc(size);
+ if (vf->read(vf, payload, size) < size) {
+ goto cleanup;
+ }
+
+ struct GBACartridge* cart = (struct GBACartridge*) gba->memory.rom;
+ memcpy(buffer.c, cart->title, 16);
+ buffer.c[0x10] = 0;
+ buffer.c[0x11] = 0;
+ buffer.c[0x12] = cart->checksum;
+ buffer.c[0x13] = cart->maker;
+ buffer.c[0x14] = 1;
+ buffer.c[0x15] = 0;
+ buffer.c[0x16] = 0;
+ buffer.c[0x17] = 0;
+ buffer.c[0x18] = 0;
+ buffer.c[0x19] = 0;
+ buffer.c[0x1A] = 0;
+ buffer.c[0x1B] = 0;
+ if (memcmp(buffer.c, payload, 0x1C) != 0) {
+ goto cleanup;
+ }
+
+ uint32_t checksum;
+ if (vf->read(vf, &buffer.i, 4) < 4) {
+ goto cleanup;
+ }
+ LOAD_32(checksum, 0, &buffer.i);
+
+ if (testChecksum) {
+ uint32_t calcChecksum = 0;
+ int i;
+ for (i = 0; i < size; ++i) {
+ calcChecksum += ((int32_t) payload[i]) << (calcChecksum % 24);
+ }
+
+ if (calcChecksum != checksum) {
+ goto cleanup;
+ }
+ }
+
+ uint32_t copySize = size - 0x1C;
+ switch (gba->memory.savedata.type) {
+ case SAVEDATA_SRAM:
+ if (copySize > SIZE_CART_SRAM) {
+ copySize = SIZE_CART_SRAM;
+ }
+ break;
+ case SAVEDATA_FLASH512:
+ if (copySize > SIZE_CART_FLASH512) {
+ GBASavedataForceType(&gba->memory.savedata, SAVEDATA_FLASH1M, gba->memory.savedata.realisticTiming);
+ }
+ // Fall through
+ case SAVEDATA_FLASH1M:
+ if (copySize > SIZE_CART_FLASH1M) {
+ copySize = SIZE_CART_FLASH1M;
+ }
+ break;
+ case SAVEDATA_EEPROM:
+ if (copySize > SIZE_CART_EEPROM) {
+ copySize = SAVEDATA_EEPROM;
+ }
+ break;
+ case SAVEDATA_FORCE_NONE:
+ case SAVEDATA_AUTODETECT:
+ goto cleanup;
+ }
+
+ memcpy(gba->memory.savedata.data, &payload[0x1C], copySize);
+
+ free(payload);
+ return true;
+
+cleanup:
+ free(payload);
+ return false;
+}
+
+
+bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf) {
+ union {
+ char c[0x1C];
+ int32_t i;
+ } buffer;
+ int32_t size = strlen(SHARKPORT_HEADER);
+ STORE_32(size, 0, &buffer.i);
+ if (vf->write(vf, &buffer.i, 4) < 4) {
+ return false;
+ }
+ if (vf->write(vf, SHARKPORT_HEADER, size) < size) {
+ return false;
+ }
+
+ size = 0x000F0000;
+ STORE_32(size, 0, &buffer.i);
+ if (vf->write(vf, &buffer.i, 4) < 4) {
+ return false;
+ }
+
+ const struct GBACartridge* cart = (const struct GBACartridge*) gba->memory.rom;
+ size = sizeof(cart->title);
+ STORE_32(size, 0, &buffer.i);
+ if (vf->write(vf, &buffer.i, 4) < 4) {
+ return false;
+ }
+ if (vf->write(vf, cart->title, size) < 4) {
+ return false;
+ }
+
+ time_t t = time(0);
+ struct tm* tm = localtime(&t);
+ size = strftime(&buffer.c[4], sizeof(buffer.c) - 4, "%m/%d/%Y %I:%M:%S %p", tm);
+ STORE_32(size, 0, &buffer.i);
+ if (vf->write(vf, buffer.c, size + 4) < size + 4) {
+ return false;
+ }
+
+ // Last field is blank
+ size = 0;
+ STORE_32(size, 0, &buffer.i);
+ if (vf->write(vf, &buffer.i, 4) < 4) {
+ return false;
+ }
+
+ // Write payload
+ size = 0x1C;
+ switch (gba->memory.savedata.type) {
+ case SAVEDATA_SRAM:
+ size += SIZE_CART_SRAM;
+ break;
+ case SAVEDATA_FLASH512:
+ size += SIZE_CART_FLASH512;
+ break;
+ case SAVEDATA_FLASH1M:
+ size += SIZE_CART_FLASH1M;
+ break;
+ case SAVEDATA_EEPROM:
+ size += SIZE_CART_EEPROM;
+ break;
+ case SAVEDATA_FORCE_NONE:
+ case SAVEDATA_AUTODETECT:
+ return false;
+ }
+ STORE_32(size, 0, &buffer.i);
+ if (vf->write(vf, &buffer.i, 4) < 4) {
+ return false;
+ }
+ size -= 0x1C;
+
+ memcpy(buffer.c, cart->title, 16);
+ buffer.c[0x10] = 0;
+ buffer.c[0x11] = 0;
+ buffer.c[0x12] = cart->checksum;
+ buffer.c[0x13] = cart->maker;
+ buffer.c[0x14] = 1;
+ buffer.c[0x15] = 0;
+ buffer.c[0x16] = 0;
+ buffer.c[0x17] = 0;
+ buffer.c[0x18] = 0;
+ buffer.c[0x19] = 0;
+ buffer.c[0x1A] = 0;
+ buffer.c[0x1B] = 0;
+ if (vf->write(vf, buffer.c, 0x1C) < 0x1C) {
+ return false;
+ }
+
+ uint32_t checksum = 0;
+ int i;
+ for (i = 0; i < 0x1C; ++i) {
+ checksum += buffer.c[i] << (checksum % 24);
+ }
+
+ if (vf->write(vf, gba->memory.savedata.data, size) < size) {
+ return false;
+ }
+
+ for (i = 0; i < size; ++i) {
+ checksum += ((char) gba->memory.savedata.data[i]) << (checksum % 24);
+ }
+
+ STORE_32(checksum, 0, &buffer.i);
+ if (vf->write(vf, &buffer.i, 4) < 4) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/gba/sharkport.h b/src/gba/sharkport.h
new file mode 100644
index 000000000..520ec246e
--- /dev/null
+++ b/src/gba/sharkport.h
@@ -0,0 +1,17 @@
+/* Copyright (c) 2013-2015 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 GBA_SHARKPORT_H
+#define GBA_SHARKPORT_H
+
+#include "util/common.h"
+
+struct GBA;
+struct VFile;
+
+bool GBASavedataImportSharkPort(struct GBA* gba, struct VFile* vf, bool testChecksum);
+bool GBASavedataExportSharkPort(const struct GBA* gba, struct VFile* vf);
+
+#endif
diff --git a/src/gba/supervisor/config.c b/src/gba/supervisor/config.c
index 9f3427af0..437f0e104 100644
--- a/src/gba/supervisor/config.c
+++ b/src/gba/supervisor/config.c
@@ -6,6 +6,7 @@
#include "config.h"
#include "util/formatting.h"
+#include "util/vfs.h"
#include
@@ -13,9 +14,6 @@
#include
#include
#include
-#define PATH_SEP "\\"
-#else
-#define PATH_SEP "/"
#endif
#define SECTION_NAME_MAX 128
@@ -181,6 +179,7 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
_lookupCharValue(config, "bios", &opts->bios);
_lookupIntValue(config, "logLevel", &opts->logLevel);
_lookupIntValue(config, "frameskip", &opts->frameskip);
+ _lookupIntValue(config, "volume", &opts->volume);
_lookupIntValue(config, "rewindBufferCapacity", &opts->rewindBufferCapacity);
_lookupIntValue(config, "rewindBufferInterval", &opts->rewindBufferInterval);
_lookupFloatValue(config, "fpsTarget", &opts->fpsTarget);
@@ -190,7 +189,7 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
}
int fakeBool;
- if (_lookupIntValue(config, "useSync", &fakeBool)) {
+ if (_lookupIntValue(config, "useBios", &fakeBool)) {
opts->useBios = fakeBool;
}
if (_lookupIntValue(config, "audioSync", &fakeBool)) {
@@ -205,6 +204,9 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
if (_lookupIntValue(config, "resampleVideo", &fakeBool)) {
opts->resampleVideo = fakeBool;
}
+ if (_lookupIntValue(config, "mute", &fakeBool)) {
+ opts->mute = fakeBool;
+ }
if (_lookupIntValue(config, "skipBios", &fakeBool)) {
opts->skipBios = fakeBool;
}
@@ -245,6 +247,8 @@ void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* op
ConfigurationSetIntValue(&config->defaultsTable, 0, "fullscreen", opts->fullscreen);
ConfigurationSetIntValue(&config->defaultsTable, 0, "width", opts->width);
ConfigurationSetIntValue(&config->defaultsTable, 0, "height", opts->height);
+ ConfigurationSetIntValue(&config->defaultsTable, 0, "volume", opts->volume);
+ ConfigurationSetIntValue(&config->defaultsTable, 0, "mute", opts->mute);
ConfigurationSetIntValue(&config->defaultsTable, 0, "lockAspectRatio", opts->lockAspectRatio);
ConfigurationSetIntValue(&config->defaultsTable, 0, "resampleVideo", opts->resampleVideo);
diff --git a/src/gba/supervisor/config.h b/src/gba/supervisor/config.h
index 369bdbd29..fd3eaba82 100644
--- a/src/gba/supervisor/config.h
+++ b/src/gba/supervisor/config.h
@@ -36,6 +36,9 @@ struct GBAOptions {
bool lockAspectRatio;
bool resampleVideo;
+ int volume;
+ bool mute;
+
bool videoSync;
bool audioSync;
diff --git a/src/gba/supervisor/overrides.c b/src/gba/supervisor/overrides.c
index 2c5759040..ca57a9a76 100644
--- a/src/gba/supervisor/overrides.c
+++ b/src/gba/supervisor/overrides.c
@@ -11,6 +11,10 @@
#include "util/configuration.h"
static const struct GBACartridgeOverride _overrides[] = {
+ // Advance Wars
+ { "AWRE", SAVEDATA_FLASH512, HW_NONE, 0x8038810 },
+ { "AWRP", SAVEDATA_FLASH512, HW_NONE, 0x8038810 },
+
// Boktai: The Sun is in Your Hand
{ "U3IJ", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
{ "U3IE", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
@@ -47,6 +51,9 @@ static const struct GBACartridgeOverride _overrides[] = {
// Mega Man Battle Network
{ "AREE", SAVEDATA_SRAM, HW_NONE, 0x800032E },
+ // Mega Man Zero
+ { "AZCE", SAVEDATA_SRAM, HW_NONE, 0x80004E8 },
+
// Metal Slug Advance
{ "BSME", SAVEDATA_EEPROM, HW_NONE, 0x8000290 },
@@ -69,13 +76,13 @@ static const struct GBACartridgeOverride _overrides[] = {
{ "AXPF", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
// Pokemon Emerald
- { "BPEJ", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
+ { "BPEJ", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 },
{ "BPEE", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 },
- { "BPEP", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
- { "BPEI", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
- { "BPES", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
- { "BPED", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
- { "BPEF", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
+ { "BPEP", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 },
+ { "BPEI", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 },
+ { "BPES", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 },
+ { "BPED", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 },
+ { "BPEF", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 },
// Pokemon Mystery Dungeon
{ "B24J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
@@ -115,15 +122,19 @@ static const struct GBACartridgeOverride _overrides[] = {
{ "U33J", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
// Super Mario Advance 2
+ { "AA2J", SAVEDATA_EEPROM, HW_NONE, 0x800052E },
{ "AA2E", SAVEDATA_EEPROM, HW_NONE, 0x800052E },
+ { "AA2P", SAVEDATA_EEPROM, HW_NONE, 0x800052E },
// Super Mario Advance 3
+ { "A3AJ", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C },
{ "A3AE", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C },
+ { "A3AP", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C },
// Super Mario Advance 4
- { "AX4J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
- { "AX4E", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
- { "AX4P", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
+ { "AX4J", SAVEDATA_FLASH1M, HW_NONE, 0x800072A },
+ { "AX4E", SAVEDATA_FLASH1M, HW_NONE, 0x800072A },
+ { "AX4P", SAVEDATA_FLASH1M, HW_NONE, 0x800072A },
// Top Gun - Combat Zones
{ "A2YE", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE },
@@ -145,7 +156,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
@@ -249,7 +260,7 @@ void GBAOverrideSave(struct Configuration* config, const struct GBACartridgeOver
void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* override) {
if (override->savetype != SAVEDATA_AUTODETECT) {
- GBASavedataForceType(&gba->memory.savedata, override->savetype);
+ GBASavedataForceType(&gba->memory.savedata, override->savetype, gba->realisticTiming);
}
if (override->hardware != HW_NO_OVERRIDE) {
diff --git a/src/gba/supervisor/thread.c b/src/gba/supervisor/thread.c
index c595d21d1..a436c522b 100644
--- a/src/gba/supervisor/thread.c
+++ b/src/gba/supervisor/thread.c
@@ -233,6 +233,15 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
GBASIOSetDriverSet(&gba.sio, &threadContext->sioDrivers);
+ if (threadContext->volume == 0) {
+ threadContext->volume = GBA_AUDIO_VOLUME_MAX;
+ }
+ if (threadContext->mute) {
+ gba.audio.volume = 0;
+ } else {
+ gba.audio.volume = threadContext->volume;
+ }
+
gba.keySource = &threadContext->activeKeys;
if (threadContext->startCallback) {
@@ -316,6 +325,8 @@ void GBAMapOptionsToContext(const struct GBAOptions* opts, struct GBAThread* thr
threadContext->bios = 0;
}
threadContext->frameskip = opts->frameskip;
+ threadContext->volume = opts->volume;
+ threadContext->mute = opts->mute;
threadContext->logLevel = opts->logLevel;
if (opts->rewindEnable) {
threadContext->rewindBufferCapacity = opts->rewindBufferCapacity;
@@ -370,6 +381,7 @@ bool GBAThreadStart(struct GBAThread* threadContext) {
threadContext->sync.videoFrameSkip = 0;
threadContext->rewindBuffer = 0;
+ threadContext->rewindScreenBuffer = 0;
int newCapacity = threadContext->rewindBufferCapacity;
int newInterval = threadContext->rewindBufferInterval;
threadContext->rewindBufferCapacity = 0;
@@ -520,6 +532,7 @@ void GBAThreadJoin(struct GBAThread* threadContext) {
}
}
free(threadContext->rewindBuffer);
+ free(threadContext->rewindScreenBuffer);
if (threadContext->rom) {
threadContext->rom->close(threadContext->rom);
diff --git a/src/gba/supervisor/thread.h b/src/gba/supervisor/thread.h
index 7a2a3778b..ab98610e5 100644
--- a/src/gba/supervisor/thread.h
+++ b/src/gba/supervisor/thread.h
@@ -80,6 +80,8 @@ struct GBAThread {
float fpsTarget;
size_t audioBuffers;
bool skipBios;
+ int volume;
+ bool mute;
// Threading state
Thread thread;
@@ -104,6 +106,7 @@ struct GBAThread {
int rewindBufferNext;
struct GBASerializedState** rewindBuffer;
int rewindBufferWriteOffset;
+ uint8_t* rewindScreenBuffer;
struct GBACheatDevice* cheats;
};
diff --git a/src/gba/video.h b/src/gba/video.h
index b7648719e..774f8de99 100644
--- a/src/gba/video.h
+++ b/src/gba/video.h
@@ -167,6 +167,9 @@ struct GBAVideoRenderer {
uint16_t* palette;
uint16_t* vram;
union GBAOAM* oam;
+
+ bool disableBG[4];
+ bool disableOBJ;
};
struct GBAVideo {
diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt
index 69cf68712..7956207a5 100644
--- a/src/platform/qt/CMakeLists.txt
+++ b/src/platform/qt/CMakeLists.txt
@@ -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)
@@ -41,6 +44,8 @@ set(SOURCE_FILES
CheatsView.cpp
ConfigController.cpp
Display.cpp
+ DisplayGL.cpp
+ DisplayQt.cpp
GBAApp.cpp
GBAKeyEditor.cpp
GIFView.cpp
@@ -53,11 +58,13 @@ set(SOURCE_FILES
LogView.cpp
MultiplayerController.cpp
OverrideView.cpp
+ PaletteView.cpp
SavestateButton.cpp
SensorView.cpp
SettingsView.cpp
ShortcutController.cpp
ShortcutView.cpp
+ Swatch.cpp
Window.cpp
VFileDevice.cpp
VideoView.cpp)
@@ -68,13 +75,14 @@ qt5_wrap_ui(UI_FILES
LoadSaveState.ui
LogView.ui
OverrideView.ui
+ PaletteView.ui
SensorView.ui
SettingsView.ui
ShortcutView.ui
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 +95,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 +123,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
diff --git a/src/platform/qt/CheatsView.cpp b/src/platform/qt/CheatsView.cpp
index 444e336c7..40e1be426 100644
--- a/src/platform/qt/CheatsView.cpp
+++ b/src/platform/qt/CheatsView.cpp
@@ -40,6 +40,10 @@ CheatsView::CheatsView(GameController* controller, QWidget* parent)
enterCheat(GBACheatAddGameSharkLine);
});
+ connect(m_ui.addPAR, &QPushButton::clicked, [this]() {
+ enterCheat(GBACheatAddProActionReplayLine);
+ });
+
connect(m_ui.addCB, &QPushButton::clicked, [this]() {
enterCheat(GBACheatAddCodeBreakerLine);
});
@@ -113,4 +117,4 @@ void CheatsView::enterCheat(std::function callb
}
m_controller->threadContinue();
m_ui.codeEntry->clear();
-}
\ No newline at end of file
+}
diff --git a/src/platform/qt/CheatsView.ui b/src/platform/qt/CheatsView.ui
index b5a1afdc0..a272e8e8f 100644
--- a/src/platform/qt/CheatsView.ui
+++ b/src/platform/qt/CheatsView.ui
@@ -37,9 +37,6 @@
-
-
- false
-
Add Pro Action Replay
diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp
index ba21ff288..9857727d8 100644
--- a/src/platform/qt/ConfigController.cpp
+++ b/src/platform/qt/ConfigController.cpp
@@ -108,6 +108,7 @@ ConfigController::ConfigController(QObject* parent)
m_opts.videoSync = GameController::VIDEO_SYNC;
m_opts.fpsTarget = 60;
m_opts.audioBuffers = 2048;
+ m_opts.volume = GBA_AUDIO_VOLUME_MAX;
m_opts.logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL;
m_opts.rewindEnable = false;
m_opts.rewindBufferInterval = 0;
diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp
index 989d967b7..8ee580cec 100644
--- a/src/platform/qt/Display.cpp
+++ b/src/platform/qt/Display.cpp
@@ -5,283 +5,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "Display.h"
-#include
-#include
-
extern "C" {
-#include "gba/supervisor/thread.h"
+#include "gba/video.h"
}
using namespace QGBA;
-static const GLint _glVertices[] = {
- 0, 0,
- 256, 0,
- 256, 256,
- 0, 256
-};
-
-static const GLint _glTexCoords[] = {
- 0, 0,
- 1, 0,
- 1, 1,
- 0, 1
-};
-
-Display::Display(QGLFormat format, QWidget* parent)
- : QGLWidget(format, parent)
- , m_painter(nullptr)
- , m_started(false)
+Display::Display(QWidget* parent)
+ : QWidget(parent)
{
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
- setAutoBufferSwap(false);
setCursor(Qt::BlankCursor);
}
-
-void Display::startDrawing(const uint32_t* buffer, GBAThread* thread) {
- if (m_started) {
- return;
- }
- m_painter = new Painter(this);
- m_painter->setContext(thread);
- m_painter->setBacking(buffer);
- m_context = thread;
- doneCurrent();
- m_painter->start();
- m_started = true;
-
- lockAspectRatio(m_lockAspectRatio);
- filter(m_filter);
-}
-
-void Display::stopDrawing() {
- if (m_started) {
- if (GBAThreadIsActive(m_context)) {
- GBAThreadInterrupt(m_context);
- GBASyncSuspendDrawing(&m_context->sync);
- }
- m_painter->stop();
- m_started = false;
- if (GBAThreadIsActive(m_context)) {
- GBASyncResumeDrawing(&m_context->sync);
- GBAThreadContinue(m_context);
- }
- }
-}
-
-void Display::pauseDrawing() {
- if (m_started) {
- if (GBAThreadIsActive(m_context)) {
- GBAThreadInterrupt(m_context);
- GBASyncSuspendDrawing(&m_context->sync);
- }
- m_painter->pause();
- if (GBAThreadIsActive(m_context)) {
- GBASyncResumeDrawing(&m_context->sync);
- GBAThreadContinue(m_context);
- }
- }
-}
-
-void Display::unpauseDrawing() {
- if (m_started) {
- if (GBAThreadIsActive(m_context)) {
- GBAThreadInterrupt(m_context);
- GBASyncSuspendDrawing(&m_context->sync);
- }
- m_painter->unpause();
- if (GBAThreadIsActive(m_context)) {
- GBASyncResumeDrawing(&m_context->sync);
- GBAThreadContinue(m_context);
- }
- }
-}
-
-void Display::forceDraw() {
- if (m_started) {
- m_painter->forceDraw();
- }
-}
-
-void Display::lockAspectRatio(bool lock) {
- m_lockAspectRatio = lock;
- if (m_started) {
- m_painter->lockAspectRatio(lock);
- }
-}
-
-void Display::filter(bool filter) {
- m_filter = filter;
- if (m_started) {
- m_painter->filter(filter);
- }
-}
-
-#ifdef USE_PNG
-void Display::screenshot() {
- GBAThreadInterrupt(m_context);
- GBAThreadTakeScreenshot(m_context);
- GBAThreadContinue(m_context);
-}
-#endif
-
-void Display::initializeGL() {
- glClearColor(0, 0, 0, 0);
- glClear(GL_COLOR_BUFFER_BIT);
- swapBuffers();
-}
-
-void Display::resizeEvent(QResizeEvent* event) {
- if (m_started) {
- GBAThreadInterrupt(m_context);
- GBASyncSuspendDrawing(&m_context->sync);
- m_painter->resize(event->size());
- GBASyncResumeDrawing(&m_context->sync);
- GBAThreadContinue(m_context);
- }
-}
-
-Painter::Painter(Display* parent)
- : m_gl(parent)
- , m_lockAspectRatio(false)
- , m_filter(false)
-{
- m_size = parent->size();
-}
-
-void Painter::setContext(GBAThread* context) {
- m_context = context;
-}
-
-void Painter::setBacking(const uint32_t* backing) {
- m_backing = backing;
-}
-
-void Painter::resize(const QSize& size) {
- m_size = size;
- forceDraw();
- forceDraw();
-}
-
-void Painter::lockAspectRatio(bool lock) {
- m_lockAspectRatio = lock;
- forceDraw();
- forceDraw();
-}
-
-void Painter::filter(bool filter) {
- m_filter = filter;
- m_gl->makeCurrent();
- if (m_filter) {
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- } else {
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- }
- m_gl->doneCurrent();
- forceDraw();
-}
-
-void Painter::start() {
- m_gl->makeCurrent();
- glEnable(GL_TEXTURE_2D);
- glGenTextures(1, &m_tex);
- glBindTexture(GL_TEXTURE_2D, m_tex);
- glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- if (m_filter) {
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- } else {
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- }
- glEnableClientState(GL_TEXTURE_COORD_ARRAY);
- glEnableClientState(GL_VERTEX_ARRAY);
- glVertexPointer(2, GL_INT, 0, _glVertices);
- glTexCoordPointer(2, GL_INT, 0, _glTexCoords);
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- glOrtho(0, 240, 160, 0, 0, 1);
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
- m_gl->doneCurrent();
-
- m_drawTimer = new QTimer;
- m_drawTimer->moveToThread(QThread::currentThread());
- m_drawTimer->setInterval(0);
- connect(m_drawTimer, SIGNAL(timeout()), this, SLOT(draw()));
- m_drawTimer->start();
-}
-
-void Painter::draw() {
- m_gl->makeCurrent();
- GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip);
- performDraw();
- GBASyncWaitFrameEnd(&m_context->sync);
- m_gl->swapBuffers();
- m_gl->doneCurrent();
-}
-
-void Painter::forceDraw() {
- m_gl->makeCurrent();
- glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio());
- glClear(GL_COLOR_BUFFER_BIT);
- performDraw();
- m_gl->swapBuffers();
- m_gl->doneCurrent();
-}
-
-void Painter::stop() {
- m_drawTimer->stop();
- delete m_drawTimer;
- m_gl->makeCurrent();
- glDeleteTextures(1, &m_tex);
- glClear(GL_COLOR_BUFFER_BIT);
- m_gl->swapBuffers();
- m_gl->doneCurrent();
- m_gl->context()->moveToThread(QApplication::instance()->thread());
-}
-
-void Painter::pause() {
- m_drawTimer->stop();
- // Make sure both buffers are filled
- forceDraw();
- forceDraw();
-}
-
-void Painter::unpause() {
- m_drawTimer->start();
-}
-
-void Painter::performDraw() {
- int w = m_size.width() * m_gl->devicePixelRatio();
- int h = m_size.height() * m_gl->devicePixelRatio();
-#ifndef Q_OS_MAC
- // TODO: This seems to cause framerates to drag down to 120 FPS on OS X,
- // even if the emulator can go faster. Look into why.
- glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio());
- glClear(GL_COLOR_BUFFER_BIT);
-#endif
- int drawW = w;
- int drawH = h;
- if (m_lockAspectRatio) {
- if (w * 2 > h * 3) {
- drawW = h * 3 / 2;
- } else if (w * 2 < h * 3) {
- drawH = w * 2 / 3;
- }
- }
- glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);
-#ifdef COLOR_16_BIT
-#ifdef COLOR_5_6_5
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, m_backing);
-#else
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, m_backing);
-#endif
-#else
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_backing);
-#endif
- glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
- if (m_context->sync.videoFrameWait) {
- glFlush();
- }
-}
diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h
index 07c22e087..6a22fce5b 100644
--- a/src/platform/qt/Display.h
+++ b/src/platform/qt/Display.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau
+/* Copyright (c) 2013-2015 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
@@ -6,77 +6,27 @@
#ifndef QGBA_DISPLAY
#define QGBA_DISPLAY
-#include
-#include
-#include
+#include
struct GBAThread;
namespace QGBA {
-class Painter;
-class Display : public QGLWidget {
+class Display : public QWidget {
Q_OBJECT
public:
- Display(QGLFormat format, QWidget* parent = nullptr);
+ Display(QWidget* parent = nullptr);
public slots:
- void startDrawing(const uint32_t* buffer, GBAThread* context);
- void stopDrawing();
- void pauseDrawing();
- void unpauseDrawing();
- void forceDraw();
- void lockAspectRatio(bool lock);
- void filter(bool filter);
-#ifdef USE_PNG
- void screenshot();
-#endif
-
-protected:
- virtual void initializeGL() override;
- virtual void paintEvent(QPaintEvent*) override {};
- virtual void resizeEvent(QResizeEvent*) override;
-
-private:
- Painter* m_painter;
- bool m_started;
- GBAThread* m_context;
- bool m_lockAspectRatio;
- bool m_filter;
-};
-
-class Painter : public QObject {
-Q_OBJECT
-
-public:
- Painter(Display* parent);
-
- void setContext(GBAThread*);
- void setBacking(const uint32_t*);
-
-public slots:
- void forceDraw();
- void draw();
- void start();
- void stop();
- void pause();
- void unpause();
- void resize(const QSize& size);
- void lockAspectRatio(bool lock);
- void filter(bool filter);
-
-private:
- void performDraw();
-
- QTimer* m_drawTimer;
- GBAThread* m_context;
- const uint32_t* m_backing;
- GLuint m_tex;
- QGLWidget* m_gl;
- QSize m_size;
- bool m_lockAspectRatio;
- bool m_filter;
+ virtual void startDrawing(GBAThread* context) = 0;
+ virtual void stopDrawing() = 0;
+ virtual void pauseDrawing() = 0;
+ virtual void unpauseDrawing() = 0;
+ virtual void forceDraw() = 0;
+ virtual void lockAspectRatio(bool lock) = 0;
+ virtual void filter(bool filter) = 0;
+ virtual void framePosted(const uint32_t*) = 0;
};
}
diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp
new file mode 100644
index 000000000..28ddb4742
--- /dev/null
+++ b/src/platform/qt/DisplayGL.cpp
@@ -0,0 +1,282 @@
+/* Copyright (c) 2013-2015 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 "DisplayGL.h"
+
+#include
+#include
+
+extern "C" {
+#include "gba/supervisor/thread.h"
+}
+
+using namespace QGBA;
+
+static const GLint _glVertices[] = {
+ 0, 0,
+ 256, 0,
+ 256, 256,
+ 0, 256
+};
+
+static const GLint _glTexCoords[] = {
+ 0, 0,
+ 1, 0,
+ 1, 1,
+ 0, 1
+};
+
+DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent)
+ : Display(parent)
+ , m_painter(new Painter(format, this))
+ , m_started(false)
+{
+}
+
+void DisplayGL::startDrawing(GBAThread* thread) {
+ if (m_started) {
+ return;
+ }
+ m_painter->setContext(thread);
+ m_context = thread;
+ m_painter->start();
+ m_painter->resize(size());
+ m_painter->move(0, 0);
+ m_started = true;
+
+ lockAspectRatio(m_lockAspectRatio);
+ filter(m_filter);
+}
+
+void DisplayGL::stopDrawing() {
+ if (m_started) {
+ if (GBAThreadIsActive(m_context)) {
+ GBAThreadInterrupt(m_context);
+ GBASyncSuspendDrawing(&m_context->sync);
+ }
+ m_painter->stop();
+ m_started = false;
+ if (GBAThreadIsActive(m_context)) {
+ GBASyncResumeDrawing(&m_context->sync);
+ GBAThreadContinue(m_context);
+ }
+ }
+}
+
+void DisplayGL::pauseDrawing() {
+ if (m_started) {
+ if (GBAThreadIsActive(m_context)) {
+ GBAThreadInterrupt(m_context);
+ GBASyncSuspendDrawing(&m_context->sync);
+ }
+ m_painter->pause();
+ if (GBAThreadIsActive(m_context)) {
+ GBASyncResumeDrawing(&m_context->sync);
+ GBAThreadContinue(m_context);
+ }
+ }
+}
+
+void DisplayGL::unpauseDrawing() {
+ if (m_started) {
+ if (GBAThreadIsActive(m_context)) {
+ GBAThreadInterrupt(m_context);
+ GBASyncSuspendDrawing(&m_context->sync);
+ }
+ m_painter->unpause();
+ if (GBAThreadIsActive(m_context)) {
+ GBASyncResumeDrawing(&m_context->sync);
+ GBAThreadContinue(m_context);
+ }
+ }
+}
+
+void DisplayGL::forceDraw() {
+ if (m_started) {
+ m_painter->forceDraw();
+ }
+}
+
+void DisplayGL::lockAspectRatio(bool lock) {
+ m_lockAspectRatio = lock;
+ if (m_started) {
+ m_painter->lockAspectRatio(lock);
+ }
+}
+
+void DisplayGL::filter(bool filter) {
+ m_filter = filter;
+ if (m_started) {
+ m_painter->filter(filter);
+ }
+}
+
+void DisplayGL::framePosted(const uint32_t* buffer) {
+ if (buffer) {
+ m_painter->setBacking(buffer);
+ }
+}
+
+void DisplayGL::resizeEvent(QResizeEvent* event) {
+ m_painter->resize(event->size());
+}
+
+Painter::Painter(const QGLFormat& format, QWidget* parent)
+ : QGLWidget(format, parent)
+ , m_drawTimer(nullptr)
+ , m_lockAspectRatio(false)
+ , m_filter(false)
+{
+ setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
+ m_size = parent->size();
+ setAutoBufferSwap(false);
+}
+
+void Painter::setContext(GBAThread* context) {
+ m_context = context;
+}
+
+void Painter::setBacking(const uint32_t* backing) {
+ makeCurrent();
+#ifdef COLOR_16_BIT
+#ifdef COLOR_5_6_5
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, backing);
+#else
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, backing);
+#endif
+#else
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, backing);
+#endif
+ doneCurrent();
+}
+
+void Painter::resize(const QSize& size) {
+ m_size = size;
+ QWidget::resize(size);
+ if (m_drawTimer) {
+ forceDraw();
+ }
+}
+
+void Painter::lockAspectRatio(bool lock) {
+ m_lockAspectRatio = lock;
+ if (m_drawTimer) {
+ forceDraw();
+ }
+}
+
+void Painter::filter(bool filter) {
+ m_filter = filter;
+ makeCurrent();
+ if (m_filter) {
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ } else {
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ }
+ doneCurrent();
+ if (m_drawTimer) {
+ forceDraw();
+ }
+}
+
+void Painter::start() {
+ makeCurrent();
+ glEnable(GL_TEXTURE_2D);
+ glGenTextures(1, &m_tex);
+ glBindTexture(GL_TEXTURE_2D, m_tex);
+ glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ if (m_filter) {
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ } else {
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ }
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glVertexPointer(2, GL_INT, 0, _glVertices);
+ glTexCoordPointer(2, GL_INT, 0, _glTexCoords);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0, 240, 160, 0, 0, 1);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ doneCurrent();
+
+ m_drawTimer = new QTimer;
+ m_drawTimer->moveToThread(QThread::currentThread());
+ m_drawTimer->setInterval(0);
+ connect(m_drawTimer, SIGNAL(timeout()), this, SLOT(draw()));
+ m_drawTimer->start();
+}
+
+void Painter::draw() {
+ makeCurrent();
+ GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip);
+ performDraw();
+ GBASyncWaitFrameEnd(&m_context->sync);
+ swapBuffers();
+ doneCurrent();
+}
+
+void Painter::forceDraw() {
+ makeCurrent();
+ glViewport(0, 0, m_size.width() * devicePixelRatio(), m_size.height() * devicePixelRatio());
+ glClear(GL_COLOR_BUFFER_BIT);
+ performDraw();
+ swapBuffers();
+ doneCurrent();
+}
+
+void Painter::stop() {
+ m_drawTimer->stop();
+ delete m_drawTimer;
+ m_drawTimer = nullptr;
+ makeCurrent();
+ glDeleteTextures(1, &m_tex);
+ glClear(GL_COLOR_BUFFER_BIT);
+ swapBuffers();
+ doneCurrent();
+}
+
+void Painter::pause() {
+ m_drawTimer->stop();
+ // Make sure both buffers are filled
+ forceDraw();
+ forceDraw();
+}
+
+void Painter::unpause() {
+ m_drawTimer->start();
+}
+
+void Painter::initializeGL() {
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+}
+
+void Painter::performDraw() {
+ int w = m_size.width() * devicePixelRatio();
+ int h = m_size.height() * devicePixelRatio();
+#ifndef Q_OS_MAC
+ // TODO: This seems to cause framerates to drag down to 120 FPS on OS X,
+ // even if the emulator can go faster. Look into why.
+ glViewport(0, 0, m_size.width() * devicePixelRatio(), m_size.height() * devicePixelRatio());
+ glClear(GL_COLOR_BUFFER_BIT);
+#endif
+ int drawW = w;
+ int drawH = h;
+ if (m_lockAspectRatio) {
+ if (w * 2 > h * 3) {
+ drawW = h * 3 / 2;
+ } else if (w * 2 < h * 3) {
+ drawH = w * 2 / 3;
+ }
+ }
+ glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+ if (m_context->sync.videoFrameWait) {
+ glFlush();
+ }
+}
diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h
new file mode 100644
index 000000000..4ccd41c1c
--- /dev/null
+++ b/src/platform/qt/DisplayGL.h
@@ -0,0 +1,85 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef QGBA_DISPLAY_GL
+#define QGBA_DISPLAY_GL
+
+#include "Display.h"
+
+#include
+#include
+#include
+
+struct GBAThread;
+
+namespace QGBA {
+
+class Painter;
+class DisplayGL : public Display {
+Q_OBJECT
+
+public:
+ DisplayGL(const QGLFormat& format, QWidget* parent = nullptr);
+
+public slots:
+ void startDrawing(GBAThread* context) override;
+ void stopDrawing() override;
+ void pauseDrawing() override;
+ void unpauseDrawing() override;
+ void forceDraw() override;
+ void lockAspectRatio(bool lock) override;
+ void filter(bool filter) override;
+ void framePosted(const uint32_t*) override;
+
+protected:
+ virtual void paintEvent(QPaintEvent*) override {};
+ virtual void resizeEvent(QResizeEvent*) override;
+
+private:
+ Painter* m_painter;
+ bool m_started;
+ GBAThread* m_context;
+ bool m_lockAspectRatio;
+ bool m_filter;
+};
+
+class Painter : public QGLWidget {
+Q_OBJECT
+
+public:
+ Painter(const QGLFormat& format, QWidget* parent);
+
+ void setContext(GBAThread*);
+ void setBacking(const uint32_t*);
+
+public slots:
+ void forceDraw();
+ void draw();
+ void start();
+ void stop();
+ void pause();
+ void unpause();
+ void resize(const QSize& size);
+ void lockAspectRatio(bool lock);
+ void filter(bool filter);
+
+protected:
+ virtual void initializeGL() override;
+ virtual void paintEvent(QPaintEvent*) override {}
+
+private:
+ void performDraw();
+
+ QTimer* m_drawTimer;
+ GBAThread* m_context;
+ GLuint m_tex;
+ QSize m_size;
+ bool m_lockAspectRatio;
+ bool m_filter;
+};
+
+}
+
+#endif
diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp
new file mode 100644
index 000000000..d81516653
--- /dev/null
+++ b/src/platform/qt/DisplayQt.cpp
@@ -0,0 +1,71 @@
+/* Copyright (c) 2013-2015 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 "DisplayQt.h"
+
+#include
+
+using namespace QGBA;
+
+DisplayQt::DisplayQt(QWidget* parent)
+ : Display(parent)
+ , m_backing(nullptr)
+ , m_lockAspectRatio(false)
+ , m_filter(false)
+{
+}
+
+void DisplayQt::startDrawing(GBAThread* context) {
+ m_context = context;
+}
+
+void DisplayQt::lockAspectRatio(bool lock) {
+ m_lockAspectRatio = lock;
+ update();
+}
+
+void DisplayQt::filter(bool filter) {
+ m_filter = filter;
+ update();
+}
+
+void DisplayQt::framePosted(const uint32_t* buffer) {
+ update();
+ if (const_cast(m_backing).bits() == reinterpret_cast(buffer)) {
+ return;
+ }
+#ifdef COLOR_16_BIT
+#ifdef COLOR_5_6_5
+ m_backing = QImage(reinterpret_cast(buffer), 256, 256, QImage::Format_RGB16);
+#else
+ m_backing = QImage(reinterpret_cast(buffer), 256, 256, QImage::Format_RGB555);
+#endif
+#else
+ m_backing = QImage(reinterpret_cast(buffer), 256, 256, QImage::Format_RGB32);
+#endif
+}
+
+void DisplayQt::paintEvent(QPaintEvent*) {
+ QPainter painter(this);
+ painter.fillRect(QRect(QPoint(), size()), Qt::black);
+ if (m_filter) {
+ painter.setRenderHint(QPainter::SmoothPixmapTransform);
+ }
+ QSize s = size();
+ QSize ds = s;
+ if (s.width() * 2 > s.height() * 3) {
+ ds.setWidth(s.height() * 3 / 2);
+ } else if (s.width() * 2 < s.height() * 3) {
+ ds.setHeight(s.width() * 2 / 3);
+ }
+ QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2);
+ QRect full(origin, ds);
+
+#ifdef COLOR_5_6_5
+ painter.drawImage(full, m_backing, QRect(0, 0, 240, 160));
+#else
+ painter.drawImage(full, m_backing.rgbSwapped(), QRect(0, 0, 240, 160));
+#endif
+}
diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h
new file mode 100644
index 000000000..6990a7719
--- /dev/null
+++ b/src/platform/qt/DisplayQt.h
@@ -0,0 +1,46 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef QGBA_DISPLAY_QT
+#define QGBA_DISPLAY_QT
+
+#include "Display.h"
+
+#include
+#include
+
+struct GBAThread;
+
+namespace QGBA {
+
+class DisplayQt : public Display {
+Q_OBJECT
+
+public:
+ DisplayQt(QWidget* parent = nullptr);
+
+public slots:
+ void startDrawing(GBAThread* context) override;
+ void stopDrawing() override {}
+ void pauseDrawing() override {}
+ void unpauseDrawing() override {}
+ void forceDraw() override { update(); }
+ void lockAspectRatio(bool lock) override;
+ void filter(bool filter) override;
+ void framePosted(const uint32_t*) override;
+
+protected:
+ virtual void paintEvent(QPaintEvent*) override;
+
+private:
+ GBAThread* m_context;
+ QImage m_backing;
+ bool m_lockAspectRatio;
+ bool m_filter;
+};
+
+}
+
+#endif
diff --git a/src/platform/qt/GBAKeyEditor.cpp b/src/platform/qt/GBAKeyEditor.cpp
index 97158af94..47037991e 100644
--- a/src/platform/qt/GBAKeyEditor.cpp
+++ b/src/platform/qt/GBAKeyEditor.cpp
@@ -47,9 +47,10 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString&
refresh();
#ifdef BUILD_SDL
- lookupAxes(map);
-
if (type == SDL_BINDING_BUTTON) {
+ controller->recalibrateAxes();
+ lookupAxes(map);
+
m_profileSelect = new QComboBox(this);
m_profileSelect->addItems(controller->connectedGamepads(type));
int activeGamepad = controller->gamepad(type);
diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp
index 38bcc0ff8..d2ce032ff 100644
--- a/src/platform/qt/GameController.cpp
+++ b/src/platform/qt/GameController.cpp
@@ -18,6 +18,7 @@ extern "C" {
#include "gba/audio.h"
#include "gba/gba.h"
#include "gba/serialize.h"
+#include "gba/sharkport.h"
#include "gba/renderers/video-software.h"
#include "gba/supervisor/config.h"
#include "util/vfs.h"
@@ -97,6 +98,9 @@ GameController::GameController(QObject* parent)
context->gba->logLevel = GBA_LOG_FATAL;
context->gba->luminanceSource = &controller->m_lux;
context->gba->rtcSource = &controller->m_rtc;
+#ifdef BUILD_SDL
+ context->gba->rumble = controller->m_inputController->rumble();
+#endif
controller->gameStarted(context);
};
@@ -114,7 +118,11 @@ GameController::GameController(QObject* parent)
controller->gamePaused(&controller->m_threadContext);
}
controller->m_pauseMutex.unlock();
- controller->frameAvailable(controller->m_drawContext);
+ if (GBASyncDrawingFrame(&controller->m_threadContext.sync)) {
+ controller->frameAvailable(controller->m_drawContext);
+ } else {
+ controller->frameAvailable(nullptr);
+ }
};
m_threadContext.logHandler = [] (GBAThread* context, enum GBALogLevel level, const char* format, va_list args) {
@@ -186,6 +194,8 @@ void GameController::setOptions(const GBAOptions* opts) {
setSkipBIOS(opts->skipBios);
setUseBIOS(opts->useBios);
setRewind(opts->rewindEnable, opts->rewindBufferCapacity, opts->rewindBufferInterval);
+ setVolume(opts->volume);
+ setMute(opts->mute);
threadInterrupt();
m_threadContext.idleOptimization = opts->idleOptimization;
@@ -267,6 +277,10 @@ void GameController::openGame() {
m_threadContext.patch = VFileOpen(m_patch.toLocal8Bit().constData(), O_RDONLY);
}
+#ifdef BUILD_SDL
+ m_inputController->recalibrateAxes();
+#endif
+
if (!GBAThreadStart(&m_threadContext)) {
m_gameOpen = false;
emit gameFailed();
@@ -294,6 +308,34 @@ void GameController::loadPatch(const QString& path) {
}
}
+void GameController::importSharkport(const QString& path) {
+ if (!m_gameOpen) {
+ return;
+ }
+ VFile* vf = VFileOpen(path.toLocal8Bit().constData(), O_RDONLY);
+ if (!vf) {
+ return;
+ }
+ threadInterrupt();
+ GBASavedataImportSharkPort(m_threadContext.gba, vf, false);
+ threadContinue();
+ vf->close(vf);
+}
+
+void GameController::exportSharkport(const QString& path) {
+ if (!m_gameOpen) {
+ return;
+ }
+ VFile* vf = VFileOpen(path.toLocal8Bit().constData(), O_WRONLY | O_CREAT | O_TRUNC);
+ if (!vf) {
+ return;
+ }
+ threadInterrupt();
+ GBASavedataExportSharkPort(m_threadContext.gba, vf);
+ threadContinue();
+ vf->close(vf);
+}
+
void GameController::closeGame() {
if (!m_gameOpen) {
return;
@@ -334,7 +376,7 @@ bool GameController::isPaused() {
}
void GameController::setPaused(bool paused) {
- if (paused == GBAThreadIsPaused(&m_threadContext)) {
+ if (!m_gameOpen || paused == GBAThreadIsPaused(&m_threadContext)) {
return;
}
if (paused) {
@@ -393,6 +435,8 @@ void GameController::rewind(int states) {
GBARewind(&m_threadContext, states);
}
threadContinue();
+ emit rewound(&m_threadContext);
+ emit frameAvailable(m_drawContext);
}
void GameController::keyPressed(int key) {
@@ -496,6 +540,24 @@ void GameController::setFrameskip(int skip) {
m_threadContext.frameskip = skip;
}
+void GameController::setVolume(int volume) {
+ threadInterrupt();
+ m_threadContext.volume = volume;
+ if (m_gameOpen) {
+ m_threadContext.gba->audio.masterVolume = volume;
+ }
+ threadContinue();
+}
+
+void GameController::setMute(bool mute) {
+ threadInterrupt();
+ m_threadContext.mute = mute;
+ if (m_gameOpen) {
+ m_threadContext.gba->audio.masterVolume = mute ? 0 : m_threadContext.volume;
+ }
+ threadContinue();
+}
+
void GameController::setTurbo(bool set, bool forced) {
if (m_turboForced && !forced) {
return;
@@ -511,15 +573,29 @@ 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();
}
+#ifdef USE_PNG
+void GameController::screenshot() {
+ GBAThreadInterrupt(&m_threadContext);
+ GBAThreadTakeScreenshot(&m_threadContext);
+ GBAThreadContinue(&m_threadContext);
+}
+#endif
+
void GameController::reloadAudioDriver() {
QMetaObject::invokeMethod(m_audioProcessor, "pause", Qt::BlockingQueuedConnection);
int samples = m_audioProcessor->getBufferSamples();
diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h
index a7036f7f8..ac39b7c5d 100644
--- a/src/platform/qt/GameController.h
+++ b/src/platform/qt/GameController.h
@@ -85,6 +85,7 @@ signals:
void gameCrashed(const QString& errorMessage);
void gameFailed();
void stateLoaded(GBAThread*);
+ void rewound(GBAThread*);
void unimplementedBiosCall(int);
void luminanceValueChanged(int);
@@ -97,6 +98,8 @@ public slots:
void setSkipBIOS(bool);
void setUseBIOS(bool);
void loadPatch(const QString& path);
+ void importSharkport(const QString& path);
+ void exportSharkport(const QString& path);
void openGame();
void closeGame();
void setPaused(bool paused);
@@ -114,11 +117,17 @@ public slots:
void setVideoSync(bool);
void setAudioSync(bool);
void setFrameskip(int);
+ void setVolume(int);
+ void setMute(bool);
void setTurbo(bool, bool forced = true);
void setAVStream(GBAAVStream*);
void clearAVStream();
void reloadAudioDriver();
+#ifdef USE_PNG
+ void screenshot();
+#endif
+
void setLuminanceValue(uint8_t value);
uint8_t luminanceValue() const { return m_luxValue; }
void setLuminanceLevel(int level);
diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp
index 73a9a9e1a..3c59d7df2 100644
--- a/src/platform/qt/InputController.cpp
+++ b/src/platform/qt/InputController.cpp
@@ -94,6 +94,9 @@ void InputController::loadConfiguration(uint32_t type) {
void InputController::loadProfile(uint32_t type, const QString& profile) {
GBAInputProfileLoad(&m_inputMap, type, m_config->input(), profile.toLocal8Bit().constData());
+#ifdef BUILD_SDL
+ recalibrateAxes();
+#endif
}
void InputController::saveConfiguration(uint32_t type) {
@@ -150,6 +153,10 @@ void InputController::setPreferredGamepad(uint32_t type, const QString& device)
}
GBAInputSetPreferredDevice(m_config->input(), type, m_sdlPlayer.playerId, device.toLocal8Bit().constData());
}
+
+GBARumble* InputController::rumble() {
+ return &m_sdlPlayer.rumble.d;
+}
#endif
GBAKey InputController::mapKeyboard(int key) const {
@@ -222,14 +229,33 @@ QSet InputController::activeGamepadButtons() {
return activeButtons;
}
+void InputController::recalibrateAxes() {
+ SDL_Joystick* joystick = m_sdlPlayer.joystick;
+ SDL_JoystickUpdate();
+ int numAxes = SDL_JoystickNumAxes(joystick);
+ if (numAxes < 1) {
+ return;
+ }
+ m_deadzones.resize(numAxes);
+ int i;
+ for (i = 0; i < numAxes; ++i) {
+ m_deadzones[i] = SDL_JoystickGetAxis(joystick, i);
+ }
+}
+
QSet> InputController::activeGamepadAxes() {
SDL_Joystick* joystick = m_sdlPlayer.joystick;
SDL_JoystickUpdate();
- int numButtons = SDL_JoystickNumAxes(joystick);
+ int numAxes = SDL_JoystickNumAxes(joystick);
QSet> activeAxes;
+ if (numAxes < 1) {
+ return activeAxes;
+ }
+ m_deadzones.resize(numAxes);
int i;
- for (i = 0; i < numButtons; ++i) {
+ for (i = 0; i < numAxes; ++i) {
int32_t axis = SDL_JoystickGetAxis(joystick, i);
+ axis -= m_deadzones[i];
if (axis >= AXIS_THRESHOLD || axis <= -AXIS_THRESHOLD) {
activeAxes.insert(qMakePair(i, axis > 0 ? GamepadAxisEvent::POSITIVE : GamepadAxisEvent::NEGATIVE));
}
@@ -246,11 +272,11 @@ void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direct
switch (direction) {
case GamepadAxisEvent::NEGATIVE:
description.lowDirection = key;
- description.deadLow = -AXIS_THRESHOLD;
+ description.deadLow = m_deadzones[axis] - AXIS_THRESHOLD;
break;
case GamepadAxisEvent::POSITIVE:
description.highDirection = key;
- description.deadHigh = AXIS_THRESHOLD;
+ description.deadHigh = m_deadzones[axis] + AXIS_THRESHOLD;
break;
default:
return;
@@ -278,18 +304,25 @@ void InputController::testGamepad() {
for (auto& axis : m_activeAxes) {
bool newlyAboveThreshold = activeAxes.contains(axis);
- GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, this);
if (newlyAboveThreshold) {
+ GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, this);
postPendingEvent(event->gbaKey());
+ QApplication::sendEvent(QApplication::focusWidget(), event);
if (!event->isAccepted()) {
clearPendingEvent(event->gbaKey());
}
- } else if (oldAxes.contains(axis)) {
- clearPendingEvent(event->gbaKey());
}
+ }
+ for (auto axis : oldAxes) {
+ GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, false, this);
+ clearPendingEvent(event->gbaKey());
QApplication::sendEvent(QApplication::focusWidget(), event);
}
+ if (!QApplication::focusWidget()) {
+ return;
+ }
+
activeButtons.subtract(oldButtons);
oldButtons.subtract(m_activeButtons);
diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h
index 2dd7fe826..c9a07f9fa 100644
--- a/src/platform/qt/InputController.h
+++ b/src/platform/qt/InputController.h
@@ -10,6 +10,7 @@
#include
#include
+#include
class QTimer;
@@ -56,6 +57,7 @@ public:
int testSDLEvents();
QSet activeGamepadButtons();
QSet> activeGamepadAxes();
+ void recalibrateAxes();
void bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction, GBAKey);
@@ -63,6 +65,7 @@ public:
int gamepad(uint32_t type) const { return m_sdlPlayer.joystickIndex; }
void setGamepad(uint32_t type, int index) { GBASDLPlayerChangeJoystick(&s_sdlEvents, &m_sdlPlayer, index); }
void setPreferredGamepad(uint32_t type, const QString& device);
+ GBARumble* rumble();
#endif
public slots:
@@ -83,6 +86,7 @@ private:
static GBASDLEvents s_sdlEvents;
GBASDLPlayer m_sdlPlayer;
bool m_playerAttached;
+ QVector m_deadzones;
#endif
QSet m_activeButtons;
diff --git a/src/platform/qt/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp
index 1f1acd814..756bd6c68 100644
--- a/src/platform/qt/LoadSaveState.cpp
+++ b/src/platform/qt/LoadSaveState.cpp
@@ -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(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);
diff --git a/src/platform/qt/PaletteView.cpp b/src/platform/qt/PaletteView.cpp
new file mode 100644
index 000000000..a41732672
--- /dev/null
+++ b/src/platform/qt/PaletteView.cpp
@@ -0,0 +1,59 @@
+/* Copyright (c) 2013-2015 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 "PaletteView.h"
+
+#include
+
+using namespace QGBA;
+
+PaletteView::PaletteView(GameController* controller, QWidget* parent)
+ : QWidget(parent)
+ , m_controller(controller)
+{
+ m_ui.setupUi(this);
+
+ connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(updatePalette()));
+ m_ui.bgGrid->setDimensions(QSize(16, 16));
+ m_ui.objGrid->setDimensions(QSize(16, 16));
+ m_ui.selected->setSize(64);
+ m_ui.selected->setDimensions(QSize(1, 1));
+ updatePalette();
+
+ const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
+
+ m_ui.hexcode->setFont(font);
+ m_ui.value->setFont(font);
+ m_ui.index->setFont(font);
+ m_ui.r->setFont(font);
+ m_ui.g->setFont(font);
+ m_ui.b->setFont(font);
+
+ connect(m_ui.bgGrid, SIGNAL(indexPressed(int)), this, SLOT(selectIndex(int)));
+ connect(m_ui.objGrid, &Swatch::indexPressed, [this] (int index) { selectIndex(index + 256); });
+}
+
+void PaletteView::updatePalette() {
+ const uint16_t* palette = m_controller->thread()->gba->video.palette;
+ for (int i = 0; i < 256; ++i) {
+ m_ui.bgGrid->setColor(i, palette[i]);
+ m_ui.objGrid->setColor(i, palette[i + 256]);
+ }
+}
+
+void PaletteView::selectIndex(int index) {
+ uint16_t color = m_controller->thread()->gba->video.palette[index];
+ m_ui.selected->setColor(0, color);
+ uint32_t r = color & 0x1F;
+ uint32_t g = (color >> 5) & 0x1F;
+ uint32_t b = (color >> 10) & 0x1F;
+ uint32_t hexcode = (r << 19) | (g << 11) | (b << 3);
+ m_ui.hexcode->setText(tr("#%0").arg(hexcode, 6, 16, QChar('0')));
+ m_ui.value->setText(tr("0x%0").arg(color, 4, 16, QChar('0')));
+ m_ui.index->setText(tr("%0").arg(index, 3, 10, QChar('0')));
+ m_ui.r->setText(tr("0x%0 (%1)").arg(r, 2, 16, QChar('0')).arg(r, 2, 10, QChar('0')));
+ m_ui.g->setText(tr("0x%0 (%1)").arg(g, 2, 16, QChar('0')).arg(g, 2, 10, QChar('0')));
+ m_ui.b->setText(tr("0x%0 (%1)").arg(b, 2, 16, QChar('0')).arg(b, 2, 10, QChar('0')));
+}
diff --git a/src/platform/qt/PaletteView.h b/src/platform/qt/PaletteView.h
new file mode 100644
index 000000000..5a4d58252
--- /dev/null
+++ b/src/platform/qt/PaletteView.h
@@ -0,0 +1,42 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef QGBA_PALETTE_VIEW
+#define QGBA_PALETTE_VIEW
+
+#include
+
+#include "GameController.h"
+#include "Swatch.h"
+
+#include "ui_PaletteView.h"
+
+class GameController;
+
+namespace QGBA {
+
+class Swatch;
+
+class PaletteView : public QWidget {
+Q_OBJECT
+
+public:
+ PaletteView(GameController* controller, QWidget* parent = nullptr);
+
+public slots:
+ void updatePalette();
+
+private slots:
+ void selectIndex(int);
+
+private:
+ Ui::PaletteView m_ui;
+
+ GameController* m_controller;
+};
+
+}
+
+#endif
diff --git a/src/platform/qt/PaletteView.ui b/src/platform/qt/PaletteView.ui
new file mode 100644
index 000000000..79a5c2bec
--- /dev/null
+++ b/src/platform/qt/PaletteView.ui
@@ -0,0 +1,321 @@
+
+
+ PaletteView
+
+
+
+ 0
+ 0
+ 452
+ 349
+
+
+
+ Palette
+
+
+
-
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Background
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 175
+ 175
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Objects
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 175
+ 175
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Selection
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 64
+ 64
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
-
+
+
+ Red
+
+
+
+ -
+
+
+ Green
+
+
+
+ -
+
+
+ Blue
+
+
+
+
+
+ -
+
+
+ 8
+
+
-
+
+
+ 0x00 (00)
+
+
+
+ -
+
+
+ 0x00 (00)
+
+
+
+ -
+
+
+ 0x00 (00)
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
-
+
+
+ 16-bit value
+
+
+
+ -
+
+
+ Hex code
+
+
+
+ -
+
+
+ Palette index
+
+
+
+
+
+ -
+
+
+ 8
+
+
-
+
+
+ 0x0000
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ #000000
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ 000
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+
+
+
+
+
+
+
+ QGBA::Swatch
+ QLabel
+
+
+
+
+
+
diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp
index b50ff95a4..44aa13a20 100644
--- a/src/platform/qt/SettingsView.cpp
+++ b/src/platform/qt/SettingsView.cpp
@@ -27,6 +27,8 @@ SettingsView::SettingsView(ConfigController* controller, QWidget* parent)
loadSetting("frameskip", m_ui.frameskip);
loadSetting("fpsTarget", m_ui.fpsTarget);
loadSetting("lockAspectRatio", m_ui.lockAspectRatio);
+ loadSetting("volume", m_ui.volume);
+ loadSetting("mute", m_ui.mute);
loadSetting("rewindEnable", m_ui.rewind);
loadSetting("rewindBufferInterval", m_ui.rewindInterval);
loadSetting("rewindBufferCapacity", m_ui.rewindCapacity);
@@ -78,6 +80,8 @@ void SettingsView::updateConfig() {
saveSetting("frameskip", m_ui.frameskip);
saveSetting("fpsTarget", m_ui.fpsTarget);
saveSetting("lockAspectRatio", m_ui.lockAspectRatio);
+ saveSetting("volume", m_ui.volume);
+ saveSetting("mute", m_ui.mute);
saveSetting("rewindEnable", m_ui.rewind);
saveSetting("rewindBufferInterval", m_ui.rewindInterval);
saveSetting("rewindBufferCapacity", m_ui.rewindCapacity);
@@ -121,6 +125,10 @@ void SettingsView::saveSetting(const char* key, const QLineEdit* field) {
saveSetting(key, field->text());
}
+void SettingsView::saveSetting(const char* key, const QSlider* field) {
+ saveSetting(key, QString::number(field->value()));
+}
+
void SettingsView::saveSetting(const char* key, const QSpinBox* field) {
saveSetting(key, field->cleanText());
}
@@ -144,6 +152,11 @@ void SettingsView::loadSetting(const char* key, QLineEdit* field) {
field->setText(option);
}
+void SettingsView::loadSetting(const char* key, QSlider* field) {
+ QString option = loadSetting(key);
+ field->setValue(option.toInt());
+}
+
void SettingsView::loadSetting(const char* key, QSpinBox* field) {
QString option = loadSetting(key);
field->setValue(option.toInt());
diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h
index 73694facd..0b0d4f211 100644
--- a/src/platform/qt/SettingsView.h
+++ b/src/platform/qt/SettingsView.h
@@ -36,12 +36,14 @@ private:
void saveSetting(const char* key, const QAbstractButton*);
void saveSetting(const char* key, const QComboBox*);
void saveSetting(const char* key, const QLineEdit*);
+ void saveSetting(const char* key, const QSlider*);
void saveSetting(const char* key, const QSpinBox*);
void saveSetting(const char* key, const QString&);
void loadSetting(const char* key, QAbstractButton*);
void loadSetting(const char* key, QComboBox*);
void loadSetting(const char* key, QLineEdit*);
+ void loadSetting(const char* key, QSlider*);
void loadSetting(const char* key, QSpinBox*);
QString loadSetting(const char* key);
};
diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui
index c9fc145f8..b7e371dea 100644
--- a/src/platform/qt/SettingsView.ui
+++ b/src/platform/qt/SettingsView.ui
@@ -6,8 +6,8 @@
0
0
- 360
- 569
+ 374
+ 608
@@ -146,13 +146,54 @@
-
+
+
+ Volume:
+
+
+
+ -
+
+
-
+
+
+ 256
+
+
+ 16
+
+
+ 256
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Mute
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
Sync:
- -
+
-
-
@@ -170,14 +211,14 @@
- -
+
-
Frameskip:
- -
+
-
-
@@ -198,14 +239,14 @@
- -
+
-
FPS target:
- -
+
-
-
@@ -226,49 +267,42 @@
- -
-
-
- Qt::Horizontal
-
-
-
- -
+
-
Lock aspect ratio
- -
+
-
Resample video
- -
+
-
Qt::Horizontal
- -
+
-
Enable rewind
- -
+
-
Rewind interval:
- -
+
-
-
@@ -289,14 +323,14 @@
- -
+
-
Rewind length:
- -
+
-
-
@@ -310,21 +344,28 @@
- -
+
-
+
+
+ Allow opposing input directions
+
+
+
+ -
Qt::Horizontal
- -
+
-
Idle loops
- -
+
-
-
@@ -343,13 +384,6 @@
- -
-
-
- Allow opposing input directions
-
-
-
-
diff --git a/src/platform/qt/ShortcutController.cpp b/src/platform/qt/ShortcutController.cpp
index d46838a24..7d0fd610a 100644
--- a/src/platform/qt/ShortcutController.cpp
+++ b/src/platform/qt/ShortcutController.cpp
@@ -40,6 +40,16 @@ QVariant ShortcutController::data(const QModelIndex& index, int role) const {
if (item->button() >= 0) {
return item->button();
}
+ if (item->axis() >= 0) {
+ char d = '\0';
+ if (item->direction() == GamepadAxisEvent::POSITIVE) {
+ d = '+';
+ }
+ if (item->direction() == GamepadAxisEvent::NEGATIVE) {
+ d = '-';
+ }
+ return QString("%1%2").arg(d).arg(item->axis());
+ }
break;
}
return QVariant();
@@ -219,6 +229,7 @@ void ShortcutController::updateButton(const QModelIndex& index, int button) {
m_buttons.take(oldButton);
}
if (button >= 0) {
+ updateAxis(index, -1, GamepadAxisEvent::NEUTRAL);
m_buttons[button] = item;
}
if (m_config) {
@@ -227,6 +238,38 @@ void ShortcutController::updateButton(const QModelIndex& index, int button) {
emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), createIndex(index.row(), 2, index.internalPointer()));
}
+void ShortcutController::updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction) {
+ if (!index.isValid()) {
+ return;
+ }
+ const QModelIndex& parent = index.parent();
+ if (!parent.isValid()) {
+ return;
+ }
+ ShortcutItem* item = itemAt(index);
+ int oldAxis = item->axis();
+ GamepadAxisEvent::Direction oldDirection = item->direction();
+ item->setAxis(axis, direction);
+ if (oldAxis >= 0) {
+ m_axes.take(qMakePair(oldAxis, oldDirection));
+ }
+ if (axis >= 0 && direction != GamepadAxisEvent::NEUTRAL) {
+ updateButton(index, -1);
+ m_axes[qMakePair(axis, direction)] = item;
+ }
+ if (m_config) {
+ char d = '\0';
+ if (direction == GamepadAxisEvent::POSITIVE) {
+ d = '+';
+ }
+ if (direction == GamepadAxisEvent::NEGATIVE) {
+ d = '-';
+ }
+ m_config->setQtOption(item->name(), QString("%1%2").arg(d).arg(axis), AXIS_SECTION);
+ }
+ emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), createIndex(index.row(), 2, index.internalPointer()));
+}
+
void ShortcutController::clearKey(const QModelIndex& index) {
updateKey(index, QKeySequence());
}
@@ -286,6 +329,31 @@ bool ShortcutController::eventFilter(QObject*, QEvent* event) {
event->accept();
return true;
}
+ if (event->type() == GamepadAxisEvent::Type()) {
+ GamepadAxisEvent* gae = static_cast(event);
+ auto item = m_axes.find(qMakePair(gae->axis(), gae->direction()));
+ if (item == m_axes.end()) {
+ return false;
+ }
+ if (gae->isNew()) {
+ QAction* action = item.value()->action();
+ if (action && action->isEnabled()) {
+ action->trigger();
+ }
+ }
+ ShortcutItem::Functions pair = item.value()->functions();
+ if (gae->isNew()) {
+ if (pair.first) {
+ pair.first();
+ }
+ } else {
+ if (pair.second) {
+ pair.second();
+ }
+ }
+ event->accept();
+ return true;
+ }
return false;
}
@@ -311,6 +379,30 @@ void ShortcutController::loadShortcuts(ShortcutItem* item) {
}
m_buttons[button.toInt()] = item;
}
+ QVariant axis = m_config->getQtOption(item->name(), AXIS_SECTION);
+ if (!axis.isNull()) {
+ int oldAxis = item->axis();
+ GamepadAxisEvent::Direction oldDirection = item->direction();
+ QString axisDesc = axis.toString();
+ if (axisDesc.size() >= 2) {
+ GamepadAxisEvent::Direction direction = GamepadAxisEvent::NEUTRAL;
+ if (axisDesc[0] == '-') {
+ direction = GamepadAxisEvent::NEGATIVE;
+ }
+ if (axisDesc[0] == '+') {
+ direction = GamepadAxisEvent::POSITIVE;
+ }
+ bool ok;
+ int axis = axisDesc.mid(1).toInt(&ok);
+ if (ok) {
+ item->setAxis(axis, direction);
+ if (oldAxis >= 0) {
+ m_axes.take(qMakePair(oldAxis, oldDirection));
+ }
+ m_axes[qMakePair(axis, direction)] = item;
+ }
+ }
+ }
}
QKeySequence ShortcutController::keyEventToSequence(const QKeyEvent* event) {
@@ -339,6 +431,8 @@ ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& n
, m_menu(nullptr)
, m_name(name)
, m_button(-1)
+ , m_axis(-1)
+ , m_direction(GamepadAxisEvent::NEUTRAL)
, m_parent(parent)
{
m_visibleName = action->text()
@@ -354,6 +448,8 @@ ShortcutController::ShortcutItem::ShortcutItem(ShortcutController::ShortcutItem:
, m_name(name)
, m_visibleName(visibleName)
, m_button(-1)
+ , m_axis(-1)
+ , m_direction(GamepadAxisEvent::NEUTRAL)
, m_parent(parent)
{
}
@@ -362,6 +458,8 @@ ShortcutController::ShortcutItem::ShortcutItem(QMenu* menu, ShortcutItem* parent
: m_action(nullptr)
, m_menu(menu)
, m_button(-1)
+ , m_axis(-1)
+ , m_direction(GamepadAxisEvent::NEUTRAL)
, m_parent(parent)
{
if (menu) {
@@ -389,3 +487,8 @@ void ShortcutController::ShortcutItem::setShortcut(const QKeySequence& shortcut)
m_action->setShortcut(shortcut);
}
}
+
+void ShortcutController::ShortcutItem::setAxis(int axis, GamepadAxisEvent::Direction direction) {
+ m_axis = axis;
+ m_direction = direction;
+}
diff --git a/src/platform/qt/ShortcutController.h b/src/platform/qt/ShortcutController.h
index 636c4a7ac..48693aeb9 100644
--- a/src/platform/qt/ShortcutController.h
+++ b/src/platform/qt/ShortcutController.h
@@ -6,6 +6,8 @@
#ifndef QGBA_SHORTCUT_MODEL
#define QGBA_SHORTCUT_MODEL
+#include "GamepadAxisEvent.h"
+
#include
#include
@@ -26,6 +28,7 @@ Q_OBJECT
private:
constexpr static const char* const KEY_SECTION = "shortcutKey";
constexpr static const char* const BUTTON_SECTION = "shortcutButton";
+ constexpr static const char* const AXIS_SECTION = "shortcutAxis";
class ShortcutItem {
public:
@@ -53,6 +56,9 @@ private:
int button() const { return m_button; }
void setShortcut(const QKeySequence& sequence);
void setButton(int button) { m_button = button; }
+ int axis() const { return m_axis; }
+ GamepadAxisEvent::Direction direction() const { return m_direction; }
+ void setAxis(int axis, GamepadAxisEvent::Direction direction);
bool operator==(const ShortcutItem& other) const { return m_menu == other.m_menu && m_action == other.m_action; }
@@ -64,6 +70,8 @@ private:
QString m_name;
QString m_visibleName;
int m_button;
+ int m_axis;
+ GamepadAxisEvent::Direction m_direction;
QList m_items;
ShortcutItem* m_parent;
};
@@ -91,6 +99,7 @@ public:
void updateKey(const QModelIndex& index, const QKeySequence& keySequence);
void updateButton(const QModelIndex& index, int button);
+ void updateAxis(const QModelIndex& index, int axis, GamepadAxisEvent::Direction direction);
void clearKey(const QModelIndex& index);
void clearButton(const QModelIndex& index);
@@ -108,6 +117,7 @@ private:
ShortcutItem m_rootMenu;
QMap m_menuMap;
QMap m_buttons;
+ QMap, ShortcutItem*> m_axes;
QMap m_heldKeys;
ConfigController* m_config;
};
diff --git a/src/platform/qt/ShortcutView.cpp b/src/platform/qt/ShortcutView.cpp
index d36b8d713..a73b08954 100644
--- a/src/platform/qt/ShortcutView.cpp
+++ b/src/platform/qt/ShortcutView.cpp
@@ -22,6 +22,7 @@ ShortcutView::ShortcutView(QWidget* parent)
connect(m_ui.keySequenceEdit, SIGNAL(keySequenceChanged(const QKeySequence&)), this, SLOT(updateKey(const QKeySequence&)));
connect(m_ui.keyEdit, SIGNAL(valueChanged(int)), this, SLOT(updateButton(int)));
+ connect(m_ui.keyEdit, SIGNAL(axisChanged(int, int)), this, SLOT(updateAxis(int, int)));
connect(m_ui.shortcutTable, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(load(const QModelIndex&)));
connect(m_ui.clearButton, SIGNAL(clicked()), this, SLOT(clear()));
}
@@ -31,15 +32,6 @@ void ShortcutView::setController(ShortcutController* controller) {
m_ui.shortcutTable->setModel(controller);
}
-bool ShortcutView::event(QEvent* event) {
- if (event->type() == GamepadButtonEvent::Down()) {
- updateButton(static_cast(event)->value());
- event->accept();
- return true;
- }
- return QWidget::event(event);
-}
-
bool ShortcutView::eventFilter(QObject*, QEvent* event) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast(event);
@@ -106,5 +98,12 @@ void ShortcutView::updateButton(int button) {
return;
}
m_controller->updateButton(m_ui.shortcutTable->selectionModel()->currentIndex(), button);
-
+}
+
+
+void ShortcutView::updateAxis(int axis, int direction) {
+ if (!m_controller || m_controller->isMenuAt(m_ui.shortcutTable->selectionModel()->currentIndex())) {
+ return;
+ }
+ m_controller->updateAxis(m_ui.shortcutTable->selectionModel()->currentIndex(), axis, static_cast(direction));
}
diff --git a/src/platform/qt/ShortcutView.h b/src/platform/qt/ShortcutView.h
index f16fa6f49..af6baadaa 100644
--- a/src/platform/qt/ShortcutView.h
+++ b/src/platform/qt/ShortcutView.h
@@ -6,6 +6,8 @@
#ifndef QGBA_SHORTCUT_VIEW
#define QGBA_SHORTCUT_VIEW
+#include "GamepadAxisEvent.h"
+
#include
#include "ui_ShortcutView.h"
@@ -23,7 +25,6 @@ public:
void setController(ShortcutController* controller);
protected:
- virtual bool event(QEvent* event) override;
virtual bool eventFilter(QObject* obj, QEvent* event) override;
private slots:
@@ -31,6 +32,7 @@ private slots:
void clear();
void updateKey(const QKeySequence&);
void updateButton(int button);
+ void updateAxis(int axis, int direction);
private:
Ui::ShortcutView m_ui;
diff --git a/src/platform/qt/Swatch.cpp b/src/platform/qt/Swatch.cpp
new file mode 100644
index 000000000..308bec9cf
--- /dev/null
+++ b/src/platform/qt/Swatch.cpp
@@ -0,0 +1,62 @@
+/* Copyright (c) 2013-2015 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 "Swatch.h"
+
+#include
+#include
+
+using namespace QGBA;
+
+Swatch::Swatch(QWidget* parent)
+ : QLabel(parent)
+{
+ m_size = 10;
+ setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
+}
+
+void Swatch::setSize(int size) {
+ m_size = size;
+ setDimensions(m_dims);
+}
+
+void Swatch::setDimensions(const QSize& size) {
+ m_dims = size;
+ m_backing = QPixmap(size * (m_size + 1) - QSize(1, 1));
+ m_backing.fill(Qt::transparent);
+ int elem = size.width() * size.height();
+ m_colors.resize(elem);
+ for (int i = 0; i < elem; ++i) {
+ updateFill(i);
+ }
+}
+
+void Swatch::setColor(int index, uint16_t color) {
+ m_colors[index].setRgb(
+ (color << 3) & 0xF8,
+ (color >> 2) & 0xF8,
+ (color >> 7) & 0xF8);
+ updateFill(index);
+}
+
+void Swatch::paintEvent(QPaintEvent* event) {
+ setPixmap(m_backing);
+ QLabel::paintEvent(event);
+}
+
+void Swatch::mousePressEvent(QMouseEvent* event) {
+ int x = event->x() / (m_size + 1);
+ int y = event->y() / (m_size + 1);
+ emit indexPressed(y * m_dims.width() + x);
+}
+
+void Swatch::updateFill(int index) {
+ QPainter painter(&m_backing);
+ int x = index % m_dims.width();
+ int y = index / m_dims.width();
+ QRect r(x * (m_size + 1), y * (m_size + 1), m_size, m_size);
+ painter.fillRect(r, m_colors[index]);
+ update(r);
+}
diff --git a/src/platform/qt/Swatch.h b/src/platform/qt/Swatch.h
new file mode 100644
index 000000000..5a84c3c38
--- /dev/null
+++ b/src/platform/qt/Swatch.h
@@ -0,0 +1,45 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef QGBA_SWATCH
+#define QGBA_SWATCH
+
+#include
+#include
+#include
+
+namespace QGBA {
+
+class Swatch : public QLabel {
+Q_OBJECT
+
+public:
+ Swatch(QWidget* parent = nullptr);
+
+ void setDimensions(const QSize&);
+ void setSize(int size);
+
+public slots:
+ void setColor(int index, uint16_t);
+
+signals:
+ void indexPressed(int index);
+
+protected:
+ void paintEvent(QPaintEvent*) override;
+ void mousePressEvent(QMouseEvent*) override;
+
+private:
+ int m_size;
+ QVector m_colors;
+ QPixmap m_backing;
+ QSize m_dims;
+
+ void updateFill(int index);
+};
+
+}
+
+#endif
diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp
index eab756f56..dee0778c9 100644
--- a/src/platform/qt/Window.cpp
+++ b/src/platform/qt/Window.cpp
@@ -12,10 +12,12 @@
#include
#include
#include
+#include
#include
#include "CheatsView.h"
#include "ConfigController.h"
+#include "DisplayGL.h"
#include "GameController.h"
#include "GBAKeyEditor.h"
#include "GDBController.h"
@@ -25,6 +27,7 @@
#include "LogView.h"
#include "MultiplayerController.h"
#include "OverrideView.h"
+#include "PaletteView.h"
#include "SensorView.h"
#include "SettingsView.h"
#include "ShortcutController.h"
@@ -67,7 +70,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 DisplayGL(format, this);
m_logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio());
m_logo = m_logo; // Free memory left over in old pixmap
@@ -79,10 +82,16 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
m_screenWidget->setLockAspectRatio(m_logo.width(), m_logo.height());
setCentralWidget(m_screenWidget);
+ QVariant windowPos = m_config->getQtOption("windowPos");
+ if (!windowPos.isNull()) {
+ move(windowPos.toPoint());
+ }
+
connect(m_controller, SIGNAL(gameStarted(GBAThread*)), this, SLOT(gameStarted(GBAThread*)));
connect(m_controller, SIGNAL(gameStopped(GBAThread*)), m_display, SLOT(stopDrawing()));
connect(m_controller, SIGNAL(gameStopped(GBAThread*)), this, SLOT(gameStopped()));
connect(m_controller, SIGNAL(stateLoaded(GBAThread*)), m_display, SLOT(forceDraw()));
+ connect(m_controller, SIGNAL(rewound(GBAThread*)), m_display, SLOT(forceDraw()));
connect(m_controller, SIGNAL(gamePaused(GBAThread*)), m_display, SLOT(pauseDrawing()));
#ifndef Q_OS_MAC
connect(m_controller, SIGNAL(gamePaused(GBAThread*)), menuBar(), SLOT(show()));
@@ -95,13 +104,14 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
connect(m_controller, SIGNAL(gameUnpaused(GBAThread*)), m_display, SLOT(unpauseDrawing()));
connect(m_controller, SIGNAL(postLog(int, const QString&)), m_logView, SLOT(postLog(int, const QString&)));
connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(recordFrame()));
+ connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), m_display, SLOT(framePosted(const uint32_t*)));
connect(m_controller, SIGNAL(gameCrashed(const QString&)), this, SLOT(gameCrashed(const QString&)));
connect(m_controller, SIGNAL(gameFailed()), this, SLOT(gameFailed()));
connect(m_controller, SIGNAL(unimplementedBiosCall(int)), this, SLOT(unimplementedBiosCall(int)));
connect(m_logView, SIGNAL(levelsSet(int)), m_controller, SLOT(setLogLevel(int)));
connect(m_logView, SIGNAL(levelsEnabled(int)), m_controller, SLOT(enableLogLevel(int)));
connect(m_logView, SIGNAL(levelsDisabled(int)), m_controller, SLOT(disableLogLevel(int)));
- connect(this, SIGNAL(startDrawing(const uint32_t*, GBAThread*)), m_display, SLOT(startDrawing(const uint32_t*, GBAThread*)), Qt::QueuedConnection);
+ connect(this, SIGNAL(startDrawing(GBAThread*)), m_display, SLOT(startDrawing(GBAThread*)), Qt::QueuedConnection);
connect(this, SIGNAL(shutdown()), m_display, SLOT(stopDrawing()));
connect(this, SIGNAL(shutdown()), m_controller, SLOT(closeGame()));
connect(this, SIGNAL(shutdown()), m_logView, SLOT(hide()));
@@ -176,6 +186,10 @@ void Window::loadConfig() {
resizeFrame(opts->width, opts->height);
}
+ if (opts->fullscreen) {
+ enterFullScreen();
+ }
+
m_mruFiles = m_config->getMRU();
updateMRU();
@@ -187,7 +201,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);
@@ -195,7 +216,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);
@@ -207,65 +235,102 @@ 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);
}
}
+void Window::openView(QWidget* widget) {
+ connect(this, SIGNAL(shutdown()), widget, SLOT(close()));
+ widget->setAttribute(Qt::WA_DeleteOnClose);
+ widget->show();
+}
+
+void Window::importSharkport() {
+ bool doPause = m_controller->isLoaded() && !m_controller->isPaused();
+ if (doPause) {
+ m_controller->setPaused(true);
+ }
+ QString filename = QFileDialog::getOpenFileName(this, tr("Select save"), m_config->getQtOption("lastDirectory").toString(), tr("GameShark saves (*.sps *.xps)"));
+ if (doPause) {
+ m_controller->setPaused(false);
+ }
+ if (!filename.isEmpty()) {
+ m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path());
+ m_controller->importSharkport(filename);
+ }
+}
+
+void Window::exportSharkport() {
+ bool doPause = m_controller->isLoaded() && !m_controller->isPaused();
+ if (doPause) {
+ m_controller->setPaused(true);
+ }
+ QString filename = QFileDialog::getSaveFileName(this, tr("Select save"), m_config->getQtOption("lastDirectory").toString(), tr("GameShark saves (*.sps *.xps)"));
+ if (doPause) {
+ m_controller->setPaused(false);
+ }
+ if (!filename.isEmpty()) {
+ m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path());
+ m_controller->exportSharkport(filename);
+ }
+}
+
void Window::openKeymapWindow() {
GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, InputController::KEYBOARD);
- connect(this, SIGNAL(shutdown()), keyEditor, SLOT(close()));
- keyEditor->setAttribute(Qt::WA_DeleteOnClose);
- keyEditor->show();
+ openView(keyEditor);
}
void Window::openSettingsWindow() {
SettingsView* settingsWindow = new SettingsView(m_config);
- connect(this, SIGNAL(shutdown()), settingsWindow, SLOT(close()));
connect(settingsWindow, SIGNAL(biosLoaded(const QString&)), m_controller, SLOT(loadBIOS(const QString&)));
connect(settingsWindow, SIGNAL(audioDriverChanged()), m_controller, SLOT(reloadAudioDriver()));
- settingsWindow->setAttribute(Qt::WA_DeleteOnClose);
- settingsWindow->show();
+ openView(settingsWindow);
}
void Window::openShortcutWindow() {
+#ifdef BUILD_SDL
+ m_inputController.recalibrateAxes();
+#endif
ShortcutView* shortcutView = new ShortcutView();
shortcutView->setController(m_shortcutController);
- connect(this, SIGNAL(shutdown()), shortcutView, SLOT(close()));
- shortcutView->setAttribute(Qt::WA_DeleteOnClose);
- shortcutView->show();
+ openView(shortcutView);
}
void Window::openOverrideWindow() {
OverrideView* overrideWindow = new OverrideView(m_controller, m_config);
- connect(this, SIGNAL(shutdown()), overrideWindow, SLOT(close()));
- overrideWindow->setAttribute(Qt::WA_DeleteOnClose);
- overrideWindow->show();
+ openView(overrideWindow);
}
void Window::openSensorWindow() {
SensorView* sensorWindow = new SensorView(m_controller);
- connect(this, SIGNAL(shutdown()), sensorWindow, SLOT(close()));
- sensorWindow->setAttribute(Qt::WA_DeleteOnClose);
- sensorWindow->show();
+ openView(sensorWindow);
}
void Window::openCheatsWindow() {
CheatsView* cheatsWindow = new CheatsView(m_controller);
- connect(this, SIGNAL(shutdown()), cheatsWindow, SLOT(close()));
- cheatsWindow->setAttribute(Qt::WA_DeleteOnClose);
- cheatsWindow->show();
+ openView(cheatsWindow);
+}
+
+void Window::openPaletteWindow() {
+ PaletteView* paletteWindow = new PaletteView(m_controller);
+ openView(paletteWindow);
}
#ifdef BUILD_SDL
void Window::openGamepadWindow() {
const char* profile = m_inputController.profileForType(SDL_BINDING_BUTTON);
GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, SDL_BINDING_BUTTON, profile);
- connect(this, SIGNAL(shutdown()), keyEditor, SLOT(close()));
- keyEditor->setAttribute(Qt::WA_DeleteOnClose);
- keyEditor->show();
+ openView(keyEditor);
}
#endif
@@ -340,10 +405,12 @@ void Window::keyReleaseEvent(QKeyEvent* event) {
void Window::resizeEvent(QResizeEvent*) {
m_config->setOption("height", m_screenWidget->height());
m_config->setOption("width", m_screenWidget->width());
+ m_config->setOption("fullscreen", isFullScreen());
}
void Window::closeEvent(QCloseEvent* event) {
emit shutdown();
+ m_config->setQtOption("windowPos", pos());
QMainWindow::closeEvent(event);
}
@@ -374,6 +441,25 @@ void Window::dropEvent(QDropEvent* event) {
m_controller->loadGame(url.path());
}
+void Window::mouseDoubleClickEvent(QMouseEvent* event) {
+ if (event->button() != Qt::LeftButton) {
+ return;
+ }
+ toggleFullScreen();
+}
+
+void Window::enterFullScreen() {
+ if (isFullScreen()) {
+ return;
+ }
+ showFullScreen();
+#ifndef Q_OS_MAC
+ if (m_controller->isLoaded() && !m_controller->isPaused()) {
+ menuBar()->hide();
+ }
+#endif
+}
+
void Window::exitFullScreen() {
if (!isFullScreen()) {
return;
@@ -384,15 +470,9 @@ void Window::exitFullScreen() {
void Window::toggleFullScreen() {
if (isFullScreen()) {
- showNormal();
- menuBar()->show();
+ exitFullScreen();
} else {
- showFullScreen();
-#ifndef Q_OS_MAC
- if (m_controller->isLoaded() && !m_controller->isPaused()) {
- menuBar()->hide();
- }
-#endif
+ enterFullScreen();
}
}
@@ -400,7 +480,7 @@ void Window::gameStarted(GBAThread* context) {
char title[13] = { '\0' };
MutexLock(&context->stateMutex);
if (context->state < THREAD_EXITING) {
- emit startDrawing(m_controller->drawContext(), context);
+ emit startDrawing(context);
GBAGetGameTitle(context->gba, title);
} else {
MutexUnlock(&context->stateMutex);
@@ -473,8 +553,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 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;
@@ -496,7 +582,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);
@@ -534,23 +620,34 @@ void Window::setupMenu(QMenuBar* menubar) {
QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load"));
QMenu* quickSaveMenu = fileMenu->addMenu(tr("Quick save"));
+ m_shortcutController->addMenu(quickLoadMenu);
+ m_shortcutController->addMenu(quickSaveMenu);
int i;
for (i = 1; i < 10; ++i) {
QAction* quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu);
quickLoad->setShortcut(tr("F%1").arg(i));
connect(quickLoad, &QAction::triggered, [this, i]() { m_controller->loadState(i); });
m_gameActions.append(quickLoad);
- addAction(quickLoad);
- quickLoadMenu->addAction(quickLoad);
+ addControlledAction(quickLoadMenu, quickLoad, QString("quickLoad.%1").arg(i));
QAction* quickSave = new QAction(tr("State &%1").arg(i), quickSaveMenu);
quickSave->setShortcut(tr("Shift+F%1").arg(i));
connect(quickSave, &QAction::triggered, [this, i]() { m_controller->saveState(i); });
m_gameActions.append(quickSave);
- addAction(quickSave);
- quickSaveMenu->addAction(quickSave);
+ addControlledAction(quickSaveMenu, quickSave, QString("quickSave.%1").arg(i));
}
+ fileMenu->addSeparator();
+ QAction* importShark = new QAction(tr("Import GameShark Save"), fileMenu);
+ connect(importShark, SIGNAL(triggered()), this, SLOT(importSharkport()));
+ m_gameActions.append(importShark);
+ addControlledAction(fileMenu, importShark, "importShark");
+
+ QAction* exportShark = new QAction(tr("Export GameShark Save"), fileMenu);
+ connect(exportShark, SIGNAL(triggered()), this, SLOT(exportSharkport()));
+ m_gameActions.append(exportShark);
+ addControlledAction(fileMenu, exportShark, "exportShark");
+
fileMenu->addSeparator();
QAction* multiWindow = new QAction(tr("New multiplayer window"), fileMenu);
connect(multiWindow, &QAction::triggered, [this]() {
@@ -624,6 +721,14 @@ void Window::setupMenu(QMenuBar* menubar) {
m_gameActions.append(rewind);
addControlledAction(emulationMenu, rewind, "rewind");
+ QAction* frameRewind = new QAction(tr("Step backwards"), emulationMenu);
+ frameRewind->setShortcut(tr("Ctrl+B"));
+ connect(frameRewind, &QAction::triggered, [this] () {
+ m_controller->rewind(1);
+ });
+ m_gameActions.append(frameRewind);
+ addControlledAction(emulationMenu, frameRewind, "frameRewind");
+
ConfigOption* videoSync = m_config->addOption("videoSync");
videoSync->addBoolean(tr("Sync to &video"), emulationMenu);
videoSync->connect([this](const QVariant& value) {
@@ -638,6 +743,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"));
@@ -648,7 +773,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");
@@ -692,14 +817,15 @@ 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());
+ emit fpsTargetChanged(value.toFloat());
}, this);
fpsTargetOption->addValue(tr("15"), 15, target);
fpsTargetOption->addValue(tr("30"), 30, target);
fpsTargetOption->addValue(tr("45"), 45, target);
+ fpsTargetOption->addValue(tr("Native (59.7)"), float(GBA_ARM7TDMI_FREQUENCY) / float(VIDEO_TOTAL_LENGTH), target);
fpsTargetOption->addValue(tr("60"), 60, target);
fpsTargetOption->addValue(tr("90"), 90, target);
fpsTargetOption->addValue(tr("120"), 120, target);
@@ -713,7 +839,7 @@ void Window::setupMenu(QMenuBar* menubar) {
#ifdef USE_PNG
QAction* screenshot = new QAction(tr("Take &screenshot"), avMenu);
screenshot->setShortcut(tr("F12"));
- connect(screenshot, SIGNAL(triggered()), m_display, SLOT(screenshot()));
+ connect(screenshot, SIGNAL(triggered()), m_controller, SLOT(screenshot()));
m_gameActions.append(screenshot);
addControlledAction(avMenu, screenshot, "screenshot");
#endif
@@ -732,6 +858,50 @@ void Window::setupMenu(QMenuBar* menubar) {
addControlledAction(avMenu, recordGIF, "recordGIF");
#endif
+ avMenu->addSeparator();
+ QMenu* videoLayers = avMenu->addMenu(tr("Video layers"));
+
+ for (int i = 0; i < 4; ++i) {
+ QAction* enableBg = new QAction(tr("Background %0").arg(i), videoLayers);
+ enableBg->setCheckable(true);
+ enableBg->setChecked(true);
+ connect(enableBg, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->video.renderer->disableBG[i] = !enable; });
+ m_gameActions.append(enableBg);
+ addControlledAction(videoLayers, enableBg, QString("enableBG%0").arg(i));
+ }
+
+ QAction* enableObj = new QAction(tr("OBJ (sprites)"), videoLayers);
+ enableObj->setCheckable(true);
+ enableObj->setChecked(true);
+ connect(enableObj, &QAction::triggered, [this](bool enable) { m_controller->thread()->gba->video.renderer->disableOBJ = !enable; });
+ m_gameActions.append(enableObj);
+ addControlledAction(videoLayers, enableObj, "enableOBJ");
+
+ QMenu* audioChannels = avMenu->addMenu(tr("Audio channels"));
+
+ for (int i = 0; i < 4; ++i) {
+ QAction* enableCh = new QAction(tr("Channel %0").arg(i + 1), audioChannels);
+ enableCh->setCheckable(true);
+ enableCh->setChecked(true);
+ connect(enableCh, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableCh[i] = !enable; });
+ m_gameActions.append(enableCh);
+ addControlledAction(audioChannels, enableCh, QString("enableCh%0").arg(i + 1));
+ }
+
+ QAction* enableChA = new QAction(tr("Channel A"), audioChannels);
+ enableChA->setCheckable(true);
+ enableChA->setChecked(true);
+ connect(enableChA, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableChA = !enable; });
+ m_gameActions.append(enableChA);
+ addControlledAction(audioChannels, enableChA, QString("enableChA"));
+
+ QAction* enableChB = new QAction(tr("Channel B"), audioChannels);
+ enableChB->setCheckable(true);
+ enableChB->setChecked(true);
+ connect(enableChB, &QAction::triggered, [this, i](bool enable) { m_controller->thread()->gba->audio.forceDisableChB = !enable; });
+ m_gameActions.append(enableChB);
+ addControlledAction(audioChannels, enableChB, QString("enableChB"));
+
QMenu* toolsMenu = menubar->addMenu(tr("&Tools"));
m_shortcutController->addMenu(toolsMenu);
QAction* viewLogs = new QAction(tr("View &logs..."), toolsMenu);
@@ -756,23 +926,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");
@@ -787,14 +940,26 @@ void Window::setupMenu(QMenuBar* menubar) {
addControlledAction(toolsMenu, gamepad, "remapGamepad");
#endif
+ toolsMenu->addSeparator();
+
+ QAction* paletteView = new QAction(tr("View &palette..."), toolsMenu);
+ connect(paletteView, SIGNAL(triggered()), this, SLOT(openPaletteWindow()));
+ m_gameActions.append(paletteView);
+ addControlledAction(toolsMenu, paletteView, "paletteWindow");
+
ConfigOption* skipBios = m_config->addOption("skipBios");
skipBios->connect([this](const QVariant& value) {
m_controller->setSkipBIOS(value.toBool());
}, this);
- ConfigOption* useBios = m_config->addOption("useBios");
- useBios->connect([this](const QVariant& value) {
- m_controller->setUseBIOS(value.toBool());
+ ConfigOption* volume = m_config->addOption("volume");
+ volume->connect([this](const QVariant& value) {
+ m_controller->setVolume(value.toInt());
+ }, this);
+
+ ConfigOption* mute = m_config->addOption("mute");
+ mute->connect([this](const QVariant& value) {
+ m_controller->setMute(value.toBool());
}, this);
ConfigOption* rewindEnable = m_config->addOption("rewindEnable");
@@ -825,7 +990,7 @@ void Window::setupMenu(QMenuBar* menubar) {
m_controller->setTurbo(false, false);
}, QKeySequence(Qt::Key_Tab), tr("Fast Forward (held)"), "holdFastForward");
- addControlledAction(other, other->addAction(tr("Exit fullscreen"), this, SLOT(exitFullScreen()), QKeySequence("Esc")), "exitFullscreen");
+ addControlledAction(other, other->addAction(tr("Exit fullscreen"), this, SLOT(exitFullScreen()), QKeySequence("Esc")), "exitFullScreen");
foreach (QAction* action, m_gameActions) {
action->setDisabled(true);
@@ -874,6 +1039,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;
}
@@ -900,13 +1066,13 @@ void WindowBackground::setLockAspectRatio(int width, int height) {
}
void WindowBackground::paintEvent(QPaintEvent*) {
- QPainter painter(this);
- painter.setRenderHint(QPainter::SmoothPixmapTransform);
const QPixmap* logo = pixmap();
- painter.fillRect(QRect(QPoint(), size()), Qt::black);
if (!logo) {
return;
}
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::SmoothPixmapTransform);
+ painter.fillRect(QRect(QPoint(), size()), Qt::black);
QSize s = size();
QSize ds = s;
if (s.width() * m_aspectHeight > s.height() * m_aspectWidth) {
diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h
index d2d569e00..010e0225c 100644
--- a/src/platform/qt/Window.h
+++ b/src/platform/qt/Window.h
@@ -18,7 +18,6 @@ extern "C" {
}
#include "GDBController.h"
-#include "Display.h"
#include "InputController.h"
#include "LoadSaveState.h"
@@ -28,6 +27,7 @@ struct GBAArguments;
namespace QGBA {
class ConfigController;
+class Display;
class GameController;
class GIFView;
class LogView;
@@ -50,7 +50,7 @@ public:
void resizeFrame(int width, int height);
signals:
- void startDrawing(const uint32_t*, GBAThread*);
+ void startDrawing(GBAThread*);
void shutdown();
void audioBufferSamplesChanged(int samples);
void fpsTargetChanged(float target);
@@ -59,11 +59,15 @@ public slots:
void selectROM();
void selectBIOS();
void selectPatch();
+ void enterFullScreen();
void exitFullScreen();
void toggleFullScreen();
void loadConfig();
void saveConfig();
+ void importSharkport();
+ void exportSharkport();
+
void openKeymapWindow();
void openSettingsWindow();
void openShortcutWindow();
@@ -72,6 +76,8 @@ public slots:
void openSensorWindow();
void openCheatsWindow();
+ void openPaletteWindow();
+
#ifdef BUILD_SDL
void openGamepadWindow();
#endif
@@ -96,6 +102,7 @@ protected:
virtual void focusOutEvent(QFocusEvent*) override;
virtual void dragEnterEvent(QDragEnterEvent*) override;
virtual void dropEvent(QDropEvent*) override;
+ virtual void mouseDoubleClickEvent(QMouseEvent*) override;
private slots:
void gameStarted(GBAThread*);
@@ -120,6 +127,8 @@ private:
void appendMRU(const QString& fname);
void updateMRU();
+ void openView(QWidget* widget);
+
QAction* addControlledAction(QMenu* menu, QAction* action, const QString& name);
GameController* m_controller;
diff --git a/src/platform/sdl/CMakeLists.txt b/src/platform/sdl/CMakeLists.txt
index 3134ffc63..19c4867a1 100644
--- a/src/platform/sdl/CMakeLists.txt
+++ b/src/platform/sdl/CMakeLists.txt
@@ -35,7 +35,7 @@ set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libsdl${SDL_VE
file(GLOB PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-*.c)
set(PLATFORM_LIBRARY ${SDL_LIBRARY} ${SDLMAIN_LIBRARY} ${PIXMAN-1_LIBRARIES})
-include_directories(${CMAKE_SOURCE_DIR}/src/platform/sdl ${SDL_INCLUDE_DIR})
+include_directories(${CMAKE_SOURCE_DIR}/src/platform/sdl ${PIXMAN-1_INCLUDE_DIR} ${SDL_INCLUDE_DIR})
set(SDL_INCLUDE_DIR "${SDL_INCLUDE_DIR}" PARENT_SCOPE)
set(SDL_LIBRARY "${SDL_LIBRARY}" PARENT_SCOPE)
@@ -55,13 +55,14 @@ endif()
if(BUILD_PANDORA)
list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/pandora-sdl.c)
-elseif(BUILD_BBB OR BUILD_RASPI OR NOT BUILD_GL)
- list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sw-sdl.c)
else()
- list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-sdl.c)
- add_definitions(-DBUILD_GL)
- find_package(OpenGL REQUIRED)
- include_directories(${OPENGL_INCLUDE_DIR})
+ list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sw-sdl.c)
+ if(BUILD_GL)
+ list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-sdl.c)
+ add_definitions(-DBUILD_GL)
+ find_package(OpenGL REQUIRED)
+ include_directories(${OPENGL_INCLUDE_DIR})
+ endif()
endif()
add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC})
diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c
index fd73a43c3..975166be7 100644
--- a/src/platform/sdl/gl-sdl.c
+++ b/src/platform/sdl/gl-sdl.c
@@ -49,7 +49,17 @@ static void _doViewport(int w, int h, struct SDLSoftwareRenderer* renderer) {
glClear(GL_COLOR_BUFFER_BIT);
}
-bool GBASDLInit(struct SDLSoftwareRenderer* renderer) {
+static bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer);
+static void GBASDLGLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer);
+static void GBASDLGLDeinit(struct SDLSoftwareRenderer* renderer);
+
+void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer) {
+ renderer->init = GBASDLGLInit;
+ renderer->deinit = GBASDLGLDeinit;
+ renderer->runloop = GBASDLGLRunloop;
+}
+
+bool GBASDLGLInit(struct SDLSoftwareRenderer* renderer) {
#ifndef COLOR_16_BIT
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
@@ -110,7 +120,7 @@ bool GBASDLInit(struct SDLSoftwareRenderer* renderer) {
return true;
}
-void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) {
+void GBASDLGLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) {
SDL_Event event;
glEnable(GL_TEXTURE_2D);
@@ -159,6 +169,6 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render
}
}
-void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) {
+void GBASDLGLDeinit(struct SDLSoftwareRenderer* renderer) {
free(renderer->d.outputBuffer);
}
diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c
index c52d8d342..79067e09b 100644
--- a/src/platform/sdl/main.c
+++ b/src/platform/sdl/main.c
@@ -28,8 +28,8 @@
#define PORT "sdl"
-static bool _GBASDLInit(struct SDLSoftwareRenderer* renderer);
-static void _GBASDLDeinit(struct SDLSoftwareRenderer* renderer);
+static bool GBASDLInit(struct SDLSoftwareRenderer* renderer);
+static void GBASDLDeinit(struct SDLSoftwareRenderer* renderer);
int main(int argc, char** argv) {
struct SDLSoftwareRenderer renderer;
@@ -83,7 +83,13 @@ int main(int argc, char** argv) {
renderer.lockAspectRatio = opts.lockAspectRatio;
renderer.filter = opts.resampleVideo;
- if (!_GBASDLInit(&renderer)) {
+#ifdef BUILD_GL
+ GBASDLGLCreate(&renderer);
+#else
+ GBASDLSWCreate(&renderer);
+#endif
+
+ if (!GBASDLInit(&renderer)) {
freeArguments(&args);
GBAConfigFreeOpts(&opts);
GBAConfigDeinit(&config);
@@ -113,7 +119,7 @@ int main(int argc, char** argv) {
int didFail = 0;
if (GBAThreadStart(&context)) {
- GBASDLRunloop(&context, &renderer);
+ renderer.runloop(&context, &renderer);
GBAThreadJoin(&context);
} else {
didFail = 1;
@@ -130,28 +136,28 @@ int main(int argc, char** argv) {
free(context.debugger);
GBAInputMapDeinit(&inputMap);
- _GBASDLDeinit(&renderer);
+ GBASDLDeinit(&renderer);
return didFail;
}
-static bool _GBASDLInit(struct SDLSoftwareRenderer* renderer) {
+static bool GBASDLInit(struct SDLSoftwareRenderer* renderer) {
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("Could not initialize video: %s\n", SDL_GetError());
return false;
}
- return GBASDLInit(renderer);
+ return renderer->init(renderer);
}
-static void _GBASDLDeinit(struct SDLSoftwareRenderer* renderer) {
+static void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) {
GBASDLDeinitEvents(&renderer->events);
GBASDLDeinitAudio(&renderer->audio);
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_DestroyWindow(renderer->window);
#endif
- GBASDLDeinit(renderer);
+ renderer->deinit(renderer);
SDL_Quit();
diff --git a/src/platform/sdl/main.h b/src/platform/sdl/main.h
index 07587c281..80e271245 100644
--- a/src/platform/sdl/main.h
+++ b/src/platform/sdl/main.h
@@ -41,12 +41,14 @@ struct SDLSoftwareRenderer {
struct GBASDLEvents events;
struct GBASDLPlayer player;
+ bool (*init)(struct SDLSoftwareRenderer* renderer);
+ void (*runloop)(struct GBAThread* context, struct SDLSoftwareRenderer* renderer);
+ void (*deinit)(struct SDLSoftwareRenderer* renderer);
+
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_Window* window;
-#ifndef BUILD_GL
- SDL_Texture* tex;
+ SDL_Texture* sdlTex;
SDL_Renderer* sdlRenderer;
-#endif
#endif
int viewportWidth;
@@ -86,9 +88,9 @@ struct SDLSoftwareRenderer {
#endif
};
-bool GBASDLInit(struct SDLSoftwareRenderer* renderer);
-void GBASDLDeinit(struct SDLSoftwareRenderer* renderer);
-void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer);
+void GBASDLSWCreate(struct SDLSoftwareRenderer* renderer);
+#ifdef BUILD_GL
+void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer);
+#endif
#endif
-
diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c
index 6e9ac8d49..142207e4d 100644
--- a/src/platform/sdl/sdl-events.c
+++ b/src/platform/sdl/sdl-events.c
@@ -20,8 +20,14 @@
#define GUI_MOD KMOD_CTRL
#endif
+static void _GBASDLSetRumble(struct GBARumble* rumble, int enable);
+
bool GBASDLInitEvents(struct GBASDLEvents* context) {
- if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) {
+ int subsystem = SDL_INIT_JOYSTICK;
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+ subsystem |= SDL_INIT_HAPTIC;
+#endif
+ if (SDL_InitSubSystem(subsystem) < 0) {
return false;
}
@@ -30,9 +36,15 @@ bool GBASDLInitEvents(struct GBASDLEvents* context) {
if (nJoysticks > 0) {
context->nJoysticks = nJoysticks;
context->joysticks = calloc(context->nJoysticks, sizeof(SDL_Joystick*));
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+ context->haptic = calloc(context->nJoysticks, sizeof(SDL_Haptic*));
+#endif
size_t i;
for (i = 0; i < context->nJoysticks; ++i) {
context->joysticks[i] = SDL_JoystickOpen(i);
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+ context->haptic[i] = SDL_HapticOpenFromJoystick(context->joysticks[i]);
+#endif
}
} else {
context->nJoysticks = 0;
@@ -56,6 +68,9 @@ bool GBASDLInitEvents(struct GBASDLEvents* context) {
void GBASDLDeinitEvents(struct GBASDLEvents* context) {
size_t i;
for (i = 0; i < context->nJoysticks; ++i) {
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+ SDL_HapticClose(context->haptic[i]);
+#endif
SDL_JoystickClose(context->joysticks[i]);
}
@@ -126,6 +141,11 @@ bool GBASDLAttachPlayer(struct GBASDLEvents* events, struct GBASDLPlayer* player
player->joystick = 0;
player->joystickIndex = SIZE_MAX;
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+ player->rumble.d.setRumble = _GBASDLSetRumble;
+ player->rumble.p = player;
+#endif
+
if (events->playersAttached >= MAX_PLAYERS) {
return false;
}
@@ -171,6 +191,13 @@ bool GBASDLAttachPlayer(struct GBASDLEvents* events, struct GBASDLPlayer* player
if (player->joystickIndex != SIZE_MAX) {
player->joystick = events->joysticks[player->joystickIndex];
events->joysticksClaimed[player->playerId] = player->joystickIndex;
+
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+ player->haptic = events->haptic[player->joystickIndex];
+ if (player->haptic) {
+ SDL_HapticRumbleInit(player->haptic);
+ }
+#endif
}
++events->playersAttached;
@@ -403,3 +430,17 @@ void GBASDLHandleEvent(struct GBAThread* context, struct GBASDLPlayer* sdlContex
break;
}
}
+
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+static void _GBASDLSetRumble(struct GBARumble* rumble, int enable) {
+ struct GBASDLRumble* sdlRumble = (struct GBASDLRumble*) rumble;
+ if (!sdlRumble->p->haptic || !SDL_HapticRumbleSupported(sdlRumble->p->haptic)) {
+ return;
+ }
+ if (enable) {
+ SDL_HapticRumblePlay(sdlRumble->p->haptic, 1.0f, 20);
+ } else {
+ SDL_HapticRumbleStop(sdlRumble->p->haptic);
+ }
+}
+#endif
diff --git a/src/platform/sdl/sdl-events.h b/src/platform/sdl/sdl-events.h
index 69e095e9d..81840d39e 100644
--- a/src/platform/sdl/sdl-events.h
+++ b/src/platform/sdl/sdl-events.h
@@ -26,6 +26,9 @@ struct GBASDLEvents {
const char* preferredJoysticks[MAX_PLAYERS];
int playersAttached;
size_t joysticksClaimed[MAX_PLAYERS];
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+ SDL_Haptic** haptic;
+#endif
};
struct GBASDLPlayer {
@@ -37,6 +40,12 @@ struct GBASDLPlayer {
SDL_Window* window;
int fullscreen;
int windowUpdated;
+ SDL_Haptic* haptic;
+
+ struct GBASDLRumble {
+ struct GBARumble d;
+ struct GBASDLPlayer* p;
+ } rumble;
#endif
};
diff --git a/src/platform/sdl/sw-sdl.c b/src/platform/sdl/sw-sdl.c
index 971710554..f44fad38d 100644
--- a/src/platform/sdl/sw-sdl.c
+++ b/src/platform/sdl/sw-sdl.c
@@ -8,7 +8,17 @@
#include "gba/supervisor/thread.h"
#include "util/arm-algo.h"
-bool GBASDLInit(struct SDLSoftwareRenderer* renderer) {
+static bool GBASDLSWInit(struct SDLSoftwareRenderer* renderer);
+static void GBASDLSWRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer);
+static void GBASDLSWDeinit(struct SDLSoftwareRenderer* renderer);
+
+void GBASDLSWCreate(struct SDLSoftwareRenderer* renderer) {
+ renderer->init = GBASDLSWInit;
+ renderer->deinit = GBASDLSWDeinit;
+ renderer->runloop = GBASDLSWRunloop;
+}
+
+bool GBASDLSWInit(struct SDLSoftwareRenderer* renderer) {
#if !SDL_VERSION_ATLEAST(2, 0, 0)
#ifdef COLOR_16_BIT
SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 16, SDL_DOUBLEBUF | SDL_HWSURFACE);
@@ -24,15 +34,15 @@ bool GBASDLInit(struct SDLSoftwareRenderer* renderer) {
renderer->sdlRenderer = SDL_CreateRenderer(renderer->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
#ifdef COLOR_16_BIT
#ifdef COLOR_5_6_5
- renderer->tex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
+ renderer->sdlTex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
#else
- renderer->tex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_ABGR1555, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
+ renderer->sdlTex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_ABGR1555, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
#endif
#else
- renderer->tex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
+ renderer->sdlTex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
#endif
- SDL_LockTexture(renderer->tex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride);
+ SDL_LockTexture(renderer->sdlTex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride);
renderer->d.outputBufferStride /= BYTES_PER_PIXEL;
#else
SDL_Surface* surface = SDL_GetVideoSurface();
@@ -72,7 +82,7 @@ bool GBASDLInit(struct SDLSoftwareRenderer* renderer) {
return true;
}
-void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) {
+void GBASDLSWRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) {
SDL_Event event;
#if !SDL_VERSION_ATLEAST(2, 0, 0)
SDL_Surface* surface = SDL_GetVideoSurface();
@@ -85,10 +95,10 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render
if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) {
#if SDL_VERSION_ATLEAST(2, 0, 0)
- SDL_UnlockTexture(renderer->tex);
- SDL_RenderCopy(renderer->sdlRenderer, renderer->tex, 0, 0);
+ SDL_UnlockTexture(renderer->sdlTex);
+ SDL_RenderCopy(renderer->sdlRenderer, renderer->sdlTex, 0, 0);
SDL_RenderPresent(renderer->sdlRenderer);
- SDL_LockTexture(renderer->tex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride);
+ SDL_LockTexture(renderer->sdlTex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride);
renderer->d.outputBufferStride /= BYTES_PER_PIXEL;
#else
#ifdef USE_PIXMAN
@@ -122,7 +132,7 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render
}
}
-void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) {
+void GBASDLSWDeinit(struct SDLSoftwareRenderer* renderer) {
if (renderer->ratio > 1) {
free(renderer->d.outputBuffer);
}
diff --git a/src/util/string.c b/src/util/string.c
index 5ca8ee299..586562543 100644
--- a/src/util/string.c
+++ b/src/util/string.c
@@ -197,3 +197,71 @@ char* utf16to8(const uint16_t* utf16, size_t length) {
newUTF8[utf8Length] = '\0';
return newUTF8;
}
+
+int hexDigit(char digit) {
+ switch (digit) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ return digit - '0';
+
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ return digit - 'a' + 10;
+
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ return digit - 'A' + 10;
+
+ default:
+ return -1;
+ }
+}
+
+const char* hex32(const char* line, uint32_t* out) {
+ uint32_t value = 0;
+ int i;
+ for (i = 0; i < 8; ++i, ++line) {
+ char digit = *line;
+ value <<= 4;
+ int nybble = hexDigit(digit);
+ if (nybble < 0) {
+ return 0;
+ }
+ value |= nybble;
+ }
+ *out = value;
+ return line;
+}
+
+const char* hex16(const char* line, uint16_t* out) {
+ uint16_t value = 0;
+ *out = 0;
+ int i;
+ for (i = 0; i < 4; ++i, ++line) {
+ char digit = *line;
+ value <<= 4;
+ int nybble = hexDigit(digit);
+ if (nybble < 0) {
+ return 0;
+ }
+ value |= nybble;
+ }
+ *out = value;
+ return line;
+}
diff --git a/src/util/string.h b/src/util/string.h
index 8e29867b3..e326bf047 100644
--- a/src/util/string.h
+++ b/src/util/string.h
@@ -18,4 +18,8 @@ char* strnrstr(const char* restrict s1, const char* restrict s2, size_t len);
int utfcmp(const uint16_t* utf16, const char* utf8, size_t utf16Length, size_t utf8Length);
char* utf16to8(const uint16_t* utf16, size_t length);
+int hexDigit(char digit);
+const char* hex32(const char* line, uint32_t* out);
+const char* hex16(const char* line, uint16_t* out);
+
#endif
diff --git a/src/util/vector.h b/src/util/vector.h
index 80223540c..f809f0084 100644
--- a/src/util/vector.h
+++ b/src/util/vector.h
@@ -24,6 +24,7 @@
void NAME ## Unshift(struct NAME* vector, size_t location, size_t difference); \
void NAME ## EnsureCapacity(struct NAME* vector, size_t capacity); \
size_t NAME ## Size(const struct NAME* vector); \
+ size_t NAME ## Index(const struct NAME* vector, const TYPE* member);
#define DEFINE_VECTOR(NAME, TYPE) \
void NAME ## Init(struct NAME* vector, size_t capacity) { \
@@ -75,5 +76,8 @@
size_t NAME ## Size(const struct NAME* vector) { \
return vector->size; \
} \
+ size_t NAME ## Index(const struct NAME* vector, const TYPE* member) { \
+ return member - (const TYPE*) vector->vector; \
+ } \
#endif
diff --git a/src/util/vfs.c b/src/util/vfs.c
index d706d0b32..b06db9c01 100644
--- a/src/util/vfs.c
+++ b/src/util/vfs.c
@@ -1,215 +1,20 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau
+/* Copyright (c) 2013-2015 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 "util/vfs.h"
+#include "vfs.h"
-#include "util/string.h"
-
-#include
-#include
-#include
-
-#ifndef _WIN32
-#include
-#define PATH_SEP '/'
-#else
-#include
-#include
-#define PATH_SEP '\\'
-#endif
-
-struct VFileFD {
- struct VFile d;
- int fd;
-#ifdef _WIN32
- HANDLE hMap;
-#endif
-};
-
-static bool _vfdClose(struct VFile* vf);
-static off_t _vfdSeek(struct VFile* vf, off_t offset, int whence);
-static ssize_t _vfdRead(struct VFile* vf, void* buffer, size_t size);
-static ssize_t _vfdReadline(struct VFile* vf, char* buffer, size_t size);
-static ssize_t _vfdWrite(struct VFile* vf, const void* buffer, size_t size);
-static void* _vfdMap(struct VFile* vf, size_t size, int flags);
-static void _vfdUnmap(struct VFile* vf, void* memory, size_t size);
-static void _vfdTruncate(struct VFile* vf, size_t size);
-static ssize_t _vfdSize(struct VFile* vf);
-
-static bool _vdClose(struct VDir* vd);
-static void _vdRewind(struct VDir* vd);
-static struct VDirEntry* _vdListNext(struct VDir* vd);
-static struct VFile* _vdOpenFile(struct VDir* vd, const char* path, int mode);
-
-static const char* _vdeName(struct VDirEntry* vde);
-
-struct VFile* VFileOpen(const char* path, int flags) {
- if (!path) {
- return 0;
- }
-#ifdef _WIN32
- flags |= O_BINARY;
-#endif
- int fd = open(path, flags, 0666);
- return VFileFromFD(fd);
-}
-
-struct VFile* VFileFromFD(int fd) {
- if (fd < 0) {
- return 0;
- }
-
- struct VFileFD* vfd = malloc(sizeof(struct VFileFD));
- if (!vfd) {
- return 0;
- }
-
- vfd->fd = fd;
- vfd->d.close = _vfdClose;
- vfd->d.seek = _vfdSeek;
- vfd->d.read = _vfdRead;
- vfd->d.readline = _vfdReadline;
- vfd->d.write = _vfdWrite;
- vfd->d.map = _vfdMap;
- vfd->d.unmap = _vfdUnmap;
- vfd->d.truncate = _vfdTruncate;
- vfd->d.size = _vfdSize;
-
- return &vfd->d;
-}
-
-bool _vfdClose(struct VFile* vf) {
- struct VFileFD* vfd = (struct VFileFD*) vf;
- if (close(vfd->fd) < 0) {
- return false;
- }
- free(vfd);
- return true;
-}
-
-off_t _vfdSeek(struct VFile* vf, off_t offset, int whence) {
- struct VFileFD* vfd = (struct VFileFD*) vf;
- return lseek(vfd->fd, offset, whence);
-}
-
-ssize_t _vfdRead(struct VFile* vf, void* buffer, size_t size) {
- struct VFileFD* vfd = (struct VFileFD*) vf;
- return read(vfd->fd, buffer, size);
-}
-
-ssize_t _vfdReadline(struct VFile* vf, char* buffer, size_t size) {
- struct VFileFD* vfd = (struct VFileFD*) vf;
+ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size) {
size_t bytesRead = 0;
while (bytesRead < size - 1) {
- size_t newRead = read(vfd->fd, &buffer[bytesRead], 1);
+ size_t newRead = vf->read(vf, &buffer[bytesRead], 1);
+ bytesRead += newRead;
if (!newRead || buffer[bytesRead] == '\n') {
break;
}
- bytesRead += newRead;
}
- buffer[bytesRead] = '\0';
- return bytesRead;
-}
-
-ssize_t _vfdWrite(struct VFile* vf, const void* buffer, size_t size) {
- struct VFileFD* vfd = (struct VFileFD*) vf;
- return write(vfd->fd, buffer, size);
-}
-
-#ifndef _WIN32
-static void* _vfdMap(struct VFile* vf, size_t size, int flags) {
- struct VFileFD* vfd = (struct VFileFD*) vf;
- int mmapFlags = MAP_PRIVATE;
- if (flags & MAP_WRITE) {
- mmapFlags = MAP_SHARED;
- }
- return mmap(0, size, PROT_READ | PROT_WRITE, mmapFlags, vfd->fd, 0);
-}
-
-static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) {
- UNUSED(vf);
- munmap(memory, size);
-}
-#else
-static void* _vfdMap(struct VFile* vf, size_t size, int flags) {
- struct VFileFD* vfd = (struct VFileFD*) vf;
- int createFlags = PAGE_WRITECOPY;
- int mapFiles = FILE_MAP_COPY;
- if (flags & MAP_WRITE) {
- createFlags = PAGE_READWRITE;
- mapFiles = FILE_MAP_WRITE;
- }
- size_t fileSize;
- struct stat stat;
- if (fstat(vfd->fd, &stat) < 0) {
- return 0;
- }
- fileSize = stat.st_size;
- if (size > fileSize) {
- size = fileSize;
- }
- vfd->hMap = CreateFileMapping((HANDLE) _get_osfhandle(vfd->fd), 0, createFlags, 0, size & 0xFFFFFFFF, 0);
- return MapViewOfFile(vfd->hMap, mapFiles, 0, 0, size);
-}
-
-static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) {
- UNUSED(size);
- struct VFileFD* vfd = (struct VFileFD*) vf;
- UnmapViewOfFile(memory);
- CloseHandle(vfd->hMap);
- vfd->hMap = 0;
-}
-#endif
-
-static void _vfdTruncate(struct VFile* vf, size_t size) {
- struct VFileFD* vfd = (struct VFileFD*) vf;
- ftruncate(vfd->fd, size);
-}
-
-static ssize_t _vfdSize(struct VFile* vf) {
- struct VFileFD* vfd = (struct VFileFD*) vf;
- struct stat stat;
- if (fstat(vfd->fd, &stat) < 0) {
- return -1;
- }
- return stat.st_size;
-}
-
-struct VDirEntryDE {
- struct VDirEntry d;
- struct dirent* ent;
-};
-
-struct VDirDE {
- struct VDir d;
- DIR* de;
- struct VDirEntryDE vde;
- char* path;
-};
-
-struct VDir* VDirOpen(const char* path) {
- DIR* de = opendir(path);
- if (!de) {
- return 0;
- }
-
- struct VDirDE* vd = malloc(sizeof(struct VDirDE));
- if (!vd) {
- return 0;
- }
-
- vd->d.close = _vdClose;
- vd->d.rewind = _vdRewind;
- vd->d.listNext = _vdListNext;
- vd->d.openFile = _vdOpenFile;
- vd->path = strdup(path);
- vd->de = de;
-
- vd->vde.d.name = _vdeName;
-
- return &vd->d;
+ return buffer[bytesRead] = '\0';
}
struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode) {
@@ -239,154 +44,3 @@ struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const
}
return vf;
}
-
-struct VFile* VDirOptionalOpenIncrementFile(struct VDir* dir, const char* realPath, const char* prefix, const char* infix, const char* suffix, int mode) {
- char path[PATH_MAX];
- path[PATH_MAX - 1] = '\0';
- char realPrefix[PATH_MAX];
- realPrefix[PATH_MAX - 1] = '\0';
- if (!dir) {
- if (!realPath) {
- return 0;
- }
- const char* separatorPoint = strrchr(realPath, '/');
- const char* dotPoint;
- size_t len;
- if (!separatorPoint) {
- strcpy(path, "./");
- separatorPoint = realPath;
- dotPoint = strrchr(realPath, '.');
- } else {
- path[0] = '\0';
- dotPoint = strrchr(separatorPoint, '.');
-
- if (separatorPoint - realPath + 1 >= PATH_MAX - 1) {
- return 0;
- }
-
- len = separatorPoint - realPath;
- strncat(path, realPath, len);
- path[len] = '\0';
- ++separatorPoint;
- }
-
- if (dotPoint - realPath + 1 >= PATH_MAX - 1) {
- return 0;
- }
-
- if (dotPoint >= separatorPoint) {
- len = dotPoint - separatorPoint;
- } else {
- len = PATH_MAX - 1;
- }
-
- strncpy(realPrefix, separatorPoint, len);
- realPrefix[len] = '\0';
-
- prefix = realPrefix;
- dir = VDirOpen(path);
- }
- if (!dir) {
- // This shouldn't be possible
- return 0;
- }
- dir->rewind(dir);
- struct VDirEntry* dirent;
- size_t prefixLen = strlen(prefix);
- size_t infixLen = strlen(infix);
- unsigned next = 0;
- while ((dirent = dir->listNext(dir))) {
- const char* filename = dirent->name(dirent);
- char* dotPoint = strrchr(filename, '.');
- size_t len = strlen(filename);
- if (dotPoint) {
- len = (dotPoint - filename);
- }
- const char* separator = strnrstr(filename, infix, len);
- if (!separator) {
- continue;
- }
- len = separator - filename;
- if (len != prefixLen) {
- continue;
- }
- if (strncmp(filename, prefix, prefixLen) == 0) {
- int nlen;
- separator += infixLen;
- snprintf(path, PATH_MAX - 1, "%%u%s%%n", suffix);
- unsigned increment;
- if (sscanf(separator, path, &increment, &nlen) < 1) {
- continue;
- }
- len = strlen(separator);
- if (nlen < (ssize_t) len) {
- continue;
- }
- if (next <= increment) {
- next = increment + 1;
- }
- }
- }
- snprintf(path, PATH_MAX - 1, "%s%s%u%s", prefix, infix, next, suffix);
- path[PATH_MAX - 1] = '\0';
- return dir->openFile(dir, path, mode);
-}
-
-bool _vdClose(struct VDir* vd) {
- struct VDirDE* vdde = (struct VDirDE*) vd;
- if (closedir(vdde->de) < 0) {
- return false;
- }
- free(vdde->path);
- free(vdde);
- return true;
-}
-
-void _vdRewind(struct VDir* vd) {
- struct VDirDE* vdde = (struct VDirDE*) vd;
- rewinddir(vdde->de);
-}
-
-struct VDirEntry* _vdListNext(struct VDir* vd) {
- struct VDirDE* vdde = (struct VDirDE*) vd;
- vdde->vde.ent = readdir(vdde->de);
- if (vdde->vde.ent) {
- return &vdde->vde.d;
- }
-
- return 0;
-}
-
-struct VFile* _vdOpenFile(struct VDir* vd, const char* path, int mode) {
- struct VDirDE* vdde = (struct VDirDE*) vd;
- if (!path) {
- return 0;
- }
- const char* dir = vdde->path;
- char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2));
- sprintf(combined, "%s%c%s", dir, PATH_SEP, path);
-
- struct VFile* file = VFileOpen(combined, mode);
- free(combined);
- return file;
-}
-
-const char* _vdeName(struct VDirEntry* vde) {
- struct VDirEntryDE* vdede = (struct VDirEntryDE*) vde;
- if (vdede->ent) {
- return vdede->ent->d_name;
- }
- return 0;
-}
-
-ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size) {
- size_t bytesRead = 0;
- while (bytesRead < size - 1) {
- size_t newRead = vf->read(vf, &buffer[bytesRead], 1);
- bytesRead += newRead;
- if (!newRead || buffer[bytesRead] == '\n') {
- break;
- }
- }
- return buffer[bytesRead] = '\0';
-}
diff --git a/src/util/vfs.h b/src/util/vfs.h
index 17be7577f..1649838aa 100644
--- a/src/util/vfs.h
+++ b/src/util/vfs.h
@@ -8,6 +8,23 @@
#include "util/common.h"
+#ifndef _WIN32
+#include
+#define PATH_SEP "/"
+#else
+#include
+#include
+#define PATH_SEP "\\"
+#endif
+
+#ifndef PATH_MAX
+#ifdef MAX_PATH
+#define PATH_MAX MAX_PATH
+#else
+#define PATH_MAX 128
+#endif
+#endif
+
enum {
MAP_READ = 1,
MAP_WRITE = 2
diff --git a/src/util/vfs/vfs-dirent.c b/src/util/vfs/vfs-dirent.c
new file mode 100644
index 000000000..37b07561b
--- /dev/null
+++ b/src/util/vfs/vfs-dirent.c
@@ -0,0 +1,191 @@
+/* Copyright (c) 2013-2015 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 "util/vfs.h"
+
+#include "util/string.h"
+
+#include
+
+static bool _vdClose(struct VDir* vd);
+static void _vdRewind(struct VDir* vd);
+static struct VDirEntry* _vdListNext(struct VDir* vd);
+static struct VFile* _vdOpenFile(struct VDir* vd, const char* path, int mode);
+
+static const char* _vdeName(struct VDirEntry* vde);
+
+struct VDirEntryDE {
+ struct VDirEntry d;
+ struct dirent* ent;
+};
+
+struct VDirDE {
+ struct VDir d;
+ DIR* de;
+ struct VDirEntryDE vde;
+ char* path;
+};
+
+struct VDir* VDirOpen(const char* path) {
+ DIR* de = opendir(path);
+ if (!de) {
+ return 0;
+ }
+
+ struct VDirDE* vd = malloc(sizeof(struct VDirDE));
+ if (!vd) {
+ return 0;
+ }
+
+ vd->d.close = _vdClose;
+ vd->d.rewind = _vdRewind;
+ vd->d.listNext = _vdListNext;
+ vd->d.openFile = _vdOpenFile;
+ vd->path = strdup(path);
+ vd->de = de;
+
+ vd->vde.d.name = _vdeName;
+
+ return &vd->d;
+}
+
+struct VFile* VDirOptionalOpenIncrementFile(struct VDir* dir, const char* realPath, const char* prefix, const char* infix, const char* suffix, int mode) {
+ char path[PATH_MAX];
+ path[PATH_MAX - 1] = '\0';
+ char realPrefix[PATH_MAX];
+ realPrefix[PATH_MAX - 1] = '\0';
+ if (!dir) {
+ if (!realPath) {
+ return 0;
+ }
+ const char* separatorPoint = strrchr(realPath, '/');
+ const char* dotPoint;
+ size_t len;
+ if (!separatorPoint) {
+ strcpy(path, "./");
+ separatorPoint = realPath;
+ dotPoint = strrchr(realPath, '.');
+ } else {
+ path[0] = '\0';
+ dotPoint = strrchr(separatorPoint, '.');
+
+ if (separatorPoint - realPath + 1 >= PATH_MAX - 1) {
+ return 0;
+ }
+
+ len = separatorPoint - realPath;
+ strncat(path, realPath, len);
+ path[len] = '\0';
+ ++separatorPoint;
+ }
+
+ if (dotPoint - realPath + 1 >= PATH_MAX - 1) {
+ return 0;
+ }
+
+ if (dotPoint >= separatorPoint) {
+ len = dotPoint - separatorPoint;
+ } else {
+ len = PATH_MAX - 1;
+ }
+
+ strncpy(realPrefix, separatorPoint, len);
+ realPrefix[len] = '\0';
+
+ prefix = realPrefix;
+ dir = VDirOpen(path);
+ }
+ if (!dir) {
+ // This shouldn't be possible
+ return 0;
+ }
+ dir->rewind(dir);
+ struct VDirEntry* dirent;
+ size_t prefixLen = strlen(prefix);
+ size_t infixLen = strlen(infix);
+ unsigned next = 0;
+ while ((dirent = dir->listNext(dir))) {
+ const char* filename = dirent->name(dirent);
+ char* dotPoint = strrchr(filename, '.');
+ size_t len = strlen(filename);
+ if (dotPoint) {
+ len = (dotPoint - filename);
+ }
+ const char* separator = strnrstr(filename, infix, len);
+ if (!separator) {
+ continue;
+ }
+ len = separator - filename;
+ if (len != prefixLen) {
+ continue;
+ }
+ if (strncmp(filename, prefix, prefixLen) == 0) {
+ int nlen;
+ separator += infixLen;
+ snprintf(path, PATH_MAX - 1, "%%u%s%%n", suffix);
+ unsigned increment;
+ if (sscanf(separator, path, &increment, &nlen) < 1) {
+ continue;
+ }
+ len = strlen(separator);
+ if (nlen < (ssize_t) len) {
+ continue;
+ }
+ if (next <= increment) {
+ next = increment + 1;
+ }
+ }
+ }
+ snprintf(path, PATH_MAX - 1, "%s%s%u%s", prefix, infix, next, suffix);
+ path[PATH_MAX - 1] = '\0';
+ return dir->openFile(dir, path, mode);
+}
+
+bool _vdClose(struct VDir* vd) {
+ struct VDirDE* vdde = (struct VDirDE*) vd;
+ if (closedir(vdde->de) < 0) {
+ return false;
+ }
+ free(vdde->path);
+ free(vdde);
+ return true;
+}
+
+void _vdRewind(struct VDir* vd) {
+ struct VDirDE* vdde = (struct VDirDE*) vd;
+ rewinddir(vdde->de);
+}
+
+struct VDirEntry* _vdListNext(struct VDir* vd) {
+ struct VDirDE* vdde = (struct VDirDE*) vd;
+ vdde->vde.ent = readdir(vdde->de);
+ if (vdde->vde.ent) {
+ return &vdde->vde.d;
+ }
+
+ return 0;
+}
+
+struct VFile* _vdOpenFile(struct VDir* vd, const char* path, int mode) {
+ struct VDirDE* vdde = (struct VDirDE*) vd;
+ if (!path) {
+ return 0;
+ }
+ const char* dir = vdde->path;
+ char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2));
+ sprintf(combined, "%s%s%s", dir, PATH_SEP, path);
+
+ struct VFile* file = VFileOpen(combined, mode);
+ free(combined);
+ return file;
+}
+
+const char* _vdeName(struct VDirEntry* vde) {
+ struct VDirEntryDE* vdede = (struct VDirEntryDE*) vde;
+ if (vdede->ent) {
+ return vdede->ent->d_name;
+ }
+ return 0;
+}
diff --git a/src/util/vfs/vfs-fd.c b/src/util/vfs/vfs-fd.c
new file mode 100644
index 000000000..f3646c6c8
--- /dev/null
+++ b/src/util/vfs/vfs-fd.c
@@ -0,0 +1,159 @@
+/* Copyright (c) 2013-2015 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 "util/vfs.h"
+
+#include
+#include
+
+struct VFileFD {
+ struct VFile d;
+ int fd;
+#ifdef _WIN32
+ HANDLE hMap;
+#endif
+};
+
+static bool _vfdClose(struct VFile* vf);
+static off_t _vfdSeek(struct VFile* vf, off_t offset, int whence);
+static ssize_t _vfdRead(struct VFile* vf, void* buffer, size_t size);
+static ssize_t _vfdReadline(struct VFile* vf, char* buffer, size_t size);
+static ssize_t _vfdWrite(struct VFile* vf, const void* buffer, size_t size);
+static void* _vfdMap(struct VFile* vf, size_t size, int flags);
+static void _vfdUnmap(struct VFile* vf, void* memory, size_t size);
+static void _vfdTruncate(struct VFile* vf, size_t size);
+static ssize_t _vfdSize(struct VFile* vf);
+
+struct VFile* VFileOpen(const char* path, int flags) {
+ if (!path) {
+ return 0;
+ }
+#ifdef _WIN32
+ flags |= O_BINARY;
+#endif
+ int fd = open(path, flags, 0666);
+ return VFileFromFD(fd);
+}
+
+struct VFile* VFileFromFD(int fd) {
+ if (fd < 0) {
+ return 0;
+ }
+
+ struct VFileFD* vfd = malloc(sizeof(struct VFileFD));
+ if (!vfd) {
+ return 0;
+ }
+
+ vfd->fd = fd;
+ vfd->d.close = _vfdClose;
+ vfd->d.seek = _vfdSeek;
+ vfd->d.read = _vfdRead;
+ vfd->d.readline = _vfdReadline;
+ vfd->d.write = _vfdWrite;
+ vfd->d.map = _vfdMap;
+ vfd->d.unmap = _vfdUnmap;
+ vfd->d.truncate = _vfdTruncate;
+ vfd->d.size = _vfdSize;
+
+ return &vfd->d;
+}
+
+bool _vfdClose(struct VFile* vf) {
+ struct VFileFD* vfd = (struct VFileFD*) vf;
+ if (close(vfd->fd) < 0) {
+ return false;
+ }
+ free(vfd);
+ return true;
+}
+
+off_t _vfdSeek(struct VFile* vf, off_t offset, int whence) {
+ struct VFileFD* vfd = (struct VFileFD*) vf;
+ return lseek(vfd->fd, offset, whence);
+}
+
+ssize_t _vfdRead(struct VFile* vf, void* buffer, size_t size) {
+ struct VFileFD* vfd = (struct VFileFD*) vf;
+ return read(vfd->fd, buffer, size);
+}
+
+ssize_t _vfdReadline(struct VFile* vf, char* buffer, size_t size) {
+ struct VFileFD* vfd = (struct VFileFD*) vf;
+ size_t bytesRead = 0;
+ while (bytesRead < size - 1) {
+ size_t newRead = read(vfd->fd, &buffer[bytesRead], 1);
+ if (!newRead || buffer[bytesRead] == '\n') {
+ break;
+ }
+ bytesRead += newRead;
+ }
+ buffer[bytesRead] = '\0';
+ return bytesRead;
+}
+
+ssize_t _vfdWrite(struct VFile* vf, const void* buffer, size_t size) {
+ struct VFileFD* vfd = (struct VFileFD*) vf;
+ return write(vfd->fd, buffer, size);
+}
+
+#ifndef _WIN32
+static void* _vfdMap(struct VFile* vf, size_t size, int flags) {
+ struct VFileFD* vfd = (struct VFileFD*) vf;
+ int mmapFlags = MAP_PRIVATE;
+ if (flags & MAP_WRITE) {
+ mmapFlags = MAP_SHARED;
+ }
+ return mmap(0, size, PROT_READ | PROT_WRITE, mmapFlags, vfd->fd, 0);
+}
+
+static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) {
+ UNUSED(vf);
+ munmap(memory, size);
+}
+#else
+static void* _vfdMap(struct VFile* vf, size_t size, int flags) {
+ struct VFileFD* vfd = (struct VFileFD*) vf;
+ int createFlags = PAGE_WRITECOPY;
+ int mapFiles = FILE_MAP_COPY;
+ if (flags & MAP_WRITE) {
+ createFlags = PAGE_READWRITE;
+ mapFiles = FILE_MAP_WRITE;
+ }
+ size_t fileSize;
+ struct stat stat;
+ if (fstat(vfd->fd, &stat) < 0) {
+ return 0;
+ }
+ fileSize = stat.st_size;
+ if (size > fileSize) {
+ size = fileSize;
+ }
+ vfd->hMap = CreateFileMapping((HANDLE) _get_osfhandle(vfd->fd), 0, createFlags, 0, size & 0xFFFFFFFF, 0);
+ return MapViewOfFile(vfd->hMap, mapFiles, 0, 0, size);
+}
+
+static void _vfdUnmap(struct VFile* vf, void* memory, size_t size) {
+ UNUSED(size);
+ struct VFileFD* vfd = (struct VFileFD*) vf;
+ UnmapViewOfFile(memory);
+ CloseHandle(vfd->hMap);
+ vfd->hMap = 0;
+}
+#endif
+
+static void _vfdTruncate(struct VFile* vf, size_t size) {
+ struct VFileFD* vfd = (struct VFileFD*) vf;
+ ftruncate(vfd->fd, size);
+}
+
+static ssize_t _vfdSize(struct VFile* vf) {
+ struct VFileFD* vfd = (struct VFileFD*) vf;
+ struct stat stat;
+ if (fstat(vfd->fd, &stat) < 0) {
+ return -1;
+ }
+ return stat.st_size;
+}
diff --git a/tools/sanitize-deb.sh b/tools/sanitize-deb.sh
index 0a818b752..af7c3b72b 100755
--- a/tools/sanitize-deb.sh
+++ b/tools/sanitize-deb.sh
@@ -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