Merge branch 'master' into feature/sio-dolphin

This commit is contained in:
Jeffrey Pfau 2015-04-18 17:02:41 -07:00
commit 7df0c9d1f0
79 changed files with 3673 additions and 1569 deletions

94
CHANGES
View File

@ -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:

View File

@ -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}

View File

@ -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/).
![Build status](https://travis-ci.org/mgba-emu/mgba.svg?branch=master)
@ -13,6 +13,7 @@ Features
- Near full Game Boy Advance hardware support[<sup>[1]</sup>](#missing).
- Fast emulation. Known to run at full speed even on low end hardware, such as netbooks.
- Qt and SDL ports for a heavy-weight and a light-weight frontend.
- Local (same computer) link cable support.
- Save type detection, even for flash memory size[<sup>[2]</sup>](#flashdetect).
- Real-time clock support, even without configuration.
- A built-in BIOS implementation, and ability to load external BIOS files.
@ -30,12 +31,13 @@ Features
### Planned features
- Local and networked multiplayer link cable support ([Bug #1](https://endrift.com/mgba/bugs/show_bug.cgi?id=1)).
- Dolphin/JOY bus link cable support ([Bug #73](https://endrift.com/mgba/bugs/show_bug.cgi?id=73)).
- Networked multiplayer link cable support ([Bug #1](http://mgba.io/b/1)).
- Dolphin/JOY bus link cable support ([Bug #73](http://mgba.io/b/73)).
- Re-recording support for tool-assist runs. ([Bugzilla keyword "TASBlocker"](https://endrift.com/mgba/bugs/buglist.cgi?quicksearch=TASBlocker))
- Lua support for scripting ([Bug #62](https://endrift.com/mgba/bugs/show_bug.cgi?id=62)).
- A comprehensive debug suite ([Bug #132](https://endrift.com/mgba/bugs/show_bug.cgi?id=132)).
- libretro core for RetroArch and OpenEmu ([Bug #86](https://endrift.com/mgba/bugs/show_bug.cgi?id=86)).
- Lua support for scripting ([Bug #62](http://mgba.io/b/62)).
- A comprehensive debug suite ([Bug #132](http://mgba.io/b/132)).
- OpenEmu core.
- e-Reader support. ([Bug #171](http://mgba.io/b/171))
Supported Platforms
@ -46,7 +48,7 @@ Supported Platforms
- Linux
- FreeBSD
Other Unix-like platforms work as well, but are untested.
Other Unix-like platforms, such as OpenBSD, are known to work as well, but are untested and not fully supported.
### System requirements
@ -72,7 +74,7 @@ Controls are configurable in the menu. The default gamepad controls are mapped s
Compiling
---------
Compiling requires using CMake 2.8.11 or newer. To use CMake to build on a Unix-based system, the recommended commands are as follows:
Compiling requires using CMake 2.8.11 or newer. GCC and Clang are both known to work to compile mGBA, but Visual Studio 2013 and older are known not to work. To use CMake to build on a Unix-based system, the recommended commands are as follows:
mkdir build
cd build
@ -99,18 +101,18 @@ Footnotes
<a name="missing">[1]</a> Currently missing features are
- OBJ window for modes 3, 4 and 5 ([Bug #5](https://endrift.com/mgba/bugs/show_bug.cgi?id=5))
- Mosaic for transformed OBJs ([Bug #9](https://endrift.com/mgba/bugs/show_bug.cgi?id=9))
- BIOS call RegisterRamReset is partially stubbed out ([Bug #141](https://endrift.com/mgba/bugs/show_bug.cgi?id=141))
- Audio channel reset flags ([Bug #142](https://endrift.com/mgba/bugs/show_bug.cgi?id=142))
- Game Pak prefetch ([Bug #195](https://endrift.com/mgba/bugs/show_bug.cgi?id=195))
- BIOS call Stop, for entering sleep mode ([Bug #199](https://endrift.com/mgba/bugs/show_bug.cgi?id=199))
- OBJ window for modes 3, 4 and 5 ([Bug #5](http://mgba.io/b/5))
- Mosaic for transformed OBJs ([Bug #9](http://mgba.io/b/9))
- BIOS call RegisterRamReset is partially stubbed out ([Bug #141](http://mgba.io/b/141))
- Audio channel reset flags ([Bug #142](http://mgba.io/b/142))
- Game Pak prefetch ([Bug #195](http://mgba.io/b/195))
- BIOS call Stop, for entering sleep mode ([Bug #199](http://mgba.io/b/199))
<a name="flashdetect">[2]</a> Flash memory size detection does not work in some cases, and may require overrides, which are not yet user configurable. Filing a bug is recommended if such a case is encountered.
<a name="flashdetect">[2]</a> Flash memory size detection does not work in some cases. These can be configured at runtime, but filing a bug is recommended if such a case is encountered.
<a name="osxver">[3]</a> 10.7 is only needed for the Qt port. The SDL port is known to work on 10.6, and may work on older.
[downloads]: https://endrift.com/mgba/downloads.html
[downloads]: http://mgba.io/downloads.html
[source]: https://github.com/mgba-emu/mgba/
Copyright

View File

@ -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) {

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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
#endif

View File

@ -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

View File

@ -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);
}

224
src/gba/cheats/gameshark.c Normal file
View File

@ -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);
}

View File

@ -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

324
src/gba/cheats/parv3.c Normal file
View File

@ -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);
}

15
src/gba/cheats/parv3.h Normal file
View File

@ -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

View File

@ -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) {

View File

@ -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 {

View File

@ -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);

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}

View File

@ -14,6 +14,8 @@
#include <errno.h>
#include <fcntl.h>
#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);
}

View File

@ -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);

View File

@ -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) {

View File

@ -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];

260
src/gba/sharkport.c Normal file
View File

@ -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;
}

17
src/gba/sharkport.h Normal file
View File

@ -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

View File

@ -6,6 +6,7 @@
#include "config.h"
#include "util/formatting.h"
#include "util/vfs.h"
#include <sys/stat.h>
@ -13,9 +14,6 @@
#include <windows.h>
#include <shlobj.h>
#include <strsafe.h>
#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);

View File

@ -36,6 +36,9 @@ struct GBAOptions {
bool lockAspectRatio;
bool resampleVideo;
int volume;
bool mute;
bool videoSync;
bool audioSync;

View File

@ -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) {

View File

@ -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);

View File

@ -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;
};

View File

@ -167,6 +167,9 @@ struct GBAVideoRenderer {
uint16_t* palette;
uint16_t* vram;
union GBAOAM* oam;
bool disableBG[4];
bool disableOBJ;
};
struct GBAVideo {

View File

@ -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

View File

@ -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<bool(GBACheatSet*, const char*)> callb
}
m_controller->threadContinue();
m_ui.codeEntry->clear();
}
}

View File

@ -37,9 +37,6 @@
</item>
<item row="8" column="1" colspan="2">
<widget class="QPushButton" name="addPAR">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Add Pro Action Replay</string>
</property>

View File

@ -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;

View File

@ -5,283 +5,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "Display.h"
#include <QApplication>
#include <QResizeEvent>
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();
}
}

View File

@ -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 <QGLWidget>
#include <QThread>
#include <QTimer>
#include <QWidget>
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;
};
}

View File

@ -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 <QApplication>
#include <QResizeEvent>
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();
}
}

View File

@ -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 <QGLWidget>
#include <QThread>
#include <QTimer>
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

View File

@ -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 <QPainter>
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<const QImage&>(m_backing).bits() == reinterpret_cast<const uchar*>(buffer)) {
return;
}
#ifdef COLOR_16_BIT
#ifdef COLOR_5_6_5
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), 256, 256, QImage::Format_RGB16);
#else
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), 256, 256, QImage::Format_RGB555);
#endif
#else
m_backing = QImage(reinterpret_cast<const uchar*>(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
}

View File

@ -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 <QImage>
#include <QTimer>
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

View File

@ -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);

View File

@ -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();

View File

@ -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);

View File

@ -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<int> 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<QPair<int, GamepadAxisEvent::Direction>> InputController::activeGamepadAxes() {
SDL_Joystick* joystick = m_sdlPlayer.joystick;
SDL_JoystickUpdate();
int numButtons = SDL_JoystickNumAxes(joystick);
int numAxes = SDL_JoystickNumAxes(joystick);
QSet<QPair<int, GamepadAxisEvent::Direction>> 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);

View File

@ -10,6 +10,7 @@
#include <QObject>
#include <QSet>
#include <QVector>
class QTimer;
@ -56,6 +57,7 @@ public:
int testSDLEvents();
QSet<int> activeGamepadButtons();
QSet<QPair<int, GamepadAxisEvent::Direction>> 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<int> m_deadzones;
#endif
QSet<int> m_activeButtons;

View File

@ -43,6 +43,12 @@ LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent)
m_slots[i]->installEventFilter(this);
connect(m_slots[i], &QAbstractButton::clicked, this, [this, i]() { triggerState(i + 1); });
}
QAction* escape = new QAction(this);
escape->connect(escape, SIGNAL(triggered()), this, SLOT(close()));
escape->setShortcut(QKeySequence("Esc"));
escape->setShortcutContext(Qt::WidgetWithChildrenShortcut);
addAction(escape);
}
void LoadSaveState::setMode(LoadSave mode) {
@ -80,9 +86,6 @@ bool LoadSaveState::eventFilter(QObject* object, QEvent* event) {
case Qt::Key_9:
triggerState(static_cast<QKeyEvent*>(event)->key() - Qt::Key_1 + 1);
break;
case Qt::Key_Escape:
close();
break;
case Qt::Key_Enter:
case Qt::Key_Return:
triggerState(m_currentFocus + 1);

View File

@ -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 <QFontDatabase>
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')));
}

View File

@ -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 <QWidget>
#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

View File

@ -0,0 +1,321 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PaletteView</class>
<widget class="QWidget" name="PaletteView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>452</width>
<height>349</height>
</rect>
</property>
<property name="windowTitle">
<string>Palette</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Background</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGBA::Swatch" name="bgGrid">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>175</width>
<height>175</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Objects</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGBA::Swatch" name="objGrid">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>175</width>
<height>175</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Selection</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QGBA::Swatch" name="selected">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Red</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Green</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Blue</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="leftMargin">
<number>8</number>
</property>
<item>
<widget class="QLabel" name="r">
<property name="text">
<string>0x00 (00)</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="g">
<property name="text">
<string>0x00 (00)</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="b">
<property name="text">
<string>0x00 (00)</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QLabel" name="label_11">
<property name="text">
<string>16-bit value</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_12">
<property name="text">
<string>Hex code</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_13">
<property name="text">
<string>Palette index</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<property name="leftMargin">
<number>8</number>
</property>
<item>
<widget class="QLabel" name="value">
<property name="text">
<string>0x0000</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="hexcode">
<property name="text">
<string>#000000</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="index">
<property name="text">
<string>000</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QGBA::Swatch</class>
<extends>QLabel</extends>
<header>Swatch.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -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());

View File

@ -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);
};

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>360</width>
<height>569</height>
<width>374</width>
<height>608</height>
</rect>
</property>
<property name="sizePolicy">
@ -146,13 +146,54 @@
</layout>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Volume:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QSlider" name="volume">
<property name="maximum">
<number>256</number>
</property>
<property name="pageStep">
<number>16</number>
</property>
<property name="value">
<number>256</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mute">
<property name="text">
<string>Mute</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="7" column="0" colspan="2">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Sync:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="8" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QCheckBox" name="videoSync">
@ -170,14 +211,14 @@
</item>
</layout>
</item>
<item row="7" column="0">
<item row="9" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Frameskip:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<item row="9" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_16">
<item>
<widget class="QLabel" name="label_12">
@ -198,14 +239,14 @@
</item>
</layout>
</item>
<item row="8" column="0">
<item row="10" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>FPS target:</string>
</property>
</widget>
</item>
<item row="8" column="1">
<item row="10" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QSpinBox" name="fpsTarget">
@ -226,49 +267,42 @@
</item>
</layout>
</item>
<item row="9" column="0" colspan="2">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="10" column="1">
<item row="11" column="1">
<widget class="QCheckBox" name="lockAspectRatio">
<property name="text">
<string>Lock aspect ratio</string>
</property>
</widget>
</item>
<item row="11" column="1">
<item row="12" column="1">
<widget class="QCheckBox" name="resampleVideo">
<property name="text">
<string>Resample video</string>
</property>
</widget>
</item>
<item row="12" column="0" colspan="2">
<item row="13" column="0" colspan="2">
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="13" column="1">
<item row="14" column="1">
<widget class="QCheckBox" name="rewind">
<property name="text">
<string>Enable rewind</string>
</property>
</widget>
</item>
<item row="14" column="0">
<item row="15" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Rewind interval:</string>
</property>
</widget>
</item>
<item row="14" column="1">
<item row="15" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QLabel" name="label_5">
@ -289,14 +323,14 @@
</item>
</layout>
</item>
<item row="15" column="0">
<item row="16" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Rewind length:</string>
</property>
</widget>
</item>
<item row="15" column="1">
<item row="16" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<widget class="QSpinBox" name="rewindCapacity"/>
@ -310,21 +344,28 @@
</item>
</layout>
</item>
<item row="17" column="0" colspan="2">
<item row="17" column="1">
<widget class="QCheckBox" name="allowOpposingDirections">
<property name="text">
<string>Allow opposing input directions</string>
</property>
</widget>
</item>
<item row="18" column="0" colspan="2">
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="18" column="0">
<item row="19" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Idle loops</string>
</property>
</widget>
</item>
<item row="18" column="1">
<item row="19" column="1">
<widget class="QComboBox" name="idleOptimization">
<item>
<property name="text">
@ -343,13 +384,6 @@
</item>
</widget>
</item>
<item row="16" column="1">
<widget class="QCheckBox" name="allowOpposingDirections">
<property name="text">
<string>Allow opposing input directions</string>
</property>
</widget>
</item>
</layout>
</item>
<item>

View File

@ -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<GamepadAxisEvent*>(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;
}

View File

@ -6,6 +6,8 @@
#ifndef QGBA_SHORTCUT_MODEL
#define QGBA_SHORTCUT_MODEL
#include "GamepadAxisEvent.h"
#include <QAbstractItemModel>
#include <QKeySequence>
@ -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<ShortcutItem> 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<QMenu*, ShortcutItem*> m_menuMap;
QMap<int, ShortcutItem*> m_buttons;
QMap<QPair<int, GamepadAxisEvent::Direction>, ShortcutItem*> m_axes;
QMap<QKeySequence, ShortcutItem*> m_heldKeys;
ConfigController* m_config;
};

View File

@ -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<GamepadButtonEvent*>(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<QKeyEvent*>(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<GamepadAxisEvent::Direction>(direction));
}

View File

@ -6,6 +6,8 @@
#ifndef QGBA_SHORTCUT_VIEW
#define QGBA_SHORTCUT_VIEW
#include "GamepadAxisEvent.h"
#include <QWidget>
#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;

View File

@ -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 <QMouseEvent>
#include <QPainter>
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);
}

45
src/platform/qt/Swatch.h Normal file
View File

@ -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 <QColor>
#include <QLabel>
#include <QVector>
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<QColor> m_colors;
QPixmap m_backing;
QSize m_dims;
void updateFill(int index);
};
}
#endif

View File

@ -12,10 +12,12 @@
#include <QMenuBar>
#include <QMessageBox>
#include <QMimeData>
#include <QPainter>
#include <QStackedLayout>
#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<MultiplayerController> multiplayer = m_controller->multiplayerController();
if (multiplayer && multiplayer->attached() > 1) {
title += tr(" - Player %1 of %2").arg(m_playerId + 1).arg(multiplayer->attached());
}
if (m_frameList.isEmpty()) {
setWindowTitle(tr(PROJECT_NAME " - %1").arg(title));
return;
@ -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) {

View File

@ -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;

View File

@ -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})

View File

@ -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);
}

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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
};

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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 <fcntl.h>
#include <dirent.h>
#include <sys/stat.h>
#ifndef _WIN32
#include <sys/mman.h>
#define PATH_SEP '/'
#else
#include <io.h>
#include <windows.h>
#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';
}

View File

@ -8,6 +8,23 @@
#include "util/common.h"
#ifndef _WIN32
#include <sys/mman.h>
#define PATH_SEP "/"
#else
#include <io.h>
#include <windows.h>
#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

191
src/util/vfs/vfs-dirent.c Normal file
View File

@ -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 <dirent.h>
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;
}

159
src/util/vfs/vfs-fd.c Normal file
View File

@ -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 <fcntl.h>
#include <sys/stat.h>
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;
}

View File

@ -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