diff --git a/CHANGES b/CHANGES index 1cd6cb461..cc0b78af7 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,13 @@ Features: - 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 @@ -58,6 +65,14 @@ Bugfixes: - 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 @@ -112,6 +127,12 @@ Misc: - 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 medusa alpha 2: (2017-04-26) Features: diff --git a/CMakeLists.txt b/CMakeLists.txt index 075358563..50b434016 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ -cmake_minimum_required(VERSION 2.6) -project(medusa C) +cmake_minimum_required(VERSION 2.8.11) +project(medusa) set(BINARY_NAME medusa-emu CACHE INTERNAL "Name of output binaries") if(NOT MSVC) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-missing-field-initializers -std=c99") @@ -56,12 +56,14 @@ 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 DS_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/ds/renderers/*.c ${CMAKE_CURRENT_SOURCE_DIR}/src/ds/gx/*.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}) @@ -306,6 +308,7 @@ else() endif() set(DISABLE_FRONTENDS ON) set(MINIMAL_CORE ON) + set(ENABLE_EXTRA ON) endif() check_function_exists(chmod HAVE_CHMOD) @@ -397,6 +400,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) @@ -412,7 +416,7 @@ if(USE_EDITLINE) list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/parser.c) if(M_CORE_GBA) list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/arm/debugger/cli-debugger.c) - list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/extra/cli.c) + list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/debugger/cli.c) endif() if(M_CORE_GB) list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/lr35902/debugger/cli-debugger.c) @@ -618,7 +622,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}) @@ -635,7 +641,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}) @@ -693,10 +699,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) @@ -704,8 +709,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) @@ -802,7 +820,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 6bce02e61..d2e3c5da6 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/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/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/core.h b/include/mgba/core/core.h index 06198d8dc..677444fa1 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 #ifdef M_CORE_DS PLATFORM_DS, @@ -42,11 +42,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; @@ -141,6 +144,8 @@ 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*); @@ -152,6 +157,11 @@ struct mCore { 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 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/src/feature/commandline.h b/include/mgba/feature/commandline.h similarity index 100% rename from src/feature/commandline.h rename to include/mgba/feature/commandline.h 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/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 fc751d43a..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); diff --git a/include/mgba/internal/gba/gba.h b/include/mgba/internal/gba/gba.h index 732a4aba6..5710864e0 100644 --- a/include/mgba/internal/gba/gba.h +++ b/include/mgba/internal/gba/gba.h @@ -175,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/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/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/core.c b/src/core/core.c index 1716de843..bd899bea6 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -22,8 +22,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; @@ -44,7 +47,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; @@ -53,6 +56,9 @@ struct mCore* mCoreFindVF(struct VFile* vf) { if (filter->open) { return filter->open(); } +#ifndef MINIMAL_CORE + return mVideoLogCoreFind(vf); +#endif return NULL; } @@ -60,7 +66,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; 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 53ee67188..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,27 +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) { - nextState->truncate(nextState, size); + 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; @@ -88,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 652561b8c..424260b7e 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -168,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 96fc1e1a2..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; 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 7c4111c86..3c149c9fc 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -495,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'; @@ -524,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 07da69760..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 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/core.c b/src/gb/core.c index 8902220c7..996d8ef6e 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 @@ -38,9 +42,27 @@ const static struct mCoreChannelInfo _GBAudioChannels[] = { { 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; @@ -93,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; @@ -541,8 +566,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; } @@ -569,6 +601,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) { @@ -658,6 +703,29 @@ static void _GBCoreEnableAudioChannel(struct mCore* core, size_t id, bool enable } } +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; @@ -665,6 +733,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; @@ -724,6 +793,7 @@ struct mCore* GBCoreCreate(void) { core->cliDebuggerSystem = _GBCoreCliDebuggerSystem; core->attachDebugger = _GBCoreAttachDebugger; core->detachDebugger = _GBCoreDetachDebugger; + core->loadSymbols = _GBCoreLoadSymbols; #endif core->cheatDevice = _GBCoreCheatDevice; core->savedataClone = _GBCoreSavedataClone; @@ -732,5 +802,120 @@ struct mCore* GBCoreCreate(void) { 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 c8922e0b8..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); @@ -43,6 +44,7 @@ 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; @@ -126,6 +128,12 @@ 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]; diff --git a/src/gb/video.c b/src/gb/video.c index cefe73cf7..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, @@ -104,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) { @@ -113,32 +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); } - 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); } @@ -158,10 +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); - } 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); } @@ -176,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); } @@ -191,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); } @@ -202,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); @@ -213,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); } @@ -252,16 +282,16 @@ void _updateFrameCount(struct mTiming* timing, void* context, uint32_t cyclesLat video->p->stream->postVideoFrame(video->p->stream, pixels, stride); } + 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) { @@ -316,9 +346,10 @@ 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); } @@ -342,20 +373,21 @@ 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); } } @@ -469,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/core.c b/src/gba/core.c index b81ff5716..9b3ad9de6 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 @@ -43,11 +45,15 @@ const static struct mCoreChannelInfo _GBAAudioChannels[] = { { 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; @@ -70,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 @@ -87,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; @@ -301,7 +310,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); @@ -346,7 +357,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); @@ -398,16 +409,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 void _GBACoreSetCursorLocation(struct mCore* core, int x, int y) { @@ -567,6 +581,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) { @@ -668,6 +686,33 @@ static void _GBACoreEnableAudioChannel(struct mCore* core, size_t id, bool enabl } } +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; @@ -734,6 +779,7 @@ struct mCore* GBACoreCreate(void) { core->cliDebuggerSystem = _GBACoreCliDebuggerSystem; core->attachDebugger = _GBACoreAttachDebugger; core->detachDebugger = _GBACoreDetachDebugger; + core->loadSymbols = _GBACoreLoadSymbols; #endif core->cheatDevice = _GBACoreCheatDevice; core->savedataClone = _GBACoreSavedataClone; @@ -742,5 +788,116 @@ struct mCore* GBACoreCreate(void) { 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..1fc3ecf3c --- /dev/null +++ b/src/gba/extra/proxy.c @@ -0,0 +1,301 @@ +/* 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; + memset(proxyRenderer->backend->vramBG, 0, sizeof(proxyRenderer->backend->vramBG)); + proxyRenderer->backend->vramBG[0] = &proxyRenderer->logger->vram[0x0000]; + proxyRenderer->backend->vramBG[1] = &proxyRenderer->logger->vram[0x2000]; + proxyRenderer->backend->vramBG[2] = &proxyRenderer->logger->vram[0x4000]; + proxyRenderer->backend->vramBG[3] = &proxyRenderer->logger->vram[0x6000]; + memset(proxyRenderer->backend->vramOBJ, 0, sizeof(proxyRenderer->backend->vramOBJ)); + proxyRenderer->backend->vramOBJ[0] = &proxyRenderer->logger->vram[0x8000]; + proxyRenderer->backend->vramOBJ[1] = &proxyRenderer->logger->vram[0xA000]; + 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[0x0000], proxyRenderer->d.vramBG[0], 0x4000); + memcpy(&proxyRenderer->logger->vram[0x2000], proxyRenderer->d.vramBG[1], 0x4000); + memcpy(&proxyRenderer->logger->vram[0x4000], proxyRenderer->d.vramBG[2], 0x4000); + memcpy(&proxyRenderer->logger->vram[0x6000], proxyRenderer->d.vramBG[3], 0x4000); + memcpy(&proxyRenderer->logger->vram[0x8000], proxyRenderer->d.vramOBJ[0], 0x4000); + memcpy(&proxyRenderer->logger->vram[0xA000], proxyRenderer->d.vramOBJ[1], 0x4000); + + 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; + memcpy(renderer->d.vramBG, renderer->backend->vramBG, sizeof(renderer->backend->vramBG)); + memcpy(renderer->d.vramOBJ, renderer->backend->vramOBJ, sizeof(renderer->backend->vramOBJ)); + 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; + memcpy(renderer->backend->vramBG, renderer->d.vramBG, sizeof(renderer->backend->vramBG)); + memcpy(renderer->backend->vramOBJ, renderer->d.vramOBJ, sizeof(renderer->backend->vramOBJ)); + 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; + if (address < 0x10000) { + return &proxyRenderer->d.vramBG[address >> VRAM_BLOCK_OFFSET][(address & VRAM_BLOCK_MASK) >> 1]; + } else { + return &proxyRenderer->d.vramOBJ[(address & 0x7FFF) >> VRAM_BLOCK_OFFSET][(address & VRAM_BLOCK_MASK) >> 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 93950ed86..91a1bbe15 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -409,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); } @@ -636,7 +632,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) { @@ -683,6 +679,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/io.c b/src/gba/io.c index 44e3ba459..9f67b296f 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -541,6 +541,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); @@ -693,6 +698,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; } @@ -725,6 +731,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; } @@ -818,7 +827,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; @@ -867,6 +875,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 f380834fe..8cf51e27a 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -98,7 +98,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); diff --git a/src/gba/renderers/thread-proxy.c b/src/gba/renderers/thread-proxy.c deleted file mode 100644 index b763f1ecd..000000000 --- a/src/gba/renderers/thread-proxy.c +++ /dev/null @@ -1,397 +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; - memset(renderer->vramBG, 0, sizeof(renderer->vramBG)); - proxyRenderer->backend->vramBG[0] = &proxyRenderer->vramProxy[0x0000]; - proxyRenderer->backend->vramBG[1] = &proxyRenderer->vramProxy[0x2000]; - proxyRenderer->backend->vramBG[2] = &proxyRenderer->vramProxy[0x4000]; - proxyRenderer->backend->vramBG[3] = &proxyRenderer->vramProxy[0x6000]; - memset(renderer->vramOBJ, 0, sizeof(renderer->vramOBJ)); - proxyRenderer->backend->vramOBJ[0] = &proxyRenderer->vramProxy[0x8000]; - proxyRenderer->backend->vramOBJ[1] = &proxyRenderer->vramProxy[0xA000]; - 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->vramBG[0], 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.vramBG[0][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/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/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/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/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 597e49649..75c6f3377 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=["medusa-emu"], 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 bcfea2790..ce6bd3fa0 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() @@ -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,8 +133,6 @@ 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") @@ -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/${BINARY_NAME}.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) -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 862cb3d06..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/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 c0b062afc..515254e23 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -53,9 +53,6 @@ Display* Display::create(QWidget* parent) { Display::Display(QWidget* parent) : QWidget(parent) - , m_lockAspectRatio(false) - , m_lockIntegerScaling(false) - , m_filter(false) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); #ifdef M_CORE_GB @@ -63,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); diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index b836f6047..6b8f05959 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -79,9 +79,9 @@ private: static const int MOUSE_DISAPPEAR_TIMER = 1000; MessagePainter m_messagePainter; - bool m_lockAspectRatio; - bool m_lockIntegerScaling; - bool m_filter; + bool m_lockAspectRatio = false; + bool m_lockIntegerScaling = false; + bool m_filter = false; QTimer m_mouseTimer; int m_coreWidth; int m_coreHeight; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 5adfa8d56..7aa18af04 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,7 +67,7 @@ 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); @@ -488,3 +487,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 0a7227c01..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 @@ -68,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 { @@ -128,3 +130,5 @@ private: } #endif + +#endif diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 88b79c3fe..c905d071a 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) { } diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index eb7ec5723..6fcab1b7a 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -40,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 088d490cc..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; } diff --git a/src/platform/qt/GBAApp.h b/src/platform/qt/GBAApp.h index be0e620d4..fec6056f3 100644 --- a/src/platform/qt/GBAApp.h +++ b/src/platform/qt/GBAApp.h @@ -71,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 2c14890be..4f6b36a32 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -32,45 +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() - , m_videoLayers() - , 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_preload(false) - , m_override(nullptr) { #ifdef M_CORE_GBA m_lux.p = this; @@ -156,6 +127,7 @@ GameController::GameController(QObject* parent) } controller->m_patch = QString(); controller->clearOverride(); + controller->endVideoLog(); QMetaObject::invokeMethod(controller->m_audioProcessor, "pause"); @@ -273,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() { @@ -332,7 +304,7 @@ void GameController::setConfig(const mCoreConfig* config) { } } -#ifdef USE_GDB_STUB +#ifdef USE_DEBUGGERS mDebugger* GameController::debugger() { if (!isLoaded()) { return nullptr; @@ -622,8 +594,9 @@ void GameController::closeGame() { if (!m_gameOpen) { return; } - +#ifdef USE_DEBUGGERS setDebugger(nullptr); +#endif if (mCoreThreadIsPaused(&m_threadContext)) { mCoreThreadUnpause(&m_threadContext); } @@ -646,6 +619,7 @@ 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; @@ -742,8 +716,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; } } } @@ -954,8 +928,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); } }); } @@ -1123,8 +1097,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(); @@ -1219,6 +1193,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; @@ -1247,10 +1247,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()); @@ -1261,7 +1261,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 6990431ed..9fa3ac1f3 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 { @@ -87,7 +88,7 @@ public: int stateSlot() const { return m_stateSlot; } -#ifdef USE_GDB_STUB +#ifdef USE_DEBUGGERS mDebugger* debugger(); void setDebugger(mDebugger*); #endif @@ -177,6 +178,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); @@ -190,60 +194,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; 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; - bool m_preload; + bool m_preload = false; - InputController* m_inputController; - MultiplayerController* m_multiplayer; + InputController* m_inputController = nullptr; + MultiplayerController* m_multiplayer = nullptr; - mAVStream* m_stream; + 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 92e93832d..4185f9d65 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -43,13 +43,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 4ea208500..000000000 --- a/src/platform/qt/LibraryModel.cpp +++ /dev/null @@ -1,324 +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 -#ifdef M_CORE_DS - case PLATFORM_DS: - return tr("DS"); -#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 dc74bebb6..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,6 +243,7 @@ void ObjView::updateTilesGB(bool force) { } #endif +#ifdef USE_PNG void ObjView::exportObj() { GameController::Interrupter interrupter(m_controller); QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"), @@ -282,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 53a8aaa6e..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() { 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 7f2198546..7ee4e1dce 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -145,7 +145,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); @@ -191,6 +196,7 @@ 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); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index db0b49728..120a2dcb3 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -398,28 +398,28 @@ - - + + - Allow opposing input directions - - - - - - - Suspend screensaver + Show when no game open true - - - - Pause when inactive - + + + + + List view + + + + + Tree view + + @@ -429,21 +429,7 @@ - - - - Show when no game open - - - - - - - Qt::Horizontal - - - - + false @@ -453,6 +439,37 @@ + + + + Qt::Horizontal + + + + + + + Allow opposing input directions + + + + + + + Suspend screensaver + + + true + + + + + + + Pause when inactive + + + 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 94da7a4d5..72fd7327f 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); @@ -75,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&))); @@ -98,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); diff --git a/src/platform/qt/VideoView.h b/src/platform/qt/VideoView.h index d366a2d5a..d9b6986b3 100644 --- a/src/platform/qt/VideoView.h +++ b/src/platform/qt/VideoView.h @@ -87,18 +87,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/Window.cpp b/src/platform/qt/Window.cpp index b12705759..e33bd05e2 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" @@ -58,7 +60,7 @@ #ifdef M_CORE_DS #include #endif -#include "feature/commandline.h" +#include #include "feature/sqlite3/no-intro.h" #include @@ -84,30 +86,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/medusa-bg.jpg") , 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); @@ -131,7 +114,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()) { @@ -145,12 +128,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) @@ -161,13 +149,13 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) 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); @@ -178,38 +166,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); @@ -403,6 +391,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(";;"); } @@ -493,8 +482,8 @@ void Window::selectPatch() { } void Window::openView(QWidget* widget) { - connect(this, SIGNAL(shutdown()), widget, SLOT(close())); - connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), 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(); } @@ -515,10 +504,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); } @@ -527,6 +516,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 [=]() { @@ -547,10 +543,10 @@ 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*)), Qt::DirectConnection); - 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, Qt::DirectConnection); + 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()); m_videoView->setNativeFrameRate(m_controller->frameRate()); @@ -559,7 +555,7 @@ void Window::openVideoWindow() { m_videoView->setNativeResolution(m_controller->screenDimensions()); m_videoView->setNativeFrameRate(m_controller->frameRate()); } - connect(this, SIGNAL(shutdown()), m_videoView, SLOT(close())); + connect(this, &Window::shutdown, m_videoView, &QWidget::close); } m_videoView->show(); } @@ -569,11 +565,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(); } @@ -780,7 +776,7 @@ void Window::gameStarted(mCoreThread* context, const QString& fname) { return; } #endif - foreach (QAction* action, m_gameActions) { + for (QAction* action : m_gameActions) { action->setDisabled(false); } for (QPair action : m_platformActions) { @@ -853,7 +849,7 @@ void Window::gameStopped() { for (QPair action : m_platformActions) { action.first->setDisabled(false); } - foreach (QAction* action, m_gameActions) { + for (QAction* action : m_gameActions) { action->setDisabled(true); } setWindowFilePath(QString()); @@ -913,7 +909,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(); } @@ -995,8 +991,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; @@ -1076,14 +1072,14 @@ 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); m_platformActions.append(qMakePair(quickLoad, SUPPORT_GB | SUPPORT_GBA)); 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"); @@ -1094,7 +1090,7 @@ 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); m_platformActions.append(qMakePair(undoLoadState, SUPPORT_GB | SUPPORT_GBA)); @@ -1102,7 +1098,7 @@ void Window::setupMenu(QMenuBar* menubar) { 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); m_platformActions.append(qMakePair(undoSaveState, SUPPORT_GB | SUPPORT_GBA)); @@ -1133,31 +1129,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_platformActions.append(qMakePair(importShark, SUPPORT_GBA)); 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_platformActions.append(qMakePair(exportShark, SUPPORT_GBA)); 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 @@ -1168,18 +1164,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_platformActions.append(qMakePair(yank, SUPPORT_GBA)); addControlledAction(emulationMenu, yank, "yank"); @@ -1190,7 +1186,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); }); @@ -1200,7 +1196,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"); @@ -1240,7 +1236,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); m_platformActions.append(qMakePair(rewind, SUPPORT_GB | SUPPORT_GBA)); @@ -1275,11 +1271,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); @@ -1369,7 +1365,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); } @@ -1407,26 +1403,35 @@ 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 - avMenu->addSeparator(); + 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(); m_videoLayers = avMenu->addMenu(tr("Video layers")); m_inputModel->addMenu(m_videoLayers, avMenu); @@ -1436,7 +1441,7 @@ void Window::setupMenu(QMenuBar* menubar) { 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); @@ -1461,13 +1466,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_platformActions.append(qMakePair(gdbWindow, SUPPORT_GBA | SUPPORT_DS)); addControlledAction(toolsMenu, gdbWindow, "gdbWindow"); #endif @@ -1568,11 +1573,74 @@ void Window::setupMenu(QMenuBar* menubar) { 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); } } diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 597533e7e..89b842866 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,46 +160,47 @@ private: QList m_gameActions; QList m_nonMpActions; QList> m_platformActions; + 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/medusa-bg.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 }; 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..6dc763e4a --- /dev/null +++ b/src/platform/qt/library/LibraryTree.cpp @@ -0,0 +1,176 @@ +/* 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 = count - 1; a >= 0; --a) { + m_widget->takeTopLevelItem(a); + } + + for (QTreeWidgetItem* i : m_pathNodes.values()) { + i->takeChildren(); + } + + 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/ts/medusa-emu-de.ts b/src/platform/qt/ts/medusa-emu-de.ts index bd638013d..7bc5df1ff 100644 --- a/src/platform/qt/ts/medusa-emu-de.ts +++ b/src/platform/qt/ts/medusa-emu-de.ts @@ -302,11 +302,31 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L - LibraryView + LibraryTree - - Library - Bibliothek + + Name + Name + + + + Location + Ort + + + + Platform + Plattform + + + + Size + Größe + + + + CRC32 + CRC32 @@ -903,14 +923,14 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L QGBA::AssetTile - + %0%1%2 %0%1%2 - - - + + + 0x%0 (%1) 0x%0 (%1) @@ -1004,17 +1024,17 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L 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) @@ -1022,23 +1042,23 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L 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 @@ -1242,7 +1262,7 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L Overflow wraps - Umbrüche + Umbrüche @@ -1286,7 +1306,7 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L Integer part - Ganzzahl-Anteil + Ganzzahl-Anteil @@ -1294,7 +1314,7 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L Integer part (bottom) - Ganzzahl-Anteil (unten) + Ganzzahl-Anteil (unten) @@ -1302,7 +1322,7 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L Integer part (top) - Ganzzahl-Anteil (oben) + Ganzzahl-Anteil (oben) @@ -1391,32 +1411,32 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L 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 @@ -2450,65 +2470,12 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L QGBA::KeyEditor - - + + --- --- - - QGBA::LibraryModel - - - Name - Name - - - - Filename - Dateiname - - - - Size - Größe - - - - Platform - Plattform - - - - GBA - GBA - - - - GB - GB - - - - DS - DS - - - - ? - ? - - - - Location - Ort - - - - CRC32 - CRC32 - - QGBA::LoadSaveState @@ -2578,63 +2545,63 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L 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 @@ -2642,54 +2609,54 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L 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 @@ -2798,37 +2765,37 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L 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 @@ -2836,12 +2803,12 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L QGBA::VideoView - + Failed to open output video file: %1 Fehler beim Öffnen der Ausgabe-Videodatei: %1 - + Native (%0x%1) Nativ (%0x%1) @@ -2874,7 +2841,12 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L Alle ROMs (%1) - + + %1 Video Logs (*.mvl) + %1 Video-Logs (*.mvl) + + + Archives (%1) Archive (%1) @@ -2924,7 +2896,16 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L DS-Unterstützung erfordert ein Abbild des BIOS und der Firmware. - + Select video log + Video-Log auswählen + + + + Video logs (*.mvl) + Video-Logs (*.mvl) + + + Crash Absturz @@ -2950,7 +2931,7 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L Unimplemented BIOS call - Nichtimplementierter BIOS-Aufruf + Nicht implementierter BIOS-Aufruf @@ -3400,6 +3381,16 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L + Record video log... + Video-Log aufzeichnen... + + + + Stop video log + Video-Log beenden + + + Video layers Video-Ebenen @@ -3503,11 +3494,84 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L 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 @@ -3778,7 +3842,7 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L - + frames Bilder @@ -3818,49 +3882,56 @@ Game Boy und Game Boy Advance sind eingetragene Warenzeichen von Nintendo Co., L 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 @@ -3877,7 +3948,8 @@ in Arbeitsspeicher vorladen Use BIOS file if found - BIOS-Datei verwenden, wenn vorhanden + BIOS-Datei verwenden, +wenn vorhanden @@ -3885,17 +3957,17 @@ in Arbeitsspeicher vorladen BIOS-Intro überspringen - + × × - + Unbounded unbegrenzt - + Suspend screensaver Bildschirmschoner deaktivieren @@ -3905,50 +3977,50 @@ in Arbeitsspeicher vorladen 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 @@ -3958,39 +4030,39 @@ in Arbeitsspeicher vorladen 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: - GB-BIOS: + Datei mit GB-BIOS: GBA BIOS file: - GBA-BIOS: + Datei mit GBA-BIOS: - + GBC BIOS file: - GBC-BIOS: + Datei mit GBC-BIOS: diff --git a/src/platform/qt/ts/medusa-emu-es.ts b/src/platform/qt/ts/medusa-emu-es.ts index 3c48832a6..98f2c259c 100644 --- a/src/platform/qt/ts/medusa-emu-es.ts +++ b/src/platform/qt/ts/medusa-emu-es.ts @@ -24,11 +24,9 @@ {projectName} desea agradecer a los siguientes mecenas de Patreon: - - © 2013 – 2016 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0 -Game Boy Advance is a registered trademark of Nintendo Co., Ltd. - © 2013 - 2016 Jeffrey Pfau, licenciado bajo la Licencia Pública de Mozilla, versión 2.0 -Game Boy Advance es una marca registrada de Nintendo Co., Ltd. + + {projectName} is an open-source Game Boy/Game Boy Advance/DS emulator + @@ -40,16 +38,17 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. {projectVersion} {projectVersion} + + + © 2013 – 2016 Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0 +Game Boy and Game Boy Advance are registered trademarks of Nintendo Co., Ltd. + + {logo} {logo} - - - {projectName} is an open-source Game Boy Advance emulator - {projectName} es un emulador de Game Boy Advance de código abierto - Branch: <tt>{gitBranch}</tt><br/>Revision: <tt>{gitCommit}</tt> @@ -302,11 +301,31 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. - LibraryView + LibraryTree - - Library - Biblioteca + + Name + Nombre + + + + Location + Ubicación + + + + Platform + Plataforma + + + + Size + Tamaño + + + + CRC32 + CRC32 @@ -903,14 +922,14 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::AssetTile - + %0%1%2 %0%1%2 - - - + + + 0x%0 (%1) 0x%0 (%1) @@ -958,29 +977,6 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Elegir archivo de trucos - - QGBA::GBAKeyEditor - - - Clear Button - Limpiar botones - - - - Clear Analog - Limpiar análogos - - - - Refresh - Actualizar - - - - Set all - Configurar todo - - QGBA::GDBWindow @@ -1027,17 +1023,17 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::GIFView - + Failed to open output GIF file: %1 Error al abrir el archivo de salida GIF: %1 - + Select output file Elegir archivo de salida - + Graphics Interchange Format (*.gif) Formato de intercambio de gráficos (*.gif) @@ -1045,28 +1041,28 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::GameController - - + + Failed to open game file: %1 Error al abrir el archivo del juego: %1 - + Failed to open save file: %1 Error al abrir el archivo de guardado: %1 - + Failed to open snapshot file for reading: %1 Error al leer el archivo de captura: %1 - + Failed to open snapshot file for writing: %1 Error al escribir al archivo de captura: %1 - + Failed to start audio processor Error al iniciar el procesador de audio @@ -2440,60 +2436,43 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. - QGBA::KeyEditor + QGBA::InputController - - - --- - --- + + Autofire + Botones turbo + + + + Bindings + - QGBA::LibraryModel + QGBA::InputModel - - Name - Nombre + + Action + Acción - - Filename - Nombre de archivo + + Keyboard + Teclado - - Size - Tamaño + + Gamepad + Mando + + + QGBA::KeyEditor - - Platform - Plataforma - - - - GBA - GBA - - - - GB - GB - - - - ? - ? - - - - Location - Ubicación - - - - CRC32 - CRC32 + + + --- + --- @@ -2565,63 +2544,63 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::MemoryModel - + Copy selection Copiar selección - + Save selection Guardar selección - + Paste Pegar - + Load Cargar - - + + All Todo - + Load TBL Cargar TBL - + Save selected memory Guardar memoria seleccionada - + Failed to open output file: %1 Error al abrir el archivo de salida: %1 - + Load memory Cargar memoria - + Failed to open input file: %1 Error al abrir el archivo de entrada: %1 - + TBL TBL - + ISO-8859-1 ISO-8859-1 @@ -2629,54 +2608,54 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::ObjView - - + + 0x%0 0x%0 - + Off No - + Normal Normal - + Trans Trans - + OBJWIN OBJWIN - + Invalid Inválido - + N/A n/d - + Export sprite - + Portable Network Graphics (*.png) - + Failed to open output PNG file: %1 @@ -2716,7 +2695,7 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Paleta de WIndows (*.pal);;Tabla de colores Adobe (*.act) - + Failed to open output palette file: %1 Error al abrir el archivo de salida de paleta: %1 @@ -2747,47 +2726,37 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::SettingsView - + Qt Multimedia Qt Multimedia - + SDL SDL - + Software (Qt) Software (Qt) - + OpenGL OpenGL - + OpenGL (force version 1.x) OpenGL (forzar versión 1.x) - - Keyboard - Teclado + + Bindings + - - Controllers - Mandos - - - - Shortcuts - Accesos directos - - - + Select BIOS Elegir BIOS @@ -2795,73 +2764,55 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::ShaderSelector - + No shader active No hay programa shader activo - + Load shader Cargar programa shader - + %1 Shader (%.shader) Programa shader de %1 (%.shader) - + No shader loaded No hay programa shader cargado - + by %1 por %1 - + Preprocessing preprocesamiento - + Pass %1 Paso %1 - - QGBA::ShortcutController - - - Action - Acción - - - - Keyboard - Teclado - - - - Gamepad - Mando - - QGBA::VideoView - + Failed to open output video file: %1 Error al abrir el archivo de salida de video: %1 - + Native (%0x%1) Nativo (%0x%1) - + Select output file Elegir archivo de salida @@ -2869,67 +2820,97 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. QGBA::Window - + Game Boy Advance ROMs (%1) ROMs de Game Boy Advance (%1) - + + DS ROMs (%1) + + + + Game Boy ROMs (%1) ROMs de Game Boy (%1) - + All ROMs (%1) Todas las ROMs (%1) - + + %1 Video Logs (*.mvl) + + + + Archives (%1) Archivos (%1) - - - + + + Select ROM Elegir ROM - + Game Boy Advance save files (%1) Archivos de guardado de Game Boy Advance (%1) - - - + + + Select save Elegir guardado - + Select patch Elegir parche - + Patches (*.ips *.ups *.bps) Parches (*.ips *.ups *.bps) - - + + GameShark saves (*.sps *.xps) Guardados de GameShark (*.sps *.xps) - + + Select video log + + + + + Video logs (*.mvl) + + + + + BIOS required + + + + + DS support requires dumps of the BIOS and firmware. + + + + Crash Error fatal - + The game has crashed with the following error: %1 @@ -2938,641 +2919,664 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. %1 - + Couldn't Load No se pudo cargar - + Could not load game. Are you sure it's in the correct format? No se pudo cargar el juego. ¿Estás seguro de que está en el formato correcto? - + Unimplemented BIOS call Llamada a BIOS no implementada - + This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience. Este juego hizo una llamada al BIOS que no está implementada. Usa el BIOS oficial para obtener la mejor experiencia. - + Really make portable? ¿Realmente hacer "portable"? - + This will make the emulator load its configuration from the same directory as the executable. Do you want to continue? Esto hará que el emulador cargue su configuración desde el mismo directorio que su ejecutable. ¿Quieres continuar? - + Restart needed Reinicio necesario - + Some changes will not take effect until the emulator is restarted. Algunos cambios no tendrán efecto hasta que se reinicie el emulador. - + - Player %1 of %2 - Jugador %1 de %2 - + %1 - %2 %1 - %2 - + %1 - %2 - %3 %1 - %2 - %3 - + %1 - %2 (%3 fps) - %4 %1 - %2 (%3 fps) - %4 - + &File &Archivo - + Load &ROM... Cargar &ROM... - + Load ROM in archive... Cargar ROM dentro de archivo... - + Load temporary save... Cargar guardado temporal... - + Load &patch... Cargar &parche... - + Boot BIOS Iniciar al BIOS - + Replace ROM... Reemplazar ROM... - + ROM &info... &Información de ROM... - + Recent Reciente - + Make portable Hacer "portable" - + &Load state Cargar captura de estado (&L) - + F10 F10 - + &Save state Guardar captura de e&stado - + Shift+F10 DO NOT TRANSLATE Shift+F10 - + Quick load Cargado rápido - + Quick save Guardado rápido - + Load recent Cargar reciente - + Save recent Guardar reciente - + Undo load state Deshacer cargar la captura de estado - + F11 DO NOT TRANSLATE F11 - + Undo save state Deshacer guardar la captura de estado - + Shift+F11 DO NOT TRANSLATE Shift+F11 - - + + State &%1 Captura de estado &%1 - + F%1 F%1 - + Shift+F%1 DO NOT TRANSLATE Shift+F%1 - + Import GameShark Save Importar guardado de GameShark - + Export GameShark Save Exportar guardado de GameShark - + New multiplayer window Nueva ventana multijugador - + About Acerca de - + E&xit Salir (&X) - + &Emulation &Emulación - + &Reset &Reinicializar - + Ctrl+R Ctrl+R - + Sh&utdown Apagar (&U) - + Yank game pak Arrancar el game pak de su ranura - + &Pause &Pausar - + Ctrl+P Ctrl+P - + &Next frame Saltar al próximo cuadro (&N) - + Ctrl+N Ctrl+N - + Fast forward (held) Avance rápido (mantener) - + &Fast forward Avance rápido (&F) - + Shift+Tab Shift+Tab - + Fast forward speed Velocidad de avance rápido - + Unbounded Ilimitado - + %0x %0x - + Rewind (held) Retroceder (mantener) - + Re&wind Retroceder (&W) - + ~ ~ - + Step backwards Paso hacia atrás - + Ctrl+B Ctrl+B - + Sync to &video Sincronizar a &video - + Sync to &audio Sincronizar a &audio - + Solar sensor Sensor solar - + Increase solar level Aumentar nivel solar - + Decrease solar level Disminuir nivel solar - + Brightest solar level Nivel solar más brillante - + Darkest solar level Nivel solar más oscuro - + Brightness %1 Brillo %1 - + Audio/&Video Audio/&Video - + Frame size Tamaño del cuadro - + %1x %1x - + Toggle fullscreen Pantalla completa - + Lock aspect ratio Bloquear relación de aspecto - + + Force integer scaling + + + + Frame&skip &Salto de cuadros - + Shader options... Opciones del programa shader... - + Mute Silenciar - + FPS target Objetivo de FPS - + 15 15 - + 30 30 - + 45 45 - + Native (59.7) Nativo (59.7) - + 60 60 - + 90 90 - + 120 120 - + 240 240 - + Take &screenshot Tomar pantallazo (&S) - + F12 F12 - + Record output... Grabar salida... - + Record GIF... Grabar GIF... - + + Record video log... + + + + + Stop video log + + + + Video layers Capas de video - - Background %0 - Fondo %0 - - - - OBJ (sprites) - OBJ (sprites) - - - + Audio channels Canales de audio - - Channel %0 - Canal %0 - - - - Channel A - Canal A - - - - Channel B - Canal B - - - + &Tools Herramien&tas - + View &logs... Ver registros... (&L) - + Game &overrides... Val&ores específicos por juego... - + Game &Pak sensors... Sensores en el Game &Pak... - + &Cheats... Tru&cos... - + Open debugger console... Abrir la consola de depuración... - + Start &GDB server... Iniciar servidor &GDB... - + Settings... Ajustes... - + + Game Boy Advance + Game Boy Advance + + + + Game Boy + Game Boy + + + + DS + + + + Select folder Elegir carpeta - + Add folder to library... Agregar carpeta a la biblioteca... - + Bilinear filtering - + View &palette... Ver &paleta... - + View &sprites... Ver &sprites... - + View &tiles... Ver &tiles... - + View memory... Ver memoria... - + View &I/O registers... Ver reg&istros E/S... - + Exit fullscreen Salir de pantalla completa - + Autofire Botones turbo - + Autofire A Turbo A - + Autofire B Turbo B - + Autofire L Turbo L - + Autofire R Turbo R - + Autofire Start Turbo Start - + Autofire Select Turbo Select - + Autofire Up Turbo Arriba - + Autofire Right Turbo Derecha - + Autofire Down Turbo Abajo - + Autofire Left Turbo Izquierda + + QObject + + + GBA + GBA + + + + GB + GB + + + + ? + ? + + ROMInfo @@ -3842,7 +3846,7 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. - + frames cuadros @@ -3877,58 +3881,86 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. Bloquear relación de aspecto - + + Force integer scaling + + + + + List view + + + + + Tree view + + + + Library: Biblioteca: - + Show when no game open Mostrar al no haber juego abierto - + Clear cache Limpiar caché - + Fast forward speed: Velocidad de avance rápido: - - - - - - - + + + + + + + + + + Browse Examinar - + + DS BIOS 7 file: + + + + + DS BIOS 9 file: + + + + Use BIOS file if found Usar archivo BIOS si hay - + Skip BIOS intro Saltar pantalla de inicio de la BIOS - + × × - + Unbounded Ilimitado - + Suspend screensaver No permitir protector de pantalla @@ -3938,50 +3970,50 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. BIOS - + Pause when inactive Pausar al estar inactivo - + Run all Ejecutar todos - + Remove known Eliminar los conocidos - + Detect and remove Detectar y eliminar - + Allow opposing input directions Permitir direcciones opuestas - - + + Screenshot Pantallazo - - + + Save data Datos de guardado - - + + Cheat codes Trucos - + Enable rewind Habilitar retroceso @@ -3991,70 +4023,80 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. - + Rewind history: Historial de retroceso: - + Idle loops: Bucles inactivos: - + Savestate extra data: Datos extras en capturas de estado: - + Load extra data: Cargar datos extra: - + Rewind affects save data - + + Preload entire ROM into memory + + + + GB BIOS file: Archivo de BIOS GB: - + GBA BIOS file: Archivo de BIOS GBA: - + GBC BIOS file: Archivo de BIOS GBC: - + + DS firmware file: + + + + Save games Guardados de juego - - - - + + + + Same directory as the ROM Mismo directorio de la ROM - + Save states Capturas de estado - + Screenshots Pantallazos - + Patches Parches @@ -4243,86 +4285,91 @@ Game Boy Advance es una marca registrada de Nintendo Co., Ltd. + h.264 (NVENC) + + + + + HEVC + + + + VP8 - - Xvid - Xvid - - - + FFV1 FFV1 - + FLAC FLAC - + Opus Opus - + Vorbis Vorbis - + MP3 MP3 - + AAC AAC - + Uncompressed Sin comprimir - + Bitrate (kbps) Tasa de bits (kbps) - + VBR VBR - + ABR ABR - + Dimensions Dimensiones - + : : - + × × - + Lock aspect ratio Bloquear relación de aspecto - + Show advanced Mostrar ajustes avanzados 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/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 03f6e4fc0..6c2306c09 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -20,7 +20,7 @@ #include #include -#include "feature/commandline.h" +#include #include #include @@ -114,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)) { 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 7e86ec914..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 @@ -176,9 +176,10 @@ loadError: 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 cab485b59..153c94228 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/util/circle-buffer.c b/src/util/circle-buffer.c index 53a3087ed..c77a71eba 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 + length > 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)) { @@ -205,34 +233,6 @@ int CircleBufferRead32(struct CircleBuffer* buffer, int32_t* value) { return 4; } -size_t CircleBufferWrite(struct CircleBuffer* buffer, const void* input, size_t length) { - int8_t* data = buffer->writePtr; - if (buffer->size + length > 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, (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; -} - size_t CircleBufferRead(struct CircleBuffer* buffer, void* output, size_t length) { int8_t* data = buffer->readPtr; if (buffer->size == 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;