diff --git a/CHANGES b/CHANGES index 656e7b80f..db620fc67 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,17 @@ Features: - 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 @@ Bugfixes: - 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 @@ Misc: - 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: diff --git a/CMakeLists.txt b/CMakeLists.txt index 04a4f1c06..e40465c6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.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 @@ else() endif() set(DISABLE_FRONTENDS ON) set(MINIMAL_CORE ON) + set(ENABLE_EXTRA ON) endif() check_function_exists(chmod HAVE_CHMOD) @@ -392,6 +395,7 @@ find_feature(USE_SQLITE3 "sqlite3") 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 @@ if(M_CORE_GB) 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 @@ if(M_CORE_GBA) ${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 @@ if(NOT MINIMAL_CORE) ${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 @@ if(BUILD_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() diff --git a/include/mgba-util/circle-buffer.h b/include/mgba-util/circle-buffer.h index 28d436e03..dee1f1f2a 100644 --- a/include/mgba-util/circle-buffer.h +++ b/include/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); diff --git a/include/mgba-util/common.h b/include/mgba-util/common.h index 4f36c0a17..63e3dd30d 100644 --- a/include/mgba-util/common.h +++ b/include/mgba-util/common.h @@ -56,6 +56,11 @@ typedef intptr_t ssize_t; #include #endif +#ifdef PSP2 +// For PATH_MAX on modern toolchains +#include +#endif + #ifndef SSIZE_MAX #define SSIZE_MAX ((ssize_t) (SIZE_MAX >> 1)) #endif diff --git a/include/mgba-util/patch/fast.h b/include/mgba-util/patch/fast.h index 6657a0419..ee59cd23f 100644 --- a/include/mgba-util/patch/fast.h +++ b/include/mgba-util/patch/fast.h @@ -13,7 +13,7 @@ CXX_GUARD_START #include #include -#define PATCH_FAST_EXTENT 256 +#define PATCH_FAST_EXTENT 128 struct PatchFastExtent { size_t length; diff --git a/include/mgba-util/platform/posix/threading.h b/include/mgba-util/platform/posix/threading.h index b880c548d..f79d97b10 100644 --- a/include/mgba-util/platform/posix/threading.h +++ b/include/mgba-util/platform/posix/threading.h @@ -14,6 +14,8 @@ CXX_GUARD_START #include #if defined(__FreeBSD__) || defined(__OpenBSD__) #include +#elif defined(__HAIKU__) +#include #endif #define THREAD_ENTRY void* @@ -88,6 +90,9 @@ static inline int ThreadSetName(const char* 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); #else diff --git a/include/mgba-util/vector.h b/include/mgba-util/vector.h index 664221b7d..ae542d41b 100644 --- a/include/mgba-util/vector.h +++ b/include/mgba-util/vector.h @@ -92,6 +92,8 @@ CXX_GUARD_START dest->size = src->size; \ } \ +DECLARE_VECTOR(StringList, char*); + CXX_GUARD_END #endif diff --git a/include/mgba-util/vfs.h b/include/mgba-util/vfs.h index 02c6d94e1..562fa52b5 100644 --- a/include/mgba-util/vfs.h +++ b/include/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); diff --git a/include/mgba/core/cheats.h b/include/mgba/core/cheats.h index 7960ae58b..25ad3bbe1 100644 --- a/include/mgba/core/cheats.h +++ b/include/mgba/core/cheats.h @@ -48,7 +48,6 @@ struct mCheat { mLOG_DECLARE_CATEGORY(CHEATS); DECLARE_VECTOR(mCheatList, struct mCheat); -DECLARE_VECTOR(StringList, char*); struct mCheatDevice; struct mCheatSet { diff --git a/include/mgba/core/config.h b/include/mgba/core/config.h index 6dbc73dc4..0c6426e7a 100644 --- a/include/mgba/core/config.h +++ b/include/mgba/core/config.h @@ -42,6 +42,7 @@ struct mCoreOptions { int width; int height; bool lockAspectRatio; + bool lockIntegerScaling; bool resampleVideo; bool suspendScreensaver; char* shader; diff --git a/include/mgba/core/core.h b/include/mgba/core/core.h index c3a56f4ac..5507a54a0 100644 --- a/include/mgba/core/core.h +++ b/include/mgba/core/core.h @@ -26,10 +26,10 @@ CXX_GUARD_START 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 @@ enum mCoreChecksumType { 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 @@ struct mCore { 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,18 +138,33 @@ 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); diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h index 933e8d2d5..7f0515a60 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -38,6 +38,7 @@ struct mCoreCallbacks { 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 { @@ -102,6 +105,13 @@ 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 #endif diff --git a/include/mgba/core/rewind.h b/include/mgba/core/rewind.h index 8762ff76a..e839209fc 100644 --- a/include/mgba/core/rewind.h +++ b/include/mgba/core/rewind.h @@ -11,6 +11,9 @@ CXX_GUARD_START #include +#ifndef DISABLE_THREADING +#include +#endif DECLARE_VECTOR(mCoreRewindPatches, struct PatchFast); @@ -22,9 +25,16 @@ struct mCoreRewindContext { 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; diff --git a/include/mgba/core/thread.h b/include/mgba/core/thread.h index f586bfd3b..3c7a168e9 100644 --- a/include/mgba/core/thread.h +++ b/include/mgba/core/thread.h @@ -62,6 +62,7 @@ struct mCoreThread { ThreadCallback resetCallback; ThreadCallback cleanCallback; ThreadCallback frameCallback; + ThreadCallback sleepCallback; void* userData; void (*run)(struct mCoreThread*); diff --git a/src/feature/commandline.h b/include/mgba/feature/commandline.h similarity index 95% rename from src/feature/commandline.h rename to include/mgba/feature/commandline.h index 322c8e87b..b47442c21 100644 --- a/src/feature/commandline.h +++ b/include/mgba/feature/commandline.h @@ -10,6 +10,8 @@ CXX_GUARD_START +#include + #include struct mArguments { @@ -21,6 +23,8 @@ struct mArguments { int logLevel; int frameskip; + struct Table configOverrides; + enum mDebuggerType debuggerType; bool debugAtStart; bool showHelp; diff --git a/include/mgba/internal/gba/renderers/thread-proxy.h b/include/mgba/feature/thread-proxy.h similarity index 57% rename from include/mgba/internal/gba/renderers/thread-proxy.h rename to include/mgba/feature/thread-proxy.h index 6fa07215a..cb663d506 100644 --- a/include/mgba/internal/gba/renderers/thread-proxy.h +++ b/include/mgba/feature/thread-proxy.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2015 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 @@ -11,36 +11,29 @@ CXX_GUARD_START #include +#include "video-logger.h" #include #include -enum GBAVideoThreadProxyState { +enum mVideoThreadProxyState { PROXY_THREAD_STOPPED = 0, PROXY_THREAD_IDLE, PROXY_THREAD_BUSY }; -struct GBAVideoThreadProxyRenderer { - struct GBAVideoRenderer d; - struct GBAVideoRenderer* backend; +struct mVideoThreadProxy { + struct mVideoLogger d; Thread thread; Condition fromThreadCond; Condition toThreadCond; Mutex mutex; - enum GBAVideoThreadProxyState threadState; + enum mVideoThreadProxyState 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); +void mVideoThreadProxyCreate(struct mVideoThreadProxy* renderer); CXX_GUARD_END diff --git a/include/mgba/feature/video-logger.h b/include/mgba/feature/video-logger.h new file mode 100644 index 000000000..f194433a2 --- /dev/null +++ b/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 + +CXX_GUARD_START + +#include + +#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 diff --git a/include/mgba/gb/core.h b/include/mgba/gb/core.h index 10b284803..90ddccad6 100644 --- a/include/mgba/gb/core.h +++ b/include/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 diff --git a/include/mgba/gb/interface.h b/include/mgba/gb/interface.h index 098a5e5dd..03b836865 100644 --- a/include/mgba/gb/interface.h +++ b/include/mgba/gb/interface.h @@ -34,6 +34,15 @@ enum GBMemoryBankControllerType { 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 diff --git a/include/mgba/gba/core.h b/include/mgba/gba/core.h index cb4c1a862..5cb0e468c 100644 --- a/include/mgba/gba/core.h +++ b/include/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 diff --git a/include/mgba/gba/interface.h b/include/mgba/gba/interface.h index 3a5412645..90f5e0697 100644 --- a/include/mgba/gba/interface.h +++ b/include/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*); diff --git a/include/mgba/internal/debugger/debugger.h b/include/mgba/internal/debugger/debugger.h index 3cd228dc7..d73afd39e 100644 --- a/include/mgba/internal/debugger/debugger.h +++ b/include/mgba/internal/debugger/debugger.h @@ -87,13 +87,14 @@ 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; diff --git a/include/mgba/internal/debugger/symbols.h b/include/mgba/internal/debugger/symbols.h new file mode 100644 index 000000000..e8456f8c1 --- /dev/null +++ b/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 + +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 diff --git a/include/mgba/internal/gb/debugger/symbols.h b/include/mgba/internal/gb/debugger/symbols.h new file mode 100644 index 000000000..ae893f289 --- /dev/null +++ b/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 + +CXX_GUARD_START + +struct mDebuggerSymbols; +struct VFile; +void GBLoadSymbols(struct mDebuggerSymbols*, struct VFile* vf); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/internal/gb/memory.h b/include/mgba/internal/gb/memory.h index 8e3d118e6..11f1e929f 100644 --- a/include/mgba/internal/gb/memory.h +++ b/include/mgba/internal/gb/memory.h @@ -85,6 +85,7 @@ enum GBMBC7MachineState { struct GBMBC1State { int mode; + int multicartStride; }; struct GBMBC7State { @@ -161,14 +162,13 @@ void GBMemorySwitchWramBank(struct GBMemory* memory, int bank); 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); struct GBSerializedState; diff --git a/include/mgba/internal/gb/renderers/proxy.h b/include/mgba/internal/gb/renderers/proxy.h new file mode 100644 index 000000000..244c5e39b --- /dev/null +++ b/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 + +CXX_GUARD_START + +#include +#include + +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 diff --git a/include/mgba/internal/gb/sio.h b/include/mgba/internal/gb/sio.h index 004cf16e8..50d62868b 100644 --- a/include/mgba/internal/gb/sio.h +++ b/include/mgba/internal/gb/sio.h @@ -12,6 +12,7 @@ CXX_GUARD_START #include #include +#include #define MAX_GBS 2 @@ -34,15 +35,6 @@ struct GBSIO { 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); DECL_BIT(GBRegisterSC, ShiftClock, 0); DECL_BIT(GBRegisterSC, ClockSpeed, 1); diff --git a/include/mgba/internal/gb/video.h b/include/mgba/internal/gb/video.h index 67bf96405..60534636b 100644 --- a/include/mgba/internal/gb/video.h +++ b/include/mgba/internal/gb/video.h @@ -20,9 +20,9 @@ enum { 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 @@ struct GBVideoRenderer { 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 @@ struct GBVideoRenderer { uint8_t* vram; union GBOAM* oam; struct mTileCache* cache; + + bool disableBG; + bool disableOBJ; + bool disableWIN; }; DECL_BITFIELD(GBRegisterLCDC, uint8_t); @@ -119,6 +124,7 @@ struct GBVideo { int ocpIndex; bool ocpIncrement; + uint16_t dmgPalette[4]; uint16_t palette[64]; int32_t frameCounter; @@ -138,6 +144,8 @@ 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); void GBVideoDeserialize(struct GBVideo* video, const struct GBSerializedState* state); diff --git a/include/mgba/internal/gba/gba.h b/include/mgba/internal/gba/gba.h index e5b1f8899..5710864e0 100644 --- a/include/mgba/internal/gba/gba.h +++ b/include/mgba/internal/gba/gba.h @@ -99,7 +99,6 @@ struct GBA { struct mAVStream* stream; struct mKeyCallback* keyCallback; - struct mStopCallback* stopCallback; struct mCoreCallbacksList coreCallbacks; enum GBAIdleLoopOptimization idleOptimization; @@ -176,6 +175,8 @@ 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); diff --git a/include/mgba/internal/gba/renderers/proxy.h b/include/mgba/internal/gba/renderers/proxy.h new file mode 100644 index 000000000..c1044c77e --- /dev/null +++ b/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 + +CXX_GUARD_START + +#include +#include + +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 diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index 7f1e5e128..14bddf6e2 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -334,9 +334,6 @@ struct VDir; 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 diff --git a/include/mgba/internal/lr35902/debugger/debugger.h b/include/mgba/internal/lr35902/debugger/debugger.h index 06801d77b..85712ee96 100644 --- a/include/mgba/internal/lr35902/debugger/debugger.h +++ b/include/mgba/internal/lr35902/debugger/debugger.h @@ -12,6 +12,9 @@ CXX_GUARD_START #include +#include + + struct LR35902DebugBreakpoint { uint16_t address; int segment; @@ -23,6 +26,12 @@ struct LR35902DebugWatchpoint { 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 LR35902Debugger { struct LR35902DebugBreakpointList breakpoints; struct LR35902DebugWatchpointList watchpoints; + struct LR35902Memory originalMemory; + + const struct LR35902Segment* segments; }; struct mDebuggerPlatform* LR35902DebuggerPlatformCreate(void); diff --git a/include/mgba/internal/lr35902/debugger/memory-debugger.h b/include/mgba/internal/lr35902/debugger/memory-debugger.h new file mode 100644 index 000000000..140e176e4 --- /dev/null +++ b/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 + +CXX_GUARD_START + +struct LR35902Debugger; + +void LR35902DebuggerInstallMemoryShim(struct LR35902Debugger* debugger); +void LR35902DebuggerRemoveMemoryShim(struct LR35902Debugger* debugger); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/internal/lr35902/lr35902.h b/include/mgba/internal/lr35902/lr35902.h index 59811cc8b..3e586f0ed 100644 --- a/include/mgba/internal/lr35902/lr35902.h +++ b/include/mgba/internal/lr35902/lr35902.h @@ -54,6 +54,8 @@ struct LR35902Memory { 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; diff --git a/res/info.plist.in b/res/info.plist.in index 3a32700f9..f37904dee 100644 --- a/res/info.plist.in +++ b/res/info.plist.in @@ -44,6 +44,17 @@ CFBundleTypeRole Viewer + + CFBundleTypeExtensions + + gb + gbc + + CFBundleTypeName + Game Boy ROM Image + CFBundleTypeRole + Viewer + diff --git a/res/patrons.txt b/res/patrons.txt index e10e13221..0cf2e6e28 100644 --- a/res/patrons.txt +++ b/res/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 diff --git a/res/shaders/fish.shader/fish.fs b/res/shaders/fish.shader/fish.fs new file mode 100644 index 000000000..7d750fd7b --- /dev/null +++ b/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); + } diff --git a/res/shaders/fish.shader/manifest.ini b/res/shaders/fish.shader/manifest.ini new file mode 100644 index 000000000..0a47be032 --- /dev/null +++ b/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 diff --git a/res/shaders/gba-color.shader/gba-color.fs b/res/shaders/gba-color.shader/gba-color.fs new file mode 100644 index 000000000..09177ddfe --- /dev/null +++ b/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))); +} diff --git a/res/shaders/gba-color.shader/manifest.ini b/res/shaders/gba-color.shader/manifest.ini new file mode 100644 index 000000000..4ab3403ec --- /dev/null +++ b/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 diff --git a/src/arm/debugger/debugger.c b/src/arm/debugger/debugger.c index 76b474841..03ae0e90e 100644 --- a/src/arm/debugger/debugger.c +++ b/src/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 @@ void ARMDebuggerClearSoftwareBreakpoint(struct mDebuggerPlatform* d, uint32_t ad } } -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 @@ static bool ARMDebuggerHasBreakpoints(struct mDebuggerPlatform* 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 @@ static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform* d, uint32_t addre 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; diff --git a/src/core/cheats.c b/src/core/cheats.c index 83bdde0b8..5085a3d67 100644 --- a/src/core/cheats.c +++ b/src/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) { diff --git a/src/core/config.c b/src/core/config.c index b4d654c54..cbb6f32ee 100644 --- a/src/core/config.c +++ b/src/core/config.c @@ -349,6 +349,9 @@ void mCoreConfigMap(const struct mCoreConfig* config, struct mCoreOptions* opts) 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 @@ void mCoreConfigLoadDefaults(struct mCoreConfig* config, const struct mCoreOptio 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); } diff --git a/src/core/core.c b/src/core/core.c index 7eacdcfc8..b82bab950 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -18,8 +18,11 @@ #include #include #endif +#ifndef MINIMAL_CORE +#include +#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 @@ struct mCore* mCoreFindVF(struct VFile* vf) { 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; @@ -115,6 +121,35 @@ bool mCoreLoadFile(struct mCore* core, const char* path) { 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); + } + return ret; +} + bool mCoreAutoloadSave(struct mCore* core) { return core->loadSave(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.save, ".sav", O_CREAT | O_RDWR)); } diff --git a/src/core/input.c b/src/core/input.c index fe2096ccd..6d391ee3b 100644 --- a/src/core/input.c +++ b/src/core/input.c @@ -11,7 +11,7 @@ #include -#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 @@ void mInputUnbindHat(struct mInputMap* map, uint32_t type, int id) { mInputHatListResize(&impl->hats, -1); } else { struct mInputHatBindings* description = mInputHatListGetPointer(&impl->hats, id); - memset(description, -1, sizeof(&description)); + memset(description, -1, sizeof(*description)); } } diff --git a/src/core/rewind.c b/src/core/rewind.c index 9919acf2e..15ac69a95 100644 --- a/src/core/rewind.c +++ b/src/core/rewind.c @@ -12,7 +12,13 @@ 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 @@ void mCoreRewindContextInit(struct mCoreRewindContext* context, size_t entries) 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 @@ void mCoreRewindContextDeinit(struct mCoreRewindContext* context) { } 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 @@ void mCoreRewindAppend(struct mCoreRewindContext* context, struct mCore* core) { 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 @@ bool mCoreRewindRestore(struct mCoreRewindContext* context, struct mCore* core) 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 + diff --git a/src/core/thread.c b/src/core/thread.c index 686330615..424260b7e 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -120,6 +120,16 @@ void _crashed(void* context) { _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 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { .videoFrameStarted = _frameStarted, .videoFrameEnded = _frameEnded, .coreCrashed = _crashed, + .sleep = _coreSleep, .context = threadContext }; core->addCoreCallbacks(core, &callbacks); @@ -157,7 +168,7 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { } 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; } diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index d5b6dc535..70cff2a5f 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -5,6 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include + #include #include #include @@ -135,6 +137,10 @@ static void _disassemble(struct CLIDebugger* debugger, struct CLIDebugVector* dv 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 @@ static void _readByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { 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 @@ static void _readHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* d 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 @@ static void _readWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { 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 @@ static void _writeByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) 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 @@ static void _writeHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* 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 @@ static void _writeWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) } 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 @@ static void _dumpByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { } 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 @@ static void _dumpHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* d } 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 @@ static void _dumpWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { } 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 @@ static void _setBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* 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 @@ static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* 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 @@ static void _setReadWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVect 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 @@ static void _setWriteWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVec 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 @@ static void _clearBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector 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 uint32_t _performOperation(enum Operation operation, uint32_t current, ui 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 @@ void CLIDebuggerAttachSystem(struct CLIDebugger* debugger, struct CLIDebuggerSys } void CLIDebuggerAttachBackend(struct CLIDebugger* debugger, struct CLIDebuggerBackend* backend) { + if (debugger->backend == backend) { + return; + } if (debugger->backend && debugger->backend->deinit) { debugger->backend->deinit(debugger->backend); } diff --git a/src/debugger/debugger.c b/src/debugger/debugger.c index 49da5fde6..bf7b9d99c 100644 --- a/src/debugger/debugger.c +++ b/src/debugger/debugger.c @@ -62,6 +62,9 @@ void mDebuggerAttach(struct mDebugger* debugger, struct mCore* core) { 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); diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index b497c24d7..3c149c9fc 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -321,13 +321,31 @@ static void _readGPRs(struct GDBStub* stub, const char* message) { 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; _sendMessage(stub); } @@ -477,16 +495,16 @@ static void _setBreakpoint(struct GDBStub* stub, const char* message) { 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 @@ static void _clearBreakpoint(struct GDBStub* stub, const char* message) { 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; diff --git a/src/debugger/symbols.c b/src/debugger/symbols.c new file mode 100644 index 000000000..a9c874b54 --- /dev/null +++ b/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 + +#include + +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); +} diff --git a/src/feature/commandline.c b/src/feature/commandline.c index ae9f72c18..ccb44f105 100644 --- a/src/feature/commandline.c +++ b/src/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 #include #include @@ -38,6 +38,7 @@ static const struct option _options[] = { { "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 const struct option _options[] = { 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 @@ bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct 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 @@ bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct 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 @@ void applyArguments(const struct mArguments* args, struct mSubParser* subparser, if (args->bios) { mCoreConfigSetOverrideValue(config, "bios", args->bios); } + HashTableEnumerate(&args->configOverrides, _tableApply, config); if (subparser) { subparser->apply(subparser, config); } @@ -164,6 +187,8 @@ void freeArguments(struct mArguments* args) { free(args->bios); args->bios = 0; + + HashTableDeinit(&args->configOverrides); } void initParserForGraphics(struct mSubParser* parser, struct mGraphicsOpts* opts) { @@ -209,18 +234,20 @@ void _applyGraphicsArgs(struct mSubParser* parser, struct mCoreConfig* config) { 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); } diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index a902d6abf..a7a206788 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -304,6 +304,14 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { } 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 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { 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 @@ void _ffmpegPostAudioFrame(struct mAVStream* stream, int16_t left, int16_t right 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); diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index c26cbe662..ad59ae893 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -298,7 +298,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { 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); diff --git a/src/feature/thread-proxy.c b/src/feature/thread-proxy.c new file mode 100644 index 000000000..16b3db70c --- /dev/null +++ b/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 + +#include +#include +#include + +#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 diff --git a/src/feature/video-logger.c b/src/feature/video-logger.c new file mode 100644 index 000000000..865bd24ea --- /dev/null +++ b/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 + +#include +#include +#include +#include + +#ifdef M_CORE_GBA +#include +#endif +#ifdef M_CORE_GB +#include +#endif + +#ifdef USE_ZLIB +#include +#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; +} diff --git a/src/gb/audio.c b/src/gb/audio.c index 7bed5d621..8fbca6d9b 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -640,8 +640,8 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { 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; } } diff --git a/src/gb/core.c b/src/gb/core.c index 749ea19f2..339eaa477 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -6,13 +6,17 @@ #include #include +#include #include +#include #include +#include #include #include #include #include #include +#include #include #include #include @@ -25,9 +29,40 @@ #include #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 @@ static void _GBCoreDeinit(struct mCore* core) { 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 @@ static void _GBCoreLoadConfig(struct mCore* core, const struct mCoreConfig* conf 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 bool _GBCoreSupportsDebuggerType(struct mCore* core, enum mDebuggerType t 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 @@ static void _GBCoreDetachDebugger(struct mCore* core) { 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 @@ static bool _GBCoreSavedataRestore(struct mCore* core, const void* sram, size_t 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 @@ struct mCore* GBCoreCreate(void) { 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 @@ struct mCore* GBCoreCreate(void) { 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 @@ struct mCore* GBCoreCreate(void) { 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 diff --git a/src/gb/extra/cli.c b/src/gb/debugger/cli.c similarity index 100% rename from src/gb/extra/cli.c rename to src/gb/debugger/cli.c diff --git a/src/gb/debugger/symbols.c b/src/gb/debugger/symbols.c new file mode 100644 index 000000000..4fc113433 --- /dev/null +++ b/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 + +#include +#include +#include + +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); + } +} diff --git a/src/gb/extra/proxy.c b/src/gb/extra/proxy.c new file mode 100644 index 000000000..2b015ae6c --- /dev/null +++ b/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 + +#include +#include +#include + +#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); + } +} diff --git a/src/gb/gb.c b/src/gb/gb.c index 8e227a816..a97fe97ef 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -124,7 +124,7 @@ bool GBLoadROM(struct GB* gb, struct VFile* vf) { 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; @@ -135,12 +135,6 @@ bool GBLoadROM(struct GB* gb, struct VFile* vf) { return true; } -bool GBLoadSave(struct GB* gb, struct VFile* vf) { - gb->sramVf = vf; - gb->sramRealVf = vf; - return vf; -} - static void GBSramDeinit(struct GB* gb) { if (gb->sramVf) { gb->sramVf->unmap(gb->sramVf, gb->memory.sram, gb->sramSize); @@ -154,6 +148,16 @@ static void GBSramDeinit(struct GB* gb) { 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 @@ void GBUnloadROM(struct GB* gb) { gb->romVf = NULL; } gb->memory.rom = NULL; + gb->memory.mbcType = GB_MBC_AUTODETECT; gb->isPristine = false; GBSavedataUnmask(gb); @@ -385,7 +390,9 @@ bool GBIsBIOS(struct VFile* vf) { 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); diff --git a/src/gb/io.c b/src/gb/io.c index b7e234ca7..8b4102c0d 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -382,7 +382,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t 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; } diff --git a/src/gb/mbc.c b/src/gb/mbc.c index efc294419..0710f10c0 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -46,6 +46,33 @@ void GBMBCSwitchBank(struct GB* gb, int bank) { } } +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 @@ void GBMBCInit(struct GB* gb) { 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: @@ -172,6 +204,14 @@ void GBMBCInit(struct GB* gb) { 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); if (gb->memory.mbcType == GB_MBC3_RTC) { @@ -233,6 +273,7 @@ static void _latchRtc(struct mRTCSource* rtc, uint8_t* rtcRegs, time_t* rtcLastL 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 @@ void _GBMBC1(struct GB* gb, uint16_t address, uint8_t value) { 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; diff --git a/src/gb/memory.c b/src/gb/memory.c index eb709b01b..8484941b7 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -16,6 +16,33 @@ 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 @@ void GBMemoryInit(struct GB* gb) { 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 @@ void GBMemoryReset(struct GB* gb) { 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 @@ void GBMemorySwitchWramBank(struct GBMemory* memory, int bank) { 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 @@ uint8_t GBLoad8(struct LR35902Core* cpu, uint16_t address) { 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 @@ void GBStore8(struct LR35902Core* cpu, uint16_t address, int8_t 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 @@ void GBStore8(struct LR35902Core* cpu, uint16_t address, int8_t 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 GBMemoryWriteHDMA5(struct GB* gb, uint8_t value) { 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 @@ void _GBMemoryHDMAService(struct mTiming* timing, void* context, uint32_t cycles } } -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 @@ void GBPatch8(struct LR35902Core* cpu, uint16_t address, int8_t value, int8_t* o } 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 @@ void _pristineCow(struct GB* gb) { } gb->memory.rom = newRom; GBMBCSwitchBank(gb, gb->memory.currentBank); + gb->isPristine = false; } diff --git a/src/gb/overrides.c b/src/gb/overrides.c index d86f08f5f..70d11cd22 100644 --- a/src/gb/overrides.c +++ b/src/gb/overrides.c @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -102,6 +103,7 @@ void GBOverrideApply(struct GB* gb, const struct GBCartridgeOverride* override) if (override->mbc != GB_MBC_AUTODETECT) { gb->memory.mbcType = override->mbc; + GBMBCInit(gb); } } diff --git a/src/gb/renderers/software.c b/src/gb/renderers/software.c index 61daa9f98..1474a86a5 100644 --- a/src/gb/renderers/software.c +++ b/src/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 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,12 +44,17 @@ void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer* renderer) { 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* 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 @@ static void GBVideoSoftwareRendererWriteVRAM(struct GBVideoRenderer* renderer, u } } +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 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i 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); @@ -197,6 +200,9 @@ static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer) mappedMemoryFree(softwareRenderer->temporaryBuffer, GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS * 4); softwareRenderer->temporaryBuffer = 0; } + if (!GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) { + _clearScreen(softwareRenderer); + } softwareRenderer->currentWy = 0; } diff --git a/src/gb/video.c b/src/gb/video.c index 465380fa9..8794a953c 100644 --- a/src/gb/video.c +++ b/src/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 @@ static struct GBVideoRenderer dummyRenderer = { .deinit = GBVideoDummyRendererDeinit, .writeVideoRegister = GBVideoDummyRendererWriteVideoRegister, .writeVRAM = GBVideoDummyRendererWriteVRAM, + .writeOAM = GBVideoDummyRendererWriteOAM, .writePalette = GBVideoDummyRendererWritePalette, .drawRange = GBVideoDummyRendererDrawRange, .finishScanline = GBVideoDummyRendererFinishScanline, @@ -61,6 +63,11 @@ void GBVideoInit(struct GBVideo* 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 @@ void GBVideoAssociateRenderer(struct GBVideo* video, struct GBVideoRenderer* ren 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 @@ void _endMode0(struct mTiming* timing, void* context, uint32_t cyclesLate) { 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 @@ void _endMode1(struct mTiming* timing, void* context, uint32_t cyclesLate) { 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 @@ void _endMode1(struct mTiming* timing, void* context, uint32_t cyclesLate) { 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 @@ void _endMode2(struct mTiming* timing, void* context, uint32_t cyclesLate) { _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 _endMode2(struct mTiming* timing, void* context, uint32_t cyclesLate) { 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 @@ void _endMode3(struct mTiming* timing, void* context, uint32_t cyclesLate) { } 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 @@ void _updateFrameCount(struct mTiming* timing, void* context, uint32_t cyclesLat 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,17 +282,16 @@ void _updateFrameCount(struct mTiming* timing, void* context, uint32_t cyclesLat 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); - } } static void _cleanOAM(struct GBVideo* video, int y) { @@ -312,13 +346,16 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) { 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 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) { 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 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) { } 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 @@ void GBVideoSwitchBank(struct GBVideo* video, uint8_t value) { 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); @@ -455,6 +501,12 @@ static void GBVideoDummyRendererWriteVRAM(struct GBVideoRenderer* renderer, uint } } +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) { UNUSED(value); if (renderer->cache) { diff --git a/src/gba/audio.c b/src/gba/audio.c index 2ceba8a5a..be736e78b 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -297,8 +297,8 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { 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; } } diff --git a/src/gba/bios.c b/src/gba/bios.c index 9d8b6ccdc..199ffab8f 100644 --- a/src/gba/bios.c +++ b/src/gba/bios.c @@ -271,7 +271,7 @@ static void _Div(struct GBA* gba, int32_t num, int32_t denom) { } } -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 @@ static int16_t _ArcTan(int16_t i) { 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 @@ static int16_t _ArcTan2(int16_t x, int16_t y) { 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 @@ void GBASwi16(struct ARMCore* cpu, int immediate) { 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: diff --git a/src/gba/core.c b/src/gba/core.c index ea53474fc..2064ee2d0 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -10,11 +10,13 @@ #include #include #include +#include #include #include #ifndef DISABLE_THREADING -#include +#include #endif +#include #include #include #include @@ -26,11 +28,32 @@ #include #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 @@ static bool _GBACoreInit(struct mCore* core) { 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 @@ static bool _GBACoreInit(struct mCore* core) { #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 @@ static void _GBACoreReset(struct mCore* core) { 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 @@ static void _GBACoreReset(struct mCore* core) { 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 bool _GBACoreSaveState(struct mCore* core, void* state) { 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) { +static void _GBACoreSetPeripheral(struct mCore* core, int type, void* periph) { struct GBA* gba = core->board; - gba->rotationSource = rotation; -} - -static void _GBACoreSetRumble(struct mCore* core, struct mRumble* rumble) { - 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 @@ static bool _GBACoreSavedataRestore(struct mCore* core, const void* sram, size_t 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 @@ struct mCore* GBACoreCreate(void) { 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 @@ struct mCore* GBACoreCreate(void) { 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 diff --git a/src/gba/extra/cli.c b/src/gba/debugger/cli.c similarity index 100% rename from src/gba/extra/cli.c rename to src/gba/debugger/cli.c diff --git a/src/gba/extra/proxy.c b/src/gba/extra/proxy.c new file mode 100644 index 000000000..e75da94c4 --- /dev/null +++ b/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 + +#include +#include +#include + +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); + } +} diff --git a/src/gba/gba.c b/src/gba/gba.c index c7059df12..a6d9467ae 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -76,6 +76,8 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) { 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 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) { 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 @@ static void GBAProcessEvents(struct ARMCore* cpu) { } } +#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 @@ void GBAAttachDebugger(struct GBA* gba, struct mDebugger* 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 @@ bool GBALoadMB(struct GBA* gba, struct VFile* vf) { 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 @@ void GBAApplyPatch(struct GBA* gba, struct Patch* patch) { } 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 @@ void GBAHalt(struct GBA* gba) { } 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 GBABreakpoint(struct ARMCore* cpu, int immediate) { } void GBAFrameStarted(struct GBA* gba) { - UNUSED(gba); + GBATestKeypadIRQ(gba); size_t c; for (c = 0; c < mCoreCallbacksListSize(&gba->coreCallbacks); ++c) { @@ -682,6 +680,29 @@ void GBAFrameEnded(struct GBA* gba) { } } +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); + } +} + void GBASetBreakpoint(struct GBA* gba, struct mCPUComponent* component, uint32_t address, enum ExecutionMode mode, uint32_t* opcode) { size_t immediate; for (immediate = 0; immediate < gba->cpu->numComponents; ++immediate) { diff --git a/src/gba/hardware.c b/src/gba/hardware.c index ff7562840..f252afd96 100644 --- a/src/gba/hardware.c +++ b/src/gba/hardware.c @@ -77,6 +77,9 @@ void GBAHardwareClear(struct GBACartridgeHardware* hw) { } 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; diff --git a/src/gba/io.c b/src/gba/io.c index 9574ec9f9..4d36f2800 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -537,6 +537,11 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t 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 @@ bool GBAIOIsReadConstant(uint32_t address) { case REG_TM2CNT_HI: case REG_TM3CNT_HI: case REG_KEYINPUT: + case REG_KEYCNT: case REG_IE: return true; } @@ -721,6 +727,9 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) { 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 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) { 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 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) { case REG_TM1CNT_HI: case REG_TM2CNT_HI: case REG_TM3CNT_HI: + case REG_KEYCNT: case REG_SIOMULTI0: case REG_SIOMULTI1: case REG_SIOMULTI2: diff --git a/src/gba/memory.c b/src/gba/memory.c index 69cdc8b28..db4a30122 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -102,7 +102,7 @@ void GBAMemoryDeinit(struct GBA* gba) { } 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 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { 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 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { #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 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { 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 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { 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 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { #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 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle 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 @@ void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* o 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 @@ void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* o 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 _pristineCow(struct GBA* gba) { 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); diff --git a/src/gba/renderers/thread-proxy.c b/src/gba/renderers/thread-proxy.c deleted file mode 100644 index 15c8bba0e..000000000 --- a/src/gba/renderers/thread-proxy.c +++ /dev/null @@ -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 - -#include -#include -#include - -#include - -#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 diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index 2c20a475a..70dac8811 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -807,10 +807,12 @@ static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) { } } } - 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) { diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 59c6ecc34..663fcc44c 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -206,11 +206,3 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { } return true; } - -struct GBASerializedState* GBAAllocateState(void) { - return anonymousMemoryMap(sizeof(struct GBASerializedState)); -} - -void GBADeallocateState(struct GBASerializedState* state) { - mappedMemoryFree(state, sizeof(struct GBASerializedState)); -} diff --git a/src/gba/video.c b/src/gba/video.c index 935d1fcea..db0614342 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -295,7 +295,7 @@ static void GBAVideoDummyRendererPutPixels(struct GBAVideoRenderer* renderer, si } 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 @@ void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* } 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) { diff --git a/src/lr35902/debugger/cli-debugger.c b/src/lr35902/debugger/cli-debugger.c index 953f6d754..ac01343df 100644 --- a/src/lr35902/debugger/cli-debugger.c +++ b/src/lr35902/debugger/cli-debugger.c @@ -8,6 +8,7 @@ #include #include #include +#include #include static void _printStatus(struct CLIDebuggerSystem*); @@ -31,11 +32,13 @@ static void _disassemble(struct CLIDebuggerSystem* debugger, struct CLIDebugVect 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 @@ static void _disassemble(struct CLIDebuggerSystem* debugger, struct CLIDebugVect 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 @@ static inline uint16_t _printLine(struct CLIDebugger* debugger, uint16_t address 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 @@ static void _printStatus(struct CLIDebuggerSystem* debugger) { 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) { diff --git a/src/lr35902/debugger/debugger.c b/src/lr35902/debugger/debugger.c index a5f08a394..5d3f3a6ce 100644 --- a/src/lr35902/debugger/debugger.c +++ b/src/lr35902/debugger/debugger.c @@ -7,6 +7,7 @@ #include #include +#include DEFINE_VECTOR(LR35902DebugBreakpointList, struct LR35902DebugBreakpoint); DEFINE_VECTOR(LR35902DebugWatchpointList, struct LR35902DebugWatchpoint); @@ -27,7 +28,9 @@ static void LR35902DebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { 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 @@ struct mDebuggerPlatform* LR35902DebuggerPlatformCreate(void) { 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 @@ static void LR35902DebuggerEnter(struct mDebuggerPlatform* platform, enum mDebug 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); + } +} diff --git a/src/lr35902/debugger/memory-debugger.c b/src/lr35902/debugger/memory-debugger.c new file mode 100644 index 000000000..3b9f66343 --- /dev/null +++ b/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 + +#include + +#include + +#include + +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; +} diff --git a/src/lr35902/decoder.c b/src/lr35902/decoder.c index 6abefc6cc..796a50b17 100644 --- a/src/lr35902/decoder.c +++ b/src/lr35902/decoder.c @@ -66,8 +66,7 @@ DEFINE_DECODER_LR35902(NOP, info->mnemonic = LR35902_MN_NOP;) 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,7 +499,10 @@ static int _decodeOperand(struct LR35902Operand op, char* buffer, int blen) { 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) { @@ -508,10 +510,6 @@ static int _decodeOperand(struct LR35902Operand op, char* buffer, int blen) { 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); ADVANCE(1); @@ -546,10 +544,12 @@ int LR35902Disassemble(struct LR35902InstructionInfo* info, char* buffer, int bl } } - 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); diff --git a/src/lr35902/lr35902.c b/src/lr35902/lr35902.c index 8d1f3f005..e4100a84a 100644 --- a/src/lr35902/lr35902.c +++ b/src/lr35902/lr35902.c @@ -158,6 +158,10 @@ void LR35902Tick(struct LR35902Core* cpu) { 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 @@ void LR35902Run(struct LR35902Core* cpu) { cpu->executionState = LR35902_CORE_FETCH; cpu->instruction(cpu); ++cpu->cycles; - if (cpu->cycles >= cpu->nextEvent) { - cpu->irqh.processEvents(cpu); - running = false; - } } } diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 0d17ba9fa..da0954a21 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -242,7 +242,7 @@ static void _setup(struct mGUIRunner* runner) { 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); } diff --git a/src/platform/example/client-server/server.c b/src/platform/example/client-server/server.c index 75af4edb1..fce25888c 100644 --- a/src/platform/example/client-server/server.c +++ b/src/platform/example/client-server/server.c @@ -1,6 +1,6 @@ // This source file is placed into the public domain. #include -#include "feature/commandline.h" +#include #include #define DEFAULT_PORT 13721 diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 95f3f8e24..d19c5006f 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -398,7 +398,7 @@ bool retro_load_game(const struct retro_game_info* game) { 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 @@ bool retro_load_game(const struct retro_game_info* game) { #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)) { diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index 31bb101aa..87b679c6b 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -65,6 +65,10 @@ static void mGLContextResized(struct VideoBackend* v, unsigned w, unsigned h) { 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); diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 2dff30f3d..47842273e 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -171,6 +171,10 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h) 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); diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index 95dabc13c..d41ae49c6 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -164,6 +163,7 @@ int main() { 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(); diff --git a/src/platform/psp2/psp2-context.c b/src/platform/psp2/psp2-context.c index f58ee6a1d..b2bef2bae 100644 --- a/src/platform/psp2/psp2-context.c +++ b/src/platform/psp2/psp2-context.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -31,7 +32,6 @@ #include #include #include -#include #include @@ -63,8 +63,8 @@ bool frameLimiter = true; 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 @@ void mPSP2Setup(struct mGUIRunner* runner) { 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 @@ void mPSP2Setup(struct mGUIRunner* runner) { 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 mPSP2Setup(struct mGUIRunner* runner) { } 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 @@ void mPSP2PrepareForFrame(struct mGUIRunner* runner) { 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 @@ void mPSP2UnloadROM(struct mGUIRunner* runner) { default: break; } - scePowerSetArmClockFrequency(333); } void mPSP2Paused(struct mGUIRunner* runner) { diff --git a/src/platform/python/CMakeLists.txt b/src/platform/python/CMakeLists.txt index adda679bd..7b7605e7d 100644 --- a/src/platform/python/CMakeLists.txt +++ b/src/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 diff --git a/src/platform/python/_builder.h b/src/platform/python/_builder.h index 58a214d93..8820a8c08 100644 --- a/src/platform/python/_builder.h +++ b/src/platform/python/_builder.h @@ -29,8 +29,12 @@ void free(void*); #include #include -#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 diff --git a/src/platform/python/_builder.py b/src/platform/python/_builder.py index 94babdb55..ccf35a880 100644 --- a/src/platform/python/_builder.py +++ b/src/platform/python/_builder.py @@ -32,24 +32,16 @@ ffi.set_source("mgba._pylib", """ #include #include -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) diff --git a/src/platform/python/log.c b/src/platform/python/log.c index 6066da4a0..53c8201e2 100644 --- a/src/platform/python/log.c +++ b/src/platform/python/log.c @@ -22,6 +22,7 @@ static void _pyLogShim(struct mLogger* logger, int category, enum mLogLevel leve 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; } diff --git a/src/platform/python/log.h b/src/platform/python/log.h index f29dba8bb..26ec70253 100644 --- a/src/platform/python/log.h +++ b/src/platform/python/log.h @@ -12,4 +12,4 @@ struct mLoggerPy { 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); diff --git a/src/platform/python/mgba/__init__.py b/src/platform/python/mgba/__init__.py index e69de29bb..aab19b4d2 100644 --- a/src/platform/python/mgba/__init__.py +++ b/src/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) diff --git a/src/platform/python/mgba/gb.py b/src/platform/python/mgba/gb.py index 0caf7ec99..2b490e008 100644 --- a/src/platform/python/mgba/gb.py +++ b/src/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 @@ -34,6 +35,48 @@ class GB(Core): 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): super(GBMemory, self).__init__(core, 0x10000) diff --git a/src/platform/python/mgba/gba.py b/src/platform/python/mgba/gba.py index 0b8e12fee..5ba050748 100644 --- a/src/platform/python/mgba/gba.py +++ b/src/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 @@ class GBA(Core): 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) @@ -40,6 +47,35 @@ class GBA(Core): 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): super(GBAMemory, self).__init__(core, 0x100000000) diff --git a/src/platform/python/mgba/log.py b/src/platform/python/mgba/log.py index a3412b186..4514e67a9 100644 --- a/src/platform/python/mgba/log.py +++ b/src/platform/python/mgba/log.py @@ -4,11 +4,9 @@ # 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) diff --git a/src/platform/python/sio.c b/src/platform/python/sio.c new file mode 100644 index 000000000..a7cba8973 --- /dev/null +++ b/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 + +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 + +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 diff --git a/src/platform/python/sio.h b/src/platform/python/sio.h new file mode 100644 index 000000000..0403fdfd9 --- /dev/null +++ b/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 + +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 + +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 diff --git a/src/platform/python/vfs-py.h b/src/platform/python/vfs-py.h index 2842d4812..6cc56dc4c 100644 --- a/src/platform/python/vfs-py.h +++ b/src/platform/python/vfs-py.h @@ -13,16 +13,12 @@ struct VFilePy { 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); diff --git a/src/platform/qt/ArchiveInspector.cpp b/src/platform/qt/ArchiveInspector.cpp index a9d346121..200abac2a 100644 --- a/src/platform/qt/ArchiveInspector.cpp +++ b/src/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 { diff --git a/src/platform/qt/ArchiveInspector.ui b/src/platform/qt/ArchiveInspector.ui index 55e0cbe6e..405c2748e 100644 --- a/src/platform/qt/ArchiveInspector.ui +++ b/src/platform/qt/ArchiveInspector.ui @@ -29,15 +29,15 @@ - + - QGBA::LibraryView + QGBA::LibraryController QWidget -
LibraryView.h
+
library/LibraryController.h
1
diff --git a/src/platform/qt/AssetTile.cpp b/src/platform/qt/AssetTile.cpp index 74dcdf961..a97895c4b 100644 --- a/src/platform/qt/AssetTile.cpp +++ b/src/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 @@ AssetTile::AssetTile(QWidget* parent) 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); diff --git a/src/platform/qt/AssetTile.h b/src/platform/qt/AssetTile.h index c3bf729f0..beb42a21a 100644 --- a/src/platform/qt/AssetTile.h +++ b/src/platform/qt/AssetTile.h @@ -31,9 +31,9 @@ private: Ui::AssetTile m_ui; std::shared_ptr 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; diff --git a/src/platform/qt/AssetView.cpp b/src/platform/qt/AssetView.cpp index 964b40761..0077cae8a 100644 --- a/src/platform/qt/AssetView.cpp +++ b/src/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(&QTimer::start)); + connect(m_controller, &GameController::gameStopped, this, &AssetView::close); + connect(m_controller, &GameController::gameStopped, &m_updateTimer, &QTimer::stop); } void AssetView::updateTiles(bool force) { diff --git a/src/platform/qt/AudioProcessor.cpp b/src/platform/qt/AudioProcessor.cpp index da32a522d..6f0b49cbe 100644 --- a/src/platform/qt/AudioProcessor.cpp +++ b/src/platform/qt/AudioProcessor.cpp @@ -44,8 +44,6 @@ AudioProcessor* AudioProcessor::create() { AudioProcessor::AudioProcessor(QObject* parent) : QObject(parent) - , m_context(nullptr) - , m_samples(2048) { } diff --git a/src/platform/qt/AudioProcessor.h b/src/platform/qt/AudioProcessor.h index 4db45ac29..9636f0bd4 100644 --- a/src/platform/qt/AudioProcessor.h +++ b/src/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; }; diff --git a/src/platform/qt/AudioProcessorQt.cpp b/src/platform/qt/AudioProcessorQt.cpp index 334c82ecd..b802d825b 100644 --- a/src/platform/qt/AudioProcessorQt.cpp +++ b/src/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) { } diff --git a/src/platform/qt/AudioProcessorQt.h b/src/platform/qt/AudioProcessorQt.h index cec1fdd0c..ecf517a15 100644 --- a/src/platform/qt/AudioProcessorQt.h +++ b/src/platform/qt/AudioProcessorQt.h @@ -32,9 +32,9 @@ public slots: 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; }; } diff --git a/src/platform/qt/AudioProcessorSDL.cpp b/src/platform/qt/AudioProcessorSDL.cpp index 0e8d50af6..3980108e7 100644 --- a/src/platform/qt/AudioProcessorSDL.cpp +++ b/src/platform/qt/AudioProcessorSDL.cpp @@ -13,7 +13,6 @@ using namespace QGBA; AudioProcessorSDL::AudioProcessorSDL(QObject* parent) : AudioProcessor(parent) - , m_audio{ 2048, 44100 } { } diff --git a/src/platform/qt/AudioProcessorSDL.h b/src/platform/qt/AudioProcessorSDL.h index cd1d53957..e0e354729 100644 --- a/src/platform/qt/AudioProcessorSDL.h +++ b/src/platform/qt/AudioProcessorSDL.h @@ -33,7 +33,7 @@ public slots: virtual void requestSampleRate(unsigned) override; private: - mSDLAudio m_audio; + mSDLAudio m_audio{2048, 44100}; }; } diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 01d006a2b..86e1af59d 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/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 @@ if(BUILD_SDL) 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 @@ set(SOURCE_FILES Swatch.cpp TilePainter.cpp TileView.cpp + utils.cpp Window.cpp VFileDevice.cpp VideoView.cpp) @@ -117,7 +113,6 @@ set(UI_FILES DebuggerConsole.ui GIFView.ui IOViewer.ui - LibraryView.ui LoadSaveState.ui LogView.ui MemoryView.ui @@ -138,10 +133,8 @@ set(GBA_SRC 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 @@ endif() 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 @@ if(Qt5LinguistTools_FOUND) 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 @@ if(APPLE) 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() diff --git a/src/platform/qt/CheatsView.cpp b/src/platform/qt/CheatsView.cpp index f8ae9b77d..73ffa09ab 100644 --- a/src/platform/qt/CheatsView.cpp +++ b/src/platform/qt/CheatsView.cpp @@ -31,12 +31,12 @@ CheatsView::CheatsView(GameController* controller, QWidget* parent) 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()) { diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp index 520b6d9f2..7832b052c 100644 --- a/src/platform/qt/ConfigController.cpp +++ b/src/platform/qt/ConfigController.cpp @@ -11,7 +11,7 @@ #include #include -#include "feature/commandline.h" +#include using namespace QGBA; @@ -76,14 +76,12 @@ void ConfigOption::setValue(const char* value) { } void ConfigOption::setValue(const QVariant& value) { - QPair action; - foreach (action, m_actions) { + for (QPair& action : m_actions) { bool signalsEnabled = action.first->blockSignals(true); action.first->setChecked(value == action.second); action.first->blockSignals(signalsEnabled); } - std::function slot; - foreach(slot, m_slots.values()) { + for (std::function& 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()); diff --git a/src/platform/qt/ConfigController.h b/src/platform/qt/ConfigController.h index 2c8512c5e..3c3a9bca3 100644 --- a/src/platform/qt/ConfigController.h +++ b/src/platform/qt/ConfigController.h @@ -17,7 +17,7 @@ #include #include -#include "feature/commandline.h" +#include 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 m_optionSet; QSettings* m_settings; diff --git a/src/platform/qt/DebuggerConsole.cpp b/src/platform/qt/DebuggerConsole.cpp index 612e86e23..a9c505da8 100644 --- a/src/platform/qt/DebuggerConsole.cpp +++ b/src/platform/qt/DebuggerConsole.cpp @@ -17,10 +17,10 @@ DebuggerConsole::DebuggerConsole(DebuggerConsoleController* controller, QWidget* { 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) { diff --git a/src/platform/qt/DebuggerConsoleController.cpp b/src/platform/qt/DebuggerConsoleController.cpp index 0752eb89e..e7c35144a 100644 --- a/src/platform/qt/DebuggerConsoleController.cpp +++ b/src/platform/qt/DebuggerConsoleController.cpp @@ -38,8 +38,16 @@ void DebuggerConsoleController::enterLine(const QString& line) { 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::init(struct CLIDebuggerBackend* be) { void DebuggerConsoleController::deinit(struct CLIDebuggerBackend* be) { Backend* consoleBe = reinterpret_cast(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 @@ const char* DebuggerConsoleController::readLine(struct CLIDebuggerBackend* be, s 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 @@ const char* DebuggerConsoleController::historyLast(struct CLIDebuggerBackend* 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(); } diff --git a/src/platform/qt/DebuggerConsoleController.h b/src/platform/qt/DebuggerConsoleController.h index 36e7903cb..543dabeae 100644 --- a/src/platform/qt/DebuggerConsoleController.h +++ b/src/platform/qt/DebuggerConsoleController.h @@ -30,6 +30,7 @@ signals: public slots: void enterLine(const QString&); + virtual void detach() override; protected: virtual void attachInternal() override; diff --git a/src/platform/qt/DebuggerController.cpp b/src/platform/qt/DebuggerController.cpp index dcec4efb2..a1a7a96c5 100644 --- a/src/platform/qt/DebuggerController.cpp +++ b/src/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 @@ void DebuggerController::attach() { 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); } } diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index 506afe76b..e300b9574 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -53,8 +53,6 @@ Display* Display::create(QWidget* parent) { 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 @@ Display::Display(QWidget* parent) #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); @@ -77,6 +75,10 @@ void Display::lockAspectRatio(bool lock) { m_messagePainter.resize(size(), m_lockAspectRatio, devicePixelRatio()); } +void Display::lockIntegerScaling(bool lock) { + m_lockIntegerScaling = lock; +} + void Display::filter(bool filter) { m_filter = filter; } diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index a78810fe8..b7e04d263 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -38,6 +38,7 @@ public: 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 @@ public slots: 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 @@ private: 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; }; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index da5781a3c..af1cc6d51 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -5,6 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "DisplayGL.h" +#if defined(BUILD_GL) || defined(BUILD_GLES) + #include #include #include @@ -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 @@ void DisplayGL::startDrawing(mCoreThread* 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()); @@ -136,6 +136,13 @@ void DisplayGL::lockAspectRatio(bool lock) { } } +void DisplayGL::lockIntegerScaling(bool lock) { + Display::lockIntegerScaling(lock); + if (m_drawThread) { + QMetaObject::invokeMethod(m_painter, "lockIntegerScaling", Q_ARG(bool, lock)); + } +} + void DisplayGL::filter(bool filter) { Display::filter(filter); if (m_drawThread) { @@ -290,6 +297,13 @@ void PainterGL::lockAspectRatio(bool lock) { } } +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 @@ void PainterGL::draw() { 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 @@ void PainterGL::clearShaders() { VideoShader* PainterGL::shaders() { return &m_shader; } + +#endif diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index b4b4bd056..f2f6dbc7c 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -6,6 +6,8 @@ #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 @@ public slots: 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 @@ protected: 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 @@ public slots: 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 @@ private: } #endif + +#endif diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 284347d58..824df243e 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -14,8 +14,6 @@ using namespace QGBA; DisplayQt::DisplayQt(QWidget* parent) : Display(parent) - , m_isDrawing(false) - , m_backing(nullptr) { } @@ -30,6 +28,11 @@ void DisplayQt::lockAspectRatio(bool lock) { update(); } +void DisplayQt::lockIntegerScaling(bool lock) { + Display::lockIntegerScaling(lock); + update(); +} + void DisplayQt::filter(bool filter) { Display::filter(filter); update(); @@ -66,6 +69,10 @@ void DisplayQt::paintEvent(QPaintEvent*) { 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); diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index d40b1053c..6fcab1b7a 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -30,6 +30,7 @@ public slots: 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}; }; } diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp index b54ea49ee..8abb6a21b 100644 --- a/src/platform/qt/GBAApp.cpp +++ b/src/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* GBAApp::newWindow() { 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 @@ Window* GBAApp::newWindow() { 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 @@ QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QStr 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 @@ QString GBAApp::getSaveFileName(QWidget* owner, const QString& title, const QStr 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 @@ QString GBAApp::getOpenDirectoryName(QWidget* owner, const QString& title) { 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); @@ -241,24 +234,6 @@ bool GBAApp::reloadGameDB() { } #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 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) : QObject(parent) diff --git a/src/platform/qt/GBAApp.h b/src/platform/qt/GBAApp.h index 8ade195e1..fec6056f3 100644 --- a/src/platform/qt/GBAApp.h +++ b/src/platform/qt/GBAApp.h @@ -55,9 +55,6 @@ public: 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* paused); @@ -84,7 +71,7 @@ private: QList m_windows; MultiplayerController m_multiplayer; - NoIntroDB* m_db; + NoIntroDB* m_db = nullptr; #ifdef USE_SQLITE3 QThread m_parseThread; #endif diff --git a/src/platform/qt/GBAKeyEditor.cpp b/src/platform/qt/GBAKeyEditor.cpp new file mode 100644 index 000000000..0b58760eb --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +#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(&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{ + 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(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(user); + if (description->highDirection != GBA_KEY_NONE) { + KeyEditor* key = self->keyById(static_cast(description->highDirection)); + if (key) { + key->setValueAxis(axis, description->deadHigh); + } + } + if (description->lowDirection != GBA_KEY_NONE) { + KeyEditor* key = self->keyById(static_cast(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(bindings.up)); + if (key) { + key->setValueHat(i, GamepadHatEvent::UP); + } + } + if (bindings.right >= 0) { + KeyEditor* key = keyById(static_cast(bindings.right)); + if (key) { + key->setValueHat(i, GamepadHatEvent::RIGHT); + } + } + if (bindings.down >= 0) { + KeyEditor* key = keyById(static_cast(bindings.down)); + if (key) { + key->setValueHat(i, GamepadHatEvent::DOWN); + } + } + if (bindings.left >= 0) { + KeyEditor* key = keyById(static_cast(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 diff --git a/src/platform/qt/GBAKeyEditor.h b/src/platform/qt/GBAKeyEditor.h new file mode 100644 index 000000000..6070d7f50 --- /dev/null +++ b/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 +#include +#include +#include + +#include + +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 m_keyOrder; + QList::iterator m_currentKey; + + uint32_t m_type; + QString m_profile; + InputController* m_controller; + + QPicture m_background; +}; + +} + +#endif diff --git a/src/platform/qt/GDBController.cpp b/src/platform/qt/GDBController.cpp index 402357154..5a72420a0 100644 --- a/src/platform/qt/GDBController.cpp +++ b/src/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); diff --git a/src/platform/qt/GDBController.h b/src/platform/qt/GDBController.h index 206c8a27e..fc8a6e6a1 100644 --- a/src/platform/qt/GDBController.h +++ b/src/platform/qt/GDBController.h @@ -40,7 +40,7 @@ private: GDBStub m_gdbStub; - ushort m_port; + ushort m_port = 2345; Address m_bindAddress; }; diff --git a/src/platform/qt/GDBWindow.cpp b/src/platform/qt/GDBWindow.cpp index 98bc89801..c32d7a52d 100644 --- a/src/platform/qt/GDBWindow.cpp +++ b/src/platform/qt/GDBWindow.cpp @@ -38,12 +38,12 @@ GDBWindow::GDBWindow(GDBController* controller, QWidget* parent) 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 @@ GDBWindow::GDBWindow(GDBController* controller, QWidget* parent) 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 @@ void GDBWindow::started() { 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 @@ void GDBWindow::stopped() { 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() { diff --git a/src/platform/qt/GIFView.cpp b/src/platform/qt/GIFView.cpp index e72475150..1b9e0156c 100644 --- a/src/platform/qt/GIFView.cpp +++ b/src/platform/qt/GIFView.cpp @@ -22,14 +22,15 @@ GIFView::GIFView(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(&QSpinBox::valueChanged), + this, &GIFView::updateDelay); + connect(m_ui.delayAuto, &QAbstractButton::clicked, this, &GIFView::updateDelay); ImageMagickGIFEncoderInit(&m_encoder); } diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 66d8eb81e..0b48ac302 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -32,44 +32,16 @@ #include #endif #include +#include 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 @@ GameController::GameController(QObject* parent) m_threadContext.startCallback = [](mCoreThread* context) { GameController* controller = static_cast(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()); + + 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]); + } -#ifdef M_CORE_GBA - GBA* gba = static_cast(context->core->board); -#endif -#ifdef M_CORE_GB - GB* gb = static_cast(context->core->board); -#endif 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 @@ GameController::GameController(QObject* parent) } 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 @@ GameController::GameController(QObject* parent) } }; - // TODO: Put back - /*m_threadContext.stopCallback = [](mCoreThread* context) { + m_threadContext.sleepCallback = [](mCoreThread* context) { if (!context) { - return false; + return; } GameController* controller = static_cast(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(logger); @@ -292,10 +245,10 @@ GameController::GameController(QObject* parent) 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 @@ void GameController::setConfig(const mCoreConfig* config) { if (isLoaded()) { Interrupter interrupter(this); mCoreLoadForeignConfig(m_threadContext.core, config); + m_audioSync = m_threadContext.sync.audioWait; + m_videoSync = m_threadContext.sync.videoFrameWait; m_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& bas 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 @@ void GameController::openGame(bool biosOnly) { 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 @@ void GameController::cleanGame() { 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 @@ void GameController::setRewind(bool enable, int capacity, bool rewindSave) { 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(m_threadContext.core->board); -#endif -#ifdef M_CORE_GB - GB* gb = static_cast(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[layer] = enable; -#ifdef M_CORE_GBA - if (isLoaded() && m_threadContext.core->platform(m_threadContext.core) == PLATFORM_GBA) { - GBA* gba = static_cast(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; - } + m_videoLayers.reserve(layer + 1); + while (m_videoLayers.size() <= layer) { + m_videoLayers.append(true); + } + m_videoLayers[layer] = enable; + if (isLoaded()) { + m_threadContext.core->enableVideoLayer(m_threadContext.core, layer, enable); } -#endif } void GameController::setFPSTarget(float fps) { @@ -980,8 +907,8 @@ void GameController::loadState(int slot) { } 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 @@ void GameController::setSync(bool enable) { } 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 @@ void GameController::reloadAudioDriver() { 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(); @@ -1154,6 +1092,10 @@ void GameController::setLoadStateExtdata(int flags) { m_loadStateFlags = flags; } +void GameController::setPreload(bool preload) { + m_preload = preload; +} + void GameController::setLuminanceValue(uint8_t value) { m_luxValue = value; value = std::max(value - 0x16, 0); @@ -1209,7 +1151,7 @@ void GameController::updateKeys() { } 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 @@ void GameController::disableLogLevel(int levels) { 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 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(m_threadContext.core->board); m_tileCache = std::make_shared(); GBAVideoTileCacheInit(m_tileCache.get()); @@ -1272,7 +1240,6 @@ std::shared_ptr GameController::tileCache() { #endif #ifdef M_CORE_GB case PLATFORM_GB: { - Interrupter interrupter(this); GB* gb = static_cast(m_threadContext.core->board); m_tileCache = std::make_shared(); GBVideoTileCacheInit(m_tileCache.get()); diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h index 70f397c62..111cbdada 100644 --- a/src/platform/qt/GameController.h +++ b/src/platform/qt/GameController.h @@ -29,6 +29,7 @@ struct GBAAudio; struct mCoreConfig; struct mDebugger; struct mTileCache; +struct mVideoLogContext; namespace QGBA { @@ -86,7 +87,7 @@ public: int stateSlot() const { return m_stateSlot; } -#ifdef USE_GDB_STUB +#ifdef USE_DEBUGGERS mDebugger* debugger(); void setDebugger(mDebugger*); #endif @@ -147,11 +148,14 @@ public slots: 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 @@ public slots: 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 @@ private: 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> 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 m_tileCache; - bool m_audioChannels[6]; - bool m_videoLayers[5]; + QList m_audioChannels; + QList 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; diff --git a/src/platform/qt/IOViewer.cpp b/src/platform/qt/IOViewer.cpp index b308de6a4..030ead19f 100644 --- a/src/platform/qt/IOViewer.cpp +++ b/src/platform/qt/IOViewer.cpp @@ -1040,9 +1040,10 @@ IOViewer::IOViewer(GameController* controller, QWidget* parent) 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(&IOViewer::selectRegister)); m_b[0] = m_ui.b0; m_b[1] = m_ui.b1; @@ -1062,7 +1063,7 @@ IOViewer::IOViewer(GameController* controller, QWidget* parent) 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 @@ void IOViewer::selectRegister(unsigned address) { 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); diff --git a/src/platform/qt/IOViewer.h b/src/platform/qt/IOViewer.h index 9186cd4d2..c2451e08d 100644 --- a/src/platform/qt/IOViewer.h +++ b/src/platform/qt/IOViewer.h @@ -21,16 +21,16 @@ Q_OBJECT 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; diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index 93b8718af..7468bcb29 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -33,13 +33,6 @@ InputController::InputController(InputModel* model, int playerId, QWidget* topLe , 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) { diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h index 1d65d8ad8..916f3b3fe 100644 --- a/src/platform/qt/InputController.h +++ b/src/platform/qt/InputController.h @@ -124,17 +124,17 @@ private: InputModel* m_inputModel; mPlatform m_platform; QMap 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 m_deadzones; @@ -146,7 +146,7 @@ private: QSet m_activeButtons; QSet> m_activeAxes; QSet> m_activeHats; - QTimer m_gamepadTimer; + QTimer m_gamepadTimer{nullptr}; QSet m_pendingEvents; }; diff --git a/src/platform/qt/InputModel.cpp b/src/platform/qt/InputModel.cpp index 58af9de6e..18bbb9f8d 100644 --- a/src/platform/qt/InputModel.cpp +++ b/src/platform/qt/InputModel.cpp @@ -18,8 +18,6 @@ using namespace QGBA; InputModel::InputModel(QObject* parent) : QAbstractItemModel(parent) , m_rootMenu(nullptr) - , m_config(nullptr) - , m_profile(nullptr) { } diff --git a/src/platform/qt/InputProfile.cpp b/src/platform/qt/InputProfile.cpp index 5499f874e..92e1c0cc9 100644 --- a/src/platform/qt/InputProfile.cpp +++ b/src/platform/qt/InputProfile.cpp @@ -179,8 +179,6 @@ constexpr InputProfile::InputProfile(const char* name, keys.keyR, keys.keyL, } - , m_shortcutButtons(shortcutButtons) - , m_shortcutAxes(shortcutAxes) , m_axes { axes.keyA, axes.keyB, @@ -193,6 +191,8 @@ constexpr InputProfile::InputProfile(const char* name, axes.keyR, axes.keyL, } + , m_shortcutButtons(shortcutButtons) + , m_shortcutAxes(shortcutAxes) , m_tiltAxis(tiltAxis) , m_gyroAxis(gyroAxis) , m_gyroSensitivity(gyroSensitivity) diff --git a/src/platform/qt/KeyEditor.cpp b/src/platform/qt/KeyEditor.cpp index 6efa3cb2f..761abed14 100644 --- a/src/platform/qt/KeyEditor.cpp +++ b/src/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); diff --git a/src/platform/qt/KeyEditor.h b/src/platform/qt/KeyEditor.h index 6fd097a85..e1a7fa949 100644 --- a/src/platform/qt/KeyEditor.h +++ b/src/platform/qt/KeyEditor.h @@ -54,10 +54,10 @@ private: 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; diff --git a/src/platform/qt/LibraryModel.cpp b/src/platform/qt/LibraryModel.cpp deleted file mode 100644 index bea45c4d6..000000000 --- a/src/platform/qt/LibraryModel.cpp +++ /dev/null @@ -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 - -#include - -using namespace QGBA; - -Q_DECLARE_METATYPE(mLibraryEntry); - -QMap LibraryModel::s_handles; -QMap 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(m_constraints.base)); - } - m_constraints.base = strdup(path.toUtf8().constData()); - reload(); -} - -void LibraryModel::clearConstraints() { - if (m_constraints.base) { - free(const_cast(m_constraints.base)); - } - if (m_constraints.filename) { - free(const_cast(m_constraints.filename)); - } - if (m_constraints.title) { - free(const_cast(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 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); -} diff --git a/src/platform/qt/LibraryModel.h b/src/platform/qt/LibraryModel.h deleted file mode 100644 index 970b08f87..000000000 --- a/src/platform/qt/LibraryModel.h +++ /dev/null @@ -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 -#include -#include - -#include - -#include - -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, int = Qt::AlignLeft); - QString name; - std::function 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 s_handles; - - mLibraryEntry m_constraints; - mLibraryListing m_listings; - QStringList m_queue; - - QList m_columns; - static QMap 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 diff --git a/src/platform/qt/LibraryView.cpp b/src/platform/qt/LibraryView.cpp deleted file mode 100644 index 44b447e0a..000000000 --- a/src/platform/qt/LibraryView.cpp +++ /dev/null @@ -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 - -#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 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); -} diff --git a/src/platform/qt/LibraryView.h b/src/platform/qt/LibraryView.h deleted file mode 100644 index d56ea7ada..000000000 --- a/src/platform/qt/LibraryView.h +++ /dev/null @@ -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 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 diff --git a/src/platform/qt/LibraryView.ui b/src/platform/qt/LibraryView.ui deleted file mode 100644 index 96c2a2bf6..000000000 --- a/src/platform/qt/LibraryView.ui +++ /dev/null @@ -1,49 +0,0 @@ - - - LibraryView - - - - 0 - 0 - 400 - 300 - - - - Library - - - - 0 - - - - - QAbstractItemView::NoEditTriggers - - - true - - - QAbstractItemView::SelectRows - - - false - - - true - - - false - - - 0 - - - - - - - - diff --git a/src/platform/qt/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp index 122cb133e..40777000f 100644 --- a/src/platform/qt/LoadSaveState.cpp +++ b/src/platform/qt/LoadSaveState.cpp @@ -23,8 +23,8 @@ using namespace QGBA; 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 @@ LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent) } 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); diff --git a/src/platform/qt/LogController.cpp b/src/platform/qt/LogController.cpp index e54922a10..5de4c74e5 100644 --- a/src/platform/qt/LogController.cpp +++ b/src/platform/qt/LogController.cpp @@ -14,10 +14,10 @@ LogController::LogController(int levels, 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 @@ QString LogController::toString(int level) { } LogController::Stream::Stream(LogController* controller, int level, int category) - : m_log(controller) - , m_level(level) + : m_level(level) , m_category(category) + , m_log(controller) { } diff --git a/src/platform/qt/LogView.cpp b/src/platform/qt/LogView.cpp index dc9a2ed84..7ed748e78 100644 --- a/src/platform/qt/LogView.cpp +++ b/src/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 @@ LogView::LogView(LogController* log, QWidget* parent) 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(&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 @@ LogView::LogView(LogController* log, QWidget* parent) 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) { diff --git a/src/platform/qt/LogView.h b/src/platform/qt/LogView.h index 8fa23d212..8151b6da8 100644 --- a/src/platform/qt/LogView.h +++ b/src/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 m_pendingLines; void setLevel(int level, bool); diff --git a/src/platform/qt/MemoryModel.cpp b/src/platform/qt/MemoryModel.cpp index e7f4d126e..8b7a08dd7 100644 --- a/src/platform/qt/MemoryModel.cpp +++ b/src/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 @@ MemoryModel::MemoryModel(QWidget* parent) 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 @@ void MemoryModel::setAlignment(int width) { 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 @@ void MemoryModel::loadTBL() { if (filename.isNull()) { return; } - loadTBL(filename); + loadTBLFromPath(filename); } void MemoryModel::jumpToAddress(const QString& hex) { diff --git a/src/platform/qt/MemoryModel.h b/src/platform/qt/MemoryModel.h index a32bb8c36..f74078528 100644 --- a/src/platform/qt/MemoryModel.h +++ b/src/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 @@ private: void operator()(TextCodec*); }; - mCore* m_core; + mCore* m_core = nullptr; std::unique_ptr 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 m_staticNumbers; QVector m_staticLatin1; QStaticText m_regionName; QSizeF m_cellSize; - QPair m_selection; - uint32_t m_selectionAnchor; + QPair m_selection{0, 0}; + uint32_t m_selectionAnchor = 0; uint32_t m_buffer; int m_bufferedNybbles; int m_currentBank; diff --git a/src/platform/qt/MemoryView.cpp b/src/platform/qt/MemoryView.cpp index 20a6dce16..b343060fa 100644 --- a/src/platform/qt/MemoryView.cpp +++ b/src/platform/qt/MemoryView.cpp @@ -81,8 +81,10 @@ MemoryView::MemoryView(GameController* controller, QWidget* parent) 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(&QComboBox::currentIndexChanged), + this, &MemoryView::setIndex); + connect(m_ui.segments, static_cast(&QSpinBox::valueChanged), + this, &MemoryView::setSegment); if (info) { for (size_t i = 0; info[i].name; ++i) { @@ -93,22 +95,23 @@ MemoryView::MemoryView(GameController* controller, QWidget* parent) 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(&QSpinBox::valueChanged), + m_ui.hexfield, static_cast(&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) { diff --git a/src/platform/qt/MessagePainter.cpp b/src/platform/qt/MessagePainter.cpp index 629ce4878..e62afe8f4 100644 --- a/src/platform/qt/MessagePainter.cpp +++ b/src/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); diff --git a/src/platform/qt/MessagePainter.h b/src/platform/qt/MessagePainter.h index 7e0bff53b..24a48b3a5 100644 --- a/src/platform/qt/MessagePainter.h +++ b/src/platform/qt/MessagePainter.h @@ -35,11 +35,11 @@ private: 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; }; } diff --git a/src/platform/qt/ObjView.cpp b/src/platform/qt/ObjView.cpp index 2749d7a50..0fa19df8c 100644 --- a/src/platform/qt/ObjView.cpp +++ b/src/platform/qt/ObjView.cpp @@ -27,9 +27,6 @@ using namespace QGBA; 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 @@ ObjView::ObjView(GameController* controller, QWidget* parent) 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(&QSpinBox::valueChanged), this, &ObjView::selectObj); connect(m_ui.magnification, static_cast(&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 @@ void ObjView::updateTilesGB(bool force) { } #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 @@ void ObjView::exportObj() { PNGWriteClose(png, info); delete[] buffer; } +#endif bool ObjView::ObjInfo::operator!=(const ObjInfo& other) { return other.tile != tile || diff --git a/src/platform/qt/ObjView.h b/src/platform/qt/ObjView.h index 16a72961b..0e03ce29c 100644 --- a/src/platform/qt/ObjView.h +++ b/src/platform/qt/ObjView.h @@ -21,8 +21,10 @@ Q_OBJECT public: ObjView(GameController* controller, QWidget* parent = nullptr); +#ifdef USE_PNG public slots: void exportObj(); +#endif private slots: void selectObj(int); @@ -39,8 +41,8 @@ private: 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 @@ private: unsigned bits; bool operator!=(const ObjInfo&); - } m_objInfo; + } m_objInfo = {}; int m_tileOffset; }; diff --git a/src/platform/qt/OverrideView.cpp b/src/platform/qt/OverrideView.cpp index 39f4e5ce9..42f90f5eb 100644 --- a/src/platform/qt/OverrideView.cpp +++ b/src/platform/qt/OverrideView.cpp @@ -56,8 +56,8 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config, #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 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config, 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 @@ OverrideView::OverrideView(GameController* controller, ConfigController* config, 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()) { diff --git a/src/platform/qt/PaletteView.cpp b/src/platform/qt/PaletteView.cpp index af55bdd4b..f669168de 100644 --- a/src/platform/qt/PaletteView.cpp +++ b/src/platform/qt/PaletteView.cpp @@ -30,7 +30,7 @@ PaletteView::PaletteView(GameController* controller, QWidget* parent) { 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 @@ PaletteView::PaletteView(GameController* controller, QWidget* parent) 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 @@ void PaletteView::exportPalette(int start, int length) { } 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(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(m_controller->thread()->core->board)->video.palette[start]); } vf->close(vf); diff --git a/src/platform/qt/SensorView.cpp b/src/platform/qt/SensorView.cpp index b0e86cc64..8f107df46 100644 --- a/src/platform/qt/SensorView.cpp +++ b/src/platform/qt/SensorView.cpp @@ -22,10 +22,11 @@ SensorView::SensorView(GameController* controller, InputController* input, QWidg { 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(&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 @@ SensorView::SensorView(GameController* controller, InputController* input, QWidg 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 { diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index ba1f377ba..161568fa6 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -136,7 +136,12 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC 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 @@ void SettingsView::updateConfig() { 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 @@ void SettingsView::updateConfig() { 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 @@ void SettingsView::reloadConfig() { 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 @@ void SettingsView::reloadConfig() { 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) { diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index ad59bc5bd..8fca6efaf 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -7,7 +7,7 @@ 0 0 650 - 450 + 454 @@ -115,7 +115,7 @@ true - + 1536 @@ -181,7 +181,7 @@ true - + 44100 @@ -380,39 +380,46 @@ - + Bilinear filtering + + + + Force integer scaling + + + - - + + - Allow opposing input directions - - - - - - - Suspend screensaver + Show when no game open true - - - - Pause when inactive - + + + + + List view + + + + + Tree view + + @@ -422,21 +429,7 @@ - - - - Show when no game open - - - - - - - Qt::Horizontal - - - - + false @@ -446,6 +439,37 @@ + + + + Qt::Horizontal + + + + + + + Allow opposing input directions + + + + + + + Suspend screensaver + + + true + + + + + + + Pause when inactive + + + @@ -568,21 +592,21 @@ - + Qt::Horizontal - + Savestate extra data: - + Screenshot @@ -592,7 +616,7 @@ - + Save data @@ -602,7 +626,7 @@ - + Cheat codes @@ -612,14 +636,14 @@ - + Load extra data: - + Screenshot @@ -629,21 +653,21 @@ - + Save data - + Cheat codes - + Qt::Horizontal @@ -660,6 +684,13 @@ + + + + Preload entire ROM into memory + + + diff --git a/src/platform/qt/ShaderSelector.cpp b/src/platform/qt/ShaderSelector.cpp index 4eb743fce..9c3a9ce77 100644 --- a/src/platform/qt/ShaderSelector.cpp +++ b/src/platform/qt/ShaderSelector.cpp @@ -31,15 +31,14 @@ ShaderSelector::ShaderSelector(Display* display, ConfigController* config, QWidg : 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 @@ void ShaderSelector::refreshShaders() { 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) { diff --git a/src/platform/qt/ShortcutController.h b/src/platform/qt/ShortcutController.h new file mode 100644 index 000000000..f0cc90148 --- /dev/null +++ b/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 + +#include + +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> 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& items() { return m_items; } + const QList& 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 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 press, std::function release, + int shortcut, const QString& visibleName, const QString& name); + void addFunctions(QMenu* menu, std::function press, std::function 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 func); + void updateKey(ShortcutItem* item, int keySequence); + + ShortcutItem m_rootMenu{nullptr}; + QMap m_menuMap; + QMap m_buttons; + QMap, ShortcutItem*> m_axes; + QMap m_heldKeys; + ConfigController* m_config = nullptr; + QString m_profileName; + const InputProfile* m_profile = nullptr; +}; + +} + +#endif diff --git a/src/platform/qt/ShortcutView.cpp b/src/platform/qt/ShortcutView.cpp index 19770a364..71376a2c8 100644 --- a/src/platform/qt/ShortcutView.cpp +++ b/src/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 @@ ShortcutView::ShortcutView(QWidget* parent) 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() { diff --git a/src/platform/qt/ShortcutView.h b/src/platform/qt/ShortcutView.h index 0cb75aaab..824950560 100644 --- a/src/platform/qt/ShortcutView.h +++ b/src/platform/qt/ShortcutView.h @@ -40,8 +40,8 @@ private slots: private: Ui::ShortcutView m_ui; - InputModel* m_controller; - InputController* m_input; + InputModel* m_controller = nullptr; + InputController* m_input = nullptr; }; } diff --git a/src/platform/qt/Swatch.cpp b/src/platform/qt/Swatch.cpp index 8c0e64212..111245413 100644 --- a/src/platform/qt/Swatch.cpp +++ b/src/platform/qt/Swatch.cpp @@ -15,7 +15,6 @@ using namespace QGBA; Swatch::Swatch(QWidget* parent) : QWidget(parent) { - m_size = 10; } void Swatch::setSize(int size) { diff --git a/src/platform/qt/Swatch.h b/src/platform/qt/Swatch.h index 266b3dca2..57fd08510 100644 --- a/src/platform/qt/Swatch.h +++ b/src/platform/qt/Swatch.h @@ -32,7 +32,7 @@ protected: void mousePressEvent(QMouseEvent*) override; private: - int m_size; + int m_size = 10; QVector m_colors; QPixmap m_backing; QSize m_dims; diff --git a/src/platform/qt/TilePainter.cpp b/src/platform/qt/TilePainter.cpp index 5354c48eb..a2d99d4af 100644 --- a/src/platform/qt/TilePainter.cpp +++ b/src/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); diff --git a/src/platform/qt/TilePainter.h b/src/platform/qt/TilePainter.h index 1c61c5e97..e76af6ae3 100644 --- a/src/platform/qt/TilePainter.h +++ b/src/platform/qt/TilePainter.h @@ -32,8 +32,8 @@ protected: void resizeEvent(QResizeEvent*) override; private: - QPixmap m_backing; - int m_size; + QPixmap m_backing{256, 768}; + int m_size = 8; int m_tileCount; }; diff --git a/src/platform/qt/TileView.cpp b/src/platform/qt/TileView.cpp index eb4dbd18d..eb74a57a6 100644 --- a/src/platform/qt/TileView.cpp +++ b/src/platform/qt/TileView.cpp @@ -19,14 +19,12 @@ using namespace QGBA; 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; diff --git a/src/platform/qt/TileView.h b/src/platform/qt/TileView.h index 623d83a47..6e35df355 100644 --- a/src/platform/qt/TileView.h +++ b/src/platform/qt/TileView.h @@ -35,8 +35,8 @@ private: 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; }; } diff --git a/src/platform/qt/VideoView.cpp b/src/platform/qt/VideoView.cpp index 6a6523b6e..e5f28458e 100644 --- a/src/platform/qt/VideoView.cpp +++ b/src/platform/qt/VideoView.cpp @@ -45,13 +45,6 @@ bool VideoView::Preset::compatible(const Preset& other) const { 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 @@ VideoView::VideoView(QWidget* parent) 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 @@ VideoView::VideoView(QWidget* parent) 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 @@ VideoView::VideoView(QWidget* parent) 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 @@ void VideoView::uncheckIncompatible() { QString VideoView::sanitizeCodec(const QString& codec, const QMap& mapping) { QString sanitized = codec.toLower(); sanitized = sanitized.remove(QChar('.')); + sanitized = sanitized.remove(QChar('(')); + sanitized = sanitized.remove(QChar(')')); if (mapping.contains(sanitized)) { sanitized = mapping[sanitized]; } diff --git a/src/platform/qt/VideoView.h b/src/platform/qt/VideoView.h index aeb4c0b02..6f3ef3ecb 100644 --- a/src/platform/qt/VideoView.h +++ b/src/platform/qt/VideoView.h @@ -86,18 +86,18 @@ private: 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 m_presets; diff --git a/src/platform/qt/VideoView.ui b/src/platform/qt/VideoView.ui index ff9dcaed1..4bdcefe75 100644 --- a/src/platform/qt/VideoView.ui +++ b/src/platform/qt/VideoView.ui @@ -266,12 +266,17 @@ - VP8 + h.264 (NVENC) - Xvid + HEVC + + + + + VP8 diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index b092c17b1..750bcff79 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -14,10 +14,12 @@ #include #include -#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 #include #endif -#include "feature/commandline.h" +#include #include "feature/sqlite3/no-intro.h" #include @@ -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 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) 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 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) } }, this); m_config->updateOption("showLibrary"); + ConfigOption* libraryStyle = m_config->addOption("libraryStyle"); + libraryStyle->connect([this](const QVariant& value) { + m_libraryView->setViewStyle(static_cast(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 path = m_libraryView->selectedPath(); if (output) { - m_controller->loadGame(output, path.first, path.second); + QPair path = m_libraryView->selectedPath(); + m_controller->loadGame(output, path.second, path.first); } }); #elif defined(M_CORE_GBA) @@ -137,15 +125,16 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) #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 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) 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(m_screenWidget->layout())->currentWidget() == m_display) { m_screenWidget->setCursor(Qt::BlankCursor); @@ -311,19 +300,6 @@ void Window::reloadConfig() { 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 @@ QString Window::getFilters() const { 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::selectPatch() { } 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::exportSharkport() { 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 @@ void Window::openAboutScreen() { 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 std::function Window::openTView(A arg) { return [=]() { @@ -518,17 +503,17 @@ std::function Window::openTView() { 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 @@ void Window::openVideoWindow() { 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 @@ void Window::gameStarted(mCoreThread* context, const QString& fname) { 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 @@ void Window::gameStarted(mCoreThread* context, const QString& fname) { 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 @@ void Window::gameStarted(mCoreThread* context, const QString& fname) { 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 @@ -765,6 +785,9 @@ void Window::gameStopped() { #endif m_screenWidget->setMinimumSize(m_display->minimumSize()); + m_videoLayers->clear(); + m_audioChannels->clear(); + m_fpsTimer.stop(); m_focusCheck.stop(); } @@ -804,7 +827,7 @@ void Window::tryMakePortable() { 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 @@ void Window::openStateWindow(LoadSave ls) { } 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { #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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { }, 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::setupMenu(QMenuBar* menubar) { #ifdef USE_PNG QAction* screenshot = new QAction(tr("Take &screenshot"), avMenu); screenshot->setShortcut(tr("F12")); - connect(screenshot, SIGNAL(triggered()), m_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 + QAction* recordVL = new QAction(tr("Record video log..."), avMenu); + connect(recordVL, &QAction::triggered, this, &Window::startVideoLog); + addControlledAction(avMenu, recordVL, "recordVL"); + m_gameActions.append(recordVL); + + 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); + avMenu->addSeparator(); - QMenu* videoLayers = avMenu->addMenu(tr("Video layers")); - m_inputModel->addMenu(videoLayers, avMenu); + m_videoLayers = avMenu->addMenu(tr("Video layers")); + m_inputModel->addMenu(m_videoLayers, avMenu); - 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* 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")); - - 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 @@ void Window::setupMenu(QMenuBar* menubar) { #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 @@ void Window::setupMenu(QMenuBar* menubar) { 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 @@ void Window::updateMRU() { 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 @@ void WindowBackground::setLockAspectRatio(int width, int height) { m_aspectHeight = height; } +void WindowBackground::setLockIntegerScaling(bool lock) { + m_lockIntegerScaling = lock; +} + void WindowBackground::paintEvent(QPaintEvent*) { const QPixmap* logo = pixmap(); if (!logo) { @@ -1573,6 +1658,10 @@ void WindowBackground::paintEvent(QPaintEvent*) { } 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); painter.drawPixmap(full, *logo); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 058848185..e5f546702 100644 --- a/src/platform/qt/Window.h +++ b/src/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; @@ -49,6 +49,8 @@ public: void resizeFrame(const QSize& size); + void updateMultiplayerStatus(bool canOpenAnother) { m_multiWindow->setEnabled(canOpenAnother); } + signals: void startDrawing(mCoreThread*); void shutdown(); @@ -81,6 +83,8 @@ public slots: void openSettingsWindow(); void openAboutScreen(); + void startVideoLog(); + #ifdef USE_DEBUGGERS void consoleOpen(); #endif @@ -156,44 +160,47 @@ private: #ifdef M_CORE_GBA QList m_gbaActions; #endif + QAction* m_multiWindow; QMap 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 m_frameList; QTimer m_fpsTimer; QList 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 @@ public: 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; }; } diff --git a/src/platform/qt/library/LibraryController.cpp b/src/platform/qt/library/LibraryController.cpp new file mode 100644 index 000000000..ecb8a1fab --- /dev/null +++ b/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 items) { + for (LibraryEntryRef o : items) { + addEntry(o); + } +} +void AbstractGameList::removeEntries(QList 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 LibraryController::selectedPath() { + LibraryEntryRef e = selectedEntry(); + return e ? qMakePair(e->base(), e->filename()) : qMakePair("", ""); +} + +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 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(entry); + m_entries.insert(fullpath, libentry); + newEntries.append(libentry); + } + allEntries.append(fullpath); + } + + // Check for entries that were removed + QList 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)); + } +} + +} diff --git a/src/platform/qt/library/LibraryController.h b/src/platform/qt/library/LibraryController.h new file mode 100644 index 000000000..3cc82c8b5 --- /dev/null +++ b/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 + +#include +#include +#include +#include + +#include + +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 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 items); + + virtual void removeEntry(LibraryEntryRef item) = 0; + virtual void removeEntries(QList 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 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 m_entries; + + LibraryStyle m_currentStyle; + AbstractGameList* m_currentList = nullptr; + + LibraryGrid* m_libraryGrid = nullptr; + LibraryTree* m_libraryTree = nullptr; +}; + +} + +#endif diff --git a/src/platform/qt/library/LibraryGrid.cpp b/src/platform/qt/library/LibraryGrid.cpp new file mode 100644 index 000000000..d4e4acacf --- /dev/null +++ b/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); +} + +} diff --git a/src/platform/qt/library/LibraryGrid.h b/src/platform/qt/library/LibraryGrid.h new file mode 100644 index 000000000..98a0df630 --- /dev/null +++ b/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 + +#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 m_items; + LibraryStyle m_currentStyle; +}; + +} + +#endif diff --git a/src/platform/qt/library/LibraryTree.cpp b/src/platform/qt/library/LibraryTree.cpp new file mode 100644 index 000000000..45b972d77 --- /dev/null +++ b/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 +#include + +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(&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 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); +} + +} diff --git a/src/platform/qt/library/LibraryTree.h b/src/platform/qt/library/LibraryTree.h new file mode 100644 index 000000000..80e3b461d --- /dev/null +++ b/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 + +#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 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 m_items; + QMap m_pathNodes; + + void rebuildTree(); + void resizeAllCols(); +}; + +} + +#endif diff --git a/src/platform/qt/main.cpp b/src/platform/qt/main.cpp index 29df3ea8d..631040d82 100644 --- a/src/platform/qt/main.cpp +++ b/src/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 @@ Q_IMPORT_PLUGIN(QWindowsAudioPlugin); #endif int main(int argc, char* argv[]) { +#ifdef BUILD_SDL + SDL_SetMainReady(); +#endif QGBA::GBAApp application(argc, argv); QLocale locale = QLocale::system(); diff --git a/src/platform/qt/ts.cmake b/src/platform/qt/ts.cmake index 3bb8c2e43..fdf58779a 100644 --- a/src/platform/qt/ts.cmake +++ b/src/platform/qt/ts.cmake @@ -1,8 +1,6 @@ file(GLOB TRANSLATION_FILES "${QM_BASE}/*.qm") file(WRITE ${TRANSLATION_QRC} "\n\t\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${TS_BASE}\n") endforeach() diff --git a/src/platform/qt/ts/mgba-de.ts b/src/platform/qt/ts/mgba-de.ts index 6b748558c..e88df58d0 100644 --- a/src/platform/qt/ts/mgba-de.ts +++ b/src/platform/qt/ts/mgba-de.ts @@ -302,11 +302,31 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. - LibraryView + LibraryTree - - Library - Bibliothek + + Name + Name + + + + Location + Ort + + + + Platform + Plattform + + + + Size + Größe + + + + CRC32 + CRC32 @@ -903,14 +923,14 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::AssetTile - + %0%1%2 %0%1%2 - - - + + + 0x%0 (%1) 0x%0 (%1) @@ -961,22 +981,22 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::GBAKeyEditor - + Clear Button Button löschen - + Clear Analog Analog löschen - + Refresh Aktualisieren - + Set all Alle belegen @@ -1027,17 +1047,17 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::GIFView - + Failed to open output GIF file: %1 Fehler beim Öffnen der Ausgabe-GIF-Datei: %1 - + Select output file Ausgabedatei auswählen - + Graphics Interchange Format (*.gif) Graphics Interchange Format (*.gif) @@ -1045,28 +1065,28 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::GameController - - + + Failed to open game file: %1 Fehler beim Öffnen der Spieldatei: %1 - + Failed to open save file: %1 Fehler beim Öffnen der Speicherdatei: %1 - + Failed to open snapshot file for reading: %1 Konnte Snapshot-Datei %1 nicht zum Lesen öffnen - + Failed to open snapshot file for writing: %1 Konnte Snapshot-Datei %1 nicht zum Schreiben öffnen - + Failed to start audio processor Fehler beim Starten des Audio-Prozessors @@ -1265,7 +1285,7 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. Overflow wraps - Umbrüche + Umbrüche @@ -1309,7 +1329,7 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. Integer part - Ganzzahl-Anteil + Ganzzahl-Anteil @@ -1317,7 +1337,7 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. Integer part (bottom) - Ganzzahl-Anteil (unten) + Ganzzahl-Anteil (unten) @@ -1325,7 +1345,7 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. Integer part (top) - Ganzzahl-Anteil (oben) + Ganzzahl-Anteil (oben) @@ -1414,32 +1434,32 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. Outside window enable BG 0 - Äußeres Fenster: aktiviere BG 0 + Äußeres Fenster: aktiviere BG 0 Outside window enable BG 1 - Äußeres Fenster: aktiviere BG 1 + Äußeres Fenster: aktiviere BG 1 Outside window enable BG 2 - Äußeres Fenster: aktiviere BG 2 + Äußeres Fenster: aktiviere BG 2 Outside window enable BG 3 - Äußeres Fenster: aktiviere BG 3 + Äußeres Fenster: aktiviere BG 3 Outside window enable OBJ - Äußeres Fenster: aktiviere OBJ + Äußeres Fenster: aktiviere OBJ Outside window enable blend - Äußeres Fenster: aktiviere Überblendung + Äußeres Fenster: aktiviere Überblendung @@ -2442,60 +2462,12 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::KeyEditor - - + + --- --- - - QGBA::LibraryModel - - - Name - Name - - - - Filename - Dateiname - - - - Size - Größe - - - - Platform - Plattform - - - - GBA - GBA - - - - GB - GB - - - - ? - ? - - - - Location - Ort - - - - CRC32 - CRC32 - - QGBA::LoadSaveState @@ -2565,63 +2537,63 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::MemoryModel - + Copy selection Auswahl kopieren - + Save selection Auswahl speichern - + Paste Einfügen - + Load Laden - - + + All Alle - + Load TBL TBL laden - + Save selected memory Ausgewählten Speicher abspeichern - + Failed to open output file: %1 Fehler beim Öffnen der Ausgabedatei: %1 - + Load memory Lade Speicher - + Failed to open input file: %1 Fehler beim Laden der Eingabedatei: %1 - + TBL TBL - + ISO-8859-1 ISO-8859-1 @@ -2629,54 +2601,54 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::ObjView - - + + 0x%0 0x%0 - + Off Aus - + Normal Normal - + Trans Trans - + OBJWIN OBJWIN - + Invalid Ungültig - + N/A N/A - + Export sprite Sprite exportieren - + Portable Network Graphics (*.png) Portable Network Graphics (*.png) - + Failed to open output PNG file: %1 Fehler beim Öffnen der Ausgabe-PNG-Datei: %1 @@ -2716,7 +2688,7 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Windows PAL (*.pal);;Adobe Color Table (*.act) - + Failed to open output palette file: %1 Fehler beim Öffnen der Ausgabe-Palettendatei: %1 @@ -2795,37 +2767,37 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::ShaderSelector - + No shader active Kein Shader aktiv - + Load shader Shader laden - + %1 Shader (%.shader) %1 Shader (%.shader) - + No shader loaded Kein Shader geladen - + by %1 von %1 - + Preprocessing Vorbehandlung - + Pass %1 Durchlauf %1 @@ -2833,17 +2805,17 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::ShortcutController - + Action Aktion - + Keyboard Tastatur - + Gamepad Gamepad @@ -2851,17 +2823,17 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::VideoView - + Failed to open output video file: %1 Fehler beim Öffnen der Ausgabe-Videodatei: %1 - + Native (%0x%1) Nativ (%0x%1) - + Select output file Ausgabedatei auswählen @@ -2869,67 +2841,82 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::Window - + Game Boy Advance ROMs (%1) Game Boy Advance-ROMs (%1) - + Game Boy ROMs (%1) Game Boy-ROMs (%1) - + All ROMs (%1) Alle ROMs (%1) - + + %1 Video Logs (*.mvl) + %1 Video-Logs (*.mvl) + + + Archives (%1) Archive (%1) - - - + + + Select ROM ROM auswählen - + Game Boy Advance save files (%1) Game Boy Advance-Speicherdateien (%1) - - - + + + Select save Speicherdatei wählen - + Select patch Patch wählen - + Patches (*.ips *.ups *.bps) Patches (*.ips *.ups *.bps) - - + + GameShark saves (*.sps *.xps) GameShark-Speicherdaten (*.sps *.xps) - + + Select video log + Video-Log auswählen + + + + Video logs (*.mvl) + Video-Logs (*.mvl) + + + Crash Absturz - + The game has crashed with the following error: %1 @@ -2938,637 +2925,645 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. - + Couldn't Load Konnte nicht geladen werden - + Could not load game. Are you sure it's in the correct format? Konnte das Spiel nicht laden. Sind Sie sicher, dass es im korrekten Format vorliegt? - + Unimplemented BIOS call - Nichtimplementierter BIOS-Aufruf + Nicht implementierter BIOS-Aufruf - + This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience. Dieses Spiel verwendet einen BIOS-Aufruf, der nicht implementiert ist. Bitte verwenden Sie für die beste Spielerfahrung das offizielle BIOS. - + Really make portable? Portablen Modus wirklich aktivieren? - + This will make the emulator load its configuration from the same directory as the executable. Do you want to continue? Diese Einstellung wird den Emulator so konfigurieren, dass er seine Konfiguration aus dem gleichen Verzeichnis wie die Programmdatei lädt. Möchten Sie fortfahren? - + Restart needed Neustart benötigt - + Some changes will not take effect until the emulator is restarted. Einige Änderungen werden erst übernommen, wenn der Emulator neu gestartet wurde. - + - Player %1 of %2 - Spieler %1 von %2 - + %1 - %2 %1 - %2 - + %1 - %2 - %3 %1 - %2 - %3 - + %1 - %2 (%3 fps) - %4 %1 - %2 (%3 Bilder/Sekunde) - %4 - + &File &Datei - + Load &ROM... &ROM laden... - + Load ROM in archive... ROM aus Archiv laden... - + Load temporary save... Temporäre Speicherdatei laden... - + Load &patch... &Patch laden... - + Boot BIOS BIOS booten - + Replace ROM... ROM ersetzen... - + ROM &info... ROM-&Informationen... - + Recent Zuletzt verwendet - + Make portable Portablen Modus aktivieren - + &Load state Savestate &laden - + F10 F10 - + &Save state Savestate &speichern - + Shift+F10 Umschalt+F10 - + Quick load Schnell laden - + Quick save Schnell speichern - + Load recent Lade zuletzt gespeicherten Savestate - + Save recent Speichere aktuellen Stand - + Undo load state Laden des Savestate rückgängig machen - + F11 F11 - + Undo save state Speichern des Savestate rückgängig machen - + Shift+F11 Umschalt+F11 - - + + State &%1 Savestate &%1 - + F%1 F%1 - + Shift+F%1 Umschalt+F%1 - + Import GameShark Save Importiere GameShark-Speicherstand - + Export GameShark Save Exportiere GameShark-Speicherstand - + New multiplayer window Neues Multiplayer-Fenster - + About Über - + E&xit &Beenden - + &Emulation &Emulation - + &Reset Zu&rücksetzen - + Ctrl+R Strg+R - + Sh&utdown B&eenden - + Yank game pak Spielmodul herausziehen - + &Pause &Pause - + Ctrl+P Strg+P - + &Next frame &Nächstes Bild - + Ctrl+N Strg+N - + Fast forward (held) Schneller Vorlauf (gehalten) - + &Fast forward Schneller &Vorlauf - + Shift+Tab Umschalt+Tab - + Fast forward speed Vorlauf-Geschwindigkeit - + Unbounded Unbegrenzt - + %0x %0x - + Rewind (held) Zurückspulen (gehalten) - + Re&wind Zur&ückspulen - + ~ ~ - + Step backwards Schrittweiser Rücklauf - + Ctrl+B Strg+B - + Sync to &video Mit &Video synchronisieren - + Sync to &audio Mit &Audio synchronisieren - + Solar sensor Solar-Sensor - + Increase solar level Sonnen-Level erhöhen - + Decrease solar level Sonnen-Level verringern - + Brightest solar level Hellster Sonnen-Level - + Darkest solar level Dunkelster Sonnen-Level - + Brightness %1 Helligkeit %1 - + Audio/&Video Audio/&Video - + Frame size Bildgröße - + %1x %1x - + Toggle fullscreen Vollbildmodus umschalten - + Lock aspect ratio - Seitenverhältnis sperren + Seitenverhältnis korrigieren - + + Force integer scaling + Pixelgenaue Skalierung (Integer scaling) + + + Frame&skip Frame&skip - + Shader options... Shader-Optionen... - + Mute Stummschalten - + FPS target Bildwiederholrate - + 15 15 - + 30 30 - + 45 45 - + Native (59.7) Nativ (59.7) - + 60 60 - + 90 90 - + 120 120 - + 240 240 - + Take &screenshot &Screenshot erstellen - + F12 F12 - + Record output... Ausgabe aufzeichen... - + Record GIF... GIF aufzeichen... - + + Record video log... + Video-Log aufzeichnen... + + + + Stop video log + Video-Log beenden + + + Video layers Video-Ebenen - - Background %0 - Hintergrund %0 - - - - OBJ (sprites) - OBJ (Sprites) - - - + Audio channels Audio-Kanäle - - Channel %0 - Kanal %0 - - - - Channel A - Kanal A - - - - Channel B - Kanal B - - - + &Tools &Werkzeuge - + View &logs... &Logs ansehen... - + Game &overrides... Spiel-&Überschreibungen... - + Game &Pak sensors... Game &Pak-Sensoren... - + &Cheats... &Cheats... - + Open debugger console... Debugger-Konsole äffnen... - + Start &GDB server... &GDB-Server starten... - + Settings... Einstellungen... - + Select folder Ordner auswählen - + Add folder to library... Ordner zur Bibliothek hinzufügen... - + Bilinear filtering Bilineare Filterung - + View &palette... &Palette betrachten... - + View &sprites... &Sprites betrachten... - + View &tiles... &Tiles betrachten... - + View memory... Speicher betrachten... - + View &I/O registers... &I/O-Register betrachten... - + Exit fullscreen Vollbildmodus beenden - + Autofire Autofeuer - + Autofire A Autofeuer A - + Autofire B Autofeuer B - + Autofire L Autofeuer L - + Autofire R Autofeuer R - + Autofire Start Autofeuer Start - + Autofire Select Autofeuer Select - + Autofire Up Autofeuer nach oben - + Autofire Right Autofeuer rechts - + Autofire Down Autofeuer nach unten - + Autofire Left Autofeuer links + + QObject + + + GBA + GBA + + + + GB + GB + + + + ? + ? + + ROMInfo @@ -3838,7 +3833,7 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. - + frames Bilder @@ -3870,67 +3865,90 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. Lock aspect ratio - Seitenverhältnis sperren + Seitenverhältnis korrigieren - + + Force integer scaling + Erzwinge pixelgenaue Skalierung +(Integer scaling) + + + + List view + Listenansicht + + + + Tree view + Baumansicht + + + Library: Bibliothek: - + Show when no game open Anzeigen, wenn kein Spiel geöffnet ist - + Clear cache Cache leeren - + Fast forward speed: Vorlauf-Geschwindigkeit: - + Rewind affects save data Rücklauf beeinflusst Speicherdaten - - - - - - - + + Preload entire ROM into memory + ROM-Datei vollständig +in Arbeitsspeicher vorladen + + + + + + + + + Browse Durchsuchen - + Use BIOS file if found - BIOS-Datei verwenden, wenn vorhanden + BIOS-Datei verwenden, +wenn vorhanden - + Skip BIOS intro BIOS-Intro überspringen - + × × - + Unbounded unbegrenzt - + Suspend screensaver Bildschirmschoner deaktivieren @@ -3940,50 +3958,50 @@ Speicherdaten BIOS - + Pause when inactive Pause, wenn inaktiv - + Run all Alle ausführen - + Remove known Bekannte entfernen - + Detect and remove Erkennen und entfernen - + Allow opposing input directions Gegensätzliche Eingaberichtungen erlauben - - + + Screenshot Screenshot - - + + Save data Speicherdaten - - + + Cheat codes Cheat-Codes - + Enable rewind Rücklauf aktivieren @@ -3993,65 +4011,65 @@ Speicherdaten Bilineare Filterung - + Rewind history: Rücklauf-Verlauf: - + Idle loops: Leerlaufprozesse: - + Savestate extra data: Zusätzliche Savestate-Daten: - + Load extra data: Lade zusätzliche Daten: - + GB BIOS file: - BIOS-Datei für GB: + Datei mit GB-BIOS: - + GBA BIOS file: - BIOS-Datei für GBA: + Datei mit GBA-BIOS: - + GBC BIOS file: - BIOS-Datei für GBC: + Datei mit GBC-BIOS: - + Save games Spielstände - - - - + + + + Same directory as the ROM - Gleiches Verzeichnis wie ROM + Verzeichnis der ROM-Datei - + Save states Savestates - + Screenshots Screenshots - + Patches Patches @@ -4240,86 +4258,91 @@ Speicherdaten + h.264 (NVENC) + h.264 (NVENC) + + + + HEVC + HEVC + + + VP8 VP8 - - Xvid - Xvid - - - + FFV1 FFV1 - + FLAC FLAC - + Opus Opus - + Vorbis Vorbis - + MP3 MP3 - + AAC AAC - + Uncompressed Unkomprimiert - + Bitrate (kbps) Bitrate (kbps) - + VBR VBR - + ABR ABR - + Dimensions Abmessungen - + : : - + × × - + Lock aspect ratio Seitenverhältnis sperren - + Show advanced Erweiterte Optionen anzeigen diff --git a/src/platform/qt/utils.cpp b/src/platform/qt/utils.cpp new file mode 100644 index 000000000..1c16df932 --- /dev/null +++ b/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 + +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("?"); + } +} + +} diff --git a/src/platform/qt/utils.h b/src/platform/qt/utils.h new file mode 100644 index 000000000..210e1f85a --- /dev/null +++ b/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 + +#include + +namespace QGBA { + +QString niceSizeFormat(size_t filesize); +QString nicePlatformFormat(mPlatform platform); + +} + +#endif diff --git a/src/platform/sdl/CMakeLists.txt b/src/platform/sdl/CMakeLists.txt index f4b4c37b9..9a653fcc3 100644 --- a/src/platform/sdl/CMakeLists.txt +++ b/src/platform/sdl/CMakeLists.txt @@ -6,7 +6,6 @@ if (SDL_VERSION EQUAL "2") 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() diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c index 7ed9aa95b..13c20811b 100644 --- a/src/platform/sdl/gl-common.c +++ b/src/platform/sdl/gl-common.c @@ -38,6 +38,9 @@ void mSDLGLCommonInit(struct mSDLRenderer* renderer) { 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 diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c index 0ecf67ecd..b723a495c 100644 --- a/src/platform/sdl/gl-sdl.c +++ b/src/platform/sdl/gl-sdl.c @@ -38,6 +38,7 @@ bool mSDLGLInit(struct mSDLRenderer* renderer) { 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); diff --git a/src/platform/sdl/gles2-sdl.c b/src/platform/sdl/gles2-sdl.c index 7d55362c9..9f9778c3f 100644 --- a/src/platform/sdl/gles2-sdl.c +++ b/src/platform/sdl/gles2-sdl.c @@ -7,7 +7,8 @@ #include "gl-common.h" -#include "core/thread.h" +#include +#include #ifndef __APPLE__ #include @@ -108,6 +109,7 @@ bool mSDLGLES2Init(struct mSDLRenderer* renderer) { 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); diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index d5188d32d..6c2306c09 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -20,14 +20,13 @@ #include #include -#include "feature/commandline.h" +#include #include #include #include #include -#include #define PORT "sdl" @@ -37,7 +36,7 @@ static void mSDLDeinit(struct mSDLRenderer* renderer); 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 @@ int main(int argc, char** argv) { #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 @@ int main(int argc, char** argv) { 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 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { 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; diff --git a/src/platform/sdl/main.h b/src/platform/sdl/main.h index 0477692cb..eb435a1c7 100644 --- a/src/platform/sdl/main.h +++ b/src/platform/sdl/main.h @@ -65,6 +65,7 @@ struct mSDLRenderer { int ratio; bool lockAspectRatio; + bool lockIntegerScaling; bool filter; #ifdef BUILD_GL diff --git a/src/platform/sdl/sw-sdl.c b/src/platform/sdl/sw-sdl.c index 3074dec0d..498c18783 100644 --- a/src/platform/sdl/sw-sdl.c +++ b/src/platform/sdl/sw-sdl.c @@ -5,9 +5,10 @@ * 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 +#include +#include +#include static bool mSDLSWInit(struct mSDLRenderer* renderer); static void mSDLSWRunloop(struct mSDLRenderer* renderer, void* user); diff --git a/src/platform/test/fuzz-main.c b/src/platform/test/fuzz-main.c index d40a15937..fb27bce57 100644 --- a/src/platform/test/fuzz-main.c +++ b/src/platform/test/fuzz-main.c @@ -12,7 +12,7 @@ #include #include -#include "feature/commandline.h" +#include #include #include #include @@ -68,6 +68,9 @@ int main(int argc, char** argv) { 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 @@ int main(int argc, char** argv) { #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 @@ int main(int argc, char** argv) { 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) { diff --git a/src/platform/test/perf-main.c b/src/platform/test/perf-main.c index 03fe093ce..aff3c30d8 100644 --- a/src/platform/test/perf-main.c +++ b/src/platform/test/perf-main.c @@ -11,7 +11,7 @@ #include #include -#include "feature/commandline.h" +#include #include #include #include diff --git a/src/platform/video-backend.h b/src/platform/video-backend.h index 79264c4e2..f45a56dc5 100644 --- a/src/platform/video-backend.h +++ b/src/platform/video-backend.h @@ -35,6 +35,7 @@ struct VideoBackend { bool filter; bool lockAspectRatio; + bool lockIntegerScaling; }; struct VideoShader { diff --git a/src/platform/wii/main.c b/src/platform/wii/main.c index 29096042e..f7561f018 100644 --- a/src/platform/wii/main.c +++ b/src/platform/wii/main.c @@ -641,8 +641,8 @@ void _guiPrepare(void) { } 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); diff --git a/src/util/circle-buffer.c b/src/util/circle-buffer.c index deb426473..c60326e3c 100644 --- a/src/util/circle-buffer.c +++ b/src/util/circle-buffer.c @@ -125,6 +125,34 @@ int CircleBufferWrite16(struct CircleBuffer* buffer, int16_t value) { 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)) { diff --git a/src/util/string.c b/src/util/string.c index a776bc16d..cef28e5cd 100644 --- a/src/util/string.c +++ b/src/util/string.c @@ -5,8 +5,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include + #include +DEFINE_VECTOR(StringList, char*); + #ifndef HAVE_STRNDUP char* strndup(const char* start, size_t len) { // This is suboptimal, but anything recent should have strndup diff --git a/src/util/vfs/vfs-dirent.c b/src/util/vfs/vfs-dirent.c index d124af355..c41c5b728 100644 --- a/src/util/vfs/vfs-dirent.c +++ b/src/util/vfs/vfs-dirent.c @@ -147,7 +147,7 @@ const char* _vdeName(struct VDirEntry* vde) { 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; } diff --git a/src/util/vfs/vfs-fd.c b/src/util/vfs/vfs-fd.c index 2b301703d..4a20eaf79 100644 --- a/src/util/vfs/vfs-fd.c +++ b/src/util/vfs/vfs-fd.c @@ -166,7 +166,11 @@ static bool _vfdSync(struct VFile* vf, const void* buffer, size_t size) { 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; } diff --git a/src/util/vfs/vfs-fifo.c b/src/util/vfs/vfs-fifo.c new file mode 100644 index 000000000..efff7bdab --- /dev/null +++ b/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 +#include + +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; +} diff --git a/src/util/vfs/vfs-mem.c b/src/util/vfs/vfs-mem.c index 7264577cf..2ccdc339c 100644 --- a/src/util/vfs/vfs-mem.c +++ b/src/util/vfs/vfs-mem.c @@ -4,12 +4,14 @@ * 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 +#include #include struct VFileMem { struct VFile d; void* mem; size_t size; + size_t bufferSize; size_t offset; }; @@ -40,6 +42,7 @@ struct VFile* VFileFromMemory(void* mem, size_t size) { vfm->mem = mem; vfm->size = size; + vfm->bufferSize = size; vfm->offset = 0; vfm->d.close = _vfmClose; vfm->d.seek = _vfmSeek; @@ -67,6 +70,7 @@ struct VFile* VFileFromConstMemory(const void* mem, size_t size) { 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 @@ struct VFile* VFileMemChunk(const void* mem, size_t size) { } 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 @@ struct VFile* VFileMemChunk(const void* mem, size_t size) { } 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 _vfmClose(struct VFile* vf) { 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; diff --git a/version.cmake b/version.cmake index 12e0d7c01..2c132db64 100644 --- a/version.cmake +++ b/version.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)