all repos — mgba @ c784fad04a7c21dfd389c1d82e8e081a347acf51

mGBA Game Boy Advance Emulator

Merge branch 'master' into feature/input-revamp
Vicki Pfau vi@endrift.com
Sat, 03 Jun 2017 17:24:05 -0700
commit

c784fad04a7c21dfd389c1d82e8e081a347acf51

parent

91068d14b9d6e1e028b32aa14a9ae46f99a6a89e

214 files changed, 6988 insertions(+), 2594 deletions(-)

jump to
M CHANGESCHANGES

@@ -12,6 +12,17 @@ - Qt: Spanish translation (by Kevin López)

- Add option for whether rewinding restores save games - Qt: German translation (by Lothar Serra Mari) - Savestates now contain any RTC override data + - Command line ability to override configuration values + - Add option to allow preloading the entire ROM before running + - GB: Video/audio channel enabling/disabling + - Add option to lock video to integer scaling + - Video log recording for testing and bug reporting + - Library view + - Debugger: Segment/bank support + - GB: Symbol table support + - GB MBC: Add MBC1 multicart support + - GBA: Implement keypad interrupts + - LR35902: Watchpoints Bugfixes: - LR35902: Fix core never exiting with certain event patterns - GB Timer: Improve DIV reset behavior

@@ -31,6 +42,28 @@ - Util: Fix highest-fd socket not being returned by SocketAccept

- Qt: Fix linking after some windows have been closed - GBA Video: Fix wrong palette on 256-color sprites in OBJWIN - Windows: Fix VDir.rewind + - SDL: Fix game crash check + - SDL: Fix race condition with audio thread when starting + - GB: Fix flickering when screen is strobed quickly + - FFmpeg: Fix overflow and general issues with audio encoding + - Qt: Fix crash when changing audio settings after a game is closed + - GBA BIOS: Fix ArcTan sign in HLE BIOS + - GBA BIOS: Fix ArcTan2 sign in HLE BIOS (fixes mgba.io/i/689) + - GBA Video: Don't update background scanline params in mode 0 (fixes mgba.io/i/377) + - Qt: Ensure CLI backend is attached when submitting commands (fixes mgba.io/i/662) + - Core: Fix crash with rewind if savestates shrink + - Test: Fix crash when loading invalid file + - GBA Hardware: Fix crash if a savestate lies about game hardware + - Test: Fix crash when fuzzing fails to load a file + - GBA: Fix multiboot loading resulting in too small WRAM + - Test: Don't rely on core for frames elapsed + - Test: Fix crash when loading invalid file + - GBA Hardware: Fix crash if a savestate lies about game hardware + - Test: Fix crash when fuzzing fails to load a file + - Qt: Disable "New multiplayer window" when MAX_GBAS is reached (fixes mgba.io/i/107) + - LR35902: Fix decoding LD r, $imm and 0-valued immediates (fixes mgba.io/i/735) + - GB: Fix STAT blocking + - GB MBC: Fix swapping carts not detect new MBC Misc: - SDL: Remove scancode key input - GBA Video: Clean up unused timers

@@ -77,6 +110,20 @@ - GBA: Ignore invalid opcodes used by the Wii U VC emulator

- Qt: Remove audio thread - Qt: Remove audio buffer sizing in AudioProcessorQt - Qt: Re-enable QtMultimedia on Windows + - FFmpeg: Return false if a file fails to open + - FFmpeg: Force MP4 files to YUV420P + - Qt: Make "Mute" able to be bound to a key + - Core: Restore sleep callback + - Qt: Add .gb/.gbc files to the extension list in Info.plist + - Feature: Make -l option explicit + - Core: Ability to enumerate and modify video and audio channels + - Debugger: Make attaching a backend idempotent + - VFS: Optimize expanding in-memory files + - VFS: Add VFileFIFO for operating on circle buffers + - Core: Move rewind diffing to its own thread + - Util: Tune patch-fast extent sizes + - Qt: Relax hard dependency on OpenGL + - GB Video: Improved video timings 0.5.2: (2016-12-31) Bugfixes:
M CMakeLists.txtCMakeLists.txt

@@ -1,5 +1,5 @@

-cmake_minimum_required(VERSION 2.6) -project(mGBA C) +cmake_minimum_required(VERSION 2.8.11) +project(mGBA) set(BINARY_NAME mgba CACHE INTERNAL "Name of output binaries") if(NOT MSVC) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-missing-field-initializers -std=c99")

@@ -54,11 +54,13 @@ file(GLOB UTIL_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/test/*.c)

file(GLOB GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/gui/*.c ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/gui/*.c) file(GLOB GBA_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/renderers/*.c) file(GLOB GBA_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/sio/lockstep.c) +file(GLOB GBA_EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/extra/*.c) file(GLOB GB_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/sio/lockstep.c) file(GLOB GB_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/renderers/*.c) +file(GLOB GB_EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/extra/*.c) file(GLOB THIRD_PARTY_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/inih/*.c) -set(CLI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/commandline.c) -set(CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-mem.c) +file(GLOB EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/*.c) +set(CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-mem.c ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-fifo.c) set(VFS_SRC) source_group("ARM core" FILES ${ARM_SRC}) source_group("LR35902 core" FILES ${LR35902_SRC})

@@ -301,6 +303,7 @@ set(DISABLE_DEPS ON CACHE BOOL "This platform cannot build with dependencies" FORCE)

endif() set(DISABLE_FRONTENDS ON) set(MINIMAL_CORE ON) + set(ENABLE_EXTRA ON) endif() check_function_exists(chmod HAVE_CHMOD)

@@ -392,6 +395,7 @@ # Features

set(DEBUGGER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/debugger.c ${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/parser.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/symbols.c ${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/cli-debugger.c) set(FEATURE_SRC)

@@ -600,7 +604,9 @@ ${GB_RENDERER_SRC})

list(APPEND DEBUGGER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/lr35902/debugger/cli-debugger.c ${CMAKE_CURRENT_SOURCE_DIR}/src/lr35902/debugger/debugger.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/extra/cli.c) + ${CMAKE_CURRENT_SOURCE_DIR}/src/lr35902/debugger/memory-debugger.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/debugger/cli.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/debugger/symbols.c) list(APPEND TEST_SRC ${LR35902_TEST_SRC} ${GB_TEST_SRC})

@@ -617,7 +623,7 @@ list(APPEND DEBUGGER_SRC

${CMAKE_CURRENT_SOURCE_DIR}/src/arm/debugger/cli-debugger.c ${CMAKE_CURRENT_SOURCE_DIR}/src/arm/debugger/debugger.c ${CMAKE_CURRENT_SOURCE_DIR}/src/arm/debugger/memory-debugger.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/extra/cli.c) + ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/debugger/cli.c) list(APPEND TEST_SRC ${ARM_TEST_SRC} ${GBA_TEST_SRC})

@@ -662,10 +668,9 @@ list(APPEND TEST_SRC ${UTIL_TEST_SRC})

set(SRC ${CORE_SRC} ${VFS_SRC}) if(NOT MINIMAL_CORE) + set(ENABLE_EXTRA ON) if(M_CORE_GBA) list(APPEND SRC - ${GBA_RR_SRC} - ${GBA_EXTRA_SRC} ${GBA_SIO_SRC}) endif() if(M_CORE_GB)

@@ -673,8 +678,21 @@ list(APPEND SRC

${GB_SIO_SRC}) endif() list(APPEND SRC - ${FEATURE_SRC} - ${CLI_SRC}) + ${FEATURE_SRC}) +endif() + +if(ENABLE_EXTRA) + if(M_CORE_GBA) + list(APPEND SRC + ${GBA_RR_SRC} + ${GBA_EXTRA_SRC}) + endif() + if(M_CORE_GB) + list(APPEND SRC + ${GB_EXTRA_SRC}) + endif() + list(APPEND SRC + ${EXTRA_SRC}) endif() if(NOT SKIP_LIBRARY)

@@ -771,7 +789,7 @@ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/qt ${CMAKE_CURRENT_BINARY_DIR}/qt)

endif() if(BUILD_PERF) - set(PERF_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/test/perf-main.c ${CLI_SRC}) + set(PERF_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/test/perf-main.c) if(UNIX AND NOT APPLE) list(APPEND PERF_LIB rt) endif()
M include/mgba-util/circle-buffer.hinclude/mgba-util/circle-buffer.h

@@ -26,6 +26,7 @@ void CircleBufferClear(struct CircleBuffer* buffer);

int CircleBufferWrite8(struct CircleBuffer* buffer, int8_t value); int CircleBufferWrite16(struct CircleBuffer* buffer, int16_t value); int CircleBufferWrite32(struct CircleBuffer* buffer, int32_t value); +size_t CircleBufferWrite(struct CircleBuffer* buffer, const void* input, size_t length); int CircleBufferRead8(struct CircleBuffer* buffer, int8_t* value); int CircleBufferRead16(struct CircleBuffer* buffer, int16_t* value); int CircleBufferRead32(struct CircleBuffer* buffer, int32_t* value);
M include/mgba-util/common.hinclude/mgba-util/common.h

@@ -56,6 +56,11 @@ #include <unistd.h>

#include <sys/time.h> #endif +#ifdef PSP2 +// For PATH_MAX on modern toolchains +#include <sys/syslimits.h> +#endif + #ifndef SSIZE_MAX #define SSIZE_MAX ((ssize_t) (SIZE_MAX >> 1)) #endif
M include/mgba-util/patch/fast.hinclude/mgba-util/patch/fast.h

@@ -13,7 +13,7 @@

#include <mgba-util/patch.h> #include <mgba-util/vector.h> -#define PATCH_FAST_EXTENT 256 +#define PATCH_FAST_EXTENT 128 struct PatchFastExtent { size_t length;
M include/mgba-util/platform/posix/threading.hinclude/mgba-util/platform/posix/threading.h

@@ -14,6 +14,8 @@ #include <pthread.h>

#include <sys/time.h> #if defined(__FreeBSD__) || defined(__OpenBSD__) #include <pthread_np.h> +#elif defined(__HAIKU__) +#include <OS.h> #endif #define THREAD_ENTRY void*

@@ -87,6 +89,9 @@ #ifdef __APPLE__

return pthread_setname_np(name); #elif defined(__FreeBSD__) || defined(__OpenBSD__) pthread_set_name_np(pthread_self(), name); + return 0; +#elif defined(__HAIKU__) + rename_thread(find_thread(NULL), name); return 0; #elif !defined(BUILD_PANDORA) // Pandora's glibc is too old return pthread_setname_np(pthread_self(), name);
M include/mgba-util/vector.hinclude/mgba-util/vector.h

@@ -92,6 +92,8 @@ memcpy(dest->vector, src->vector, src->size * sizeof(TYPE)); \

dest->size = src->size; \ } \ +DECLARE_VECTOR(StringList, char*); + CXX_GUARD_END #endif
M include/mgba-util/vfs.hinclude/mgba-util/vfs.h

@@ -73,6 +73,9 @@ struct VFile* VFileFromMemory(void* mem, size_t size);

struct VFile* VFileFromConstMemory(const void* mem, size_t size); struct VFile* VFileMemChunk(const void* mem, size_t size); +struct CircleBuffer; +struct VFile* VFileFIFO(struct CircleBuffer* backing); + struct VDir* VDirOpen(const char* path); struct VDir* VDirOpenArchive(const char* path);
M include/mgba/core/cheats.hinclude/mgba/core/cheats.h

@@ -48,7 +48,6 @@

mLOG_DECLARE_CATEGORY(CHEATS); DECLARE_VECTOR(mCheatList, struct mCheat); -DECLARE_VECTOR(StringList, char*); struct mCheatDevice; struct mCheatSet {
M include/mgba/core/config.hinclude/mgba/core/config.h

@@ -42,6 +42,7 @@ int fullscreen;

int width; int height; bool lockAspectRatio; + bool lockIntegerScaling; bool resampleVideo; bool suspendScreensaver; char* shader;
M include/mgba/core/core.hinclude/mgba/core/core.h

@@ -26,10 +26,10 @@

enum mPlatform { PLATFORM_NONE = -1, #ifdef M_CORE_GBA - PLATFORM_GBA, + PLATFORM_GBA = 0, #endif #ifdef M_CORE_GB - PLATFORM_GB, + PLATFORM_GB = 1, #endif };

@@ -39,11 +39,14 @@ };

struct mCoreConfig; struct mCoreSync; +struct mDebuggerSymbols; struct mStateExtdata; +struct mVideoLogContext; struct mCore { void* cpu; void* board; struct mDebugger* debugger; + struct mDebuggerSymbols* symbolTable; #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 struct mDirectorySet dirs;

@@ -111,8 +114,7 @@

void (*getGameTitle)(const struct mCore*, char* title); void (*getGameCode)(const struct mCore*, char* title); - void (*setRotation)(struct mCore*, struct mRotationSource*); - void (*setRumble)(struct mCore*, struct mRumble*); + void (*setPeripheral)(struct mCore*, int type, void*); uint32_t (*busRead8)(struct mCore*, uint32_t address); uint32_t (*busRead16)(struct mCore*, uint32_t address);

@@ -136,17 +138,32 @@ struct mDebuggerPlatform* (*debuggerPlatform)(struct mCore*);

struct CLIDebuggerSystem* (*cliDebuggerSystem)(struct mCore*); void (*attachDebugger)(struct mCore*, struct mDebugger*); void (*detachDebugger)(struct mCore*); + + void (*loadSymbols)(struct mCore*, struct VFile*); #endif struct mCheatDevice* (*cheatDevice)(struct mCore*); size_t (*savedataClone)(struct mCore*, void** sram); bool (*savedataRestore)(struct mCore*, const void* sram, size_t size, bool writeback); + + size_t (*listVideoLayers)(const struct mCore*, const struct mCoreChannelInfo**); + size_t (*listAudioChannels)(const struct mCore*, const struct mCoreChannelInfo**); + void (*enableVideoLayer)(struct mCore*, size_t id, bool enable); + void (*enableAudioChannel)(struct mCore*, size_t id, bool enable); + +#ifndef MINIMAL_CORE + void (*startVideoLog)(struct mCore*, struct mVideoLogContext*); + void (*endVideoLog)(struct mCore*); +#endif }; #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 struct mCore* mCoreFind(const char* path); bool mCoreLoadFile(struct mCore* core, const char* path); + +bool mCorePreloadVF(struct mCore* core, struct VFile* vf); +bool mCorePreloadFile(struct mCore* core, const char* path); bool mCoreAutoloadSave(struct mCore* core); bool mCoreAutoloadPatch(struct mCore* core);
M include/mgba/core/interface.hinclude/mgba/core/interface.h

@@ -38,6 +38,7 @@ void* context;

void (*videoFrameStarted)(void* context); void (*videoFrameEnded)(void* context); void (*coreCrashed)(void* context); + void (*sleep)(void* context); }; DECLARE_VECTOR(mCoreCallbacksList, struct mCoreCallbacks);

@@ -53,8 +54,10 @@ struct mKeyCallback {

uint16_t (*readKeys)(struct mKeyCallback*); }; -struct mStopCallback { - void (*stop)(struct mStopCallback*); +enum mPeripheral { + mPERIPH_ROTATION = 1, + mPERIPH_RUMBLE, + mPERIPH_CUSTOM = 0x1000 }; struct mRotationSource {

@@ -100,6 +103,13 @@ void mRTCGenericSourceInit(struct mRTCGenericSource* rtc, struct mCore* core);

struct mRumble { void (*setRumble)(struct mRumble*, int enable); +}; + +struct mCoreChannelInfo { + size_t id; + const char* internalName; + const char* visibleName; + const char* visibleType; }; CXX_GUARD_END
M include/mgba/core/rewind.hinclude/mgba/core/rewind.h

@@ -11,6 +11,9 @@

CXX_GUARD_START #include <mgba-util/vector.h> +#ifndef DISABLE_THREADING +#include <mgba-util/threading.h> +#endif DECLARE_VECTOR(mCoreRewindPatches, struct PatchFast);

@@ -22,9 +25,16 @@ size_t size;

int stateFlags; struct VFile* previousState; struct VFile* currentState; + +#ifndef DISABLE_THREADING + bool onThread; + Thread thread; + Condition cond; + Mutex mutex; +#endif }; -void mCoreRewindContextInit(struct mCoreRewindContext*, size_t entries); +void mCoreRewindContextInit(struct mCoreRewindContext*, size_t entries, bool onThread); void mCoreRewindContextDeinit(struct mCoreRewindContext*); struct mCore;
M include/mgba/core/thread.hinclude/mgba/core/thread.h

@@ -62,6 +62,7 @@ ThreadCallback startCallback;

ThreadCallback resetCallback; ThreadCallback cleanCallback; ThreadCallback frameCallback; + ThreadCallback sleepCallback; void* userData; void (*run)(struct mCoreThread*);
A include/mgba/feature/thread-proxy.h

@@ -0,0 +1,40 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef VIDEO_THREAD_PROXY_H +#define VIDEO_THREAD_PROXY_H + +#include <mgba-util/common.h> + +CXX_GUARD_START + +#include <mgba/internal/gba/video.h> +#include "video-logger.h" +#include <mgba-util/threading.h> +#include <mgba-util/ring-fifo.h> + +enum mVideoThreadProxyState { + PROXY_THREAD_STOPPED = 0, + PROXY_THREAD_IDLE, + PROXY_THREAD_BUSY +}; + +struct mVideoThreadProxy { + struct mVideoLogger d; + + Thread thread; + Condition fromThreadCond; + Condition toThreadCond; + Mutex mutex; + enum mVideoThreadProxyState threadState; + + struct RingFIFO dirtyQueue; +}; + +void mVideoThreadProxyCreate(struct mVideoThreadProxy* renderer); + +CXX_GUARD_END + +#endif
A include/mgba/feature/video-logger.h

@@ -0,0 +1,109 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef VIDEO_LOGGER_H +#define VIDEO_LOGGER_H + +#include <mgba-util/common.h> + +CXX_GUARD_START + +#include <mgba-util/circle-buffer.h> + +#define mVL_MAX_CHANNELS 32 + +enum mVideoLoggerDirtyType { + DIRTY_DUMMY = 0, + DIRTY_FLUSH, + DIRTY_SCANLINE, + DIRTY_REGISTER, + DIRTY_OAM, + DIRTY_PALETTE, + DIRTY_VRAM, + DIRTY_FRAME, + DIRTY_RANGE, + DIRTY_BUFFER, +}; + +struct mVideoLoggerDirtyInfo { + enum mVideoLoggerDirtyType type; + uint32_t address; + uint32_t value; + uint32_t value2; +}; + +struct VFile; +struct mVideoLogger { + bool (*writeData)(struct mVideoLogger* logger, const void* data, size_t length); + bool (*readData)(struct mVideoLogger* logger, void* data, size_t length, bool block); + void* dataContext; + + bool block; + void (*init)(struct mVideoLogger*); + void (*deinit)(struct mVideoLogger*); + void (*reset)(struct mVideoLogger*); + + void (*lock)(struct mVideoLogger*); + void (*unlock)(struct mVideoLogger*); + void (*wait)(struct mVideoLogger*); + void (*wake)(struct mVideoLogger*, int y); + void* context; + + bool (*parsePacket)(struct mVideoLogger* logger, const struct mVideoLoggerDirtyInfo* packet); + uint16_t* (*vramBlock)(struct mVideoLogger* logger, uint32_t address); + + size_t vramSize; + size_t oamSize; + size_t paletteSize; + + uint32_t* vramDirtyBitmap; + uint32_t* oamDirtyBitmap; + + uint16_t* vram; + uint16_t* oam; + uint16_t* palette; +}; + +void mVideoLoggerRendererCreate(struct mVideoLogger* logger, bool readonly); +void mVideoLoggerRendererInit(struct mVideoLogger* logger); +void mVideoLoggerRendererDeinit(struct mVideoLogger* logger); +void mVideoLoggerRendererReset(struct mVideoLogger* logger); + +void mVideoLoggerRendererWriteVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value); +void mVideoLoggerRendererWriteVRAM(struct mVideoLogger* logger, uint32_t address); +void mVideoLoggerRendererWritePalette(struct mVideoLogger* logger, uint32_t address, uint16_t value); +void mVideoLoggerRendererWriteOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value); + +void mVideoLoggerWriteBuffer(struct mVideoLogger* logger, uint32_t bufferId, uint32_t offset, uint32_t length, const void* data); + +void mVideoLoggerRendererDrawScanline(struct mVideoLogger* logger, int y); +void mVideoLoggerRendererDrawRange(struct mVideoLogger* logger, int startX, int endX, int y); +void mVideoLoggerRendererFlush(struct mVideoLogger* logger); +void mVideoLoggerRendererFinishFrame(struct mVideoLogger* logger); + +bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block); + +struct mVideoLogContext; +void mVideoLoggerAttachChannel(struct mVideoLogger* logger, struct mVideoLogContext* context, size_t channelId); + +struct mCore; +struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core); + +void mVideoLogContextSetOutput(struct mVideoLogContext*, struct VFile*); +void mVideoLogContextWriteHeader(struct mVideoLogContext*, struct mCore* core); + +bool mVideoLogContextLoad(struct mVideoLogContext*, struct VFile*); +void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext*); + +void mVideoLogContextRewind(struct mVideoLogContext*, struct mCore*); +void* mVideoLogContextInitialState(struct mVideoLogContext*, size_t* size); + +int mVideoLoggerAddChannel(struct mVideoLogContext*); + +struct mCore* mVideoLogCoreFind(struct VFile*); + +CXX_GUARD_END + +#endif
M include/mgba/gb/core.hinclude/mgba/gb/core.h

@@ -12,6 +12,9 @@ CXX_GUARD_START

struct mCore; struct mCore* GBCoreCreate(void); +#ifndef MINIMAL_CORE +struct mCore* GBVideoLogPlayerCreate(void); +#endif CXX_GUARD_END
M include/mgba/gb/interface.hinclude/mgba/gb/interface.h

@@ -34,6 +34,15 @@ GB_MBC3_RTC = 0x103,

GB_MBC5_RUMBLE = 0x105 }; +struct GBSIODriver { + struct GBSIO* p; + + bool (*init)(struct GBSIODriver* driver); + void (*deinit)(struct GBSIODriver* driver); + void (*writeSB)(struct GBSIODriver* driver, uint8_t value); + uint8_t (*writeSC)(struct GBSIODriver* driver, uint8_t value); +}; + CXX_GUARD_END #endif
M include/mgba/gba/core.hinclude/mgba/gba/core.h

@@ -12,6 +12,9 @@ CXX_GUARD_START

struct mCore; struct mCore* GBACoreCreate(void); +#ifndef MINIMAL_CORE +struct mCore* GBAVideoLogPlayerCreate(void); +#endif CXX_GUARD_END
M include/mgba/gba/interface.hinclude/mgba/gba/interface.h

@@ -28,6 +28,10 @@ struct GBAVideoRenderer;

extern const int GBA_LUX_LEVELS[10]; +enum { + mPERIPH_GBA_LUMINANCE = 0x1000 +}; + struct GBALuminanceSource { void (*sample)(struct GBALuminanceSource*);
M include/mgba/internal/debugger/debugger.hinclude/mgba/internal/debugger/debugger.h

@@ -87,13 +87,14 @@ void (*deinit)(struct mDebuggerPlatform*);

void (*entered)(struct mDebuggerPlatform*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*); bool (*hasBreakpoints)(struct mDebuggerPlatform*); - void (*setBreakpoint)(struct mDebuggerPlatform*, uint32_t address); - void (*clearBreakpoint)(struct mDebuggerPlatform*, uint32_t address); - void (*setWatchpoint)(struct mDebuggerPlatform*, uint32_t address, enum mWatchpointType type); - void (*clearWatchpoint)(struct mDebuggerPlatform*, uint32_t address); + void (*setBreakpoint)(struct mDebuggerPlatform*, uint32_t address, int segment); + void (*clearBreakpoint)(struct mDebuggerPlatform*, uint32_t address, int segment); + void (*setWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); + void (*clearWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment); void (*checkBreakpoints)(struct mDebuggerPlatform*); }; +struct mDebuggerSymbols; struct mDebugger { struct mCPUComponent d; struct mDebuggerPlatform* platform;
A include/mgba/internal/debugger/symbols.h

@@ -0,0 +1,25 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef DEBUGGER_SYMBOLS_H +#define DEBUGGER_SYMBOLS_H + +#include <mgba-util/common.h> + +CXX_GUARD_START + +struct mDebuggerSymbols; + +struct mDebuggerSymbols* mDebuggerSymbolTableCreate(void); +void mDebuggerSymbolTableDestroy(struct mDebuggerSymbols*); + +bool mDebuggerSymbolLookup(const struct mDebuggerSymbols*, const char* name, int32_t* value, int* segment); + +void mDebuggerSymbolAdd(struct mDebuggerSymbols*, const char* name, int32_t value, int segment); +void mDebuggerSymbolRemove(struct mDebuggerSymbols*, const char* name); + +CXX_GUARD_END + +#endif
A include/mgba/internal/gb/debugger/symbols.h

@@ -0,0 +1,19 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GB_SYMBOLS_H +#define GB_SYMBOLS_H + +#include <mgba-util/common.h> + +CXX_GUARD_START + +struct mDebuggerSymbols; +struct VFile; +void GBLoadSymbols(struct mDebuggerSymbols*, struct VFile* vf); + +CXX_GUARD_END + +#endif
M include/mgba/internal/gb/memory.hinclude/mgba/internal/gb/memory.h

@@ -85,6 +85,7 @@ };

struct GBMBC1State { int mode; + int multicartStride; }; struct GBMBC7State {

@@ -161,13 +162,12 @@

uint8_t GBLoad8(struct LR35902Core* cpu, uint16_t address); void GBStore8(struct LR35902Core* cpu, uint16_t address, int8_t value); +int GBCurrentSegment(struct LR35902Core* cpu, uint16_t address); + uint8_t GBView8(struct LR35902Core* cpu, uint16_t address, int segment); void GBMemoryDMA(struct GB* gb, uint16_t base); void GBMemoryWriteHDMA5(struct GB* gb, uint8_t value); - -uint8_t GBDMALoad8(struct LR35902Core* cpu, uint16_t address); -void GBDMAStore8(struct LR35902Core* cpu, uint16_t address, int8_t value); void GBPatch8(struct LR35902Core* cpu, uint16_t address, int8_t value, int8_t* old, int segment);
A include/mgba/internal/gb/renderers/proxy.h

@@ -0,0 +1,31 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GB_VIDEO_PROXY_H +#define GB_VIDEO_PROXY_H + +#include <mgba-util/common.h> + +CXX_GUARD_START + +#include <mgba/internal/gb/video.h> +#include <mgba/feature/video-logger.h> + +struct GBVideoProxyRenderer { + struct GBVideoRenderer d; + struct GBVideoRenderer* backend; + struct mVideoLogger* logger; + + struct GBObj objThisLine[40]; + size_t oamMax; +}; + +void GBVideoProxyRendererCreate(struct GBVideoProxyRenderer* renderer, struct GBVideoRenderer* backend); +void GBVideoProxyRendererShim(struct GBVideo* video, struct GBVideoProxyRenderer* renderer); +void GBVideoProxyRendererUnshim(struct GBVideo* video, struct GBVideoProxyRenderer* renderer); + +CXX_GUARD_END + +#endif
M include/mgba/internal/gb/sio.hinclude/mgba/internal/gb/sio.h

@@ -12,6 +12,7 @@ CXX_GUARD_START

#include <mgba/core/log.h> #include <mgba/core/timing.h> +#include <mgba/gb/interface.h> #define MAX_GBS 2

@@ -32,15 +33,6 @@ int32_t period;

int remainingBits; uint8_t pendingSB; -}; - -struct GBSIODriver { - struct GBSIO* p; - - bool (*init)(struct GBSIODriver* driver); - void (*deinit)(struct GBSIODriver* driver); - void (*writeSB)(struct GBSIODriver* driver, uint8_t value); - uint8_t (*writeSC)(struct GBSIODriver* driver, uint8_t value); }; DECL_BITFIELD(GBRegisterSC, uint8_t);
M include/mgba/internal/gb/video.hinclude/mgba/internal/gb/video.h

@@ -20,9 +20,9 @@ GB_VIDEO_VBLANK_PIXELS = 10,

GB_VIDEO_VERTICAL_TOTAL_PIXELS = 154, // TODO: Figure out exact lengths - GB_VIDEO_MODE_2_LENGTH = 76, - GB_VIDEO_MODE_3_LENGTH_BASE = 171, - GB_VIDEO_MODE_0_LENGTH_BASE = 209, + GB_VIDEO_MODE_2_LENGTH = 80, + GB_VIDEO_MODE_3_LENGTH_BASE = 172, + GB_VIDEO_MODE_0_LENGTH_BASE = 204, GB_VIDEO_HORIZONTAL_LENGTH = 456,

@@ -61,6 +61,7 @@

uint8_t (*writeVideoRegister)(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value); void (*writeVRAM)(struct GBVideoRenderer* renderer, uint16_t address); void (*writePalette)(struct GBVideoRenderer* renderer, int index, uint16_t value); + void (*writeOAM)(struct GBVideoRenderer* renderer, uint16_t oam); void (*drawRange)(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj* objOnLine, size_t nObj); void (*finishScanline)(struct GBVideoRenderer* renderer, int y); void (*finishFrame)(struct GBVideoRenderer* renderer);

@@ -71,6 +72,10 @@

uint8_t* vram; union GBOAM* oam; struct mTileCache* cache; + + bool disableBG; + bool disableOBJ; + bool disableWIN; }; DECL_BITFIELD(GBRegisterLCDC, uint8_t);

@@ -119,6 +124,7 @@ bool bcpIncrement;

int ocpIndex; bool ocpIncrement; + uint16_t dmgPalette[4]; uint16_t palette[64]; int32_t frameCounter;

@@ -137,6 +143,8 @@ void GBVideoWriteSTAT(struct GBVideo* video, GBRegisterSTAT value);

void GBVideoWriteLYC(struct GBVideo* video, uint8_t value); void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value); void GBVideoSwitchBank(struct GBVideo* video, uint8_t value); + +void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint16_t color); struct GBSerializedState; void GBVideoSerialize(const struct GBVideo* video, struct GBSerializedState* state);
M include/mgba/internal/gba/gba.hinclude/mgba/internal/gba/gba.h

@@ -99,7 +99,6 @@ struct VFile* biosVf;

struct mAVStream* stream; struct mKeyCallback* keyCallback; - struct mStopCallback* stopCallback; struct mCoreCallbacksList coreCallbacks; enum GBAIdleLoopOptimization idleOptimization;

@@ -175,6 +174,8 @@ bool GBAIsMB(struct VFile* vf);

bool GBAIsBIOS(struct VFile* vf); void GBAGetGameCode(const struct GBA* gba, char* out); void GBAGetGameTitle(const struct GBA* gba, char* out); + +void GBATestKeypadIRQ(struct GBA* gba); void GBAFrameStarted(struct GBA* gba); void GBAFrameEnded(struct GBA* gba);
A include/mgba/internal/gba/renderers/proxy.h

@@ -0,0 +1,28 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GBA_VIDEO_PROXY_H +#define GBA_VIDEO_PROXY_H + +#include <mgba-util/common.h> + +CXX_GUARD_START + +#include <mgba/internal/gba/video.h> +#include <mgba/feature/video-logger.h> + +struct GBAVideoProxyRenderer { + struct GBAVideoRenderer d; + struct GBAVideoRenderer* backend; + struct mVideoLogger* logger; +}; + +void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct GBAVideoRenderer* backend); +void GBAVideoProxyRendererShim(struct GBAVideo* video, struct GBAVideoProxyRenderer* renderer); +void GBAVideoProxyRendererUnshim(struct GBAVideo* video, struct GBAVideoProxyRenderer* renderer); + +CXX_GUARD_END + +#endif
D include/mgba/internal/gba/renderers/thread-proxy.h

@@ -1,47 +0,0 @@

-/* 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 VIDEO_THREAD_PROXY_H -#define VIDEO_THREAD_PROXY_H - -#include <mgba-util/common.h> - -CXX_GUARD_START - -#include <mgba/internal/gba/video.h> -#include <mgba-util/threading.h> -#include <mgba-util/ring-fifo.h> - -enum GBAVideoThreadProxyState { - PROXY_THREAD_STOPPED = 0, - PROXY_THREAD_IDLE, - PROXY_THREAD_BUSY -}; - -struct GBAVideoThreadProxyRenderer { - struct GBAVideoRenderer d; - struct GBAVideoRenderer* backend; - - Thread thread; - Condition fromThreadCond; - Condition toThreadCond; - Mutex mutex; - enum GBAVideoThreadProxyState threadState; - - struct RingFIFO dirtyQueue; - - uint32_t vramDirtyBitmap; - uint32_t oamDirtyBitmap[16]; - - uint16_t* vramProxy; - union GBAOAM oamProxy; - uint16_t paletteProxy[512]; -}; - -void GBAVideoThreadProxyRendererCreate(struct GBAVideoThreadProxyRenderer* renderer, struct GBAVideoRenderer* backend); - -CXX_GUARD_END - -#endif
M include/mgba/internal/gba/serialize.hinclude/mgba/internal/gba/serialize.h

@@ -334,9 +334,6 @@

void GBASerialize(struct GBA* gba, struct GBASerializedState* state); bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state); -struct GBASerializedState* GBAAllocateState(void); -void GBADeallocateState(struct GBASerializedState* state); - CXX_GUARD_END #endif
M include/mgba/internal/lr35902/debugger/debugger.hinclude/mgba/internal/lr35902/debugger/debugger.h

@@ -12,6 +12,9 @@ CXX_GUARD_START

#include <mgba/internal/debugger/debugger.h> +#include <mgba/internal/lr35902/lr35902.h> + + struct LR35902DebugBreakpoint { uint16_t address; int segment;

@@ -23,6 +26,12 @@ int segment;

enum mWatchpointType type; }; +struct LR35902Segment { + uint16_t start; + uint16_t end; + const char* name; +}; + DECLARE_VECTOR(LR35902DebugBreakpointList, struct LR35902DebugBreakpoint); DECLARE_VECTOR(LR35902DebugWatchpointList, struct LR35902DebugWatchpoint);

@@ -32,6 +41,9 @@ struct LR35902Core* cpu;

struct LR35902DebugBreakpointList breakpoints; struct LR35902DebugWatchpointList watchpoints; + struct LR35902Memory originalMemory; + + const struct LR35902Segment* segments; }; struct mDebuggerPlatform* LR35902DebuggerPlatformCreate(void);
A include/mgba/internal/lr35902/debugger/memory-debugger.h

@@ -0,0 +1,20 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef LR35902_MEMORY_DEBUGGER_H +#define LR35902_MEMORY_DEBUGGER_H + +#include <mgba-util/common.h> + +CXX_GUARD_START + +struct LR35902Debugger; + +void LR35902DebuggerInstallMemoryShim(struct LR35902Debugger* debugger); +void LR35902DebuggerRemoveMemoryShim(struct LR35902Debugger* debugger); + +CXX_GUARD_END + +#endif
M include/mgba/internal/lr35902/lr35902.hinclude/mgba/internal/lr35902/lr35902.h

@@ -54,6 +54,8 @@ uint8_t (*cpuLoad8)(struct LR35902Core*, uint16_t address);

uint8_t (*load8)(struct LR35902Core*, uint16_t address); void (*store8)(struct LR35902Core*, uint16_t address, int8_t value); + int (*currentSegment)(struct LR35902Core*, uint16_t address); + uint8_t* activeRegion; uint16_t activeMask; uint16_t activeRegionEnd;
M res/info.plist.inres/info.plist.in

@@ -44,6 +44,17 @@ <string>Game Boy Advance ROM Image</string>

<key>CFBundleTypeRole</key> <string>Viewer</string> </dict> + <dict> + <key>CFBundleTypeExtensions</key> + <array> + <string>gb</string> + <string>gbc</string> + </array> + <key>CFBundleTypeName</key> + <string>Game Boy ROM Image</string> + <key>CFBundleTypeRole</key> + <string>Viewer</string> + </dict> </array> </dict> </plist>
M res/patrons.txtres/patrons.txt

@@ -1,7 +1,9 @@

-Trey Boyer -Christopher Cole +Jaime J. Denizard +Fog +Reilly Grant Philip Horton Jordan Jorgensen -Joshua Minor Rohit Nirmal +Rhys Powell +rootfather Yuri Kunde Schlesner
A res/shaders/fish.shader/fish.fs

@@ -0,0 +1,181 @@

+/* + fish shader + + algorithm and original implementation by Miloslav "drummyfish" Ciz + (tastyfish@seznam.cz) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +precision highp float; + +varying vec2 texCoord; +uniform sampler2D tex; +uniform vec2 texSize; + +uniform float similarity_threshold; + +#define screen_res 240,160 + +vec4 texel_fetch(sampler2D t, ivec2 c) // because GLSL TexelFetch is not supported + { + return texture2D(tex, (2 * vec2(c) + vec2(1,1)) / (2 * vec2(screen_res)) ); + } + +float pixel_brightness(vec4 pixel) + { + return 0.21 * pixel.x + 0.72 * pixel.y + 0.07 * pixel.z; + } + +bool pixel_is_brighter(vec4 pixel1, vec4 pixel2) + { + return pixel_brightness(pixel1) > pixel_brightness(pixel2); + } + +vec3 pixel_to_yuv(vec4 pixel) + { + float y = 0.299 * pixel.x + 0.587 * pixel.y + 0.114 * pixel.z; + return vec3(y, 0.492 * (pixel.z - y), 0.877 * (pixel.x - y)); + } + +bool yuvs_are_similar(vec3 yuv1, vec3 yuv2) + { + vec3 yuv_difference = abs(yuv1 - yuv2); + return yuv_difference.x <= similarity_threshold && yuv_difference.y <= similarity_threshold && yuv_difference.z <= similarity_threshold; + } + +bool pixels_are_similar(vec4 pixel1, vec4 pixel2) + { + vec3 yuv1 = pixel_to_yuv(pixel1); + vec3 yuv2 = pixel_to_yuv(pixel2); + + return yuvs_are_similar(yuv1, yuv2); + } + +vec4 interpolate_nondiagonal(vec4 neighbour1, vec4 neighbour2) + { + if (pixels_are_similar(neighbour1,neighbour2)) + return mix(neighbour1,neighbour2,0.5); + else + return pixel_is_brighter(neighbour1, neighbour2) ? neighbour1 : neighbour2; + } + +vec4 mix3(vec4 value1, vec4 value2, vec4 value3) + { + return (value1 + value2 + value3) / 3.0; + } + +vec4 straight_line(vec4 p0, vec4 p1, vec4 p2, vec4 p3) + { + return pixel_is_brighter(p2,p0) ? mix(p2,p3,0.5) : mix(p0,p1,0.5); + } + +vec4 corner(vec4 p0, vec4 p1, vec4 p2, vec4 p3) + { + return pixel_is_brighter(p1,p0) ? mix3(p1,p2,p3) : mix3(p0,p1,p2); + } + +vec4 interpolate_diagonal(vec4 a, vec4 b, vec4 c, vec4 d) + { + // a b + // c d + + vec3 a_yuv = pixel_to_yuv(a); + vec3 b_yuv = pixel_to_yuv(b); + vec3 c_yuv = pixel_to_yuv(c); + vec3 d_yuv = pixel_to_yuv(d); + + bool ad = yuvs_are_similar(a_yuv,d_yuv); + bool bc = yuvs_are_similar(b_yuv,c_yuv); + bool ab = yuvs_are_similar(a_yuv,b_yuv); + bool cd = yuvs_are_similar(c_yuv,d_yuv); + bool ac = yuvs_are_similar(a_yuv,c_yuv); + bool bd = yuvs_are_similar(b_yuv,d_yuv); + + if (ad && cd && ab) // all pixels are equal? + return( mix(mix(a,b,0.5), mix(c,d,0.5), 0.5) ); + + else if (ac && cd && ! ab) // corner 1? + return corner(b,a,d,c); + else if (bd && cd && ! ab) // corner 2? + return corner(a,b,c,d); + else if (ac && ab && ! bd) // corner 3? + return corner(d,c,b,a); + else if (ab && bd && ! ac) // corner 4? + return corner(c,a,d,b); + + else if (ad && (!bc || pixel_is_brighter(b,a))) // diagonal line 1? + return mix(a,d,0.5); + else if (bc && (!ad || pixel_is_brighter(a,b))) // diagonal line 2? + return mix(b,c,0.5); + + else if (ab) // horizontal line 1? + return straight_line(a,b,c,d); + else if (cd) // horizontal line 2? + return straight_line(c,d,a,b); + + else if (ac) // vertical line 1? + return straight_line(a,c,b,d); + else if (bd) // vertical line 2? + return straight_line(b,d,a,c); + + return( mix(mix(a,b,0.5), mix(c,d,0.5), 0.5) ); + } + +void main() + { + ivec2 pixel_coords2 = ivec2(texCoord * vec2(screen_res) * 2); + ivec2 pixel_coords = pixel_coords2 / 2; + + bool x_even = mod(pixel_coords2.x,2) == 0; + bool y_even = mod(pixel_coords2.y,2) == 0; + + if (x_even) + { + if (y_even) + { + + gl_FragColor = interpolate_diagonal( + texel_fetch(tex, pixel_coords + ivec2(-1,-1)), + texel_fetch(tex, pixel_coords + ivec2(0,-1)), + texel_fetch(tex, pixel_coords + ivec2(-1,0)), + texel_fetch(tex, pixel_coords + ivec2(0,0)) + ); + + } + else + { + gl_FragColor = interpolate_nondiagonal + ( + texel_fetch(tex, pixel_coords + ivec2(-1,0)), + texel_fetch(tex, pixel_coords) + ); + } + } + else if (y_even) + { + gl_FragColor = interpolate_nondiagonal + ( + texel_fetch(tex, pixel_coords + ivec2(0,-1)), + texel_fetch(tex, pixel_coords) + ); + } + else + gl_FragColor = texel_fetch(tex, pixel_coords); + }
A res/shaders/fish.shader/manifest.ini

@@ -0,0 +1,16 @@

+[shader] +name=fish +author=Drummyfish +description=Attempts to keep thin lines thin. +passes=1 + +[pass.0] +fragmentShader=fish.fs +integerScaling=1 + +[pass.0.uniform.similarity_threshold] +type=float +default=0.2 +readableName=Similarity Threshold +min=0 +max=1
A res/shaders/gba-color.shader/gba-color.fs

@@ -0,0 +1,34 @@

+varying vec2 texCoord; +uniform sampler2D tex; +uniform vec2 texSize; + +uniform float darken_screen; +const float target_gamma = 2.2; +const float display_gamma = 2.5; +const float sat = 1.0; +const float lum = 0.99; +const float contrast = 1.0; +const vec3 bl = vec3(0.0, 0.0, 0.0); +const vec3 r = vec3(0.84, 0.09, 0.15); +const vec3 g = vec3(0.18, 0.67, 0.10); +const vec3 b = vec3(0.0, 0.26, 0.73); + +void main() { + vec4 screen = pow(texture2D(tex, texCoord), vec4(target_gamma + darken_screen)).rgba; + vec4 avglum = vec4(0.5); + screen = mix(screen, avglum, (1.0 - contrast)); + + mat4 color = mat4( r.r, r.g, r.b, 0.0, + g.r, g.g, g.b, 0.0, + b.r, b.g, b.b, 0.0, + bl.r, bl.g, bl.b, 1.0); + + mat4 adjust = mat4( (1.0 - sat) * 0.3086 + sat, (1.0 - sat) * 0.3086, (1.0 - sat) * 0.3086, 1.0, + (1.0 - sat) * 0.6094, (1.0 - sat) * 0.6094 + sat, (1.0 - sat) * 0.6094, 1.0, + (1.0 - sat) * 0.0820, (1.0 - sat) * 0.0820, (1.0 - sat) * 0.0820 + sat, 1.0, + 0.0, 0.0, 0.0, 1.0); + color *= adjust; + screen = clamp(screen * lum, 0.0, 1.0); + screen = color * screen; + gl_FragColor = pow(screen, vec4(1.0 / display_gamma + (darken_screen * 0.125))); +}
A res/shaders/gba-color.shader/manifest.ini

@@ -0,0 +1,14 @@

+[shader] +name=GBA Color +author=Pokefan531 and hunterk +description=Modifies the color output to simulate the GBA LCD characteristics. +passes=1 + +[pass.0] +fragmentShader=gba-color.fs +blend=1 + +[pass.0.uniform.darken_screen] +type=float +default=0.5 +readableName=Darken Screen
M src/arm/debugger/debugger.csrc/arm/debugger/debugger.c

@@ -48,10 +48,10 @@ static void ARMDebuggerDeinit(struct mDebuggerPlatform* platform);

static void ARMDebuggerEnter(struct mDebuggerPlatform* d, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info); -static void ARMDebuggerSetBreakpoint(struct mDebuggerPlatform*, uint32_t address); -static void ARMDebuggerClearBreakpoint(struct mDebuggerPlatform*, uint32_t address); -static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t address, enum mWatchpointType type); -static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform*, uint32_t address); +static void ARMDebuggerSetBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); +static void ARMDebuggerClearBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); +static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); +static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment); static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform*); static bool ARMDebuggerHasBreakpoints(struct mDebuggerPlatform*);

@@ -157,14 +157,16 @@ debugger->clearSoftwareBreakpoint(debugger, address, breakpoint->sw.mode, breakpoint->sw.opcode);

} } -static void ARMDebuggerSetBreakpoint(struct mDebuggerPlatform* d, uint32_t address) { +static void ARMDebuggerSetBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) { + UNUSED(segment); struct ARMDebugger* debugger = (struct ARMDebugger*) d; struct ARMDebugBreakpoint* breakpoint = ARMDebugBreakpointListAppend(&debugger->breakpoints); breakpoint->address = address; breakpoint->isSw = false; } -static void ARMDebuggerClearBreakpoint(struct mDebuggerPlatform* d, uint32_t address) { +static void ARMDebuggerClearBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) { + UNUSED(segment); struct ARMDebugger* debugger = (struct ARMDebugger*) d; struct ARMDebugBreakpointList* breakpoints = &debugger->breakpoints; size_t i;

@@ -180,7 +182,8 @@ struct ARMDebugger* debugger = (struct ARMDebugger*) d;

return ARMDebugBreakpointListSize(&debugger->breakpoints) || ARMDebugWatchpointListSize(&debugger->watchpoints); } -static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform* d, uint32_t address, enum mWatchpointType type) { +static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, enum mWatchpointType type) { + UNUSED(segment); struct ARMDebugger* debugger = (struct ARMDebugger*) d; if (!ARMDebugWatchpointListSize(&debugger->watchpoints)) { ARMDebuggerInstallMemoryShim(debugger);

@@ -190,7 +193,8 @@ watchpoint->address = address;

watchpoint->type = type; } -static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform* d, uint32_t address) { +static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) { + UNUSED(segment); struct ARMDebugger* debugger = (struct ARMDebugger*) d; struct ARMDebugWatchpointList* watchpoints = &debugger->watchpoints; size_t i;
M src/core/cheats.csrc/core/cheats.c

@@ -17,7 +17,6 @@ mLOG_DEFINE_CATEGORY(CHEATS, "Cheats", "core.cheats");

DEFINE_VECTOR(mCheatList, struct mCheat); DEFINE_VECTOR(mCheatSets, struct mCheatSet*); -DEFINE_VECTOR(StringList, char*); static int32_t _readMem(struct mCore* core, uint32_t address, int width) { switch (width) {
M src/core/config.csrc/core/config.c

@@ -349,6 +349,9 @@ }

if (_lookupIntValue(config, "lockAspectRatio", &fakeBool)) { opts->lockAspectRatio = fakeBool; } + if (_lookupIntValue(config, "lockIntegerScaling", &fakeBool)) { + opts->lockIntegerScaling = fakeBool; + } if (_lookupIntValue(config, "resampleVideo", &fakeBool)) { opts->resampleVideo = fakeBool; }

@@ -396,6 +399,7 @@ 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, "lockIntegerScaling", opts->lockIntegerScaling); ConfigurationSetIntValue(&config->defaultsTable, 0, "resampleVideo", opts->resampleVideo); ConfigurationSetIntValue(&config->defaultsTable, 0, "suspendScreensaver", opts->suspendScreensaver); }
M src/core/core.csrc/core/core.c

@@ -18,8 +18,11 @@ #ifdef M_CORE_GBA

#include <mgba/gba/core.h> #include <mgba/internal/gba/gba.h> #endif +#ifndef MINIMAL_CORE +#include <mgba/feature/video-logger.h> +#endif -static struct mCoreFilter { +const static struct mCoreFilter { bool (*filter)(struct VFile*); struct mCore* (*open)(void); enum mPlatform platform;

@@ -37,7 +40,7 @@ struct mCore* mCoreFindVF(struct VFile* vf) {

if (!vf) { return NULL; } - struct mCoreFilter* filter; + const struct mCoreFilter* filter; for (filter = &_filters[0]; filter->filter; ++filter) { if (filter->filter(vf)) { break;

@@ -46,6 +49,9 @@ }

if (filter->open) { return filter->open(); } +#ifndef MINIMAL_CORE + return mVideoLogCoreFind(vf); +#endif return NULL; }

@@ -53,7 +59,7 @@ enum mPlatform mCoreIsCompatible(struct VFile* vf) {

if (!vf) { return false; } - struct mCoreFilter* filter; + const struct mCoreFilter* filter; for (filter = &_filters[0]; filter->filter; ++filter) { if (filter->filter(vf)) { return filter->platform;

@@ -109,6 +115,35 @@ return false;

} bool ret = core->loadROM(core, rom); + if (!ret) { + rom->close(rom); + } + return ret; +} + +bool mCorePreloadVF(struct mCore* core, struct VFile* vf) { + struct VFile* vfm = VFileMemChunk(NULL, vf->size(vf)); + uint8_t buffer[2048]; + ssize_t read; + vf->seek(vf, 0, SEEK_SET); + while ((read = vf->read(vf, buffer, sizeof(buffer))) > 0) { + vfm->write(vfm, buffer, read); + } + vf->close(vf); + bool ret = core->loadROM(core, vfm); + if (!ret) { + vfm->close(vfm); + } + return ret; +} + +bool mCorePreloadFile(struct mCore* core, const char* path) { + struct VFile* rom = mDirectorySetOpenPath(&core->dirs, path, core->isROM); + if (!rom) { + return false; + } + + bool ret = mCorePreloadVF(core, rom); if (!ret) { rom->close(rom); }
M src/core/input.csrc/core/input.c

@@ -11,7 +11,7 @@ #include <mgba-util/vector.h>

#include <inttypes.h> -#define SECTION_NAME_MAX 128 +#define SECTION_NAME_MAX 50 #define KEY_NAME_MAX 32 #define KEY_VALUE_MAX 16 #define AXIS_INFO_MAX 12

@@ -563,7 +563,7 @@ if (mInputHatListSize(&impl->hats) && id + 1 == (ssize_t) mInputHatListSize(&impl->hats)) {

mInputHatListResize(&impl->hats, -1); } else { struct mInputHatBindings* description = mInputHatListGetPointer(&impl->hats, id); - memset(description, -1, sizeof(&description)); + memset(description, -1, sizeof(*description)); } }
M src/core/rewind.csrc/core/rewind.c

@@ -12,7 +12,13 @@ #include <mgba-util/vfs.h>

DEFINE_VECTOR(mCoreRewindPatches, struct PatchFast); -void mCoreRewindContextInit(struct mCoreRewindContext* context, size_t entries) { +void _rewindDiff(struct mCoreRewindContext* context); + +#ifndef DISABLE_THREADING +THREAD_ENTRY _rewindThread(void* context); +#endif + +void mCoreRewindContextInit(struct mCoreRewindContext* context, size_t entries, bool onThread) { mCoreRewindPatchesInit(&context->patchMemory, entries); size_t e; for (e = 0; e < entries; ++e) {

@@ -22,9 +28,30 @@ context->previousState = VFileMemChunk(0, 0);

context->currentState = VFileMemChunk(0, 0); context->size = 0; context->stateFlags = SAVESTATE_SAVEDATA; +#ifndef DISABLE_THREADING + context->onThread = onThread; + if (onThread) { + MutexInit(&context->mutex); + ConditionInit(&context->cond); + ThreadCreate(&context->thread, _rewindThread, context); + } +#else + UNUSED(onThread); +#endif } void mCoreRewindContextDeinit(struct mCoreRewindContext* context) { +#ifndef DISABLE_THREADING + if (context->onThread) { + MutexLock(&context->mutex); + context->onThread = false; + MutexUnlock(&context->mutex); + ConditionWake(&context->cond); + ThreadJoin(context->thread); + MutexDeinit(&context->mutex); + ConditionDeinit(&context->cond); + } +#endif context->previousState->close(context->previousState); context->currentState->close(context->currentState); size_t s;

@@ -35,7 +62,26 @@ mCoreRewindPatchesDeinit(&context->patchMemory);

} void mCoreRewindAppend(struct mCoreRewindContext* context, struct mCore* core) { +#ifndef DISABLE_THREADING + if (context->onThread) { + MutexLock(&context->mutex); + } +#endif struct VFile* nextState = context->previousState; + mCoreSaveStateNamed(core, nextState, context->stateFlags); + context->previousState = context->currentState; + context->currentState = nextState; +#ifndef DISABLE_THREADING + if (context->onThread) { + ConditionWake(&context->cond); + MutexUnlock(&context->mutex); + return; + } +#endif + _rewindDiff(context); +} + +void _rewindDiff(struct mCoreRewindContext* context) { ++context->current; if (context->size < mCoreRewindPatchesSize(&context->patchMemory)) { ++context->size;

@@ -43,25 +89,34 @@ }

if (context->current >= mCoreRewindPatchesSize(&context->patchMemory)) { context->current = 0; } - mCoreSaveStateNamed(core, nextState, context->stateFlags); struct PatchFast* patch = mCoreRewindPatchesGetPointer(&context->patchMemory, context->current); - size_t size2 = nextState->size(nextState); - size_t size = context->currentState->size(context->currentState); + size_t size2 = context->currentState->size(context->currentState); + size_t size = context->previousState->size(context->previousState); if (size2 > size) { - context->currentState->truncate(context->currentState, size2); + context->previousState->truncate(context->previousState, size2); size = size2; + } else if (size > size2) { + context->currentState->truncate(context->currentState, size); } - void* current = context->currentState->map(context->currentState, size, MAP_READ); - void* next = nextState->map(nextState, size, MAP_READ); + void* current = context->previousState->map(context->previousState, size, MAP_READ); + void* next = context->currentState->map(context->currentState, size, MAP_READ); diffPatchFast(patch, current, next, size); - context->currentState->unmap(context->currentState, current, size); - nextState->unmap(next, nextState, size); - context->previousState = context->currentState; - context->currentState = nextState; + context->previousState->unmap(context->previousState, current, size); + context->currentState->unmap(context->currentState, next, size); } bool mCoreRewindRestore(struct mCoreRewindContext* context, struct mCore* core) { +#ifndef DISABLE_THREADING + if (context->onThread) { + MutexLock(&context->mutex); + } +#endif if (!context->size) { +#ifndef DISABLE_THREADING + if (context->onThread) { + MutexUnlock(&context->mutex); + } +#endif return false; } --context->size;

@@ -86,5 +141,29 @@ if (context->current == 0) {

context->current = mCoreRewindPatchesSize(&context->patchMemory); } --context->current; +#ifndef DISABLE_THREADING + if (context->onThread) { + MutexUnlock(&context->mutex); + } +#endif return true; } + +#ifndef DISABLE_THREADING +THREAD_ENTRY _rewindThread(void* context) { + struct mCoreRewindContext* rewindContext = context; + ThreadSetName("Rewind Diff Thread"); + MutexLock(&rewindContext->mutex); + struct VFile* state = rewindContext->currentState; + while (rewindContext->onThread) { + if (rewindContext->currentState != state) { + _rewindDiff(rewindContext); + state = rewindContext->currentState; + } + ConditionWait(&rewindContext->cond, &rewindContext->mutex); + } + MutexUnlock(&rewindContext->mutex); + return 0; +} +#endif +
M src/core/thread.csrc/core/thread.c

@@ -120,6 +120,16 @@ }

_changeState(thread, THREAD_CRASHED, true); } +void _coreSleep(void* context) { + struct mCoreThread* thread = context; + if (!thread) { + return; + } + if (thread->sleepCallback) { + thread->sleepCallback(thread); + } +} + static THREAD_ENTRY _mCoreThreadRun(void* context) { struct mCoreThread* threadContext = context; #ifdef USE_PTHREADS

@@ -143,6 +153,7 @@ struct mCoreCallbacks callbacks = {

.videoFrameStarted = _frameStarted, .videoFrameEnded = _frameEnded, .coreCrashed = _crashed, + .sleep = _coreSleep, .context = threadContext }; core->addCoreCallbacks(core, &callbacks);

@@ -157,7 +168,7 @@ mLogFilterLoad(threadContext->logger.d.filter, &core->config);

} if (core->opts.rewindEnable && core->opts.rewindBufferCapacity > 0) { - mCoreRewindContextInit(&threadContext->rewind, core->opts.rewindBufferCapacity); + mCoreRewindContextInit(&threadContext->rewind, core->opts.rewindBufferCapacity, true); threadContext->rewind.stateFlags = core->opts.rewindSave ? SAVESTATE_SAVEDATA : 0; }
M src/debugger/cli-debugger.csrc/debugger/cli-debugger.c

@@ -5,6 +5,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include <mgba/internal/debugger/cli-debugger.h> +#include <mgba/internal/debugger/symbols.h> + #include <mgba/core/core.h> #include <mgba/core/version.h> #include <mgba/internal/debugger/parser.h>

@@ -135,6 +137,10 @@ }

static void _print(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { for (; dv; dv = dv->next) { + if (dv->segmentValue >= 0) { + debugger->backend->printf(debugger->backend, " $%02X:%04X", dv->segmentValue, dv->intValue); + continue; + } debugger->backend->printf(debugger->backend, " %u", dv->intValue); } debugger->backend->printf(debugger->backend, "\n");

@@ -209,7 +215,12 @@ debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS);

return; } uint32_t address = dv->intValue; - uint8_t value = debugger->d.core->busRead8(debugger->d.core, address); + uint8_t value; + if (dv->segmentValue >= 0) { + value = debugger->d.core->rawRead8(debugger->d.core, address, dv->segmentValue); + } else { + value = debugger->d.core->busRead8(debugger->d.core, address); + } debugger->backend->printf(debugger->backend, " 0x%02X\n", value); }

@@ -225,7 +236,12 @@ debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS);

return; } uint32_t address = dv->intValue; - uint16_t value = debugger->d.core->busRead16(debugger->d.core, address & ~1); + uint16_t value; + if (dv->segmentValue >= 0) { + value = debugger->d.core->rawRead16(debugger->d.core, address & -1, dv->segmentValue); + } else { + value = debugger->d.core->busRead16(debugger->d.core, address & ~1); + } debugger->backend->printf(debugger->backend, " 0x%04X\n", value); }

@@ -235,7 +251,12 @@ debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS);

return; } uint32_t address = dv->intValue; - uint32_t value = debugger->d.core->busRead32(debugger->d.core, address & ~3); + uint32_t value; + if (dv->segmentValue >= 0) { + value = debugger->d.core->rawRead32(debugger->d.core, address & -3, dv->segmentValue); + } else { + value = debugger->d.core->busRead32(debugger->d.core, address & ~3); + } debugger->backend->printf(debugger->backend, " 0x%08X\n", value); }

@@ -254,7 +275,11 @@ if (value > 0xFF) {

debugger->backend->printf(debugger->backend, "%s\n", ERROR_OVERFLOW); return; } - debugger->d.core->busWrite8(debugger->d.core, address, value); + if (dv->segmentValue >= 0) { + debugger->d.core->rawWrite8(debugger->d.core, address, value, dv->segmentValue); + } else { + debugger->d.core->busWrite8(debugger->d.core, address, value); + } } static void _writeHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {

@@ -272,7 +297,11 @@ if (value > 0xFFFF) {

debugger->backend->printf(debugger->backend, "%s\n", ERROR_OVERFLOW); return; } - debugger->d.core->busWrite16(debugger->d.core, address, value); + if (dv->segmentValue >= 0) { + debugger->d.core->rawWrite16(debugger->d.core, address, value, dv->segmentValue); + } else { + debugger->d.core->busWrite16(debugger->d.core, address, value); + } } static void _writeWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {

@@ -286,7 +315,11 @@ return;

} uint32_t address = dv->intValue; uint32_t value = dv->next->intValue; - debugger->d.core->busWrite32(debugger->d.core, address, value); + if (dv->segmentValue >= 0) { + debugger->d.core->rawWrite32(debugger->d.core, address, value, dv->segmentValue); + } else { + debugger->d.core->busWrite32(debugger->d.core, address, value); + } } static void _dumpByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {

@@ -306,7 +339,12 @@ line = words;

} debugger->backend->printf(debugger->backend, "0x%08X:", address); for (; line > 0; --line, ++address, --words) { - uint32_t value = debugger->d.core->busRead8(debugger->d.core, address); + uint32_t value; + if (dv->segmentValue >= 0) { + value = debugger->d.core->rawRead8(debugger->d.core, address, dv->segmentValue); + } else { + value = debugger->d.core->busRead8(debugger->d.core, address); + } debugger->backend->printf(debugger->backend, " %02X", value); } debugger->backend->printf(debugger->backend, "\n");

@@ -330,7 +368,12 @@ line = words;

} debugger->backend->printf(debugger->backend, "0x%08X:", address); for (; line > 0; --line, address += 2, --words) { - uint32_t value = debugger->d.core->busRead16(debugger->d.core, address); + uint32_t value; + if (dv->segmentValue >= 0) { + value = debugger->d.core->rawRead16(debugger->d.core, address, dv->segmentValue); + } else { + value = debugger->d.core->busRead16(debugger->d.core, address); + } debugger->backend->printf(debugger->backend, " %04X", value); } debugger->backend->printf(debugger->backend, "\n");

@@ -354,7 +397,12 @@ line = words;

} debugger->backend->printf(debugger->backend, "0x%08X:", address); for (; line > 0; --line, address += 4, --words) { - uint32_t value = debugger->d.core->busRead32(debugger->d.core, address); + uint32_t value; + if (dv->segmentValue >= 0) { + value = debugger->d.core->rawRead32(debugger->d.core, address, dv->segmentValue); + } else { + value = debugger->d.core->busRead32(debugger->d.core, address); + } debugger->backend->printf(debugger->backend, " %08X", value); } debugger->backend->printf(debugger->backend, "\n");

@@ -367,7 +415,7 @@ debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS);

return; } uint32_t address = dv->intValue; - debugger->d.platform->setBreakpoint(debugger->d.platform, address); + debugger->d.platform->setBreakpoint(debugger->d.platform, address, dv->segmentValue); } static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {

@@ -380,7 +428,7 @@ debugger->backend->printf(debugger->backend, "Watchpoints are not supported by this platform.\n");

return; } uint32_t address = dv->intValue; - debugger->d.platform->setWatchpoint(debugger->d.platform, address, WATCHPOINT_RW); + debugger->d.platform->setWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_RW); } static void _setReadWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {

@@ -393,7 +441,7 @@ debugger->backend->printf(debugger->backend, "Watchpoints are not supported by this platform.\n");

return; } uint32_t address = dv->intValue; - debugger->d.platform->setWatchpoint(debugger->d.platform, address, WATCHPOINT_READ); + debugger->d.platform->setWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_READ); } static void _setWriteWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {

@@ -406,7 +454,7 @@ debugger->backend->printf(debugger->backend, "Watchpoints are not supported by this platform.\n");

return; } uint32_t address = dv->intValue; - debugger->d.platform->setWatchpoint(debugger->d.platform, address, WATCHPOINT_WRITE); + debugger->d.platform->setWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_WRITE); } static void _clearBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {

@@ -415,9 +463,9 @@ debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS);

return; } uint32_t address = dv->intValue; - debugger->d.platform->clearBreakpoint(debugger->d.platform, address); + debugger->d.platform->clearBreakpoint(debugger->d.platform, address, dv->segmentValue); if (debugger->d.platform->clearWatchpoint) { - debugger->d.platform->clearWatchpoint(debugger->d.platform, address); + debugger->d.platform->clearWatchpoint(debugger->d.platform, address, dv->segmentValue); } }

@@ -455,7 +503,11 @@

static void _lookupIdentifier(struct mDebugger* debugger, const char* name, struct CLIDebugVector* dv) { struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; if (cliDebugger->system) { - uint32_t value = cliDebugger->system->lookupPlatformIdentifier(cliDebugger->system, name, dv); + uint32_t value; + if (debugger->core->symbolTable && mDebuggerSymbolLookup(debugger->core->symbolTable, name, &dv->intValue, &dv->segmentValue)) { + return; + } + value = cliDebugger->system->lookupPlatformIdentifier(cliDebugger->system, name, dv); if (dv->type != CLIDV_ERROR_TYPE) { dv->intValue = value; return;

@@ -751,6 +803,9 @@ system->p = debugger;

} void CLIDebuggerAttachBackend(struct CLIDebugger* debugger, struct CLIDebuggerBackend* backend) { + if (debugger->backend == backend) { + return; + } if (debugger->backend && debugger->backend->deinit) { debugger->backend->deinit(debugger->backend); }
M src/debugger/debugger.csrc/debugger/debugger.c

@@ -62,6 +62,9 @@ debugger->d.id = DEBUGGER_ID;

debugger->d.init = mDebuggerInit; debugger->d.deinit = mDebuggerDeinit; debugger->core = core; + if (!debugger->core->symbolTable) { + debugger->core->loadSymbols(debugger->core, NULL); + } debugger->platform = core->debuggerPlatform(core); debugger->platform->p = debugger; core->attachDebugger(core, debugger);
M src/debugger/gdb-stub.csrc/debugger/gdb-stub.c

@@ -321,11 +321,29 @@ struct ARMCore* cpu = stub->d.core->cpu;

UNUSED(message); int r; int i = 0; + + // General purpose registers for (r = 0; r < ARM_PC; ++r) { _int2hex32(cpu->gprs[r], &stub->outgoing[i]); i += 8; } + + // Program counter _int2hex32(cpu->gprs[ARM_PC] - (cpu->cpsr.t ? WORD_SIZE_THUMB : WORD_SIZE_ARM), &stub->outgoing[i]); + i += 8; + + // Floating point registers, unused on the GBA (8 of them, 24 bits each) + for (r = 0; r < 8 * 3; ++r) { + _int2hex32(0, &stub->outgoing[i]); + i += 8; + } + + // Floating point status, unused on the GBA (32 bits) + _int2hex32(0, &stub->outgoing[i]); + i += 8; + + // CPU status + _int2hex32(cpu->cpsr.packed, &stub->outgoing[i]); i += 8; stub->outgoing[i] = 0;

@@ -477,16 +495,16 @@ case '0':

ARMDebuggerSetSoftwareBreakpoint(stub->d.platform, address, kind == 2 ? MODE_THUMB : MODE_ARM); break; case '1': - stub->d.platform->setBreakpoint(stub->d.platform, address); + stub->d.platform->setBreakpoint(stub->d.platform, address, -1); break; case '2': - stub->d.platform->setWatchpoint(stub->d.platform, address, WATCHPOINT_WRITE); + stub->d.platform->setWatchpoint(stub->d.platform, address, -1, WATCHPOINT_WRITE); break; case '3': - stub->d.platform->setWatchpoint(stub->d.platform, address, WATCHPOINT_READ); + stub->d.platform->setWatchpoint(stub->d.platform, address, -1, WATCHPOINT_READ); break; case '4': - stub->d.platform->setWatchpoint(stub->d.platform, address, WATCHPOINT_RW); + stub->d.platform->setWatchpoint(stub->d.platform, address, -1, WATCHPOINT_RW); break; default: stub->outgoing[0] = '\0';

@@ -506,12 +524,12 @@ case '0':

ARMDebuggerClearSoftwareBreakpoint(stub->d.platform, address); break; case '1': - stub->d.platform->clearBreakpoint(stub->d.platform, address); + stub->d.platform->clearBreakpoint(stub->d.platform, address, -1); break; case '2': case '3': case '4': - stub->d.platform->clearWatchpoint(stub->d.platform, address); + stub->d.platform->clearWatchpoint(stub->d.platform, address, -1); break; default: break;
A src/debugger/symbols.c

@@ -0,0 +1,49 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <mgba/internal/debugger/symbols.h> + +#include <mgba-util/table.h> + +struct mDebuggerSymbol { + int32_t value; + int segment; +}; + +struct mDebuggerSymbols { + struct Table names; +}; + +struct mDebuggerSymbols* mDebuggerSymbolTableCreate(void) { + struct mDebuggerSymbols* st = malloc(sizeof(*st)); + HashTableInit(&st->names, 0, free); + return st; +} + +void mDebuggerSymbolTableDestroy(struct mDebuggerSymbols* st) { + HashTableDeinit(&st->names); + free(st); +} + +bool mDebuggerSymbolLookup(const struct mDebuggerSymbols* st, const char* name, int32_t* value, int* segment) { + struct mDebuggerSymbol* sym = HashTableLookup(&st->names, name); + if (!sym) { + return false; + } + *value = sym->value; + *segment = sym->segment; + return true; +} + +void mDebuggerSymbolAdd(struct mDebuggerSymbols* st, const char* name, int32_t value, int segment) { + struct mDebuggerSymbol* sym = malloc(sizeof(*sym)); + sym->value = value; + sym->segment = segment; + HashTableInsert(&st->names, name, sym); +} + +void mDebuggerSymbolRemove(struct mDebuggerSymbols* st, const char* name) { + HashTableRemove(&st->names, name); +}
M src/feature/commandline.csrc/feature/commandline.c

@@ -3,7 +3,7 @@ *

* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "commandline.h" +#include <mgba/feature/commandline.h> #include <mgba/core/config.h> #include <mgba/core/version.h>

@@ -38,6 +38,7 @@ #ifdef USE_GDB_STUB

{ "gdb", no_argument, 0, 'g' }, #endif { "help", no_argument, 0, 'h' }, + { "log-level", required_argument, 0, 'l' }, { "movie", required_argument, 0, 'v' }, { "patch", required_argument, 0, 'p' }, { "version", no_argument, 0, '\0' },

@@ -47,10 +48,27 @@

static bool _parseGraphicsArg(struct mSubParser* parser, int option, const char* arg); static void _applyGraphicsArgs(struct mSubParser* parser, struct mCoreConfig* config); +static void _tableInsert(struct Table* table, const char* pair) { + char* eq = strchr(pair, '='); + if (eq) { + char option[128] = ""; + strncpy(option, pair, eq - pair); + option[sizeof(option) - 1] = '\0'; + HashTableInsert(table, option, strdup(&eq[1])); + } else { + HashTableInsert(table, pair, strdup("1")); + } +} + +static void _tableApply(const char* key, void* value, void* user) { + struct mCoreConfig* config = user; + mCoreConfigSetOverrideValue(config, key, value); +} + bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct mSubParser* subparser) { int ch; char options[64] = - "b:c:hl:p:s:v:" + "b:c:C:hl:p:s:v:" #ifdef USE_EDITLINE "d" #endif

@@ -61,6 +79,7 @@ ;

memset(args, 0, sizeof(*args)); args->frameskip = -1; args->logLevel = INT_MIN; + HashTableInit(&args->configOverrides, 0, free); if (subparser && subparser->extraOptions) { // TODO: modularize options to subparsers strncat(options, subparser->extraOptions, sizeof(options) - strlen(options) - 1);

@@ -82,6 +101,9 @@ break;

case 'c': args->cheatsFile = strdup(optarg); break; + case 'C': + _tableInsert(&args->configOverrides, optarg); + break; #ifdef USE_EDITLINE case 'd': if (args->debuggerType != DEBUGGER_NONE) {

@@ -144,6 +166,7 @@ }

if (args->bios) { mCoreConfigSetOverrideValue(config, "bios", args->bios); } + HashTableEnumerate(&args->configOverrides, _tableApply, config); if (subparser) { subparser->apply(subparser, config); }

@@ -164,6 +187,8 @@ args->cheatsFile = 0;

free(args->bios); args->bios = 0; + + HashTableDeinit(&args->configOverrides); } void initParserForGraphics(struct mSubParser* parser, struct mGraphicsOpts* opts) {

@@ -209,18 +234,20 @@

void usage(const char* arg0, const char* extraOptions) { printf("usage: %s [option ...] file\n", arg0); puts("\nGeneric options:"); - puts(" -b, --bios FILE GBA BIOS file to use"); - puts(" -c, --cheats FILE Apply cheat codes from a file"); + puts(" -b, --bios FILE GBA BIOS file to use"); + puts(" -c, --cheats FILE Apply cheat codes from a file"); + puts(" -C, --config OPTION=VALUE Override config value"); #ifdef USE_EDITLINE - puts(" -d, --debug Use command-line debugger"); + puts(" -d, --debug Use command-line debugger"); #endif #ifdef USE_GDB_STUB - puts(" -g, --gdb Start GDB session (default port 2345)"); + puts(" -g, --gdb Start GDB session (default port 2345)"); #endif - puts(" -v, --movie FILE Play back a movie of recorded input"); - puts(" -p, --patch FILE Apply a specified patch file when running"); - puts(" -s, --frameskip N Skip every N frames"); - puts(" --version Print version and exit"); + puts(" -l, --log-level N Log level mask"); + puts(" -v, --movie FILE Play back a movie of recorded input"); + puts(" -p, --patch FILE Apply a specified patch file when running"); + puts(" -s, --frameskip N Skip every N frames"); + puts(" --version Print version and exit"); if (extraOptions) { puts(extraOptions); }
M src/feature/commandline.hinclude/mgba/feature/commandline.h

@@ -10,6 +10,8 @@ #include <mgba-util/common.h>

CXX_GUARD_START +#include <mgba-util/table.h> + #include <mgba/internal/debugger/debugger.h> struct mArguments {

@@ -20,6 +22,8 @@ char* movie;

char* bios; int logLevel; int frameskip; + + struct Table configOverrides; enum mDebuggerType debuggerType; bool debugAtStart;
M src/feature/ffmpeg/ffmpeg-encoder.csrc/feature/ffmpeg/ffmpeg-encoder.c

@@ -304,6 +304,14 @@ av_opt_set(encoder->video->priv_data, "preset", "faster", 0);

} av_opt_set(encoder->video->priv_data, "tune", "zerolatency", 0); } + + if (encoder->video->codec->id == AV_CODEC_ID_H264 && + (strcasecmp(encoder->containerFormat, "mp4") || + strcasecmp(encoder->containerFormat, "m4v") || + strcasecmp(encoder->containerFormat, "mov"))) { + // QuickTime and a few other things require YUV420 + encoder->video->pix_fmt = AV_PIX_FMT_YUV420P; + } avcodec_open2(encoder->video, vcodec, 0); #if LIBAVCODEC_VERSION_MAJOR >= 55 encoder->videoFrame = av_frame_alloc();

@@ -320,7 +328,9 @@ #ifdef FFMPEG_USE_CODECPAR

avcodec_parameters_from_context(encoder->videoStream->codecpar, encoder->video); #endif - avio_open(&encoder->context->pb, outfile, AVIO_FLAG_WRITE); + if (avio_open(&encoder->context->pb, outfile, AVIO_FLAG_WRITE) < 0) { + return false; + } return avformat_write_header(encoder->context, 0) >= 0; }

@@ -389,27 +399,27 @@

encoder->audioBuffer[encoder->currentAudioSample * 2] = left; encoder->audioBuffer[encoder->currentAudioSample * 2 + 1] = right; - ++encoder->currentAudioFrame; ++encoder->currentAudioSample; - if ((encoder->currentAudioSample * 4) < encoder->audioBufferSize) { + if (encoder->currentAudioSample * 4 < encoder->audioBufferSize) { return; } int channelSize = 2 * av_get_bytes_per_sample(encoder->audio->sample_fmt); - avresample_convert(encoder->resampleContext, - 0, 0, 0, - (uint8_t**) &encoder->audioBuffer, 0, encoder->audioBufferSize / 4); + avresample_convert(encoder->resampleContext, 0, 0, 0, + (uint8_t**) &encoder->audioBuffer, 0, encoder->audioBufferSize / 4); + + encoder->currentAudioSample = 0; if (avresample_available(encoder->resampleContext) < encoder->audioFrame->nb_samples) { return; } #if LIBAVCODEC_VERSION_MAJOR >= 55 av_frame_make_writable(encoder->audioFrame); #endif - avresample_read(encoder->resampleContext, encoder->audioFrame->data, encoder->postaudioBufferSize / channelSize); + int samples = avresample_read(encoder->resampleContext, encoder->audioFrame->data, encoder->postaudioBufferSize / channelSize); - encoder->audioFrame->pts = av_rescale_q(encoder->currentAudioFrame - encoder->currentAudioSample, encoder->audio->time_base, encoder->audioStream->time_base); - encoder->currentAudioSample = 0; + encoder->audioFrame->pts = av_rescale_q(encoder->currentAudioFrame, encoder->audio->time_base, encoder->audioStream->time_base); + encoder->currentAudioFrame += samples; AVPacket packet; av_init_packet(&packet);
M src/feature/gui/gui-runner.csrc/feature/gui/gui-runner.c

@@ -298,7 +298,7 @@ GUIShowMessageBox(&runner->params, GUI_MESSAGE_BOX_OK, 240, "Load failed!");

return; } if (runner->core->platform(runner->core) == PLATFORM_GBA) { - ((struct GBA*) runner->core->board)->luminanceSource = &runner->luminanceSource.d; + runner->core->setPeripheral(runner->core, mPERIPH_GBA_LUMINANCE, &runner->luminanceSource.d); } mLOG(GUI_RUNNER, DEBUG, "Loading config..."); mCoreLoadForeignConfig(runner->core, &runner->config);
A src/feature/thread-proxy.c

@@ -0,0 +1,195 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <mgba/feature/thread-proxy.h> + +#include <mgba/core/tile-cache.h> +#include <mgba/internal/gba/gba.h> +#include <mgba/internal/gba/io.h> + +#ifndef DISABLE_THREADING + +static void mVideoThreadProxyInit(struct mVideoLogger* logger); +static void mVideoThreadProxyReset(struct mVideoLogger* logger); +static void mVideoThreadProxyDeinit(struct mVideoLogger* logger); + +static THREAD_ENTRY _proxyThread(void* renderer); + +static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length); +static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block); + +static void _lock(struct mVideoLogger* logger); +static void _unlock(struct mVideoLogger* logger); +static void _wait(struct mVideoLogger* logger); +static void _wake(struct mVideoLogger* logger, int y); + +void mVideoThreadProxyCreate(struct mVideoThreadProxy* renderer) { + mVideoLoggerRendererCreate(&renderer->d, false); + renderer->d.block = true; + + renderer->d.init = mVideoThreadProxyInit; + renderer->d.reset = mVideoThreadProxyReset; + renderer->d.deinit = mVideoThreadProxyDeinit; + renderer->d.lock = _lock; + renderer->d.unlock = _unlock; + renderer->d.wait = _wait; + renderer->d.wake = _wake; + + renderer->d.writeData = _writeData; + renderer->d.readData = _readData; +} + +void mVideoThreadProxyInit(struct mVideoLogger* logger) { + struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger; + ConditionInit(&proxyRenderer->fromThreadCond); + ConditionInit(&proxyRenderer->toThreadCond); + MutexInit(&proxyRenderer->mutex); + RingFIFOInit(&proxyRenderer->dirtyQueue, 0x40000); + + proxyRenderer->threadState = PROXY_THREAD_IDLE; + ThreadCreate(&proxyRenderer->thread, _proxyThread, proxyRenderer); +} + +void mVideoThreadProxyReset(struct mVideoLogger* logger) { + struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger; + MutexLock(&proxyRenderer->mutex); + while (proxyRenderer->threadState == PROXY_THREAD_BUSY) { + ConditionWake(&proxyRenderer->toThreadCond); + ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex); + } + MutexUnlock(&proxyRenderer->mutex); +} + +void mVideoThreadProxyDeinit(struct mVideoLogger* logger) { + struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger; + bool waiting = false; + MutexLock(&proxyRenderer->mutex); + while (proxyRenderer->threadState == PROXY_THREAD_BUSY) { + ConditionWake(&proxyRenderer->toThreadCond); + ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex); + } + if (proxyRenderer->threadState == PROXY_THREAD_IDLE) { + proxyRenderer->threadState = PROXY_THREAD_STOPPED; + ConditionWake(&proxyRenderer->toThreadCond); + waiting = true; + } + MutexUnlock(&proxyRenderer->mutex); + if (waiting) { + ThreadJoin(proxyRenderer->thread); + } + ConditionDeinit(&proxyRenderer->fromThreadCond); + ConditionDeinit(&proxyRenderer->toThreadCond); + MutexDeinit(&proxyRenderer->mutex); +} + +void _proxyThreadRecover(struct mVideoThreadProxy* proxyRenderer) { + MutexLock(&proxyRenderer->mutex); + if (proxyRenderer->threadState != PROXY_THREAD_STOPPED) { + MutexUnlock(&proxyRenderer->mutex); + return; + } + RingFIFOClear(&proxyRenderer->dirtyQueue); + MutexUnlock(&proxyRenderer->mutex); + ThreadJoin(proxyRenderer->thread); + proxyRenderer->threadState = PROXY_THREAD_IDLE; + ThreadCreate(&proxyRenderer->thread, _proxyThread, proxyRenderer); +} + +static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) { + struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger; + while (!RingFIFOWrite(&proxyRenderer->dirtyQueue, data, length)) { + mLOG(GBA_VIDEO, DEBUG, "Can't write %"PRIz"u bytes. Proxy thread asleep?", length); + MutexLock(&proxyRenderer->mutex); + if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) { + mLOG(GBA_VIDEO, ERROR, "Proxy thread stopped prematurely!"); + MutexUnlock(&proxyRenderer->mutex); + return false; + } + ConditionWake(&proxyRenderer->toThreadCond); + ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex); + MutexUnlock(&proxyRenderer->mutex); + } + return true; +} + +static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block) { + struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger; + bool read = false; + while (true) { + read = RingFIFORead(&proxyRenderer->dirtyQueue, data, length); + if (!block || read) { + break; + } + mLOG(GBA_VIDEO, DEBUG, "Proxy thread can't read VRAM. CPU thread asleep?"); + MutexLock(&proxyRenderer->mutex); + ConditionWake(&proxyRenderer->fromThreadCond); + ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex); + MutexUnlock(&proxyRenderer->mutex); + } + return read; +} + +static void _lock(struct mVideoLogger* logger) { + struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger; + MutexLock(&proxyRenderer->mutex); +} + +static void _wait(struct mVideoLogger* logger) { + struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger; + if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) { + mLOG(GBA_VIDEO, ERROR, "Proxy thread stopped prematurely!"); + _proxyThreadRecover(proxyRenderer); + return; + } + while (proxyRenderer->threadState == PROXY_THREAD_BUSY) { + ConditionWake(&proxyRenderer->toThreadCond); + ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex); + } +} + +static void _unlock(struct mVideoLogger* logger) { + struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger; + MutexUnlock(&proxyRenderer->mutex); +} + +static void _wake(struct mVideoLogger* logger, int y) { + struct mVideoThreadProxy* proxyRenderer = (struct mVideoThreadProxy*) logger; + if ((y & 15) == 15) { + ConditionWake(&proxyRenderer->toThreadCond); + } +} + +static THREAD_ENTRY _proxyThread(void* logger) { + struct mVideoThreadProxy* proxyRenderer = logger; + ThreadSetName("Proxy Renderer Thread"); + + MutexLock(&proxyRenderer->mutex); + while (proxyRenderer->threadState != PROXY_THREAD_STOPPED) { + ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex); + if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) { + break; + } + proxyRenderer->threadState = PROXY_THREAD_BUSY; + MutexUnlock(&proxyRenderer->mutex); + if (!mVideoLoggerRendererRun(&proxyRenderer->d, false)) { + // FIFO was corrupted + proxyRenderer->threadState = PROXY_THREAD_STOPPED; + mLOG(GBA_VIDEO, ERROR, "Proxy thread queue got corrupted!"); + } + MutexLock(&proxyRenderer->mutex); + ConditionWake(&proxyRenderer->fromThreadCond); + if (proxyRenderer->threadState != PROXY_THREAD_STOPPED) { + proxyRenderer->threadState = PROXY_THREAD_IDLE; + } + } + MutexUnlock(&proxyRenderer->mutex); + +#ifdef _3DS + svcExitThread(); +#endif + return 0; +} + +#endif
A src/feature/video-logger.c

@@ -0,0 +1,951 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <mgba/feature/video-logger.h> + +#include <mgba/core/core.h> +#include <mgba-util/memory.h> +#include <mgba-util/vfs.h> +#include <mgba-util/math.h> + +#ifdef M_CORE_GBA +#include <mgba/gba/core.h> +#endif +#ifdef M_CORE_GB +#include <mgba/gb/core.h> +#endif + +#ifdef USE_ZLIB +#include <zlib.h> +#endif + +#define BUFFER_BASE_SIZE 0x20000 +#define MAX_BLOCK_SIZE 0x800000 + +const char mVL_MAGIC[] = "mVL\0"; + +const static struct mVLDescriptor { + enum mPlatform platform; + struct mCore* (*open)(void); +} _descriptors[] = { +#ifdef M_CORE_GBA + { PLATFORM_GBA, GBAVideoLogPlayerCreate }, +#endif +#ifdef M_CORE_GB + { PLATFORM_GB, GBVideoLogPlayerCreate }, +#endif + { PLATFORM_NONE, 0 } +}; + +enum mVLBlockType { + mVL_BLOCK_DUMMY = 0, + mVL_BLOCK_INITIAL_STATE, + mVL_BLOCK_CHANNEL_HEADER, + mVL_BLOCK_DATA, + mVL_BLOCK_FOOTER = 0x784C566D +}; + +enum mVLHeaderFlag { + mVL_FLAG_HAS_INITIAL_STATE = 1 +}; + +struct mVLBlockHeader { + uint32_t blockType; + uint32_t length; + uint32_t channelId; + uint32_t flags; +}; + +enum mVLBlockFlag { + mVL_FLAG_BLOCK_COMPRESSED = 1 +}; + +struct mVideoLogHeader { + char magic[4]; + uint32_t flags; + uint32_t platform; + uint32_t nChannels; +}; + +struct mVideoLogContext; +struct mVideoLogChannel { + struct mVideoLogContext* p; + + uint32_t type; + void* initialState; + size_t initialStateSize; + + off_t currentPointer; + size_t bufferRemaining; +#ifdef USE_ZLIB + bool inflating; + z_stream inflateStream; +#endif + + struct CircleBuffer buffer; +}; + +struct mVideoLogContext { + void* initialState; + size_t initialStateSize; + uint32_t nChannels; + struct mVideoLogChannel channels[mVL_MAX_CHANNELS]; + + bool write; + uint32_t activeChannel; + struct VFile* backing; +}; + + +static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length); +static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length); +static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block); + +static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* data, size_t length); +static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const void* data, size_t length); + +static inline size_t _roundUp(size_t value, int shift) { + value += (1 << shift) - 1; + return value >> shift; +} + +void mVideoLoggerRendererCreate(struct mVideoLogger* logger, bool readonly) { + if (readonly) { + logger->writeData = _writeNull; + logger->block = true; + } else { + logger->writeData = _writeData; + } + logger->readData = _readData; + logger->dataContext = NULL; + + logger->init = NULL; + logger->deinit = NULL; + logger->reset = NULL; + + logger->lock = NULL; + logger->unlock = NULL; + logger->wait = NULL; + logger->wake = NULL; +} + +void mVideoLoggerRendererInit(struct mVideoLogger* logger) { + logger->palette = anonymousMemoryMap(logger->paletteSize); + logger->vram = anonymousMemoryMap(logger->vramSize); + logger->oam = anonymousMemoryMap(logger->oamSize); + + logger->vramDirtyBitmap = calloc(_roundUp(logger->vramSize, 17), sizeof(uint32_t)); + logger->oamDirtyBitmap = calloc(_roundUp(logger->oamSize, 6), sizeof(uint32_t)); + + if (logger->init) { + logger->init(logger); + } +} + +void mVideoLoggerRendererDeinit(struct mVideoLogger* logger) { + if (logger->deinit) { + logger->deinit(logger); + } + + mappedMemoryFree(logger->palette, logger->paletteSize); + mappedMemoryFree(logger->vram, logger->vramSize); + mappedMemoryFree(logger->oam, logger->oamSize); + + free(logger->vramDirtyBitmap); + free(logger->oamDirtyBitmap); +} + +void mVideoLoggerRendererReset(struct mVideoLogger* logger) { + memset(logger->vramDirtyBitmap, 0, sizeof(uint32_t) * _roundUp(logger->vramSize, 17)); + memset(logger->oamDirtyBitmap, 0, sizeof(uint32_t) * _roundUp(logger->oamSize, 6)); + + if (logger->reset) { + logger->reset(logger); + } +} + +void mVideoLoggerRendererWriteVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value) { + struct mVideoLoggerDirtyInfo dirty = { + DIRTY_REGISTER, + address, + value, + 0xDEADBEEF, + }; + logger->writeData(logger, &dirty, sizeof(dirty)); +} + +void mVideoLoggerRendererWriteVRAM(struct mVideoLogger* logger, uint32_t address) { + int bit = 1 << (address >> 12); + if (logger->vramDirtyBitmap[address >> 17] & bit) { + return; + } + logger->vramDirtyBitmap[address >> 17] |= bit; +} + +void mVideoLoggerRendererWritePalette(struct mVideoLogger* logger, uint32_t address, uint16_t value) { + struct mVideoLoggerDirtyInfo dirty = { + DIRTY_PALETTE, + address, + value, + 0xDEADBEEF, + }; + logger->writeData(logger, &dirty, sizeof(dirty)); +} + +void mVideoLoggerRendererWriteOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value) { + struct mVideoLoggerDirtyInfo dirty = { + DIRTY_OAM, + address, + value, + 0xDEADBEEF, + }; + logger->writeData(logger, &dirty, sizeof(dirty)); +} + +static void _flushVRAM(struct mVideoLogger* logger) { + size_t i; + for (i = 0; i < _roundUp(logger->vramSize, 17); ++i) { + if (logger->vramDirtyBitmap[i]) { + uint32_t bitmap = logger->vramDirtyBitmap[i]; + logger->vramDirtyBitmap[i] = 0; + int j; + for (j = 0; j < mVL_MAX_CHANNELS; ++j) { + if (!(bitmap & (1 << j))) { + continue; + } + struct mVideoLoggerDirtyInfo dirty = { + DIRTY_VRAM, + j * 0x1000, + 0x1000, + 0xDEADBEEF, + }; + logger->writeData(logger, &dirty, sizeof(dirty)); + logger->writeData(logger, logger->vramBlock(logger, j * 0x1000), 0x1000); + } + } + } +} + +void mVideoLoggerRendererDrawScanline(struct mVideoLogger* logger, int y) { + _flushVRAM(logger); + struct mVideoLoggerDirtyInfo dirty = { + DIRTY_SCANLINE, + y, + 0, + 0xDEADBEEF, + }; + logger->writeData(logger, &dirty, sizeof(dirty)); +} + +void mVideoLoggerRendererDrawRange(struct mVideoLogger* logger, int startX, int endX, int y) { + _flushVRAM(logger); + struct mVideoLoggerDirtyInfo dirty = { + DIRTY_RANGE, + y, + startX, + endX, + }; + logger->writeData(logger, &dirty, sizeof(dirty)); +} + +void mVideoLoggerRendererFlush(struct mVideoLogger* logger) { + struct mVideoLoggerDirtyInfo dirty = { + DIRTY_FLUSH, + 0, + 0, + 0xDEADBEEF, + }; + logger->writeData(logger, &dirty, sizeof(dirty)); +} + +void mVideoLoggerRendererFinishFrame(struct mVideoLogger* logger) { + struct mVideoLoggerDirtyInfo dirty = { + DIRTY_FRAME, + 0, + 0, + 0xDEADBEEF, + }; + logger->writeData(logger, &dirty, sizeof(dirty)); +} + +void mVideoLoggerWriteBuffer(struct mVideoLogger* logger, uint32_t bufferId, uint32_t offset, uint32_t length, const void* data) { + struct mVideoLoggerDirtyInfo dirty = { + DIRTY_BUFFER, + bufferId, + offset, + length, + }; + logger->writeData(logger, &dirty, sizeof(dirty)); + logger->writeData(logger, data, length); +} + +bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block) { + struct mVideoLoggerDirtyInfo item = {0}; + while (logger->readData(logger, &item, sizeof(item), block)) { + switch (item.type) { + case DIRTY_REGISTER: + case DIRTY_PALETTE: + case DIRTY_OAM: + case DIRTY_VRAM: + case DIRTY_SCANLINE: + case DIRTY_FLUSH: + case DIRTY_FRAME: + case DIRTY_RANGE: + case DIRTY_BUFFER: + if (!logger->parsePacket(logger, &item)) { + return true; + } + break; + default: + return false; + } + } + return !block; +} + +static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) { + struct mVideoLogChannel* channel = logger->dataContext; + return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length; +} + +static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length) { + UNUSED(logger); + UNUSED(data); + UNUSED(length); + return false; +} + +static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block) { + UNUSED(block); + struct mVideoLogChannel* channel = logger->dataContext; + return mVideoLoggerReadChannel(channel, data, length) == (ssize_t) length; +} + +#ifdef USE_ZLIB +static void _copyVf(struct VFile* dest, struct VFile* src) { + size_t size = src->size(src); + void* mem = src->map(src, size, MAP_READ); + dest->write(dest, mem, size); + src->unmap(src, mem, size); +} + +static void _compress(struct VFile* dest, struct VFile* src) { + uint8_t writeBuffer[0x800]; + uint8_t compressBuffer[0x400]; + z_stream zstr; + zstr.zalloc = Z_NULL; + zstr.zfree = Z_NULL; + zstr.opaque = Z_NULL; + zstr.avail_in = 0; + zstr.avail_out = sizeof(compressBuffer); + zstr.next_out = (Bytef*) compressBuffer; + if (deflateInit(&zstr, 9) != Z_OK) { + return; + } + + while (true) { + size_t read = src->read(src, writeBuffer, sizeof(writeBuffer)); + if (!read) { + break; + } + zstr.avail_in = read; + zstr.next_in = (Bytef*) writeBuffer; + while (zstr.avail_in) { + if (deflate(&zstr, Z_NO_FLUSH) == Z_STREAM_ERROR) { + break; + } + dest->write(dest, compressBuffer, sizeof(compressBuffer) - zstr.avail_out); + zstr.avail_out = sizeof(compressBuffer); + zstr.next_out = (Bytef*) compressBuffer; + } + } + + do { + zstr.avail_out = sizeof(compressBuffer); + zstr.next_out = (Bytef*) compressBuffer; + zstr.avail_in = 0; + int ret = deflate(&zstr, Z_FINISH); + if (ret == Z_STREAM_ERROR) { + break; + } + dest->write(dest, compressBuffer, sizeof(compressBuffer) - zstr.avail_out); + } while (sizeof(compressBuffer) - zstr.avail_out); +} + +static bool _decompress(struct VFile* dest, struct VFile* src, size_t compressedLength) { + uint8_t fbuffer[0x400]; + uint8_t zbuffer[0x800]; + z_stream zstr; + zstr.zalloc = Z_NULL; + zstr.zfree = Z_NULL; + zstr.opaque = Z_NULL; + zstr.avail_in = 0; + zstr.avail_out = sizeof(zbuffer); + zstr.next_out = (Bytef*) zbuffer; + bool started = false; + + while (true) { + size_t thisWrite = sizeof(zbuffer); + size_t thisRead = 0; + if (zstr.avail_in) { + zstr.next_out = zbuffer; + zstr.avail_out = thisWrite; + thisRead = zstr.avail_in; + } else if (compressedLength) { + thisRead = sizeof(fbuffer); + if (thisRead > compressedLength) { + thisRead = compressedLength; + } + + thisRead = src->read(src, fbuffer, thisRead); + if (thisRead <= 0) { + break; + } + + zstr.next_in = fbuffer; + zstr.avail_in = thisRead; + zstr.next_out = zbuffer; + zstr.avail_out = thisWrite; + + if (!started) { + if (inflateInit(&zstr) != Z_OK) { + break; + } + started = true; + } + } else { + zstr.next_in = Z_NULL; + zstr.avail_in = 0; + zstr.next_out = zbuffer; + zstr.avail_out = thisWrite; + } + + int ret = inflate(&zstr, Z_NO_FLUSH); + + if (zstr.next_in != Z_NULL) { + thisRead -= zstr.avail_in; + compressedLength -= thisRead; + } + + if (ret != Z_OK) { + inflateEnd(&zstr); + started = false; + if (ret != Z_STREAM_END) { + break; + } + } + + thisWrite = dest->write(dest, zbuffer, thisWrite - zstr.avail_out); + + if (!started) { + break; + } + } + return !compressedLength; +} +#endif + +void mVideoLoggerAttachChannel(struct mVideoLogger* logger, struct mVideoLogContext* context, size_t channelId) { + if (channelId >= mVL_MAX_CHANNELS) { + return; + } + logger->dataContext = &context->channels[channelId]; +} + +struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core) { + struct mVideoLogContext* context = malloc(sizeof(*context)); + memset(context, 0, sizeof(*context)); + + context->write = !!core; + context->initialStateSize = 0; + context->initialState = NULL; + + if (core) { + context->initialStateSize = core->stateSize(core); + context->initialState = anonymousMemoryMap(context->initialStateSize); + core->saveState(core, context->initialState); + core->startVideoLog(core, context); + } + + context->activeChannel = 0; + return context; +} + +void mVideoLogContextSetOutput(struct mVideoLogContext* context, struct VFile* vf) { + context->backing = vf; + vf->truncate(vf, 0); + vf->seek(vf, 0, SEEK_SET); +} + +void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore* core) { + struct mVideoLogHeader header = { { 0 } }; + memcpy(header.magic, mVL_MAGIC, sizeof(header.magic)); + enum mPlatform platform = core->platform(core); + STORE_32LE(platform, 0, &header.platform); + STORE_32LE(context->nChannels, 0, &header.nChannels); + + uint32_t flags = 0; + if (context->initialState) { + flags |= mVL_FLAG_HAS_INITIAL_STATE; + } + STORE_32LE(flags, 0, &header.flags); + context->backing->write(context->backing, &header, sizeof(header)); + if (context->initialState) { + struct mVLBlockHeader chheader = { 0 }; + STORE_32LE(mVL_BLOCK_INITIAL_STATE, 0, &chheader.blockType); +#ifdef USE_ZLIB + STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &chheader.flags); + + struct VFile* vfm = VFileMemChunk(NULL, 0); + struct VFile* src = VFileFromConstMemory(context->initialState, context->initialStateSize); + _compress(vfm, src); + src->close(src); + STORE_32LE(vfm->size(vfm), 0, &chheader.length); + context->backing->write(context->backing, &chheader, sizeof(chheader)); + _copyVf(context->backing, vfm); + vfm->close(vfm); +#else + STORE_32LE(context->initialStateSize, 0, &chheader.length); + context->backing->write(context->backing, &chheader, sizeof(chheader)); + context->backing->write(context->backing, context->initialState, context->initialStateSize); +#endif + } + + size_t i; + for (i = 0; i < context->nChannels; ++i) { + struct mVLBlockHeader chheader = { 0 }; + STORE_32LE(mVL_BLOCK_CHANNEL_HEADER, 0, &chheader.blockType); + STORE_32LE(i, 0, &chheader.channelId); + context->backing->write(context->backing, &chheader, sizeof(chheader)); + } +} + +bool _readBlockHeader(struct mVideoLogContext* context, struct mVLBlockHeader* header) { + struct mVLBlockHeader buffer; + if (context->backing->read(context->backing, &buffer, sizeof(buffer)) != sizeof(buffer)) { + return false; + } + LOAD_32LE(header->blockType, 0, &buffer.blockType); + LOAD_32LE(header->length, 0, &buffer.length); + LOAD_32LE(header->channelId, 0, &buffer.channelId); + LOAD_32LE(header->flags, 0, &buffer.flags); + + if (header->length > MAX_BLOCK_SIZE) { + // Pre-emptively reject blocks that are too big. + // If we encounter one, the file is probably corrupted. + return false; + } + return true; +} + +bool _readHeader(struct mVideoLogContext* context) { + struct mVideoLogHeader header; + context->backing->seek(context->backing, 0, SEEK_SET); + if (context->backing->read(context->backing, &header, sizeof(header)) != sizeof(header)) { + return false; + } + if (memcmp(header.magic, mVL_MAGIC, sizeof(header.magic)) != 0) { + return false; + } + + LOAD_32LE(context->nChannels, 0, &header.nChannels); + if (context->nChannels > mVL_MAX_CHANNELS) { + return false; + } + + uint32_t flags; + LOAD_32LE(flags, 0, &header.flags); + if (flags & mVL_FLAG_HAS_INITIAL_STATE) { + struct mVLBlockHeader header; + if (!_readBlockHeader(context, &header)) { + return false; + } + if (header.blockType != mVL_BLOCK_INITIAL_STATE || !header.length) { + return false; + } + if (context->initialState) { + mappedMemoryFree(context->initialState, context->initialStateSize); + context->initialState = NULL; + context->initialStateSize = 0; + } + if (header.flags & mVL_FLAG_BLOCK_COMPRESSED) { +#ifdef USE_ZLIB + struct VFile* vfm = VFileMemChunk(NULL, 0); + if (!_decompress(vfm, context->backing, header.length)) { + vfm->close(vfm); + return false; + } + context->initialStateSize = vfm->size(vfm); + context->initialState = anonymousMemoryMap(context->initialStateSize); + void* mem = vfm->map(vfm, context->initialStateSize, MAP_READ); + memcpy(context->initialState, mem, context->initialStateSize); + vfm->unmap(vfm, mem, context->initialStateSize); + vfm->close(vfm); +#else + return false; +#endif + } else { + context->initialStateSize = header.length; + context->initialState = anonymousMemoryMap(header.length); + context->backing->read(context->backing, context->initialState, context->initialStateSize); + } + } + return true; +} + +bool mVideoLogContextLoad(struct mVideoLogContext* context, struct VFile* vf) { + context->backing = vf; + + if (!_readHeader(context)) { + return false; + } + + off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR); + + size_t i; + for (i = 0; i < context->nChannels; ++i) { + CircleBufferInit(&context->channels[i].buffer, BUFFER_BASE_SIZE); + context->channels[i].bufferRemaining = 0; + context->channels[i].currentPointer = pointer; + context->channels[i].p = context; +#ifdef USE_ZLIB + context->channels[i].inflating = false; +#endif + } + return true; +} + +#ifdef USE_ZLIB +static void _flushBufferCompressed(struct mVideoLogContext* context) { + struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer; + if (!CircleBufferSize(buffer)) { + return; + } + struct VFile* vfm = VFileMemChunk(NULL, 0); + struct VFile* src = VFileFIFO(buffer); + _compress(vfm, src); + src->close(src); + + size_t size = vfm->size(vfm); + + struct mVLBlockHeader header = { 0 }; + STORE_32LE(mVL_BLOCK_DATA, 0, &header.blockType); + STORE_32LE(context->activeChannel, 0, &header.channelId); + STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &header.flags); + STORE_32LE(size, 0, &header.length); + + context->backing->write(context->backing, &header, sizeof(header)); + _copyVf(context->backing, vfm); + vfm->close(vfm); +} +#endif + +static void _flushBuffer(struct mVideoLogContext* context) { +#ifdef USE_ZLIB + // TODO: Make optional + _flushBufferCompressed(context); + return; +#endif + + struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer; + if (!CircleBufferSize(buffer)) { + return; + } + struct mVLBlockHeader header = { 0 }; + STORE_32LE(mVL_BLOCK_DATA, 0, &header.blockType); + STORE_32LE(CircleBufferSize(buffer), 0, &header.length); + STORE_32LE(context->activeChannel, 0, &header.channelId); + + context->backing->write(context->backing, &header, sizeof(header)); + + uint8_t writeBuffer[0x800]; + while (CircleBufferSize(buffer)) { + size_t read = CircleBufferRead(buffer, writeBuffer, sizeof(writeBuffer)); + context->backing->write(context->backing, writeBuffer, read); + } +} + +void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext* context) { + if (context->write) { + _flushBuffer(context); + + struct mVLBlockHeader header = { 0 }; + STORE_32LE(mVL_BLOCK_FOOTER, 0, &header.blockType); + context->backing->write(context->backing, &header, sizeof(header)); + } + + if (core) { + core->endVideoLog(core); + } + if (context->initialState) { + mappedMemoryFree(context->initialState, context->initialStateSize); + } + free(context); +} + +void mVideoLogContextRewind(struct mVideoLogContext* context, struct mCore* core) { + _readHeader(context); + if (core && core->stateSize(core) == context->initialStateSize) { + core->loadState(core, context->initialState); + } + + off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR); + + size_t i; + for (i = 0; i < context->nChannels; ++i) { + CircleBufferClear(&context->channels[i].buffer); + context->channels[i].bufferRemaining = 0; + context->channels[i].currentPointer = pointer; +#ifdef USE_ZLIB + if (context->channels[i].inflating) { + inflateEnd(&context->channels[i].inflateStream); + context->channels[i].inflating = false; + } +#endif + } +} + +void* mVideoLogContextInitialState(struct mVideoLogContext* context, size_t* size) { + if (size) { + *size = context->initialStateSize; + } + return context->initialState; +} + +int mVideoLoggerAddChannel(struct mVideoLogContext* context) { + if (context->nChannels >= mVL_MAX_CHANNELS) { + return -1; + } + int chid = context->nChannels; + ++context->nChannels; + context->channels[chid].p = context; + CircleBufferInit(&context->channels[chid].buffer, BUFFER_BASE_SIZE); + return chid; +} + +#ifdef USE_ZLIB +static size_t _readBufferCompressed(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) { + uint8_t fbuffer[0x400]; + uint8_t zbuffer[0x800]; + size_t read = 0; + + // TODO: Share with _decompress + channel->inflateStream.avail_in = 0; + while (length) { + size_t thisWrite = sizeof(zbuffer); + if (thisWrite > length) { + thisWrite = length; + } + + size_t thisRead = 0; + if (channel->inflating && channel->inflateStream.avail_in) { + channel->inflateStream.next_out = zbuffer; + channel->inflateStream.avail_out = thisWrite; + thisRead = channel->inflateStream.avail_in; + } else if (channel->bufferRemaining) { + thisRead = sizeof(fbuffer); + if (thisRead > channel->bufferRemaining) { + thisRead = channel->bufferRemaining; + } + + thisRead = vf->read(vf, fbuffer, thisRead); + if (thisRead <= 0) { + break; + } + + channel->inflateStream.next_in = fbuffer; + channel->inflateStream.avail_in = thisRead; + channel->inflateStream.next_out = zbuffer; + channel->inflateStream.avail_out = thisWrite; + + if (!channel->inflating) { + if (inflateInit(&channel->inflateStream) != Z_OK) { + break; + } + channel->inflating = true; + } + } else { + channel->inflateStream.next_in = Z_NULL; + channel->inflateStream.avail_in = 0; + channel->inflateStream.next_out = zbuffer; + channel->inflateStream.avail_out = thisWrite; + } + + int ret = inflate(&channel->inflateStream, Z_NO_FLUSH); + + if (channel->inflateStream.next_in != Z_NULL) { + thisRead -= channel->inflateStream.avail_in; + channel->currentPointer += thisRead; + channel->bufferRemaining -= thisRead; + } + + if (ret != Z_OK) { + inflateEnd(&channel->inflateStream); + channel->inflating = false; + if (ret != Z_STREAM_END) { + break; + } + } + + thisWrite = CircleBufferWrite(&channel->buffer, zbuffer, thisWrite - channel->inflateStream.avail_out); + length -= thisWrite; + read += thisWrite; + + if (!channel->inflating) { + break; + } + } + return read; +} +#endif + +static void _readBuffer(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) { + uint8_t buffer[0x800]; + while (length) { + size_t thisRead = sizeof(buffer); + if (thisRead > length) { + thisRead = length; + } + thisRead = vf->read(vf, buffer, thisRead); + if (thisRead <= 0) { + return; + } + size_t thisWrite = CircleBufferWrite(&channel->buffer, buffer, thisRead); + length -= thisWrite; + channel->bufferRemaining -= thisWrite; + channel->currentPointer += thisWrite; + if (thisWrite < thisRead) { + break; + } + } +} + +static bool _fillBuffer(struct mVideoLogContext* context, size_t channelId, size_t length) { + struct mVideoLogChannel* channel = &context->channels[channelId]; + context->backing->seek(context->backing, channel->currentPointer, SEEK_SET); + struct mVLBlockHeader header; + while (length) { + size_t bufferRemaining = channel->bufferRemaining; + if (bufferRemaining) { +#ifdef USE_ZLIB + if (channel->inflating) { + length -= _readBufferCompressed(context->backing, channel, length); + continue; + } +#endif + if (bufferRemaining > length) { + bufferRemaining = length; + } + + _readBuffer(context->backing, channel, bufferRemaining); + length -= bufferRemaining; + continue; + } + + if (!_readBlockHeader(context, &header)) { + return false; + } + if (header.blockType == mVL_BLOCK_FOOTER) { + return true; + } + if (header.channelId != channelId || header.blockType != mVL_BLOCK_DATA) { + context->backing->seek(context->backing, header.length, SEEK_CUR); + continue; + } + channel->currentPointer = context->backing->seek(context->backing, 0, SEEK_CUR); + if (!header.length) { + continue; + } + channel->bufferRemaining = header.length; + + if (header.flags & mVL_FLAG_BLOCK_COMPRESSED) { +#ifdef USE_ZLIB + length -= _readBufferCompressed(context->backing, channel, length); +#else + return false; +#endif + } + } + return true; +} + +static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* data, size_t length) { + struct mVideoLogContext* context = channel->p; + unsigned channelId = channel - context->channels; + if (channelId >= mVL_MAX_CHANNELS) { + return 0; + } + if (CircleBufferSize(&channel->buffer) >= length) { + return CircleBufferRead(&channel->buffer, data, length); + } + ssize_t size = 0; + if (CircleBufferSize(&channel->buffer)) { + size = CircleBufferRead(&channel->buffer, data, CircleBufferSize(&channel->buffer)); + if (size <= 0) { + return size; + } + data = (uint8_t*) data + size; + length -= size; + } + if (!_fillBuffer(context, channelId, BUFFER_BASE_SIZE)) { + return size; + } + size += CircleBufferRead(&channel->buffer, data, length); + return size; +} + +static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const void* data, size_t length) { + struct mVideoLogContext* context = channel->p; + unsigned channelId = channel - context->channels; + if (channelId >= mVL_MAX_CHANNELS) { + return 0; + } + if (channelId != context->activeChannel) { + _flushBuffer(context); + context->activeChannel = channelId; + } + if (CircleBufferCapacity(&channel->buffer) - CircleBufferSize(&channel->buffer) < length) { + _flushBuffer(context); + if (CircleBufferCapacity(&channel->buffer) < length) { + CircleBufferDeinit(&channel->buffer); + CircleBufferInit(&channel->buffer, toPow2(length << 1)); + } + } + + ssize_t read = CircleBufferWrite(&channel->buffer, data, length); + if (CircleBufferCapacity(&channel->buffer) == CircleBufferSize(&channel->buffer)) { + _flushBuffer(context); + } + return read; +} + +struct mCore* mVideoLogCoreFind(struct VFile* vf) { + if (!vf) { + return NULL; + } + struct mVideoLogHeader header = { { 0 } }; + vf->seek(vf, 0, SEEK_SET); + ssize_t read = vf->read(vf, &header, sizeof(header)); + if (read != sizeof(header)) { + return NULL; + } + if (memcmp(header.magic, mVL_MAGIC, sizeof(header.magic)) != 0) { + return NULL; + } + enum mPlatform platform; + LOAD_32LE(platform, 0, &header.platform); + + const struct mVLDescriptor* descriptor; + for (descriptor = &_descriptors[0]; descriptor->platform != PLATFORM_NONE; ++descriptor) { + if (platform == descriptor->platform) { + break; + } + } + struct mCore* core = NULL; + if (descriptor->open) { + core = descriptor->open(); + } + return core; +}
M src/gb/audio.csrc/gb/audio.c

@@ -640,8 +640,8 @@ audio->lastLeft = sampleLeft;

audio->lastRight = sampleRight; audio->clock += audio->sampleInterval; if (audio->clock >= CLOCKS_PER_BLIP_FRAME) { - blip_end_frame(audio->left, audio->clock); - blip_end_frame(audio->right, audio->clock); + blip_end_frame(audio->left, CLOCKS_PER_BLIP_FRAME); + blip_end_frame(audio->right, CLOCKS_PER_BLIP_FRAME); audio->clock -= CLOCKS_PER_BLIP_FRAME; } }
M src/gb/core.csrc/gb/core.c

@@ -6,13 +6,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <mgba/gb/core.h> #include <mgba/core/core.h> +#include <mgba/internal/debugger/symbols.h> #include <mgba/internal/gb/cheats.h> +#include <mgba/internal/gb/debugger/symbols.h> #include <mgba/internal/gb/extra/cli.h> +#include <mgba/internal/gb/io.h> #include <mgba/internal/gb/gb.h> #include <mgba/internal/gb/input.h> #include <mgba/internal/gb/mbc.h> #include <mgba/internal/gb/overrides.h> #include <mgba/internal/gb/renderers/software.h> +#include <mgba/internal/gb/renderers/proxy.h> #include <mgba/internal/gb/serialize.h> #include <mgba/internal/lr35902/lr35902.h> #include <mgba/internal/lr35902/debugger/debugger.h>

@@ -25,9 +29,40 @@ #ifndef MINIMAL_CORE

#include <mgba/internal/gba/input.h> #endif +const static struct mCoreChannelInfo _GBVideoLayers[] = { + { 0, "bg", "Background", NULL }, + { 1, "obj", "Objects", NULL }, + { 2, "win", "Window", NULL }, +}; + +const static struct mCoreChannelInfo _GBAudioChannels[] = { + { 0, "ch0", "Channel 0", "Square/Sweep" }, + { 1, "ch1", "Channel 1", "Square" }, + { 2, "ch2", "Channel 2", "PCM" }, + { 3, "ch3", "Channel 3", "Noise" }, +}; + +const static struct LR35902Segment _GBSegments[] = { + { .name = "ROM", .start = GB_BASE_CART_BANK1, .end = GB_BASE_VRAM }, + { .name = "RAM", .start = GB_BASE_EXTERNAL_RAM, .end = GB_BASE_WORKING_RAM_BANK0 }, + { 0 } +}; + +const static struct LR35902Segment _GBCSegments[] = { + { .name = "ROM", .start = GB_BASE_CART_BANK1, .end = GB_BASE_VRAM }, + { .name = "RAM", .start = GB_BASE_EXTERNAL_RAM, .end = GB_BASE_WORKING_RAM_BANK0 }, + { .name = "WRAM", .start = GB_BASE_WORKING_RAM_BANK1, .end = 0xE000 }, + { .name = "VRAM", .start = GB_BASE_VRAM, .end = GB_BASE_EXTERNAL_RAM }, + { 0 } +}; + +struct mVideoLogContext; struct GBCore { struct mCore d; struct GBVideoSoftwareRenderer renderer; + struct GBVideoProxyRenderer proxyRenderer; + struct mVideoLogContext* logContext; + struct mCoreCallbacks logCallbacks; uint8_t keys; struct mCPUComponent* components[CPU_COMPONENT_MAX]; const struct Configuration* overrides;

@@ -80,8 +115,11 @@ LR35902Deinit(core->cpu);

GBDestroy(core->board); mappedMemoryFree(core->cpu, sizeof(struct LR35902Core)); mappedMemoryFree(core->board, sizeof(struct GB)); -#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 +#if defined USE_DEBUGGERS && (!defined(MINIMAL_CORE) || MINIMAL_CORE < 2) mDirectorySetDeinit(&core->dirs); + if (core->symbolTable) { + mDebuggerSymbolTableDestroy(core->symbolTable); + } #endif struct GBCore* gbcore = (struct GBCore*) core;

@@ -114,6 +152,21 @@ } else {

gb->audio.masterVolume = core->opts.volume; } gb->video.frameskip = core->opts.frameskip; + + int color; + if (mCoreConfigGetIntValue(&core->config, "gb.pal[0]", &color)) { + GBVideoSetPalette(&gb->video, 0, color); + } + if (mCoreConfigGetIntValue(&core->config, "gb.pal[1]", &color)) { + GBVideoSetPalette(&gb->video, 1, color); + } + if (mCoreConfigGetIntValue(&core->config, "gb.pal[2]", &color)) { + GBVideoSetPalette(&gb->video, 2, color); + } + if (mCoreConfigGetIntValue(&core->config, "gb.pal[3]", &color)) { + GBVideoSetPalette(&gb->video, 3, color); + } + mCoreConfigCopyValue(&core->config, config, "gb.bios"); mCoreConfigCopyValue(&core->config, config, "gbc.bios");

@@ -402,14 +455,18 @@ static void _GBCoreGetGameCode(const struct mCore* core, char* title) {

GBGetGameCode(core->board, title); } -static void _GBCoreSetRotation(struct mCore* core, struct mRotationSource* rotation) { +static void _GBCoreSetPeripheral(struct mCore* core, int type, void* periph) { struct GB* gb = core->board; - gb->memory.rotation = rotation; -} - -static void _GBCoreSetRumble(struct mCore* core, struct mRumble* rumble) { - struct GB* gb = core->board; - gb->memory.rumble = rumble; + switch (type) { + case mPERIPH_ROTATION: + gb->memory.rotation = periph; + break; + case mPERIPH_RUMBLE: + gb->memory.rumble = periph; + break; + default: + return; + } } static uint32_t _GBCoreBusRead8(struct mCore* core, uint32_t address) {

@@ -495,8 +552,15 @@ }

static struct mDebuggerPlatform* _GBCoreDebuggerPlatform(struct mCore* core) { struct GBCore* gbcore = (struct GBCore*) core; + struct GB* gb = core->board; if (!gbcore->debuggerPlatform) { - gbcore->debuggerPlatform = LR35902DebuggerPlatformCreate(); + struct LR35902Debugger* platform = (struct LR35902Debugger*) LR35902DebuggerPlatformCreate(); + if (gb->model >= GB_MODEL_CGB) { + platform->segments = _GBCSegments; + } else { + platform->segments = _GBSegments; + } + gbcore->debuggerPlatform = &platform->d; } return gbcore->debuggerPlatform; }

@@ -523,6 +587,19 @@ }

cpu->components[CPU_COMPONENT_DEBUGGER] = NULL; core->debugger = NULL; } + +static void _GBCoreLoadSymbols(struct mCore* core, struct VFile* vf) { + core->symbolTable = mDebuggerSymbolTableCreate(); +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 + if (!vf) { + vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".sym", O_RDONLY); + } +#endif + if (!vf) { + return; + } + GBLoadSymbols(core->symbolTable, vf); +} #endif static struct mCheatDevice* _GBCoreCheatDevice(struct mCore* core) {

@@ -569,6 +646,72 @@ memcpy(gb->memory.sram, sram, size);

return true; } +static size_t _GBCoreListVideoLayers(const struct mCore* core, const struct mCoreChannelInfo** info) { + UNUSED(core); + *info = _GBVideoLayers; + return sizeof(_GBVideoLayers) / sizeof(*_GBVideoLayers); +} + +static size_t _GBCoreListAudioChannels(const struct mCore* core, const struct mCoreChannelInfo** info) { + UNUSED(core); + *info = _GBAudioChannels; + return sizeof(_GBAudioChannels) / sizeof(*_GBAudioChannels); +} + +static void _GBCoreEnableVideoLayer(struct mCore* core, size_t id, bool enable) { + struct GB* gb = core->board; + switch (id) { + case 0: + gb->video.renderer->disableBG = !enable; + break; + case 1: + gb->video.renderer->disableOBJ = !enable; + break; + case 2: + gb->video.renderer->disableWIN = !enable; + break; + default: + break; + } +} + +static void _GBCoreEnableAudioChannel(struct mCore* core, size_t id, bool enable) { + struct GB* gb = core->board; + switch (id) { + case 0: + case 1: + case 2: + case 3: + gb->audio.forceDisableCh[id] = !enable; + break; + default: + break; + } +} + +static void _GBCoreStartVideoLog(struct mCore* core, struct mVideoLogContext* context) { + struct GBCore* gbcore = (struct GBCore*) core; + struct GB* gb = core->board; + gbcore->logContext = context; + + int channelId = mVideoLoggerAddChannel(context); + gbcore->proxyRenderer.logger = malloc(sizeof(struct mVideoLogger)); + mVideoLoggerRendererCreate(gbcore->proxyRenderer.logger, false); + mVideoLoggerAttachChannel(gbcore->proxyRenderer.logger, context, channelId); + gbcore->proxyRenderer.logger->block = false; + + GBVideoProxyRendererCreate(&gbcore->proxyRenderer, &gbcore->renderer.d); + GBVideoProxyRendererShim(&gb->video, &gbcore->proxyRenderer); +} + +static void _GBCoreEndVideoLog(struct mCore* core) { + struct GBCore* gbcore = (struct GBCore*) core; + struct GB* gb = core->board; + GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer); + free(gbcore->proxyRenderer.logger); + gbcore->proxyRenderer.logger = NULL; +} + struct mCore* GBCoreCreate(void) { struct GBCore* gbcore = malloc(sizeof(*gbcore)); struct mCore* core = &gbcore->d;

@@ -576,6 +719,7 @@ memset(&core->opts, 0, sizeof(core->opts));

core->cpu = NULL; core->board = NULL; core->debugger = NULL; + core->symbolTable = NULL; core->init = _GBCoreInit; core->deinit = _GBCoreDeinit; core->platform = _GBCorePlatform;

@@ -614,8 +758,7 @@ core->frameCycles = _GBCoreFrameCycles;

core->frequency = _GBCoreFrequency; core->getGameTitle = _GBCoreGetGameTitle; core->getGameCode = _GBCoreGetGameCode; - core->setRotation = _GBCoreSetRotation; - core->setRumble = _GBCoreSetRumble; + core->setPeripheral = _GBCoreSetPeripheral; core->busRead8 = _GBCoreBusRead8; core->busRead16 = _GBCoreBusRead16; core->busRead32 = _GBCoreBusRead32;

@@ -634,9 +777,129 @@ core->debuggerPlatform = _GBCoreDebuggerPlatform;

core->cliDebuggerSystem = _GBCoreCliDebuggerSystem; core->attachDebugger = _GBCoreAttachDebugger; core->detachDebugger = _GBCoreDetachDebugger; + core->loadSymbols = _GBCoreLoadSymbols; #endif core->cheatDevice = _GBCoreCheatDevice; core->savedataClone = _GBCoreSavedataClone; core->savedataRestore = _GBCoreSavedataRestore; + core->listVideoLayers = _GBCoreListVideoLayers; + core->listAudioChannels = _GBCoreListAudioChannels; + core->enableVideoLayer = _GBCoreEnableVideoLayer; + core->enableAudioChannel = _GBCoreEnableAudioChannel; +#ifndef MINIMAL_CORE + core->startVideoLog = _GBCoreStartVideoLog; + core->endVideoLog = _GBCoreEndVideoLog; +#endif return core; } + +#ifndef MINIMAL_CORE +static void _GBVLPStartFrameCallback(void *context) { + struct mCore* core = context; + struct GBCore* gbcore = (struct GBCore*) core; + struct GB* gb = core->board; + + if (!mVideoLoggerRendererRun(gbcore->proxyRenderer.logger, true)) { + GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer); + mVideoLogContextRewind(gbcore->logContext, core); + GBVideoProxyRendererShim(&gb->video, &gbcore->proxyRenderer); + } +} + +static bool _GBVLPInit(struct mCore* core) { + struct GBCore* gbcore = (struct GBCore*) core; + if (!_GBCoreInit(core)) { + return false; + } + gbcore->proxyRenderer.logger = malloc(sizeof(struct mVideoLogger)); + mVideoLoggerRendererCreate(gbcore->proxyRenderer.logger, true); + GBVideoProxyRendererCreate(&gbcore->proxyRenderer, NULL); + memset(&gbcore->logCallbacks, 0, sizeof(gbcore->logCallbacks)); + gbcore->logCallbacks.videoFrameStarted = _GBVLPStartFrameCallback; + gbcore->logCallbacks.context = core; + core->addCoreCallbacks(core, &gbcore->logCallbacks); + return true; +} + +static void _GBVLPDeinit(struct mCore* core) { + struct GBCore* gbcore = (struct GBCore*) core; + if (gbcore->logContext) { + mVideoLogContextDestroy(core, gbcore->logContext); + } + _GBCoreDeinit(core); +} + +static void _GBVLPReset(struct mCore* core) { + struct GBCore* gbcore = (struct GBCore*) core; + struct GB* gb = (struct GB*) core->board; + if (gb->video.renderer == &gbcore->proxyRenderer.d) { + GBVideoProxyRendererUnshim(&gb->video, &gbcore->proxyRenderer); + } else if (gbcore->renderer.outputBuffer) { + struct GBVideoRenderer* renderer = &gbcore->renderer.d; + GBVideoAssociateRenderer(&gb->video, renderer); + } + + LR35902Reset(core->cpu); + mVideoLogContextRewind(gbcore->logContext, core); + GBVideoProxyRendererShim(&gb->video, &gbcore->proxyRenderer); + + // Make sure CPU loop never spins + GBHalt(gb->cpu); + gb->memory.ie = 0; + gb->memory.ime = false; +} + +static bool _GBVLPLoadROM(struct mCore* core, struct VFile* vf) { + struct GBCore* gbcore = (struct GBCore*) core; + gbcore->logContext = mVideoLogContextCreate(NULL); + if (!mVideoLogContextLoad(gbcore->logContext, vf)) { + mVideoLogContextDestroy(core, gbcore->logContext); + gbcore->logContext = NULL; + return false; + } + mVideoLoggerAttachChannel(gbcore->proxyRenderer.logger, gbcore->logContext, 0); + return true; +} + +static bool _GBVLPLoadState(struct mCore* core, const void* buffer) { + struct GB* gb = (struct GB*) core->board; + const struct GBSerializedState* state = buffer; + + gb->timing.root = NULL; + gb->model = state->model; + + gb->cpu->pc = GB_BASE_HRAM; + gb->cpu->memory.setActiveRegion(gb->cpu, gb->cpu->pc); + + GBVideoDeserialize(&gb->video, state); + GBIODeserialize(gb, state); + GBAudioReset(&gb->audio); + + // Make sure CPU loop never spins + GBHalt(gb->cpu); + gb->memory.ie = 0; + gb->memory.ime = false; + + return true; +} + +static bool _returnTrue(struct VFile* vf) { + UNUSED(vf); + return true; +} + +struct mCore* GBVideoLogPlayerCreate(void) { + struct mCore* core = GBCoreCreate(); + core->init = _GBVLPInit; + core->deinit = _GBVLPDeinit; + core->reset = _GBVLPReset; + core->loadROM = _GBVLPLoadROM; + core->loadState = _GBVLPLoadState; + core->isROM = _returnTrue; + return core; +} +#else +struct mCore* GBVideoLogPlayerCreate(void) { + return false; +} +#endif
A src/gb/debugger/symbols.c

@@ -0,0 +1,55 @@

+/* Copyright (c) 2013-2016 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <mgba/internal/gb/debugger/symbols.h> + +#include <mgba/internal/debugger/symbols.h> +#include <mgba-util/string.h> +#include <mgba-util/vfs.h> + +void GBLoadSymbols(struct mDebuggerSymbols* st, struct VFile* vf) { + char line[512]; + + while (true) { + ssize_t bytesRead = vf->readline(vf, line, sizeof(line)); + if (bytesRead <= 0) { + break; + } + if (line[bytesRead - 1] == '\n') { + line[bytesRead - 1] = '\0'; + } + int segment = -1; + uint32_t address = 0; + + uint8_t byte; + const char* buf = line; + while (buf) { + buf = hex8(buf, &byte); + if (!buf) { + break; + } + address <<= 8; + address += byte; + + if (buf[0] == ':') { + segment = address; + address = 0; + ++buf; + } + if (isspace((int) buf[0])) { + break; + } + } + if (!buf) { + continue; + } + + while (isspace((int) buf[0])) { + ++buf; + } + + mDebuggerSymbolAdd(st, buf, address, segment); + } +}
A src/gb/extra/proxy.c

@@ -0,0 +1,272 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <mgba/internal/gb/renderers/proxy.h> + +#include <mgba/core/tile-cache.h> +#include <mgba/internal/gb/gb.h> +#include <mgba/internal/gb/io.h> + +#define BUFFER_OAM 1 + +static void GBVideoProxyRendererInit(struct GBVideoRenderer* renderer, enum GBModel model); +static void GBVideoProxyRendererDeinit(struct GBVideoRenderer* renderer); +static uint8_t GBVideoProxyRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value); +static void GBVideoProxyRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address); +static void GBVideoProxyRendererWriteOAM(struct GBVideoRenderer* renderer, uint16_t oam); +static void GBVideoProxyRendererWritePalette(struct GBVideoRenderer* renderer, int address, uint16_t value); +static void GBVideoProxyRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj* obj, size_t oamMax); +static void GBVideoProxyRendererFinishScanline(struct GBVideoRenderer* renderer, int y); +static void GBVideoProxyRendererFinishFrame(struct GBVideoRenderer* renderer); +static void GBVideoProxyRendererGetPixels(struct GBVideoRenderer* renderer, size_t* stride, const void** pixels); +static void GBVideoProxyRendererPutPixels(struct GBVideoRenderer* renderer, size_t stride, const void* pixels); + +static bool _parsePacket(struct mVideoLogger* logger, const struct mVideoLoggerDirtyInfo* packet); +static uint16_t* _vramBlock(struct mVideoLogger* logger, uint32_t address); + +void GBVideoProxyRendererCreate(struct GBVideoProxyRenderer* renderer, struct GBVideoRenderer* backend) { + renderer->d.init = GBVideoProxyRendererInit; + renderer->d.deinit = GBVideoProxyRendererDeinit; + renderer->d.writeVideoRegister = GBVideoProxyRendererWriteVideoRegister; + renderer->d.writeVRAM = GBVideoProxyRendererWriteVRAM; + renderer->d.writeOAM = GBVideoProxyRendererWriteOAM; + renderer->d.writePalette = GBVideoProxyRendererWritePalette; + renderer->d.drawRange = GBVideoProxyRendererDrawRange; + renderer->d.finishScanline = GBVideoProxyRendererFinishScanline; + renderer->d.finishFrame = GBVideoProxyRendererFinishFrame; + renderer->d.getPixels = GBVideoProxyRendererGetPixels; + renderer->d.putPixels = GBVideoProxyRendererPutPixels; + + renderer->logger->context = renderer; + renderer->logger->parsePacket = _parsePacket; + renderer->logger->vramBlock = _vramBlock; + renderer->logger->paletteSize = 0; + renderer->logger->vramSize = GB_SIZE_VRAM; + renderer->logger->oamSize = GB_SIZE_OAM; + + renderer->backend = backend; +} + +static void _init(struct GBVideoProxyRenderer* proxyRenderer) { + mVideoLoggerRendererInit(proxyRenderer->logger); + + if (proxyRenderer->logger->block) { + proxyRenderer->backend->vram = (uint8_t*) proxyRenderer->logger->vram; + proxyRenderer->backend->oam = (union GBOAM*) proxyRenderer->logger->oam; + proxyRenderer->backend->cache = NULL; + } +} + +static void _reset(struct GBVideoProxyRenderer* proxyRenderer, enum GBModel model) { + memcpy(proxyRenderer->logger->oam, &proxyRenderer->d.oam->raw, GB_SIZE_OAM); + memcpy(proxyRenderer->logger->vram, proxyRenderer->d.vram, GB_SIZE_VRAM); + + proxyRenderer->oamMax = 0; + + mVideoLoggerRendererReset(proxyRenderer->logger); +} + +void GBVideoProxyRendererShim(struct GBVideo* video, struct GBVideoProxyRenderer* renderer) { + if ((renderer->backend && video->renderer != renderer->backend) || video->renderer == &renderer->d) { + return; + } + renderer->backend = video->renderer; + video->renderer = &renderer->d; + renderer->d.cache = renderer->backend->cache; + renderer->d.vram = video->vram; + renderer->d.oam = &video->oam; + _init(renderer); + _reset(renderer, video->p->model); +} + +void GBVideoProxyRendererUnshim(struct GBVideo* video, struct GBVideoProxyRenderer* renderer) { + if (video->renderer != &renderer->d) { + return; + } + renderer->backend->cache = video->renderer->cache; + video->renderer = renderer->backend; + renderer->backend->vram = video->vram; + renderer->backend->oam = &video->oam; + + mVideoLoggerRendererDeinit(renderer->logger); +} + +void GBVideoProxyRendererInit(struct GBVideoRenderer* renderer, enum GBModel model) { + struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; + + _init(proxyRenderer); + + proxyRenderer->backend->init(proxyRenderer->backend, model); +} + +void GBVideoProxyRendererDeinit(struct GBVideoRenderer* renderer) { + struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; + + proxyRenderer->backend->deinit(proxyRenderer->backend); + + mVideoLoggerRendererDeinit(proxyRenderer->logger); +} + +static bool _parsePacket(struct mVideoLogger* logger, const struct mVideoLoggerDirtyInfo* item) { + struct GBVideoProxyRenderer* proxyRenderer = logger->context; + switch (item->type) { + case DIRTY_REGISTER: + proxyRenderer->backend->writeVideoRegister(proxyRenderer->backend, item->address, item->value); + break; + case DIRTY_PALETTE: + if (item->address < 64) { + proxyRenderer->backend->writePalette(proxyRenderer->backend, item->address, item->value); + } + break; + case DIRTY_OAM: + if (item->address < GB_SIZE_OAM) { + logger->oam[item->address] = item->value; + proxyRenderer->backend->writeOAM(proxyRenderer->backend, item->address); + } + break; + case DIRTY_VRAM: + if (item->address <= GB_SIZE_VRAM - 0x1000) { + logger->readData(logger, &logger->vram[item->address >> 1], 0x1000, true); + proxyRenderer->backend->writeVRAM(proxyRenderer->backend, item->address); + } + break; + case DIRTY_SCANLINE: + if (item->address < GB_VIDEO_VERTICAL_PIXELS) { + proxyRenderer->backend->finishScanline(proxyRenderer->backend, item->address); + } + break; + case DIRTY_RANGE: + if (item->value < item->value2 && item->value2 <= GB_VIDEO_HORIZONTAL_PIXELS && item->address < GB_VIDEO_VERTICAL_PIXELS) { + proxyRenderer->backend->drawRange(proxyRenderer->backend, item->value, item->value2, item->address, proxyRenderer->objThisLine, proxyRenderer->oamMax); + } + break; + case DIRTY_FRAME: + proxyRenderer->backend->finishFrame(proxyRenderer->backend); + break; + case DIRTY_BUFFER: + switch (item->address) { + case BUFFER_OAM: + proxyRenderer->oamMax = item->value2 / sizeof(struct GBObj); + if (proxyRenderer->oamMax > 40) { + proxyRenderer->oamMax = 0; + return false; + } + logger->readData(logger, &proxyRenderer->objThisLine, item->value2, true); + } + break; + case DIRTY_FLUSH: + return false; + default: + return false; + } + return true; +} + +static uint16_t* _vramBlock(struct mVideoLogger* logger, uint32_t address) { + struct GBVideoProxyRenderer* proxyRenderer = logger->context; + return (uint16_t*) &proxyRenderer->d.vram[address]; +} + +uint8_t GBVideoProxyRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value) { + struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; + + mVideoLoggerRendererWriteVideoRegister(proxyRenderer->logger, address, value); + if (!proxyRenderer->logger->block) { + proxyRenderer->backend->writeVideoRegister(proxyRenderer->backend, address, value); + } + return value; +} + +void GBVideoProxyRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address) { + struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; + mVideoLoggerRendererWriteVRAM(proxyRenderer->logger, address); + if (!proxyRenderer->logger->block) { + proxyRenderer->backend->writeVRAM(proxyRenderer->backend, address); + } + if (renderer->cache) { + mTileCacheWriteVRAM(renderer->cache, address); + } +} + +void GBVideoProxyRendererWritePalette(struct GBVideoRenderer* renderer, int address, uint16_t value) { + struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; + mVideoLoggerRendererWritePalette(proxyRenderer->logger, address, value); + if (!proxyRenderer->logger->block) { + proxyRenderer->backend->writePalette(proxyRenderer->backend, address, value); + } + if (renderer->cache) { + mTileCacheWritePalette(renderer->cache, address); + } +} + +void GBVideoProxyRendererWriteOAM(struct GBVideoRenderer* renderer, uint16_t oam) { + struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; + if (!proxyRenderer->logger->block) { + proxyRenderer->backend->writeOAM(proxyRenderer->backend, oam); + } + mVideoLoggerRendererWriteOAM(proxyRenderer->logger, oam, ((uint8_t*) proxyRenderer->d.oam->raw)[oam]); +} + +void GBVideoProxyRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj* obj, size_t oamMax) { + struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; + if (!proxyRenderer->logger->block) { + proxyRenderer->backend->drawRange(proxyRenderer->backend, startX, endX, y, obj, oamMax); + } + mVideoLoggerWriteBuffer(proxyRenderer->logger, BUFFER_OAM, 0, oamMax * sizeof(*obj), obj); + mVideoLoggerRendererDrawRange(proxyRenderer->logger, startX, endX, y); +} + +void GBVideoProxyRendererFinishScanline(struct GBVideoRenderer* renderer, int y) { + struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; + if (!proxyRenderer->logger->block) { + proxyRenderer->backend->finishScanline(proxyRenderer->backend, y); + } + mVideoLoggerRendererDrawScanline(proxyRenderer->logger, y); + if (proxyRenderer->logger->block && proxyRenderer->logger->wake) { + proxyRenderer->logger->wake(proxyRenderer->logger, y); + } +} + +void GBVideoProxyRendererFinishFrame(struct GBVideoRenderer* renderer) { + struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; + if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { + proxyRenderer->logger->lock(proxyRenderer->logger); + proxyRenderer->logger->wait(proxyRenderer->logger); + } + proxyRenderer->backend->finishFrame(proxyRenderer->backend); + mVideoLoggerRendererFinishFrame(proxyRenderer->logger); + mVideoLoggerRendererFlush(proxyRenderer->logger); + if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { + proxyRenderer->logger->unlock(proxyRenderer->logger); + } +} + +static void GBVideoProxyRendererGetPixels(struct GBVideoRenderer* renderer, size_t* stride, const void** pixels) { + struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; + if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { + proxyRenderer->logger->lock(proxyRenderer->logger); + // Insert an extra item into the queue to make sure it gets flushed + mVideoLoggerRendererFlush(proxyRenderer->logger); + proxyRenderer->logger->wait(proxyRenderer->logger); + } + proxyRenderer->backend->getPixels(proxyRenderer->backend, stride, pixels); + if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { + proxyRenderer->logger->unlock(proxyRenderer->logger); + } +} + +static void GBVideoProxyRendererPutPixels(struct GBVideoRenderer* renderer, size_t stride, const void* pixels) { + struct GBVideoProxyRenderer* proxyRenderer = (struct GBVideoProxyRenderer*) renderer; + if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { + proxyRenderer->logger->lock(proxyRenderer->logger); + // Insert an extra item into the queue to make sure it gets flushed + mVideoLoggerRendererFlush(proxyRenderer->logger); + proxyRenderer->logger->wait(proxyRenderer->logger); + } + proxyRenderer->backend->putPixels(proxyRenderer->backend, stride, pixels); + if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { + proxyRenderer->logger->unlock(proxyRenderer->logger); + } +}
M src/gb/gb.csrc/gb/gb.c

@@ -124,7 +124,7 @@ gb->yankedRomSize = 0;

gb->memory.romBase = gb->memory.rom; gb->memory.romSize = gb->pristineRomSize; gb->romCrc32 = doCrc32(gb->memory.rom, gb->memory.romSize); - GBMBCSwitchBank(gb, gb->memory.currentBank); + GBMBCInit(gb); if (gb->cpu) { struct LR35902Core* cpu = gb->cpu;

@@ -133,12 +133,6 @@ }

// TODO: error check return true; -} - -bool GBLoadSave(struct GB* gb, struct VFile* vf) { - gb->sramVf = vf; - gb->sramRealVf = vf; - return vf; } static void GBSramDeinit(struct GB* gb) {

@@ -154,6 +148,16 @@ }

gb->memory.sram = 0; } +bool GBLoadSave(struct GB* gb, struct VFile* vf) { + GBSramDeinit(gb); + gb->sramVf = vf; + gb->sramRealVf = vf; + if (gb->sramSize) { + GBResizeSram(gb, gb->sramSize); + } + return vf; +} + void GBResizeSram(struct GB* gb, size_t size) { if (gb->memory.sram && size <= gb->sramSize) { return;

@@ -280,6 +284,7 @@ gb->romVf->close(gb->romVf);

gb->romVf = NULL; } gb->memory.rom = NULL; + gb->memory.mbcType = GB_MBC_AUTODETECT; gb->isPristine = false; GBSavedataUnmask(gb);

@@ -385,7 +390,9 @@ }

void GBReset(struct LR35902Core* cpu) { struct GB* gb = (struct GB*) cpu->master; + gb->memory.romBase = gb->memory.rom; GBDetectModel(gb); + if (gb->biosVf) { if (!GBIsBIOS(gb->biosVf)) { gb->biosVf->close(gb->biosVf);
M src/gb/io.csrc/gb/io.c

@@ -382,7 +382,7 @@ GBVideoWriteSTAT(&gb->video, value);

value = gb->video.stat; break; case 0x50: - if (gb->memory.romBase != gb->memory.rom) { + if (gb->memory.romBase < gb->memory.rom || gb->memory.romBase > &gb->memory.rom[gb->memory.romSize - 1]) { free(gb->memory.romBase); gb->memory.romBase = gb->memory.rom; }
M src/gb/mbc.csrc/gb/mbc.c

@@ -46,6 +46,33 @@ gb->cpu->memory.setActiveRegion(gb->cpu, gb->cpu->pc);

} } +static void _switchBank0(struct GB* gb, int bank) { + size_t bankStart = bank * GB_SIZE_CART_BANK0 << gb->memory.mbcState.mbc1.multicartStride; + if (bankStart + GB_SIZE_CART_BANK0 > gb->memory.romSize) { + mLOG(GB_MBC, GAME_ERROR, "Attempting to switch to an invalid ROM bank: %0X", bank); + bankStart &= (gb->memory.romSize - 1); + } + gb->memory.romBase = &gb->memory.rom[bankStart]; + if (gb->cpu->pc < GB_SIZE_CART_BANK0) { + gb->cpu->memory.setActiveRegion(gb->cpu, gb->cpu->pc); + } +} + +static bool _isMulticart(const uint8_t* mem) { + bool success = true; + struct VFile* vf; + + vf = VFileFromConstMemory(&mem[GB_SIZE_CART_BANK0 * 0x10], 1024); + success = success && GBIsROM(vf); + vf->close(vf); + + vf = VFileFromConstMemory(&mem[GB_SIZE_CART_BANK0 * 0x20], 1024); + success = success && GBIsROM(vf); + vf->close(vf); + + return success; +} + void GBMBCSwitchSramBank(struct GB* gb, int bank) { size_t bankStart = bank * GB_SIZE_EXTERNAL_RAM; GBResizeSram(gb, (bank + 1) * GB_SIZE_EXTERNAL_RAM);

@@ -83,6 +110,11 @@ case 1:

case 2: case 3: gb->memory.mbcType = GB_MBC1; + if (gb->memory.romSize >= GB_SIZE_CART_BANK0 * 0x31 && _isMulticart(gb->memory.rom)) { + gb->memory.mbcState.mbc1.multicartStride = 4; + } else { + gb->memory.mbcState.mbc1.multicartStride = 5; + } break; case 5: case 6:

@@ -171,6 +203,14 @@ case GB_MBC5_RUMBLE:

gb->memory.mbc = _GBMBC5; break; } + + gb->memory.currentBank = 1; + gb->memory.sramCurrentBank = 0; + gb->memory.sramAccess = false; + gb->memory.rtcAccess = false; + gb->memory.activeRtcReg = 0; + gb->memory.rtcLatched = false; + memset(&gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs)); GBResizeSram(gb, gb->sramSize);

@@ -233,6 +273,7 @@

void _GBMBC1(struct GB* gb, uint16_t address, uint8_t value) { struct GBMemory* memory = &gb->memory; int bank = value & 0x1F; + int stride = 1 << memory->mbcState.mbc1.multicartStride; switch (address >> 13) { case 0x0: switch (value) {

@@ -253,21 +294,23 @@ case 0x1:

if (!bank) { ++bank; } - GBMBCSwitchBank(gb, bank | (memory->currentBank & 0x60)); + bank &= stride - 1; + GBMBCSwitchBank(gb, bank | (memory->currentBank & (3 * stride))); break; case 0x2: bank &= 3; - if (!memory->mbcState.mbc1.mode) { - GBMBCSwitchBank(gb, (bank << 5) | (memory->currentBank & 0x1F)); - } else { + if (memory->mbcState.mbc1.mode) { + _switchBank0(gb, bank); GBMBCSwitchSramBank(gb, bank); } + GBMBCSwitchBank(gb, (bank << memory->mbcState.mbc1.multicartStride) | (memory->currentBank & (stride - 1))); break; case 0x3: memory->mbcState.mbc1.mode = value & 1; if (memory->mbcState.mbc1.mode) { - GBMBCSwitchBank(gb, memory->currentBank & 0x1F); + _switchBank0(gb, memory->currentBank >> memory->mbcState.mbc1.multicartStride); } else { + _switchBank0(gb, 0); GBMBCSwitchSramBank(gb, 0); } break;
M src/gb/memory.csrc/gb/memory.c

@@ -16,6 +16,33 @@ #include <mgba-util/memory.h>

mLOG_DEFINE_CATEGORY(GB_MEM, "GB Memory", "gb.memory"); +struct OAMBlock { + uint16_t low; + uint16_t high; +}; + +static const struct OAMBlock _oamBlockDMG[] = { + { 0xA000, 0xFE00 }, + { 0xA000, 0xFE00 }, + { 0xA000, 0xFE00 }, + { 0xA000, 0xFE00 }, + { 0x8000, 0xA000 }, + { 0xA000, 0xFE00 }, + { 0xA000, 0xFE00 }, + { 0xA000, 0xFE00 }, +}; + +static const struct OAMBlock _oamBlockCGB[] = { + { 0xA000, 0xC000 }, + { 0xA000, 0xC000 }, + { 0xA000, 0xC000 }, + { 0xA000, 0xC000 }, + { 0x8000, 0xA000 }, + { 0xA000, 0xC000 }, + { 0xC000, 0xFE00 }, + { 0xA000, 0xC000 }, +}; + static void _pristineCow(struct GB* gba); static uint8_t GBFastLoad8(struct LR35902Core* cpu, uint16_t address) {

@@ -62,6 +89,7 @@ struct LR35902Core* cpu = gb->cpu;

cpu->memory.cpuLoad8 = GBLoad8; cpu->memory.load8 = GBLoad8; cpu->memory.store8 = GBStore8; + cpu->memory.currentSegment = GBCurrentSegment; cpu->memory.setActiveRegion = GBSetActiveRegion; gb->memory.wram = 0;

@@ -130,14 +158,14 @@ gb->memory.hdmaEvent.name = "GB HDMA";

gb->memory.hdmaEvent.callback = _GBMemoryHDMAService; gb->memory.hdmaEvent.priority = 0x41; - gb->memory.sramAccess = false; - gb->memory.rtcAccess = false; - gb->memory.activeRtcReg = 0; - gb->memory.rtcLatched = false; - memset(&gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs)); - memset(&gb->memory.hram, 0, sizeof(gb->memory.hram)); - memset(&gb->memory.mbcState, 0, sizeof(gb->memory.mbcState)); + switch (gb->memory.mbcType) { + case GB_MBC1: + gb->memory.mbcState.mbc1.mode = 0; + break; + default: + memset(&gb->memory.mbcState, 0, sizeof(gb->memory.mbcState)); + } GBMBCInit(gb); gb->memory.sramBank = gb->memory.sram;

@@ -159,6 +187,16 @@

uint8_t GBLoad8(struct LR35902Core* cpu, uint16_t address) { struct GB* gb = (struct GB*) cpu->master; struct GBMemory* memory = &gb->memory; + if (gb->memory.dmaRemaining) { + const struct OAMBlock* block = gb->model < GB_MODEL_CGB ? _oamBlockDMG : _oamBlockCGB; + block = &block[memory->dmaSource >> 13]; + if (address >= block->low && address < block->high) { + return 0xFF; + } + if (address >= GB_BASE_OAM && address < GB_BASE_UNUSABLE) { + return 0xFF; + } + } switch (address >> 12) { case GB_REGION_CART_BANK0: case GB_REGION_CART_BANK0 + 1:

@@ -217,6 +255,16 @@

void GBStore8(struct LR35902Core* cpu, uint16_t address, int8_t value) { struct GB* gb = (struct GB*) cpu->master; struct GBMemory* memory = &gb->memory; + if (gb->memory.dmaRemaining) { + const struct OAMBlock* block = gb->model < GB_MODEL_CGB ? _oamBlockDMG : _oamBlockCGB; + block = &block[memory->dmaSource >> 13]; + if (address >= block->low && address < block->high) { + return; + } + if (address >= GB_BASE_OAM && address < GB_BASE_UNUSABLE) { + return; + } + } switch (address >> 12) { case GB_REGION_CART_BANK0: case GB_REGION_CART_BANK0 + 1:

@@ -258,6 +306,7 @@ memory->wramBank[address & (GB_SIZE_WORKING_RAM_BANK0 - 1)] = value;

} else if (address < GB_BASE_UNUSABLE) { if (gb->video.mode < 2) { gb->video.oam.raw[address & 0xFF] = value; + gb->video.renderer->writeOAM(gb->video.renderer, address & 0xFF); } } else if (address < GB_BASE_IO) { mLOG(GB_MEM, GAME_ERROR, "Attempt to write to unusable memory: %04X:%02X", address, value);

@@ -270,6 +319,37 @@ GBIOWrite(gb, REG_IE, value);

} } } + +int GBCurrentSegment(struct LR35902Core* cpu, uint16_t address) { + struct GB* gb = (struct GB*) cpu->master; + struct GBMemory* memory = &gb->memory; + switch (address >> 12) { + case GB_REGION_CART_BANK0: + case GB_REGION_CART_BANK0 + 1: + case GB_REGION_CART_BANK0 + 2: + case GB_REGION_CART_BANK0 + 3: + return 0; + case GB_REGION_CART_BANK1: + case GB_REGION_CART_BANK1 + 1: + case GB_REGION_CART_BANK1 + 2: + case GB_REGION_CART_BANK1 + 3: + return memory->currentBank; + case GB_REGION_VRAM: + case GB_REGION_VRAM + 1: + return gb->video.vramCurrentBank; + case GB_REGION_EXTERNAL_RAM: + case GB_REGION_EXTERNAL_RAM + 1: + return memory->sramCurrentBank; + case GB_REGION_WORKING_RAM_BANK0: + case GB_REGION_WORKING_RAM_BANK0 + 2: + return 0; + case GB_REGION_WORKING_RAM_BANK1: + return memory->wramCurrentBank; + default: + return 0; + } +} + uint8_t GBView8(struct LR35902Core* cpu, uint16_t address, int segment) { struct GB* gb = (struct GB*) cpu->master; struct GBMemory* memory = &gb->memory;

@@ -356,9 +436,6 @@ void GBMemoryDMA(struct GB* gb, uint16_t base) {

if (base > 0xF100) { return; } - gb->cpu->memory.store8 = GBDMAStore8; - gb->cpu->memory.load8 = GBDMALoad8; - gb->cpu->memory.cpuLoad8 = GBDMALoad8; mTimingSchedule(&gb->timing, &gb->memory.dmaEvent, 8); if (gb->cpu->cycles + 8 < gb->cpu->nextEvent) { gb->cpu->nextEvent = gb->cpu->cycles + 8;

@@ -392,17 +469,17 @@ }

void _GBMemoryDMAService(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GB* gb = context; + int dmaRemaining = gb->memory.dmaRemaining; + gb->memory.dmaRemaining = 0; uint8_t b = GBLoad8(gb->cpu, gb->memory.dmaSource); // TODO: Can DMA write OAM during modes 2-3? gb->video.oam.raw[gb->memory.dmaDest] = b; + gb->video.renderer->writeOAM(gb->video.renderer, gb->memory.dmaDest); ++gb->memory.dmaSource; ++gb->memory.dmaDest; - --gb->memory.dmaRemaining; + gb->memory.dmaRemaining = dmaRemaining - 1; if (gb->memory.dmaRemaining) { mTimingSchedule(timing, &gb->memory.dmaEvent, 4 - cyclesLate); - } else { - gb->cpu->memory.store8 = GBStore8; - gb->cpu->memory.load8 = GBLoad8; } }

@@ -434,61 +511,6 @@ }

} } -struct OAMBlock { - uint16_t low; - uint16_t high; -}; - -static const struct OAMBlock _oamBlockDMG[] = { - { 0xA000, 0xFE00 }, - { 0xA000, 0xFE00 }, - { 0xA000, 0xFE00 }, - { 0xA000, 0xFE00 }, - { 0x8000, 0xA000 }, - { 0xA000, 0xFE00 }, - { 0xA000, 0xFE00 }, - { 0xA000, 0xFE00 }, -}; - -static const struct OAMBlock _oamBlockCGB[] = { - { 0xA000, 0xC000 }, - { 0xA000, 0xC000 }, - { 0xA000, 0xC000 }, - { 0xA000, 0xC000 }, - { 0x8000, 0xA000 }, - { 0xA000, 0xC000 }, - { 0xC000, 0xFE00 }, - { 0xA000, 0xC000 }, -}; - -uint8_t GBDMALoad8(struct LR35902Core* cpu, uint16_t address) { - struct GB* gb = (struct GB*) cpu->master; - struct GBMemory* memory = &gb->memory; - const struct OAMBlock* block = gb->model < GB_MODEL_CGB ? _oamBlockDMG : _oamBlockCGB; - block = &block[memory->dmaSource >> 13]; - if (address >= block->low && address < block->high) { - return 0xFF; - } - if (address >= GB_BASE_OAM && address < GB_BASE_UNUSABLE) { - return 0xFF; - } - return GBLoad8(cpu, address); -} - -void GBDMAStore8(struct LR35902Core* cpu, uint16_t address, int8_t value) { - struct GB* gb = (struct GB*) cpu->master; - struct GBMemory* memory = &gb->memory; - const struct OAMBlock* block = gb->model < GB_MODEL_CGB ? _oamBlockDMG : _oamBlockCGB; - block = &block[memory->dmaSource >> 13]; - if (address >= block->low && address < block->high) { - return; - } - if (address >= GB_BASE_OAM && address < GB_BASE_UNUSABLE) { - return; - } - GBStore8(cpu, address, value); -} - void GBPatch8(struct LR35902Core* cpu, uint16_t address, int8_t value, int8_t* old, int segment) { struct GB* gb = (struct GB*) cpu->master; struct GBMemory* memory = &gb->memory;

@@ -559,6 +581,7 @@ memory->wramBank[address & (GB_SIZE_WORKING_RAM_BANK0 - 1)] = value;

} else if (address < GB_BASE_UNUSABLE) { oldValue = gb->video.oam.raw[address & 0xFF]; gb->video.oam.raw[address & 0xFF] = value; + gb->video.renderer->writeOAM(gb->video.renderer, address & 0xFF); } else if (address < GB_BASE_HRAM) { mLOG(GB_MEM, STUB, "Unimplemented memory Patch8: 0x%08X", address); return;

@@ -660,4 +683,5 @@ gb->memory.romBase = newRom;

} gb->memory.rom = newRom; GBMBCSwitchBank(gb, gb->memory.currentBank); + gb->isPristine = false; }
M src/gb/overrides.csrc/gb/overrides.c

@@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <mgba/internal/gb/overrides.h> #include <mgba/internal/gb/gb.h> +#include <mgba/internal/gb/mbc.h> #include <mgba-util/configuration.h> #include <mgba-util/crc32.h>

@@ -102,6 +103,7 @@ }

if (override->mbc != GB_MBC_AUTODETECT) { gb->memory.mbcType = override->mbc; + GBMBCInit(gb); } }
M src/gb/renderers/software.csrc/gb/renderers/software.c

@@ -14,6 +14,7 @@ static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer);

static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value); static void GBVideoSoftwareRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value); static void GBVideoSoftwareRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address); +static void GBVideoSoftwareRendererWriteOAM(struct GBVideoRenderer* renderer, uint16_t oam); static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj* obj, size_t oamMax); static void GBVideoSoftwareRendererFinishScanline(struct GBVideoRenderer* renderer, int y); static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer);

@@ -24,26 +25,15 @@ static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int startX, int endX, int sx, int sy);

static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int startX, int endX, int y); static void _clearScreen(struct GBVideoSoftwareRenderer* renderer) { - // TODO: Dynamic from dmgPalette -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - color_t palette0 = 0xFFDF; -#else - color_t palette0 = 0x7FFF; -#endif -#else - color_t palette0 = 0xFFFFFF; -#endif - int y; for (y = 0; y < GB_VIDEO_VERTICAL_PIXELS; ++y) { color_t* row = &renderer->outputBuffer[renderer->outputBufferStride * y]; int x; for (x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; x += 4) { - row[x + 0] = palette0; - row[x + 1] = palette0; - row[x + 2] = palette0; - row[x + 3] = palette0; + row[x + 0] = renderer->palette[0]; + row[x + 1] = renderer->palette[0]; + row[x + 2] = renderer->palette[0]; + row[x + 3] = renderer->palette[0]; } } }

@@ -54,11 +44,16 @@ renderer->d.deinit = GBVideoSoftwareRendererDeinit;

renderer->d.writeVideoRegister = GBVideoSoftwareRendererWriteVideoRegister; renderer->d.writePalette = GBVideoSoftwareRendererWritePalette; renderer->d.writeVRAM = GBVideoSoftwareRendererWriteVRAM; + renderer->d.writeOAM = GBVideoSoftwareRendererWriteOAM; renderer->d.drawRange = GBVideoSoftwareRendererDrawRange; renderer->d.finishScanline = GBVideoSoftwareRendererFinishScanline; renderer->d.finishFrame = GBVideoSoftwareRendererFinishFrame; renderer->d.getPixels = GBVideoSoftwareRendererGetPixels; renderer->d.putPixels = GBVideoSoftwareRendererPutPixels; + + renderer->d.disableBG = false; + renderer->d.disableOBJ = false; + renderer->d.disableWIN = false; renderer->temporaryBuffer = 0; }

@@ -85,9 +80,6 @@ static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value) {

struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; switch (address) { case REG_LCDC: - if (GBRegisterLCDCIsEnable(softwareRenderer->lcdc) && !GBRegisterLCDCIsEnable(value)) { - _clearScreen(softwareRenderer); - } softwareRenderer->lcdc = value; break; case REG_SCY:

@@ -136,15 +128,24 @@ mTileCacheWriteVRAM(renderer->cache, address);

} } +static void GBVideoSoftwareRendererWriteOAM(struct GBVideoRenderer* renderer, uint16_t oam) { + UNUSED(renderer); + UNUSED(oam); + // Nothing to do +} + static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj* obj, size_t oamMax) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; uint8_t* maps = &softwareRenderer->d.vram[GB_BASE_MAP]; if (GBRegisterLCDCIsTileMap(softwareRenderer->lcdc)) { maps += GB_SIZE_MAP; } + if (softwareRenderer->d.disableBG) { + memset(&softwareRenderer->row[startX], 0, endX - startX); + } if (GBRegisterLCDCIsBgEnable(softwareRenderer->lcdc) || softwareRenderer->model >= GB_MODEL_CGB) { if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->wy <= y && endX >= softwareRenderer->wx - 7) { - if (softwareRenderer->wx - 7 > 0) { + if (softwareRenderer->wx - 7 > 0 && !softwareRenderer->d.disableBG) { GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, softwareRenderer->wx - 7, softwareRenderer->scx, softwareRenderer->scy + y); }

@@ -152,15 +153,17 @@ maps = &softwareRenderer->d.vram[GB_BASE_MAP];

if (GBRegisterLCDCIsWindowTileMap(softwareRenderer->lcdc)) { maps += GB_SIZE_MAP; } - GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, softwareRenderer->wx - 7, endX, 7 - softwareRenderer->wx, softwareRenderer->currentWy); - } else { + if (!softwareRenderer->d.disableWIN) { + GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, softwareRenderer->wx - 7, endX, 7 - softwareRenderer->wx, softwareRenderer->currentWy); + } + } else if (!softwareRenderer->d.disableBG) { GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, endX, softwareRenderer->scx, softwareRenderer->scy + y); } - } else { + } else if (!softwareRenderer->d.disableBG) { memset(&softwareRenderer->row[startX], 0, endX - startX); } - if (GBRegisterLCDCIsObjEnable(softwareRenderer->lcdc)) { + if (GBRegisterLCDCIsObjEnable(softwareRenderer->lcdc) && !softwareRenderer->d.disableOBJ) { size_t i; for (i = 0; i < oamMax; ++i) { GBVideoSoftwareRendererDrawObj(softwareRenderer, &obj[i], startX, endX, y);

@@ -196,6 +199,9 @@

if (softwareRenderer->temporaryBuffer) { mappedMemoryFree(softwareRenderer->temporaryBuffer, GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS * 4); softwareRenderer->temporaryBuffer = 0; + } + if (!GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) { + _clearScreen(softwareRenderer); } softwareRenderer->currentWy = 0; }
M src/gb/video.csrc/gb/video.c

@@ -20,6 +20,7 @@ static void GBVideoDummyRendererDeinit(struct GBVideoRenderer* renderer);

static uint8_t GBVideoDummyRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value); static void GBVideoDummyRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value); static void GBVideoDummyRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address); +static void GBVideoDummyRendererWriteOAM(struct GBVideoRenderer* renderer, uint16_t oam); static void GBVideoDummyRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj* obj, size_t oamMax); static void GBVideoDummyRendererFinishScanline(struct GBVideoRenderer* renderer, int y); static void GBVideoDummyRendererFinishFrame(struct GBVideoRenderer* renderer);

@@ -39,6 +40,7 @@ .init = GBVideoDummyRendererInit,

.deinit = GBVideoDummyRendererDeinit, .writeVideoRegister = GBVideoDummyRendererWriteVideoRegister, .writeVRAM = GBVideoDummyRendererWriteVRAM, + .writeOAM = GBVideoDummyRendererWriteOAM, .writePalette = GBVideoDummyRendererWritePalette, .drawRange = GBVideoDummyRendererDrawRange, .finishScanline = GBVideoDummyRendererFinishScanline,

@@ -61,6 +63,11 @@ video->frameEvent.context = video;

video->frameEvent.name = "GB Video Frame"; video->frameEvent.callback = _updateFrameCount; video->frameEvent.priority = 9; + + video->dmgPalette[0] = 0x7FFF; + video->dmgPalette[1] = 0x56B5; + video->dmgPalette[2] = 0x294A; + video->dmgPalette[3] = 0x0000; } void GBVideoReset(struct GBVideo* video) {

@@ -99,6 +106,33 @@ renderer->vram = video->vram;

video->renderer->init(video->renderer, video->p->model); } +static bool _statIRQAsserted(struct GBVideo* video, GBRegisterSTAT stat) { + // TODO: variable for the IRQ line value? + if (GBRegisterSTATIsLYCIRQ(stat) && GBRegisterSTATIsLYC(stat)) { + return true; + } + switch (GBRegisterSTATGetMode(stat)) { + case 0: + if (GBRegisterSTATIsHblankIRQ(stat)) { + return true; + } + break; + case 1: + if (GBRegisterSTATIsVblankIRQ(stat)) { + return true; + } + break; + case 2: + if (GBRegisterSTATIsOAMIRQ(stat)) { + return true; + } + break; + case 3: + break; + } + return false; +} + void _endMode0(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBVideo* video = context; if (video->frameskipCounter <= 0) {

@@ -108,40 +142,30 @@ int lyc = video->p->memory.io[REG_LYC];

int32_t next; ++video->ly; video->p->memory.io[REG_LY] = video->ly; + GBRegisterSTAT oldStat = video->stat; video->stat = GBRegisterSTATSetLYC(video->stat, lyc == video->ly); if (video->ly < GB_VIDEO_VERTICAL_PIXELS) { // TODO: Cache SCX & 7 in case it changes during mode 2 next = GB_VIDEO_MODE_2_LENGTH + (video->p->memory.io[REG_SCX] & 7); video->mode = 2; video->modeEvent.callback = _endMode2; - if (!GBRegisterSTATIsHblankIRQ(video->stat) && GBRegisterSTATIsOAMIRQ(video->stat)) { - video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); - } } else { next = GB_VIDEO_HORIZONTAL_LENGTH; video->mode = 1; video->modeEvent.callback = _endMode1; - _updateFrameCount(timing, video, cyclesLate); + mTimingSchedule(&video->p->timing, &video->frameEvent, -cyclesLate); - if (GBRegisterSTATIsVblankIRQ(video->stat) || GBRegisterSTATIsOAMIRQ(video->stat)) { + if (!_statIRQAsserted(video, oldStat) && GBRegisterSTATIsOAMIRQ(video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); } video->p->memory.io[REG_IF] |= (1 << GB_IRQ_VBLANK); - - size_t c; - for (c = 0; c < mCoreCallbacksListSize(&video->p->coreCallbacks); ++c) { - struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&video->p->coreCallbacks, c); - if (callbacks->videoFrameEnded) { - callbacks->videoFrameEnded(callbacks->context); - } - } } - if (!GBRegisterSTATIsHblankIRQ(video->stat) && GBRegisterSTATIsLYCIRQ(video->stat) && lyc == video->ly) { + video->stat = GBRegisterSTATSetMode(video->stat, video->mode); + if (!_statIRQAsserted(video, oldStat) && _statIRQAsserted(video, video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); } GBUpdateIRQs(video->p); - video->stat = GBRegisterSTATSetMode(video->stat, video->mode); video->p->memory.io[REG_STAT] = video->stat; mTimingSchedule(timing, &video->modeEvent, (next << video->p->doubleSpeed) - cyclesLate); }

@@ -161,11 +185,6 @@ video->p->memory.io[REG_LY] = video->ly;

next = GB_VIDEO_MODE_2_LENGTH + (video->p->memory.io[REG_SCX] & 7); video->mode = 2; video->modeEvent.callback = _endMode2; - if (GBRegisterSTATIsOAMIRQ(video->stat)) { - video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); - GBUpdateIRQs(video->p); - } - video->renderer->finishFrame(video->renderer); if (video->p->memory.mbcType == GB_MBC7 && video->p->memory.rotation && video->p->memory.rotation->sample) { video->p->memory.rotation->sample(video->p->memory.rotation); }

@@ -180,9 +199,10 @@ video->p->memory.io[REG_LY] = video->ly;

next = GB_VIDEO_HORIZONTAL_LENGTH; } + GBRegisterSTAT oldStat = video->stat; video->stat = GBRegisterSTATSetMode(video->stat, video->mode); video->stat = GBRegisterSTATSetLYC(video->stat, lyc == video->p->memory.io[REG_LY]); - if (video->ly && GBRegisterSTATIsLYCIRQ(video->stat) && lyc == video->p->memory.io[REG_LY]) { + if (!_statIRQAsserted(video, oldStat) && _statIRQAsserted(video, video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); GBUpdateIRQs(video->p); }

@@ -195,10 +215,15 @@ struct GBVideo* video = context;

_cleanOAM(video, video->ly); video->x = 0; video->dotClock = timing->masterCycles - cyclesLate; - int32_t next = GB_VIDEO_MODE_3_LENGTH_BASE + video->objMax * 11 - (video->p->memory.io[REG_SCX] & 7); + int32_t next = GB_VIDEO_MODE_3_LENGTH_BASE + video->objMax * 6 - (video->p->memory.io[REG_SCX] & 7); video->mode = 3; video->modeEvent.callback = _endMode3; + GBRegisterSTAT oldStat = video->stat; video->stat = GBRegisterSTATSetMode(video->stat, video->mode); + if (!_statIRQAsserted(video, oldStat) && _statIRQAsserted(video, video->stat)) { + video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); + GBUpdateIRQs(video->p); + } video->p->memory.io[REG_STAT] = video->stat; mTimingSchedule(timing, &video->modeEvent, (next << video->p->doubleSpeed) - cyclesLate); }

@@ -206,10 +231,6 @@

void _endMode3(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBVideo* video = context; GBVideoProcessDots(video); - if (GBRegisterSTATIsHblankIRQ(video->stat)) { - video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); - GBUpdateIRQs(video->p); - } if (video->ly < GB_VIDEO_VERTICAL_PIXELS && video->p->memory.isHdma && video->p->memory.io[REG_HDMA5] != 0xFF) { video->p->memory.hdmaRemaining = 0x10; mTimingDeschedule(timing, &video->p->memory.hdmaEvent);

@@ -217,9 +238,14 @@ mTimingSchedule(timing, &video->p->memory.hdmaEvent, 0);

} video->mode = 0; video->modeEvent.callback = _endMode0; + GBRegisterSTAT oldStat = video->stat; video->stat = GBRegisterSTATSetMode(video->stat, video->mode); + if (!_statIRQAsserted(video, oldStat) && _statIRQAsserted(video, video->stat)) { + video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); + GBUpdateIRQs(video->p); + } video->p->memory.io[REG_STAT] = video->stat; - int32_t next = GB_VIDEO_MODE_0_LENGTH_BASE - video->objMax * 11; + int32_t next = GB_VIDEO_MODE_0_LENGTH_BASE - video->objMax * 6; mTimingSchedule(timing, &video->modeEvent, (next << video->p->doubleSpeed) - cyclesLate); }

@@ -231,9 +257,18 @@ mTimingSchedule(timing, &video->frameEvent, 4 - ((video->p->cpu->executionState + 1) & 3));

return; } + size_t c; + for (c = 0; c < mCoreCallbacksListSize(&video->p->coreCallbacks); ++c) { + struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&video->p->coreCallbacks, c); + if (callbacks->videoFrameEnded) { + callbacks->videoFrameEnded(callbacks->context); + } + } + GBFrameEnded(video->p); --video->frameskipCounter; if (video->frameskipCounter < 0) { + video->renderer->finishFrame(video->renderer); mCoreSyncPostFrame(video->p->sync); video->frameskipCounter = video->frameskip; }

@@ -247,16 +282,15 @@ video->renderer->getPixels(video->renderer, &stride, (const void**) &pixels);

video->p->stream->postVideoFrame(video->p->stream, pixels, stride); } - size_t c; + if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC])) { + mTimingSchedule(timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH); + } + for (c = 0; c < mCoreCallbacksListSize(&video->p->coreCallbacks); ++c) { struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&video->p->coreCallbacks, c); if (callbacks->videoFrameStarted) { callbacks->videoFrameStarted(callbacks->context); } - } - - if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC])) { - mTimingSchedule(timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH); } }

@@ -312,13 +346,16 @@

video->ly = 0; video->p->memory.io[REG_LY] = 0; // TODO: Does this read as 0 for 4 T-cycles? + GBRegisterSTAT oldStat = video->stat; video->stat = GBRegisterSTATSetMode(video->stat, 2); video->stat = GBRegisterSTATSetLYC(video->stat, video->ly == video->p->memory.io[REG_LYC]); - if (GBRegisterSTATIsLYCIRQ(video->stat) && video->ly == video->p->memory.io[REG_LYC]) { + if (!_statIRQAsserted(video, oldStat) && _statIRQAsserted(video, video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); GBUpdateIRQs(video->p); } video->p->memory.io[REG_STAT] = video->stat; + video->renderer->writePalette(video->renderer, 0, video->palette[0]); + mTimingDeschedule(&video->p->timing, &video->frameEvent); } if (GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC]) && !GBRegisterLCDCIsEnable(value)) {

@@ -327,6 +364,8 @@ video->stat = GBRegisterSTATSetMode(video->stat, 0);

video->p->memory.io[REG_STAT] = video->stat; video->ly = 0; video->p->memory.io[REG_LY] = 0; + video->renderer->writePalette(video->renderer, 0, video->dmgPalette[0]); + mTimingDeschedule(&video->p->timing, &video->modeEvent); mTimingSchedule(&video->p->timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH); }

@@ -334,52 +373,52 @@ video->p->memory.io[REG_STAT] = video->stat;

} void GBVideoWriteSTAT(struct GBVideo* video, GBRegisterSTAT value) { + GBRegisterSTAT oldStat = video->stat; video->stat = (video->stat & 0x7) | (value & 0x78); - if (video->p->model == GB_MODEL_DMG && video->mode == 1) { + if (video->p->model == GB_MODEL_DMG && !_statIRQAsserted(video, oldStat) && video->mode < 3) { + // TODO: variable for the IRQ line value? video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); GBUpdateIRQs(video->p); } } void GBVideoWriteLYC(struct GBVideo* video, uint8_t value) { - if (video->mode == 2) { - video->stat = GBRegisterSTATSetLYC(video->stat, value == video->ly); - if (GBRegisterSTATIsLYCIRQ(video->stat) && value == video->ly) { - video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); - GBUpdateIRQs(video->p); - } + GBRegisterSTAT oldStat = video->stat; + video->stat = GBRegisterSTATSetLYC(video->stat, value == video->ly); + if (!_statIRQAsserted(video, oldStat) && _statIRQAsserted(video, video->stat)) { + video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); + GBUpdateIRQs(video->p); } } void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value) { - static const uint16_t dmgPalette[4] = { 0x7FFF, 0x56B5, 0x294A, 0x0000}; if (video->p->model < GB_MODEL_CGB) { switch (address) { case REG_BGP: - video->palette[0] = dmgPalette[value & 3]; - video->palette[1] = dmgPalette[(value >> 2) & 3]; - video->palette[2] = dmgPalette[(value >> 4) & 3]; - video->palette[3] = dmgPalette[(value >> 6) & 3]; + video->palette[0] = video->dmgPalette[value & 3]; + video->palette[1] = video->dmgPalette[(value >> 2) & 3]; + video->palette[2] = video->dmgPalette[(value >> 4) & 3]; + video->palette[3] = video->dmgPalette[(value >> 6) & 3]; video->renderer->writePalette(video->renderer, 0, video->palette[0]); video->renderer->writePalette(video->renderer, 1, video->palette[1]); video->renderer->writePalette(video->renderer, 2, video->palette[2]); video->renderer->writePalette(video->renderer, 3, video->palette[3]); break; case REG_OBP0: - video->palette[8 * 4 + 0] = dmgPalette[value & 3]; - video->palette[8 * 4 + 1] = dmgPalette[(value >> 2) & 3]; - video->palette[8 * 4 + 2] = dmgPalette[(value >> 4) & 3]; - video->palette[8 * 4 + 3] = dmgPalette[(value >> 6) & 3]; + video->palette[8 * 4 + 0] = video->dmgPalette[value & 3]; + video->palette[8 * 4 + 1] = video->dmgPalette[(value >> 2) & 3]; + video->palette[8 * 4 + 2] = video->dmgPalette[(value >> 4) & 3]; + video->palette[8 * 4 + 3] = video->dmgPalette[(value >> 6) & 3]; video->renderer->writePalette(video->renderer, 8 * 4 + 0, video->palette[8 * 4 + 0]); video->renderer->writePalette(video->renderer, 8 * 4 + 1, video->palette[8 * 4 + 1]); video->renderer->writePalette(video->renderer, 8 * 4 + 2, video->palette[8 * 4 + 2]); video->renderer->writePalette(video->renderer, 8 * 4 + 3, video->palette[8 * 4 + 3]); break; case REG_OBP1: - video->palette[9 * 4 + 0] = dmgPalette[value & 3]; - video->palette[9 * 4 + 1] = dmgPalette[(value >> 2) & 3]; - video->palette[9 * 4 + 2] = dmgPalette[(value >> 4) & 3]; - video->palette[9 * 4 + 3] = dmgPalette[(value >> 6) & 3]; + video->palette[9 * 4 + 0] = video->dmgPalette[value & 3]; + video->palette[9 * 4 + 1] = video->dmgPalette[(value >> 2) & 3]; + video->palette[9 * 4 + 2] = video->dmgPalette[(value >> 4) & 3]; + video->palette[9 * 4 + 3] = video->dmgPalette[(value >> 6) & 3]; video->renderer->writePalette(video->renderer, 9 * 4 + 0, video->palette[9 * 4 + 0]); video->renderer->writePalette(video->renderer, 9 * 4 + 1, video->palette[9 * 4 + 1]); video->renderer->writePalette(video->renderer, 9 * 4 + 2, video->palette[9 * 4 + 2]);

@@ -432,6 +471,13 @@ video->vramBank = &video->vram[value * GB_SIZE_VRAM_BANK0];

video->vramCurrentBank = value; } +void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint16_t color) { + if (index >= 4) { + return; + } + video->dmgPalette[index] = color; +} + static void GBVideoDummyRendererInit(struct GBVideoRenderer* renderer, enum GBModel model) { UNUSED(renderer); UNUSED(model);

@@ -453,6 +499,12 @@ static void GBVideoDummyRendererWriteVRAM(struct GBVideoRenderer* renderer, uint16_t address) {

if (renderer->cache) { mTileCacheWriteVRAM(renderer->cache, address); } +} + +static void GBVideoDummyRendererWriteOAM(struct GBVideoRenderer* renderer, uint16_t oam) { + UNUSED(renderer); + UNUSED(oam); + // Nothing to do } static void GBVideoDummyRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value) {
M src/gba/audio.csrc/gba/audio.c

@@ -297,8 +297,8 @@ audio->lastLeft = sampleLeft;

audio->lastRight = sampleRight; audio->clock += audio->sampleInterval; if (audio->clock >= CLOCKS_PER_FRAME) { - blip_end_frame(audio->psg.left, audio->clock); - blip_end_frame(audio->psg.right, audio->clock); + blip_end_frame(audio->psg.left, CLOCKS_PER_FRAME); + blip_end_frame(audio->psg.right, CLOCKS_PER_FRAME); audio->clock -= CLOCKS_PER_FRAME; } }
M src/gba/bios.csrc/gba/bios.c

@@ -271,7 +271,7 @@ cpu->gprs[3] = 1;

} } -static int16_t _ArcTan(int16_t i) { +static int16_t _ArcTan(int32_t i, int32_t* r1, int32_t* r3) { int32_t a = -((i * i) >> 14); int32_t b = ((0xA9 * a) >> 14) + 0x390; b = ((b * a) >> 14) + 0x91C;

@@ -280,10 +280,16 @@ b = ((b * a) >> 14) + 0x16AA;

b = ((b * a) >> 14) + 0x2081; b = ((b * a) >> 14) + 0x3651; b = ((b * a) >> 14) + 0xA2F9; + if (r1) { + *r1 = a; + } + if (r3) { + *r3 = b; + } return (i * b) >> 16; } -static int16_t _ArcTan2(int16_t x, int16_t y) { +static int16_t _ArcTan2(int32_t x, int32_t y, int32_t* r1) { if (!y) { if (x >= 0) { return 0;

@@ -299,21 +305,21 @@ }

if (y >= 0) { if (x >= 0) { if (x >= y) { - return _ArcTan((y << 14)/ x); + return _ArcTan((y << 14) / x, r1, NULL); } } else if (-x >= y) { - return _ArcTan((y << 14) / x) + 0x8000; + return _ArcTan((y << 14) / x, r1, NULL) + 0x8000; } - return 0x4000 - _ArcTan((x << 14) / y); + return 0x4000 - _ArcTan((x << 14) / y, r1, NULL); } else { if (x <= 0) { if (-x > -y) { - return _ArcTan((y << 14) / x) + 0x8000; + return _ArcTan((y << 14) / x, r1, NULL) + 0x8000; } } else if (x >= -y) { - return _ArcTan((y << 14) / x) + 0x10000; + return _ArcTan((y << 14) / x, r1, NULL) + 0x10000; } - return 0xC000 - _ArcTan((x << 14) / y); + return 0xC000 - _ArcTan((x << 14) / y, r1, NULL); } }

@@ -356,10 +362,11 @@ case 0x8:

cpu->gprs[0] = sqrt((uint32_t) cpu->gprs[0]); break; case 0x9: - cpu->gprs[0] = (uint16_t) _ArcTan(cpu->gprs[0]); + cpu->gprs[0] = _ArcTan(cpu->gprs[0], &cpu->gprs[1], &cpu->gprs[3]); break; case 0xA: - cpu->gprs[0] = (uint16_t) _ArcTan2(cpu->gprs[0], cpu->gprs[1]); + cpu->gprs[0] = (uint16_t) _ArcTan2(cpu->gprs[0], cpu->gprs[1], &cpu->gprs[1]); + cpu->gprs[3] = 0x170; break; case 0xB: case 0xC:
M src/gba/core.csrc/gba/core.c

@@ -10,11 +10,13 @@ #include <mgba/core/log.h>

#include <mgba/internal/arm/debugger/debugger.h> #include <mgba/internal/gba/cheats.h> #include <mgba/internal/gba/gba.h> +#include <mgba/internal/gba/io.h> #include <mgba/internal/gba/extra/cli.h> #include <mgba/internal/gba/overrides.h> #ifndef DISABLE_THREADING -#include <mgba/internal/gba/renderers/thread-proxy.h> +#include <mgba/feature/thread-proxy.h> #endif +#include <mgba/internal/gba/renderers/proxy.h> #include <mgba/internal/gba/renderers/video-software.h> #include <mgba/internal/gba/savedata.h> #include <mgba/internal/gba/serialize.h>

@@ -26,11 +28,32 @@ #ifndef MINIMAL_CORE

#include <mgba/internal/gba/input.h> #endif +const static struct mCoreChannelInfo _GBAVideoLayers[] = { + { 0, "bg0", "Background 0", NULL }, + { 1, "bg1", "Background 1", NULL }, + { 2, "bg2", "Background 2", NULL }, + { 3, "bg3", "Background 3", NULL }, + { 4, "obj", "Objects", NULL }, +}; + +const static struct mCoreChannelInfo _GBAAudioChannels[] = { + { 0, "ch0", "PSG Channel 0", "Square/Sweep" }, + { 1, "ch1", "PSG Channel 1", "Square" }, + { 2, "ch2", "PSG Channel 2", "PCM" }, + { 3, "ch3", "PSG Channel 3", "Noise" }, + { 4, "chA", "FIFO Channel A", NULL }, + { 5, "chB", "FIFO Channel B", NULL }, +}; + +struct mVideoLogContext; struct GBACore { struct mCore d; struct GBAVideoSoftwareRenderer renderer; + struct GBAVideoProxyRenderer proxyRenderer; + struct mVideoLogContext* logContext; + struct mCoreCallbacks logCallbacks; #ifndef DISABLE_THREADING - struct GBAVideoThreadProxyRenderer threadProxy; + struct mVideoThreadProxy threadProxy; int threadedVideo; #endif int keys;

@@ -53,9 +76,11 @@ }

core->cpu = cpu; core->board = gba; core->debugger = NULL; + core->symbolTable = NULL; gbacore->overrides = NULL; gbacore->debuggerPlatform = NULL; gbacore->cheatDevice = NULL; + gbacore->logContext = NULL; GBACreate(gba); // TODO: Restore cheats

@@ -70,8 +95,9 @@ gbacore->renderer.outputBuffer = NULL;

#ifndef DISABLE_THREADING gbacore->threadedVideo = false; - GBAVideoThreadProxyRendererCreate(&gbacore->threadProxy, &gbacore->renderer.d); + mVideoThreadProxyCreate(&gbacore->threadProxy); #endif + gbacore->proxyRenderer.logger = NULL; gbacore->keys = 0; gba->keySource = &gbacore->keys;

@@ -281,7 +307,9 @@ if (gbacore->renderer.outputBuffer) {

struct GBAVideoRenderer* renderer = &gbacore->renderer.d; #ifndef DISABLE_THREADING if (gbacore->threadedVideo) { - renderer = &gbacore->threadProxy.d; + gbacore->proxyRenderer.logger = &gbacore->threadProxy.d; + GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, renderer); + renderer = &gbacore->proxyRenderer.d; } #endif GBAVideoAssociateRenderer(&gba->video, renderer);

@@ -326,7 +354,7 @@ char path[PATH_MAX];

mCoreConfigDirectory(path, PATH_MAX); strncat(path, PATH_SEP "gba_bios.bin", PATH_MAX - strlen(path)); bios = VFileOpen(path, O_RDONLY); - if (bios && GBIsBIOS(bios)) { + if (bios && GBAIsBIOS(bios)) { found = true; } else if (bios) { bios->close(bios);

@@ -378,16 +406,19 @@

static void _GBACoreSetKeys(struct mCore* core, uint32_t keys) { struct GBACore* gbacore = (struct GBACore*) core; gbacore->keys = keys; + GBATestKeypadIRQ(core->board); } static void _GBACoreAddKeys(struct mCore* core, uint32_t keys) { struct GBACore* gbacore = (struct GBACore*) core; gbacore->keys |= keys; + GBATestKeypadIRQ(core->board); } static void _GBACoreClearKeys(struct mCore* core, uint32_t keys) { struct GBACore* gbacore = (struct GBACore*) core; gbacore->keys &= ~keys; + GBATestKeypadIRQ(core->board); } static int32_t _GBACoreFrameCounter(const struct mCore* core) {

@@ -413,14 +444,21 @@ static void _GBACoreGetGameCode(const struct mCore* core, char* title) {

GBAGetGameCode(core->board, title); } -static void _GBACoreSetRotation(struct mCore* core, struct mRotationSource* rotation) { - struct GBA* gba = core->board; - gba->rotationSource = rotation; -} - -static void _GBACoreSetRumble(struct mCore* core, struct mRumble* rumble) { +static void _GBACoreSetPeripheral(struct mCore* core, int type, void* periph) { struct GBA* gba = core->board; - gba->rumble = rumble; + switch (type) { + case mPERIPH_ROTATION: + gba->rotationSource = periph; + break; + case mPERIPH_RUMBLE: + gba->rumble = periph; + break; + case mPERIPH_GBA_LUMINANCE: + gba->luminanceSource = periph; + break; + default: + return; + } } static uint32_t _GBACoreBusRead8(struct mCore* core, uint32_t address) {

@@ -529,6 +567,10 @@ static void _GBACoreDetachDebugger(struct mCore* core) {

GBADetachDebugger(core->board); core->debugger = NULL; } + +static void _GBACoreLoadSymbols(struct mCore* core, struct VFile* vf) { + // TODO +} #endif static struct mCheatDevice* _GBACoreCheatDevice(struct mCore* core) {

@@ -582,6 +624,81 @@ }

return success; } +static size_t _GBACoreListVideoLayers(const struct mCore* core, const struct mCoreChannelInfo** info) { + UNUSED(core); + *info = _GBAVideoLayers; + return sizeof(_GBAVideoLayers) / sizeof(*_GBAVideoLayers); +} + +static size_t _GBACoreListAudioChannels(const struct mCore* core, const struct mCoreChannelInfo** info) { + UNUSED(core); + *info = _GBAAudioChannels; + return sizeof(_GBAAudioChannels) / sizeof(*_GBAAudioChannels); +} + +static void _GBACoreEnableVideoLayer(struct mCore* core, size_t id, bool enable) { + struct GBA* gba = core->board; + switch (id) { + case 0: + case 1: + case 2: + case 3: + gba->video.renderer->disableBG[id] = !enable; + break; + case 4: + gba->video.renderer->disableOBJ = !enable; + break; + default: + break; + } +} + +static void _GBACoreEnableAudioChannel(struct mCore* core, size_t id, bool enable) { + struct GBA* gba = core->board; + switch (id) { + case 0: + case 1: + case 2: + case 3: + gba->audio.psg.forceDisableCh[id] = !enable; + break; + case 4: + gba->audio.forceDisableChA = !enable; + case 5: + gba->audio.forceDisableChB = !enable; + break; + default: + break; + } +} + +static void _GBACoreStartVideoLog(struct mCore* core, struct mVideoLogContext* context) { + struct GBACore* gbacore = (struct GBACore*) core; + struct GBA* gba = core->board; + gbacore->logContext = context; + + struct GBASerializedState* state = mVideoLogContextInitialState(context, NULL); + state->id = 0; + state->cpu.gprs[ARM_PC] = BASE_WORKING_RAM; + + int channelId = mVideoLoggerAddChannel(context); + gbacore->proxyRenderer.logger = malloc(sizeof(struct mVideoLogger)); + mVideoLoggerRendererCreate(gbacore->proxyRenderer.logger, false); + mVideoLoggerAttachChannel(gbacore->proxyRenderer.logger, context, channelId); + gbacore->proxyRenderer.logger->block = false; + + GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, &gbacore->renderer.d); + GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer); +} + +static void _GBACoreEndVideoLog(struct mCore* core) { + struct GBACore* gbacore = (struct GBACore*) core; + struct GBA* gba = core->board; + GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer); + free(gbacore->proxyRenderer.logger); + gbacore->proxyRenderer.logger = NULL; +} + struct mCore* GBACoreCreate(void) { struct GBACore* gbacore = malloc(sizeof(*gbacore)); struct mCore* core = &gbacore->d;

@@ -627,8 +744,7 @@ core->frameCycles = _GBACoreFrameCycles;

core->frequency = _GBACoreFrequency; core->getGameTitle = _GBACoreGetGameTitle; core->getGameCode = _GBACoreGetGameCode; - core->setRotation = _GBACoreSetRotation; - core->setRumble = _GBACoreSetRumble; + core->setPeripheral = _GBACoreSetPeripheral; core->busRead8 = _GBACoreBusRead8; core->busRead16 = _GBACoreBusRead16; core->busRead32 = _GBACoreBusRead32;

@@ -647,9 +763,125 @@ core->debuggerPlatform = _GBACoreDebuggerPlatform;

core->cliDebuggerSystem = _GBACoreCliDebuggerSystem; core->attachDebugger = _GBACoreAttachDebugger; core->detachDebugger = _GBACoreDetachDebugger; + core->loadSymbols = _GBACoreLoadSymbols; #endif core->cheatDevice = _GBACoreCheatDevice; core->savedataClone = _GBACoreSavedataClone; core->savedataRestore = _GBACoreSavedataRestore; + core->listVideoLayers = _GBACoreListVideoLayers; + core->listAudioChannels = _GBACoreListAudioChannels; + core->enableVideoLayer = _GBACoreEnableVideoLayer; + core->enableAudioChannel = _GBACoreEnableAudioChannel; +#ifndef MINIMAL_CORE + core->startVideoLog = _GBACoreStartVideoLog; + core->endVideoLog = _GBACoreEndVideoLog; +#endif return core; } + +#ifndef MINIMAL_CORE +static void _GBAVLPStartFrameCallback(void *context) { + struct mCore* core = context; + struct GBACore* gbacore = (struct GBACore*) core; + struct GBA* gba = core->board; + + if (!mVideoLoggerRendererRun(gbacore->proxyRenderer.logger, true)) { + GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer); + mVideoLogContextRewind(gbacore->logContext, core); + GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer); + } +} + +static bool _GBAVLPInit(struct mCore* core) { + struct GBACore* gbacore = (struct GBACore*) core; + if (!_GBACoreInit(core)) { + return false; + } + gbacore->proxyRenderer.logger = malloc(sizeof(struct mVideoLogger)); + mVideoLoggerRendererCreate(gbacore->proxyRenderer.logger, true); + GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, NULL); + memset(&gbacore->logCallbacks, 0, sizeof(gbacore->logCallbacks)); + gbacore->logCallbacks.videoFrameStarted = _GBAVLPStartFrameCallback; + gbacore->logCallbacks.context = core; + core->addCoreCallbacks(core, &gbacore->logCallbacks); + return true; +} + +static void _GBAVLPDeinit(struct mCore* core) { + struct GBACore* gbacore = (struct GBACore*) core; + if (gbacore->logContext) { + mVideoLogContextDestroy(core, gbacore->logContext); + } + _GBACoreDeinit(core); +} + +static void _GBAVLPReset(struct mCore* core) { + struct GBACore* gbacore = (struct GBACore*) core; + struct GBA* gba = (struct GBA*) core->board; + if (gba->video.renderer == &gbacore->proxyRenderer.d) { + GBAVideoProxyRendererUnshim(&gba->video, &gbacore->proxyRenderer); + } else if (gbacore->renderer.outputBuffer) { + struct GBAVideoRenderer* renderer = &gbacore->renderer.d; + GBAVideoAssociateRenderer(&gba->video, renderer); + } + + ARMReset(core->cpu); + mVideoLogContextRewind(gbacore->logContext, core); + GBAVideoProxyRendererShim(&gba->video, &gbacore->proxyRenderer); + + // Make sure CPU loop never spins + GBAHalt(gba); + gba->cpu->memory.store16(gba->cpu, BASE_IO | REG_IME, 0, NULL); + gba->cpu->memory.store16(gba->cpu, BASE_IO | REG_IE, 0, NULL); +} + +static bool _GBAVLPLoadROM(struct mCore* core, struct VFile* vf) { + struct GBACore* gbacore = (struct GBACore*) core; + gbacore->logContext = mVideoLogContextCreate(NULL); + if (!mVideoLogContextLoad(gbacore->logContext, vf)) { + mVideoLogContextDestroy(core, gbacore->logContext); + gbacore->logContext = NULL; + return false; + } + mVideoLoggerAttachChannel(gbacore->proxyRenderer.logger, gbacore->logContext, 0); + return true; +} + +static bool _GBAVLPLoadState(struct mCore* core, const void* state) { + struct GBA* gba = (struct GBA*) core->board; + + gba->timing.root = NULL; + gba->cpu->gprs[ARM_PC] = BASE_WORKING_RAM; + gba->cpu->memory.setActiveRegion(gba->cpu, gba->cpu->gprs[ARM_PC]); + + // Make sure CPU loop never spins + GBAHalt(gba); + gba->cpu->memory.store16(gba->cpu, BASE_IO | REG_IME, 0, NULL); + gba->cpu->memory.store16(gba->cpu, BASE_IO | REG_IE, 0, NULL); + GBAVideoDeserialize(&gba->video, state); + GBAIODeserialize(gba, state); + GBAAudioReset(&gba->audio); + + return true; +} + +static bool _returnTrue(struct VFile* vf) { + UNUSED(vf); + return true; +} + +struct mCore* GBAVideoLogPlayerCreate(void) { + struct mCore* core = GBACoreCreate(); + core->init = _GBAVLPInit; + core->deinit = _GBAVLPDeinit; + core->reset = _GBAVLPReset; + core->loadROM = _GBAVLPLoadROM; + core->loadState = _GBAVLPLoadState; + core->isROM = _returnTrue; + return core; +} +#else +struct mCore* GBAVideoLogPlayerCreate(void) { + return false; +} +#endif
A src/gba/extra/proxy.c

@@ -0,0 +1,283 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <mgba/internal/gba/renderers/proxy.h> + +#include <mgba/core/tile-cache.h> +#include <mgba/internal/gba/gba.h> +#include <mgba/internal/gba/io.h> + +static void GBAVideoProxyRendererInit(struct GBAVideoRenderer* renderer); +static void GBAVideoProxyRendererReset(struct GBAVideoRenderer* renderer); +static void GBAVideoProxyRendererDeinit(struct GBAVideoRenderer* renderer); +static uint16_t GBAVideoProxyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); +static void GBAVideoProxyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address); +static void GBAVideoProxyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); +static void GBAVideoProxyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam); +static void GBAVideoProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y); +static void GBAVideoProxyRendererFinishFrame(struct GBAVideoRenderer* renderer); +static void GBAVideoProxyRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels); +static void GBAVideoProxyRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels); + +static bool _parsePacket(struct mVideoLogger* logger, const struct mVideoLoggerDirtyInfo* packet); +static uint16_t* _vramBlock(struct mVideoLogger* logger, uint32_t address); + +void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct GBAVideoRenderer* backend) { + renderer->d.init = GBAVideoProxyRendererInit; + renderer->d.reset = GBAVideoProxyRendererReset; + renderer->d.deinit = GBAVideoProxyRendererDeinit; + renderer->d.writeVideoRegister = GBAVideoProxyRendererWriteVideoRegister; + renderer->d.writeVRAM = GBAVideoProxyRendererWriteVRAM; + renderer->d.writeOAM = GBAVideoProxyRendererWriteOAM; + renderer->d.writePalette = GBAVideoProxyRendererWritePalette; + renderer->d.drawScanline = GBAVideoProxyRendererDrawScanline; + renderer->d.finishFrame = GBAVideoProxyRendererFinishFrame; + renderer->d.getPixels = GBAVideoProxyRendererGetPixels; + renderer->d.putPixels = GBAVideoProxyRendererPutPixels; + + renderer->d.disableBG[0] = false; + renderer->d.disableBG[1] = false; + renderer->d.disableBG[2] = false; + renderer->d.disableBG[3] = false; + renderer->d.disableOBJ = false; + + renderer->logger->context = renderer; + renderer->logger->parsePacket = _parsePacket; + renderer->logger->vramBlock = _vramBlock; + renderer->logger->paletteSize = SIZE_PALETTE_RAM; + renderer->logger->vramSize = SIZE_VRAM; + renderer->logger->oamSize = SIZE_OAM; + + renderer->backend = backend; +} + +static void _init(struct GBAVideoProxyRenderer* proxyRenderer) { + mVideoLoggerRendererInit(proxyRenderer->logger); + + if (proxyRenderer->logger->block) { + proxyRenderer->backend->palette = proxyRenderer->logger->palette; + proxyRenderer->backend->vram = proxyRenderer->logger->vram; + proxyRenderer->backend->oam = (union GBAOAM*) proxyRenderer->logger->oam; + proxyRenderer->backend->cache = NULL; + } +} + +static void _reset(struct GBAVideoProxyRenderer* proxyRenderer) { + memcpy(proxyRenderer->logger->oam, &proxyRenderer->d.oam->raw, SIZE_OAM); + memcpy(proxyRenderer->logger->palette, proxyRenderer->d.palette, SIZE_PALETTE_RAM); + memcpy(proxyRenderer->logger->vram, proxyRenderer->d.vram, SIZE_VRAM); + + mVideoLoggerRendererReset(proxyRenderer->logger); +} + +void GBAVideoProxyRendererShim(struct GBAVideo* video, struct GBAVideoProxyRenderer* renderer) { + if ((renderer->backend && video->renderer != renderer->backend) || video->renderer == &renderer->d) { + return; + } + renderer->backend = video->renderer; + video->renderer = &renderer->d; + renderer->d.cache = renderer->backend->cache; + renderer->d.palette = video->palette; + renderer->d.vram = video->vram; + renderer->d.oam = &video->oam; + _init(renderer); + _reset(renderer); +} + +void GBAVideoProxyRendererUnshim(struct GBAVideo* video, struct GBAVideoProxyRenderer* renderer) { + if (video->renderer != &renderer->d) { + return; + } + renderer->backend->cache = video->renderer->cache; + video->renderer = renderer->backend; + renderer->backend->palette = video->palette; + renderer->backend->vram = video->vram; + renderer->backend->oam = &video->oam; + + mVideoLoggerRendererDeinit(renderer->logger); +} + +void GBAVideoProxyRendererInit(struct GBAVideoRenderer* renderer) { + struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; + + _init(proxyRenderer); + + proxyRenderer->backend->init(proxyRenderer->backend); +} + +void GBAVideoProxyRendererReset(struct GBAVideoRenderer* renderer) { + struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; + + _reset(proxyRenderer); + + proxyRenderer->backend->reset(proxyRenderer->backend); +} + +void GBAVideoProxyRendererDeinit(struct GBAVideoRenderer* renderer) { + struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; + + proxyRenderer->backend->deinit(proxyRenderer->backend); + + mVideoLoggerRendererDeinit(proxyRenderer->logger); +} + +static bool _parsePacket(struct mVideoLogger* logger, const struct mVideoLoggerDirtyInfo* item) { + struct GBAVideoProxyRenderer* proxyRenderer = logger->context; + switch (item->type) { + case DIRTY_REGISTER: + proxyRenderer->backend->writeVideoRegister(proxyRenderer->backend, item->address, item->value); + break; + case DIRTY_PALETTE: + if (item->address < SIZE_PALETTE_RAM) { + logger->palette[item->address >> 1] = item->value; + proxyRenderer->backend->writePalette(proxyRenderer->backend, item->address, item->value); + } + break; + case DIRTY_OAM: + if (item->address < SIZE_PALETTE_RAM) { + logger->oam[item->address] = item->value; + proxyRenderer->backend->writeOAM(proxyRenderer->backend, item->address); + } + break; + case DIRTY_VRAM: + if (item->address <= SIZE_VRAM - 0x1000) { + logger->readData(logger, &logger->vram[item->address >> 1], 0x1000, true); + proxyRenderer->backend->writeVRAM(proxyRenderer->backend, item->address); + } + break; + case DIRTY_SCANLINE: + if (item->address < VIDEO_VERTICAL_PIXELS) { + proxyRenderer->backend->drawScanline(proxyRenderer->backend, item->address); + } + break; + case DIRTY_FRAME: + proxyRenderer->backend->finishFrame(proxyRenderer->backend); + break; + case DIRTY_FLUSH: + return false; + default: + return false; + } + return true; +} + +static uint16_t* _vramBlock(struct mVideoLogger* logger, uint32_t address) { + struct GBAVideoProxyRenderer* proxyRenderer = logger->context; + return &proxyRenderer->d.vram[address >> 1]; +} + +uint16_t GBAVideoProxyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) { + struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; + switch (address) { + case REG_BG0CNT: + case REG_BG1CNT: + case REG_BG2CNT: + case REG_BG3CNT: + value &= 0xFFCF; + break; + case REG_BG0HOFS: + case REG_BG0VOFS: + case REG_BG1HOFS: + case REG_BG1VOFS: + case REG_BG2HOFS: + case REG_BG2VOFS: + case REG_BG3HOFS: + case REG_BG3VOFS: + value &= 0x01FF; + break; + } + if (address > REG_BLDY) { + return value; + } + + mVideoLoggerRendererWriteVideoRegister(proxyRenderer->logger, address, value); + if (!proxyRenderer->logger->block) { + proxyRenderer->backend->writeVideoRegister(proxyRenderer->backend, address, value); + } + return value; +} + +void GBAVideoProxyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) { + struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; + mVideoLoggerRendererWriteVRAM(proxyRenderer->logger, address); + if (!proxyRenderer->logger->block) { + proxyRenderer->backend->writeVRAM(proxyRenderer->backend, address); + } + if (renderer->cache) { + mTileCacheWriteVRAM(renderer->cache, address); + } +} + +void GBAVideoProxyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) { + struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; + mVideoLoggerRendererWritePalette(proxyRenderer->logger, address, value); + if (!proxyRenderer->logger->block) { + proxyRenderer->backend->writePalette(proxyRenderer->backend, address, value); + } + if (renderer->cache) { + mTileCacheWritePalette(renderer->cache, address); + } +} + +void GBAVideoProxyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) { + struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; + if (!proxyRenderer->logger->block) { + proxyRenderer->backend->writeOAM(proxyRenderer->backend, oam); + } + mVideoLoggerRendererWriteOAM(proxyRenderer->logger, oam, proxyRenderer->d.oam->raw[oam]); +} + +void GBAVideoProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { + struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; + if (!proxyRenderer->logger->block) { + proxyRenderer->backend->drawScanline(proxyRenderer->backend, y); + } + mVideoLoggerRendererDrawScanline(proxyRenderer->logger, y); + if (proxyRenderer->logger->block && proxyRenderer->logger->wake) { + proxyRenderer->logger->wake(proxyRenderer->logger, y); + } +} + +void GBAVideoProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) { + struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; + if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { + proxyRenderer->logger->lock(proxyRenderer->logger); + proxyRenderer->logger->wait(proxyRenderer->logger); + } + proxyRenderer->backend->finishFrame(proxyRenderer->backend); + mVideoLoggerRendererFinishFrame(proxyRenderer->logger); + mVideoLoggerRendererFlush(proxyRenderer->logger); + if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { + proxyRenderer->logger->unlock(proxyRenderer->logger); + } +} + +static void GBAVideoProxyRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) { + struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; + if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { + proxyRenderer->logger->lock(proxyRenderer->logger); + // Insert an extra item into the queue to make sure it gets flushed + mVideoLoggerRendererFlush(proxyRenderer->logger); + proxyRenderer->logger->wait(proxyRenderer->logger); + } + proxyRenderer->backend->getPixels(proxyRenderer->backend, stride, pixels); + if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { + proxyRenderer->logger->unlock(proxyRenderer->logger); + } +} + +static void GBAVideoProxyRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels) { + struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; + if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { + proxyRenderer->logger->lock(proxyRenderer->logger); + // Insert an extra item into the queue to make sure it gets flushed + mVideoLoggerRendererFlush(proxyRenderer->logger); + proxyRenderer->logger->wait(proxyRenderer->logger); + } + proxyRenderer->backend->putPixels(proxyRenderer->backend, stride, pixels); + if (proxyRenderer->logger->block && proxyRenderer->logger->wait) { + proxyRenderer->logger->unlock(proxyRenderer->logger); + } +}
M src/gba/gba.csrc/gba/gba.c

@@ -76,6 +76,8 @@

gba->sio.p = gba; GBASIOInit(&gba->sio); + GBAHardwareInit(&gba->memory.hw, NULL); + gba->springIRQ = 0; gba->keySource = 0; gba->rotationSource = 0;

@@ -89,8 +91,6 @@ gba->biosVf = 0;

gba->stream = NULL; gba->keyCallback = NULL; - gba->stopCallback = NULL; - gba->stopCallback = NULL; mCoreCallbacksListInit(&gba->coreCallbacks, 0); gba->biosChecksum = GBAChecksum(gba->memory.bios, SIZE_BIOS);

@@ -270,6 +270,7 @@ #endif

} } +#ifdef USE_DEBUGGERS void GBAAttachDebugger(struct GBA* gba, struct mDebugger* debugger) { gba->debugger = (struct ARMDebugger*) debugger->platform; gba->debugger->setSoftwareBreakpoint = _setSoftwareBreakpoint;

@@ -279,10 +280,13 @@ ARMHotplugAttach(gba->cpu, CPU_COMPONENT_DEBUGGER);

} void GBADetachDebugger(struct GBA* gba) { - gba->debugger = 0; - ARMHotplugDetach(gba->cpu, CPU_COMPONENT_DEBUGGER); - gba->cpu->components[CPU_COMPONENT_DEBUGGER] = 0; + if (gba->debugger) { + ARMHotplugDetach(gba->cpu, CPU_COMPONENT_DEBUGGER); + } + gba->cpu->components[CPU_COMPONENT_DEBUGGER] = NULL; + gba->debugger = NULL; } +#endif bool GBALoadMB(struct GBA* gba, struct VFile* vf) { GBAUnloadROM(gba);

@@ -293,14 +297,9 @@ if (gba->pristineRomSize > SIZE_WORKING_RAM) {

gba->pristineRomSize = SIZE_WORKING_RAM; } gba->isPristine = true; -#ifdef _3DS - if (gba->pristineRomSize <= romBufferSize) { - gba->memory.wram = romBuffer; - vf->read(vf, romBuffer, gba->pristineRomSize); - } -#else - gba->memory.wram = vf->map(vf, gba->pristineRomSize, MAP_READ); -#endif + gba->memory.wram = anonymousMemoryMap(SIZE_WORKING_RAM); + memset(gba->memory.wram, 0, SIZE_WORKING_RAM); + vf->read(vf, gba->memory.wram, gba->pristineRomSize); if (!gba->memory.wram) { mLOG(GBA, WARN, "Couldn't map ROM"); return false;

@@ -410,10 +409,6 @@ gba->romCrc32 = doCrc32(gba->memory.rom, gba->memory.romSize);

} void GBAWriteIE(struct GBA* gba, uint16_t value) { - if (value & (1 << IRQ_KEYPAD)) { - mLOG(GBA, STUB, "Keypad interrupts not implemented"); - } - if (gba->memory.io[REG_IME >> 1] && value & gba->memory.io[REG_IF >> 1]) { ARMRaiseIRQ(gba->cpu); }

@@ -450,11 +445,14 @@ gba->cpu->halted = 1;

} void GBAStop(struct GBA* gba) { - if (!gba->stopCallback) { - return; + size_t c; + for (c = 0; c < mCoreCallbacksListSize(&gba->coreCallbacks); ++c) { + struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&gba->coreCallbacks, c); + if (callbacks->sleep) { + callbacks->sleep(callbacks->context); + } } gba->cpu->nextEvent = gba->cpu->cycles; - gba->stopCallback->stop(gba->stopCallback); } void GBADebug(struct GBA* gba, uint16_t flags) {

@@ -635,7 +633,7 @@ }

} void GBAFrameStarted(struct GBA* gba) { - UNUSED(gba); + GBATestKeypadIRQ(gba); size_t c; for (c = 0; c < mCoreCallbacksListSize(&gba->coreCallbacks); ++c) {

@@ -679,6 +677,29 @@ struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&gba->coreCallbacks, c);

if (callbacks->videoFrameEnded) { callbacks->videoFrameEnded(callbacks->context); } + } +} + +void GBATestKeypadIRQ(struct GBA* gba) { + uint16_t keycnt = gba->memory.io[REG_KEYCNT >> 1]; + if (!(keycnt & 0x4000)) { + return; + } + int isAnd = keycnt & 0x8000; + uint16_t keyInput; + + if (!gba->keySource) { + // TODO? + return; + } + + keycnt &= 0x3FF; + keyInput = *gba->keySource; + + if (isAnd && keycnt == keyInput) { + GBARaiseIRQ(gba, IRQ_KEYPAD); + } else if (!isAnd && keycnt & keyInput) { + GBARaiseIRQ(gba, IRQ_KEYPAD); } }
M src/gba/hardware.csrc/gba/hardware.c

@@ -77,6 +77,9 @@ }

} void GBAHardwareGPIOWrite(struct GBACartridgeHardware* hw, uint32_t address, uint16_t value) { + if (!hw->gpioBase) { + return; + } switch (address) { case GPIO_REG_DATA: hw->pinState &= ~hw->direction;
M src/gba/io.csrc/gba/io.c

@@ -537,6 +537,11 @@ value = GBASIOWriteRegister(&gba->sio, address, value);

break; // Interrupts and misc + case REG_KEYCNT: + value &= 0xC3FF; + gba->memory.io[address >> 1] = value; + GBATestKeypadIRQ(gba); + return; case REG_WAITCNT: value &= 0x5FFF; GBAAdjustWaitstates(gba, value);

@@ -689,6 +694,7 @@ case REG_TM1CNT_HI:

case REG_TM2CNT_HI: case REG_TM3CNT_HI: case REG_KEYINPUT: + case REG_KEYCNT: case REG_IE: return true; }

@@ -721,6 +727,9 @@ } else {

uint16_t input = 0x3FF; if (gba->keyCallback) { input = gba->keyCallback->readKeys(gba->keyCallback); + if (gba->keySource) { + *gba->keySource = input; + } } else if (gba->keySource) { input = *gba->keySource; }

@@ -814,7 +823,6 @@ gba->memory.io[REG_JOYSTAT >> 1] &= ~JOYSTAT_RECV_BIT;

break; case REG_SOUNDBIAS: - case REG_KEYCNT: case REG_POSTFLG: mLOG(GBA_IO, STUB, "Stub I/O register read: %03x", address); break;

@@ -863,6 +871,7 @@ case REG_TM0CNT_HI:

case REG_TM1CNT_HI: case REG_TM2CNT_HI: case REG_TM3CNT_HI: + case REG_KEYCNT: case REG_SIOMULTI0: case REG_SIOMULTI1: case REG_SIOMULTI2:
M src/gba/memory.csrc/gba/memory.c

@@ -102,7 +102,7 @@ }

} void GBAMemoryReset(struct GBA* gba) { - if (gba->memory.rom || gba->memory.fullBios) { + if (gba->memory.rom || gba->memory.fullBios || !gba->memory.wram) { // Not multiboot if (gba->memory.wram) { mappedMemoryFree(gba->memory.wram, SIZE_WORKING_RAM);

@@ -274,10 +274,10 @@ cpu->memory.activeMask = SIZE_PALETTE_RAM - 1;

break; case REGION_VRAM: if (address & 0x10000) { - cpu->memory.activeRegion = (uint32_t*) &gba->video.renderer->vram[0x8000]; + cpu->memory.activeRegion = (uint32_t*) &gba->video.vram[0x8000]; cpu->memory.activeMask = 0x00007FFF; } else { - cpu->memory.activeRegion = (uint32_t*) gba->video.renderer->vram; + cpu->memory.activeRegion = (uint32_t*) gba->video.vram; cpu->memory.activeMask = 0x0000FFFF; } break;

@@ -377,9 +377,9 @@ wait += waitstatesRegion[REGION_PALETTE_RAM];

#define LOAD_VRAM \ if ((address & 0x0001FFFF) < SIZE_VRAM) { \ - LOAD_32(value, address & 0x0001FFFC, gba->video.renderer->vram); \ + LOAD_32(value, address & 0x0001FFFC, gba->video.vram); \ } else { \ - LOAD_32(value, address & 0x00017FFC, gba->video.renderer->vram); \ + LOAD_32(value, address & 0x00017FFC, gba->video.vram); \ } \ wait += waitstatesRegion[REGION_VRAM];

@@ -507,9 +507,9 @@ LOAD_16(value, address & (SIZE_PALETTE_RAM - 2), gba->video.palette);

break; case REGION_VRAM: if ((address & 0x0001FFFF) < SIZE_VRAM) { - LOAD_16(value, address & 0x0001FFFE, gba->video.renderer->vram); + LOAD_16(value, address & 0x0001FFFE, gba->video.vram); } else { - LOAD_16(value, address & 0x00017FFE, gba->video.renderer->vram); + LOAD_16(value, address & 0x00017FFE, gba->video.vram); } break; case REGION_OAM:

@@ -608,9 +608,9 @@ value = ((uint8_t*) gba->video.palette)[address & (SIZE_PALETTE_RAM - 1)];

break; case REGION_VRAM: if ((address & 0x0001FFFF) < SIZE_VRAM) { - value = ((uint8_t*) gba->video.renderer->vram)[address & 0x0001FFFF]; + value = ((uint8_t*) gba->video.vram)[address & 0x0001FFFF]; } else { - value = ((uint8_t*) gba->video.renderer->vram)[address & 0x00017FFF]; + value = ((uint8_t*) gba->video.vram)[address & 0x00017FFF]; } break; case REGION_OAM:

@@ -691,11 +691,11 @@ gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 4), value);

#define STORE_VRAM \ if ((address & 0x0001FFFF) < SIZE_VRAM) { \ - STORE_32(value, address & 0x0001FFFC, gba->video.renderer->vram); \ + STORE_32(value, address & 0x0001FFFC, gba->video.vram); \ gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC) + 2); \ gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC)); \ } else { \ - STORE_32(value, address & 0x00017FFC, gba->video.renderer->vram); \ + STORE_32(value, address & 0x00017FFC, gba->video.vram); \ gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC) + 2); \ gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC)); \ } \

@@ -796,10 +796,10 @@ gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 2), value);

break; case REGION_VRAM: if ((address & 0x0001FFFF) < SIZE_VRAM) { - STORE_16(value, address & 0x0001FFFE, gba->video.renderer->vram); + STORE_16(value, address & 0x0001FFFE, gba->video.vram); gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE); } else { - STORE_16(value, address & 0x00017FFE, gba->video.renderer->vram); + STORE_16(value, address & 0x00017FFE, gba->video.vram); gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x00017FFE); } break;

@@ -1052,11 +1052,11 @@ gba->video.renderer->writePalette(gba->video.renderer, (address & (SIZE_PALETTE_RAM - 4)) + 2, value >> 16);

break; case REGION_VRAM: if ((address & 0x0001FFFF) < SIZE_VRAM) { - LOAD_32(oldValue, address & 0x0001FFFC, gba->video.renderer->vram); - STORE_32(value, address & 0x0001FFFC, gba->video.renderer->vram); + LOAD_32(oldValue, address & 0x0001FFFC, gba->video.vram); + STORE_32(value, address & 0x0001FFFC, gba->video.vram); } else { - LOAD_32(oldValue, address & 0x00017FFC, gba->video.renderer->vram); - STORE_32(value, address & 0x00017FFC, gba->video.renderer->vram); + LOAD_32(oldValue, address & 0x00017FFC, gba->video.vram); + STORE_32(value, address & 0x00017FFC, gba->video.vram); } break; case REGION_OAM:

@@ -1121,11 +1121,11 @@ gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 2), value);

break; case REGION_VRAM: if ((address & 0x0001FFFF) < SIZE_VRAM) { - LOAD_16(oldValue, address & 0x0001FFFE, gba->video.renderer->vram); - STORE_16(value, address & 0x0001FFFE, gba->video.renderer->vram); + LOAD_16(oldValue, address & 0x0001FFFE, gba->video.vram); + STORE_16(value, address & 0x0001FFFE, gba->video.vram); } else { - LOAD_16(oldValue, address & 0x00017FFE, gba->video.renderer->vram); - STORE_16(value, address & 0x00017FFE, gba->video.renderer->vram); + LOAD_16(oldValue, address & 0x00017FFE, gba->video.vram); + STORE_16(value, address & 0x00017FFE, gba->video.vram); } break; case REGION_OAM:

@@ -1552,6 +1552,9 @@ }

void* newRom = anonymousMemoryMap(SIZE_CART0); memcpy(newRom, gba->memory.rom, gba->memory.romSize); memset(((uint8_t*) newRom) + gba->memory.romSize, 0xFF, SIZE_CART0 - gba->memory.romSize); + if (gba->cpu->memory.activeRegion == gba->memory.rom) { + gba->cpu->memory.activeRegion = newRom; + } if (gba->romVf) { #ifndef _3DS gba->romVf->unmap(gba->romVf, gba->memory.rom, gba->memory.romSize);
D src/gba/renderers/thread-proxy.c

@@ -1,390 +0,0 @@

-/* 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 <mgba/internal/gba/renderers/thread-proxy.h> - -#include <mgba/core/tile-cache.h> -#include <mgba/internal/gba/gba.h> -#include <mgba/internal/gba/io.h> - -#include <mgba-util/memory.h> - -#ifndef DISABLE_THREADING - -enum GBAVideoDirtyType { - DIRTY_DUMMY = 0, - DIRTY_REGISTER, - DIRTY_OAM, - DIRTY_PALETTE, - DIRTY_VRAM, - DIRTY_SCANLINE, - DIRTY_FLUSH -}; - -struct GBAVideoDirtyInfo { - enum GBAVideoDirtyType type; - uint32_t address; - uint16_t value; - uint32_t padding; -}; - -static void GBAVideoThreadProxyRendererInit(struct GBAVideoRenderer* renderer); -static void GBAVideoThreadProxyRendererReset(struct GBAVideoRenderer* renderer); -static void GBAVideoThreadProxyRendererDeinit(struct GBAVideoRenderer* renderer); -static uint16_t GBAVideoThreadProxyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); -static void GBAVideoThreadProxyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address); -static void GBAVideoThreadProxyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); -static void GBAVideoThreadProxyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam); -static void GBAVideoThreadProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y); -static void GBAVideoThreadProxyRendererFinishFrame(struct GBAVideoRenderer* renderer); -static void GBAVideoThreadProxyRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels); -static void GBAVideoThreadProxyRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels); - -static THREAD_ENTRY _proxyThread(void* renderer); - -void GBAVideoThreadProxyRendererCreate(struct GBAVideoThreadProxyRenderer* renderer, struct GBAVideoRenderer* backend) { - renderer->d.init = GBAVideoThreadProxyRendererInit; - renderer->d.reset = GBAVideoThreadProxyRendererReset; - renderer->d.deinit = GBAVideoThreadProxyRendererDeinit; - renderer->d.writeVideoRegister = GBAVideoThreadProxyRendererWriteVideoRegister; - renderer->d.writeVRAM = GBAVideoThreadProxyRendererWriteVRAM; - renderer->d.writeOAM = GBAVideoThreadProxyRendererWriteOAM; - renderer->d.writePalette = GBAVideoThreadProxyRendererWritePalette; - renderer->d.drawScanline = GBAVideoThreadProxyRendererDrawScanline; - renderer->d.finishFrame = GBAVideoThreadProxyRendererFinishFrame; - renderer->d.getPixels = GBAVideoThreadProxyRendererGetPixels; - renderer->d.putPixels = GBAVideoThreadProxyRendererPutPixels; - - renderer->d.disableBG[0] = false; - renderer->d.disableBG[1] = false; - renderer->d.disableBG[2] = false; - renderer->d.disableBG[3] = false; - renderer->d.disableOBJ = false; - - renderer->backend = backend; -} - -void GBAVideoThreadProxyRendererInit(struct GBAVideoRenderer* renderer) { - struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; - ConditionInit(&proxyRenderer->fromThreadCond); - ConditionInit(&proxyRenderer->toThreadCond); - MutexInit(&proxyRenderer->mutex); - RingFIFOInit(&proxyRenderer->dirtyQueue, 0x40000); - - proxyRenderer->vramProxy = anonymousMemoryMap(SIZE_VRAM); - proxyRenderer->backend->palette = proxyRenderer->paletteProxy; - proxyRenderer->backend->vram = proxyRenderer->vramProxy; - proxyRenderer->backend->oam = &proxyRenderer->oamProxy; - proxyRenderer->backend->cache = NULL; - - proxyRenderer->backend->init(proxyRenderer->backend); - - proxyRenderer->vramDirtyBitmap = 0; - proxyRenderer->threadState = PROXY_THREAD_IDLE; - ThreadCreate(&proxyRenderer->thread, _proxyThread, proxyRenderer); -} - -void GBAVideoThreadProxyRendererReset(struct GBAVideoRenderer* renderer) { - struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; - MutexLock(&proxyRenderer->mutex); - while (proxyRenderer->threadState == PROXY_THREAD_BUSY) { - ConditionWake(&proxyRenderer->toThreadCond); - ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex); - } - memcpy(&proxyRenderer->oamProxy.raw, &renderer->oam->raw, SIZE_OAM); - memcpy(proxyRenderer->paletteProxy, renderer->palette, SIZE_PALETTE_RAM); - memcpy(proxyRenderer->vramProxy, renderer->vram, SIZE_VRAM); - proxyRenderer->backend->reset(proxyRenderer->backend); - MutexUnlock(&proxyRenderer->mutex); -} - -void GBAVideoThreadProxyRendererDeinit(struct GBAVideoRenderer* renderer) { - struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; - bool waiting = false; - MutexLock(&proxyRenderer->mutex); - while (proxyRenderer->threadState == PROXY_THREAD_BUSY) { - ConditionWake(&proxyRenderer->toThreadCond); - ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex); - } - if (proxyRenderer->threadState == PROXY_THREAD_IDLE) { - proxyRenderer->threadState = PROXY_THREAD_STOPPED; - ConditionWake(&proxyRenderer->toThreadCond); - waiting = true; - } - MutexUnlock(&proxyRenderer->mutex); - if (waiting) { - ThreadJoin(proxyRenderer->thread); - } - ConditionDeinit(&proxyRenderer->fromThreadCond); - ConditionDeinit(&proxyRenderer->toThreadCond); - MutexDeinit(&proxyRenderer->mutex); - proxyRenderer->backend->deinit(proxyRenderer->backend); - - mappedMemoryFree(proxyRenderer->vramProxy, SIZE_VRAM); -} - -void _proxyThreadRecover(struct GBAVideoThreadProxyRenderer* proxyRenderer) { - MutexLock(&proxyRenderer->mutex); - if (proxyRenderer->threadState != PROXY_THREAD_STOPPED) { - MutexUnlock(&proxyRenderer->mutex); - return; - } - RingFIFOClear(&proxyRenderer->dirtyQueue); - MutexUnlock(&proxyRenderer->mutex); - ThreadJoin(proxyRenderer->thread); - proxyRenderer->threadState = PROXY_THREAD_IDLE; - ThreadCreate(&proxyRenderer->thread, _proxyThread, proxyRenderer); -} - -static bool _writeData(struct GBAVideoThreadProxyRenderer* proxyRenderer, void* data, size_t length) { - while (!RingFIFOWrite(&proxyRenderer->dirtyQueue, data, length)) { - mLOG(GBA_VIDEO, DEBUG, "Can't write %"PRIz"u bytes. Proxy thread asleep?", length); - MutexLock(&proxyRenderer->mutex); - if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) { - mLOG(GBA_VIDEO, ERROR, "Proxy thread stopped prematurely!"); - MutexUnlock(&proxyRenderer->mutex); - return false; - } - ConditionWake(&proxyRenderer->toThreadCond); - ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex); - MutexUnlock(&proxyRenderer->mutex); - } - return true; -} - -uint16_t GBAVideoThreadProxyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) { - struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; - switch (address) { - case REG_BG0CNT: - case REG_BG1CNT: - case REG_BG2CNT: - case REG_BG3CNT: - value &= 0xFFCF; - break; - case REG_BG0HOFS: - case REG_BG0VOFS: - case REG_BG1HOFS: - case REG_BG1VOFS: - case REG_BG2HOFS: - case REG_BG2VOFS: - case REG_BG3HOFS: - case REG_BG3VOFS: - value &= 0x01FF; - break; - } - if (address > REG_BLDY) { - return value; - } - - struct GBAVideoDirtyInfo dirty = { - DIRTY_REGISTER, - address, - value, - 0xDEADBEEF, - }; - _writeData(proxyRenderer, &dirty, sizeof(dirty)); - return value; -} - -void GBAVideoThreadProxyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) { - struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; - int bit = 1 << (address >> 12); - if (proxyRenderer->vramDirtyBitmap & bit) { - return; - } - proxyRenderer->vramDirtyBitmap |= bit; - if (renderer->cache) { - mTileCacheWriteVRAM(renderer->cache, address); - } -} - -void GBAVideoThreadProxyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) { - struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; - struct GBAVideoDirtyInfo dirty = { - DIRTY_PALETTE, - address, - value, - 0xDEADBEEF, - }; - _writeData(proxyRenderer, &dirty, sizeof(dirty)); - if (renderer->cache) { - mTileCacheWritePalette(renderer->cache, address); - } -} - -void GBAVideoThreadProxyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) { - struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; - struct GBAVideoDirtyInfo dirty = { - DIRTY_OAM, - oam, - proxyRenderer->d.oam->raw[oam], - 0xDEADBEEF, - }; - _writeData(proxyRenderer, &dirty, sizeof(dirty)); -} - -void GBAVideoThreadProxyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { - struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; - if (proxyRenderer->vramDirtyBitmap) { - int bitmap = proxyRenderer->vramDirtyBitmap; - proxyRenderer->vramDirtyBitmap = 0; - int j; - for (j = 0; j < 24; ++j) { - if (!(bitmap & (1 << j))) { - continue; - } - struct GBAVideoDirtyInfo dirty = { - DIRTY_VRAM, - j * 0x1000, - 0xABCD, - 0xDEADBEEF, - }; - _writeData(proxyRenderer, &dirty, sizeof(dirty)); - _writeData(proxyRenderer, &proxyRenderer->d.vram[j * 0x800], 0x1000); - } - } - struct GBAVideoDirtyInfo dirty = { - DIRTY_SCANLINE, - y, - 0, - 0xDEADBEEF, - }; - _writeData(proxyRenderer, &dirty, sizeof(dirty)); - if ((y & 15) == 15) { - ConditionWake(&proxyRenderer->toThreadCond); - } -} - -void GBAVideoThreadProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) { - struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; - if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) { - mLOG(GBA_VIDEO, ERROR, "Proxy thread stopped prematurely!"); - _proxyThreadRecover(proxyRenderer); - return; - } - MutexLock(&proxyRenderer->mutex); - // Insert an extra item into the queue to make sure it gets flushed - struct GBAVideoDirtyInfo dirty = { - DIRTY_FLUSH, - 0, - 0, - 0xDEADBEEF, - }; - _writeData(proxyRenderer, &dirty, sizeof(dirty)); - do { - ConditionWake(&proxyRenderer->toThreadCond); - ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex); - } while (proxyRenderer->threadState == PROXY_THREAD_BUSY); - proxyRenderer->backend->finishFrame(proxyRenderer->backend); - proxyRenderer->vramDirtyBitmap = 0; - MutexUnlock(&proxyRenderer->mutex); -} - -static void GBAVideoThreadProxyRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) { - struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; - MutexLock(&proxyRenderer->mutex); - // Insert an extra item into the queue to make sure it gets flushed - struct GBAVideoDirtyInfo dirty = { - DIRTY_FLUSH, - 0, - 0, - 0xDEADBEEF, - }; - _writeData(proxyRenderer, &dirty, sizeof(dirty)); - while (proxyRenderer->threadState == PROXY_THREAD_BUSY) { - ConditionWake(&proxyRenderer->toThreadCond); - ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex); - } - proxyRenderer->backend->getPixels(proxyRenderer->backend, stride, pixels); - MutexUnlock(&proxyRenderer->mutex); -} - -static void GBAVideoThreadProxyRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels) { - struct GBAVideoThreadProxyRenderer* proxyRenderer = (struct GBAVideoThreadProxyRenderer*) renderer; - MutexLock(&proxyRenderer->mutex); - // Insert an extra item into the queue to make sure it gets flushed - struct GBAVideoDirtyInfo dirty = { - DIRTY_FLUSH, - 0, - 0, - 0xDEADBEEF, - }; - _writeData(proxyRenderer, &dirty, sizeof(dirty)); - while (proxyRenderer->threadState == PROXY_THREAD_BUSY) { - ConditionWake(&proxyRenderer->toThreadCond); - ConditionWait(&proxyRenderer->fromThreadCond, &proxyRenderer->mutex); - } - proxyRenderer->backend->putPixels(proxyRenderer->backend, stride, pixels); - MutexUnlock(&proxyRenderer->mutex); -} - -static THREAD_ENTRY _proxyThread(void* renderer) { - struct GBAVideoThreadProxyRenderer* proxyRenderer = renderer; - ThreadSetName("Proxy Renderer Thread"); - - MutexLock(&proxyRenderer->mutex); - struct GBAVideoDirtyInfo item = {0}; - while (proxyRenderer->threadState != PROXY_THREAD_STOPPED) { - ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex); - if (proxyRenderer->threadState == PROXY_THREAD_STOPPED) { - break; - } - if (RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item))) { - proxyRenderer->threadState = PROXY_THREAD_BUSY; - MutexUnlock(&proxyRenderer->mutex); - do { - switch (item.type) { - case DIRTY_REGISTER: - proxyRenderer->backend->writeVideoRegister(proxyRenderer->backend, item.address, item.value); - break; - case DIRTY_PALETTE: - proxyRenderer->paletteProxy[item.address >> 1] = item.value; - proxyRenderer->backend->writePalette(proxyRenderer->backend, item.address, item.value); - break; - case DIRTY_OAM: - proxyRenderer->oamProxy.raw[item.address] = item.value; - proxyRenderer->backend->writeOAM(proxyRenderer->backend, item.address); - break; - case DIRTY_VRAM: - while (!RingFIFORead(&proxyRenderer->dirtyQueue, &proxyRenderer->vramProxy[item.address >> 1], 0x1000)) { - mLOG(GBA_VIDEO, DEBUG, "Proxy thread can't read VRAM. CPU thread asleep?"); - MutexLock(&proxyRenderer->mutex); - ConditionWake(&proxyRenderer->fromThreadCond); - ConditionWait(&proxyRenderer->toThreadCond, &proxyRenderer->mutex); - MutexUnlock(&proxyRenderer->mutex); - } - proxyRenderer->backend->writeVRAM(proxyRenderer->backend, item.address); - break; - case DIRTY_SCANLINE: - proxyRenderer->backend->drawScanline(proxyRenderer->backend, item.address); - break; - case DIRTY_FLUSH: - MutexLock(&proxyRenderer->mutex); - goto out; - default: - // FIFO was corrupted - MutexLock(&proxyRenderer->mutex); - proxyRenderer->threadState = PROXY_THREAD_STOPPED; - mLOG(GBA_VIDEO, ERROR, "Proxy thread queue got corrupted!"); - goto out; - } - } while (proxyRenderer->threadState == PROXY_THREAD_BUSY && RingFIFORead(&proxyRenderer->dirtyQueue, &item, sizeof(item))); - MutexLock(&proxyRenderer->mutex); - } - out: - ConditionWake(&proxyRenderer->fromThreadCond); - if (proxyRenderer->threadState != PROXY_THREAD_STOPPED) { - proxyRenderer->threadState = PROXY_THREAD_IDLE; - } - } - MutexUnlock(&proxyRenderer->mutex); - -#ifdef _3DS - svcExitThread(); -#endif - return 0; -} - -#endif
M src/gba/renderers/video-software.csrc/gba/renderers/video-software.c

@@ -807,10 +807,12 @@ }

} } } - renderer->bg[2].sx += renderer->bg[2].dmx; - renderer->bg[2].sy += renderer->bg[2].dmy; - renderer->bg[3].sx += renderer->bg[3].dmx; - renderer->bg[3].sy += renderer->bg[3].dmy; + if (GBARegisterDISPCNTGetMode(renderer->dispcnt) != 0) { + renderer->bg[2].sx += renderer->bg[2].dmx; + renderer->bg[2].sy += renderer->bg[2].dmy; + renderer->bg[3].sx += renderer->bg[3].dmx; + renderer->bg[3].sy += renderer->bg[3].dmy; + } } static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) {
M src/gba/serialize.csrc/gba/serialize.c

@@ -206,11 +206,3 @@ gba->rr->stateLoaded(gba->rr, state);

} return true; } - -struct GBASerializedState* GBAAllocateState(void) { - return anonymousMemoryMap(sizeof(struct GBASerializedState)); -} - -void GBADeallocateState(struct GBASerializedState* state) { - mappedMemoryFree(state, sizeof(struct GBASerializedState)); -}
M src/gba/video.csrc/gba/video.c

@@ -295,7 +295,7 @@ // Nothing to do

} void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* state) { - memcpy(state->vram, video->renderer->vram, SIZE_VRAM); + memcpy(state->vram, video->vram, SIZE_VRAM); memcpy(state->oam, video->oam.raw, SIZE_OAM); memcpy(state->pram, video->palette, SIZE_PALETTE_RAM); STORE_32(video->event.when - mTimingCurrentTime(&video->p->timing), 0, &state->video.nextEvent);

@@ -303,7 +303,7 @@ STORE_32(video->frameCounter, 0, &state->video.frameCounter);

} void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState* state) { - memcpy(video->renderer->vram, state->vram, SIZE_VRAM); + memcpy(video->vram, state->vram, SIZE_VRAM); uint16_t value; int i; for (i = 0; i < SIZE_OAM; i += 2) {
M src/lr35902/debugger/cli-debugger.csrc/lr35902/debugger/cli-debugger.c

@@ -8,6 +8,7 @@

#include <mgba/core/core.h> #include <mgba/internal/debugger/cli-debugger.h> #include <mgba/internal/lr35902/decoder.h> +#include <mgba/internal/lr35902/debugger/debugger.h> #include <mgba/internal/lr35902/lr35902.h> static void _printStatus(struct CLIDebuggerSystem*);

@@ -31,11 +32,13 @@ static void _disassemble(struct CLIDebuggerSystem* debugger, struct CLIDebugVector* dv) {

struct LR35902Core* cpu = debugger->p->d.core->cpu; uint16_t address; + int segment = -1; size_t size; if (!dv || dv->type != CLIDV_INT_TYPE) { address = cpu->pc; } else { address = dv->intValue; + segment = dv->segmentValue; dv = dv->next; }

@@ -48,7 +51,7 @@ }

size_t i; for (i = 0; i < size; ++i) { - address = _printLine(debugger->p, address, -1); + address = _printLine(debugger->p, address, segment); } }

@@ -58,7 +61,7 @@ struct LR35902InstructionInfo info = {0};

char disassembly[48]; char* disPtr = disassembly; if (segment >= 0) { - be->printf(be, "%02X: ", segment); + be->printf(be, "%02X:", segment); } be->printf(be, "%04X: ", address); uint8_t instruction;

@@ -84,8 +87,17 @@ be->printf(be, "B: %02X C: %02X (BC: %04X)\n", cpu->b, cpu->c, cpu->bc);

be->printf(be, "D: %02X E: %02X (DE: %04X)\n", cpu->d, cpu->e, cpu->de); be->printf(be, "H: %02X L: %02X (HL: %04X)\n", cpu->h, cpu->l, cpu->hl); be->printf(be, "PC: %04X SP: %04X\n", cpu->pc, cpu->sp); + + struct LR35902Debugger* platDebugger = (struct LR35902Debugger*) debugger->p->d.platform; + size_t i; + for (i = 0; platDebugger->segments[i].name; ++i) { + be->printf(be, "%s%s: %02X", i ? " " : "", platDebugger->segments[i].name, cpu->memory.currentSegment(cpu, platDebugger->segments[i].start)); + } + if (i) { + be->printf(be, "\n"); + } _printFlags(be, cpu->f); - _printLine(debugger->p, cpu->pc, -1); + _printLine(debugger->p, cpu->pc, cpu->memory.currentSegment(cpu, cpu->pc)); } static uint32_t _lookupPlatformIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) {
M src/lr35902/debugger/debugger.csrc/lr35902/debugger/debugger.c

@@ -7,6 +7,7 @@ #include <mgba/internal/lr35902/debugger/debugger.h>

#include <mgba/core/core.h> #include <mgba/internal/lr35902/lr35902.h> +#include <mgba/internal/lr35902/debugger/memory-debugger.h> DEFINE_VECTOR(LR35902DebugBreakpointList, struct LR35902DebugBreakpoint); DEFINE_VECTOR(LR35902DebugWatchpointList, struct LR35902DebugWatchpoint);

@@ -27,7 +28,9 @@ struct LR35902DebugBreakpoint* breakpoint = _lookupBreakpoint(&debugger->breakpoints, debugger->cpu->pc);

if (!breakpoint) { return; } - // TODO: Segments + if (breakpoint->segment >= 0 && debugger->cpu->memory.currentSegment(debugger->cpu, breakpoint->address) != breakpoint->segment) { + return; + } struct mDebuggerEntryInfo info = { .address = breakpoint->address };

@@ -39,8 +42,10 @@ static void LR35902DebuggerDeinit(struct mDebuggerPlatform* platform);

static void LR35902DebuggerEnter(struct mDebuggerPlatform* d, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info); -static void LR35902DebuggerSetBreakpoint(struct mDebuggerPlatform*, uint32_t address); -static void LR35902DebuggerClearBreakpoint(struct mDebuggerPlatform*, uint32_t address); +static void LR35902DebuggerSetBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); +static void LR35902DebuggerClearBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); +static void LR35902DebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); +static void LR35902DebuggerClearWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment); static void LR35902DebuggerCheckBreakpoints(struct mDebuggerPlatform*); static bool LR35902DebuggerHasBreakpoints(struct mDebuggerPlatform*);

@@ -51,8 +56,8 @@ platform->init = LR35902DebuggerInit;

platform->deinit = LR35902DebuggerDeinit; platform->setBreakpoint = LR35902DebuggerSetBreakpoint; platform->clearBreakpoint = LR35902DebuggerClearBreakpoint; - platform->setWatchpoint = NULL; - platform->clearWatchpoint = NULL; + platform->setWatchpoint = LR35902DebuggerSetWatchpoint; + platform->clearWatchpoint = LR35902DebuggerClearWatchpoint; platform->checkBreakpoints = LR35902DebuggerCheckBreakpoints; platform->hasBreakpoints = LR35902DebuggerHasBreakpoints; return platform;

@@ -77,21 +82,26 @@ UNUSED(info);

struct LR35902Debugger* debugger = (struct LR35902Debugger*) platform; struct LR35902Core* cpu = debugger->cpu; cpu->nextEvent = cpu->cycles; + + if (debugger->d.p->entered) { + debugger->d.p->entered(debugger->d.p, reason, info); + } } -static void LR35902DebuggerSetBreakpoint(struct mDebuggerPlatform* d, uint32_t address) { +static void LR35902DebuggerSetBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) { struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; struct LR35902DebugBreakpoint* breakpoint = LR35902DebugBreakpointListAppend(&debugger->breakpoints); breakpoint->address = address; - breakpoint->segment = -1; + breakpoint->segment = segment; } -static void LR35902DebuggerClearBreakpoint(struct mDebuggerPlatform* d, uint32_t address) { +static void LR35902DebuggerClearBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) { struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; struct LR35902DebugBreakpointList* breakpoints = &debugger->breakpoints; size_t i; for (i = 0; i < LR35902DebugBreakpointListSize(breakpoints); ++i) { - if (LR35902DebugBreakpointListGetPointer(breakpoints, i)->address == address) { + struct LR35902DebugBreakpoint* breakpoint = LR35902DebugBreakpointListGetPointer(breakpoints, i); + if (breakpoint->address == address && breakpoint->segment == segment) { LR35902DebugBreakpointListShift(breakpoints, i, 1); } }

@@ -101,3 +111,29 @@ static bool LR35902DebuggerHasBreakpoints(struct mDebuggerPlatform* d) {

struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; return LR35902DebugBreakpointListSize(&debugger->breakpoints) || LR35902DebugWatchpointListSize(&debugger->watchpoints); } + +static void LR35902DebuggerSetWatchpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, enum mWatchpointType type) { + struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; + if (!LR35902DebugWatchpointListSize(&debugger->watchpoints)) { + LR35902DebuggerInstallMemoryShim(debugger); + } + struct LR35902DebugWatchpoint* watchpoint = LR35902DebugWatchpointListAppend(&debugger->watchpoints); + watchpoint->address = address; + watchpoint->type = type; + watchpoint->segment = segment; +} + +static void LR35902DebuggerClearWatchpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) { + struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; + struct LR35902DebugWatchpointList* watchpoints = &debugger->watchpoints; + size_t i; + for (i = 0; i < LR35902DebugWatchpointListSize(watchpoints); ++i) { + struct LR35902DebugWatchpoint* watchpoint = LR35902DebugWatchpointListGetPointer(watchpoints, i); + if (watchpoint->address == address && watchpoint->segment == segment) { + LR35902DebugWatchpointListShift(watchpoints, i, 1); + } + } + if (!LR35902DebugWatchpointListSize(&debugger->watchpoints)) { + LR35902DebuggerRemoveMemoryShim(debugger); + } +}
A src/lr35902/debugger/memory-debugger.c

@@ -0,0 +1,70 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <mgba/internal/lr35902/debugger/memory-debugger.h> + +#include <mgba/internal/lr35902/debugger/debugger.h> + +#include <mgba-util/math.h> + +#include <string.h> + +static bool _checkWatchpoints(struct LR35902Debugger* debugger, uint16_t address, struct mDebuggerEntryInfo* info, enum mWatchpointType type, uint8_t newValue); + +#define FIND_DEBUGGER(DEBUGGER, CPU) \ + do { \ + DEBUGGER = 0; \ + size_t i; \ + for (i = 0; i < CPU->numComponents; ++i) { \ + if (CPU->components[i]->id == DEBUGGER_ID) { \ + DEBUGGER = (struct LR35902Debugger*) ((struct mDebugger*) cpu->components[i])->platform; \ + goto debuggerFound; \ + } \ + } \ + abort(); \ + debuggerFound: break; \ + } while(0) + +#define CREATE_WATCHPOINT_SHIM(NAME, RW, RETURN, TYPES, ...) \ + static RETURN DebuggerShim_ ## NAME TYPES { \ + struct LR35902Debugger* debugger; \ + FIND_DEBUGGER(debugger, cpu); \ + struct mDebuggerEntryInfo info; \ + if (_checkWatchpoints(debugger, address, &info, WATCHPOINT_ ## RW, 0)) { \ + mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); \ + } \ + return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \ + } + +CREATE_WATCHPOINT_SHIM(load8, READ, uint8_t, (struct LR35902Core* cpu, uint16_t address), address) +CREATE_WATCHPOINT_SHIM(store8, WRITE, void, (struct LR35902Core* cpu, uint16_t address, int8_t value), address, value) + +static bool _checkWatchpoints(struct LR35902Debugger* debugger, uint16_t address, struct mDebuggerEntryInfo* info, enum mWatchpointType type, uint8_t newValue) { + struct LR35902DebugWatchpoint* watchpoint; + size_t i; + for (i = 0; i < LR35902DebugWatchpointListSize(&debugger->watchpoints); ++i) { + watchpoint = LR35902DebugWatchpointListGetPointer(&debugger->watchpoints, i); + if (watchpoint->address == address && (watchpoint->segment < 0 || watchpoint->segment == debugger->originalMemory.currentSegment(debugger->cpu, address)) && watchpoint->type & type) { + info->oldValue = debugger->originalMemory.load8(debugger->cpu, address); + info->newValue = newValue; + info->address = address; + info->watchType = watchpoint->type; + info->accessType = type; + return true; + } + } + return false; +} + +void LR35902DebuggerInstallMemoryShim(struct LR35902Debugger* debugger) { + debugger->originalMemory = debugger->cpu->memory; + debugger->cpu->memory.store8 = DebuggerShim_store8; + debugger->cpu->memory.load8 = DebuggerShim_load8; +} + +void LR35902DebuggerRemoveMemoryShim(struct LR35902Debugger* debugger) { + debugger->cpu->memory.store8 = debugger->originalMemory.store8; + debugger->cpu->memory.load8 = debugger->originalMemory.load8; +}
M src/lr35902/decoder.csrc/lr35902/decoder.c

@@ -66,8 +66,7 @@ #define DEFINE_LD_DECODER_LR35902(NAME) \

DEFINE_LD_DECODER_LR35902_MEM(NAME, HL) \ DEFINE_LD_DECODER_LR35902_MEM_2(NAME, HL) \ DEFINE_DECODER_LR35902(LD ## NAME ## _, info->mnemonic = LR35902_MN_LD; \ - info->op1.reg = LR35902_REG_A; \ - info->op1.flags = LR35902_OP_FLAG_IMPLICIT; \ + info->op1.reg = LR35902_REG_ ## NAME; \ return 1;) \ DEFINE_LD_DECODER_LR35902_NOHL(NAME)

@@ -500,17 +499,16 @@ if (op.flags & LR35902_OP_FLAG_MEMORY) {

strncpy(buffer, "(", blen - 1); ADVANCE(1); } - if (op.immediate) { + if (op.reg) { + int written = snprintf(buffer, blen - 1, "%s", _lr35902Registers[op.reg]); + ADVANCE(written); + } else { int written = snprintf(buffer, blen - 1, "$%02X", op.immediate); ADVANCE(written); if (op.reg) { strncpy(buffer, "+", blen - 1); ADVANCE(1); } - } - if (op.reg) { - int written = snprintf(buffer, blen - 1, "%s", _lr35902Registers[op.reg]); - ADVANCE(written); } if (op.flags & LR35902_OP_FLAG_INCREMENT) { strncpy(buffer, "+", blen - 1);

@@ -546,10 +544,12 @@ ADVANCE(2);

} } - written = _decodeOperand(info->op1, buffer, blen); - ADVANCE(written); + if (info->op1.reg || info->op1.immediate) { + written = _decodeOperand(info->op1, buffer, blen); + ADVANCE(written); + } - if (info->op2.reg || info->op2.immediate) { + if (info->op2.reg || (!info->op1.immediate && info->opcodeSize > 1 && info->opcode[0] != 0xCB)) { if (written) { strncpy(buffer, ", ", blen - 1); ADVANCE(2);
M src/lr35902/lr35902.csrc/lr35902/lr35902.c

@@ -158,6 +158,10 @@

void LR35902Run(struct LR35902Core* cpu) { bool running = true; while (running || cpu->executionState != LR35902_CORE_FETCH) { + if (cpu->cycles >= cpu->nextEvent) { + cpu->irqh.processEvents(cpu); + break; + } _LR35902Step(cpu); if (cpu->cycles + 2 >= cpu->nextEvent) { int32_t diff = cpu->nextEvent - cpu->cycles;

@@ -172,9 +176,5 @@ }

cpu->executionState = LR35902_CORE_FETCH; cpu->instruction(cpu); ++cpu->cycles; - if (cpu->cycles >= cpu->nextEvent) { - cpu->irqh.processEvents(cpu); - running = false; - } } }
M src/platform/3ds/main.csrc/platform/3ds/main.c

@@ -242,7 +242,7 @@ mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo", 1);

mCoreLoadForeignConfig(runner->core, &runner->config); } - runner->core->setRotation(runner->core, &rotation.d); + runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation.d); if (hasSound != NO_SOUND) { runner->core->setAVStream(runner->core, &stream); }
M src/platform/example/client-server/server.csrc/platform/example/client-server/server.c

@@ -1,6 +1,6 @@

// This source file is placed into the public domain. #include <mgba/core/core.h> -#include "feature/commandline.h" +#include <mgba/feature/commandline.h> #include <mgba-util/socket.h> #define DEFAULT_PORT 13721
M src/platform/libretro/libretro.csrc/platform/libretro/libretro.c

@@ -398,7 +398,7 @@

blip_set_rates(core->getAudioChannel(core, 0), core->frequency(core), 32768); blip_set_rates(core->getAudioChannel(core, 1), core->frequency(core), 32768); - core->setRumble(core, &rumble); + core->setPeripheral(core, mPERIPH_RUMBLE, &rumble); savedata = anonymousMemoryMap(SIZE_CART_FLASH1M); struct VFile* save = VFileFromMemory(savedata, SIZE_CART_FLASH1M);

@@ -409,8 +409,7 @@ core->loadSave(core, save);

#ifdef M_CORE_GBA if (core->platform(core) == PLATFORM_GBA) { - struct GBA* gba = core->board; - gba->luminanceSource = &lux; + core->setPeripheral(core, mPERIPH_GBA_LUMINANCE, &lux); const char* sysDir = 0; if (core->opts.useBios && environCallback(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &sysDir)) {
M src/platform/opengl/gl.csrc/platform/opengl/gl.c

@@ -65,6 +65,10 @@ } else if (w * v->height < h * v->width) {

drawH = w * v->height / v->width; } } + if (v->lockIntegerScaling) { + drawW -= drawW % v->width; + drawH -= drawH % v->height; + } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glClearColor(0, 0, 0, 0);
M src/platform/opengl/gles2.csrc/platform/opengl/gles2.c

@@ -171,6 +171,10 @@ } else if (w * v->height < h * v->width) {

drawH = w * v->height / v->width; } } + if (v->lockIntegerScaling) { + drawW -= drawW % v->width; + drawH -= drawH % v->height; + } glViewport(0, 0, v->width, v->height); glClearColor(0.f, 0.f, 0.f, 1.f); glClear(GL_COLOR_BUFFER_BIT);
M src/platform/psp2/main.csrc/platform/psp2/main.c

@@ -16,7 +16,6 @@ #include <psp2/ctrl.h>

#include <psp2/display.h> #include <psp2/kernel/processmgr.h> #include <psp2/kernel/threadmgr.h> -#include <psp2/moduleinfo.h> #include <psp2/power.h> #include <psp2/sysmodule.h> #include <psp2/touch.h>

@@ -164,6 +163,7 @@ mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_LEFT, GUI_INPUT_LEFT);

mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_RIGHT, GUI_INPUT_RIGHT); mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_SQUARE, mGUI_INPUT_SCREEN_MODE); + scePowerSetArmClockFrequency(444); mGUIRunloop(&runner); vita2d_fini();
M src/platform/psp2/psp2-context.csrc/platform/psp2/psp2-context.c

@@ -20,6 +20,7 @@ #include <mgba/internal/gba/input.h>

#include <mgba-util/memory.h> #include <mgba-util/circle-buffer.h> +#include <mgba-util/math.h> #include <mgba-util/ring-fifo.h> #include <mgba-util/threading.h> #include <mgba-util/vfs.h>

@@ -31,7 +32,6 @@ #include <psp2/display.h>

#include <psp2/gxm.h> #include <psp2/kernel/sysmem.h> #include <psp2/motion.h> -#include <psp2/power.h> #include <vita2d.h>

@@ -63,8 +63,8 @@

extern const uint8_t _binary_backdrop_png_start[]; static vita2d_texture* backdrop = 0; -#define PSP2_SAMPLES 128 -#define PSP2_AUDIO_BUFFER_SIZE (PSP2_SAMPLES * 40) +#define PSP2_SAMPLES 256 +#define PSP2_AUDIO_BUFFER_SIZE (PSP2_SAMPLES * 20) static struct mPSP2AudioContext { struct RingFIFO buffer;

@@ -176,7 +176,6 @@ void mPSP2Setup(struct mGUIRunner* runner) {

mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo", 1); mCoreLoadForeignConfig(runner->core, &runner->config); - scePowerSetArmClockFrequency(333); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_CROSS, GBA_KEY_A); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_CIRCLE, GBA_KEY_B); mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_START, GBA_KEY_START);

@@ -193,8 +192,10 @@ mInputBindAxis(&runner->core->inputMap, PSP2_INPUT, 0, &desc);

desc = (struct mInputAxis) { GBA_KEY_RIGHT, GBA_KEY_LEFT, 192, 64 }; mInputBindAxis(&runner->core->inputMap, PSP2_INPUT, 1, &desc); - tex = vita2d_create_empty_texture_format(256, 256, SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); - screenshot = vita2d_create_empty_texture_format(256, 256, SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); + unsigned width, height; + runner->core->desiredVideoDimensions(runner->core, &width, &height); + tex = vita2d_create_empty_texture_format(256, toPow2(height), SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); + screenshot = vita2d_create_empty_texture_format(256, toPow2(height), SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); outputBuffer = vita2d_texture_get_datap(tex); runner->core->setVideoBuffer(runner->core, outputBuffer, 256);

@@ -203,11 +204,11 @@ rotation.d.sample = _sampleRotation;

rotation.d.readTiltX = _readTiltX; rotation.d.readTiltY = _readTiltY; rotation.d.readGyroZ = _readGyroZ; - runner->core->setRotation(runner->core, &rotation.d); + runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation.d); rumble.d.setRumble = _setRumble; CircleBufferInit(&rumble.history, RUMBLE_PWM); - runner->core->setRumble(runner->core, &rumble.d); + runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble.d); frameLimiter = true; backdrop = vita2d_load_PNG_buffer(_binary_backdrop_png_start);

@@ -219,7 +220,6 @@ }

} void mPSP2LoadROM(struct mGUIRunner* runner) { - scePowerSetArmClockFrequency(444); float rate = 60.0f / 1.001f; sceDisplayGetRefreshRate(&rate); double ratio = GBAAudioCalculateRatio(1, rate, 1);

@@ -262,7 +262,6 @@ blip_clear(runner->core->getAudioChannel(runner->core, 0));

blip_clear(runner->core->getAudioChannel(runner->core, 1)); break; } - sceKernelDelayThread(400); } blip_read_samples(runner->core->getAudioChannel(runner->core, 0), &samples[0].left, PSP2_SAMPLES, true); blip_read_samples(runner->core->getAudioChannel(runner->core, 1), &samples[0].right, PSP2_SAMPLES, true);

@@ -297,7 +296,6 @@ #endif

default: break; } - scePowerSetArmClockFrequency(333); } void mPSP2Paused(struct mGUIRunner* runner) {
M src/platform/python/CMakeLists.txtsrc/platform/python/CMakeLists.txt

@@ -2,8 +2,13 @@ find_program(PYTHON python)

configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) +get_property(INCLUDE_DIRECTORIES DIRECTORY PROPERTY INCLUDE_DIRECTORIES) +set(INCLUDE_FLAGS) +foreach(DIR IN LISTS INCLUDE_DIRECTORIES) + list(APPEND INCLUDE_FLAGS "-I${DIR}") +endforeach() add_custom_command(OUTPUT build/lib/${BINARY_NAME}/__init__.py - COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. ${PYTHON} ${CMAKE_CURRENT_BINARY_DIR}/setup.py build --build-base ${CMAKE_CURRENT_BINARY_DIR} + COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON} ${CMAKE_CURRENT_BINARY_DIR}/setup.py build --build-base ${CMAKE_CURRENT_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${BINARY_NAME} DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/setup.py
M src/platform/python/_builder.hsrc/platform/python/_builder.h

@@ -29,8 +29,12 @@ #include "flags.h"

#include <mgba/core/core.h> #include <mgba/core/tile-cache.h> -#include "platform/python/vfs-py.h" + +#define PYEXPORT extern "Python+C" #include "platform/python/log.h" +#include "platform/python/sio.h" +#include "platform/python/vfs-py.h" +#undef PYEXPORT #ifdef USE_PNG #include <mgba-util/png-io.h>
M src/platform/python/_builder.pysrc/platform/python/_builder.py

@@ -32,24 +32,16 @@ #include <mgba/internal/gb/renderers/tile-cache.h>

#include <mgba-util/png-io.h> #include <mgba-util/vfs.h> -struct VFile* VFileFromPython(void* fileobj); - -struct VFilePy { - struct VFile d; - void* fileobj; -}; - -struct mLogger* mLoggerPythonCreate(void* pyobj); - -struct mLoggerPy { - struct mLogger d; - void* pyobj; -}; +#define PYEXPORT +#include "platform/python/log.h" +#include "platform/python/sio.h" +#include "platform/python/vfs-py.h" +#undef PYEXPORT """, include_dirs=[incdir, srcdir], extra_compile_args=cppflags, libraries=["mgba"], library_dirs=[bindir], - sources=[os.path.join(pydir, path) for path in ["vfs-py.c", "log.c"]]) + sources=[os.path.join(pydir, path) for path in ["vfs-py.c", "log.c", "sio.c"]]) preprocessed = subprocess.check_output(cpp + ["-fno-inline", "-P"] + cppflags + [os.path.join(pydir, "_builder.h")], universal_newlines=True)
M src/platform/python/log.csrc/platform/python/log.c

@@ -22,6 +22,7 @@

struct mLogger* mLoggerPythonCreate(void* pyobj) { struct mLoggerPy* logger = malloc(sizeof(*logger)); logger->d.log = _pyLogShim; + logger->d.filter = NULL; logger->pyobj = pyobj; return &logger->d; }
M src/platform/python/log.hsrc/platform/python/log.h

@@ -12,4 +12,4 @@ };

struct mLogger* mLoggerPythonCreate(void* pyobj); -extern "Python+C" void _pyLog(void* logger, int category, enum mLogLevel level, const char* message); +PYEXPORT void _pyLog(void* logger, int category, enum mLogLevel level, const char* message);
M src/platform/python/mgba/__init__.pysrc/platform/python/mgba/__init__.py

@@ -0,0 +1,15 @@

+# Copyright (c) 2013-2017 Jeffrey Pfau +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +from ._pylib import ffi, lib + +def createCallback(structName, cbName, funcName=None): + funcName = funcName or "_py{}{}".format(structName, cbName[0].upper() + cbName[1:]) + fullStruct = "struct {}*".format(structName) + def cb(handle, *args): + h = ffi.cast(fullStruct, handle) + return getattr(ffi.from_handle(h.pyobj), cbName)(*args) + + return ffi.def_extern(name=funcName)(cb)
M src/platform/python/mgba/gb.pysrc/platform/python/mgba/gb.py

@@ -1,4 +1,4 @@

-# Copyright (c) 2013-2016 Jeffrey Pfau +# Copyright (c) 2013-2017 Jeffrey Pfau # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this

@@ -8,6 +8,7 @@ from .lr35902 import LR35902Core

from .core import Core, needsReset from .memory import Memory from .tile import Sprite +from . import createCallback class GB(Core): KEY_A = lib.GBA_KEY_A

@@ -33,6 +34,48 @@

def _deinitTileCache(self, cache): self._native.video.renderer.cache = ffi.NULL lib.mTileCacheDeinit(cache) + + def attachSIO(self, link): + lib.GBSIOSetDriver(ffi.addressof(self._native.sio), link._native) + +createCallback("GBSIOPythonDriver", "init") +createCallback("GBSIOPythonDriver", "deinit") +createCallback("GBSIOPythonDriver", "writeSB") +createCallback("GBSIOPythonDriver", "writeSC") + +class GBSIODriver(object): + def __init__(self): + self._handle = ffi.new_handle(self) + self._native = ffi.gc(lib.GBSIOPythonDriverCreate(self._handle), lib.free) + + def init(self): + return True + + def deinit(self): + pass + + def writeSB(self, value): + pass + + def writeSC(self, value): + return value + +class GBSIOSimpleDriver(GBSIODriver): + def __init__(self): + super(GBSIOSimpleDriver, self).__init__() + self.tx = 0xFF + self.rx = 0xFF + + def writeSB(self, value): + self.rx = value + + def schedule(self, period=0x100, when=0): + self._native.p.remainingBits = 8 + self._native.p.period = period + self._native.p.pendingSB = self.tx + self.tx = 0xFF + lib.mTimingDeschedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event)) + lib.mTimingSchedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event), when) class GBMemory(Memory): def __init__(self, core):
M src/platform/python/mgba/gba.pysrc/platform/python/mgba/gba.py

@@ -8,6 +8,7 @@ from .arm import ARMCore

from .core import Core, needsReset from .tile import Sprite from .memory import Memory +from . import createCallback class GBA(Core): KEY_A = lib.GBA_KEY_A

@@ -21,6 +22,12 @@ KEY_RIGHT = lib.GBA_KEY_RIGHT

KEY_L = lib.GBA_KEY_L KEY_R = lib.GBA_KEY_R + SIO_NORMAL_8 = lib.SIO_NORMAL_8 + SIO_NORMAL_32 = lib.SIO_NORMAL_32 + SIO_MULTI = lib.SIO_MULTI + SIO_UART = lib.SIO_UART + SIO_GPIO = lib.SIO_GPIO + def __init__(self, native): super(GBA, self).__init__(native) self._native = ffi.cast("struct GBA*", native.board)

@@ -39,6 +46,35 @@

def reset(self): super(GBA, self).reset() self.memory = GBAMemory(self._core, self._native.memory.romSize) + + def attachSIO(self, link, mode=lib.SIO_MULTI): + lib.GBASIOSetDriver(ffi.addressof(self._native.sio), link._native, mode) + +createCallback("GBASIOPythonDriver", "init") +createCallback("GBASIOPythonDriver", "deinit") +createCallback("GBASIOPythonDriver", "load") +createCallback("GBASIOPythonDriver", "unload") +createCallback("GBASIOPythonDriver", "writeRegister") + +class GBASIODriver(object): + def __init__(self): + self._handle = ffi.new_handle(self) + self._native = ffi.gc(lib.GBASIOPythonDriverCreate(self._handle), lib.free) + + def init(self): + return True + + def deinit(self): + pass + + def load(self): + return True + + def unload(self): + return True + + def writeRegister(self, address, value): + return value class GBAMemory(Memory): def __init__(self, core, romSize=lib.SIZE_CART0):
M src/platform/python/mgba/log.pysrc/platform/python/mgba/log.py

@@ -4,11 +4,9 @@ # This Source Code Form is subject to the terms of the Mozilla Public

# License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. from ._pylib import ffi, lib +from . import createCallback -@ffi.def_extern() -def _pyLog(logger, category, level, message): - l = ffi.cast("struct mLoggerPy*", logger) - ffi.from_handle(l.pyobj).log(category, level, ffi.string(message).decode('UTF-8')) +createCallback("mLoggerPy", "log", "_pyLog") def installDefault(logger): lib.mLogSetDefaultLogger(logger._native)
A src/platform/python/sio.c

@@ -0,0 +1,76 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "flags.h" + +#define CREATE_SHIM(PLAT, NAME, RETURN) \ + RETURN _py ## PLAT ## SIOPythonDriver ## NAME (void* driver); \ + static RETURN _py ## PLAT ## SIOPythonDriver ## NAME ## Shim(struct PLAT ## SIODriver* driver) { \ + struct PLAT ## SIODriver* py = (struct PLAT ## SIODriver*) driver; \ + return _py ## PLAT ## SIOPythonDriver ## NAME(py); \ + } + +#define CREATE_SHIM_ARGS(PLAT, NAME, RETURN, TYPES, ...) \ + RETURN _py ## PLAT ## SIOPythonDriver ## NAME TYPES; \ + static RETURN _py ## PLAT ## SIOPythonDriver ## NAME ## Shim TYPES { \ + struct PLAT ## SIODriver* py = (struct PLAT ## SIODriver*) driver; \ + return _py ## PLAT ## SIOPythonDriver ## NAME(py, __VA_ARGS__); \ + } + +#ifdef M_CORE_GBA + +#include <mgba/gba/interface.h> + +struct GBASIOPythonDriver { + struct GBASIODriver d; + void* pyobj; +}; + +CREATE_SHIM(GBA, Init, bool); +CREATE_SHIM(GBA, Deinit, void); +CREATE_SHIM(GBA, Load, bool); +CREATE_SHIM(GBA, Unload, bool); +CREATE_SHIM_ARGS(GBA, WriteRegister, uint16_t, (struct GBASIODriver* driver, uint32_t address, uint16_t value), address, value); + +struct GBASIODriver* GBASIOPythonDriverCreate(void* pyobj) { + struct GBASIOPythonDriver* driver = malloc(sizeof(*driver)); + driver->d.init = _pyGBASIOPythonDriverInitShim; + driver->d.deinit = _pyGBASIOPythonDriverDeinitShim; + driver->d.load = _pyGBASIOPythonDriverLoadShim; + driver->d.unload = _pyGBASIOPythonDriverUnloadShim; + driver->d.writeRegister = _pyGBASIOPythonDriverWriteRegisterShim; + + driver->pyobj = pyobj; + return &driver->d; +} + +#endif + +#ifdef M_CORE_GB + +#include <mgba/gb/interface.h> + +struct GBSIOPythonDriver { + struct GBSIODriver d; + void* pyobj; +}; + +CREATE_SHIM(GB, Init, bool); +CREATE_SHIM(GB, Deinit, void); +CREATE_SHIM_ARGS(GB, WriteSB, void, (struct GBSIODriver* driver, uint8_t value), value); +CREATE_SHIM_ARGS(GB, WriteSC, uint8_t, (struct GBSIODriver* driver, uint8_t value), value); + +struct GBSIODriver* GBSIOPythonDriverCreate(void* pyobj) { + struct GBSIOPythonDriver* driver = malloc(sizeof(*driver)); + driver->d.init = _pyGBSIOPythonDriverInitShim; + driver->d.deinit = _pyGBSIOPythonDriverDeinitShim; + driver->d.writeSB = _pyGBSIOPythonDriverWriteSBShim; + driver->d.writeSC = _pyGBSIOPythonDriverWriteSCShim; + + driver->pyobj = pyobj; + return &driver->d; +} + +#endif
A src/platform/python/sio.h

@@ -0,0 +1,41 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifdef M_CORE_GBA + +#include <mgba/gba/interface.h> + +struct GBASIOPythonDriver { + struct GBASIODriver d; + void* pyobj; +}; + +struct GBASIODriver* GBASIOPythonDriverCreate(void* pyobj); + +PYEXPORT bool _pyGBASIOPythonDriverInit(void* driver); +PYEXPORT void _pyGBASIOPythonDriverDeinit(void* driver); +PYEXPORT bool _pyGBASIOPythonDriverLoad(void* driver); +PYEXPORT bool _pyGBASIOPythonDriverUnload(void* driver); +PYEXPORT uint16_t _pyGBASIOPythonDriverWriteRegister(void* driver, uint32_t address, uint16_t value); + +#endif + +#ifdef M_CORE_GB + +#include <mgba/gb/interface.h> + +struct GBSIOPythonDriver { + struct GBSIODriver d; + void* pyobj; +}; + +struct GBSIODriver* GBSIOPythonDriverCreate(void* pyobj); + +PYEXPORT bool _pyGBSIOPythonDriverInit(void* driver); +PYEXPORT void _pyGBSIOPythonDriverDeinit(void* driver); +PYEXPORT void _pyGBSIOPythonDriverWriteSB(void* driver, uint8_t value); +PYEXPORT uint8_t _pyGBSIOPythonDriverWriteSC(void* driver, uint8_t value); + +#endif
M src/platform/python/vfs-py.hsrc/platform/python/vfs-py.h

@@ -13,16 +13,12 @@ };

struct VFile* VFileFromPython(void* fileobj); -extern "Python+C" { - -bool _vfpClose(struct VFile* vf); -off_t _vfpSeek(struct VFile* vf, off_t offset, int whence); -ssize_t _vfpRead(struct VFile* vf, void* buffer, size_t size); -ssize_t _vfpWrite(struct VFile* vf, const void* buffer, size_t size); -void* _vfpMap(struct VFile* vf, size_t size, int flags); -void _vfpUnmap(struct VFile* vf, void* memory, size_t size); -void _vfpTruncate(struct VFile* vf, size_t size); -ssize_t _vfpSize(struct VFile* vf); -bool _vfpSync(struct VFile* vf, const void* buffer, size_t size); - -} +PYEXPORT bool _vfpClose(struct VFile* vf); +PYEXPORT off_t _vfpSeek(struct VFile* vf, off_t offset, int whence); +PYEXPORT ssize_t _vfpRead(struct VFile* vf, void* buffer, size_t size); +PYEXPORT ssize_t _vfpWrite(struct VFile* vf, const void* buffer, size_t size); +PYEXPORT void* _vfpMap(struct VFile* vf, size_t size, int flags); +PYEXPORT void _vfpUnmap(struct VFile* vf, void* memory, size_t size); +PYEXPORT void _vfpTruncate(struct VFile* vf, size_t size); +PYEXPORT ssize_t _vfpSize(struct VFile* vf); +PYEXPORT bool _vfpSync(struct VFile* vf, const void* buffer, size_t size);
M src/platform/qt/ArchiveInspector.cppsrc/platform/qt/ArchiveInspector.cpp

@@ -13,11 +13,12 @@ ArchiveInspector::ArchiveInspector(const QString& filename, QWidget* parent)

: QDialog(parent) { m_ui.setupUi(this); - connect(m_ui.archiveView, &LibraryView::doneLoading, [this]() { + connect(m_ui.archiveView, &LibraryController::doneLoading, [this]() { m_ui.loading->hide(); }); - connect(m_ui.archiveView, SIGNAL(accepted()), this, SIGNAL(accepted())); - m_ui.archiveView->setDirectory(filename); + connect(m_ui.archiveView, &LibraryController::startGame, this, &ArchiveInspector::accepted); + m_ui.archiveView->setViewStyle(LibraryStyle::STYLE_LIST); + m_ui.archiveView->addDirectory(filename); } VFile* ArchiveInspector::selectedVFile() const {
M src/platform/qt/ArchiveInspector.uisrc/platform/qt/ArchiveInspector.ui

@@ -29,15 +29,15 @@ </property>

</widget> </item> <item row="0" column="0" colspan="2"> - <widget class="QGBA::LibraryView" name="archiveView" native="true"/> + <widget class="QGBA::LibraryController" name="archiveView" native="true"/> </item> </layout> </widget> <customwidgets> <customwidget> - <class>QGBA::LibraryView</class> + <class>QGBA::LibraryController</class> <extends>QWidget</extends> - <header>LibraryView.h</header> + <header>library/LibraryController.h</header> <container>1</container> </customwidget> </customwidgets>
M src/platform/qt/AssetTile.cppsrc/platform/qt/AssetTile.cpp

@@ -21,10 +21,6 @@ using namespace QGBA;

AssetTile::AssetTile(QWidget* parent) : QGroupBox(parent) - , m_tileCache(nullptr) - , m_paletteId(0) - , m_paletteSet(0) - , m_index(0) { m_ui.setupUi(this);

@@ -32,7 +28,7 @@ m_ui.preview->setDimensions(QSize(8, 8));

m_ui.color->setDimensions(QSize(1, 1)); m_ui.color->setSize(50); - connect(m_ui.preview, SIGNAL(indexPressed(int)), this, SLOT(selectColor(int))); + connect(m_ui.preview, &Swatch::indexPressed, this, &AssetTile::selectColor); const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
M src/platform/qt/AssetTile.hsrc/platform/qt/AssetTile.h

@@ -31,9 +31,9 @@ private:

Ui::AssetTile m_ui; std::shared_ptr<mTileCache> m_tileCache; - int m_paletteId; - int m_paletteSet; - int m_index; + int m_paletteId = 0; + int m_paletteSet = 0; + int m_index = 0; int m_addressWidth; int m_addressBase;
M src/platform/qt/AssetView.cppsrc/platform/qt/AssetView.cpp

@@ -13,16 +13,17 @@ using namespace QGBA;

AssetView::AssetView(GameController* controller, QWidget* parent) : QWidget(parent) - , m_controller(controller) , m_tileCache(controller->tileCache()) + , m_controller(controller) { m_updateTimer.setSingleShot(true); m_updateTimer.setInterval(1); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateTiles())); - connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), &m_updateTimer, SLOT(start())); - connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), this, SLOT(close())); - connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), &m_updateTimer, SLOT(stop())); + connect(m_controller, &GameController::frameAvailable, &m_updateTimer, + static_cast<void(QTimer::*)()>(&QTimer::start)); + connect(m_controller, &GameController::gameStopped, this, &AssetView::close); + connect(m_controller, &GameController::gameStopped, &m_updateTimer, &QTimer::stop); } void AssetView::updateTiles(bool force) {
M src/platform/qt/AudioProcessor.cppsrc/platform/qt/AudioProcessor.cpp

@@ -44,8 +44,6 @@ }

AudioProcessor::AudioProcessor(QObject* parent) : QObject(parent) - , m_context(nullptr) - , m_samples(2048) { }
M src/platform/qt/AudioProcessor.hsrc/platform/qt/AudioProcessor.h

@@ -47,8 +47,8 @@ protected:

mCoreThread* input() { return m_context; } private: - mCoreThread* m_context; - int m_samples; + mCoreThread* m_context = nullptr; + int m_samples = 2048; static Driver s_driver; };
M src/platform/qt/AudioProcessorQt.cppsrc/platform/qt/AudioProcessorQt.cpp

@@ -17,9 +17,6 @@ using namespace QGBA;

AudioProcessorQt::AudioProcessorQt(QObject* parent) : AudioProcessor(parent) - , m_audioOutput(nullptr) - , m_device(nullptr) - , m_sampleRate(44100) { }
M src/platform/qt/AudioProcessorQt.hsrc/platform/qt/AudioProcessorQt.h

@@ -32,9 +32,9 @@

virtual void requestSampleRate(unsigned) override; private: - QAudioOutput* m_audioOutput; - AudioDevice* m_device; - unsigned m_sampleRate; + QAudioOutput* m_audioOutput = nullptr; + AudioDevice* m_device = nullptr; + unsigned m_sampleRate = 44100; }; }
M src/platform/qt/AudioProcessorSDL.cppsrc/platform/qt/AudioProcessorSDL.cpp

@@ -13,7 +13,6 @@ using namespace QGBA;

AudioProcessorSDL::AudioProcessorSDL(QObject* parent) : AudioProcessor(parent) - , m_audio{ 2048, 44100 } { }
M src/platform/qt/AudioProcessorSDL.hsrc/platform/qt/AudioProcessorSDL.h

@@ -33,7 +33,7 @@

virtual void requestSampleRate(unsigned) override; private: - mSDLAudio m_audio; + mSDLAudio m_audio{2048, 44100}; }; }
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -1,6 +1,3 @@

-cmake_minimum_required(VERSION 2.8.11) -enable_language(CXX) - if(NOT MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") endif()

@@ -22,7 +19,7 @@ endif()

if(SDL2_FOUND) link_directories(${SDL2_LIBDIR}) endif() - list(APPEND PLATFORM_LIBRARY ${SDL_LIBRARY} ${SDLMAIN_LIBRARY}) + list(APPEND PLATFORM_LIBRARY ${SDL_LIBRARY}) list(APPEND PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-events.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-audio.c) include_directories(${SDL_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/src/platform/sdl) endif()

@@ -35,12 +32,10 @@ find_package(Qt5OpenGL)

find_package(Qt5Widgets) if(NOT BUILD_GL AND NOT BUILD_GLES2) - message(WARNING "OpenGL is required to build the Qt port") - set(BUILD_QT OFF PARENT_SCOPE) - return() + message(WARNING "OpenGL is recommended to build the Qt port") endif() -if(NOT Qt5OpenGL_FOUND OR NOT Qt5Widgets_FOUND) +if(NOT Qt5Widgets_FOUND) message(WARNING "Cannot find Qt modules") set(BUILD_QT OFF PARENT_SCOPE) return()

@@ -105,6 +100,7 @@ ShortcutView.cpp

Swatch.cpp TilePainter.cpp TileView.cpp + utils.cpp Window.cpp VFileDevice.cpp VideoView.cpp)

@@ -117,7 +113,6 @@ CheatsView.ui

DebuggerConsole.ui GIFView.ui IOViewer.ui - LibraryView.ui LoadSaveState.ui LogView.ui MemoryView.ui

@@ -138,10 +133,8 @@

set(GB_SRC GBOverride.cpp) -qt5_wrap_ui(UI_SRC ${UI_FILES}) - set(QT_LIBRARIES) -set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5,libqt5opengl5,libqt5network5") +set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5,libqt5opengl5") set(AUDIO_SRC) if(BUILD_SDL)

@@ -189,8 +182,9 @@

if(USE_SQLITE3) list(APPEND SOURCE_FILES ArchiveInspector.cpp - LibraryModel.cpp - LibraryView.cpp) + library/LibraryController.cpp + library/LibraryGrid.cpp + library/LibraryTree.cpp) endif() qt5_add_resources(RESOURCES resources.qrc)

@@ -239,11 +233,16 @@ qt5_add_resources(TRANSLATION_RESOURCES ${TRANSLATION_QRC})

list(APPEND RESOURCES ${TRANSLATION_RESOURCES}) endif() +qt5_wrap_ui(UI_SRC ${UI_FILES}) + add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR}/res/mgba.icns ${SOURCE_FILES} ${PLATFORM_SRC} ${UI_SRC} ${AUDIO_SRC} ${RESOURCES}) set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES};${OS_DEFINES};${QT_DEFINES}") -list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::OpenGL Qt5::Network) -target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) +list(APPEND QT_LIBRARIES Qt5::Widgets) +if(BUILD_GL OR BUILD_GLES2) + list(APPEND QT_LIBRARIES Qt5::OpenGL ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) +endif() +target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES}) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}" PARENT_SCOPE) install(TARGETS ${BINARY_NAME}-qt

@@ -290,3 +289,7 @@ endif()

install(CODE "execute_process(COMMAND \"${CMAKE_SOURCE_DIR}/tools/deploy-mac.py\" -v ${DEPLOY_OPTIONS} \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/Applications/${PROJECT_NAME}.app\")") endif() endif() +if(WIN32 AND CMAKE_MAJOR_VERSION GREATER 2 AND CMAKE_MINOR_VERSION GREATER 7) + # Work around CMake issue #16907 + set_target_properties(${BINARY_NAME}-qt PROPERTIES AUTORCC ON SKIP_AUTORCC ON) +endif()
M src/platform/qt/CheatsView.cppsrc/platform/qt/CheatsView.cpp

@@ -31,12 +31,12 @@

m_ui.cheatList->installEventFilter(this); m_ui.cheatList->setModel(&m_model); - connect(m_ui.load, SIGNAL(clicked()), this, SLOT(load())); - connect(m_ui.save, SIGNAL(clicked()), this, SLOT(save())); - connect(m_ui.addSet, SIGNAL(clicked()), this, SLOT(addSet())); - connect(m_ui.remove, SIGNAL(clicked()), this, SLOT(removeSet())); - connect(controller, SIGNAL(gameStopped(mCoreThread*)), this, SLOT(close())); - connect(controller, SIGNAL(stateLoaded(mCoreThread*)), &m_model, SLOT(invalidated())); + connect(m_ui.load, &QPushButton::clicked, this, &CheatsView::load); + connect(m_ui.save, &QPushButton::clicked, this, &CheatsView::save); + connect(m_ui.addSet, &QPushButton::clicked, this, &CheatsView::addSet); + connect(m_ui.remove, &QPushButton::clicked, this, &CheatsView::removeSet); + connect(controller, &GameController::gameStopped, this, &CheatsView::close); + connect(controller, &GameController::stateLoaded, &m_model, &CheatsModel::invalidated); QPushButton* add; switch (controller->platform()) {
M src/platform/qt/ConfigController.cppsrc/platform/qt/ConfigController.cpp

@@ -11,7 +11,7 @@ #include <QAction>

#include <QDir> #include <QMenu> -#include "feature/commandline.h" +#include <mgba/feature/commandline.h> using namespace QGBA;

@@ -76,14 +76,12 @@ setValue(QVariant(QString(value)));

} void ConfigOption::setValue(const QVariant& value) { - QPair<QAction*, QVariant> action; - foreach (action, m_actions) { + for (QPair<QAction*, QVariant>& action : m_actions) { bool signalsEnabled = action.first->blockSignals(true); action.first->setChecked(value == action.second); action.first->blockSignals(signalsEnabled); } - std::function<void(const QVariant&)> slot; - foreach(slot, m_slots.values()) { + for (std::function<void(const QVariant&)>& slot : m_slots.values()) { slot(value); } }

@@ -92,7 +90,6 @@ QString ConfigController::s_configDir;

ConfigController::ConfigController(QObject* parent) : QObject(parent) - , m_opts() { QString fileName = configDir(); fileName.append(QDir::separator());
M src/platform/qt/ConfigController.hsrc/platform/qt/ConfigController.h

@@ -17,7 +17,7 @@ #include <functional>

#include <mgba/core/config.h> #include <mgba-util/configuration.h> -#include "feature/commandline.h" +#include <mgba/feature/commandline.h> class QAction; class QMenu;

@@ -102,7 +102,7 @@ private:

Configuration* defaults() { return &m_config.defaultsTable; } mCoreConfig m_config; - mCoreOptions m_opts; + mCoreOptions m_opts{}; QMap<QString, ConfigOption*> m_optionSet; QSettings* m_settings;
M src/platform/qt/DebuggerConsole.cppsrc/platform/qt/DebuggerConsole.cpp

@@ -17,10 +17,10 @@ , m_consoleController(controller)

{ m_ui.setupUi(this); - connect(m_ui.prompt, SIGNAL(returnPressed()), this, SLOT(postLine())); - connect(controller, SIGNAL(log(const QString&)), this, SLOT(log(const QString&))); - connect(m_ui.breakpoint, SIGNAL(clicked()), controller, SLOT(attach())); - connect(m_ui.breakpoint, SIGNAL(clicked()), controller, SLOT(breakInto())); + connect(m_ui.prompt, &QLineEdit::returnPressed, this, &DebuggerConsole::postLine); + connect(controller, &DebuggerConsoleController::log, this, &DebuggerConsole::log); + connect(m_ui.breakpoint, &QAbstractButton::clicked, controller, &DebuggerController::attach); + connect(m_ui.breakpoint, &QAbstractButton::clicked, controller, &DebuggerController::breakInto); } void DebuggerConsole::log(const QString& line) {
M src/platform/qt/DebuggerConsoleController.cppsrc/platform/qt/DebuggerConsoleController.cpp

@@ -38,8 +38,16 @@ }

m_cond.wakeOne(); } +void DebuggerConsoleController::detach() { + m_lines.append(QString()); + m_cond.wakeOne(); + DebuggerController::detach(); +} + void DebuggerConsoleController::attachInternal() { + m_history.clear(); mCore* core = m_gameController->thread()->core; + CLIDebuggerAttachBackend(&m_cliDebugger, &m_backend.d); CLIDebuggerAttachSystem(&m_cliDebugger, core->cliDebuggerSystem(core)); }

@@ -60,6 +68,8 @@

void DebuggerConsoleController::deinit(struct CLIDebuggerBackend* be) { Backend* consoleBe = reinterpret_cast<Backend*>(be); DebuggerConsoleController* self = consoleBe->self; + self->m_lines.append(QString()); + self->m_cond.wakeOne(); } const char* DebuggerConsoleController::readLine(struct CLIDebuggerBackend* be, size_t* len) {

@@ -70,7 +80,11 @@ QMutexLocker lock(&self->m_mutex);

while (self->m_lines.isEmpty()) { self->m_cond.wait(&self->m_mutex); } - self->m_last = self->m_lines.takeFirst().toUtf8(); + QString last = self->m_lines.takeFirst(); + if (last.isNull()) { + return nullptr; + } + self->m_last = last.toUtf8(); *len = self->m_last.size(); return self->m_last.constData();

@@ -87,6 +101,9 @@ Backend* consoleBe = reinterpret_cast<Backend*>(be);

DebuggerConsoleController* self = consoleBe->self; GameController::Interrupter interrupter(self->m_gameController, true); QMutexLocker lock(&self->m_mutex); + if (self->m_history.isEmpty()) { + return "i"; + } self->m_last = self->m_history.last().toUtf8(); return self->m_last.constData(); }
M src/platform/qt/DebuggerConsoleController.hsrc/platform/qt/DebuggerConsoleController.h

@@ -30,6 +30,7 @@ void lineAppend(const QString&);

public slots: void enterLine(const QString&); + virtual void detach() override; protected: virtual void attachInternal() override;
M src/platform/qt/DebuggerController.cppsrc/platform/qt/DebuggerController.cpp

@@ -11,8 +11,8 @@ using namespace QGBA;

DebuggerController::DebuggerController(GameController* controller, mDebugger* debugger, QObject* parent) : QObject(parent) - , m_gameController(controller) , m_debugger(debugger) + , m_gameController(controller) { }

@@ -30,7 +30,7 @@ m_gameController->setDebugger(m_debugger);

mDebuggerEnter(m_debugger, DEBUGGER_ENTER_ATTACHED, 0); } else { QObject::disconnect(m_autoattach); - m_autoattach = connect(m_gameController, SIGNAL(gameStarted(mCoreThread*, const QString&)), this, SLOT(attach())); + m_autoattach = connect(m_gameController, &GameController::gameStarted, this, &DebuggerController::attach); } }
M src/platform/qt/Display.cppsrc/platform/qt/Display.cpp

@@ -53,8 +53,6 @@ }

Display::Display(QWidget* parent) : QWidget(parent) - , m_lockAspectRatio(false) - , m_filter(false) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); #ifdef M_CORE_GB

@@ -62,7 +60,7 @@ setMinimumSize(GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS);

#elif defined(M_CORE_GBA) setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); #endif - connect(&m_mouseTimer, SIGNAL(timeout()), this, SIGNAL(hideCursor())); + connect(&m_mouseTimer, &QTimer::timeout, this, &Display::hideCursor); m_mouseTimer.setSingleShot(true); m_mouseTimer.setInterval(MOUSE_DISAPPEAR_TIMER); setMouseTracking(true);

@@ -75,6 +73,10 @@

void Display::lockAspectRatio(bool lock) { m_lockAspectRatio = lock; m_messagePainter.resize(size(), m_lockAspectRatio, devicePixelRatio()); +} + +void Display::lockIntegerScaling(bool lock) { + m_lockIntegerScaling = lock; } void Display::filter(bool filter) {
M src/platform/qt/Display.hsrc/platform/qt/Display.h

@@ -38,6 +38,7 @@ static Display* create(QWidget* parent = nullptr);

static void setDriver(Driver driver) { s_driver = driver; } bool isAspectRatioLocked() const { return m_lockAspectRatio; } + bool isIntegerScalingLocked() const { return m_lockIntegerScaling; } bool isFiltered() const { return m_filter; } virtual bool isDrawing() const = 0;

@@ -55,6 +56,7 @@ virtual void pauseDrawing() = 0;

virtual void unpauseDrawing() = 0; virtual void forceDraw() = 0; virtual void lockAspectRatio(bool lock); + virtual void lockIntegerScaling(bool lock); virtual void filter(bool filter); virtual void framePosted(const uint32_t*) = 0; virtual void setShaders(struct VDir*) = 0;

@@ -73,8 +75,9 @@ static Driver s_driver;

static const int MOUSE_DISAPPEAR_TIMER = 1000; MessagePainter m_messagePainter; - bool m_lockAspectRatio; - bool m_filter; + bool m_lockAspectRatio = false; + bool m_lockIntegerScaling = false; + bool m_filter = false; QTimer m_mouseTimer; };
M src/platform/qt/DisplayGL.cppsrc/platform/qt/DisplayGL.cpp

@@ -5,6 +5,8 @@ * 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" +#if defined(BUILD_GL) || defined(BUILD_GLES) + #include <QApplication> #include <QResizeEvent> #include <QTimer>

@@ -25,10 +27,7 @@ using namespace QGBA;

DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent) : Display(parent) - , m_isDrawing(false) , m_gl(new EmptyGLWidget(format, this)) - , m_drawThread(nullptr) - , m_context(nullptr) { m_painter = new PainterGL(format.majorVersion() < 2 ? 1 : m_gl->format().majorVersion(), m_gl); m_gl->setMouseTracking(true);

@@ -68,11 +67,12 @@ m_drawThread->setObjectName("Painter Thread");

m_gl->context()->doneCurrent(); m_gl->context()->moveToThread(m_drawThread); m_painter->moveToThread(m_drawThread); - connect(m_drawThread, SIGNAL(started()), m_painter, SLOT(start())); + connect(m_drawThread, &QThread::started, m_painter, &PainterGL::start); m_drawThread->start(); mCoreSyncSetVideoSync(&m_context->sync, false); lockAspectRatio(isAspectRatioLocked()); + lockIntegerScaling(isIntegerScalingLocked()); filter(isFiltered()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatioF());

@@ -133,6 +133,13 @@ void DisplayGL::lockAspectRatio(bool lock) {

Display::lockAspectRatio(lock); if (m_drawThread) { QMetaObject::invokeMethod(m_painter, "lockAspectRatio", Q_ARG(bool, lock)); + } +} + +void DisplayGL::lockIntegerScaling(bool lock) { + Display::lockIntegerScaling(lock); + if (m_drawThread) { + QMetaObject::invokeMethod(m_painter, "lockIntegerScaling", Q_ARG(bool, lock)); } }

@@ -290,6 +297,13 @@ forceDraw();

} } +void PainterGL::lockIntegerScaling(bool lock) { + m_backend->lockIntegerScaling = lock; + if (m_started && !m_active) { + forceDraw(); + } +} + void PainterGL::filter(bool filter) { m_backend->filter = filter; if (m_started && !m_active) {

@@ -318,15 +332,6 @@ void PainterGL::draw() {

if (m_queue.isEmpty() || !mCoreThreadIsActive(m_context)) { return; } - if (!m_delayTimer.isValid()) { - m_delayTimer.start(); - } else if (m_delayTimer.elapsed() < 16) { - QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection); - QThread::usleep(500); - return; - } else { - m_delayTimer.restart(); - } if (mCoreSyncWaitFrameStart(&m_context->sync) || !m_queue.isEmpty()) { dequeue();

@@ -335,6 +340,14 @@ m_painter.begin(m_gl->context()->device());

performDraw(); m_painter.end(); m_backend->swap(m_backend); + if (!m_delayTimer.isValid()) { + m_delayTimer.start(); + } else { + while (m_delayTimer.elapsed() < 15) { + QThread::usleep(100); + } + m_delayTimer.restart(); + } } else { mCoreSyncWaitFrameEnd(&m_context->sync); }

@@ -469,3 +482,5 @@

VideoShader* PainterGL::shaders() { return &m_shader; } + +#endif
M src/platform/qt/DisplayGL.hsrc/platform/qt/DisplayGL.h

@@ -6,6 +6,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef QGBA_DISPLAY_GL #define QGBA_DISPLAY_GL +#if defined(BUILD_GL) || defined(BUILD_GLES) + #include "Display.h" #ifdef USE_EPOXY

@@ -55,6 +57,7 @@ void pauseDrawing() override;

void unpauseDrawing() override; void forceDraw() override; void lockAspectRatio(bool lock) override; + void lockIntegerScaling(bool lock) override; void filter(bool filter) override; void framePosted(const uint32_t*) override; void setShaders(struct VDir*) override;

@@ -67,11 +70,11 @@

private: void resizePainter(); - bool m_isDrawing; + bool m_isDrawing = false; QGLWidget* m_gl; PainterGL* m_painter; - QThread* m_drawThread; - mCoreThread* m_context; + QThread* m_drawThread = nullptr; + mCoreThread* m_context = nullptr; }; class PainterGL : public QObject {

@@ -96,6 +99,7 @@ void pause();

void unpause(); void resize(const QSize& size); void lockAspectRatio(bool lock); + void lockIntegerScaling(bool lock); void filter(bool filter); void setShaders(struct VDir*);

@@ -126,3 +130,5 @@

} #endif + +#endif
M src/platform/qt/DisplayQt.cppsrc/platform/qt/DisplayQt.cpp

@@ -14,8 +14,6 @@ using namespace QGBA;

DisplayQt::DisplayQt(QWidget* parent) : Display(parent) - , m_isDrawing(false) - , m_backing(nullptr) { }

@@ -27,6 +25,11 @@ }

void DisplayQt::lockAspectRatio(bool lock) { Display::lockAspectRatio(lock); + update(); +} + +void DisplayQt::lockIntegerScaling(bool lock) { + Display::lockIntegerScaling(lock); update(); }

@@ -65,6 +68,10 @@ ds.setWidth(s.height() * m_width / m_height);

} else if (s.width() * m_height < s.height() * m_width) { ds.setHeight(s.width() * m_height / m_width); } + } + if (isIntegerScalingLocked()) { + ds.setWidth(ds.width() - ds.width() % m_width); + ds.setHeight(ds.height() - ds.height() % m_height); } QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2); QRect full(origin, ds);
M src/platform/qt/DisplayQt.hsrc/platform/qt/DisplayQt.h

@@ -30,6 +30,7 @@ void pauseDrawing() override { m_isDrawing = false; }

void unpauseDrawing() override { m_isDrawing = true; } void forceDraw() override { update(); } void lockAspectRatio(bool lock) override; + void lockIntegerScaling(bool lock) override; void filter(bool filter) override; void framePosted(const uint32_t*) override; void setShaders(struct VDir*) override {}

@@ -39,10 +40,10 @@ protected:

virtual void paintEvent(QPaintEvent*) override; private: - bool m_isDrawing; + bool m_isDrawing = false; unsigned m_width; unsigned m_height; - QImage m_backing; + QImage m_backing{nullptr}; }; }
M src/platform/qt/GBAApp.cppsrc/platform/qt/GBAApp.cpp

@@ -33,7 +33,6 @@ mLOG_DEFINE_CATEGORY(QT, "Qt", "platform.qt");

GBAApp::GBAApp(int& argc, char* argv[]) : QApplication(argc, argv) - , m_db(nullptr) { g_app = this;

@@ -127,6 +126,9 @@ Window* w = new Window(&m_configController, m_multiplayer.attached());

int windowId = m_multiplayer.attached(); connect(w, &Window::destroyed, [this, w]() { m_windows.removeAll(w); + for (Window* w : m_windows) { + w->updateMultiplayerStatus(m_windows.count() < MAX_GBAS); + } }); m_windows.append(w); w->setAttribute(Qt::WA_DeleteOnClose);

@@ -134,6 +136,9 @@ w->loadConfig();

w->show(); w->controller()->setMultiplayerController(&m_multiplayer); w->multiplayerChanged(); + for (Window* w : m_windows) { + w->updateMultiplayerStatus(m_windows.count() < MAX_GBAS); + } return w; }

@@ -163,7 +168,7 @@ pauseAll(&paused);

QString filename = QFileDialog::getOpenFileName(owner, title, m_configController.getOption("lastDirectory"), filter); continueAll(paused); if (!filename.isEmpty()) { - m_configController.setOption("lastDirectory", QFileInfo(filename).dir().path()); + m_configController.setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath()); } return filename; }

@@ -174,7 +179,7 @@ pauseAll(&paused);

QString filename = QFileDialog::getSaveFileName(owner, title, m_configController.getOption("lastDirectory"), filter); continueAll(paused); if (!filename.isEmpty()) { - m_configController.setOption("lastDirectory", QFileInfo(filename).dir().path()); + m_configController.setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath()); } return filename; }

@@ -185,23 +190,11 @@ pauseAll(&paused);

QString filename = QFileDialog::getExistingDirectory(owner, title, m_configController.getOption("lastDirectory")); continueAll(paused); if (!filename.isEmpty()) { - m_configController.setOption("lastDirectory", QFileInfo(filename).dir().path()); + m_configController.setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath()); } return filename; } -QFileDialog* GBAApp::getOpenFileDialog(QWidget* owner, const QString& title, const QString& filter) { - FileDialog* dialog = new FileDialog(this, owner, title, filter); - dialog->setAcceptMode(QFileDialog::AcceptOpen); - return dialog; -} - -QFileDialog* GBAApp::getSaveFileDialog(QWidget* owner, const QString& title, const QString& filter) { - FileDialog* dialog = new FileDialog(this, owner, title, filter); - dialog->setAcceptMode(QFileDialog::AcceptSave); - return dialog; -} - QString GBAApp::dataDir() { #ifdef DATADIR QString path = QString::fromUtf8(DATADIR);

@@ -240,24 +233,6 @@ bool GBAApp::reloadGameDB() {

return false; } #endif - -GBAApp::FileDialog::FileDialog(GBAApp* app, QWidget* parent, const QString& caption, const QString& filter) - : QFileDialog(parent, caption, app->m_configController.getOption("lastDirectory"), filter) - , m_app(app) -{ -} - -int GBAApp::FileDialog::exec() { - QList<Window*> paused; - m_app->pauseAll(&paused); - bool didAccept = QFileDialog::exec() == QDialog::Accepted; - QStringList filenames = selectedFiles(); - if (!filenames.isEmpty()) { - m_app->m_configController.setOption("lastDirectory", QFileInfo(filenames[0]).dir().path()); - } - m_app->continueAll(paused); - return didAccept; -} #ifdef USE_SQLITE3 GameDBParser::GameDBParser(NoIntroDB* db, QObject* parent)
M src/platform/qt/GBAApp.hsrc/platform/qt/GBAApp.h

@@ -55,9 +55,6 @@ QString getOpenFileName(QWidget* owner, const QString& title, const QString& filter = QString());

QString getSaveFileName(QWidget* owner, const QString& title, const QString& filter = QString()); QString getOpenDirectoryName(QWidget* owner, const QString& title); - QFileDialog* getOpenFileDialog(QWidget* owner, const QString& title, const QString& filter = QString()); - QFileDialog* getSaveFileDialog(QWidget* owner, const QString& title, const QString& filter = QString()); - const NoIntroDB* gameDB() const { return m_db; } bool reloadGameDB();

@@ -65,16 +62,6 @@ protected:

bool event(QEvent*); private: - class FileDialog : public QFileDialog { - public: - FileDialog(GBAApp* app, QWidget* parent = nullptr, const QString& caption = QString(), - const QString& filter = QString()); - virtual int exec() override; - - private: - GBAApp* m_app; - }; - Window* newWindowInternal(); void pauseAll(QList<Window*>* paused);

@@ -84,7 +71,7 @@ ConfigController m_configController;

QList<Window*> m_windows; MultiplayerController m_multiplayer; - NoIntroDB* m_db; + NoIntroDB* m_db = nullptr; #ifdef USE_SQLITE3 QThread m_parseThread; #endif
A src/platform/qt/GBAKeyEditor.cpp

@@ -0,0 +1,408 @@

+/* Copyright (c) 2013-2014 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 "GBAKeyEditor.h" + +#include <QComboBox> +#include <QHBoxLayout> +#include <QPaintEvent> +#include <QPainter> +#include <QPushButton> +#include <QVBoxLayout> + +#include "InputController.h" +#include "KeyEditor.h" + +#ifdef BUILD_SDL +#include "platform/sdl/sdl-events.h" +#endif + +using namespace QGBA; + +const qreal GBAKeyEditor::DPAD_CENTER_X = 0.247; +const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.432; +const qreal GBAKeyEditor::DPAD_WIDTH = 0.12; +const qreal GBAKeyEditor::DPAD_HEIGHT = 0.12; + +GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& profile, QWidget* parent) + : QWidget(parent) + , m_type(type) + , m_profile(profile) + , m_controller(controller) +{ + setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint); + setMinimumSize(300, 300); + + const mInputMap* map = controller->map(); + controller->stealFocus(this); + + m_keyDU = new KeyEditor(this); + m_keyDD = new KeyEditor(this); + m_keyDL = new KeyEditor(this); + m_keyDR = new KeyEditor(this); + m_keySelect = new KeyEditor(this); + m_keyStart = new KeyEditor(this); + m_keyA = new KeyEditor(this); + m_keyB = new KeyEditor(this); + m_keyL = new KeyEditor(this); + m_keyR = new KeyEditor(this); + + refresh(); + +#ifdef BUILD_SDL + if (type == SDL_BINDING_BUTTON) { + m_profileSelect = new QComboBox(this); + connect(m_profileSelect, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), + this, &GBAKeyEditor::selectGamepad); + + updateJoysticks(); + + m_clear = new QWidget(this); + QHBoxLayout* layout = new QHBoxLayout; + m_clear->setLayout(layout); + layout->setSpacing(6); + + QPushButton* clearButton = new QPushButton(tr("Clear Button")); + layout->addWidget(clearButton); + connect(clearButton, &QAbstractButton::pressed, [this]() { + if (!findFocus()) { + return; + } + bool signalsBlocked = (*m_currentKey)->blockSignals(true); + (*m_currentKey)->clearButton(); + (*m_currentKey)->clearHat(); + (*m_currentKey)->blockSignals(signalsBlocked); + }); + + QPushButton* clearAxis = new QPushButton(tr("Clear Analog")); + layout->addWidget(clearAxis); + connect(clearAxis, &QAbstractButton::pressed, [this]() { + if (!findFocus()) { + return; + } + bool signalsBlocked = (*m_currentKey)->blockSignals(true); + (*m_currentKey)->clearAxis(); + (*m_currentKey)->blockSignals(signalsBlocked); + }); + + QPushButton* updateJoysticksButton = new QPushButton(tr("Refresh")); + layout->addWidget(updateJoysticksButton); + connect(updateJoysticksButton, &QAbstractButton::pressed, this, &GBAKeyEditor::updateJoysticks); + } +#endif + + m_buttons = new QWidget(this); + QVBoxLayout* layout = new QVBoxLayout; + m_buttons->setLayout(layout); + + QPushButton* setAll = new QPushButton(tr("Set all")); + connect(setAll, &QAbstractButton::pressed, this, &GBAKeyEditor::setAll); + layout->addWidget(setAll); + + layout->setSpacing(6); + + m_keyOrder = QList<KeyEditor*>{ + m_keyDU, + m_keyDR, + m_keyDD, + m_keyDL, + m_keyA, + m_keyB, + m_keySelect, + m_keyStart, + m_keyL, + m_keyR + }; + + for (auto& key : m_keyOrder) { + connect(key, &KeyEditor::valueChanged, this, &GBAKeyEditor::setNext); + connect(key, &KeyEditor::axisChanged, this, &GBAKeyEditor::setNext); + connect(key, &KeyEditor::hatChanged, this, &GBAKeyEditor::setNext); + key->installEventFilter(this); + } + + m_currentKey = m_keyOrder.end(); + + m_background.load(":/res/keymap.qpic"); + + setAll->setFocus(); +} + +GBAKeyEditor::~GBAKeyEditor() { + m_controller->releaseFocus(this); +} + +void GBAKeyEditor::setAll() { + m_currentKey = m_keyOrder.begin(); + (*m_currentKey)->setFocus(); +} + +void GBAKeyEditor::resizeEvent(QResizeEvent* event) { + setLocation(m_buttons, 0.5, 0.2); + setLocation(m_keyDU, DPAD_CENTER_X, DPAD_CENTER_Y - DPAD_HEIGHT); + setLocation(m_keyDD, DPAD_CENTER_X, DPAD_CENTER_Y + DPAD_HEIGHT); + setLocation(m_keyDL, DPAD_CENTER_X - DPAD_WIDTH, DPAD_CENTER_Y); + setLocation(m_keyDR, DPAD_CENTER_X + DPAD_WIDTH, DPAD_CENTER_Y); + setLocation(m_keySelect, 0.415, 0.93); + setLocation(m_keyStart, 0.585, 0.93); + setLocation(m_keyA, 0.826, 0.475); + setLocation(m_keyB, 0.667, 0.514); + setLocation(m_keyL, 0.1, 0.1); + setLocation(m_keyR, 0.9, 0.1); + + if (m_profileSelect) { + setLocation(m_profileSelect, 0.5, 0.67); + } + + if (m_clear) { + setLocation(m_clear, 0.5, 0.77); + } +} + +void GBAKeyEditor::paintEvent(QPaintEvent* event) { + QPainter painter(this); + painter.scale(width() / 480.0, height() / 480.0); + painter.drawPicture(0, 0, m_background); +} + +void GBAKeyEditor::closeEvent(QCloseEvent*) { + m_controller->releaseFocus(this); +} + +bool GBAKeyEditor::event(QEvent* event) { + QEvent::Type type = event->type(); + if (type == QEvent::WindowActivate || type == QEvent::Show) { + m_controller->stealFocus(this); + } else if (type == QEvent::WindowDeactivate || type == QEvent::Hide) { + m_controller->releaseFocus(this); + } + return QWidget::event(event); +} + +bool GBAKeyEditor::eventFilter(QObject* obj, QEvent* event) { + if (event->type() != QEvent::FocusIn) { + return false; + } + findFocus(static_cast<KeyEditor*>(obj)); + return true; +} + +void GBAKeyEditor::setNext() { + if (m_currentKey == m_keyOrder.end()) { + return; + } + + ++m_currentKey; + if (m_currentKey != m_keyOrder.end()) { + (*m_currentKey)->setFocus(); + } else { + (*(m_currentKey - 1))->clearFocus(); + } +} + +void GBAKeyEditor::save() { +#ifdef BUILD_SDL + m_controller->unbindAllAxes(m_type); +#endif + + bindKey(m_keyDU, GBA_KEY_UP); + bindKey(m_keyDD, GBA_KEY_DOWN); + bindKey(m_keyDL, GBA_KEY_LEFT); + bindKey(m_keyDR, GBA_KEY_RIGHT); + bindKey(m_keySelect, GBA_KEY_SELECT); + bindKey(m_keyStart, GBA_KEY_START); + bindKey(m_keyA, GBA_KEY_A); + bindKey(m_keyB, GBA_KEY_B); + bindKey(m_keyL, GBA_KEY_L); + bindKey(m_keyR, GBA_KEY_R); + m_controller->saveConfiguration(m_type); + +#ifdef BUILD_SDL + if (m_profileSelect) { + m_controller->setPreferredGamepad(m_type, m_profileSelect->currentText()); + } +#endif + + if (!m_profile.isNull()) { + m_controller->saveProfile(m_type, m_profile); + } +} + +void GBAKeyEditor::refresh() { + const mInputMap* map = m_controller->map(); + lookupBinding(map, m_keyDU, GBA_KEY_UP); + lookupBinding(map, m_keyDD, GBA_KEY_DOWN); + lookupBinding(map, m_keyDL, GBA_KEY_LEFT); + lookupBinding(map, m_keyDR, GBA_KEY_RIGHT); + lookupBinding(map, m_keySelect, GBA_KEY_SELECT); + lookupBinding(map, m_keyStart, GBA_KEY_START); + lookupBinding(map, m_keyA, GBA_KEY_A); + lookupBinding(map, m_keyB, GBA_KEY_B); + lookupBinding(map, m_keyL, GBA_KEY_L); + lookupBinding(map, m_keyR, GBA_KEY_R); + +#ifdef BUILD_SDL + lookupAxes(map); + lookupHats(map); +#endif +} + +void GBAKeyEditor::lookupBinding(const mInputMap* map, KeyEditor* keyEditor, GBAKey key) { +#ifdef BUILD_SDL + if (m_type == SDL_BINDING_BUTTON) { + int value = mInputQueryBinding(map, m_type, key); + keyEditor->setValueButton(value); + return; + } +#endif + keyEditor->setValueKey(mInputQueryBinding(map, m_type, key)); +} + +#ifdef BUILD_SDL +void GBAKeyEditor::lookupAxes(const mInputMap* map) { + mInputEnumerateAxes(map, m_type, [](int axis, const mInputAxis* description, void* user) { + GBAKeyEditor* self = static_cast<GBAKeyEditor*>(user); + if (description->highDirection != GBA_KEY_NONE) { + KeyEditor* key = self->keyById(static_cast<enum GBAKey>(description->highDirection)); + if (key) { + key->setValueAxis(axis, description->deadHigh); + } + } + if (description->lowDirection != GBA_KEY_NONE) { + KeyEditor* key = self->keyById(static_cast<enum GBAKey>(description->lowDirection)); + if (key) { + key->setValueAxis(axis, description->deadLow); + } + } + }, this); +} + +void GBAKeyEditor::lookupHats(const mInputMap* map) { + struct mInputHatBindings bindings; + int i = 0; + while (mInputQueryHat(map, m_type, i, &bindings)) { + if (bindings.up >= 0) { + KeyEditor* key = keyById(static_cast<enum GBAKey>(bindings.up)); + if (key) { + key->setValueHat(i, GamepadHatEvent::UP); + } + } + if (bindings.right >= 0) { + KeyEditor* key = keyById(static_cast<enum GBAKey>(bindings.right)); + if (key) { + key->setValueHat(i, GamepadHatEvent::RIGHT); + } + } + if (bindings.down >= 0) { + KeyEditor* key = keyById(static_cast<enum GBAKey>(bindings.down)); + if (key) { + key->setValueHat(i, GamepadHatEvent::DOWN); + } + } + if (bindings.left >= 0) { + KeyEditor* key = keyById(static_cast<enum GBAKey>(bindings.left)); + if (key) { + key->setValueHat(i, GamepadHatEvent::LEFT); + } + } + ++i; + } +} +#endif + +void GBAKeyEditor::bindKey(const KeyEditor* keyEditor, GBAKey key) { +#ifdef BUILD_SDL + if (m_type == SDL_BINDING_BUTTON && keyEditor->axis() >= 0) { + m_controller->bindAxis(m_type, keyEditor->axis(), keyEditor->direction(), key); + } + if (m_type == SDL_BINDING_BUTTON && keyEditor->hat() >= 0) { + m_controller->bindHat(m_type, keyEditor->hat(), keyEditor->hatDirection(), key); + } +#endif + m_controller->bindKey(m_type, keyEditor->value(), key); +} + +bool GBAKeyEditor::findFocus(KeyEditor* needle) { + if (m_currentKey != m_keyOrder.end() && (*m_currentKey)->hasFocus()) { + return true; + } + + for (auto key = m_keyOrder.begin(); key != m_keyOrder.end(); ++key) { + if ((*key)->hasFocus() || needle == *key) { + m_currentKey = key; + return true; + } + } + return m_currentKey != m_keyOrder.end(); +} + +#ifdef BUILD_SDL +void GBAKeyEditor::setAxisValue(int axis, int32_t value) { + if (!findFocus()) { + return; + } + KeyEditor* focused = *m_currentKey; + focused->setValueAxis(axis, value); +} + +void GBAKeyEditor::selectGamepad(int index) { + m_controller->setGamepad(m_type, index); + m_profile = m_profileSelect->currentText(); + m_controller->loadProfile(m_type, m_profile); + refresh(); +} +#endif + +KeyEditor* GBAKeyEditor::keyById(GBAKey key) { + switch (key) { + case GBA_KEY_UP: + return m_keyDU; + case GBA_KEY_DOWN: + return m_keyDD; + case GBA_KEY_LEFT: + return m_keyDL; + case GBA_KEY_RIGHT: + return m_keyDR; + case GBA_KEY_A: + return m_keyA; + case GBA_KEY_B: + return m_keyB; + case GBA_KEY_L: + return m_keyL; + case GBA_KEY_R: + return m_keyR; + case GBA_KEY_SELECT: + return m_keySelect; + case GBA_KEY_START: + return m_keyStart; + default: + break; + } + return nullptr; +} + +void GBAKeyEditor::setLocation(QWidget* widget, qreal x, qreal y) { + QSize s = size(); + QSize hint = widget->sizeHint(); + widget->setGeometry(s.width() * x - hint.width() / 2.0, s.height() * y - hint.height() / 2.0, hint.width(), + hint.height()); +} + +#ifdef BUILD_SDL +void GBAKeyEditor::updateJoysticks() { + m_controller->updateJoysticks(); + m_controller->recalibrateAxes(); + + // Block the currentIndexChanged signal while rearranging the combo box + auto wasBlocked = m_profileSelect->blockSignals(true); + m_profileSelect->clear(); + m_profileSelect->addItems(m_controller->connectedGamepads(m_type)); + int activeGamepad = m_controller->gamepad(m_type); + m_profileSelect->setCurrentIndex(activeGamepad); + m_profileSelect->blockSignals(wasBlocked); + + selectGamepad(activeGamepad); +} +#endif
A src/platform/qt/GBAKeyEditor.h

@@ -0,0 +1,96 @@

+/* 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_GBA_KEY_EDITOR +#define QGBA_GBA_KEY_EDITOR + +#include <QList> +#include <QPicture> +#include <QSet> +#include <QWidget> + +#include <mgba/internal/gba/input.h> + +class QComboBox; +class QTimer; + +namespace QGBA { + +class InputController; +class KeyEditor; + +class GBAKeyEditor : public QWidget { +Q_OBJECT + +public: + GBAKeyEditor(InputController* controller, int type, const QString& profile = QString(), QWidget* parent = nullptr); + virtual ~GBAKeyEditor(); + +public slots: + void setAll(); + void save(); + +protected: + virtual void resizeEvent(QResizeEvent*) override; + virtual void paintEvent(QPaintEvent*) override; + virtual bool event(QEvent*) override; + virtual void closeEvent(QCloseEvent*) override; + virtual bool eventFilter(QObject* obj, QEvent* event) override; + +private slots: + void setNext(); + void refresh(); +#ifdef BUILD_SDL + void setAxisValue(int axis, int32_t value); + void selectGamepad(int index); + void updateJoysticks(); +#endif + +private: + static const qreal DPAD_CENTER_X; + static const qreal DPAD_CENTER_Y; + static const qreal DPAD_WIDTH; + static const qreal DPAD_HEIGHT; + + void setLocation(QWidget* widget, qreal x, qreal y); + + void lookupBinding(const mInputMap*, KeyEditor*, GBAKey); + void bindKey(const KeyEditor*, GBAKey); + + bool findFocus(KeyEditor* needle = nullptr); + +#ifdef BUILD_SDL + void lookupAxes(const mInputMap*); + void lookupHats(const mInputMap*); +#endif + + KeyEditor* keyById(GBAKey); + + QComboBox* m_profileSelect = nullptr; + QWidget* m_clear = nullptr; + QWidget* m_buttons; + KeyEditor* m_keyDU; + KeyEditor* m_keyDD; + KeyEditor* m_keyDL; + KeyEditor* m_keyDR; + KeyEditor* m_keySelect; + KeyEditor* m_keyStart; + KeyEditor* m_keyA; + KeyEditor* m_keyB; + KeyEditor* m_keyL; + KeyEditor* m_keyR; + QList<KeyEditor*> m_keyOrder; + QList<KeyEditor*>::iterator m_currentKey; + + uint32_t m_type; + QString m_profile; + InputController* m_controller; + + QPicture m_background; +}; + +} + +#endif
M src/platform/qt/GDBController.cppsrc/platform/qt/GDBController.cpp

@@ -11,7 +11,6 @@ using namespace QGBA;

GDBController::GDBController(GameController* controller, QObject* parent) : DebuggerController(controller, &m_gdbStub.d, parent) - , m_port(2345) , m_bindAddress({ IPV4, 0 }) { GDBStubCreate(&m_gdbStub);
M src/platform/qt/GDBController.hsrc/platform/qt/GDBController.h

@@ -40,7 +40,7 @@ virtual void shutdownInternal() override;

GDBStub m_gdbStub; - ushort m_port; + ushort m_port = 2345; Address m_bindAddress; };
M src/platform/qt/GDBWindow.cppsrc/platform/qt/GDBWindow.cpp

@@ -38,12 +38,12 @@ settingsGrid->addWidget(bindAddressLabel, 1, 0, Qt::AlignRight);

m_portEdit = new QLineEdit("2345"); m_portEdit->setMaxLength(5); - connect(m_portEdit, SIGNAL(textChanged(const QString&)), this, SLOT(portChanged(const QString&))); + connect(m_portEdit, &QLineEdit::textChanged, this, &GDBWindow::portChanged); settingsGrid->addWidget(m_portEdit, 0, 1, Qt::AlignLeft); m_bindAddressEdit = new QLineEdit("0.0.0.0"); m_bindAddressEdit->setMaxLength(15); - connect(m_bindAddressEdit, SIGNAL(textChanged(const QString&)), this, SLOT(bindAddressChanged(const QString&))); + connect(m_bindAddressEdit, &QLineEdit::textChanged, this, &GDBWindow::bindAddressChanged); settingsGrid->addWidget(m_bindAddressEdit, 1, 1, Qt::AlignLeft); QHBoxLayout* buttons = new QHBoxLayout;

@@ -57,9 +57,9 @@ buttons->addWidget(m_breakButton);

mainSegment->addLayout(buttons); - connect(m_gdbController, SIGNAL(listening()), this, SLOT(started())); - connect(m_gdbController, SIGNAL(listenFailed()), this, SLOT(failed())); - connect(m_breakButton, SIGNAL(clicked()), controller, SLOT(breakInto())); + connect(m_gdbController, &GDBController::listening, this, &GDBWindow::started); + connect(m_gdbController, &GDBController::listenFailed, this, &GDBWindow::failed); + connect(m_breakButton, &QAbstractButton::clicked, controller, &DebuggerController::breakInto); if (m_gdbController->isAttached()) { started();

@@ -103,9 +103,9 @@ m_portEdit->setEnabled(false);

m_bindAddressEdit->setEnabled(false); m_startStopButton->setText(tr("Stop")); m_breakButton->setEnabled(true); - disconnect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(listen())); - connect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(detach())); - connect(m_startStopButton, SIGNAL(clicked()), this, SLOT(stopped())); + disconnect(m_startStopButton, &QAbstractButton::clicked, m_gdbController, &GDBController::listen); + connect(m_startStopButton, &QAbstractButton::clicked, m_gdbController, &DebuggerController::detach); + connect(m_startStopButton, &QAbstractButton::clicked, this, &GDBWindow::stopped); } void GDBWindow::stopped() {

@@ -113,9 +113,9 @@ m_portEdit->setEnabled(true);

m_bindAddressEdit->setEnabled(true); m_startStopButton->setText(tr("Start")); m_breakButton->setEnabled(false); - disconnect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(detach())); - disconnect(m_startStopButton, SIGNAL(clicked()), this, SLOT(stopped())); - connect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(listen())); + disconnect(m_startStopButton, &QAbstractButton::clicked, m_gdbController, &DebuggerController::detach); + disconnect(m_startStopButton, &QAbstractButton::clicked, this, &GDBWindow::stopped); + connect(m_startStopButton, &QAbstractButton::clicked, m_gdbController, &GDBController::listen); } void GDBWindow::failed() {
M src/platform/qt/GIFView.cppsrc/platform/qt/GIFView.cpp

@@ -22,14 +22,15 @@ : QWidget(parent)

{ m_ui.setupUi(this); - connect(m_ui.start, SIGNAL(clicked()), this, SLOT(startRecording())); - connect(m_ui.stop, SIGNAL(clicked()), this, SLOT(stopRecording())); + connect(m_ui.start, &QAbstractButton::clicked, this, &GIFView::startRecording); + connect(m_ui.stop, &QAbstractButton::clicked, this, &GIFView::stopRecording); - connect(m_ui.selectFile, SIGNAL(clicked()), this, SLOT(selectFile())); - connect(m_ui.filename, SIGNAL(textChanged(const QString&)), this, SLOT(setFilename(const QString&))); + connect(m_ui.selectFile, &QAbstractButton::clicked, this, &GIFView::selectFile); + connect(m_ui.filename, &QLineEdit::textChanged, this, &GIFView::setFilename); - connect(m_ui.frameskip, SIGNAL(valueChanged(int)), this, SLOT(updateDelay())); - connect(m_ui.delayAuto, SIGNAL(clicked(bool)), this, SLOT(updateDelay())); + connect(m_ui.frameskip, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), + this, &GIFView::updateDelay); + connect(m_ui.delayAuto, &QAbstractButton::clicked, this, &GIFView::updateDelay); ImageMagickGIFEncoderInit(&m_encoder); }
M src/platform/qt/GameController.cppsrc/platform/qt/GameController.cpp

@@ -32,44 +32,16 @@ #include <mgba/internal/gb/gb.h>

#include <mgba/internal/gb/renderers/tile-cache.h> #endif #include <mgba-util/vfs.h> +#include <mgba/feature/video-logger.h> using namespace QGBA; using namespace std; GameController::GameController(QObject* parent) : QObject(parent) - , m_drawContext(nullptr) - , m_frontBuffer(nullptr) - , m_threadContext() - , m_activeKeys(0) - , m_inactiveKeys(0) - , m_logLevels(0) - , m_gameOpen(false) - , m_vf(nullptr) - , m_useBios(false) , m_audioProcessor(AudioProcessor::create()) - , m_pauseAfterFrame(false) - , m_sync(true) - , m_videoSync(VIDEO_SYNC) - , m_audioSync(AUDIO_SYNC) - , m_fpsTarget(-1) - , m_turbo(false) - , m_turboForced(false) - , m_turboSpeed(-1) - , m_wasPaused(false) - , m_audioChannels{ true, true, true, true, true, true } - , m_videoLayers{ true, true, true, true, true } - , m_autofire{} - , m_autofireStatus{} - , m_inputController(nullptr) - , m_multiplayer(nullptr) - , m_stream(nullptr) - , m_stateSlot(1) - , m_backupLoadState(nullptr) - , m_backupSaveState(nullptr) , m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC) , m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC) - , m_override(nullptr) { #ifdef M_CORE_GBA m_lux.p = this;

@@ -87,38 +59,20 @@ #endif

m_threadContext.startCallback = [](mCoreThread* context) { GameController* controller = static_cast<GameController*>(context->userData); - context->core->setRotation(context->core, controller->m_inputController->rotationSource()); - context->core->setRumble(context->core, controller->m_inputController->rumble()); + context->core->setPeripheral(context->core, mPERIPH_ROTATION, controller->m_inputController->rotationSource()); + context->core->setPeripheral(context->core, mPERIPH_RUMBLE, controller->m_inputController->rumble()); -#ifdef M_CORE_GBA - GBA* gba = static_cast<GBA*>(context->core->board); -#endif -#ifdef M_CORE_GB - GB* gb = static_cast<GB*>(context->core->board); -#endif + for (size_t i = 0; i < controller->m_audioChannels.size(); ++i) { + context->core->enableAudioChannel(context->core, i, controller->m_audioChannels[i]); + } + for (size_t i = 0; i < controller->m_videoLayers.size(); ++i) { + context->core->enableVideoLayer(context->core, i, controller->m_videoLayers[i]); + } + switch (context->core->platform(context->core)) { #ifdef M_CORE_GBA case PLATFORM_GBA: - gba->luminanceSource = &controller->m_lux; - gba->audio.psg.forceDisableCh[0] = !controller->m_audioChannels[0]; - gba->audio.psg.forceDisableCh[1] = !controller->m_audioChannels[1]; - gba->audio.psg.forceDisableCh[2] = !controller->m_audioChannels[2]; - gba->audio.psg.forceDisableCh[3] = !controller->m_audioChannels[3]; - gba->audio.forceDisableChA = !controller->m_audioChannels[4]; - gba->audio.forceDisableChB = !controller->m_audioChannels[5]; - gba->video.renderer->disableBG[0] = !controller->m_videoLayers[0]; - gba->video.renderer->disableBG[1] = !controller->m_videoLayers[1]; - gba->video.renderer->disableBG[2] = !controller->m_videoLayers[2]; - gba->video.renderer->disableBG[3] = !controller->m_videoLayers[3]; - gba->video.renderer->disableOBJ = !controller->m_videoLayers[4]; - break; -#endif -#ifdef M_CORE_GB - case PLATFORM_GB: - gb->audio.forceDisableCh[0] = !controller->m_audioChannels[0]; - gb->audio.forceDisableCh[1] = !controller->m_audioChannels[1]; - gb->audio.forceDisableCh[2] = !controller->m_audioChannels[2]; - gb->audio.forceDisableCh[3] = !controller->m_audioChannels[3]; + context->core->setPeripheral(context->core, mPERIPH_GBA_LUMINANCE, &controller->m_lux); break; #endif default:

@@ -173,8 +127,9 @@ controller->m_multiplayer->detachGame(controller);

} controller->m_patch = QString(); controller->clearOverride(); + controller->endVideoLog(); - controller->m_audioProcessor->pause(); + QMetaObject::invokeMethod(controller->m_audioProcessor, "pause"); QMetaObject::invokeMethod(controller, "gameStopped", Q_ARG(mCoreThread*, context)); QMetaObject::invokeMethod(controller, "cleanGame");

@@ -217,18 +172,16 @@ QMetaObject::invokeMethod(controller, "gamePaused", Q_ARG(mCoreThread*, context));

} }; - // TODO: Put back - /*m_threadContext.stopCallback = [](mCoreThread* context) { + m_threadContext.sleepCallback = [](mCoreThread* context) { if (!context) { - return false; + return; } GameController* controller = static_cast<GameController*>(context->userData); if (!mCoreSaveState(context->core, 0, controller->m_saveStateFlags)) { - return false; + return; } QMetaObject::invokeMethod(controller, "closeGame"); - return true; - };*/ + }; m_threadContext.logger.d.log = [](mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) { mThreadLogger* logContext = reinterpret_cast<mThreadLogger*>(logger);

@@ -292,10 +245,10 @@ };

m_threadContext.userData = this; - connect(this, SIGNAL(gamePaused(mCoreThread*)), m_audioProcessor, SLOT(pause())); - connect(this, SIGNAL(gameStarted(mCoreThread*, const QString&)), m_audioProcessor, SLOT(setInput(mCoreThread*))); - connect(this, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(pollEvents())); - connect(this, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(updateAutofire())); + connect(this, &GameController::gamePaused, m_audioProcessor, &AudioProcessor::pause); + connect(this, &GameController::gameStarted, m_audioProcessor, &AudioProcessor::setInput); + connect(this, &GameController::frameAvailable, this, &GameController::pollEvents); + connect(this, &GameController::frameAvailable, this, &GameController::updateAutofire); } GameController::~GameController() {

@@ -345,11 +298,13 @@ m_config = config;

if (isLoaded()) { Interrupter interrupter(this); mCoreLoadForeignConfig(m_threadContext.core, config); + m_audioSync = m_threadContext.sync.audioWait; + m_videoSync = m_threadContext.sync.videoFrameWait; m_audioProcessor->setInput(&m_threadContext); } } -#ifdef USE_GDB_STUB +#ifdef USE_DEBUGGERS mDebugger* GameController::debugger() { if (!isLoaded()) { return nullptr;

@@ -407,10 +362,10 @@ void GameController::loadGame(VFile* vf, const QString& path, const QString& base) {

closeGame(); QFileInfo info(base); if (info.isDir()) { - m_fname = base + QDir::separator() + path; + m_fname = QFileInfo(base + '/' + path).canonicalFilePath(); m_fsub = QString(); } else { - m_fname = base; + m_fname = info.canonicalFilePath(); m_fsub = path; } m_vf = vf;

@@ -476,11 +431,20 @@

QByteArray bytes; if (!biosOnly) { bytes = m_fname.toUtf8(); - if (m_vf) { - m_threadContext.core->loadROM(m_threadContext.core, m_vf); + if (m_preload) { + if (m_vf) { + mCorePreloadVF(m_threadContext.core, m_vf); + } else { + mCorePreloadFile(m_threadContext.core, bytes.constData()); + mDirectorySetDetachBase(&m_threadContext.core->dirs); + } } else { - mCoreLoadFile(m_threadContext.core, bytes.constData()); - mDirectorySetDetachBase(&m_threadContext.core->dirs); + if (m_vf) { + m_threadContext.core->loadROM(m_threadContext.core, m_vf); + } else { + mCoreLoadFile(m_threadContext.core, bytes.constData()); + mDirectorySetDetachBase(&m_threadContext.core->dirs); + } } } else { bytes = m_bios.toUtf8();

@@ -630,7 +594,9 @@ void GameController::closeGame() {

if (!m_gameOpen) { return; } - +#ifdef USE_DEBUGGERS + setDebugger(nullptr); +#endif if (mCoreThreadIsPaused(&m_threadContext)) { mCoreThreadUnpause(&m_threadContext); }

@@ -641,6 +607,8 @@ void GameController::cleanGame() {

if (!m_gameOpen || mCoreThreadIsActive(&m_threadContext)) { return; } + + m_audioProcessor->pause(); mCoreThreadJoin(&m_threadContext); if (m_tileCache) {

@@ -651,7 +619,9 @@

delete[] m_drawContext; delete[] m_frontBuffer; + mCoreConfigDeinit(&m_threadContext.core->config); m_threadContext.core->deinit(m_threadContext.core); + m_threadContext.core = nullptr; m_gameOpen = false; }

@@ -739,8 +709,8 @@ m_threadContext.core->opts.rewindEnable = enable;

m_threadContext.core->opts.rewindBufferCapacity = capacity; m_threadContext.core->opts.rewindSave = rewindSave; if (enable && capacity > 0) { - mCoreRewindContextInit(&m_threadContext.rewind, capacity); - m_threadContext.rewind.stateFlags = rewindSave ? SAVESTATE_SAVEDATA : 0; + mCoreRewindContextInit(&m_threadContext.rewind, capacity, true); + m_threadContext.rewind.stateFlags = rewindSave ? SAVESTATE_SAVEDATA : 0; } } }

@@ -863,47 +833,13 @@ void GameController::setAudioChannelEnabled(int channel, bool enable) {

if (channel > 5 || channel < 0) { return; } -#ifdef M_CORE_GBA - GBA* gba = static_cast<GBA*>(m_threadContext.core->board); -#endif -#ifdef M_CORE_GB - GB* gb = static_cast<GB*>(m_threadContext.core->board); -#endif + m_audioChannels.reserve(channel + 1); + while (m_audioChannels.size() <= channel) { + m_audioChannels.append(true); + } m_audioChannels[channel] = enable; if (isLoaded()) { - switch (channel) { - case 0: - case 1: - case 2: - case 3: - switch (m_threadContext.core->platform(m_threadContext.core)) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: - gba->audio.psg.forceDisableCh[channel] = !enable; - break; -#endif -#ifdef M_CORE_GB - case PLATFORM_GB: - gb->audio.forceDisableCh[channel] = !enable; - break; -#endif - default: - break; - } - break; -#ifdef M_CORE_GBA - case 4: - if (m_threadContext.core->platform(m_threadContext.core) == PLATFORM_GBA) { - gba->audio.forceDisableChA = !enable; - } - break; - case 5: - if (m_threadContext.core->platform(m_threadContext.core) == PLATFORM_GBA) { - gba->audio.forceDisableChB = !enable; - } - break; -#endif - } + m_threadContext.core->enableAudioChannel(m_threadContext.core, channel, enable); } }

@@ -922,23 +858,14 @@ void GameController::setVideoLayerEnabled(int layer, bool enable) {

if (layer > 4 || layer < 0) { return; } + m_videoLayers.reserve(layer + 1); + while (m_videoLayers.size() <= layer) { + m_videoLayers.append(true); + } m_videoLayers[layer] = enable; -#ifdef M_CORE_GBA - if (isLoaded() && m_threadContext.core->platform(m_threadContext.core) == PLATFORM_GBA) { - GBA* gba = static_cast<GBA*>(m_threadContext.core->board); - switch (layer) { - case 0: - case 1: - case 2: - case 3: - gba->video.renderer->disableBG[layer] = !enable; - break; - case 4: - gba->video.renderer->disableOBJ = !enable; - break; - } + if (isLoaded()) { + m_threadContext.core->enableVideoLayer(m_threadContext.core, layer, enable); } -#endif } void GameController::setFPSTarget(float fps) {

@@ -980,8 +907,8 @@ controller->m_backupLoadState = VFileMemChunk(nullptr, 0);

} mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags); if (mCoreLoadState(context->core, controller->m_stateSlot, controller->m_loadStateFlags)) { - controller->frameAvailable(controller->m_drawContext); - controller->stateLoaded(context); + emit controller->frameAvailable(controller->m_drawContext); + emit controller->stateLoaded(context); } }); }

@@ -1098,6 +1025,17 @@ m_threadContext.sync.videoFrameWait = m_videoSync;

} m_sync = enable; } + +void GameController::setAudioSync(bool enable) { + m_audioSync = enable; + m_threadContext.sync.audioWait = enable; +} + +void GameController::setVideoSync(bool enable) { + m_videoSync = enable; + m_threadContext.sync.videoFrameWait = enable; +} + void GameController::setAVStream(mAVStream* stream) { Interrupter interrupter(this); m_stream = stream;

@@ -1138,8 +1076,8 @@ }

if (sampleRate) { m_audioProcessor->requestSampleRate(sampleRate); } - connect(this, SIGNAL(gamePaused(mCoreThread*)), m_audioProcessor, SLOT(pause())); - connect(this, SIGNAL(gameStarted(mCoreThread*, const QString&)), m_audioProcessor, SLOT(setInput(mCoreThread*))); + connect(this, &GameController::gamePaused, m_audioProcessor, &AudioProcessor::pause); + connect(this, &GameController::gameStarted, m_audioProcessor, &AudioProcessor::setInput); if (isLoaded()) { m_audioProcessor->setInput(&m_threadContext); startAudio();

@@ -1152,6 +1090,10 @@ }

void GameController::setLoadStateExtdata(int flags) { m_loadStateFlags = flags; +} + +void GameController::setPreload(bool preload) { + m_preload = preload; } void GameController::setLuminanceValue(uint8_t value) {

@@ -1209,7 +1151,7 @@ }

} void GameController::redoSamples(int samples) { - if (m_threadContext.core) { + if (m_gameOpen && m_threadContext.core) { m_threadContext.core->setAudioBufferSize(m_threadContext.core, samples); } m_audioProcessor->inputParametersChanged();

@@ -1230,6 +1172,32 @@ Interrupter interrupter(this);

m_logLevels &= ~levels; } +void GameController::startVideoLog(const QString& path) { + if (!isLoaded() || m_vl) { + return; + } + + Interrupter interrupter(this); + m_vl = mVideoLogContextCreate(m_threadContext.core); + m_vlVf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC); + mVideoLogContextSetOutput(m_vl, m_vlVf); + mVideoLogContextWriteHeader(m_vl, m_threadContext.core); +} + +void GameController::endVideoLog() { + if (!m_vl) { + return; + } + + Interrupter interrupter(this); + mVideoLogContextDestroy(m_threadContext.core, m_vl); + if (m_vlVf) { + m_vlVf->close(m_vlVf); + m_vlVf = nullptr; + } + m_vl = nullptr; +} + void GameController::pollEvents() { if (!m_inputController) { return;

@@ -1258,10 +1226,10 @@ std::shared_ptr<mTileCache> GameController::tileCache() {

if (m_tileCache) { return m_tileCache; } + Interrupter interrupter(this); switch (platform()) { #ifdef M_CORE_GBA case PLATFORM_GBA: { - Interrupter interrupter(this); GBA* gba = static_cast<GBA*>(m_threadContext.core->board); m_tileCache = std::make_shared<mTileCache>(); GBAVideoTileCacheInit(m_tileCache.get());

@@ -1272,7 +1240,6 @@ }

#endif #ifdef M_CORE_GB case PLATFORM_GB: { - Interrupter interrupter(this); GB* gb = static_cast<GB*>(m_threadContext.core->board); m_tileCache = std::make_shared<mTileCache>(); GBVideoTileCacheInit(m_tileCache.get());
M src/platform/qt/GameController.hsrc/platform/qt/GameController.h

@@ -29,6 +29,7 @@ struct GBAAudio;

struct mCoreConfig; struct mDebugger; struct mTileCache; +struct mVideoLogContext; namespace QGBA {

@@ -86,7 +87,7 @@ void setConfig(const mCoreConfig*);

int stateSlot() const { return m_stateSlot; } -#ifdef USE_GDB_STUB +#ifdef USE_DEBUGGERS mDebugger* debugger(); void setDebugger(mDebugger*); #endif

@@ -147,11 +148,14 @@ void saveBackupState();

void setTurbo(bool, bool forced = true); void setTurboSpeed(float ratio); void setSync(bool); + void setAudioSync(bool); + void setVideoSync(bool); void setAVStream(mAVStream*); void clearAVStream(); void reloadAudioDriver(); void setSaveStateExtdata(int flags); void setLoadStateExtdata(int flags); + void setPreload(bool); #ifdef USE_PNG void screenshot();

@@ -171,6 +175,9 @@ void setLogLevel(int);

void enableLogLevel(int); void disableLogLevel(int); + void startVideoLog(const QString& path); + void endVideoLog(); + private slots: void openGame(bool bios = false); void crashGame(const QString& crashMessage);

@@ -184,58 +191,63 @@ void updateKeys();

void redoSamples(int samples); void enableTurbo(); - uint32_t* m_drawContext; - uint32_t* m_frontBuffer; - mCoreThread m_threadContext; + uint32_t* m_drawContext = nullptr; + uint32_t* m_frontBuffer = nullptr; + mCoreThread m_threadContext{}; const mCoreConfig* m_config; mCheatDevice* m_cheatDevice; - int m_activeKeys; - int m_activeButtons; - int m_inactiveKeys; - int m_logLevels; + int m_activeKeys = 0; + int m_activeButtons = 0; + int m_inactiveKeys = 0; + int m_logLevels = 0; - bool m_gameOpen; + bool m_gameOpen = false; QString m_fname; QString m_fsub; - VFile* m_vf; + VFile* m_vf = nullptr; QString m_bios; - bool m_useBios; + bool m_useBios = false; QString m_patch; - Override* m_override; + Override* m_override = nullptr; AudioProcessor* m_audioProcessor; - QAtomicInt m_pauseAfterFrame; + QAtomicInt m_pauseAfterFrame{false}; QList<std::function<void ()>> m_resetActions; - bool m_sync; - bool m_videoSync; - bool m_audioSync; - float m_fpsTarget; - bool m_turbo; - bool m_turboForced; - float m_turboSpeed; - bool m_wasPaused; + bool m_sync = true; + bool m_videoSync = VIDEO_SYNC; + bool m_audioSync = AUDIO_SYNC; + float m_fpsTarget = -1; + bool m_turbo = false; + bool m_turboForced = false; + float m_turboSpeed = -1; + bool m_wasPaused = false; std::shared_ptr<mTileCache> m_tileCache; - bool m_audioChannels[6]; - bool m_videoLayers[5]; + QList<bool> m_audioChannels; + QList<bool> m_videoLayers; - bool m_autofire[GBA_KEY_MAX]; - int m_autofireStatus[GBA_KEY_MAX]; + bool m_autofire[GBA_KEY_MAX] = {}; + int m_autofireStatus[GBA_KEY_MAX] = {}; - int m_stateSlot; - struct VFile* m_backupLoadState; - QByteArray m_backupSaveState; + int m_stateSlot = 1; + struct VFile* m_backupLoadState = nullptr; + QByteArray m_backupSaveState{nullptr}; int m_saveStateFlags; int m_loadStateFlags; - InputController* m_inputController; - MultiplayerController* m_multiplayer; + bool m_preload = false; - mAVStream* m_stream; + InputController* m_inputController = nullptr; + MultiplayerController* m_multiplayer = nullptr; + + mAVStream* m_stream = nullptr; + + mVideoLogContext* m_vl = nullptr; + VFile* m_vlVf = nullptr; struct GameControllerLux : GBALuminanceSource { GameController* p;
M src/platform/qt/IOViewer.cppsrc/platform/qt/IOViewer.cpp

@@ -1040,9 +1040,10 @@

const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); m_ui.regValue->setFont(font); - connect(m_ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonPressed(QAbstractButton*))); - connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close())); - connect(m_ui.regSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRegister())); + connect(m_ui.buttonBox, &QDialogButtonBox::clicked, this, &IOViewer::buttonPressed); + connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); + connect(m_ui.regSelect, &QComboBox::currentTextChanged, + this, static_cast<void (IOViewer::*)()>(&IOViewer::selectRegister)); m_b[0] = m_ui.b0; m_b[1] = m_ui.b1;

@@ -1062,7 +1063,7 @@ m_b[14] = m_ui.bE;

m_b[15] = m_ui.bF; for (int i = 0; i < 16; ++i) { - connect(m_b[i], SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + connect(m_b[i], &QAbstractButton::toggled, this, &IOViewer::bitFlipped); } selectRegister(0);

@@ -1128,8 +1129,8 @@ if (ri.size == 1) {

QCheckBox* check = new QCheckBox; check->setEnabled(!ri.readonly); box->addWidget(check, i, 1, Qt::AlignRight); - connect(check, SIGNAL(toggled(bool)), m_b[ri.start], SLOT(setChecked(bool))); - connect(m_b[ri.start], SIGNAL(toggled(bool)), check, SLOT(setChecked(bool))); + connect(check, &QAbstractButton::toggled, m_b[ri.start], &QAbstractButton::setChecked); + connect(m_b[ri.start], &QAbstractButton::toggled, check, &QAbstractButton::setChecked); } else if (ri.items.empty()) { QSpinBox* sbox = new QSpinBox; sbox->setEnabled(!ri.readonly);
M src/platform/qt/IOViewer.hsrc/platform/qt/IOViewer.h

@@ -21,16 +21,16 @@

public: struct RegisterItem { RegisterItem(const QString& description, uint start, uint size = 1, bool readonly = false) - : description(description) - , start(start) + : start(start) , size(size) - , readonly(readonly) {} + , readonly(readonly) + , description(description) {} RegisterItem(const QString& description, uint start, uint size, QStringList items, bool readonly = false) - : description(description) - , start(start) + : start(start) , size(size) - , items(items) - , readonly(readonly) {} + , readonly(readonly) + , description(description) + , items(items) {} uint start; uint size; bool readonly;
M src/platform/qt/InputController.cppsrc/platform/qt/InputController.cpp

@@ -33,13 +33,6 @@ : QObject(parent)

, m_inputModel(model) , m_platform(PLATFORM_NONE) , m_playerId(playerId) - , m_config(nullptr) - , m_gamepadTimer(nullptr) -#ifdef BUILD_SDL - , m_sdlPlayer{} - , m_playerAttached(false) -#endif - , m_allowOpposing(false) , m_topLevel(topLevel) , m_focusParent(topLevel) {
M src/platform/qt/InputController.hsrc/platform/qt/InputController.h

@@ -124,17 +124,17 @@

InputModel* m_inputModel; mPlatform m_platform; QMap<mPlatform, mInputMap> m_inputMaps; - ConfigController* m_config; + ConfigController* m_config = nullptr; int m_playerId; - bool m_allowOpposing; + bool m_allowOpposing = false; QWidget* m_topLevel; QWidget* m_focusParent; #ifdef BUILD_SDL static int s_sdlInited; static mSDLEvents s_sdlEvents; - mSDLPlayer m_sdlPlayer; - bool m_playerAttached; + mSDLPlayer m_sdlPlayer{}; + bool m_playerAttached = false; #endif QVector<int> m_deadzones;

@@ -146,7 +146,7 @@

QSet<int> m_activeButtons; QSet<QPair<int, GamepadAxisEvent::Direction>> m_activeAxes; QSet<QPair<int, GamepadHatEvent::Direction>> m_activeHats; - QTimer m_gamepadTimer; + QTimer m_gamepadTimer{nullptr}; QSet<int> m_pendingEvents; };
M src/platform/qt/InputModel.cppsrc/platform/qt/InputModel.cpp

@@ -18,8 +18,6 @@

InputModel::InputModel(QObject* parent) : QAbstractItemModel(parent) , m_rootMenu(nullptr) - , m_config(nullptr) - , m_profile(nullptr) { }
M src/platform/qt/InputProfile.cppsrc/platform/qt/InputProfile.cpp

@@ -179,8 +179,6 @@ keys.keyDown,

keys.keyR, keys.keyL, } - , m_shortcutButtons(shortcutButtons) - , m_shortcutAxes(shortcutAxes) , m_axes { axes.keyA, axes.keyB,

@@ -193,6 +191,8 @@ axes.keyDown,

axes.keyR, axes.keyL, } + , m_shortcutButtons(shortcutButtons) + , m_shortcutAxes(shortcutAxes) , m_tiltAxis(tiltAxis) , m_gyroAxis(gyroAxis) , m_gyroSensitivity(gyroSensitivity)
M src/platform/qt/KeyEditor.cppsrc/platform/qt/KeyEditor.cpp

@@ -18,10 +18,6 @@ KeyEditor::KeyEditor(QWidget* parent)

: QLineEdit(parent) , m_direction(GamepadAxisEvent::NEUTRAL) , m_hatDirection(GamepadHatEvent::CENTER) - , m_key(-1) - , m_axis(-1) - , m_hat(-1) - , m_button(false) { setAlignment(Qt::AlignCenter); setFocusPolicy(Qt::ClickFocus);
M src/platform/qt/KeyEditor.hsrc/platform/qt/KeyEditor.h

@@ -54,10 +54,10 @@ static const int KEY_TIME = 2000;

void updateButtonText(); - int m_key; - int m_axis; - int m_hat; - bool m_button; + int m_key = -1; + int m_axis = -1; + int m_hat = -1; + bool m_button = false; GamepadAxisEvent::Direction m_direction; GamepadHatEvent::Direction m_hatDirection; QTimer m_lastKey;
D src/platform/qt/LibraryModel.cpp

@@ -1,320 +0,0 @@

-/* Copyright (c) 2013-2016 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "LibraryModel.h" - -#include <QFontMetrics> - -#include <mgba-util/vfs.h> - -using namespace QGBA; - -Q_DECLARE_METATYPE(mLibraryEntry); - -QMap<QString, LibraryModel::LibraryHandle*> LibraryModel::s_handles; -QMap<QString, LibraryModel::LibraryColumn> LibraryModel::s_columns; - -LibraryModel::LibraryModel(const QString& path, QObject* parent) - : QAbstractItemModel(parent) -{ - if (s_columns.empty()) { - s_columns["name"] = { - tr("Name"), - [](const mLibraryEntry& e) -> QString { - if (e.title) { - return QString::fromUtf8(e.title); - } - return QString::fromUtf8(e.filename); - } - }; - s_columns["filename"] = { - tr("Filename"), - [](const mLibraryEntry& e) -> QString { - return QString::fromUtf8(e.filename); - } - }; - s_columns["size"] = { - tr("Size"), - [](const mLibraryEntry& e) -> QString { - double size = e.filesize; - QString unit = "B"; - if (size >= 1024.0) { - size /= 1024.0; - unit = "kiB"; - } - if (size >= 1024.0) { - size /= 1024.0; - unit = "MiB"; - } - return QString("%0 %1").arg(size, 0, 'f', 1).arg(unit); - }, - Qt::AlignRight - }; - s_columns["platform"] = { - tr("Platform"), - [](const mLibraryEntry& e) -> QString { - int platform = e.platform; - switch (platform) { -#ifdef M_CORE_GBA - case PLATFORM_GBA: - return tr("GBA"); -#endif -#ifdef M_CORE_GB - case PLATFORM_GB: - return tr("GB"); -#endif - default: - return tr("?"); - } - } - }; - s_columns["location"] = { - tr("Location"), - [](const mLibraryEntry& e) -> QString { - return QString::fromUtf8(e.base); - } - }; - s_columns["crc32"] = { - tr("CRC32"), - [](const mLibraryEntry& e) -> QString { - return QString("%0").arg(e.crc32, 8, 16, QChar('0')); - } - }; - } - if (!path.isNull()) { - if (s_handles.contains(path)) { - m_library = s_handles[path]; - m_library->ref(); - } else { - m_library = new LibraryHandle(mLibraryLoad(path.toUtf8().constData()), path); - if (m_library->library) { - s_handles[path] = m_library; - } else { - delete m_library; - m_library = new LibraryHandle(mLibraryCreateEmpty()); - } - } - } else { - m_library = new LibraryHandle(mLibraryCreateEmpty()); - } - mLibraryListingInit(&m_listings, 0); - memset(&m_constraints, 0, sizeof(m_constraints)); - m_constraints.platform = PLATFORM_NONE; - m_columns.append(s_columns["name"]); - m_columns.append(s_columns["location"]); - m_columns.append(s_columns["platform"]); - m_columns.append(s_columns["size"]); - m_columns.append(s_columns["crc32"]); - - connect(m_library->loader, SIGNAL(directoryLoaded(const QString&)), this, SLOT(directoryLoaded(const QString&))); -} - -LibraryModel::~LibraryModel() { - clearConstraints(); - mLibraryListingDeinit(&m_listings); - if (!m_library->deref()) { - s_handles.remove(m_library->path); - delete m_library; - } -} - -void LibraryModel::loadDirectory(const QString& path) { - m_queue.append(path); - QMetaObject::invokeMethod(m_library->loader, "loadDirectory", Q_ARG(const QString&, path)); -} - -bool LibraryModel::entryAt(int row, mLibraryEntry* out) const { - if (mLibraryListingSize(&m_listings) <= row) { - return false; - } - *out = *mLibraryListingGetConstPointer(&m_listings, row); - return true; -} - -VFile* LibraryModel::openVFile(const QModelIndex& index) const { - mLibraryEntry entry; - if (!entryAt(index.row(), &entry)) { - return nullptr; - } - return mLibraryOpenVFile(m_library->library, &entry); -} - -QString LibraryModel::filename(const QModelIndex& index) const { - mLibraryEntry entry; - if (!entryAt(index.row(), &entry)) { - return QString(); - } - return QString::fromUtf8(entry.filename); -} - -QString LibraryModel::location(const QModelIndex& index) const { - mLibraryEntry entry; - if (!entryAt(index.row(), &entry)) { - return QString(); - } - return QString::fromUtf8(entry.base); -} - -QVariant LibraryModel::data(const QModelIndex& index, int role) const { - if (!index.isValid()) { - return QVariant(); - } - mLibraryEntry entry; - if (!entryAt(index.row(), &entry)) { - return QVariant(); - } - if (role == Qt::UserRole) { - return QVariant::fromValue(entry); - } - if (index.column() >= m_columns.count()) { - return QVariant(); - } - switch (role) { - case Qt::DisplayRole: - return m_columns[index.column()].value(entry); - case Qt::SizeHintRole: { - QFontMetrics fm((QFont())); - return fm.size(Qt::TextSingleLine, m_columns[index.column()].value(entry)); - } - case Qt::TextAlignmentRole: - return m_columns[index.column()].alignment; - default: - return QVariant(); - } -} - -QVariant LibraryModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role != Qt::DisplayRole) { - return QAbstractItemModel::headerData(section, orientation, role); - } - if (orientation == Qt::Horizontal) { - if (section >= m_columns.count()) { - return QVariant(); - } - return m_columns[section].name; - } - return section; -} - -QModelIndex LibraryModel::index(int row, int column, const QModelIndex& parent) const { - if (parent.isValid()) { - return QModelIndex(); - } - return createIndex(row, column, nullptr); -} - -QModelIndex LibraryModel::parent(const QModelIndex&) const { - return QModelIndex(); -} - -int LibraryModel::columnCount(const QModelIndex& parent) const { - if (parent.isValid()) { - return 0; - } - return m_columns.count(); -} - -int LibraryModel::rowCount(const QModelIndex& parent) const { - if (parent.isValid()) { - return 0; - } - return mLibraryCount(m_library->library, &m_constraints); -} - -void LibraryModel::attachGameDB(const NoIntroDB* gameDB) { - mLibraryAttachGameDB(m_library->library, gameDB); -} - -void LibraryModel::constrainBase(const QString& path) { - clearConstraints(); - if (m_constraints.base) { - free(const_cast<char*>(m_constraints.base)); - } - m_constraints.base = strdup(path.toUtf8().constData()); - reload(); -} - -void LibraryModel::clearConstraints() { - if (m_constraints.base) { - free(const_cast<char*>(m_constraints.base)); - } - if (m_constraints.filename) { - free(const_cast<char*>(m_constraints.filename)); - } - if (m_constraints.title) { - free(const_cast<char*>(m_constraints.title)); - } - memset(&m_constraints, 0, sizeof(m_constraints)); - size_t i; - for (i = 0; i < mLibraryListingSize(&m_listings); ++i) { - mLibraryEntryFree(mLibraryListingGetPointer(&m_listings, i)); - } - mLibraryListingClear(&m_listings); -} - -void LibraryModel::reload() { - mLibraryGetEntries(m_library->library, &m_listings, 0, 0, m_constraints.base ? &m_constraints : nullptr); -} - -void LibraryModel::directoryLoaded(const QString& path) { - m_queue.removeOne(path); - beginResetModel(); - endResetModel(); - if (m_queue.empty()) { - emit doneLoading(); - } -} - -LibraryModel::LibraryColumn::LibraryColumn() { -} - -LibraryModel::LibraryColumn::LibraryColumn(const QString& name, std::function<QString(const mLibraryEntry&)> value, int alignment) - : name(name) - , value(value) - , alignment(alignment) -{ -} - -LibraryModel::LibraryHandle::LibraryHandle(mLibrary* lib, const QString& p) - : library(lib) - , loader(new LibraryLoader(library)) - , path(p) - , m_ref(1) -{ - if (!library) { - return; - } - loader->moveToThread(&m_loaderThread); - m_loaderThread.setObjectName("Library Loader Thread"); - m_loaderThread.start(); -} - -LibraryModel::LibraryHandle::~LibraryHandle() { - m_loaderThread.quit(); - m_loaderThread.wait(); - if (library) { - mLibraryDestroy(library); - } -} - -void LibraryModel::LibraryHandle::ref() { - ++m_ref; -} - -bool LibraryModel::LibraryHandle::deref() { - --m_ref; - return m_ref > 0; -} - -LibraryLoader::LibraryLoader(mLibrary* library, QObject* parent) - : QObject(parent) - , m_library(library) -{ -} - -void LibraryLoader::loadDirectory(const QString& path) { - mLibraryLoadDirectory(m_library, path.toUtf8().constData()); - emit directoryLoaded(path); -}
D src/platform/qt/LibraryModel.h

@@ -1,115 +0,0 @@

-/* Copyright (c) 2013-2016 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef QGBA_LIBRARY_MODEL -#define QGBA_LIBRARY_MODEL - -#include <QAbstractItemModel> -#include <QStringList> -#include <QThread> - -#include <mgba/core/library.h> - -#include <functional> - -struct VDir; -struct VFile; -struct NoIntroDB; - -namespace QGBA { - -class LibraryLoader; -class LibraryModel : public QAbstractItemModel { -Q_OBJECT - -public: - LibraryModel(const QString& path, QObject* parent = nullptr); - virtual ~LibraryModel(); - - bool entryAt(int row, mLibraryEntry* out) const; - VFile* openVFile(const QModelIndex& index) const; - QString filename(const QModelIndex& index) const; - QString location(const QModelIndex& index) const; - - virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - - virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override; - virtual QModelIndex parent(const QModelIndex& index) const override; - - virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; - virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; - - void attachGameDB(const NoIntroDB* gameDB); - -signals: - void doneLoading(); - -public slots: - void loadDirectory(const QString& path); - - void constrainBase(const QString& path); - void clearConstraints(); - void reload(); - -private slots: - void directoryLoaded(const QString& path); - -private: - struct LibraryColumn { - LibraryColumn(); - LibraryColumn(const QString&, std::function<QString(const mLibraryEntry&)>, int = Qt::AlignLeft); - QString name; - std::function<QString(const mLibraryEntry&)> value; - int alignment; - }; - - class LibraryHandle { - public: - LibraryHandle(mLibrary*, const QString& path = QString()); - ~LibraryHandle(); - - mLibrary* const library; - LibraryLoader* const loader; - const QString path; - - void ref(); - bool deref(); - - private: - QThread m_loaderThread; - size_t m_ref; - }; - - LibraryHandle* m_library; - static QMap<QString, LibraryHandle*> s_handles; - - mLibraryEntry m_constraints; - mLibraryListing m_listings; - QStringList m_queue; - - QList<LibraryColumn> m_columns; - static QMap<QString, LibraryColumn> s_columns; -}; - -class LibraryLoader : public QObject { -Q_OBJECT - -public: - LibraryLoader(mLibrary* library, QObject* parent = nullptr); - -public slots: - void loadDirectory(const QString& path); - -signals: - void directoryLoaded(const QString& path); - -private: - mLibrary* m_library; -}; - -} - -#endif
D src/platform/qt/LibraryView.cpp

@@ -1,58 +0,0 @@

-/* Copyright (c) 2013-2017 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "LibraryView.h" - -#include <mgba-util/vfs.h> - -#include "ConfigController.h" -#include "GBAApp.h" - -using namespace QGBA; - -LibraryView::LibraryView(QWidget* parent) - : QWidget(parent) - , m_model(ConfigController::configDir() + "/library.sqlite3") -{ - m_ui.setupUi(this); - m_model.attachGameDB(GBAApp::app()->gameDB()); - connect(&m_model, SIGNAL(doneLoading()), this, SIGNAL(doneLoading())); - connect(&m_model, SIGNAL(doneLoading()), this, SLOT(resizeColumns())); - connect(m_ui.listing, SIGNAL(activated(const QModelIndex&)), this, SIGNAL(accepted())); - m_ui.listing->horizontalHeader()->setSectionsMovable(true); - m_ui.listing->setModel(&m_model); - m_ui.listing->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); - m_model.reload(); - resizeColumns(); -} - -void LibraryView::setDirectory(const QString& filename) { - m_model.loadDirectory(filename); - m_model.constrainBase(filename); -} - -void LibraryView::addDirectory(const QString& filename) { - m_model.loadDirectory(filename); -} - -VFile* LibraryView::selectedVFile() const { - QModelIndex index = m_ui.listing->selectionModel()->currentIndex(); - if (!index.isValid()) { - return nullptr; - } - return m_model.openVFile(index); -} - -QPair<QString, QString> LibraryView::selectedPath() const { - QModelIndex index = m_ui.listing->selectionModel()->currentIndex(); - if (!index.isValid()) { - return qMakePair(QString(), QString()); - } - return qMakePair(m_model.filename(index), m_model.location(index)); -} - -void LibraryView::resizeColumns() { - m_ui.listing->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); -}
D src/platform/qt/LibraryView.h

@@ -1,45 +0,0 @@

-/* Copyright (c) 2013-2017 Jeffrey Pfau - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef QGBA_LIBRARY_VIEW -#define QGBA_LIBRARY_VIEW - -#include "LibraryModel.h" - -#include "ui_LibraryView.h" - -struct VFile; - -namespace QGBA { - -class LibraryView : public QWidget { -Q_OBJECT - -public: - LibraryView(QWidget* parent = nullptr); - - VFile* selectedVFile() const; - QPair<QString, QString> selectedPath() const; - -signals: - void doneLoading(); - void accepted(); - -public slots: - void setDirectory(const QString&); - void addDirectory(const QString&); - -private slots: - void resizeColumns(); - -private: - Ui::LibraryView m_ui; - - LibraryModel m_model; -}; - -} - -#endif
D src/platform/qt/LibraryView.ui

@@ -1,49 +0,0 @@

-<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>LibraryView</class> - <widget class="QWidget" name="LibraryView"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>400</width> - <height>300</height> - </rect> - </property> - <property name="windowTitle"> - <string>Library</string> - </property> - <layout class="QGridLayout"> - <property name="margin"> - <number>0</number> - </property> - <item row="0" column="0"> - <widget class="QTableView" name="listing"> - <property name="editTriggers"> - <set>QAbstractItemView::NoEditTriggers</set> - </property> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <property name="selectionBehavior"> - <enum>QAbstractItemView::SelectRows</enum> - </property> - <property name="showGrid"> - <bool>false</bool> - </property> - <attribute name="horizontalHeaderStretchLastSection"> - <bool>true</bool> - </attribute> - <attribute name="verticalHeaderVisible"> - <bool>false</bool> - </attribute> - <attribute name="verticalHeaderMinimumSectionSize"> - <number>0</number> - </attribute> - </widget> - </item> - </layout> - </widget> - <resources/> - <connections/> -</ui>
M src/platform/qt/LoadSaveState.cppsrc/platform/qt/LoadSaveState.cpp

@@ -23,8 +23,8 @@

LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent) : QWidget(parent) , m_controller(controller) - , m_currentFocus(controller->stateSlot() - 1) , m_mode(LoadSave::LOAD) + , m_currentFocus(controller->stateSlot() - 1) { setAttribute(Qt::WA_TranslucentBackground); m_ui.setupUi(this);

@@ -57,7 +57,7 @@ m_currentFocus = 0;

} QAction* escape = new QAction(this); - escape->connect(escape, SIGNAL(triggered()), this, SLOT(close())); + connect(escape, &QAction::triggered, this, &QWidget::close); escape->setShortcut(QKeySequence("Esc")); escape->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(escape);
M src/platform/qt/LogController.cppsrc/platform/qt/LogController.cpp

@@ -14,10 +14,10 @@ : QObject(parent)

, m_logLevel(levels) { if (this != &s_global) { - connect(&s_global, SIGNAL(logPosted(int, int, const QString&)), this, SLOT(postLog(int, int, const QString&))); - connect(this, SIGNAL(levelsSet(int)), &s_global, SLOT(setLevels(int))); - connect(this, SIGNAL(levelsEnabled(int)), &s_global, SLOT(enableLevels(int))); - connect(this, SIGNAL(levelsDisabled(int)), &s_global, SLOT(disableLevels(int))); + connect(&s_global, &LogController::logPosted, this, &LogController::postLog); + connect(this, &LogController::levelsSet, &s_global, &LogController::setLevels); + connect(this, &LogController::levelsEnabled, &s_global, &LogController::enableLevels); + connect(this, &LogController::levelsDisabled, &s_global, &LogController::disableLevels); } }

@@ -72,9 +72,9 @@ return QString();

} LogController::Stream::Stream(LogController* controller, int level, int category) - : m_log(controller) - , m_level(level) + : m_level(level) , m_category(category) + , m_log(controller) { }
M src/platform/qt/LogView.cppsrc/platform/qt/LogView.cpp

@@ -14,8 +14,6 @@ using namespace QGBA;

LogView::LogView(LogController* log, QWidget* parent) : QWidget(parent) - , m_lines(0) - , m_lineLimit(DEFAULT_LINE_LIMIT) { m_ui.setupUi(this); connect(m_ui.levelDebug, &QAbstractButton::toggled, [this](bool set) {

@@ -39,12 +37,13 @@ });

connect(m_ui.levelGameError, &QAbstractButton::toggled, [this](bool set) { setLevel(mLOG_GAME_ERROR, set); }); - connect(m_ui.clear, SIGNAL(clicked()), this, SLOT(clear())); - connect(m_ui.maxLines, SIGNAL(valueChanged(int)), this, SLOT(setMaxLines(int))); + connect(m_ui.clear, &QAbstractButton::clicked, this, &LogView::clear); + connect(m_ui.maxLines, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), + this, &LogView::setMaxLines); m_ui.maxLines->setValue(DEFAULT_LINE_LIMIT); - connect(log, SIGNAL(logPosted(int, int, const QString&)), this, SLOT(postLog(int, int, const QString&))); - connect(log, SIGNAL(levelsSet(int)), this, SLOT(setLevels(int))); + connect(log, &LogController::logPosted, this, &LogView::postLog); + connect(log, &LogController::levelsSet, this, &LogView::setLevels); connect(log, &LogController::levelsEnabled, [this](int level) { bool s = blockSignals(true); setLevel(level, true);

@@ -55,8 +54,8 @@ bool s = blockSignals(true);

setLevel(level, false); blockSignals(s); }); - connect(this, SIGNAL(levelsEnabled(int)), log, SLOT(enableLevels(int))); - connect(this, SIGNAL(levelsDisabled(int)), log, SLOT(disableLevels(int))); + connect(this, &LogView::levelsEnabled, log, &LogController::enableLevels); + connect(this, &LogView::levelsDisabled, log, &LogController::disableLevels); } void LogView::postLog(int level, int category, const QString& log) {
M src/platform/qt/LogView.hsrc/platform/qt/LogView.h

@@ -40,8 +40,8 @@ private:

static const int DEFAULT_LINE_LIMIT = 1000; Ui::LogView m_ui; - int m_lines; - int m_lineLimit; + int m_lines = 0; + int m_lineLimit = DEFAULT_LINE_LIMIT; QQueue<QString> m_pendingLines; void setLevel(int level, bool);
M src/platform/qt/MemoryModel.cppsrc/platform/qt/MemoryModel.cpp

@@ -26,12 +26,6 @@ using namespace QGBA;

MemoryModel::MemoryModel(QWidget* parent) : QAbstractScrollArea(parent) - , m_core(nullptr) - , m_top(0) - , m_align(1) - , m_selection(0, 0) - , m_selectionAnchor(0) - , m_codec(nullptr) { m_font.setFamily("Source Code Pro"); m_font.setStyleHint(QFont::Monospace);

@@ -49,22 +43,22 @@ setContextMenuPolicy(Qt::ActionsContextMenu);

QAction* copy = new QAction(tr("Copy selection"), this); copy->setShortcut(QKeySequence::Copy); - connect(copy, SIGNAL(triggered()), this, SLOT(copy())); + connect(copy, &QAction::triggered, this, &MemoryModel::copy); addAction(copy); QAction* save = new QAction(tr("Save selection"), this); save->setShortcut(QKeySequence::Save); - connect(save, SIGNAL(triggered()), this, SLOT(save())); + connect(save, &QAction::triggered, this, &MemoryModel::save); addAction(save); QAction* paste = new QAction(tr("Paste"), this); paste->setShortcut(QKeySequence::Paste); - connect(paste, SIGNAL(triggered()), this, SLOT(paste())); + connect(paste, &QAction::triggered, this, &MemoryModel::paste); addAction(paste); QAction* load = new QAction(tr("Load"), this); load->setShortcut(QKeySequence::Open); - connect(load, SIGNAL(triggered()), this, SLOT(load())); + connect(load, &QAction::triggered, this, &MemoryModel::load); addAction(load); static QString arg("%0");

@@ -128,7 +122,7 @@ m_bufferedNybbles = 0;

viewport()->update(); } -void MemoryModel::loadTBL(const QString& path) { +void MemoryModel::loadTBLFromPath(const QString& path) { VFile* vf = VFileDevice::open(path, O_RDONLY); if (!vf) { return;

@@ -143,7 +137,7 @@ QString filename = GBAApp::app()->getOpenFileName(this, tr("Load TBL"));

if (filename.isNull()) { return; } - loadTBL(filename); + loadTBLFromPath(filename); } void MemoryModel::jumpToAddress(const QString& hex) {
M src/platform/qt/MemoryModel.hsrc/platform/qt/MemoryModel.h

@@ -43,7 +43,7 @@ public slots:

void jumpToAddress(const QString& hex); void jumpToAddress(uint32_t); - void loadTBL(const QString& path); + void loadTBLFromPath(const QString& path); void loadTBL(); void copy();

@@ -76,22 +76,22 @@ public:

void operator()(TextCodec*); }; - mCore* m_core; + mCore* m_core = nullptr; std::unique_ptr<TextCodec, TextCodecFree> m_codec; QFont m_font; int m_cellHeight; int m_letterWidth; uint32_t m_base; uint32_t m_size; - int m_top; - int m_align; + int m_top = 0; + int m_align = 1; QMargins m_margins; QVector<QStaticText> m_staticNumbers; QVector<QStaticText> m_staticLatin1; QStaticText m_regionName; QSizeF m_cellSize; - QPair<uint32_t, uint32_t> m_selection; - uint32_t m_selectionAnchor; + QPair<uint32_t, uint32_t> m_selection{0, 0}; + uint32_t m_selectionAnchor = 0; uint32_t m_buffer; int m_bufferedNybbles; int m_currentBank;
M src/platform/qt/MemoryView.cppsrc/platform/qt/MemoryView.cpp

@@ -81,8 +81,10 @@ default:

break; } - connect(m_ui.regions, SIGNAL(currentIndexChanged(int)), this, SLOT(setIndex(int))); - connect(m_ui.segments, SIGNAL(valueChanged(int)), this, SLOT(setSegment(int))); + connect(m_ui.regions, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), + this, &MemoryView::setIndex); + connect(m_ui.segments, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), + this, &MemoryView::setSegment); if (info) { for (size_t i = 0; info[i].name; ++i) {

@@ -93,22 +95,23 @@

connect(m_ui.width8, &QAbstractButton::clicked, [this]() { m_ui.hexfield->setAlignment(1); }); connect(m_ui.width16, &QAbstractButton::clicked, [this]() { m_ui.hexfield->setAlignment(2); }); connect(m_ui.width32, &QAbstractButton::clicked, [this]() { m_ui.hexfield->setAlignment(4); }); - connect(m_ui.setAddress, SIGNAL(valueChanged(const QString&)), m_ui.hexfield, SLOT(jumpToAddress(const QString&))); - connect(m_ui.hexfield, SIGNAL(selectionChanged(uint32_t, uint32_t)), this, SLOT(updateSelection(uint32_t, uint32_t))); + connect(m_ui.setAddress, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), + m_ui.hexfield, static_cast<void (MemoryModel::*)(uint32_t)>(&MemoryModel::jumpToAddress)); + connect(m_ui.hexfield, &MemoryModel::selectionChanged, this, &MemoryView::updateSelection); - connect(controller, SIGNAL(gameStopped(mCoreThread*)), this, SLOT(close())); + connect(controller, &GameController::gameStopped, this, &QWidget::close); - connect(controller, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(update())); - connect(controller, SIGNAL(gamePaused(mCoreThread*)), this, SLOT(update())); - connect(controller, SIGNAL(stateLoaded(mCoreThread*)), this, SLOT(update())); - connect(controller, SIGNAL(rewound(mCoreThread*)), this, SLOT(update())); + connect(controller, &GameController::frameAvailable, this, &MemoryView::update); + connect(controller, &GameController::gamePaused, this, &MemoryView::update); + connect(controller, &GameController::stateLoaded, this, &MemoryView::update); + connect(controller, &GameController::rewound, this, &MemoryView::update); - connect(m_ui.copy, SIGNAL(clicked()), m_ui.hexfield, SLOT(copy())); - connect(m_ui.save, SIGNAL(clicked()), m_ui.hexfield, SLOT(save())); - connect(m_ui.paste, SIGNAL(clicked()), m_ui.hexfield, SLOT(paste())); - connect(m_ui.load, SIGNAL(clicked()), m_ui.hexfield, SLOT(load())); + connect(m_ui.copy, &QAbstractButton::clicked, m_ui.hexfield, &MemoryModel::copy); + connect(m_ui.save, &QAbstractButton::clicked, m_ui.hexfield, &MemoryModel::save); + connect(m_ui.paste, &QAbstractButton::clicked, m_ui.hexfield, &MemoryModel::paste); + connect(m_ui.load, &QAbstractButton::clicked, m_ui.hexfield, &MemoryModel::load); - connect(m_ui.loadTBL, SIGNAL(clicked()), m_ui.hexfield, SLOT(loadTBL())); + connect(m_ui.loadTBL, &QAbstractButton::clicked, m_ui.hexfield, &MemoryModel::loadTBL); } void MemoryView::setIndex(int index) {
M src/platform/qt/MessagePainter.cppsrc/platform/qt/MessagePainter.cpp

@@ -15,13 +15,11 @@ using namespace QGBA;

MessagePainter::MessagePainter(QObject* parent) : QObject(parent) - , m_messageTimer(this) - , m_scaleFactor(1) { m_messageFont.setFamily("Source Code Pro"); m_messageFont.setStyleHint(QFont::Monospace); m_messageFont.setPixelSize(13); - connect(&m_messageTimer, SIGNAL(timeout()), this, SLOT(clearMessage())); + connect(&m_messageTimer, &QTimer::timeout, this, &MessagePainter::clearMessage); m_messageTimer.setSingleShot(true); m_messageTimer.setInterval(5000);
M src/platform/qt/MessagePainter.hsrc/platform/qt/MessagePainter.h

@@ -35,11 +35,11 @@ QMutex m_mutex;

QStaticText m_message; QPixmap m_pixmap; QPixmap m_pixmapBuffer; - QTimer m_messageTimer; + QTimer m_messageTimer{this}; QPoint m_local; QTransform m_world; QFont m_messageFont; - qreal m_scaleFactor; + qreal m_scaleFactor = 1; }; }
M src/platform/qt/ObjView.cppsrc/platform/qt/ObjView.cpp

@@ -27,9 +27,6 @@

ObjView::ObjView(GameController* controller, QWidget* parent) : AssetView(controller, parent) , m_controller(controller) - , m_tileStatus{} - , m_objId(0) - , m_objInfo{} { m_ui.setupUi(this); m_ui.tile->setController(controller);

@@ -46,12 +43,16 @@ m_ui.palette->setFont(font);

m_ui.transform->setFont(font); m_ui.mode->setFont(font); - connect(m_ui.tiles, SIGNAL(indexPressed(int)), this, SLOT(translateIndex(int))); - connect(m_ui.objId, SIGNAL(valueChanged(int)), this, SLOT(selectObj(int))); + connect(m_ui.tiles, &TilePainter::indexPressed, this, &ObjView::translateIndex); + connect(m_ui.objId, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ObjView::selectObj); connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() { updateTiles(true); }); - connect(m_ui.exportButton, SIGNAL(clicked()), this, SLOT(exportObj())); +#ifdef USE_PNG + connect(m_ui.exportButton, &QAbstractButton::clicked, this, &ObjView::exportObj); +#else + m_ui.exportButton->setVisible(false); +#endif } void ObjView::selectObj(int obj) {

@@ -242,14 +243,11 @@ m_ui.mode->setText(tr("N/A"));

} #endif +#ifdef USE_PNG void ObjView::exportObj() { GameController::Interrupter interrupter(m_controller); - QFileDialog* dialog = GBAApp::app()->getSaveFileDialog(this, tr("Export sprite"), - tr("Portable Network Graphics (*.png)")); - if (!dialog->exec()) { - return; - } - QString filename = dialog->selectedFiles()[0]; + QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"), + tr("Portable Network Graphics (*.png)")); VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC); if (!vf) { LOG(QT, ERROR) << tr("Failed to open output PNG file: %1").arg(filename);

@@ -286,6 +284,7 @@ PNGWritePixels8(png, m_objInfo.width * 8, m_objInfo.height * 8, m_objInfo.width * 8, static_cast<void*>(buffer));

PNGWriteClose(png, info); delete[] buffer; } +#endif bool ObjView::ObjInfo::operator!=(const ObjInfo& other) { return other.tile != tile ||
M src/platform/qt/ObjView.hsrc/platform/qt/ObjView.h

@@ -21,8 +21,10 @@

public: ObjView(GameController* controller, QWidget* parent = nullptr); +#ifdef USE_PNG public slots: void exportObj(); +#endif private slots: void selectObj(int);

@@ -39,8 +41,8 @@

Ui::ObjView m_ui; GameController* m_controller; - mTileCacheEntry m_tileStatus[1024 * 32]; // TODO: Correct size - int m_objId; + mTileCacheEntry m_tileStatus[1024 * 32] = {}; // TODO: Correct size + int m_objId = 0; struct ObjInfo { unsigned tile; unsigned width;

@@ -51,7 +53,7 @@ unsigned paletteSet;

unsigned bits; bool operator!=(const ObjInfo&); - } m_objInfo; + } m_objInfo = {}; int m_tileOffset; };
M src/platform/qt/OverrideView.cppsrc/platform/qt/OverrideView.cpp

@@ -56,8 +56,8 @@ }

#endif m_ui.setupUi(this); - connect(controller, SIGNAL(gameStarted(mCoreThread*, const QString&)), this, SLOT(gameStarted(mCoreThread*))); - connect(controller, SIGNAL(gameStopped(mCoreThread*)), this, SLOT(gameStopped())); + connect(controller, &GameController::gameStarted, this, &OverrideView::gameStarted); + connect(controller, &GameController::gameStopped, this, &OverrideView::gameStopped); connect(m_ui.hwAutodetect, &QAbstractButton::toggled, [this] (bool enabled) { m_ui.hwRTC->setEnabled(!enabled);

@@ -67,19 +67,19 @@ m_ui.hwTilt->setEnabled(!enabled);

m_ui.hwRumble->setEnabled(!enabled); }); - connect(m_ui.savetype, SIGNAL(currentIndexChanged(int)), this, SLOT(updateOverrides())); - connect(m_ui.hwAutodetect, SIGNAL(clicked()), this, SLOT(updateOverrides())); - connect(m_ui.hwRTC, SIGNAL(clicked()), this, SLOT(updateOverrides())); - connect(m_ui.hwGyro, SIGNAL(clicked()), this, SLOT(updateOverrides())); - connect(m_ui.hwLight, SIGNAL(clicked()), this, SLOT(updateOverrides())); - connect(m_ui.hwTilt, SIGNAL(clicked()), this, SLOT(updateOverrides())); - connect(m_ui.hwRumble, SIGNAL(clicked()), this, SLOT(updateOverrides())); - connect(m_ui.hwGBPlayer, SIGNAL(clicked()), this, SLOT(updateOverrides())); + connect(m_ui.savetype, &QComboBox::currentTextChanged, this, &OverrideView::updateOverrides); + connect(m_ui.hwAutodetect, &QAbstractButton::clicked, this, &OverrideView::updateOverrides); + connect(m_ui.hwRTC, &QAbstractButton::clicked, this, &OverrideView::updateOverrides); + connect(m_ui.hwGyro, &QAbstractButton::clicked, this, &OverrideView::updateOverrides); + connect(m_ui.hwLight, &QAbstractButton::clicked, this, &OverrideView::updateOverrides); + connect(m_ui.hwTilt, &QAbstractButton::clicked, this, &OverrideView::updateOverrides); + connect(m_ui.hwRumble, &QAbstractButton::clicked, this, &OverrideView::updateOverrides); + connect(m_ui.hwGBPlayer, &QAbstractButton::clicked, this, &OverrideView::updateOverrides); - connect(m_ui.gbModel, SIGNAL(currentIndexChanged(int)), this, SLOT(updateOverrides())); - connect(m_ui.mbc, SIGNAL(currentIndexChanged(int)), this, SLOT(updateOverrides())); + connect(m_ui.gbModel, &QComboBox::currentTextChanged, this, &OverrideView::updateOverrides); + connect(m_ui.mbc, &QComboBox::currentTextChanged, this, &OverrideView::updateOverrides); - connect(m_ui.tabWidget, SIGNAL(currentChanged(int)), this, SLOT(updateOverrides())); + connect(m_ui.tabWidget, &QTabWidget::currentChanged, this, &OverrideView::updateOverrides); #ifndef M_CORE_GBA m_ui.tabWidget->removeTab(m_ui.tabWidget->indexOf(m_ui.tabGBA)); #endif

@@ -87,8 +87,8 @@ #ifndef M_CORE_GB

m_ui.tabWidget->removeTab(m_ui.tabWidget->indexOf(m_ui.tabGB)); #endif - connect(m_ui.buttonBox, SIGNAL(accepted()), this, SLOT(saveOverride())); - connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close())); + connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &OverrideView::saveOverride); + connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); m_ui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(false); if (controller->isLoaded()) {
M src/platform/qt/PaletteView.cppsrc/platform/qt/PaletteView.cpp

@@ -30,7 +30,7 @@ , m_controller(controller)

{ m_ui.setupUi(this); - connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(updatePalette())); + connect(m_controller, &GameController::frameAvailable, this, &PaletteView::updatePalette); m_ui.bgGrid->setDimensions(QSize(16, 16)); m_ui.objGrid->setDimensions(QSize(16, 16)); int count = 256;

@@ -56,12 +56,12 @@ 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.bgGrid, &Swatch::indexPressed, this, &PaletteView::selectIndex); connect(m_ui.objGrid, &Swatch::indexPressed, [this, count] (int index) { selectIndex(index + count); }); connect(m_ui.exportBG, &QAbstractButton::clicked, [this, count] () { exportPalette(0, count); }); connect(m_ui.exportOBJ, &QAbstractButton::clicked, [this, count] () { exportPalette(count, count); }); - connect(controller, SIGNAL(gameStopped(mCoreThread*)), this, SLOT(close())); + connect(controller, &GameController::gameStopped, this, &QWidget::close); } void PaletteView::updatePalette() {

@@ -134,21 +134,16 @@ length = 512 - start;

} GameController::Interrupter interrupter(m_controller); - QFileDialog* dialog = GBAApp::app()->getSaveFileDialog(this, tr("Export palette"), - tr("Windows PAL (*.pal);;Adobe Color Table (*.act)")); - if (!dialog->exec()) { - return; - } - QString filename = dialog->selectedFiles()[0]; + QString filename = GBAApp::app()->getSaveFileName(this, tr("Export palette"), + tr("Windows PAL (*.pal);;Adobe Color Table (*.act)")); VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC); if (!vf) { LOG(QT, ERROR) << tr("Failed to open output palette file: %1").arg(filename); return; } - QString filter = dialog->selectedNameFilter(); - if (filter.contains("*.pal")) { + if (filename.endsWith(".pal", Qt::CaseInsensitive)) { exportPaletteRIFF(vf, length, &static_cast<GBA*>(m_controller->thread()->core->board)->video.palette[start]); - } else if (filter.contains("*.act")) { + } else if (filename.endsWith(".act", Qt::CaseInsensitive)) { exportPaletteACT(vf, length, &static_cast<GBA*>(m_controller->thread()->core->board)->video.palette[start]); } vf->close(vf);
M src/platform/qt/SensorView.cppsrc/platform/qt/SensorView.cpp

@@ -22,10 +22,11 @@ , m_rotation(input->rotationSource())

{ m_ui.setupUi(this); - connect(m_ui.lightSpin, SIGNAL(valueChanged(int)), this, SLOT(setLuminanceValue(int))); - connect(m_ui.lightSlide, SIGNAL(valueChanged(int)), this, SLOT(setLuminanceValue(int))); + connect(m_ui.lightSpin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), + this, &SensorView::setLuminanceValue); + connect(m_ui.lightSlide, &QAbstractSlider::valueChanged, this, &SensorView::setLuminanceValue); - connect(m_ui.timeNoOverride, SIGNAL(clicked()), controller, SLOT(setRealTime())); + connect(m_ui.timeNoOverride, &QAbstractButton::clicked, controller, &GameController::setRealTime); connect(m_ui.timeFixed, &QRadioButton::clicked, [controller, this] () { controller->setFixedTime(m_ui.time->dateTime()); });

@@ -39,10 +40,10 @@ connect(m_ui.timeNow, &QPushButton::clicked, [controller, this] () {

m_ui.time->setDateTime(QDateTime::currentDateTime()); }); - connect(m_controller, SIGNAL(luminanceValueChanged(int)), this, SLOT(luminanceValueChanged(int))); + connect(m_controller, &GameController::luminanceValueChanged, this, &SensorView::luminanceValueChanged); m_timer.setInterval(2); - connect(&m_timer, SIGNAL(timeout()), this, SLOT(updateSensors())); + connect(&m_timer, &QTimer::timeout, this, &SensorView::updateSensors); if (!m_rotation || !m_rotation->readTiltX || !m_rotation->readTiltY) { m_ui.tilt->hide(); } else {
M src/platform/qt/SettingsView.cppsrc/platform/qt/SettingsView.cpp

@@ -136,7 +136,12 @@ connect(m_ui.gbcBiosBrowse, &QPushButton::clicked, [this]() {

selectBios(m_ui.gbcBios); }); - connect(m_ui.buttonBox, SIGNAL(accepted()), this, SLOT(updateConfig())); + connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &SettingsView::updateConfig); + connect(m_ui.buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) { + if (m_ui.buttonBox->buttonRole(button) == QDialogButtonBox::ApplyRole) { + updateConfig(); + } + }); ShortcutView* shortcutView = new ShortcutView(); shortcutView->setModel(inputModel);

@@ -165,6 +170,7 @@ saveSetting("audioSync", m_ui.audioSync);

saveSetting("frameskip", m_ui.frameskip); saveSetting("fpsTarget", m_ui.fpsTarget); saveSetting("lockAspectRatio", m_ui.lockAspectRatio); + saveSetting("lockIntegerScaling", m_ui.lockIntegerScaling); saveSetting("volume", m_ui.volume); saveSetting("mute", m_ui.mute); saveSetting("rewindEnable", m_ui.rewind);

@@ -178,7 +184,9 @@ saveSetting("savegamePath", m_ui.savegamePath);

saveSetting("savestatePath", m_ui.savestatePath); saveSetting("screenshotPath", m_ui.screenshotPath); saveSetting("patchPath", m_ui.patchPath); + saveSetting("libraryStyle", m_ui.libraryStyle->currentIndex()); saveSetting("showLibrary", m_ui.showLibrary); + saveSetting("preload", m_ui.preload); if (m_ui.fastForwardUnbounded->isChecked()) { saveSetting("fastForwardRatio", "-1");

@@ -244,6 +252,7 @@ loadSetting("audioSync", m_ui.audioSync);

loadSetting("frameskip", m_ui.frameskip); loadSetting("fpsTarget", m_ui.fpsTarget); loadSetting("lockAspectRatio", m_ui.lockAspectRatio); + loadSetting("lockIntegerScaling", m_ui.lockIntegerScaling); loadSetting("volume", m_ui.volume); loadSetting("mute", m_ui.mute); loadSetting("rewindEnable", m_ui.rewind);

@@ -258,6 +267,7 @@ loadSetting("savestatePath", m_ui.savestatePath);

loadSetting("screenshotPath", m_ui.screenshotPath); loadSetting("patchPath", m_ui.patchPath); loadSetting("showLibrary", m_ui.showLibrary); + loadSetting("preload", m_ui.preload); double fastForwardRatio = loadSetting("fastForwardRatio").toDouble(); if (fastForwardRatio <= 0) {
M src/platform/qt/SettingsView.uisrc/platform/qt/SettingsView.ui

@@ -7,7 +7,7 @@ <rect>

<x>0</x> <y>0</y> <width>650</width> - <height>450</height> + <height>454</height> </rect> </property> <property name="sizePolicy">

@@ -115,7 +115,7 @@ <widget class="QComboBox" name="audioBufferSize">

<property name="editable"> <bool>true</bool> </property> - <property name="currentText" stdset="0"> + <property name="currentText"> <string>1536</string> </property> <property name="currentIndex">

@@ -181,7 +181,7 @@ <widget class="QComboBox" name="sampleRate">

<property name="editable"> <bool>true</bool> </property> - <property name="currentText" stdset="0"> + <property name="currentText"> <string>44100</string> </property> <property name="currentIndex">

@@ -380,39 +380,46 @@ <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>Bilinear filtering</string> </property> </widget> </item> + <item row="11" column="1"> + <widget class="QCheckBox" name="lockIntegerScaling"> + <property name="text"> + <string>Force integer scaling</string> + </property> + </widget> + </item> </layout> </widget> <widget class="QWidget" name="interface_2"> <layout class="QFormLayout" name="formLayout_4"> - <item row="3" column="1"> - <widget class="QCheckBox" name="allowOpposingDirections"> + <item row="1" column="1"> + <widget class="QCheckBox" name="showLibrary"> <property name="text"> - <string>Allow opposing input directions</string> - </property> - </widget> - </item> - <item row="4" column="1"> - <widget class="QCheckBox" name="suspendScreensaver"> - <property name="text"> - <string>Suspend screensaver</string> + <string>Show when no game open</string> </property> <property name="checked"> <bool>true</bool> </property> </widget> </item> - <item row="5" column="1"> - <widget class="QCheckBox" name="pauseOnFocusLost"> - <property name="text"> - <string>Pause when inactive</string> - </property> + <item row="0" column="1"> + <widget class="QComboBox" name="libraryStyle"> + <item> + <property name="text"> + <string>List view</string> + </property> + </item> + <item> + <property name="text"> + <string>Tree view</string> + </property> + </item> </widget> </item> <item row="0" column="0">

@@ -422,27 +429,44 @@ <string>Library:</string>

</property> </widget> </item> - <item row="0" column="1"> - <widget class="QCheckBox" name="showLibrary"> + <item row="2" column="1"> + <widget class="QPushButton" name="clearCache"> + <property name="enabled"> + <bool>false</bool> + </property> <property name="text"> - <string>Show when no game open</string> + <string>Clear cache</string> </property> </widget> </item> - <item row="2" column="0" colspan="2"> + <item row="3" column="0" colspan="2"> <widget class="Line" name="line_8"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> - <item row="1" column="1"> - <widget class="QPushButton" name="clearCache"> - <property name="enabled"> - <bool>false</bool> + <item row="4" column="1"> + <widget class="QCheckBox" name="allowOpposingDirections"> + <property name="text"> + <string>Allow opposing input directions</string> </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QCheckBox" name="suspendScreensaver"> <property name="text"> - <string>Clear cache</string> + <string>Suspend screensaver</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="6" column="1"> + <widget class="QCheckBox" name="pauseOnFocusLost"> + <property name="text"> + <string>Pause when inactive</string> </property> </widget> </item>

@@ -568,21 +592,21 @@ </property>

</item> </widget> </item> - <item row="7" column="0" colspan="2"> + <item row="8" column="0" colspan="2"> <widget class="Line" name="line_2"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> - <item row="8" column="0"> + <item row="9" column="0"> <widget class="QLabel" name="label_24"> <property name="text"> <string>Savestate extra data:</string> </property> </widget> </item> - <item row="8" column="1"> + <item row="9" column="1"> <widget class="QCheckBox" name="saveStateScreenshot"> <property name="text"> <string>Screenshot</string>

@@ -592,7 +616,7 @@ <bool>true</bool>

</property> </widget> </item> - <item row="9" column="1"> + <item row="10" column="1"> <widget class="QCheckBox" name="saveStateSave"> <property name="text"> <string>Save data</string>

@@ -602,7 +626,7 @@ <bool>true</bool>

</property> </widget> </item> - <item row="10" column="1"> + <item row="11" column="1"> <widget class="QCheckBox" name="saveStateCheats"> <property name="text"> <string>Cheat codes</string>

@@ -612,14 +636,14 @@ <bool>true</bool>

</property> </widget> </item> - <item row="12" column="0"> + <item row="13" column="0"> <widget class="QLabel" name="label_25"> <property name="text"> <string>Load extra data:</string> </property> </widget> </item> - <item row="12" column="1"> + <item row="13" column="1"> <widget class="QCheckBox" name="loadStateScreenshot"> <property name="text"> <string>Screenshot</string>

@@ -629,21 +653,21 @@ <bool>true</bool>

</property> </widget> </item> - <item row="13" column="1"> + <item row="14" column="1"> <widget class="QCheckBox" name="loadStateSave"> <property name="text"> <string>Save data</string> </property> </widget> </item> - <item row="14" column="1"> + <item row="15" column="1"> <widget class="QCheckBox" name="loadStateCheats"> <property name="text"> <string>Cheat codes</string> </property> </widget> </item> - <item row="11" column="0" colspan="2"> + <item row="12" column="0" colspan="2"> <widget class="Line" name="line_9"> <property name="orientation"> <enum>Qt::Horizontal</enum>

@@ -657,6 +681,13 @@ <string>Rewind affects save data</string>

</property> <property name="checked"> <bool>true</bool> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QCheckBox" name="preload"> + <property name="text"> + <string>Preload entire ROM into memory</string> </property> </widget> </item>
M src/platform/qt/ShaderSelector.cppsrc/platform/qt/ShaderSelector.cpp

@@ -31,15 +31,14 @@ ShaderSelector::ShaderSelector(Display* display, ConfigController* config, QWidget* parent)

: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) , m_display(display) , m_config(config) - , m_shaderPath("") { m_ui.setupUi(this); refreshShaders(); - connect(m_ui.load, SIGNAL(clicked()), this, SLOT(selectShader())); - connect(m_ui.unload, SIGNAL(clicked()), this, SLOT(clearShader())); - connect(m_ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonPressed(QAbstractButton*))); + connect(m_ui.load, &QAbstractButton::clicked, this, &ShaderSelector::selectShader); + connect(m_ui.unload, &QAbstractButton::clicked, this, &ShaderSelector::clearShader); + connect(m_ui.buttonBox, &QDialogButtonBox::clicked, this, &ShaderSelector::buttonPressed); } ShaderSelector::~ShaderSelector() {

@@ -112,9 +111,9 @@ } else {

m_ui.author->clear(); } - disconnect(this, SIGNAL(saved()), 0, 0); - disconnect(this, SIGNAL(reset()), 0, 0); - disconnect(this, SIGNAL(resetToDefault()), 0, 0); + disconnect(this, &ShaderSelector::saved, 0, 0); + disconnect(this, &ShaderSelector::reset, 0, 0); + disconnect(this, &ShaderSelector::resetToDefault, 0, 0); #if !defined(_WIN32) || defined(USE_EPOXY) if (m_shaders->preprocessShader) {
A src/platform/qt/ShortcutController.h

@@ -0,0 +1,148 @@

+/* 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_SHORTCUT_MODEL +#define QGBA_SHORTCUT_MODEL + +#include "GamepadAxisEvent.h" + +#include <QAbstractItemModel> + +#include <functional> + +class QAction; +class QKeyEvent; +class QMenu; +class QString; + +namespace QGBA { + +class ConfigController; +class InputProfile; + +class ShortcutController : public QAbstractItemModel { +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"; + constexpr static const char* const BUTTON_PROFILE_SECTION = "shortcutProfileButton."; + constexpr static const char* const AXIS_PROFILE_SECTION = "shortcutProfileAxis."; + + class ShortcutItem { + public: + typedef QPair<std::function<void ()>, std::function<void ()>> Functions; + + ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent = nullptr); + ShortcutItem(Functions functions, int shortcut, const QString& visibleName, const QString& name, + ShortcutItem* parent = nullptr); + ShortcutItem(QMenu* action, ShortcutItem* parent = nullptr); + + QAction* action() { return m_action; } + const QAction* action() const { return m_action; } + const int shortcut() const { return m_shortcut; } + Functions functions() const { return m_functions; } + QMenu* menu() { return m_menu; } + const QMenu* menu() const { return m_menu; } + const QString& visibleName() const { return m_visibleName; } + const QString& name() const { return m_name; } + QList<ShortcutItem>& items() { return m_items; } + const QList<ShortcutItem>& items() const { return m_items; } + ShortcutItem* parent() { return m_parent; } + const ShortcutItem* parent() const { return m_parent; } + void addAction(QAction* action, const QString& name); + void addFunctions(Functions functions, int shortcut, const QString& visibleName, + const QString& name); + void addSubmenu(QMenu* menu); + int button() const { return m_button; } + void setShortcut(int 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; + } + + private: + QAction* m_action = nullptr; + int m_shortcut = 0; + QMenu* m_menu = nullptr; + Functions m_functions; + QString m_name; + QString m_visibleName; + int m_button = -1; + int m_axis = -1; + GamepadAxisEvent::Direction m_direction; + QList<ShortcutItem> m_items; + ShortcutItem* m_parent; + }; + +public: + ShortcutController(QObject* parent = nullptr); + + void setConfigController(ConfigController* controller); + void setProfile(const QString& profile); + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override; + virtual QModelIndex parent(const QModelIndex& index) const override; + + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + + void addAction(QMenu* menu, QAction* action, const QString& name); + void addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release, + int shortcut, const QString& visibleName, const QString& name); + void addFunctions(QMenu* menu, std::function<void()> press, std::function<void()> release, + const QKeySequence& shortcut, const QString& visibleName, const QString& name); + void addMenu(QMenu* menu, QMenu* parent = nullptr); + + QAction* getAction(const QString& name); + int shortcutAt(const QModelIndex& index) const; + bool isMenuAt(const QModelIndex& index) const; + + void updateKey(const QModelIndex& index, int 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); + + static int toModifierShortcut(const QString& shortcut); + static bool isModifierKey(int key); + static int toModifierKey(int key); + +public slots: + void loadProfile(const QString& profile); + +protected: + bool eventFilter(QObject*, QEvent*) override; + +private: + ShortcutItem* itemAt(const QModelIndex& index); + const ShortcutItem* itemAt(const QModelIndex& index) const; + bool loadShortcuts(ShortcutItem*); + void loadGamepadShortcuts(ShortcutItem*); + void onSubitems(ShortcutItem*, std::function<void(ShortcutItem*)> func); + void updateKey(ShortcutItem* item, int keySequence); + + ShortcutItem m_rootMenu{nullptr}; + QMap<QMenu*, ShortcutItem*> m_menuMap; + QMap<int, ShortcutItem*> m_buttons; + QMap<QPair<int, GamepadAxisEvent::Direction>, ShortcutItem*> m_axes; + QMap<int, ShortcutItem*> m_heldKeys; + ConfigController* m_config = nullptr; + QString m_profileName; + const InputProfile* m_profile = nullptr; +}; + +} + +#endif
M src/platform/qt/ShortcutView.cppsrc/platform/qt/ShortcutView.cpp

@@ -15,8 +15,6 @@ using namespace QGBA;

ShortcutView::ShortcutView(QWidget* parent) : QWidget(parent) - , m_controller(nullptr) - , m_input(nullptr) { m_ui.setupUi(this); m_ui.keyEdit->setValueKey(0);

@@ -31,10 +29,10 @@ bool signalsBlocked = m_ui.keyEdit->blockSignals(true);

m_ui.keyEdit->setValueKey(0); m_ui.keyEdit->blockSignals(signalsBlocked); }); - 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())); + connect(m_ui.keyEdit, &KeyEditor::valueChanged, this, &ShortcutView::updateButton); + connect(m_ui.keyEdit, &KeyEditor::axisChanged, this, &ShortcutView::updateAxis); + connect(m_ui.shortcutTable, &QAbstractItemView::doubleClicked, this, &ShortcutView::load); + connect(m_ui.clearButton, &QAbstractButton::clicked, this, &ShortcutView::clear); } ShortcutView::~ShortcutView() {
M src/platform/qt/ShortcutView.hsrc/platform/qt/ShortcutView.h

@@ -40,8 +40,8 @@

private: Ui::ShortcutView m_ui; - InputModel* m_controller; - InputController* m_input; + InputModel* m_controller = nullptr; + InputController* m_input = nullptr; }; }
M src/platform/qt/Swatch.cppsrc/platform/qt/Swatch.cpp

@@ -15,7 +15,6 @@

Swatch::Swatch(QWidget* parent) : QWidget(parent) { - m_size = 10; } void Swatch::setSize(int size) {
M src/platform/qt/Swatch.hsrc/platform/qt/Swatch.h

@@ -32,7 +32,7 @@ void paintEvent(QPaintEvent*) override;

void mousePressEvent(QMouseEvent*) override; private: - int m_size; + int m_size = 10; QVector<QColor> m_colors; QPixmap m_backing; QSize m_dims;
M src/platform/qt/TilePainter.cppsrc/platform/qt/TilePainter.cpp

@@ -13,9 +13,7 @@ using namespace QGBA;

TilePainter::TilePainter(QWidget* parent) : QWidget(parent) - , m_size(8) { - m_backing = QPixmap(256, 768); m_backing.fill(Qt::transparent); resize(256, 768); setTileCount(3072);
M src/platform/qt/TilePainter.hsrc/platform/qt/TilePainter.h

@@ -32,8 +32,8 @@ void mousePressEvent(QMouseEvent*) override;

void resizeEvent(QResizeEvent*) override; private: - QPixmap m_backing; - int m_size; + QPixmap m_backing{256, 768}; + int m_size = 8; int m_tileCount; };
M src/platform/qt/TileView.cppsrc/platform/qt/TileView.cpp

@@ -19,14 +19,12 @@

TileView::TileView(GameController* controller, QWidget* parent) : AssetView(controller, parent) , m_controller(controller) - , m_tileStatus{} - , m_paletteId(0) { m_ui.setupUi(this); m_ui.tile->setController(controller); - connect(m_ui.tiles, SIGNAL(indexPressed(int)), m_ui.tile, SLOT(selectIndex(int))); - connect(m_ui.paletteId, SIGNAL(valueChanged(int)), this, SLOT(updatePalette(int))); + connect(m_ui.tiles, &TilePainter::indexPressed, m_ui.tile, &AssetTile::selectIndex); + connect(m_ui.paletteId, &QAbstractSlider::valueChanged, this, &TileView::updatePalette); int max = 1024; int boundary = 1024;
M src/platform/qt/TileView.hsrc/platform/qt/TileView.h

@@ -35,8 +35,8 @@

Ui::TileView m_ui; GameController* m_controller; - mTileCacheEntry m_tileStatus[3072 * 32]; // TODO: Correct size - int m_paletteId; + mTileCacheEntry m_tileStatus[3072 * 32] = {}; // TODO: Correct size + int m_paletteId = 0; }; }
M src/platform/qt/VideoView.cppsrc/platform/qt/VideoView.cpp

@@ -45,13 +45,6 @@ }

VideoView::VideoView(QWidget* parent) : QWidget(parent) - , m_audioCodecCstr(nullptr) - , m_videoCodecCstr(nullptr) - , m_containerCstr(nullptr) - , m_nativeWidth(0) - , m_nativeHeight(0) - , m_width(1) - , m_height(1) { m_ui.setupUi(this);

@@ -63,7 +56,9 @@ }

if (s_vcodecMap.empty()) { s_vcodecMap["dirac"] = "libschroedinger"; s_vcodecMap["h264"] = "libx264"; + s_vcodecMap["h264 nvenc"] = "h264_nvenc"; s_vcodecMap["hevc"] = "libx265"; + s_vcodecMap["hevc nvenc"] = "hevc_nvenc"; s_vcodecMap["theora"] = "libtheora"; s_vcodecMap["vp8"] = "libvpx"; s_vcodecMap["vp9"] = "libvpx-vp9";

@@ -73,12 +68,12 @@ if (s_containerMap.empty()) {

s_containerMap["mkv"] = "matroska"; } - connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close())); - connect(m_ui.start, SIGNAL(clicked()), this, SLOT(startRecording())); - connect(m_ui.stop, SIGNAL(clicked()), this, SLOT(stopRecording())); + connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &VideoView::close); + connect(m_ui.start, &QAbstractButton::clicked, this, &VideoView::startRecording); + connect(m_ui.stop, &QAbstractButton::clicked, this, &VideoView::stopRecording); - connect(m_ui.selectFile, SIGNAL(clicked()), this, SLOT(selectFile())); - connect(m_ui.filename, SIGNAL(textChanged(const QString&)), this, SLOT(setFilename(const QString&))); + connect(m_ui.selectFile, &QAbstractButton::clicked, this, &VideoView::selectFile); + connect(m_ui.filename, &QLineEdit::textChanged, this, &VideoView::setFilename); connect(m_ui.audio, SIGNAL(activated(const QString&)), this, SLOT(setAudioCodec(const QString&))); connect(m_ui.video, SIGNAL(activated(const QString&)), this, SLOT(setVideoCodec(const QString&)));

@@ -96,7 +91,7 @@

connect(m_ui.wratio, SIGNAL(valueChanged(int)), this, SLOT(setAspectWidth(int))); connect(m_ui.hratio, SIGNAL(valueChanged(int)), this, SLOT(setAspectHeight(int))); - connect(m_ui.showAdvanced, SIGNAL(clicked(bool)), this, SLOT(showAdvanced(bool))); + connect(m_ui.showAdvanced, &QAbstractButton::clicked, this, &VideoView::showAdvanced); FFmpegEncoderInit(&m_encoder);

@@ -458,6 +453,8 @@

QString VideoView::sanitizeCodec(const QString& codec, const QMap<QString, QString>& mapping) { QString sanitized = codec.toLower(); sanitized = sanitized.remove(QChar('.')); + sanitized = sanitized.remove(QChar('(')); + sanitized = sanitized.remove(QChar(')')); if (mapping.contains(sanitized)) { sanitized = mapping[sanitized]; }
M src/platform/qt/VideoView.hsrc/platform/qt/VideoView.h

@@ -86,18 +86,18 @@ QString m_filename;

QString m_audioCodec; QString m_videoCodec; QString m_container; - char* m_audioCodecCstr; - char* m_videoCodecCstr; - char* m_containerCstr; + char* m_audioCodecCstr = nullptr; + char* m_videoCodecCstr = nullptr; + char* m_containerCstr = nullptr; int m_abr; int m_vbr; - int m_width; - int m_height; + int m_width = 1; + int m_height = 1; - int m_nativeWidth; - int m_nativeHeight; + int m_nativeWidth = 0; + int m_nativeHeight = 0; QMap<QAbstractButton*, Preset> m_presets;
M src/platform/qt/VideoView.uisrc/platform/qt/VideoView.ui

@@ -266,12 +266,17 @@ </property>

</item> <item> <property name="text"> - <string>VP8</string> + <string>h.264 (NVENC)</string> + </property> + </item> + <item> + <property name="text"> + <string>HEVC</string> </property> </item> <item> <property name="text"> - <string>Xvid</string> + <string>VP8</string> </property> </item> <item>
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -14,10 +14,12 @@ #include <QMimeData>

#include <QPainter> #include <QStackedLayout> -#include "AboutScreen.h" #ifdef USE_SQLITE3 #include "ArchiveInspector.h" +#include "library/LibraryController.h" #endif + +#include "AboutScreen.h" #include "CheatsView.h" #include "ConfigController.h" #include "DebuggerConsole.h"

@@ -55,7 +57,7 @@ #include <mgba/internal/gba/gba.h>

#include <mgba/internal/gba/input.h> #include <mgba/internal/gba/video.h> #endif -#include "feature/commandline.h" +#include <mgba/feature/commandline.h> #include "feature/sqlite3/no-intro.h" #include <mgba-util/vfs.h>

@@ -63,30 +65,11 @@ using namespace QGBA;

Window::Window(ConfigController* config, int playerId, QWidget* parent) : QMainWindow(parent) - , m_log(0) , m_logView(new LogView(&m_log)) - , m_stateWindow(nullptr) , m_screenWidget(new WindowBackground()) - , m_logo(":/res/mgba-1024.png") , m_config(config) , m_inputModel(new InputModel(this)) , m_inputController(m_inputModel, playerId, this) -#ifdef USE_FFMPEG - , m_videoView(nullptr) -#endif -#ifdef USE_MAGICK - , m_gifView(nullptr) -#endif -#ifdef USE_GDB_STUB - , m_gdbController(nullptr) -#endif -#ifdef USE_DEBUGGERS - , m_console(nullptr) -#endif - , m_mruMenu(nullptr) - , m_fullscreenOnStart(false) - , m_autoresume(false) - , m_wasOpened(false) { setFocusPolicy(Qt::StrongFocus); setAcceptDrops(true);

@@ -110,7 +93,7 @@ m_savedScale = multiplier.toInt();

i = m_savedScale; } #ifdef USE_SQLITE3 - m_libraryView = new LibraryView(); + m_libraryView = new LibraryController(nullptr, ConfigController::configDir() + "/library.sqlite3", m_config); ConfigOption* showLibrary = m_config->addOption("showLibrary"); showLibrary->connect([this](const QVariant& value) { if (value.toBool()) {

@@ -124,12 +107,17 @@ detachWidget(m_libraryView);

} }, this); m_config->updateOption("showLibrary"); + ConfigOption* libraryStyle = m_config->addOption("libraryStyle"); + libraryStyle->connect([this](const QVariant& value) { + m_libraryView->setViewStyle(static_cast<LibraryStyle>(value.toInt())); + }, this); + m_config->updateOption("libraryStyle"); - connect(m_libraryView, &LibraryView::accepted, [this]() { + connect(m_libraryView, &LibraryController::startGame, [this]() { VFile* output = m_libraryView->selectedVFile(); - QPair<QString, QString> path = m_libraryView->selectedPath(); if (output) { - m_controller->loadGame(output, path.first, path.second); + QPair<QString, QString> path = m_libraryView->selectedPath(); + m_controller->loadGame(output, path.second, path.first); } }); #elif defined(M_CORE_GBA)

@@ -137,15 +125,16 @@ m_screenWidget->setSizeHint(QSize(VIDEO_HORIZONTAL_PIXELS * i, VIDEO_VERTICAL_PIXELS * i));

#endif m_screenWidget->setPixmap(m_logo); m_screenWidget->setLockAspectRatio(m_logo.width(), m_logo.height()); + m_screenWidget->setLockIntegerScaling(false); setCentralWidget(m_screenWidget); - connect(m_controller, SIGNAL(gameStarted(mCoreThread*, const QString&)), this, SLOT(gameStarted(mCoreThread*, const QString&))); - connect(m_controller, SIGNAL(gameStarted(mCoreThread*, const QString&)), &m_inputController, SLOT(suspendScreensaver())); - connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), m_display, SLOT(stopDrawing())); - connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), this, SLOT(gameStopped())); - connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), &m_inputController, SLOT(resumeScreensaver())); - connect(m_controller, SIGNAL(stateLoaded(mCoreThread*)), m_display, SLOT(forceDraw())); - connect(m_controller, SIGNAL(rewound(mCoreThread*)), m_display, SLOT(forceDraw())); + connect(m_controller, &GameController::gameStarted, this, &Window::gameStarted); + connect(m_controller, &GameController::gameStarted, &m_inputController, &InputController::suspendScreensaver); + connect(m_controller, &GameController::gameStopped, m_display, &Display::stopDrawing); + connect(m_controller, &GameController::gameStopped, this, &Window::gameStopped); + connect(m_controller, &GameController::gameStopped, &m_inputController, &InputController::resumeScreensaver); + connect(m_controller, &GameController::stateLoaded, m_display, &Display::forceDraw); + connect(m_controller, &GameController::rewound, m_display, &Display::forceDraw); connect(m_controller, &GameController::gamePaused, [this](mCoreThread* context) { unsigned width, height; context->core->desiredVideoDimensions(context->core, &width, &height);

@@ -156,38 +145,38 @@ pixmap.convertFromImage(currentImage);

m_screenWidget->setPixmap(pixmap); m_screenWidget->setLockAspectRatio(width, height); }); - connect(m_controller, SIGNAL(gamePaused(mCoreThread*)), m_display, SLOT(pauseDrawing())); + connect(m_controller, &GameController::gamePaused, m_display, &Display::pauseDrawing); #ifndef Q_OS_MAC - connect(m_controller, SIGNAL(gamePaused(mCoreThread*)), menuBar(), SLOT(show())); + connect(m_controller, &GameController::gamePaused, menuBar(), &QWidget::show); connect(m_controller, &GameController::gameUnpaused, [this]() { if(isFullScreen()) { menuBar()->hide(); } }); #endif - connect(m_controller, SIGNAL(gamePaused(mCoreThread*)), &m_inputController, SLOT(resumeScreensaver())); - connect(m_controller, SIGNAL(gameUnpaused(mCoreThread*)), m_display, SLOT(unpauseDrawing())); - connect(m_controller, SIGNAL(gameUnpaused(mCoreThread*)), &m_inputController, SLOT(suspendScreensaver())); - connect(m_controller, SIGNAL(postLog(int, int, const QString&)), &m_log, SLOT(postLog(int, 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_controller, SIGNAL(statusPosted(const QString&)), m_display, SLOT(showMessage(const QString&))); - connect(&m_log, SIGNAL(levelsSet(int)), m_controller, SLOT(setLogLevel(int))); - connect(&m_log, SIGNAL(levelsEnabled(int)), m_controller, SLOT(enableLogLevel(int))); - connect(&m_log, SIGNAL(levelsDisabled(int)), m_controller, SLOT(disableLogLevel(int))); - connect(this, SIGNAL(startDrawing(mCoreThread*)), m_display, SLOT(startDrawing(mCoreThread*)), 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())); - connect(this, SIGNAL(shutdown()), m_shaderView, SLOT(hide())); - connect(this, SIGNAL(audioBufferSamplesChanged(int)), m_controller, SLOT(setAudioBufferSamples(int))); - connect(this, SIGNAL(sampleRateChanged(unsigned)), m_controller, SLOT(setAudioSampleRate(unsigned))); - connect(this, SIGNAL(fpsTargetChanged(float)), m_controller, SLOT(setFPSTarget(float))); - connect(&m_fpsTimer, SIGNAL(timeout()), this, SLOT(showFPS())); - connect(&m_focusCheck, SIGNAL(timeout()), this, SLOT(focusCheck())); + connect(m_controller, &GameController::gamePaused, &m_inputController, &InputController::resumeScreensaver); + connect(m_controller, &GameController::gameUnpaused, m_display, &Display::unpauseDrawing); + connect(m_controller, &GameController::gameUnpaused, &m_inputController, &InputController::suspendScreensaver); + connect(m_controller, &GameController::postLog, &m_log, &LogController::postLog); + connect(m_controller, &GameController::frameAvailable, this, &Window::recordFrame); + connect(m_controller, &GameController::frameAvailable, m_display, &Display::framePosted); + connect(m_controller, &GameController::gameCrashed, this, &Window::gameCrashed); + connect(m_controller, &GameController::gameFailed, this, &Window::gameFailed); + connect(m_controller, &GameController::unimplementedBiosCall, this, &Window::unimplementedBiosCall); + connect(m_controller, &GameController::statusPosted, m_display, &Display::showMessage); + connect(&m_log, &LogController::levelsSet, m_controller, &GameController::setLogLevel); + connect(&m_log, &LogController::levelsEnabled, m_controller, &GameController::enableLogLevel); + connect(&m_log, &LogController::levelsDisabled, m_controller, &GameController::disableLogLevel); + connect(this, &Window::startDrawing, m_display, &Display::startDrawing, Qt::QueuedConnection); + connect(this, &Window::shutdown, m_display, &Display::stopDrawing); + connect(this, &Window::shutdown, m_controller, &GameController::closeGame); + connect(this, &Window::shutdown, m_logView, &QWidget::hide); + connect(this, &Window::shutdown, m_shaderView, &QWidget::hide); + connect(this, &Window::audioBufferSamplesChanged, m_controller, &GameController::setAudioBufferSamples); + connect(this, &Window::sampleRateChanged, m_controller, &GameController::setAudioSampleRate); + connect(this, &Window::fpsTargetChanged, m_controller, &GameController::setFPSTarget); + connect(&m_fpsTimer, &QTimer::timeout, this, &Window::showFPS); + connect(&m_focusCheck, &QTimer::timeout, this, &Window::focusCheck); connect(m_display, &Display::hideCursor, [this]() { if (static_cast<QStackedLayout*>(m_screenWidget->layout())->currentWidget() == m_display) { m_screenWidget->setCursor(Qt::BlankCursor);

@@ -311,19 +300,6 @@ const mCoreOptions* opts = m_config->options();

m_log.setLevels(opts->logLevel); - QString saveStateExtdata = m_config->getOption("saveStateExtdata"); - bool ok; - int flags = saveStateExtdata.toInt(&ok); - if (ok) { - m_controller->setSaveStateExtdata(flags); - } - - QString loadStateExtdata = m_config->getOption("loadStateExtdata"); - flags = loadStateExtdata.toInt(&ok); - if (ok) { - m_controller->setLoadStateExtdata(flags); - } - m_controller->setConfig(m_config->config()); m_display->lockAspectRatio(opts->lockAspectRatio); m_display->filter(opts->resampleVideo);

@@ -375,6 +351,7 @@ #endif

formats.removeDuplicates(); filters.prepend(tr("All ROMs (%1)").arg(formats.join(QChar(' ')))); + filters.append(tr("%1 Video Logs (*.mvl)").arg(projectName)); return filters.join(";;"); }

@@ -465,7 +442,8 @@ }

} void Window::openView(QWidget* widget) { - connect(this, SIGNAL(shutdown()), widget, SLOT(close())); + connect(this, &Window::shutdown, widget, &QWidget::close); + connect(m_controller, &GameController::gameStopped, widget, &QWidget::close); widget->setAttribute(Qt::WA_DeleteOnClose); widget->show(); }

@@ -486,10 +464,10 @@ }

void Window::openSettingsWindow() { SettingsView* settingsWindow = new SettingsView(m_config, &m_inputController, m_inputModel); - connect(settingsWindow, SIGNAL(biosLoaded(int, const QString&)), m_controller, SLOT(loadBIOS(int, const QString&))); - connect(settingsWindow, SIGNAL(audioDriverChanged()), m_controller, SLOT(reloadAudioDriver())); - connect(settingsWindow, SIGNAL(displayDriverChanged()), this, SLOT(mustRestart())); - connect(settingsWindow, SIGNAL(pathsChanged()), this, SLOT(reloadConfig())); + connect(settingsWindow, &SettingsView::biosLoaded, m_controller, &GameController::loadBIOS); + connect(settingsWindow, &SettingsView::audioDriverChanged, m_controller, &GameController::reloadAudioDriver); + connect(settingsWindow, &SettingsView::displayDriverChanged, this, &Window::mustRestart); + connect(settingsWindow, &SettingsView::pathsChanged, this, &Window::reloadConfig); openView(settingsWindow); }

@@ -498,6 +476,13 @@ AboutScreen* about = new AboutScreen();

openView(about); } +void Window::startVideoLog() { + QString filename = GBAApp::app()->getSaveFileName(this, tr("Select video log"), tr("Video logs (*.mvl)")); + if (!filename.isEmpty()) { + m_controller->startVideoLog(filename); + } +} + template <typename T, typename A> std::function<void()> Window::openTView(A arg) { return [=]() {

@@ -518,17 +503,17 @@ #ifdef USE_FFMPEG

void Window::openVideoWindow() { if (!m_videoView) { m_videoView = new VideoView(); - connect(m_videoView, SIGNAL(recordingStarted(mAVStream*)), m_controller, SLOT(setAVStream(mAVStream*))); - connect(m_videoView, SIGNAL(recordingStopped()), m_controller, SLOT(clearAVStream()), Qt::DirectConnection); - connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), m_videoView, SLOT(stopRecording())); - connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), m_videoView, SLOT(close())); + connect(m_videoView, &VideoView::recordingStarted, m_controller, &GameController::setAVStream); + connect(m_videoView, &VideoView::recordingStopped, m_controller, &GameController::clearAVStream, Qt::DirectConnection); + connect(m_controller, &GameController::gameStopped, m_videoView, &VideoView::stopRecording); + connect(m_controller, &GameController::gameStopped, m_videoView, &QWidget::close); connect(m_controller, &GameController::gameStarted, [this]() { m_videoView->setNativeResolution(m_controller->screenDimensions()); }); if (m_controller->isLoaded()) { m_videoView->setNativeResolution(m_controller->screenDimensions()); } - connect(this, SIGNAL(shutdown()), m_videoView, SLOT(close())); + connect(this, &Window::shutdown, m_videoView, &QWidget::close); } m_videoView->show(); }

@@ -538,11 +523,11 @@ #ifdef USE_MAGICK

void Window::openGIFWindow() { if (!m_gifView) { m_gifView = new GIFView(); - connect(m_gifView, SIGNAL(recordingStarted(mAVStream*)), m_controller, SLOT(setAVStream(mAVStream*))); - connect(m_gifView, SIGNAL(recordingStopped()), m_controller, SLOT(clearAVStream()), Qt::DirectConnection); - connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), m_gifView, SLOT(stopRecording())); - connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), m_gifView, SLOT(close())); - connect(this, SIGNAL(shutdown()), m_gifView, SLOT(close())); + connect(m_gifView, &GIFView::recordingStarted, m_controller, &GameController::setAVStream); + connect(m_gifView, &GIFView::recordingStopped, m_controller, &GameController::clearAVStream, Qt::DirectConnection); + connect(m_controller, &GameController::gameStopped, m_gifView, &GIFView::stopRecording); + connect(m_controller, &GameController::gameStopped, m_gifView, &QWidget::close); + connect(this, &Window::shutdown, m_gifView, &QWidget::close); } m_gifView->show(); }

@@ -707,11 +692,11 @@ MutexUnlock(&context->stateMutex);

return; } MutexUnlock(&context->stateMutex); - foreach (QAction* action, m_gameActions) { + for (QAction* action : m_gameActions) { action->setDisabled(false); } #ifdef M_CORE_GBA - foreach (QAction* action, m_gbaActions) { + for (QAction* action : m_gbaActions) { action->setDisabled(context->core->platform(context->core) != PLATFORM_GBA); } #endif

@@ -725,6 +710,7 @@ unsigned width, height;

context->core->desiredVideoDimensions(context->core, &width, &height); m_display->setMinimumSize(width, height); m_screenWidget->setMinimumSize(m_display->minimumSize()); + m_config->updateOption("lockIntegerScaling"); if (m_savedScale > 0) { resizeFrame(QSize(width, height) * m_savedScale); }

@@ -741,21 +727,55 @@

m_hitUnimplementedBiosCall = false; m_fpsTimer.start(); m_focusCheck.start(); + + m_controller->threadInterrupt(); + if (m_controller->isLoaded()) { + mCore* core = m_controller->thread()->core; + const mCoreChannelInfo* videoLayers; + const mCoreChannelInfo* audioChannels; + size_t nVideo = core->listVideoLayers(core, &videoLayers); + size_t nAudio = core->listAudioChannels(core, &audioChannels); + + if (nVideo) { + for (size_t i = 0; i < nVideo; ++i) { + QAction* action = new QAction(videoLayers[i].visibleName, m_videoLayers); + action->setCheckable(true); + action->setChecked(true); + connect(action, &QAction::triggered, [this, videoLayers, i](bool enable) { + m_controller->setVideoLayerEnabled(videoLayers[i].id, enable); + }); + m_videoLayers->addAction(action); + } + } + if (nAudio) { + for (size_t i = 0; i < nAudio; ++i) { + QAction* action = new QAction(audioChannels[i].visibleName, m_audioChannels); + action->setCheckable(true); + action->setChecked(true); + connect(action, &QAction::triggered, [this, audioChannels, i](bool enable) { + m_controller->setAudioChannelEnabled(audioChannels[i].id, enable); + }); + m_audioChannels->addAction(action); + } + } + } + m_controller->threadContinue(); } void Window::gameStopped() { #ifdef M_CORE_GBA - foreach (QAction* action, m_gbaActions) { + for (QAction* action : m_gbaActions) { action->setDisabled(false); } #endif - foreach (QAction* action, m_gameActions) { + for (QAction* action : m_gameActions) { action->setDisabled(true); } setWindowFilePath(QString()); updateTitle(); detachWidget(m_display); m_screenWidget->setLockAspectRatio(m_logo.width(), m_logo.height()); + m_screenWidget->setLockIntegerScaling(false); m_screenWidget->setPixmap(m_logo); m_screenWidget->unsetCursor(); #ifdef M_CORE_GB

@@ -764,6 +784,9 @@ #elif defined(M_CORE_GBA)

m_display->setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); #endif m_screenWidget->setMinimumSize(m_display->minimumSize()); + + m_videoLayers->clear(); + m_audioChannels->clear(); m_fpsTimer.stop(); m_focusCheck.stop();

@@ -804,7 +827,7 @@ QMessageBox* confirm = new QMessageBox(QMessageBox::Question, tr("Really make portable?"),

tr("This will make the emulator load its configuration from the same directory as the executable. Do you want to continue?"), QMessageBox::Yes | QMessageBox::Cancel, this, Qt::Sheet); confirm->setAttribute(Qt::WA_DeleteOnClose); - connect(confirm->button(QMessageBox::Yes), SIGNAL(clicked()), m_config, SLOT(makePortable())); + connect(confirm->button(QMessageBox::Yes), &QAbstractButton::clicked, m_config, &ConfigController::makePortable); confirm->show(); }

@@ -886,8 +909,8 @@ return;

} bool wasPaused = m_controller->isPaused(); m_stateWindow = new LoadSaveState(m_controller); - connect(this, SIGNAL(shutdown()), m_stateWindow, SLOT(close())); - connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), m_stateWindow, SLOT(close())); + connect(this, &Window::shutdown, m_stateWindow, &QWidget::close); + connect(m_controller, &GameController::gameStopped, m_stateWindow, &QWidget::close); connect(m_stateWindow, &LoadSaveState::closed, [this]() { detachWidget(m_stateWindow); m_stateWindow = nullptr;

@@ -965,13 +988,13 @@ m_inputModel->addMenu(quickLoadMenu);

m_inputModel->addMenu(quickSaveMenu); QAction* quickLoad = new QAction(tr("Load recent"), quickLoadMenu); - connect(quickLoad, SIGNAL(triggered()), m_controller, SLOT(loadState())); + connect(quickLoad, &QAction::triggered, m_controller, &GameController::loadState); m_gameActions.append(quickLoad); m_nonMpActions.append(quickLoad); addControlledAction(quickLoadMenu, quickLoad, "quickLoad"); QAction* quickSave = new QAction(tr("Save recent"), quickSaveMenu); - connect(quickSave, SIGNAL(triggered()), m_controller, SLOT(saveState())); + connect(quickSave, &QAction::triggered, m_controller, &GameController::saveState); m_gameActions.append(quickSave); m_nonMpActions.append(quickSave); addControlledAction(quickSaveMenu, quickSave, "quickSave");

@@ -981,14 +1004,14 @@ quickSaveMenu->addSeparator();

QAction* undoLoadState = new QAction(tr("Undo load state"), quickLoadMenu); undoLoadState->setShortcut(tr("F11")); - connect(undoLoadState, SIGNAL(triggered()), m_controller, SLOT(loadBackupState())); + connect(undoLoadState, &QAction::triggered, m_controller, &GameController::loadBackupState); m_gameActions.append(undoLoadState); m_nonMpActions.append(undoLoadState); addControlledAction(quickLoadMenu, undoLoadState, "undoLoadState"); QAction* undoSaveState = new QAction(tr("Undo save state"), quickSaveMenu); undoSaveState->setShortcut(tr("Shift+F11")); - connect(undoSaveState, SIGNAL(triggered()), m_controller, SLOT(saveBackupState())); + connect(undoSaveState, &QAction::triggered, m_controller, &GameController::saveBackupState); m_gameActions.append(undoSaveState); m_nonMpActions.append(undoSaveState); addControlledAction(quickSaveMenu, undoSaveState, "undoSaveState");

@@ -1016,31 +1039,31 @@

#ifdef M_CORE_GBA fileMenu->addSeparator(); QAction* importShark = new QAction(tr("Import GameShark Save"), fileMenu); - connect(importShark, SIGNAL(triggered()), this, SLOT(importSharkport())); + connect(importShark, &QAction::triggered, this, &Window::importSharkport); m_gameActions.append(importShark); m_gbaActions.append(importShark); addControlledAction(fileMenu, importShark, "importShark"); QAction* exportShark = new QAction(tr("Export GameShark Save"), fileMenu); - connect(exportShark, SIGNAL(triggered()), this, SLOT(exportSharkport())); + connect(exportShark, &QAction::triggered, this, &Window::exportSharkport); m_gameActions.append(exportShark); m_gbaActions.append(exportShark); addControlledAction(fileMenu, exportShark, "exportShark"); #endif fileMenu->addSeparator(); - QAction* multiWindow = new QAction(tr("New multiplayer window"), fileMenu); - connect(multiWindow, &QAction::triggered, [this]() { + m_multiWindow = new QAction(tr("New multiplayer window"), fileMenu); + connect(m_multiWindow, &QAction::triggered, [this]() { GBAApp::app()->newWindow(); }); - addControlledAction(fileMenu, multiWindow, "multiWindow"); + addControlledAction(fileMenu, m_multiWindow, "multiWindow"); #ifndef Q_OS_MAC fileMenu->addSeparator(); #endif QAction* about = new QAction(tr("About"), fileMenu); - connect(about, SIGNAL(triggered()), this, SLOT(openAboutScreen())); + connect(about, &QAction::triggered, this, &Window::openAboutScreen); fileMenu->addAction(about); #ifndef Q_OS_MAC

@@ -1051,18 +1074,18 @@ QMenu* emulationMenu = menubar->addMenu(tr("&Emulation"));

m_inputModel->addMenu(emulationMenu); QAction* reset = new QAction(tr("&Reset"), emulationMenu); reset->setShortcut(tr("Ctrl+R")); - connect(reset, SIGNAL(triggered()), m_controller, SLOT(reset())); + connect(reset, &QAction::triggered, m_controller, &GameController::reset); m_gameActions.append(reset); addControlledAction(emulationMenu, reset, "reset"); QAction* shutdown = new QAction(tr("Sh&utdown"), emulationMenu); - connect(shutdown, SIGNAL(triggered()), m_controller, SLOT(closeGame())); + connect(shutdown, &QAction::triggered, m_controller, &GameController::closeGame); m_gameActions.append(shutdown); addControlledAction(emulationMenu, shutdown, "shutdown"); #ifdef M_CORE_GBA QAction* yank = new QAction(tr("Yank game pak"), emulationMenu); - connect(yank, SIGNAL(triggered()), m_controller, SLOT(yankPak())); + connect(yank, &QAction::triggered, m_controller, &GameController::yankPak); m_gameActions.append(yank); m_gbaActions.append(yank); addControlledAction(emulationMenu, yank, "yank");

@@ -1073,7 +1096,7 @@ QAction* pause = new QAction(tr("&Pause"), emulationMenu);

pause->setChecked(false); pause->setCheckable(true); pause->setShortcut(tr("Ctrl+P")); - connect(pause, SIGNAL(triggered(bool)), m_controller, SLOT(setPaused(bool))); + connect(pause, &QAction::triggered, m_controller, &GameController::setPaused); connect(m_controller, &GameController::gamePaused, [this, pause]() { pause->setChecked(true); });

@@ -1083,7 +1106,7 @@ addControlledAction(emulationMenu, pause, "pause");

QAction* frameAdvance = new QAction(tr("&Next frame"), emulationMenu); frameAdvance->setShortcut(tr("Ctrl+N")); - connect(frameAdvance, SIGNAL(triggered()), m_controller, SLOT(frameAdvance())); + connect(frameAdvance, &QAction::triggered, m_controller, &GameController::frameAdvance); m_gameActions.append(frameAdvance); addControlledAction(emulationMenu, frameAdvance, "frameAdvance");

@@ -1123,7 +1146,7 @@ }, QKeySequence("`"), tr("Rewind (held)"), "holdRewind");

QAction* rewind = new QAction(tr("Re&wind"), emulationMenu); rewind->setShortcut(tr("~")); - connect(rewind, SIGNAL(triggered()), m_controller, SLOT(rewind())); + connect(rewind, &QAction::triggered, m_controller, &GameController::rewind); m_gameActions.append(rewind); m_nonMpActions.append(rewind); addControlledAction(emulationMenu, rewind, "rewind");

@@ -1140,14 +1163,14 @@

ConfigOption* videoSync = m_config->addOption("videoSync"); videoSync->addBoolean(tr("Sync to &video"), emulationMenu); videoSync->connect([this](const QVariant& value) { - reloadConfig(); + m_controller->setVideoSync(value.toBool()); }, this); m_config->updateOption("videoSync"); ConfigOption* audioSync = m_config->addOption("audioSync"); audioSync->addBoolean(tr("Sync to &audio"), emulationMenu); audioSync->connect([this](const QVariant& value) { - reloadConfig(); + m_controller->setAudioSync(value.toBool()); }, this); m_config->updateOption("audioSync");

@@ -1156,11 +1179,11 @@

QMenu* solarMenu = emulationMenu->addMenu(tr("Solar sensor")); m_inputModel->addMenu(solarMenu); QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu); - connect(solarIncrease, SIGNAL(triggered()), m_controller, SLOT(increaseLuminanceLevel())); + connect(solarIncrease, &QAction::triggered, m_controller, &GameController::increaseLuminanceLevel); addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel"); QAction* solarDecrease = new QAction(tr("Decrease solar level"), solarMenu); - connect(solarDecrease, SIGNAL(triggered()), m_controller, SLOT(decreaseLuminanceLevel())); + connect(solarDecrease, &QAction::triggered, m_controller, &GameController::decreaseLuminanceLevel); addControlledAction(solarMenu, solarDecrease, "decreaseLuminanceLevel"); QAction* maxSolar = new QAction(tr("Brightest solar level"), solarMenu);

@@ -1222,6 +1245,16 @@ m_display->lockAspectRatio(value.toBool());

}, this); m_config->updateOption("lockAspectRatio"); + ConfigOption* lockIntegerScaling = m_config->addOption("lockIntegerScaling"); + lockIntegerScaling->addBoolean(tr("Force integer scaling"), avMenu); + lockIntegerScaling->connect([this](const QVariant& value) { + m_display->lockIntegerScaling(value.toBool()); + if (m_controller->isLoaded()) { + m_screenWidget->setLockIntegerScaling(value.toBool()); + } + }, this); + m_config->updateOption("lockIntegerScaling"); + ConfigOption* resampleVideo = m_config->addOption("resampleVideo"); resampleVideo->addBoolean(tr("Bilinear filtering"), avMenu); resampleVideo->connect([this](const QVariant& value) {

@@ -1240,7 +1273,7 @@ }

m_config->updateOption("frameskip"); QAction* shaderView = new QAction(tr("Shader options..."), avMenu); - connect(shaderView, SIGNAL(triggered()), m_shaderView, SLOT(show())); + connect(shaderView, &QAction::triggered, m_shaderView, &QWidget::show); if (!m_display->supportsShaders()) { shaderView->setEnabled(false); }

@@ -1249,11 +1282,12 @@

avMenu->addSeparator(); ConfigOption* mute = m_config->addOption("mute"); - mute->addBoolean(tr("Mute"), avMenu); + QAction* muteAction = mute->addBoolean(tr("Mute"), avMenu); mute->connect([this](const QVariant& value) { reloadConfig(); }, this); m_config->updateOption("mute"); + addControlledAction(avMenu, muteAction, "mute"); QMenu* target = avMenu->addMenu(tr("FPS target")); ConfigOption* fpsTargetOption = m_config->addOption("fpsTarget");

@@ -1277,69 +1311,45 @@

#ifdef USE_PNG QAction* screenshot = new QAction(tr("Take &screenshot"), avMenu); screenshot->setShortcut(tr("F12")); - connect(screenshot, SIGNAL(triggered()), m_controller, SLOT(screenshot())); + connect(screenshot, &QAction::triggered, m_controller, &GameController::screenshot); m_gameActions.append(screenshot); addControlledAction(avMenu, screenshot, "screenshot"); #endif #ifdef USE_FFMPEG QAction* recordOutput = new QAction(tr("Record output..."), avMenu); - connect(recordOutput, SIGNAL(triggered()), this, SLOT(openVideoWindow())); + connect(recordOutput, &QAction::triggered, this, &Window::openVideoWindow); addControlledAction(avMenu, recordOutput, "recordOutput"); m_gameActions.append(recordOutput); #endif #ifdef USE_MAGICK QAction* recordGIF = new QAction(tr("Record GIF..."), avMenu); - connect(recordGIF, SIGNAL(triggered()), this, SLOT(openGIFWindow())); + connect(recordGIF, &QAction::triggered, this, &Window::openGIFWindow); addControlledAction(avMenu, recordGIF, "recordGIF"); #endif - avMenu->addSeparator(); - QMenu* videoLayers = avMenu->addMenu(tr("Video layers")); - m_inputModel->addMenu(videoLayers, avMenu); + QAction* recordVL = new QAction(tr("Record video log..."), avMenu); + connect(recordVL, &QAction::triggered, this, &Window::startVideoLog); + addControlledAction(avMenu, recordVL, "recordVL"); + m_gameActions.append(recordVL); - 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->setVideoLayerEnabled(i, enable); }); - addControlledAction(videoLayers, enableBg, QString("enableBG%0").arg(i)); - } + QAction* stopVL = new QAction(tr("Stop video log"), avMenu); + connect(stopVL, &QAction::triggered, m_controller, &GameController::endVideoLog); + addControlledAction(avMenu, stopVL, "stopVL"); + m_gameActions.append(stopVL); - QAction* enableObj = new QAction(tr("OBJ (sprites)"), videoLayers); - enableObj->setCheckable(true); - enableObj->setChecked(true); - connect(enableObj, &QAction::triggered, [this](bool enable) { m_controller->setVideoLayerEnabled(4, enable); }); - addControlledAction(videoLayers, enableObj, "enableOBJ"); - - QMenu* audioChannels = avMenu->addMenu(tr("Audio channels")); - m_inputModel->addMenu(audioChannels, avMenu); - - 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->setAudioChannelEnabled(i, enable); }); - 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->setAudioChannelEnabled(4, enable); }); - addControlledAction(audioChannels, enableChA, QString("enableChA")); + avMenu->addSeparator(); + m_videoLayers = avMenu->addMenu(tr("Video layers")); + m_inputModel->addMenu(m_videoLayers, avMenu); - QAction* enableChB = new QAction(tr("Channel B"), audioChannels); - enableChB->setCheckable(true); - enableChB->setChecked(true); - connect(enableChB, &QAction::triggered, [this, i](bool enable) { m_controller->setAudioChannelEnabled(5, enable); }); - addControlledAction(audioChannels, enableChB, QString("enableChB")); + m_audioChannels = avMenu->addMenu(tr("Audio channels")); + m_inputModel->addMenu(m_audioChannels, avMenu); QMenu* toolsMenu = menubar->addMenu(tr("&Tools")); m_inputModel->addMenu(toolsMenu); QAction* viewLogs = new QAction(tr("View &logs..."), toolsMenu); - connect(viewLogs, SIGNAL(triggered()), m_logView, SLOT(show())); + connect(viewLogs, &QAction::triggered, m_logView, &QWidget::show); addControlledAction(toolsMenu, viewLogs, "viewLogs"); QAction* overrides = new QAction(tr("Game &overrides..."), toolsMenu);

@@ -1363,13 +1373,13 @@ toolsMenu->addSeparator();

#ifdef USE_DEBUGGERS QAction* consoleWindow = new QAction(tr("Open debugger console..."), toolsMenu); - connect(consoleWindow, SIGNAL(triggered()), this, SLOT(consoleOpen())); + connect(consoleWindow, &QAction::triggered, this, &Window::consoleOpen); addControlledAction(toolsMenu, consoleWindow, "debuggerWindow"); #endif #ifdef USE_GDB_STUB QAction* gdbWindow = new QAction(tr("Start &GDB server..."), toolsMenu); - connect(gdbWindow, SIGNAL(triggered()), this, SLOT(gdbOpen())); + connect(gdbWindow, &QAction::triggered, this, &Window::gdbOpen); m_gbaActions.append(gdbWindow); addControlledAction(toolsMenu, gdbWindow, "gdbWindow"); #endif

@@ -1452,18 +1462,89 @@ ConfigOption* saveStateExtdata = m_config->addOption("saveStateExtdata");

saveStateExtdata->connect([this](const QVariant& value) { m_controller->setSaveStateExtdata(value.toInt()); }, this); + m_config->updateOption("saveStateExtdata"); ConfigOption* loadStateExtdata = m_config->addOption("loadStateExtdata"); loadStateExtdata->connect([this](const QVariant& value) { m_controller->setLoadStateExtdata(value.toInt()); }, this); + m_config->updateOption("loadStateExtdata"); + + ConfigOption* preload = m_config->addOption("preload"); + preload->connect([this](const QVariant& value) { + m_controller->setPreload(value.toBool()); + }, this); + m_config->updateOption("preload"); QAction* exitFullScreen = new QAction(tr("Exit fullscreen"), frameMenu); - connect(exitFullScreen, SIGNAL(triggered()), this, SLOT(exitFullScreen())); + connect(exitFullScreen, &QAction::triggered, this, &Window::exitFullScreen); exitFullScreen->setShortcut(QKeySequence("Esc")); addHiddenAction(frameMenu, exitFullScreen, "exitFullScreen"); - foreach (QAction* action, m_gameActions) { + QMenu* autofireMenu = new QMenu(tr("Autofire"), this); + m_inputModel->addMenu(autofireMenu); + + m_inputModel->addFunctions(autofireMenu, [this]() { + m_controller->setAutofire(GBA_KEY_A, true); + }, [this]() { + m_controller->setAutofire(GBA_KEY_A, false); + }, QKeySequence(), tr("Autofire A"), "autofireA"); + + m_inputModel->addFunctions(autofireMenu, [this]() { + m_controller->setAutofire(GBA_KEY_B, true); + }, [this]() { + m_controller->setAutofire(GBA_KEY_B, false); + }, QKeySequence(), tr("Autofire B"), "autofireB"); + + m_inputModel->addFunctions(autofireMenu, [this]() { + m_controller->setAutofire(GBA_KEY_L, true); + }, [this]() { + m_controller->setAutofire(GBA_KEY_L, false); + }, QKeySequence(), tr("Autofire L"), "autofireL"); + + m_inputModel->addFunctions(autofireMenu, [this]() { + m_controller->setAutofire(GBA_KEY_R, true); + }, [this]() { + m_controller->setAutofire(GBA_KEY_R, false); + }, QKeySequence(), tr("Autofire R"), "autofireR"); + + m_inputModel->addFunctions(autofireMenu, [this]() { + m_controller->setAutofire(GBA_KEY_START, true); + }, [this]() { + m_controller->setAutofire(GBA_KEY_START, false); + }, QKeySequence(), tr("Autofire Start"), "autofireStart"); + + m_inputModel->addFunctions(autofireMenu, [this]() { + m_controller->setAutofire(GBA_KEY_SELECT, true); + }, [this]() { + m_controller->setAutofire(GBA_KEY_SELECT, false); + }, QKeySequence(), tr("Autofire Select"), "autofireSelect"); + + m_inputModel->addFunctions(autofireMenu, [this]() { + m_controller->setAutofire(GBA_KEY_UP, true); + }, [this]() { + m_controller->setAutofire(GBA_KEY_UP, false); + }, QKeySequence(), tr("Autofire Up"), "autofireUp"); + + m_inputModel->addFunctions(autofireMenu, [this]() { + m_controller->setAutofire(GBA_KEY_RIGHT, true); + }, [this]() { + m_controller->setAutofire(GBA_KEY_RIGHT, false); + }, QKeySequence(), tr("Autofire Right"), "autofireRight"); + + m_inputModel->addFunctions(autofireMenu, [this]() { + m_controller->setAutofire(GBA_KEY_DOWN, true); + }, [this]() { + m_controller->setAutofire(GBA_KEY_DOWN, false); + }, QKeySequence(), tr("Autofire Down"), "autofireDown"); + + m_inputModel->addFunctions(autofireMenu, [this]() { + m_controller->setAutofire(GBA_KEY_LEFT, true); + }, [this]() { + m_controller->setAutofire(GBA_KEY_LEFT, false); + }, QKeySequence(), tr("Autofire Left"), "autofireLeft"); + + for (QAction* action : m_gameActions) { action->setDisabled(true); } }

@@ -1500,7 +1581,7 @@ }

m_mruMenu->clear(); int i = 0; for (const QString& file : m_mruFiles) { - QAction* item = new QAction(file, m_mruMenu); + QAction* item = new QAction(QDir::toNativeSeparators(file).replace("&", "&&"), m_mruMenu); item->setShortcut(QString("Ctrl+%1").arg(i)); connect(item, &QAction::triggered, [this, file]() { m_controller->loadGame(file); }); m_mruMenu->addAction(item);

@@ -1558,6 +1639,10 @@ m_aspectWidth = width;

m_aspectHeight = height; } +void WindowBackground::setLockIntegerScaling(bool lock) { + m_lockIntegerScaling = lock; +} + void WindowBackground::paintEvent(QPaintEvent*) { const QPixmap* logo = pixmap(); if (!logo) {

@@ -1572,6 +1657,10 @@ if (ds.width() * m_aspectHeight > ds.height() * m_aspectWidth) {

ds.setWidth(ds.height() * m_aspectWidth / m_aspectHeight); } else if (ds.width() * m_aspectHeight < ds.height() * m_aspectWidth) { ds.setHeight(ds.width() * m_aspectHeight / m_aspectWidth); + } + if (m_lockIntegerScaling) { + ds.setWidth(ds.width() - ds.width() % m_aspectWidth); + ds.setHeight(ds.height() - ds.height() % m_aspectHeight); } QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2); QRect full(origin, ds);
M src/platform/qt/Window.hsrc/platform/qt/Window.h

@@ -29,7 +29,7 @@ class GameController;

class GDBController; class GIFView; class InputModel; -class LibraryView; +class LibraryController; class LogView; class ShaderSelector; class VideoView;

@@ -48,6 +48,8 @@ void setConfig(ConfigController*);

void argumentsPassed(mArguments*); void resizeFrame(const QSize& size); + + void updateMultiplayerStatus(bool canOpenAnother) { m_multiWindow->setEnabled(canOpenAnother); } signals: void startDrawing(mCoreThread*);

@@ -80,6 +82,8 @@ void exportSharkport();

void openSettingsWindow(); void openAboutScreen(); + + void startVideoLog(); #ifdef USE_DEBUGGERS void consoleOpen();

@@ -156,44 +160,47 @@ QList<QAction*> m_nonMpActions;

#ifdef M_CORE_GBA QList<QAction*> m_gbaActions; #endif + QAction* m_multiWindow; QMap<int, QAction*> m_frameSizes; - LogController m_log; + LogController m_log{0}; LogView* m_logView; #ifdef USE_DEBUGGERS - DebuggerConsoleController* m_console; + DebuggerConsoleController* m_console = nullptr; #endif - LoadSaveState* m_stateWindow; + LoadSaveState* m_stateWindow = nullptr; WindowBackground* m_screenWidget; - QPixmap m_logo; + QPixmap m_logo{":/res/mgba-1024.png"}; ConfigController* m_config; InputModel* m_inputModel; InputController m_inputController; QList<QDateTime> m_frameList; QTimer m_fpsTimer; QList<QString> m_mruFiles; - QMenu* m_mruMenu; + QMenu* m_mruMenu = nullptr; + QMenu* m_videoLayers; + QMenu* m_audioChannels; ShaderSelector* m_shaderView; - bool m_fullscreenOnStart; + bool m_fullscreenOnStart = false; QTimer m_focusCheck; - bool m_autoresume; - bool m_wasOpened; + bool m_autoresume = false; + bool m_wasOpened = false; bool m_hitUnimplementedBiosCall; #ifdef USE_FFMPEG - VideoView* m_videoView; + VideoView* m_videoView = nullptr; #endif #ifdef USE_MAGICK - GIFView* m_gifView; + GIFView* m_gifView = nullptr; #endif #ifdef USE_GDB_STUB - GDBController* m_gdbController; + GDBController* m_gdbController = nullptr; #endif #ifdef USE_SQLITE3 - LibraryView* m_libraryView; + LibraryController* m_libraryView; #endif };

@@ -206,6 +213,7 @@

void setSizeHint(const QSize& size); virtual QSize sizeHint() const override; void setLockAspectRatio(int width, int height); + void setLockIntegerScaling(bool lock); protected: virtual void paintEvent(QPaintEvent*) override;

@@ -214,6 +222,7 @@ private:

QSize m_sizeHint; int m_aspectWidth; int m_aspectHeight; + bool m_lockIntegerScaling; }; }
A src/platform/qt/library/LibraryController.cpp

@@ -0,0 +1,193 @@

+/* Copyright (c) 2014-2017 waddlesplash + * + * 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 "LibraryController.h" + +#include "../GBAApp.h" +#include "LibraryGrid.h" +#include "LibraryTree.h" + +namespace QGBA { + +LibraryEntry::LibraryEntry(mLibraryEntry* entry) + : entry(entry) + , m_fullpath(QString("%1/%2").arg(entry->base, entry->filename)) +{ +} + +void AbstractGameList::addEntries(QList<LibraryEntryRef> items) { + for (LibraryEntryRef o : items) { + addEntry(o); + } +} +void AbstractGameList::removeEntries(QList<LibraryEntryRef> items) { + for (LibraryEntryRef o : items) { + addEntry(o); + } +} + +LibraryLoaderThread::LibraryLoaderThread(QObject* parent) + : QThread(parent) +{ +} + +void LibraryLoaderThread::run() { + mLibraryLoadDirectory(m_library, m_directory.toUtf8().constData()); + m_directory = QString(); +} + +LibraryController::LibraryController(QWidget* parent, const QString& path, ConfigController* config) + : QStackedWidget(parent) + , m_config(config) +{ + mLibraryListingInit(&m_listing, 0); + + if (!path.isNull()) { + m_library = mLibraryLoad(path.toUtf8().constData()); + } else { + m_library = mLibraryCreateEmpty(); + } + + mLibraryAttachGameDB(m_library, GBAApp::app()->gameDB()); + + m_libraryTree = new LibraryTree(this); + addWidget(m_libraryTree->widget()); + + m_libraryGrid = new LibraryGrid(this); + addWidget(m_libraryGrid->widget()); + + connect(&m_loaderThread, &QThread::finished, this, &LibraryController::refresh, Qt::QueuedConnection); + + setViewStyle(LibraryStyle::STYLE_LIST); + refresh(); +} + +LibraryController::~LibraryController() { + mLibraryListingDeinit(&m_listing); + + if (m_loaderThread.isRunning()) { + m_loaderThread.wait(); + } + if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { + m_library = m_loaderThread.m_library; + m_loaderThread.m_library = nullptr; + } + if (m_library) { + mLibraryDestroy(m_library); + } +} + +void LibraryController::setViewStyle(LibraryStyle newStyle) { + m_currentStyle = newStyle; + + AbstractGameList* newCurrentList = nullptr; + if (newStyle == LibraryStyle::STYLE_LIST || newStyle == LibraryStyle::STYLE_TREE) { + newCurrentList = m_libraryTree; + } else { + newCurrentList = m_libraryGrid; + } + newCurrentList->selectEntry(selectedEntry()); + newCurrentList->setViewStyle(newStyle); + setCurrentWidget(newCurrentList->widget()); + m_currentList = newCurrentList; +} + +void LibraryController::selectEntry(LibraryEntryRef entry) { + if (!m_currentList) { + return; + } + m_currentList->selectEntry(entry); +} + +LibraryEntryRef LibraryController::selectedEntry() { + if (!m_currentList) { + return LibraryEntryRef(); + } + return m_currentList->selectedEntry(); +} + +VFile* LibraryController::selectedVFile() { + LibraryEntryRef entry = selectedEntry(); + if (entry) { + return mLibraryOpenVFile(m_library, entry->entry); + } else { + return nullptr; + } +} + +QPair<QString, QString> LibraryController::selectedPath() { + LibraryEntryRef e = selectedEntry(); + return e ? qMakePair(e->base(), e->filename()) : qMakePair<QString, QString>("", ""); +} + +void LibraryController::addDirectory(const QString& dir) { + m_loaderThread.m_directory = dir; + m_loaderThread.m_library = m_library; + // The m_loaderThread temporarily owns the library + m_library = nullptr; + m_loaderThread.start(); +} + +void LibraryController::refresh() { + if (!m_library) { + if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { + m_library = m_loaderThread.m_library; + m_loaderThread.m_library = nullptr; + } else { + return; + } + } + + setDisabled(true); + + QStringList allEntries; + QList<LibraryEntryRef> newEntries; + + mLibraryListingClear(&m_listing); + mLibraryGetEntries(m_library, &m_listing, 0, 0, nullptr); + for (size_t i = 0; i < mLibraryListingSize(&m_listing); i++) { + mLibraryEntry* entry = mLibraryListingGetPointer(&m_listing, i); + QString fullpath = QString("%1/%2").arg(entry->base, entry->filename); + if (m_entries.contains(fullpath)) { + m_entries.value(fullpath)->entry = entry; + } else { + LibraryEntryRef libentry = std::make_shared<LibraryEntry>(entry); + m_entries.insert(fullpath, libentry); + newEntries.append(libentry); + } + allEntries.append(fullpath); + } + + // Check for entries that were removed + QList<LibraryEntryRef> removedEntries; + for (QString& path : m_entries.keys()) { + if (!allEntries.contains(path)) { + removedEntries.append(m_entries.value(path)); + m_entries.remove(path); + } + } + + m_libraryTree->addEntries(newEntries); + m_libraryGrid->addEntries(newEntries); + + m_libraryTree->removeEntries(removedEntries); + m_libraryGrid->removeEntries(removedEntries); + + setDisabled(false); + selectLastBootedGame(); + emit doneLoading(); +} + +void LibraryController::selectLastBootedGame() { + if (!m_config) { + return; + } + const QString lastfile = m_config->getMRU().first(); + if (m_entries.contains(lastfile)) { + selectEntry(m_entries.value(lastfile)); + } +} + +}
A src/platform/qt/library/LibraryController.h

@@ -0,0 +1,126 @@

+/* Copyright (c) 2014-2017 waddlesplash + * + * 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_LIBRARY_CONTROLLER +#define QGBA_LIBRARY_CONTROLLER + +#include <memory> + +#include <QList> +#include <QMap> +#include <QThread> +#include <QStackedWidget> + +#include <mgba/core/library.h> + +namespace QGBA { + +// Predefinitions +class LibraryGrid; +class LibraryTree; +class ConfigController; + +enum class LibraryStyle { + STYLE_LIST = 0, + STYLE_TREE, + STYLE_GRID, + STYLE_ICON +}; + +class LibraryEntry final { +public: + LibraryEntry(mLibraryEntry* entry); + + QString displayTitle() const { return title().isNull() ? filename() : title(); } + + QString base() const { return QString(entry->base); } + QString filename() const { return QString(entry->filename); } + QString fullpath() const { return m_fullpath; } + QString title() const { return QString(entry->title); } + QByteArray internalTitle() const { return QByteArray(entry->internalTitle); } + QByteArray internalCode() const { return QByteArray(entry->internalCode); } + mPlatform platform() const { return entry->platform; } + size_t filesize() const { return entry->filesize; } + uint32_t crc32() const { return entry->crc32; } + + const mLibraryEntry* entry; +private: + const QString m_fullpath; +}; +typedef std::shared_ptr<LibraryEntry> LibraryEntryRef; + +class AbstractGameList { +public: + virtual LibraryEntryRef selectedEntry() = 0; + virtual void selectEntry(LibraryEntryRef game) = 0; + + virtual void setViewStyle(LibraryStyle newStyle) = 0; + + virtual void addEntry(LibraryEntryRef item) = 0; + virtual void addEntries(QList<LibraryEntryRef> items); + + virtual void removeEntry(LibraryEntryRef item) = 0; + virtual void removeEntries(QList<LibraryEntryRef> items); + + virtual QWidget* widget() = 0; +}; + +class LibraryLoaderThread final : public QThread { +Q_OBJECT + +public: + LibraryLoaderThread(QObject* parent = nullptr); + + mLibrary* m_library = nullptr; + QString m_directory; + +protected: + virtual void run() override; +}; + +class LibraryController final : public QStackedWidget { +Q_OBJECT + +public: + LibraryController(QWidget* parent = nullptr, const QString& path = QString(), + ConfigController* config = nullptr); + ~LibraryController(); + + LibraryStyle viewStyle() const { return m_currentStyle; } + void setViewStyle(LibraryStyle newStyle); + + void selectEntry(LibraryEntryRef entry); + LibraryEntryRef selectedEntry(); + VFile* selectedVFile(); + QPair<QString, QString> selectedPath(); + + void selectLastBootedGame(); + + void addDirectory(const QString& dir); + +signals: + void startGame(); + void doneLoading(); + +private slots: + void refresh(); + +private: + ConfigController* m_config = nullptr; + LibraryLoaderThread m_loaderThread; + mLibrary* m_library = nullptr; + mLibraryListing m_listing; + QMap<QString, LibraryEntryRef> m_entries; + + LibraryStyle m_currentStyle; + AbstractGameList* m_currentList = nullptr; + + LibraryGrid* m_libraryGrid = nullptr; + LibraryTree* m_libraryTree = nullptr; +}; + +} + +#endif
A src/platform/qt/library/LibraryGrid.cpp

@@ -0,0 +1,79 @@

+/* Copyright (c) 2014-2017 waddlesplash + * + * 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 "LibraryGrid.h" + +namespace QGBA { + +LibraryGrid::LibraryGrid(LibraryController* parent) + : m_widget(new QListWidget(parent)) +{ + m_widget->setObjectName("LibraryGrid"); + m_widget->setWrapping(true); + m_widget->setResizeMode(QListView::Adjust); + m_widget->setUniformItemSizes(true); + setViewStyle(LibraryStyle::STYLE_GRID); + + QObject::connect(m_widget, &QListWidget::itemActivated, parent, &LibraryController::startGame); +} + +LibraryGrid::~LibraryGrid() { + delete m_widget; +} + +LibraryEntryRef LibraryGrid::selectedEntry() { + if (!m_widget->selectedItems().empty()) { + return m_items.key(m_widget->selectedItems().at(0)); + } else { + return LibraryEntryRef(); + } +} + +void LibraryGrid::selectEntry(LibraryEntryRef game) { + if (!game) { + return; + } + if (!m_widget->selectedItems().empty()) { + m_widget->selectedItems().at(0)->setSelected(false); + } + m_items.value(game)->setSelected(true); +} + +void LibraryGrid::setViewStyle(LibraryStyle newStyle) { + if (newStyle == LibraryStyle::STYLE_GRID) { + m_currentStyle = LibraryStyle::STYLE_GRID; + m_widget->setIconSize(QSize(GRID_BANNER_WIDTH, GRID_BANNER_HEIGHT)); + m_widget->setViewMode(QListView::IconMode); + } else { + m_currentStyle = LibraryStyle::STYLE_ICON; + m_widget->setIconSize(QSize(ICON_BANNER_WIDTH, ICON_BANNER_HEIGHT)); + m_widget->setViewMode(QListView::ListMode); + } + + // QListView resets this when you change the view mode, so let's set it again + m_widget->setDragEnabled(false); +} + +void LibraryGrid::addEntry(LibraryEntryRef item) { + if (m_items.contains(item)) { + return; + } + + QListWidgetItem* i = new QListWidgetItem; + i->setText(item->displayTitle()); + + m_widget->addItem(i); + m_items.insert(item, i); +} + +void LibraryGrid::removeEntry(LibraryEntryRef entry) { + if (!m_items.contains(entry)) { + return; + } + + delete m_items.take(entry); +} + +}
A src/platform/qt/library/LibraryGrid.h

@@ -0,0 +1,50 @@

+/* Copyright (c) 2014-2017 waddlesplash + * + * 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_LIBRARY_GRID +#define QGBA_LIBRARY_GRID + +#include <QListWidget> + +#include "LibraryController.h" + +namespace QGBA { + +class LibraryGrid final : public AbstractGameList { +public: + explicit LibraryGrid(LibraryController* parent = nullptr); + ~LibraryGrid(); + + // AbstractGameList stuff + virtual LibraryEntryRef selectedEntry() override; + virtual void selectEntry(LibraryEntryRef game) override; + + virtual void setViewStyle(LibraryStyle newStyle) override; + + virtual void addEntry(LibraryEntryRef item) override; + virtual void removeEntry(LibraryEntryRef entry) override; + + virtual QWidget* widget() override { return m_widget; } + +signals: + void startGame(); + +private: + QListWidget* m_widget; + + // Game banner image size + const quint32 GRID_BANNER_WIDTH = 320; + const quint32 GRID_BANNER_HEIGHT = 240; + + const quint32 ICON_BANNER_WIDTH = 64; + const quint32 ICON_BANNER_HEIGHT = 64; + + QMap<LibraryEntryRef, QListWidgetItem*> m_items; + LibraryStyle m_currentStyle; +}; + +} + +#endif
A src/platform/qt/library/LibraryTree.cpp

@@ -0,0 +1,179 @@

+/* Copyright (c) 2014-2017 waddlesplash + * + * 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 "LibraryTree.h" + +#include "utils.h" + +#include <QApplication> +#include <QDir> + +namespace QGBA { + +class TreeWidgetItem : public QTreeWidgetItem { +public: + TreeWidgetItem(QTreeWidget* parent = nullptr) : QTreeWidgetItem(parent) {} + void setFilesize(size_t size); + + virtual bool operator<(const QTreeWidgetItem& other) const override; +protected: + size_t m_size = 0; +}; + +void TreeWidgetItem::setFilesize(size_t size) { + m_size = size; + setText(LibraryTree::COL_SIZE, niceSizeFormat(size)); +} + +bool TreeWidgetItem::operator<(const QTreeWidgetItem& other) const { + const int column = treeWidget()->sortColumn(); + return ((column == LibraryTree::COL_SIZE) ? + m_size < dynamic_cast<const TreeWidgetItem*>(&other)->m_size : + QTreeWidgetItem::operator<(other)); +} + +LibraryTree::LibraryTree(LibraryController* parent) + : m_widget(new QTreeWidget(parent)) + , m_controller(parent) +{ + m_widget->setObjectName("LibraryTree"); + m_widget->setSortingEnabled(true); + m_widget->setAlternatingRowColors(true); + + QTreeWidgetItem* header = new QTreeWidgetItem({ + QApplication::translate("LibraryTree", "Name", nullptr), + QApplication::translate("LibraryTree", "Location", nullptr), + QApplication::translate("LibraryTree", "Platform", nullptr), + QApplication::translate("LibraryTree", "Size", nullptr), + QApplication::translate("LibraryTree", "CRC32", nullptr), + }); + header->setTextAlignment(3, Qt::AlignTrailing | Qt::AlignVCenter); + m_widget->setHeaderItem(header); + + setViewStyle(LibraryStyle::STYLE_TREE); + m_widget->sortByColumn(COL_NAME, Qt::AscendingOrder); + + QObject::connect(m_widget, &QTreeWidget::itemActivated, [this](QTreeWidgetItem* item, int) -> void { + if (!m_pathNodes.values().contains(item)) { + emit m_controller->startGame(); + } + }); +} + +void LibraryTree::resizeAllCols() { + for (int i = 0; i < m_widget->columnCount(); i++) { + m_widget->resizeColumnToContents(i); + } +} + +LibraryEntryRef LibraryTree::selectedEntry() { + if (!m_widget->selectedItems().empty()) { + return m_items.key(m_widget->selectedItems().at(0)); + } else { + return LibraryEntryRef(); + } +} + +void LibraryTree::selectEntry(LibraryEntryRef game) { + if (!game) { + return; + } + if (!m_widget->selectedItems().empty()) { + m_widget->selectedItems().at(0)->setSelected(false); + } + m_items.value(game)->setSelected(true); +} + +void LibraryTree::setViewStyle(LibraryStyle newStyle) { + if (newStyle == LibraryStyle::STYLE_LIST) { + m_currentStyle = LibraryStyle::STYLE_LIST; + m_widget->setIndentation(0); + rebuildTree(); + } else { + m_currentStyle = LibraryStyle::STYLE_TREE; + m_widget->setIndentation(20); + rebuildTree(); + } +} + +void LibraryTree::addEntries(QList<LibraryEntryRef> items) { + m_deferredTreeRebuild = true; + AbstractGameList::addEntries(items); + m_deferredTreeRebuild = false; + rebuildTree(); +} + +void LibraryTree::addEntry(LibraryEntryRef item) { + if (m_items.contains(item)) { + return; + } + + QString folder = item->base(); + if (!m_pathNodes.contains(folder)) { + QTreeWidgetItem* i = new TreeWidgetItem; + i->setText(0, folder.section("/", -1)); + m_pathNodes.insert(folder, i); + if (m_currentStyle == LibraryStyle::STYLE_TREE) { + m_widget->addTopLevelItem(i); + } + } + + TreeWidgetItem* i = new TreeWidgetItem; + i->setText(COL_NAME, item->displayTitle()); + i->setText(COL_LOCATION, QDir::toNativeSeparators(item->base())); + i->setText(COL_PLATFORM, nicePlatformFormat(item->platform())); + i->setFilesize(item->filesize()); + i->setTextAlignment(COL_SIZE, Qt::AlignRight); + i->setText(COL_CRC32, QString("%0").arg(item->crc32(), 8, 16, QChar('0'))); + m_items.insert(item, i); + + rebuildTree(); +} + +void LibraryTree::removeEntry(LibraryEntryRef item) { + if (!m_items.contains(item)) { + return; + } + delete m_items.take(item); +} + +void LibraryTree::rebuildTree() { + if (m_deferredTreeRebuild) { + return; + } + + LibraryEntryRef currentGame = selectedEntry(); + + int count = m_widget->topLevelItemCount(); + for (int a = 0; a < count; a++) { + m_widget->takeTopLevelItem(0); + } + + for (QTreeWidgetItem* i : m_pathNodes.values()) { + count = i->childCount(); + for (int a = 0; a < count; a++) { + i->takeChild(0); + } + } + + if (m_currentStyle == LibraryStyle::STYLE_TREE) { + for (QTreeWidgetItem* i : m_pathNodes.values()) { + m_widget->addTopLevelItem(i); + } + for (QTreeWidgetItem* i : m_items.values()) { + m_pathNodes.value(m_items.key(i)->base())->addChild(i); + } + } else { + for (QTreeWidgetItem* i : m_items.values()) { + m_widget->addTopLevelItem(i); + } + } + + m_widget->expandAll(); + resizeAllCols(); + selectEntry(currentGame); +} + +}
A src/platform/qt/library/LibraryTree.h

@@ -0,0 +1,57 @@

+/* Copyright (c) 2014-2017 waddlesplash + * + * 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_LIBRARY_TREE +#define QGBA_LIBRARY_TREE + +#include <QTreeWidget> + +#include "LibraryController.h" + +namespace QGBA { + +class LibraryTree final : public AbstractGameList { + +public: + enum Columns { + COL_NAME = 0, + COL_LOCATION = 1, + COL_PLATFORM = 2, + COL_SIZE = 3, + COL_CRC32 = 4, + }; + + explicit LibraryTree(LibraryController* parent = nullptr); + ~LibraryTree(); + + // AbstractGameList stuff + virtual LibraryEntryRef selectedEntry() override; + virtual void selectEntry(LibraryEntryRef game) override; + + virtual void setViewStyle(LibraryStyle newStyle) override; + + virtual void addEntries(QList<LibraryEntryRef> items) override; + virtual void addEntry(LibraryEntryRef item) override; + virtual void removeEntry(LibraryEntryRef item) override; + + virtual QWidget* widget() override { return m_widget; } + +private: + QTreeWidget* m_widget; + LibraryStyle m_currentStyle; + + LibraryController* m_controller; + + bool m_deferredTreeRebuild = false; + QMap<LibraryEntryRef, QTreeWidgetItem*> m_items; + QMap<QString, QTreeWidgetItem*> m_pathNodes; + + void rebuildTree(); + void resizeAllCols(); +}; + +} + +#endif
M src/platform/qt/main.cppsrc/platform/qt/main.cpp

@@ -3,6 +3,10 @@ *

* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This must be defined before anything else is included. +#define SDL_MAIN_HANDLED + #include "GBAApp.h" #include "Window.h"

@@ -22,6 +26,9 @@ #endif

#endif int main(int argc, char* argv[]) { +#ifdef BUILD_SDL + SDL_SetMainReady(); +#endif QGBA::GBAApp application(argc, argv); QLocale locale = QLocale::system();
M src/platform/qt/ts.cmakesrc/platform/qt/ts.cmake

@@ -1,8 +1,6 @@

file(GLOB TRANSLATION_FILES "${QM_BASE}/*.qm") file(WRITE ${TRANSLATION_QRC} "<RCC>\n\t<qresource prefix=\"/translations/\">\n") -message(STATUS ${TRANSLATION_FILES}) foreach(TS ${TRANSLATION_FILES}) - message(STATUS ${TS}) get_filename_component(TS_BASE "${TS}" NAME) file(APPEND ${TRANSLATION_QRC} "\t\t<file>${TS_BASE}</file>\n") endforeach()
M src/platform/qt/ts/mgba-de.tssrc/platform/qt/ts/mgba-de.ts

@@ -302,11 +302,31 @@ <translation>B</translation>

</message> </context> <context> - <name>LibraryView</name> + <name>LibraryTree</name> <message> - <location filename="../LibraryView.ui" line="14"/> - <source>Library</source> - <translation>Bibliothek</translation> + <location filename="../library/LibraryTree.cpp" line="46"/> + <source>Name</source> + <translation>Name</translation> + </message> + <message> + <location filename="../library/LibraryTree.cpp" line="47"/> + <source>Location</source> + <translation>Ort</translation> + </message> + <message> + <location filename="../library/LibraryTree.cpp" line="48"/> + <source>Platform</source> + <translation>Plattform</translation> + </message> + <message> + <location filename="../library/LibraryTree.cpp" line="49"/> + <source>Size</source> + <translation>Größe</translation> + </message> + <message> + <location filename="../library/LibraryTree.cpp" line="50"/> + <source>CRC32</source> + <translation>CRC32</translation> </message> </context> <context>

@@ -903,14 +923,14 @@ </context>

<context> <name>QGBA::AssetTile</name> <message> - <location filename="../AssetTile.cpp" line="107"/> + <location filename="../AssetTile.cpp" line="103"/> <source>%0%1%2</source> <translation>%0%1%2</translation> </message> <message> - <location filename="../AssetTile.cpp" line="136"/> - <location filename="../AssetTile.cpp" line="137"/> - <location filename="../AssetTile.cpp" line="138"/> + <location filename="../AssetTile.cpp" line="132"/> + <location filename="../AssetTile.cpp" line="133"/> + <location filename="../AssetTile.cpp" line="134"/> <source>0x%0 (%1)</source> <translation>0x%0 (%1)</translation> </message>

@@ -961,22 +981,22 @@ </context>

<context> <name>QGBA::GBAKeyEditor</name> <message> - <location filename="../GBAKeyEditor.cpp" line="68"/> + <location filename="../GBAKeyEditor.cpp" line="67"/> <source>Clear Button</source> <translation>Button löschen</translation> </message> <message> - <location filename="../GBAKeyEditor.cpp" line="80"/> + <location filename="../GBAKeyEditor.cpp" line="79"/> <source>Clear Analog</source> <translation>Analog löschen</translation> </message> <message> - <location filename="../GBAKeyEditor.cpp" line="91"/> + <location filename="../GBAKeyEditor.cpp" line="90"/> <source>Refresh</source> <translation>Aktualisieren</translation> </message> <message> - <location filename="../GBAKeyEditor.cpp" line="101"/> + <location filename="../GBAKeyEditor.cpp" line="100"/> <source>Set all</source> <translation>Alle belegen</translation> </message>

@@ -1027,17 +1047,17 @@ </context>

<context> <name>QGBA::GIFView</name> <message> - <location filename="../GIFView.cpp" line="45"/> + <location filename="../GIFView.cpp" line="46"/> <source>Failed to open output GIF file: %1</source> <translation>Fehler beim Öffnen der Ausgabe-GIF-Datei: %1</translation> </message> <message> - <location filename="../GIFView.cpp" line="63"/> + <location filename="../GIFView.cpp" line="64"/> <source>Select output file</source> <translation>Ausgabedatei auswählen</translation> </message> <message> - <location filename="../GIFView.cpp" line="63"/> + <location filename="../GIFView.cpp" line="64"/> <source>Graphics Interchange Format (*.gif)</source> <translation>Graphics Interchange Format (*.gif)</translation> </message>

@@ -1045,28 +1065,28 @@ </context>

<context> <name>QGBA::GameController</name> <message> - <location filename="../GameController.cpp" line="397"/> - <location filename="../GameController.cpp" line="572"/> + <location filename="../GameController.cpp" line="350"/> + <location filename="../GameController.cpp" line="534"/> <source>Failed to open game file: %1</source> <translation>Fehler beim Öffnen der Spieldatei: %1</translation> </message> <message> - <location filename="../GameController.cpp" line="544"/> + <location filename="../GameController.cpp" line="506"/> <source>Failed to open save file: %1</source> <translation>Fehler beim Öffnen der Speicherdatei: %1</translation> </message> <message> - <location filename="../GameController.cpp" line="601"/> + <location filename="../GameController.cpp" line="563"/> <source>Failed to open snapshot file for reading: %1</source> <translation>Konnte Snapshot-Datei %1 nicht zum Lesen öffnen</translation> </message> <message> - <location filename="../GameController.cpp" line="621"/> + <location filename="../GameController.cpp" line="583"/> <source>Failed to open snapshot file for writing: %1</source> <translation>Konnte Snapshot-Datei %1 nicht zum Schreiben öffnen</translation> </message> <message> - <location filename="../GameController.cpp" line="916"/> + <location filename="../GameController.cpp" line="848"/> <source>Failed to start audio processor</source> <translation>Fehler beim Starten des Audio-Prozessors</translation> </message>

@@ -1265,7 +1285,7 @@ <message>

<location filename="../IOViewer.cpp" line="95"/> <location filename="../IOViewer.cpp" line="105"/> <source>Overflow wraps</source> - <translation type="unfinished">Umbrüche</translation> + <translation>Umbrüche</translation> </message> <message> <location filename="../IOViewer.cpp" line="110"/>

@@ -1309,7 +1329,7 @@ <location filename="../IOViewer.cpp" line="186"/>

<location filename="../IOViewer.cpp" line="191"/> <location filename="../IOViewer.cpp" line="196"/> <source>Integer part</source> - <translation type="unfinished">Ganzzahl-Anteil</translation> + <translation>Ganzzahl-Anteil</translation> </message> <message> <location filename="../IOViewer.cpp" line="163"/>

@@ -1317,7 +1337,7 @@ <location filename="../IOViewer.cpp" line="172"/>

<location filename="../IOViewer.cpp" line="201"/> <location filename="../IOViewer.cpp" line="210"/> <source>Integer part (bottom)</source> - <translation type="unfinished">Ganzzahl-Anteil (unten)</translation> + <translation>Ganzzahl-Anteil (unten)</translation> </message> <message> <location filename="../IOViewer.cpp" line="167"/>

@@ -1325,7 +1345,7 @@ <location filename="../IOViewer.cpp" line="176"/>

<location filename="../IOViewer.cpp" line="205"/> <location filename="../IOViewer.cpp" line="214"/> <source>Integer part (top)</source> - <translation type="unfinished">Ganzzahl-Anteil (oben)</translation> + <translation>Ganzzahl-Anteil (oben)</translation> </message> <message> <location filename="../IOViewer.cpp" line="218"/>

@@ -1414,32 +1434,32 @@ </message>

<message> <location filename="../IOViewer.cpp" line="253"/> <source>Outside window enable BG 0</source> - <translation type="unfinished">Äußeres Fenster: aktiviere BG 0</translation> + <translation>Äußeres Fenster: aktiviere BG 0</translation> </message> <message> <location filename="../IOViewer.cpp" line="254"/> <source>Outside window enable BG 1</source> - <translation type="unfinished">Äußeres Fenster: aktiviere BG 1</translation> + <translation>Äußeres Fenster: aktiviere BG 1</translation> </message> <message> <location filename="../IOViewer.cpp" line="255"/> <source>Outside window enable BG 2</source> - <translation type="unfinished">Äußeres Fenster: aktiviere BG 2</translation> + <translation>Äußeres Fenster: aktiviere BG 2</translation> </message> <message> <location filename="../IOViewer.cpp" line="256"/> <source>Outside window enable BG 3</source> - <translation type="unfinished">Äußeres Fenster: aktiviere BG 3</translation> + <translation>Äußeres Fenster: aktiviere BG 3</translation> </message> <message> <location filename="../IOViewer.cpp" line="257"/> <source>Outside window enable OBJ</source> - <translation type="unfinished">Äußeres Fenster: aktiviere OBJ</translation> + <translation>Äußeres Fenster: aktiviere OBJ</translation> </message> <message> <location filename="../IOViewer.cpp" line="258"/> <source>Outside window enable blend</source> - <translation type="unfinished">Äußeres Fenster: aktiviere Überblendung</translation> + <translation>Äußeres Fenster: aktiviere Überblendung</translation> </message> <message> <location filename="../IOViewer.cpp" line="259"/>

@@ -2442,61 +2462,13 @@ </context>

<context> <name>QGBA::KeyEditor</name> <message> - <location filename="../KeyEditor.cpp" line="37"/> - <location filename="../KeyEditor.cpp" line="211"/> + <location filename="../KeyEditor.cpp" line="33"/> + <location filename="../KeyEditor.cpp" line="207"/> <source>---</source> <translation>---</translation> </message> </context> <context> - <name>QGBA::LibraryModel</name> - <message> - <location filename="../LibraryModel.cpp" line="24"/> - <source>Name</source> - <translation>Name</translation> - </message> - <message> - <location filename="../LibraryModel.cpp" line="33"/> - <source>Filename</source> - <translation>Dateiname</translation> - </message> - <message> - <location filename="../LibraryModel.cpp" line="39"/> - <source>Size</source> - <translation>Größe</translation> - </message> - <message> - <location filename="../LibraryModel.cpp" line="56"/> - <source>Platform</source> - <translation>Plattform</translation> - </message> - <message> - <location filename="../LibraryModel.cpp" line="62"/> - <source>GBA</source> - <translation>GBA</translation> - </message> - <message> - <location filename="../LibraryModel.cpp" line="66"/> - <source>GB</source> - <translation>GB</translation> - </message> - <message> - <location filename="../LibraryModel.cpp" line="69"/> - <source>?</source> - <translation>?</translation> - </message> - <message> - <location filename="../LibraryModel.cpp" line="74"/> - <source>Location</source> - <translation>Ort</translation> - </message> - <message> - <location filename="../LibraryModel.cpp" line="80"/> - <source>CRC32</source> - <translation>CRC32</translation> - </message> -</context> -<context> <name>QGBA::LoadSaveState</name> <message> <location filename="../LoadSaveState.cpp" line="68"/>

@@ -2565,63 +2537,63 @@ </context>

<context> <name>QGBA::MemoryModel</name> <message> - <location filename="../MemoryModel.cpp" line="50"/> + <location filename="../MemoryModel.cpp" line="44"/> <source>Copy selection</source> <translation>Auswahl kopieren</translation> </message> <message> - <location filename="../MemoryModel.cpp" line="55"/> + <location filename="../MemoryModel.cpp" line="49"/> <source>Save selection</source> <translation>Auswahl speichern</translation> </message> <message> - <location filename="../MemoryModel.cpp" line="60"/> + <location filename="../MemoryModel.cpp" line="54"/> <source>Paste</source> <translation>Einfügen</translation> </message> <message> - <location filename="../MemoryModel.cpp" line="65"/> + <location filename="../MemoryModel.cpp" line="59"/> <source>Load</source> <translation>Laden</translation> </message> <message> - <location filename="../MemoryModel.cpp" line="97"/> - <location filename="../MemoryModel.cpp" line="162"/> + <location filename="../MemoryModel.cpp" line="91"/> + <location filename="../MemoryModel.cpp" line="156"/> <source>All</source> <translation>Alle</translation> </message> <message> - <location filename="../MemoryModel.cpp" line="142"/> + <location filename="../MemoryModel.cpp" line="136"/> <source>Load TBL</source> <translation>TBL laden</translation> </message> <message> - <location filename="../MemoryModel.cpp" line="202"/> + <location filename="../MemoryModel.cpp" line="196"/> <source>Save selected memory</source> <translation>Ausgewählten Speicher abspeichern</translation> </message> <message> - <location filename="../MemoryModel.cpp" line="208"/> + <location filename="../MemoryModel.cpp" line="202"/> <source>Failed to open output file: %1</source> <translation>Fehler beim Öffnen der Ausgabedatei: %1</translation> </message> <message> - <location filename="../MemoryModel.cpp" line="216"/> + <location filename="../MemoryModel.cpp" line="210"/> <source>Load memory</source> <translation>Lade Speicher</translation> </message> <message> - <location filename="../MemoryModel.cpp" line="222"/> + <location filename="../MemoryModel.cpp" line="216"/> <source>Failed to open input file: %1</source> <translation>Fehler beim Laden der Eingabedatei: %1</translation> </message> <message> - <location filename="../MemoryModel.cpp" line="338"/> + <location filename="../MemoryModel.cpp" line="332"/> <source>TBL</source> <translation>TBL</translation> </message> <message> - <location filename="../MemoryModel.cpp" line="338"/> + <location filename="../MemoryModel.cpp" line="332"/> <source>ISO-8859-1</source> <translation>ISO-8859-1</translation> </message>

@@ -2629,54 +2601,54 @@ </context>

<context> <name>QGBA::ObjView</name> <message> - <location filename="../ObjView.cpp" line="141"/> - <location filename="../ObjView.cpp" line="233"/> + <location filename="../ObjView.cpp" line="142"/> + <location filename="../ObjView.cpp" line="234"/> <source>0x%0</source> <translation>0x%0</translation> </message> <message> - <location filename="../ObjView.cpp" line="152"/> + <location filename="../ObjView.cpp" line="153"/> <source>Off</source> <translation>Aus</translation> </message> <message> - <location filename="../ObjView.cpp" line="157"/> + <location filename="../ObjView.cpp" line="158"/> <source>Normal</source> <translation>Normal</translation> </message> <message> - <location filename="../ObjView.cpp" line="160"/> + <location filename="../ObjView.cpp" line="161"/> <source>Trans</source> <translation>Trans</translation> </message> <message> - <location filename="../ObjView.cpp" line="163"/> + <location filename="../ObjView.cpp" line="164"/> <source>OBJWIN</source> <translation>OBJWIN</translation> </message> <message> - <location filename="../ObjView.cpp" line="166"/> + <location filename="../ObjView.cpp" line="167"/> <source>Invalid</source> <translation>Ungültig</translation> </message> <message> - <location filename="../ObjView.cpp" line="240"/> <location filename="../ObjView.cpp" line="241"/> + <location filename="../ObjView.cpp" line="242"/> <source>N/A</source> <translation>N/A</translation> </message> <message> - <location filename="../ObjView.cpp" line="247"/> + <location filename="../ObjView.cpp" line="249"/> <source>Export sprite</source> <translation>Sprite exportieren</translation> </message> <message> - <location filename="../ObjView.cpp" line="248"/> + <location filename="../ObjView.cpp" line="250"/> <source>Portable Network Graphics (*.png)</source> <translation>Portable Network Graphics (*.png)</translation> </message> <message> - <location filename="../ObjView.cpp" line="255"/> + <location filename="../ObjView.cpp" line="253"/> <source>Failed to open output PNG file: %1</source> <translation>Fehler beim Öffnen der Ausgabe-PNG-Datei: %1</translation> </message>

@@ -2716,7 +2688,7 @@ <source>Windows PAL (*.pal);;Adobe Color Table (*.act)</source>

<translation>Windows PAL (*.pal);;Adobe Color Table (*.act)</translation> </message> <message> - <location filename="../PaletteView.cpp" line="145"/> + <location filename="../PaletteView.cpp" line="141"/> <source>Failed to open output palette file: %1</source> <translation>Fehler beim Öffnen der Ausgabe-Palettendatei: %1</translation> </message>

@@ -2795,37 +2767,37 @@ </context>

<context> <name>QGBA::ShaderSelector</name> <message> - <location filename="../ShaderSelector.cpp" line="50"/> + <location filename="../ShaderSelector.cpp" line="49"/> <source>No shader active</source> <translation>Kein Shader aktiv</translation> </message> <message> - <location filename="../ShaderSelector.cpp" line="63"/> + <location filename="../ShaderSelector.cpp" line="62"/> <source>Load shader</source> <translation>Shader laden</translation> </message> <message> - <location filename="../ShaderSelector.cpp" line="63"/> + <location filename="../ShaderSelector.cpp" line="62"/> <source>%1 Shader (%.shader)</source> <translation>%1 Shader (%.shader)</translation> </message> <message> - <location filename="../ShaderSelector.cpp" line="102"/> + <location filename="../ShaderSelector.cpp" line="101"/> <source>No shader loaded</source> <translation>Kein Shader geladen</translation> </message> <message> - <location filename="../ShaderSelector.cpp" line="110"/> + <location filename="../ShaderSelector.cpp" line="109"/> <source>by %1</source> <translation>von %1</translation> </message> <message> - <location filename="../ShaderSelector.cpp" line="121"/> + <location filename="../ShaderSelector.cpp" line="120"/> <source>Preprocessing</source> <translation>Vorbehandlung</translation> </message> <message> - <location filename="../ShaderSelector.cpp" line="128"/> + <location filename="../ShaderSelector.cpp" line="127"/> <source>Pass %1</source> <translation>Durchlauf %1</translation> </message>

@@ -2833,17 +2805,17 @@ </context>

<context> <name>QGBA::ShortcutController</name> <message> - <location filename="../ShortcutController.cpp" line="67"/> + <location filename="../ShortcutController.cpp" line="64"/> <source>Action</source> <translation>Aktion</translation> </message> <message> - <location filename="../ShortcutController.cpp" line="69"/> + <location filename="../ShortcutController.cpp" line="66"/> <source>Keyboard</source> <translation>Tastatur</translation> </message> <message> - <location filename="../ShortcutController.cpp" line="71"/> + <location filename="../ShortcutController.cpp" line="68"/> <source>Gamepad</source> <translation>Gamepad</translation> </message>

@@ -2851,17 +2823,17 @@ </context>

<context> <name>QGBA::VideoView</name> <message> - <location filename="../VideoView.cpp" line="208"/> + <location filename="../VideoView.cpp" line="203"/> <source>Failed to open output video file: %1</source> <translation>Fehler beim Öffnen der Ausgabe-Videodatei: %1</translation> </message> <message> - <location filename="../VideoView.cpp" line="226"/> + <location filename="../VideoView.cpp" line="221"/> <source>Native (%0x%1)</source> <translation>Nativ (%0x%1)</translation> </message> <message> - <location filename="../VideoView.cpp" line="241"/> + <location filename="../VideoView.cpp" line="236"/> <source>Select output file</source> <translation>Ausgabedatei auswählen</translation> </message>

@@ -2869,67 +2841,82 @@ </context>

<context> <name>QGBA::Window</name> <message> - <location filename="../Window.cpp" line="340"/> + <location filename="../Window.cpp" line="324"/> <source>Game Boy Advance ROMs (%1)</source> <translation>Game Boy Advance-ROMs (%1)</translation> </message> <message> - <location filename="../Window.cpp" line="356"/> + <location filename="../Window.cpp" line="340"/> <source>Game Boy ROMs (%1)</source> <translation>Game Boy-ROMs (%1)</translation> </message> <message> - <location filename="../Window.cpp" line="360"/> + <location filename="../Window.cpp" line="344"/> <source>All ROMs (%1)</source> <translation>Alle ROMs (%1)</translation> </message> <message> - <location filename="../Window.cpp" line="375"/> + <location filename="../Window.cpp" line="345"/> + <source>%1 Video Logs (*.mvl)</source> + <translation>%1 Video-Logs (*.mvl)</translation> + </message> + <message> + <location filename="../Window.cpp" line="360"/> <source>Archives (%1)</source> <translation>Archive (%1)</translation> </message> <message> - <location filename="../Window.cpp" line="380"/> - <location filename="../Window.cpp" line="388"/> - <location filename="../Window.cpp" line="415"/> + <location filename="../Window.cpp" line="365"/> + <location filename="../Window.cpp" line="373"/> + <location filename="../Window.cpp" line="400"/> <source>Select ROM</source> <translation>ROM auswählen</translation> </message> <message> - <location filename="../Window.cpp" line="423"/> + <location filename="../Window.cpp" line="408"/> <source>Game Boy Advance save files (%1)</source> <translation>Game Boy Advance-Speicherdateien (%1)</translation> </message> <message> - <location filename="../Window.cpp" line="424"/> - <location filename="../Window.cpp" line="457"/> - <location filename="../Window.cpp" line="464"/> + <location filename="../Window.cpp" line="409"/> + <location filename="../Window.cpp" line="443"/> + <location filename="../Window.cpp" line="450"/> <source>Select save</source> <translation>Speicherdatei wählen</translation> </message> <message> - <location filename="../Window.cpp" line="444"/> + <location filename="../Window.cpp" line="429"/> <source>Select patch</source> <translation>Patch wählen</translation> </message> <message> - <location filename="../Window.cpp" line="444"/> + <location filename="../Window.cpp" line="429"/> <source>Patches (*.ips *.ups *.bps)</source> <translation>Patches (*.ips *.ups *.bps)</translation> </message> <message> - <location filename="../Window.cpp" line="457"/> - <location filename="../Window.cpp" line="464"/> + <location filename="../Window.cpp" line="443"/> + <location filename="../Window.cpp" line="450"/> <source>GameShark saves (*.sps *.xps)</source> <translation>GameShark-Speicherdaten (*.sps *.xps)</translation> </message> <message> - <location filename="../Window.cpp" line="782"/> + <location filename="../Window.cpp" line="471"/> + <source>Select video log</source> + <translation>Video-Log auswählen</translation> + </message> + <message> + <location filename="../Window.cpp" line="471"/> + <source>Video logs (*.mvl)</source> + <translation>Video-Logs (*.mvl)</translation> + </message> + <message> + <location filename="../Window.cpp" line="813"/> <source>Crash</source> <translation>Absturz</translation> </message> <message> - <location filename="../Window.cpp" line="783"/> + <location filename="../Window.cpp" line="814"/> <source>The game has crashed with the following error: %1</source>

@@ -2938,638 +2925,646 @@

%1</translation> </message> <message> - <location filename="../Window.cpp" line="790"/> + <location filename="../Window.cpp" line="821"/> <source>Couldn&apos;t Load</source> <translation>Konnte nicht geladen werden</translation> </message> <message> - <location filename="../Window.cpp" line="791"/> + <location filename="../Window.cpp" line="822"/> <source>Could not load game. Are you sure it&apos;s in the correct format?</source> <translation>Konnte das Spiel nicht laden. Sind Sie sicher, dass es im korrekten Format vorliegt?</translation> </message> <message> - <location filename="../Window.cpp" line="804"/> + <location filename="../Window.cpp" line="835"/> <source>Unimplemented BIOS call</source> - <translation>Nichtimplementierter BIOS-Aufruf</translation> + <translation>Nicht implementierter BIOS-Aufruf</translation> </message> <message> - <location filename="../Window.cpp" line="805"/> + <location filename="../Window.cpp" line="836"/> <source>This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience.</source> <translation>Dieses Spiel verwendet einen BIOS-Aufruf, der nicht implementiert ist. Bitte verwenden Sie für die beste Spielerfahrung das offizielle BIOS.</translation> </message> <message> - <location filename="../Window.cpp" line="812"/> + <location filename="../Window.cpp" line="843"/> <source>Really make portable?</source> <translation>Portablen Modus wirklich aktivieren?</translation> </message> <message> - <location filename="../Window.cpp" line="813"/> + <location filename="../Window.cpp" line="844"/> <source>This will make the emulator load its configuration from the same directory as the executable. Do you want to continue?</source> <translation>Diese Einstellung wird den Emulator so konfigurieren, dass er seine Konfiguration aus dem gleichen Verzeichnis wie die Programmdatei lädt. Möchten Sie fortfahren?</translation> </message> <message> - <location filename="../Window.cpp" line="821"/> + <location filename="../Window.cpp" line="852"/> <source>Restart needed</source> <translation>Neustart benötigt</translation> </message> <message> - <location filename="../Window.cpp" line="822"/> + <location filename="../Window.cpp" line="853"/> <source>Some changes will not take effect until the emulator is restarted.</source> <translation>Einige Änderungen werden erst übernommen, wenn der Emulator neu gestartet wurde.</translation> </message> <message> - <location filename="../Window.cpp" line="869"/> + <location filename="../Window.cpp" line="900"/> <source> - Player %1 of %2</source> <translation> - Spieler %1 von %2</translation> </message> <message> - <location filename="../Window.cpp" line="880"/> + <location filename="../Window.cpp" line="911"/> <source>%1 - %2</source> <translation>%1 - %2</translation> </message> <message> - <location filename="../Window.cpp" line="882"/> + <location filename="../Window.cpp" line="913"/> <source>%1 - %2 - %3</source> <translation>%1 - %2 - %3</translation> </message> <message> - <location filename="../Window.cpp" line="884"/> + <location filename="../Window.cpp" line="915"/> <source>%1 - %2 (%3 fps) - %4</source> <translation>%1 - %2 (%3 Bilder/Sekunde) - %4</translation> </message> <message> - <location filename="../Window.cpp" line="916"/> + <location filename="../Window.cpp" line="947"/> <source>&amp;File</source> <translation>&amp;Datei</translation> </message> <message> - <location filename="../Window.cpp" line="919"/> + <location filename="../Window.cpp" line="950"/> <source>Load &amp;ROM...</source> <translation>&amp;ROM laden...</translation> </message> <message> - <location filename="../Window.cpp" line="922"/> + <location filename="../Window.cpp" line="953"/> <source>Load ROM in archive...</source> <translation>ROM aus Archiv laden...</translation> </message> <message> - <location filename="../Window.cpp" line="928"/> + <location filename="../Window.cpp" line="959"/> <source>Load temporary save...</source> <translation>Temporäre Speicherdatei laden...</translation> </message> <message> - <location filename="../Window.cpp" line="933"/> + <location filename="../Window.cpp" line="964"/> <source>Load &amp;patch...</source> <translation>&amp;Patch laden...</translation> </message> <message> - <location filename="../Window.cpp" line="935"/> + <location filename="../Window.cpp" line="966"/> <source>Boot BIOS</source> <translation>BIOS booten</translation> </message> <message> - <location filename="../Window.cpp" line="942"/> + <location filename="../Window.cpp" line="973"/> <source>Replace ROM...</source> <translation>ROM ersetzen...</translation> </message> <message> - <location filename="../Window.cpp" line="944"/> + <location filename="../Window.cpp" line="975"/> <source>ROM &amp;info...</source> <translation>ROM-&amp;Informationen...</translation> </message> <message> - <location filename="../Window.cpp" line="949"/> + <location filename="../Window.cpp" line="980"/> <source>Recent</source> <translation>Zuletzt verwendet</translation> </message> <message> - <location filename="../Window.cpp" line="953"/> + <location filename="../Window.cpp" line="984"/> <source>Make portable</source> <translation>Portablen Modus aktivieren</translation> </message> <message> - <location filename="../Window.cpp" line="957"/> + <location filename="../Window.cpp" line="988"/> <source>&amp;Load state</source> <translation>Savestate &amp;laden</translation> </message> <message> - <location filename="../Window.cpp" line="958"/> + <location filename="../Window.cpp" line="989"/> <source>F10</source> <translation>F10</translation> </message> <message> - <location filename="../Window.cpp" line="964"/> + <location filename="../Window.cpp" line="995"/> <source>&amp;Save state</source> <translation>Savestate &amp;speichern</translation> </message> <message> - <location filename="../Window.cpp" line="965"/> + <location filename="../Window.cpp" line="996"/> <source>Shift+F10</source> <translation>Umschalt+F10</translation> </message> <message> - <location filename="../Window.cpp" line="971"/> + <location filename="../Window.cpp" line="1002"/> <source>Quick load</source> <translation>Schnell laden</translation> </message> <message> - <location filename="../Window.cpp" line="972"/> + <location filename="../Window.cpp" line="1003"/> <source>Quick save</source> <translation>Schnell speichern</translation> </message> <message> - <location filename="../Window.cpp" line="976"/> + <location filename="../Window.cpp" line="1007"/> <source>Load recent</source> <translation>Lade zuletzt gespeicherten Savestate</translation> </message> <message> - <location filename="../Window.cpp" line="982"/> + <location filename="../Window.cpp" line="1013"/> <source>Save recent</source> <translation>Speichere aktuellen Stand</translation> </message> <message> - <location filename="../Window.cpp" line="991"/> + <location filename="../Window.cpp" line="1022"/> <source>Undo load state</source> <translation>Laden des Savestate rückgängig machen</translation> </message> <message> - <location filename="../Window.cpp" line="992"/> + <location filename="../Window.cpp" line="1023"/> <source>F11</source> <translation>F11</translation> </message> <message> - <location filename="../Window.cpp" line="998"/> + <location filename="../Window.cpp" line="1029"/> <source>Undo save state</source> <translation>Speichern des Savestate rückgängig machen</translation> </message> <message> - <location filename="../Window.cpp" line="999"/> + <location filename="../Window.cpp" line="1030"/> <source>Shift+F11</source> <translation>Umschalt+F11</translation> </message> <message> - <location filename="../Window.cpp" line="1010"/> - <location filename="../Window.cpp" line="1017"/> + <location filename="../Window.cpp" line="1041"/> + <location filename="../Window.cpp" line="1048"/> <source>State &amp;%1</source> <translation>Savestate &amp;%1</translation> </message> <message> - <location filename="../Window.cpp" line="1011"/> + <location filename="../Window.cpp" line="1042"/> <source>F%1</source> <translation>F%1</translation> </message> <message> - <location filename="../Window.cpp" line="1018"/> + <location filename="../Window.cpp" line="1049"/> <source>Shift+F%1</source> <translation>Umschalt+F%1</translation> </message> <message> - <location filename="../Window.cpp" line="1027"/> + <location filename="../Window.cpp" line="1058"/> <source>Import GameShark Save</source> <translation>Importiere GameShark-Speicherstand</translation> </message> <message> - <location filename="../Window.cpp" line="1033"/> + <location filename="../Window.cpp" line="1064"/> <source>Export GameShark Save</source> <translation>Exportiere GameShark-Speicherstand</translation> </message> <message> - <location filename="../Window.cpp" line="1041"/> + <location filename="../Window.cpp" line="1072"/> <source>New multiplayer window</source> <translation>Neues Multiplayer-Fenster</translation> </message> <message> - <location filename="../Window.cpp" line="1051"/> + <location filename="../Window.cpp" line="1082"/> <source>About</source> <translation>Über</translation> </message> <message> - <location filename="../Window.cpp" line="1056"/> + <location filename="../Window.cpp" line="1087"/> <source>E&amp;xit</source> <translation>&amp;Beenden</translation> </message> <message> - <location filename="../Window.cpp" line="1059"/> + <location filename="../Window.cpp" line="1090"/> <source>&amp;Emulation</source> <translation>&amp;Emulation</translation> </message> <message> - <location filename="../Window.cpp" line="1061"/> + <location filename="../Window.cpp" line="1092"/> <source>&amp;Reset</source> <translation>Zu&amp;rücksetzen</translation> </message> <message> - <location filename="../Window.cpp" line="1062"/> + <location filename="../Window.cpp" line="1093"/> <source>Ctrl+R</source> <translation>Strg+R</translation> </message> <message> - <location filename="../Window.cpp" line="1067"/> + <location filename="../Window.cpp" line="1098"/> <source>Sh&amp;utdown</source> <translation>B&amp;eenden</translation> </message> <message> - <location filename="../Window.cpp" line="1073"/> + <location filename="../Window.cpp" line="1104"/> <source>Yank game pak</source> <translation>Spielmodul herausziehen</translation> </message> <message> - <location filename="../Window.cpp" line="1081"/> + <location filename="../Window.cpp" line="1112"/> <source>&amp;Pause</source> <translation>&amp;Pause</translation> </message> <message> - <location filename="../Window.cpp" line="1084"/> + <location filename="../Window.cpp" line="1115"/> <source>Ctrl+P</source> <translation>Strg+P</translation> </message> <message> - <location filename="../Window.cpp" line="1093"/> + <location filename="../Window.cpp" line="1124"/> <source>&amp;Next frame</source> <translation>&amp;Nächstes Bild</translation> </message> <message> - <location filename="../Window.cpp" line="1094"/> + <location filename="../Window.cpp" line="1125"/> <source>Ctrl+N</source> <translation>Strg+N</translation> </message> <message> - <location filename="../Window.cpp" line="1105"/> + <location filename="../Window.cpp" line="1136"/> <source>Fast forward (held)</source> <translation>Schneller Vorlauf (gehalten)</translation> </message> <message> - <location filename="../Window.cpp" line="1107"/> + <location filename="../Window.cpp" line="1138"/> <source>&amp;Fast forward</source> <translation>Schneller &amp;Vorlauf</translation> </message> <message> - <location filename="../Window.cpp" line="1110"/> + <location filename="../Window.cpp" line="1141"/> <source>Shift+Tab</source> <translation>Umschalt+Tab</translation> </message> <message> - <location filename="../Window.cpp" line="1114"/> + <location filename="../Window.cpp" line="1145"/> <source>Fast forward speed</source> <translation>Vorlauf-Geschwindigkeit</translation> </message> <message> - <location filename="../Window.cpp" line="1119"/> + <location filename="../Window.cpp" line="1150"/> <source>Unbounded</source> <translation>Unbegrenzt</translation> </message> <message> - <location filename="../Window.cpp" line="1123"/> + <location filename="../Window.cpp" line="1154"/> <source>%0x</source> <translation>%0x</translation> </message> <message> - <location filename="../Window.cpp" line="1131"/> + <location filename="../Window.cpp" line="1162"/> <source>Rewind (held)</source> <translation>Zurückspulen (gehalten)</translation> </message> <message> - <location filename="../Window.cpp" line="1133"/> + <location filename="../Window.cpp" line="1164"/> <source>Re&amp;wind</source> <translation>Zur&amp;ückspulen</translation> </message> <message> - <location filename="../Window.cpp" line="1134"/> + <location filename="../Window.cpp" line="1165"/> <source>~</source> <translation>~</translation> </message> <message> - <location filename="../Window.cpp" line="1140"/> + <location filename="../Window.cpp" line="1171"/> <source>Step backwards</source> <translation>Schrittweiser Rücklauf</translation> </message> <message> - <location filename="../Window.cpp" line="1141"/> + <location filename="../Window.cpp" line="1172"/> <source>Ctrl+B</source> <translation>Strg+B</translation> </message> <message> - <location filename="../Window.cpp" line="1150"/> + <location filename="../Window.cpp" line="1181"/> <source>Sync to &amp;video</source> <translation>Mit &amp;Video synchronisieren</translation> </message> <message> - <location filename="../Window.cpp" line="1157"/> + <location filename="../Window.cpp" line="1188"/> <source>Sync to &amp;audio</source> <translation>Mit &amp;Audio synchronisieren</translation> </message> <message> - <location filename="../Window.cpp" line="1165"/> + <location filename="../Window.cpp" line="1196"/> <source>Solar sensor</source> <translation>Solar-Sensor</translation> </message> <message> - <location filename="../Window.cpp" line="1167"/> + <location filename="../Window.cpp" line="1198"/> <source>Increase solar level</source> <translation>Sonnen-Level erhöhen</translation> </message> <message> - <location filename="../Window.cpp" line="1171"/> + <location filename="../Window.cpp" line="1202"/> <source>Decrease solar level</source> <translation>Sonnen-Level verringern</translation> </message> <message> - <location filename="../Window.cpp" line="1175"/> + <location filename="../Window.cpp" line="1206"/> <source>Brightest solar level</source> <translation>Hellster Sonnen-Level</translation> </message> <message> - <location filename="../Window.cpp" line="1179"/> + <location filename="../Window.cpp" line="1210"/> <source>Darkest solar level</source> <translation>Dunkelster Sonnen-Level</translation> </message> <message> - <location filename="../Window.cpp" line="1185"/> + <location filename="../Window.cpp" line="1216"/> <source>Brightness %1</source> <translation>Helligkeit %1</translation> </message> <message> - <location filename="../Window.cpp" line="1192"/> + <location filename="../Window.cpp" line="1223"/> <source>Audio/&amp;Video</source> <translation>Audio/&amp;Video</translation> </message> <message> - <location filename="../Window.cpp" line="1194"/> + <location filename="../Window.cpp" line="1225"/> <source>Frame size</source> <translation>Bildgröße</translation> </message> <message> - <location filename="../Window.cpp" line="1197"/> + <location filename="../Window.cpp" line="1228"/> <source>%1x</source> <translation>%1x</translation> </message> <message> - <location filename="../Window.cpp" line="1225"/> + <location filename="../Window.cpp" line="1256"/> <source>Toggle fullscreen</source> <translation>Vollbildmodus umschalten</translation> </message> <message> - <location filename="../Window.cpp" line="1228"/> + <location filename="../Window.cpp" line="1259"/> <source>Lock aspect ratio</source> - <translation>Seitenverhältnis sperren</translation> + <translation>Seitenverhältnis korrigieren</translation> </message> <message> - <location filename="../Window.cpp" line="1241"/> + <location filename="../Window.cpp" line="1266"/> + <source>Force integer scaling</source> + <translation>Pixelgenaue Skalierung (Integer scaling)</translation> + </message> + <message> + <location filename="../Window.cpp" line="1282"/> <source>Frame&amp;skip</source> <translation>Frame&amp;skip</translation> </message> <message> - <location filename="../Window.cpp" line="1251"/> + <location filename="../Window.cpp" line="1292"/> <source>Shader options...</source> <translation>Shader-Optionen...</translation> </message> <message> - <location filename="../Window.cpp" line="1261"/> + <location filename="../Window.cpp" line="1302"/> <source>Mute</source> <translation>Stummschalten</translation> </message> <message> - <location filename="../Window.cpp" line="1267"/> + <location filename="../Window.cpp" line="1309"/> <source>FPS target</source> <translation>Bildwiederholrate</translation> </message> <message> - <location filename="../Window.cpp" line="1272"/> + <location filename="../Window.cpp" line="1314"/> <source>15</source> <translation>15</translation> </message> <message> - <location filename="../Window.cpp" line="1273"/> + <location filename="../Window.cpp" line="1315"/> <source>30</source> <translation>30</translation> </message> <message> - <location filename="../Window.cpp" line="1274"/> + <location filename="../Window.cpp" line="1316"/> <source>45</source> <translation>45</translation> </message> <message> - <location filename="../Window.cpp" line="1275"/> + <location filename="../Window.cpp" line="1317"/> <source>Native (59.7)</source> <translation>Nativ (59.7)</translation> </message> <message> - <location filename="../Window.cpp" line="1276"/> + <location filename="../Window.cpp" line="1318"/> <source>60</source> <translation>60</translation> </message> <message> - <location filename="../Window.cpp" line="1277"/> + <location filename="../Window.cpp" line="1319"/> <source>90</source> <translation>90</translation> </message> <message> - <location filename="../Window.cpp" line="1278"/> + <location filename="../Window.cpp" line="1320"/> <source>120</source> <translation>120</translation> </message> <message> - <location filename="../Window.cpp" line="1279"/> + <location filename="../Window.cpp" line="1321"/> <source>240</source> <translation>240</translation> </message> <message> - <location filename="../Window.cpp" line="1287"/> + <location filename="../Window.cpp" line="1329"/> <source>Take &amp;screenshot</source> <translation>&amp;Screenshot erstellen</translation> </message> <message> - <location filename="../Window.cpp" line="1288"/> + <location filename="../Window.cpp" line="1330"/> <source>F12</source> <translation>F12</translation> </message> <message> - <location filename="../Window.cpp" line="1295"/> + <location filename="../Window.cpp" line="1337"/> <source>Record output...</source> <translation>Ausgabe aufzeichen...</translation> </message> <message> - <location filename="../Window.cpp" line="1302"/> + <location filename="../Window.cpp" line="1344"/> <source>Record GIF...</source> <translation>GIF aufzeichen...</translation> </message> <message> - <location filename="../Window.cpp" line="1308"/> - <source>Video layers</source> - <translation>Video-Ebenen</translation> + <location filename="../Window.cpp" line="1349"/> + <source>Record video log...</source> + <translation>Video-Log aufzeichnen...</translation> </message> <message> - <location filename="../Window.cpp" line="1312"/> - <source>Background %0</source> - <translation>Hintergrund %0</translation> + <location filename="../Window.cpp" line="1354"/> + <source>Stop video log</source> + <translation>Video-Log beenden</translation> </message> <message> - <location filename="../Window.cpp" line="1319"/> - <source>OBJ (sprites)</source> - <translation>OBJ (Sprites)</translation> + <location filename="../Window.cpp" line="1360"/> + <source>Video layers</source> + <translation>Video-Ebenen</translation> </message> <message> - <location filename="../Window.cpp" line="1325"/> + <location filename="../Window.cpp" line="1363"/> <source>Audio channels</source> <translation>Audio-Kanäle</translation> </message> <message> - <location filename="../Window.cpp" line="1329"/> - <source>Channel %0</source> - <translation>Kanal %0</translation> - </message> - <message> - <location filename="../Window.cpp" line="1336"/> - <source>Channel A</source> - <translation>Kanal A</translation> - </message> - <message> - <location filename="../Window.cpp" line="1342"/> - <source>Channel B</source> - <translation>Kanal B</translation> - </message> - <message> - <location filename="../Window.cpp" line="1348"/> + <location filename="../Window.cpp" line="1366"/> <source>&amp;Tools</source> <translation>&amp;Werkzeuge</translation> </message> <message> - <location filename="../Window.cpp" line="1350"/> + <location filename="../Window.cpp" line="1368"/> <source>View &amp;logs...</source> <translation>&amp;Logs ansehen...</translation> </message> <message> - <location filename="../Window.cpp" line="1354"/> + <location filename="../Window.cpp" line="1372"/> <source>Game &amp;overrides...</source> <translation>Spiel-&amp;Überschreibungen...</translation> </message> <message> - <location filename="../Window.cpp" line="1358"/> + <location filename="../Window.cpp" line="1376"/> <source>Game &amp;Pak sensors...</source> <translation>Game &amp;Pak-Sensoren...</translation> </message> <message> - <location filename="../Window.cpp" line="1362"/> + <location filename="../Window.cpp" line="1380"/> <source>&amp;Cheats...</source> <translation>&amp;Cheats...</translation> </message> <message> - <location filename="../Window.cpp" line="1374"/> + <location filename="../Window.cpp" line="1392"/> <source>Open debugger console...</source> <translation>Debugger-Konsole äffnen...</translation> </message> <message> - <location filename="../Window.cpp" line="1380"/> + <location filename="../Window.cpp" line="1398"/> <source>Start &amp;GDB server...</source> <translation>&amp;GDB-Server starten...</translation> </message> <message> - <location filename="../Window.cpp" line="1368"/> + <location filename="../Window.cpp" line="1386"/> <source>Settings...</source> <translation>Einstellungen...</translation> </message> <message> - <location filename="../Window.cpp" line="406"/> + <location filename="../Window.cpp" line="391"/> <source>Select folder</source> <translation>Ordner auswählen</translation> </message> <message> - <location filename="../Window.cpp" line="924"/> + <location filename="../Window.cpp" line="955"/> <source>Add folder to library...</source> <translation>Ordner zur Bibliothek hinzufügen...</translation> </message> <message> - <location filename="../Window.cpp" line="1235"/> + <location filename="../Window.cpp" line="1276"/> <source>Bilinear filtering</source> <translation>Bilineare Filterung</translation> </message> <message> - <location filename="../Window.cpp" line="1387"/> + <location filename="../Window.cpp" line="1405"/> <source>View &amp;palette...</source> <translation>&amp;Palette betrachten...</translation> </message> <message> - <location filename="../Window.cpp" line="1392"/> + <location filename="../Window.cpp" line="1410"/> <source>View &amp;sprites...</source> <translation>&amp;Sprites betrachten...</translation> </message> <message> - <location filename="../Window.cpp" line="1397"/> + <location filename="../Window.cpp" line="1415"/> <source>View &amp;tiles...</source> <translation>&amp;Tiles betrachten...</translation> </message> <message> - <location filename="../Window.cpp" line="1402"/> + <location filename="../Window.cpp" line="1420"/> <source>View memory...</source> <translation>Speicher betrachten...</translation> </message> <message> - <location filename="../Window.cpp" line="1408"/> + <location filename="../Window.cpp" line="1426"/> <source>View &amp;I/O registers...</source> <translation>&amp;I/O-Register betrachten...</translation> </message> <message> - <location filename="../Window.cpp" line="1470"/> + <location filename="../Window.cpp" line="1496"/> <source>Exit fullscreen</source> <translation>Vollbildmodus beenden</translation> </message> <message> - <location filename="../Window.cpp" line="1475"/> + <location filename="../Window.cpp" line="1501"/> <source>Autofire</source> <translation>Autofeuer</translation> </message> <message> - <location filename="../Window.cpp" line="1482"/> + <location filename="../Window.cpp" line="1508"/> <source>Autofire A</source> <translation>Autofeuer A</translation> </message> <message> - <location filename="../Window.cpp" line="1488"/> + <location filename="../Window.cpp" line="1514"/> <source>Autofire B</source> <translation>Autofeuer B</translation> </message> <message> - <location filename="../Window.cpp" line="1494"/> + <location filename="../Window.cpp" line="1520"/> <source>Autofire L</source> <translation>Autofeuer L</translation> </message> <message> - <location filename="../Window.cpp" line="1500"/> + <location filename="../Window.cpp" line="1526"/> <source>Autofire R</source> <translation>Autofeuer R</translation> </message> <message> - <location filename="../Window.cpp" line="1506"/> + <location filename="../Window.cpp" line="1532"/> <source>Autofire Start</source> <translation>Autofeuer Start</translation> </message> <message> - <location filename="../Window.cpp" line="1512"/> + <location filename="../Window.cpp" line="1538"/> <source>Autofire Select</source> <translation>Autofeuer Select</translation> </message> <message> - <location filename="../Window.cpp" line="1518"/> + <location filename="../Window.cpp" line="1544"/> <source>Autofire Up</source> <translation>Autofeuer nach oben</translation> </message> <message> - <location filename="../Window.cpp" line="1524"/> + <location filename="../Window.cpp" line="1550"/> <source>Autofire Right</source> <translation>Autofeuer rechts</translation> </message> <message> - <location filename="../Window.cpp" line="1530"/> + <location filename="../Window.cpp" line="1556"/> <source>Autofire Down</source> <translation>Autofeuer nach unten</translation> </message> <message> - <location filename="../Window.cpp" line="1536"/> + <location filename="../Window.cpp" line="1562"/> <source>Autofire Left</source> <translation>Autofeuer links</translation> </message> </context> <context> + <name>QObject</name> + <message> + <location filename="../utils.cpp" line="29"/> + <source>GBA</source> + <translation>GBA</translation> + </message> + <message> + <location filename="../utils.cpp" line="33"/> + <source>GB</source> + <translation>GB</translation> + </message> + <message> + <location filename="../utils.cpp" line="36"/> + <source>?</source> + <translation>?</translation> + </message> +</context> +<context> <name>ROMInfo</name> <message> <location filename="../ROMInfo.ui" line="14"/>

@@ -3838,7 +3833,7 @@ <translation>Alle</translation>

</message> <message> <location filename="../SettingsView.ui" line="307"/> - <location filename="../SettingsView.ui" line="532"/> + <location filename="../SettingsView.ui" line="556"/> <source>frames</source> <translation>Bilder</translation> </message>

@@ -3870,67 +3865,90 @@ </message>

<message> <location filename="../SettingsView.ui" line="379"/> <source>Lock aspect ratio</source> - <translation>Seitenverhältnis sperren</translation> + <translation>Seitenverhältnis korrigieren</translation> + </message> + <message> + <location filename="../SettingsView.ui" line="393"/> + <source>Force integer scaling</source> + <translation>Erzwinge pixelgenaue Skalierung +(Integer scaling)</translation> + </message> + <message> + <location filename="../SettingsView.ui" line="415"/> + <source>List view</source> + <translation>Listenansicht</translation> </message> <message> - <location filename="../SettingsView.ui" line="421"/> + <location filename="../SettingsView.ui" line="420"/> + <source>Tree view</source> + <translation>Baumansicht</translation> + </message> + <message> + <location filename="../SettingsView.ui" line="428"/> <source>Library:</source> <translation>Bibliothek:</translation> </message> <message> - <location filename="../SettingsView.ui" line="428"/> + <location filename="../SettingsView.ui" line="404"/> <source>Show when no game open</source> <translation>Anzeigen, wenn kein Spiel geöffnet ist</translation> </message> <message> - <location filename="../SettingsView.ui" line="445"/> + <location filename="../SettingsView.ui" line="438"/> <source>Clear cache</source> <translation>Cache leeren</translation> </message> <message> - <location filename="../SettingsView.ui" line="459"/> + <location filename="../SettingsView.ui" line="483"/> <source>Fast forward speed:</source> <translation>Vorlauf-Geschwindigkeit:</translation> </message> <message> - <location filename="../SettingsView.ui" line="656"/> + <location filename="../SettingsView.ui" line="680"/> <source>Rewind affects save data</source> <translation>Rücklauf beeinflusst Speicherdaten</translation> </message> <message> - <location filename="../SettingsView.ui" line="689"/> - <location filename="../SettingsView.ui" line="727"/> - <location filename="../SettingsView.ui" line="762"/> - <location filename="../SettingsView.ui" line="803"/> - <location filename="../SettingsView.ui" line="851"/> - <location filename="../SettingsView.ui" line="899"/> - <location filename="../SettingsView.ui" line="947"/> + <location filename="../SettingsView.ui" line="690"/> + <source>Preload entire ROM into memory</source> + <translation>ROM-Datei vollständig +in Arbeitsspeicher vorladen</translation> + </message> + <message> + <location filename="../SettingsView.ui" line="720"/> + <location filename="../SettingsView.ui" line="758"/> + <location filename="../SettingsView.ui" line="793"/> + <location filename="../SettingsView.ui" line="834"/> + <location filename="../SettingsView.ui" line="882"/> + <location filename="../SettingsView.ui" line="930"/> + <location filename="../SettingsView.ui" line="978"/> <source>Browse</source> <translation>Durchsuchen</translation> </message> <message> - <location filename="../SettingsView.ui" line="698"/> + <location filename="../SettingsView.ui" line="729"/> <source>Use BIOS file if found</source> - <translation>BIOS-Datei verwenden, wenn vorhanden</translation> + <translation>BIOS-Datei verwenden, +wenn vorhanden</translation> </message> <message> - <location filename="../SettingsView.ui" line="708"/> + <location filename="../SettingsView.ui" line="739"/> <source>Skip BIOS intro</source> <translation>BIOS-Intro überspringen</translation> </message> <message> - <location filename="../SettingsView.ui" line="471"/> + <location filename="../SettingsView.ui" line="495"/> <source>×</source> <translation>×</translation> </message> <message> - <location filename="../SettingsView.ui" line="490"/> + <location filename="../SettingsView.ui" line="514"/> <source>Unbounded</source> <translation>unbegrenzt</translation> </message> <message> - <location filename="../SettingsView.ui" line="404"/> + <location filename="../SettingsView.ui" line="459"/> <source>Suspend screensaver</source> <translation>Bildschirmschoner deaktivieren</translation> </message>

@@ -3940,50 +3958,50 @@ <source>BIOS</source>

<translation>BIOS</translation> </message> <message> - <location filename="../SettingsView.ui" line="414"/> + <location filename="../SettingsView.ui" line="469"/> <source>Pause when inactive</source> <translation>Pause, wenn inaktiv</translation> </message> <message> - <location filename="../SettingsView.ui" line="556"/> + <location filename="../SettingsView.ui" line="580"/> <source>Run all</source> <translation>Alle ausführen</translation> </message> <message> - <location filename="../SettingsView.ui" line="561"/> + <location filename="../SettingsView.ui" line="585"/> <source>Remove known</source> <translation>Bekannte entfernen</translation> </message> <message> - <location filename="../SettingsView.ui" line="566"/> + <location filename="../SettingsView.ui" line="590"/> <source>Detect and remove</source> <translation>Erkennen und entfernen</translation> </message> <message> - <location filename="../SettingsView.ui" line="397"/> + <location filename="../SettingsView.ui" line="452"/> <source>Allow opposing input directions</source> <translation>Gegensätzliche Eingaberichtungen erlauben</translation> </message> <message> - <location filename="../SettingsView.ui" line="588"/> - <location filename="../SettingsView.ui" line="625"/> + <location filename="../SettingsView.ui" line="612"/> + <location filename="../SettingsView.ui" line="649"/> <source>Screenshot</source> <translation>Screenshot</translation> </message> <message> - <location filename="../SettingsView.ui" line="598"/> - <location filename="../SettingsView.ui" line="635"/> + <location filename="../SettingsView.ui" line="622"/> + <location filename="../SettingsView.ui" line="659"/> <source>Save data</source> <translation>Speicherdaten</translation> </message> <message> - <location filename="../SettingsView.ui" line="608"/> - <location filename="../SettingsView.ui" line="642"/> + <location filename="../SettingsView.ui" line="632"/> + <location filename="../SettingsView.ui" line="666"/> <source>Cheat codes</source> <translation>Cheat-Codes</translation> </message> <message> - <location filename="../SettingsView.ui" line="509"/> + <location filename="../SettingsView.ui" line="533"/> <source>Enable rewind</source> <translation>Rücklauf aktivieren</translation> </message>

@@ -3993,65 +4011,65 @@ <source>Bilinear filtering</source>

<translation>Bilineare Filterung</translation> </message> <message> - <location filename="../SettingsView.ui" line="516"/> + <location filename="../SettingsView.ui" line="540"/> <source>Rewind history:</source> <translation>Rücklauf-Verlauf:</translation> </message> <message> - <location filename="../SettingsView.ui" line="548"/> + <location filename="../SettingsView.ui" line="572"/> <source>Idle loops:</source> <translation>Leerlaufprozesse:</translation> </message> <message> - <location filename="../SettingsView.ui" line="581"/> + <location filename="../SettingsView.ui" line="605"/> <source>Savestate extra data:</source> <translation>Zusätzliche Savestate-Daten:</translation> </message> <message> - <location filename="../SettingsView.ui" line="618"/> + <location filename="../SettingsView.ui" line="642"/> <source>Load extra data:</source> <translation>Lade zusätzliche Daten:</translation> </message> <message> - <location filename="../SettingsView.ui" line="670"/> + <location filename="../SettingsView.ui" line="701"/> <source>GB BIOS file:</source> - <translation>BIOS-Datei für GB:</translation> + <translation>Datei mit GB-BIOS:</translation> </message> <message> - <location filename="../SettingsView.ui" line="736"/> + <location filename="../SettingsView.ui" line="767"/> <source>GBA BIOS file:</source> - <translation>BIOS-Datei für GBA:</translation> + <translation>Datei mit GBA-BIOS:</translation> </message> <message> - <location filename="../SettingsView.ui" line="743"/> + <location filename="../SettingsView.ui" line="774"/> <source>GBC BIOS file:</source> - <translation>BIOS-Datei für GBC:</translation> + <translation>Datei mit GBC-BIOS:</translation> </message> <message> - <location filename="../SettingsView.ui" line="778"/> + <location filename="../SettingsView.ui" line="809"/> <source>Save games</source> <translation>Spielstände</translation> </message> <message> - <location filename="../SettingsView.ui" line="812"/> - <location filename="../SettingsView.ui" line="860"/> - <location filename="../SettingsView.ui" line="908"/> - <location filename="../SettingsView.ui" line="956"/> + <location filename="../SettingsView.ui" line="843"/> + <location filename="../SettingsView.ui" line="891"/> + <location filename="../SettingsView.ui" line="939"/> + <location filename="../SettingsView.ui" line="987"/> <source>Same directory as the ROM</source> - <translation>Gleiches Verzeichnis wie ROM</translation> + <translation>Verzeichnis der ROM-Datei</translation> </message> <message> - <location filename="../SettingsView.ui" line="826"/> + <location filename="../SettingsView.ui" line="857"/> <source>Save states</source> <translation>Savestates</translation> </message> <message> - <location filename="../SettingsView.ui" line="874"/> + <location filename="../SettingsView.ui" line="905"/> <source>Screenshots</source> <translation>Screenshots</translation> </message> <message> - <location filename="../SettingsView.ui" line="922"/> + <location filename="../SettingsView.ui" line="953"/> <source>Patches</source> <translation>Patches</translation> </message>

@@ -4240,86 +4258,91 @@ <translation>h.264</translation>

</message> <message> <location filename="../VideoView.ui" line="269"/> - <source>VP8</source> - <translation>VP8</translation> + <source>h.264 (NVENC)</source> + <translation>h.264 (NVENC)</translation> </message> <message> <location filename="../VideoView.ui" line="274"/> - <source>Xvid</source> - <translation>Xvid</translation> + <source>HEVC</source> + <translation>HEVC</translation> </message> <message> <location filename="../VideoView.ui" line="279"/> + <source>VP8</source> + <translation>VP8</translation> + </message> + <message> + <location filename="../VideoView.ui" line="284"/> <source>FFV1</source> <translation>FFV1</translation> </message> <message> - <location filename="../VideoView.ui" line="291"/> + <location filename="../VideoView.ui" line="296"/> <source>FLAC</source> <translation>FLAC</translation> </message> <message> - <location filename="../VideoView.ui" line="296"/> + <location filename="../VideoView.ui" line="301"/> <source>Opus</source> <translation>Opus</translation> </message> <message> - <location filename="../VideoView.ui" line="301"/> + <location filename="../VideoView.ui" line="306"/> <source>Vorbis</source> <translation>Vorbis</translation> </message> <message> - <location filename="../VideoView.ui" line="306"/> + <location filename="../VideoView.ui" line="311"/> <source>MP3</source> <translation>MP3</translation> </message> <message> - <location filename="../VideoView.ui" line="311"/> + <location filename="../VideoView.ui" line="316"/> <source>AAC</source> <translation>AAC</translation> </message> <message> - <location filename="../VideoView.ui" line="316"/> + <location filename="../VideoView.ui" line="321"/> <source>Uncompressed</source> <translation>Unkomprimiert</translation> </message> <message> - <location filename="../VideoView.ui" line="327"/> + <location filename="../VideoView.ui" line="332"/> <source> Bitrate (kbps)</source> <translation> Bitrate (kbps)</translation> </message> <message> - <location filename="../VideoView.ui" line="333"/> + <location filename="../VideoView.ui" line="338"/> <source>VBR </source> <translation>VBR </translation> </message> <message> - <location filename="../VideoView.ui" line="381"/> + <location filename="../VideoView.ui" line="386"/> <source>ABR</source> <translation>ABR</translation> </message> <message> - <location filename="../VideoView.ui" line="397"/> + <location filename="../VideoView.ui" line="402"/> <source>Dimensions</source> <translation>Abmessungen</translation> </message> <message> - <location filename="../VideoView.ui" line="403"/> + <location filename="../VideoView.ui" line="408"/> <source>:</source> <translation>:</translation> </message> <message> - <location filename="../VideoView.ui" line="413"/> + <location filename="../VideoView.ui" line="418"/> <source>×</source> <translation>×</translation> </message> <message> - <location filename="../VideoView.ui" line="463"/> + <location filename="../VideoView.ui" line="468"/> <source>Lock aspect ratio</source> <translation>Seitenverhältnis sperren</translation> </message> <message> - <location filename="../VideoView.ui" line="478"/> + <location filename="../VideoView.ui" line="483"/> <source>Show advanced</source> <translation>Erweiterte Optionen anzeigen</translation> </message>
A src/platform/qt/utils.cpp

@@ -0,0 +1,40 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "utils.h" + +#include <QObject> + +namespace QGBA { + +QString niceSizeFormat(size_t filesize) { + double size = filesize; + QString unit = "B"; + if (size >= 1024.0) { + size /= 1024.0; + unit = "kiB"; + } + if (size >= 1024.0) { + size /= 1024.0; + unit = "MiB"; + } + return QString("%0 %1").arg(size, 0, 'f', 1).arg(unit); +} +QString nicePlatformFormat(mPlatform platform) { + switch (platform) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + return QObject::tr("GBA"); +#endif +#ifdef M_CORE_GB + case PLATFORM_GB: + return QObject::tr("GB"); +#endif + default: + return QObject::tr("?"); + } +} + +}
A src/platform/qt/utils.h

@@ -0,0 +1,20 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_UTILS_H +#define QGBA_UTILS_H + +#include <mgba/core/core.h> + +#include <QString> + +namespace QGBA { + +QString niceSizeFormat(size_t filesize); +QString nicePlatformFormat(mPlatform platform); + +} + +#endif
M src/platform/sdl/CMakeLists.txtsrc/platform/sdl/CMakeLists.txt

@@ -6,7 +6,6 @@ pkg_search_module(SDL2 sdl2)

if (SDL2_FOUND) set(SDL_INCLUDE_DIR ${SDL2_INCLUDE_DIRS}) set(SDL_LIBRARY ${SDL2_LIBRARIES}) - set(SDLMAIN_LIBRARY "") link_directories(${SDL2_LIBDIR}) set(SDL_VERSION_DEBIAN "2-2.0-0") endif()
M src/platform/sdl/gl-common.csrc/platform/sdl/gl-common.c

@@ -38,6 +38,9 @@ renderer->glCtx = SDL_GL_CreateContext(renderer->window);

SDL_GL_SetSwapInterval(1); SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); renderer->player.window = renderer->window; + if (renderer->lockIntegerScaling) { + SDL_SetWindowMinimumSize(renderer->window, renderer->width, renderer->height); + } #else SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1); #ifdef COLOR_16_BIT
M src/platform/sdl/gl-sdl.csrc/platform/sdl/gl-sdl.c

@@ -38,6 +38,7 @@

mGLContextCreate(&renderer->gl); renderer->gl.d.user = renderer; renderer->gl.d.lockAspectRatio = renderer->lockAspectRatio; + renderer->gl.d.lockIntegerScaling = renderer->lockIntegerScaling; renderer->gl.d.filter = renderer->filter; renderer->gl.d.swap = mSDLGLCommonSwap; renderer->gl.d.init(&renderer->gl.d, 0);
M src/platform/sdl/gles2-sdl.csrc/platform/sdl/gles2-sdl.c

@@ -7,7 +7,8 @@ #include "main.h"

#include "gl-common.h" -#include "core/thread.h" +#include <mgba/core/core.h> +#include <mgba/core/thread.h> #ifndef __APPLE__ #include <malloc.h>

@@ -108,6 +109,7 @@

mGLES2ContextCreate(&renderer->gl2); renderer->gl2.d.user = renderer; renderer->gl2.d.lockAspectRatio = renderer->lockAspectRatio; + renderer->gl2.d.lockIntegerScaling = renderer->lockIntegerScaling; renderer->gl2.d.filter = renderer->filter; renderer->gl2.d.swap = mSDLGLCommonSwap; renderer->gl2.d.init(&renderer->gl2.d, 0);
M src/platform/sdl/main.csrc/platform/sdl/main.c

@@ -20,14 +20,13 @@ #include <mgba/core/input.h>

#include <mgba/core/thread.h> #include <mgba/internal/gba/input.h> -#include "feature/commandline.h" +#include <mgba/feature/commandline.h> #include <mgba-util/vfs.h> #include <SDL.h> #include <errno.h> #include <signal.h> -#include <sys/time.h> #define PORT "sdl"

@@ -37,7 +36,7 @@

static int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args); int main(int argc, char** argv) { - struct mSDLRenderer renderer = {}; + struct mSDLRenderer renderer = {0}; struct mCoreOptions opts = { .useBios = true,

@@ -115,6 +114,7 @@ renderer.fullscreen = renderer.core->opts.fullscreen;

#endif renderer.lockAspectRatio = renderer.core->opts.lockAspectRatio; + renderer.lockIntegerScaling = renderer.core->opts.lockIntegerScaling; renderer.filter = renderer.core->opts.resampleVideo; if (!mSDLInit(&renderer)) {

@@ -131,7 +131,7 @@ mSDLAttachPlayer(&renderer.events, &renderer.player);

mSDLPlayerLoadConfig(&renderer.player, mCoreConfigGetInput(&renderer.core->config)); #if SDL_VERSION_ATLEAST(2, 0, 0) - renderer.core->setRumble(renderer.core, &renderer.player.rumble.d); + renderer.core->setPeripheral(renderer.core, mPERIPH_RUMBLE, &renderer.player.rumble.d); #endif int ret;

@@ -185,30 +185,31 @@

renderer->audio.samples = renderer->core->opts.audioBuffers; renderer->audio.sampleRate = 44100; - bool didFail = !mSDLInitAudio(&renderer->audio, &thread); + bool didFail = !mCoreThreadStart(&thread); if (!didFail) { #if SDL_VERSION_ATLEAST(2, 0, 0) mSDLSetScreensaverSuspendable(&renderer->events, renderer->core->opts.suspendScreensaver); mSDLSuspendScreensaver(&renderer->events); #endif - if (mCoreThreadStart(&thread)) { + if (mSDLInitAudio(&renderer->audio, &thread)) { renderer->runloop(renderer, &thread); mSDLPauseAudio(&renderer->audio); - mCoreThreadJoin(&thread); + if (mCoreThreadHasCrashed(&thread)) { + didFail = true; + printf("The game crashed!\n"); + } } else { didFail = true; - printf("Could not run game. Are you sure the file exists and is a compatible game?\n"); + printf("Could not initialize audio.\n"); } - #if SDL_VERSION_ATLEAST(2, 0, 0) mSDLResumeScreensaver(&renderer->events); mSDLSetScreensaverSuspendable(&renderer->events, false); #endif - if (mCoreThreadHasCrashed(&thread)) { - didFail = true; - printf("The game crashed!\n"); - } + mCoreThreadJoin(&thread); + } else { + printf("Could not run game. Are you sure the file exists and is a compatible game?\n"); } renderer->core->unloadROM(renderer->core); return didFail;
M src/platform/sdl/main.hsrc/platform/sdl/main.h

@@ -65,6 +65,7 @@ int viewportHeight;

int ratio; bool lockAspectRatio; + bool lockIntegerScaling; bool filter; #ifdef BUILD_GL
M src/platform/sdl/sw-sdl.csrc/platform/sdl/sw-sdl.c

@@ -5,9 +5,10 @@ * 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 "main.h" -#include "core/thread.h" -#include "core/version.h" -#include "util/arm-algo.h" +#include <mgba/core/core.h> +#include <mgba/core/thread.h> +#include <mgba/core/version.h> +#include <mgba-util/arm-algo.h> static bool mSDLSWInit(struct mSDLRenderer* renderer); static void mSDLSWRunloop(struct mSDLRenderer* renderer, void* user);
M src/platform/test/fuzz-main.csrc/platform/test/fuzz-main.c

@@ -12,7 +12,7 @@ #include <mgba/gb/core.h>

#include <mgba/gba/core.h> #include <mgba/internal/gba/gba.h> -#include "feature/commandline.h" +#include <mgba/feature/commandline.h> #include <mgba-util/memory.h> #include <mgba-util/string.h> #include <mgba-util/vfs.h>

@@ -68,6 +68,9 @@ version(argv[0]);

return 0; } struct mCore* core = mCoreFind(args.fname); + if (!core) { + return 1; + } core->init(core); mCoreInitConfig(core, "fuzz"); applyArguments(&args, NULL, &core->config);

@@ -91,10 +94,15 @@

#ifdef __AFL_HAVE_MANUAL_CONTROL __AFL_INIT(); #endif + + bool cleanExit = true; + if (!mCoreLoadFile(core, args.fname)) { + cleanExit = false; + goto loadError; + } if (args.patch) { core->loadPatch(core, VFileOpen(args.patch, O_RDONLY)); } - mCoreLoadFile(core, args.fname); struct VFile* savestate = 0; struct VFile* savestateOverlay = 0;

@@ -155,21 +163,23 @@ if (savestateOverlay) {

savestateOverlay->close(savestateOverlay); } +loadError: freeArguments(&args); if (outputBuffer) { free(outputBuffer); } core->deinit(core); - return 0; + return !cleanExit; } static void _fuzzRunloop(struct mCore* core, int frames) { do { core->runFrame(core); + --frames; blip_clear(core->getAudioChannel(core, 0)); blip_clear(core->getAudioChannel(core, 1)); - } while (core->frameCounter(core) < frames && !_dispatchExiting); + } while (frames > 0 && !_dispatchExiting); } static void _fuzzShutdown(int signal) {
M src/platform/test/perf-main.csrc/platform/test/perf-main.c

@@ -11,7 +11,7 @@ #include <mgba/core/serialize.h>

#include <mgba/gb/core.h> #include <mgba/gba/core.h> -#include "feature/commandline.h" +#include <mgba/feature/commandline.h> #include <mgba-util/socket.h> #include <mgba-util/string.h> #include <mgba-util/vfs.h>
M src/platform/video-backend.hsrc/platform/video-backend.h

@@ -35,6 +35,7 @@ unsigned height;

bool filter; bool lockAspectRatio; + bool lockIntegerScaling; }; struct VideoShader {
M src/platform/wii/main.csrc/platform/wii/main.c

@@ -641,8 +641,8 @@ _reproj2(vmode->fbWidth * guiScale * wAdjust, vmode->efbHeight * guiScale * hAdjust);

} void _setup(struct mGUIRunner* runner) { - runner->core->setRotation(runner->core, &rotation); - runner->core->setRumble(runner->core, &rumble); + runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation); + runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble); _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_A, GBA_KEY_A); _mapKey(&runner->core->inputMap, GCN1_INPUT, PAD_BUTTON_B, GBA_KEY_B);
M src/util/circle-buffer.csrc/util/circle-buffer.c

@@ -125,6 +125,34 @@ #endif

return 2; } +size_t CircleBufferWrite(struct CircleBuffer* buffer, const void* input, size_t length) { + int8_t* data = buffer->writePtr; + if (buffer->size + sizeof(int16_t) > buffer->capacity) { + return 0; + } + size_t remaining = buffer->capacity - ((int8_t*) data - (int8_t*) buffer->data); + if (length <= remaining) { + memcpy(data, input, length); + if (length == remaining) { + buffer->writePtr = buffer->data; + } else { + buffer->writePtr = (int8_t*) data + length; + } + } else { + memcpy(data, input, remaining); + memcpy(buffer->data, (const int8_t*) input + remaining, length - remaining); + buffer->writePtr = (int8_t*) buffer->data + length - remaining; + } + + buffer->size += length; +#ifndef NDEBUG + if (!_checkIntegrity(buffer)) { + abort(); + } +#endif + return length; +} + int CircleBufferRead8(struct CircleBuffer* buffer, int8_t* value) { int8_t* data = buffer->readPtr; if (buffer->size < sizeof(int8_t)) {
M src/util/string.csrc/util/string.c

@@ -5,7 +5,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this

* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include <mgba-util/string.h> +#include <mgba-util/vector.h> + #include <string.h> + +DEFINE_VECTOR(StringList, char*); #ifndef HAVE_STRNDUP char* strndup(const char* start, size_t len) {
M src/util/vfs/vfs-dirent.csrc/util/vfs/vfs-dirent.c

@@ -147,7 +147,7 @@ }

static enum VFSType _vdeType(struct VDirEntry* vde) { struct VDirEntryDE* vdede = (struct VDirEntryDE*) vde; -#ifndef WIN32 +#if !defined(WIN32) && !defined(__HAIKU__) if (vdede->ent->d_type == DT_DIR) { return VFS_DIRECTORY; }
M src/util/vfs/vfs-fd.csrc/util/vfs/vfs-fd.c

@@ -166,7 +166,11 @@ UNUSED(buffer);

UNUSED(size); struct VFileFD* vfd = (struct VFileFD*) vf; #ifndef _WIN32 +#ifdef __HAIKU__ + futimens(vfd->fd, NULL); +#else futimes(vfd->fd, NULL); +#endif if (buffer && size) { return msync(buffer, size, MS_SYNC) == 0; }
A src/util/vfs/vfs-fifo.c

@@ -0,0 +1,102 @@

+/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <mgba-util/vfs.h> +#include <mgba-util/circle-buffer.h> + +struct VFileFIFO { + struct VFile d; + struct CircleBuffer* backing; +}; + +static bool _vffClose(struct VFile* vf); +static off_t _vffSeek(struct VFile* vf, off_t offset, int whence); +static ssize_t _vffRead(struct VFile* vf, void* buffer, size_t size); +static ssize_t _vffWrite(struct VFile* vf, const void* buffer, size_t size); +static void* _vffMap(struct VFile* vf, size_t size, int flags); +static void _vffUnmap(struct VFile* vf, void* memory, size_t size); +static void _vffTruncate(struct VFile* vf, size_t size); +static ssize_t _vffSize(struct VFile* vf); +static bool _vffSync(struct VFile* vf, const void* buffer, size_t size); + +struct VFile* VFileFIFO(struct CircleBuffer* backing) { + if (!backing) { + return NULL; + } + + struct VFileFIFO* vff = malloc(sizeof(*vff)); + if (!vff) { + return NULL; + } + + vff->backing = backing; + vff->d.close = _vffClose; + vff->d.seek = _vffSeek; + vff->d.read = _vffRead; + vff->d.readline = VFileReadline; + vff->d.write = _vffWrite; + vff->d.map = _vffMap; + vff->d.unmap = _vffUnmap; + vff->d.truncate = _vffTruncate; + vff->d.size = _vffSize; + vff->d.sync = _vffSync; + + return &vff->d; +} + + +static bool _vffClose(struct VFile* vf) { + free(vf); + return true; +} + +static off_t _vffSeek(struct VFile* vf, off_t offset, int whence) { + UNUSED(vf); + UNUSED(offset); + UNUSED(whence); + return 0; +} + +static ssize_t _vffRead(struct VFile* vf, void* buffer, size_t size) { + struct VFileFIFO* vff = (struct VFileFIFO*) vf; + return CircleBufferRead(vff->backing, buffer, size); +} + +static ssize_t _vffWrite(struct VFile* vf, const void* buffer, size_t size) { + struct VFileFIFO* vff = (struct VFileFIFO*) vf; + return CircleBufferWrite(vff->backing, buffer, size); +} + +static void* _vffMap(struct VFile* vf, size_t size, int flags) { + UNUSED(vf); + UNUSED(size); + UNUSED(flags); + return NULL; +} + +static void _vffUnmap(struct VFile* vf, void* memory, size_t size) { + UNUSED(vf); + UNUSED(memory); + UNUSED(size); +} + +static void _vffTruncate(struct VFile* vf, size_t size) { + struct VFileFIFO* vff = (struct VFileFIFO*) vf; + if (!size) { + CircleBufferClear(vff->backing); + } +} + +static ssize_t _vffSize(struct VFile* vf) { + struct VFileFIFO* vff = (struct VFileFIFO*) vf; + return CircleBufferSize(vff->backing); +} + +static bool _vffSync(struct VFile* vf, const void* buffer, size_t size) { + UNUSED(vf); + UNUSED(buffer); + UNUSED(size); + return true; +}
M src/util/vfs/vfs-mem.csrc/util/vfs/vfs-mem.c

@@ -4,12 +4,14 @@ * This Source Code Form is subject to the terms of the Mozilla Public

* License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include <mgba-util/vfs.h> +#include <mgba-util/math.h> #include <mgba-util/memory.h> struct VFileMem { struct VFile d; void* mem; size_t size; + size_t bufferSize; size_t offset; };

@@ -40,6 +42,7 @@ }

vfm->mem = mem; vfm->size = size; + vfm->bufferSize = size; vfm->offset = 0; vfm->d.close = _vfmClose; vfm->d.seek = _vfmSeek;

@@ -67,6 +70,7 @@ }

vfm->mem = (void*) mem; vfm->size = size; + vfm->bufferSize = size; vfm->offset = 0; vfm->d.close = _vfmClose; vfm->d.seek = _vfmSeek;

@@ -89,8 +93,9 @@ return 0;

} vfm->size = size; + vfm->bufferSize = toPow2(size); if (size) { - vfm->mem = anonymousMemoryMap(size); + vfm->mem = anonymousMemoryMap(vfm->bufferSize); if (mem) { memcpy(vfm->mem, mem, size); }

@@ -113,15 +118,19 @@ return &vfm->d;

} void _vfmExpand(struct VFileMem* vfm, size_t newSize) { - void* oldBuf = vfm->mem; - vfm->mem = anonymousMemoryMap(newSize); - if (oldBuf) { - if (newSize < vfm->size) { - memcpy(vfm->mem, oldBuf, newSize); - } else { - memcpy(vfm->mem, oldBuf, vfm->size); + size_t alignedSize = toPow2(newSize); + if (alignedSize > vfm->bufferSize) { + void* oldBuf = vfm->mem; + vfm->mem = anonymousMemoryMap(alignedSize); + if (oldBuf) { + if (newSize < vfm->size) { + memcpy(vfm->mem, oldBuf, newSize); + } else { + memcpy(vfm->mem, oldBuf, vfm->size); + } + mappedMemoryFree(oldBuf, vfm->bufferSize); } - mappedMemoryFree(oldBuf, vfm->size); + vfm->bufferSize = alignedSize; } vfm->size = newSize; }

@@ -135,7 +144,7 @@ }

bool _vfmCloseFree(struct VFile* vf) { struct VFileMem* vfm = (struct VFileMem*) vf; - mappedMemoryFree(vfm->mem, vfm->size); + mappedMemoryFree(vfm->mem, vfm->bufferSize); vfm->mem = 0; free(vfm); return true;
M version.cmakeversion.cmake

@@ -10,11 +10,11 @@ set(SUMMARY "${PROJECT_NAME} Game Boy Advance Emulator")

find_program(GIT git) if(GIT AND NOT SKIP_GIT) - execute_process(COMMAND ${GIT} describe --always --abbrev=40 --dirty OUTPUT_VARIABLE GIT_COMMIT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${GIT} describe --always --dirty OUTPUT_VARIABLE GIT_COMMIT_SHORT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${GIT} symbolic-ref --short HEAD OUTPUT_VARIABLE GIT_BRANCH ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${GIT} rev-list HEAD --count OUTPUT_VARIABLE GIT_REV ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${GIT} describe --tag --exact-match OUTPUT_VARIABLE GIT_TAG ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${GIT} describe --always --abbrev=40 --dirty WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE GIT_COMMIT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${GIT} describe --always --dirty WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE GIT_COMMIT_SHORT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${GIT} symbolic-ref --short HEAD WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE GIT_BRANCH ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${GIT} rev-list HEAD --count WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE GIT_REV ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${GIT} describe --tag --exact-match WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE GIT_TAG ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) endif() if(NOT GIT_REV)