diff --git a/.appveyor.yml b/.appveyor.yml index 552fdbd2f..586eb059e 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -12,7 +12,11 @@ install: - vcpkg --no-dry-run upgrade - rd /Q /S C:\Tools\vcpkg\buildtrees before_build: -- cmake . -DCMAKE_PREFIX_PATH=C:\Qt\5.15\msvc2019_64 -DCMAKE_TOOLCHAIN_FILE=C:\Tools\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-release -DCMAKE_CONFIGURATION_TYPES=Release +- cmake . -DCMAKE_PREFIX_PATH=C:\Qt\5.15\msvc2019_64 \ + -DCMAKE_TOOLCHAIN_FILE=C:\Tools\vcpkg\scripts\buildsystems\vcpkg.cmake \ + -DVCPKG_TARGET_TRIPLET=x64-windows-release \ + -DCMAKE_CONFIGURATION_TYPES=Release \ + -DCMAKE_SYSTEM_VERSION=10.0.22000.1 build: parallel: true project: mGBA.sln diff --git a/CHANGES b/CHANGES index 3aef9e246..f3a8cc539 100644 --- a/CHANGES +++ b/CHANGES @@ -8,42 +8,32 @@ Features: - Debugger: Add range watchpoints Emulation fixes: - ARM: Add framework for coprocessor support - - GB Audio: Fix audio envelope timing resetting too often (fixes mgba.io/i/3164) - - GB I/O: Fix STAT writing IRQ trigger conditions (fixes mgba.io/i/2501) - GB Serialize: Add missing Pocket Cam state to savestates - GB Video: Implement DMG-style sprite ordering - GBA: Unhandled bkpt should be treated as an undefined exception - GBA: Add baseline CP0 (Wii U VC) and CP1 (DCC) implementations - - GBA GPIO: Fix gyro read-out start (fixes mgba.io/i/3141) - - GBA I/O: Fix HALTCNT access behavior (fixes mgba.io/i/2309) - GBA Serialize: Fix some minor save state edge cases - - GBA SIO: Fix MULTI mode SIOCNT bit 7 writes on secondary GBAs (fixes mgba.io/i/3110) - GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) - GBA Video: Improve emulation of window start/end conditions (fixes mgba.io/i/1945) Other fixes: - Core: Fix inconsistencies with setting game-specific overrides (fixes mgba.io/i/2963) - Debugger: Fix writing to specific segment in command-line debugger - - GB: Fix uninitialized save data when loading undersized temporary saves - - GB, GBA Core: Fix memory leak if reloading debug symbols - GB Serialize: Prevent loading invalid states where LY >= 144 in modes other than 1 + - GBA Hardware: Fix loading states unconditionally overwriting GPIO memory - GBA: Fix getting game info for multiboot ROMs - - GBA Audio: Fix crash if audio FIFOs and timers get out of sync - - GBA Audio: Fix crash in audio subsampling if timing lockstep breaks - - GBA Core: Fix loading symbols from ELF files if the file doesn't end with .elf - - GBA Memory: Let raw access read high MMIO addresses - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) - Qt: Fix potential crash when configuring shortcuts - - Qt: Fix crash when applying changes to GB I/O registers in I/O view - - Qt: Fix LCDC background priority/enable bit being mis-mapped in I/O view - - Updater: Fix updating appimage across filesystems Misc: - Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826) - Core: Improve rumble emulation by averaging state over entire frame (fixes mgba.io/i/3232) + - Core: Add MD5 hashing for ROMs - GB: Prevent incompatible BIOSes from being used on differing models - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs - GBA Audio: Remove broken XQ audio pending rewrite + - GBA BIOS: Move SoftReset implementation to assembly - GBA Memory: Improve VRAM access stall cycle estimation + - GBA SIO: Rewrite lockstep driver for improved stability - GBA Video: Add special circlular window handling in OpenGL renderer - Libretro: Add Super Game Boy Color support (closes mgba.io/i/3188) - mGUI: Enable auto-softpatching (closes mgba.io/i/2899) @@ -52,7 +42,36 @@ Misc: - Qt: Remove maligned double-click-to-fullscreen shortcut (closes mgba.io/i/2632) - Qt: Pass logging context through to video proxy thread (fixes mgba.io/i/3095) - Qt: Show maker code and game version in ROM info + - Qt: Show a dummy shader settings tab if shaders aren't supported + - Res: Port NSO-gba-colors shader (closes mgba.io/i/2834) - Scripting: Add `callbacks:oneshot` for single-call callbacks + +0.10.4: (2024-12-07) +Emulation fixes: + - GB Audio: Fix audio envelope timing resetting too often (fixes mgba.io/i/3164) + - GB I/O: Fix STAT writing IRQ trigger conditions (fixes mgba.io/i/2501) + - GBA GPIO: Fix gyro read-out start (fixes mgba.io/i/3141) + - GBA I/O: Fix HALTCNT access behavior (fixes mgba.io/i/2309) + - GBA I/O: Fix audio register 8-bit write behavior (fixes mgba.io/i/3086) + - GBA Serialize: Properly restore GPIO register state (fixes mgba.io/i/3294) + - GBA SIO: Fix MULTI mode SIOCNT bit 7 writes on secondary GBAs (fixes mgba.io/i/3110) +Other fixes: + - Core: Fix patch autoloading leaking the file handle + - GB: Fix uninitialized save data when loading undersized temporary saves + - GB, GBA Core: Fix memory leak if reloading debug symbols + - GB Serialize: Prevent loading invalid states where LY >= 144 in modes other than 1 + - GBA Audio: Fix crash if audio FIFOs and timers get out of sync + - GBA Audio: Fix crash in audio subsampling if timing lockstep breaks + - GBA Core: Fix loading symbols from ELF files if the file doesn't end with .elf + - GBA Memory: Let raw access read high MMIO addresses + - Qt: Fix crash when applying changes to GB I/O registers in I/O view + - Qt: Fix LCDC background priority/enable bit being mis-mapped in I/O view + - Qt: Fix saving named states breaking when screenshot states disabled (fixes mgba.io/i/3320) + - Qt: Fix potential crash on Wayland with OpenGL (fixes mgba.io/i/3276) + - Qt: Fix installer updates if a version number is in the filename (fixes mgba.io/i/3109) + - Updater: Fix updating appimage across filesystems +Misc: + - Qt: Make window corners square on Windows 11 (fixes mgba.io/i/3285) - Switch: Add bilinear filtering option (closes mgba.io/i/3111) - Vita: Add imc0 and xmc0 mount point support diff --git a/CMakeLists.txt b/CMakeLists.txt index 78fddfce4..5daa78976 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -242,8 +242,14 @@ if(APPLE) execute_process(COMMAND xcrun --show-sdk-version OUTPUT_VARIABLE MACOSX_SDK) add_definitions(-D_DARWIN_C_SOURCE) list(APPEND OS_LIB "-framework Foundation") + + # Xcode 15 introduced a warning about duplicate libraries that CMake doesn't disable itself, we do it here globally + if(MACOSX_SDK VERSION_GREATER_EQUAL 10.15) + add_link_options(LINKER:-no_warn_duplicate_libraries) + endif() + if(NOT CMAKE_SYSTEM_VERSION VERSION_LESS "10.0") # Darwin 10.x is Mac OS X 10.6 - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.6") + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.6") endif() # Not supported until Xcode 9 if(CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_LESS "9") @@ -753,6 +759,7 @@ if(USE_ELF) endif() if (USE_DISCORD_RPC) + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.7") add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/discord-rpc discord-rpc EXCLUDE_FROM_ALL) list(APPEND FEATURES DISCORD_RPC) include_directories(AFTER ${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/discord-rpc/include) diff --git a/include/mgba-util/geometry.h b/include/mgba-util/geometry.h index aa7d65e94..5164b0b8d 100644 --- a/include/mgba-util/geometry.h +++ b/include/mgba-util/geometry.h @@ -11,15 +11,15 @@ CXX_GUARD_START struct mSize { - int width; - int height; + int width; + int height; }; struct mRectangle { - int x; - int y; - int width; - int height; + int x; + int y; + int width; + int height; }; void mRectangleUnion(struct mRectangle* dst, const struct mRectangle* add); diff --git a/include/mgba-util/md5.h b/include/mgba-util/md5.h new file mode 100644 index 000000000..9a2c58145 --- /dev/null +++ b/include/mgba-util/md5.h @@ -0,0 +1,34 @@ +/* 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/. + * + * Based on https://github.com/Zunawe/md5-c + * Originally released under the Unlicense */ +#ifndef MD5_H +#define MD5_H + +#include + +CXX_GUARD_START + +struct MD5Context { + size_t size; // Size of input in bytes + uint32_t buffer[4]; // Current accumulation of hash + uint8_t input[0x40]; // Input to be used in the next step + uint8_t digest[0x10]; // Result of algorithm +}; + +void md5Init(struct MD5Context* ctx); +void md5Update(struct MD5Context* ctx, const void* input, size_t len); +void md5Finalize(struct MD5Context* ctx); + +void md5Buffer(const void* input, size_t len, uint8_t* result); + +struct VFile; +bool md5File(struct VFile* vf, uint8_t* result); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/core/core.h b/include/mgba/core/core.h index e8c5df7bc..94dfb1114 100644 --- a/include/mgba/core/core.h +++ b/include/mgba/core/core.h @@ -30,6 +30,7 @@ enum mPlatform { enum mCoreChecksumType { mCHECKSUM_CRC32, + mCHECKSUM_MD5, }; struct mAudioBuffer; diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h index 597387753..033047cac 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -133,6 +133,7 @@ struct mRumbleIntegrator { }; void mRumbleIntegratorInit(struct mRumbleIntegrator*); +void mRumbleIntegratorReset(struct mRumbleIntegrator*); struct mCoreChannelInfo { size_t id; diff --git a/include/mgba/core/lockstep.h b/include/mgba/core/lockstep.h index ac6cb3f84..b83d17657 100644 --- a/include/mgba/core/lockstep.h +++ b/include/mgba/core/lockstep.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016 Jeffrey Pfau +/* Copyright (c) 2013-2024 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 @@ -53,6 +53,25 @@ static inline void mLockstepUnlock(struct mLockstep* lockstep) { } } +struct mLockstepUser { + void (*sleep)(struct mLockstepUser*); + void (*wake)(struct mLockstepUser*); + + int (*requestedId)(struct mLockstepUser*); + void (*playerIdChanged)(struct mLockstepUser*, int id); +}; + +#ifndef DISABLE_THREADING +struct mCoreThread; +struct mLockstepThreadUser { + struct mLockstepUser d; + + struct mCoreThread* thread; +}; + +void mLockstepThreadUserInit(struct mLockstepThreadUser* lockstep, struct mCoreThread* thread); +#endif + CXX_GUARD_END #endif diff --git a/include/mgba/feature/proxy-backend.h b/include/mgba/feature/proxy-backend.h index ffd38eb47..0851b3a68 100644 --- a/include/mgba/feature/proxy-backend.h +++ b/include/mgba/feature/proxy-backend.h @@ -57,7 +57,7 @@ struct mVideoBackendCommand { struct mVideoProxyBackend { struct VideoBackend d; struct VideoBackend* backend; - + struct RingFIFO in; struct RingFIFO out; diff --git a/include/mgba/gba/interface.h b/include/mgba/gba/interface.h index 97e0a5b9c..298292dd3 100644 --- a/include/mgba/gba/interface.h +++ b/include/mgba/gba/interface.h @@ -68,7 +68,9 @@ enum GBAHardwareDevice { HW_TILT = 16, HW_GB_PLAYER = 32, HW_GB_PLAYER_DETECTION = 64, - HW_EREADER = 128 + HW_EREADER = 128, + + HW_GPIO = HW_RTC | HW_RUMBLE | HW_LIGHT_SENSOR | HW_GYRO | HW_TILT, }; struct Configuration; @@ -81,7 +83,7 @@ extern MGBA_EXPORT const int GBA_LUX_LEVELS[10]; enum { mPERIPH_GBA_LUMINANCE = 0x1000, - mPERIPH_GBA_BATTLECHIP_GATE, + mPERIPH_GBA_LINK_PORT, }; struct GBACartridgeOverride { @@ -110,13 +112,22 @@ struct GBASIODriver { bool (*init)(struct GBASIODriver* driver); void (*deinit)(struct GBASIODriver* driver); - bool (*load)(struct GBASIODriver* driver); - bool (*unload)(struct GBASIODriver* driver); - uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value); + void (*reset)(struct GBASIODriver* driver); + uint32_t (*driverId)(const struct GBASIODriver* renderer); + bool (*loadState)(struct GBASIODriver* renderer, const void* state, size_t size); + void (*saveState)(struct GBASIODriver* renderer, void** state, size_t* size); + void (*setMode)(struct GBASIODriver* driver, enum GBASIOMode mode); + bool (*handlesMode)(struct GBASIODriver* driver, enum GBASIOMode mode); + int (*connectedDevices)(struct GBASIODriver* driver); + int (*deviceId)(struct GBASIODriver* driver); + uint16_t (*writeSIOCNT)(struct GBASIODriver* driver, uint16_t value); + uint16_t (*writeRCNT)(struct GBASIODriver* driver, uint16_t value); + bool (*start)(struct GBASIODriver* driver); + void (*finishMultiplayer)(struct GBASIODriver* driver, uint16_t data[4]); + uint8_t (*finishNormal8)(struct GBASIODriver* driver); + uint32_t (*finishNormal32)(struct GBASIODriver* driver); }; -void GBASIOJOYCreate(struct GBASIODriver* sio); - enum GBASIOBattleChipGateFlavor { GBA_FLAVOR_BATTLECHIP_GATE = 4, GBA_FLAVOR_PROGRESS_GATE = 5, @@ -126,7 +137,6 @@ enum GBASIOBattleChipGateFlavor { struct GBASIOBattlechipGate { struct GBASIODriver d; - struct mTimingEvent event; uint16_t chipId; uint16_t data[2]; int state; diff --git a/include/mgba/internal/defines.h b/include/mgba/internal/defines.h index 8af603736..67b8ec8fd 100644 --- a/include/mgba/internal/defines.h +++ b/include/mgba/internal/defines.h @@ -9,23 +9,23 @@ #define mSAVEDATA_CLEANUP_THRESHOLD 15 enum { - mSAVEDATA_DIRT_NONE = 0, + mSAVEDATA_DIRT_NONE = 0, mSAVEDATA_DIRT_NEW = 1, mSAVEDATA_DIRT_SEEN = 2, }; static inline bool mSavedataClean(int* dirty, uint32_t* dirtAge, uint32_t frameCount) { - if (*dirty & mSAVEDATA_DIRT_NEW) { - *dirtAge = frameCount; - *dirty &= ~mSAVEDATA_DIRT_NEW; - if (!(*dirty & mSAVEDATA_DIRT_SEEN)) { - *dirty |= mSAVEDATA_DIRT_SEEN; - } - } else if ((*dirty & mSAVEDATA_DIRT_SEEN) && frameCount - *dirtAge > mSAVEDATA_CLEANUP_THRESHOLD) { - *dirty = 0; - return true; - } - return false; + if (*dirty & mSAVEDATA_DIRT_NEW) { + *dirtAge = frameCount; + *dirty &= ~mSAVEDATA_DIRT_NEW; + if (!(*dirty & mSAVEDATA_DIRT_SEEN)) { + *dirty |= mSAVEDATA_DIRT_SEEN; + } + } else if ((*dirty & mSAVEDATA_DIRT_SEEN) && frameCount - *dirtAge > mSAVEDATA_CLEANUP_THRESHOLD) { + *dirty = 0; + return true; + } + return false; } #endif diff --git a/include/mgba/internal/gba/cart/gpio.h b/include/mgba/internal/gba/cart/gpio.h index 77785e612..be3cd9173 100644 --- a/include/mgba/internal/gba/cart/gpio.h +++ b/include/mgba/internal/gba/cart/gpio.h @@ -86,6 +86,7 @@ struct GBACartridgeHardware { }; void GBAHardwareInit(struct GBACartridgeHardware* gpio, uint16_t* gpioBase); +void GBAHardwareReset(struct GBACartridgeHardware* gpio); void GBAHardwareClear(struct GBACartridgeHardware* gpio); void GBAHardwareInitRTC(struct GBACartridgeHardware* gpio); diff --git a/include/mgba/internal/gba/cart/unlicensed.h b/include/mgba/internal/gba/cart/unlicensed.h new file mode 100644 index 000000000..f9adbee7e --- /dev/null +++ b/include/mgba/internal/gba/cart/unlicensed.h @@ -0,0 +1,77 @@ +/* Copyright (c) 2013-2024 Jeffrey Pfau + * Copyright (c) 2016 taizou + * + * 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_UNLICENSED_H +#define GBA_UNLICENSED_H + +#include + +CXX_GUARD_START + +#include + +enum GBAVFameCartType { + VFAME_STANDARD = 0, + VFAME_GEORGE = 1, + VFAME_ALTERNATE = 2, +}; + +enum GBAUnlCartType { + GBA_UNL_CART_NONE = 0, + GBA_UNL_CART_VFAME = 1, + GBA_UNL_CART_MULTICART = 2, +}; + +struct GBAVFameCart { + enum GBAVFameCartType cartType; + int sramMode; + int romMode; + int8_t writeSequence[5]; + bool acceptingModeChange; +}; + +struct GBAMulticart { + struct mTimingEvent settle; + uint32_t* rom; + size_t fullSize; + + uint8_t bank; + uint8_t offset; + uint8_t size; + bool sramActive; + bool locked; + uint8_t unk; +}; + +struct GBAUnlCart { + enum GBAUnlCartType type; + union { + struct GBAVFameCart vfame; + struct GBAMulticart multi; + }; +}; + +struct GBA; +struct GBAMemory; +void GBAUnlCartInit(struct GBA*); +void GBAUnlCartReset(struct GBA*); +void GBAUnlCartUnload(struct GBA*); +void GBAUnlCartDetect(struct GBA*); +void GBAUnlCartWriteSRAM(struct GBA*, uint32_t address, uint8_t value); +void GBAUnlCartWriteROM(struct GBA*, uint32_t address, uint16_t value); + +struct GBASerializedState; +void GBAUnlCartSerialize(const struct GBA* gba, struct GBASerializedState* state); +void GBAUnlCartDeserialize(struct GBA* gba, const struct GBASerializedState* state); + +bool GBAVFameDetect(struct GBAVFameCart* cart, uint32_t* rom, size_t romSize, uint32_t crc32); +void GBAVFameSramWrite(struct GBAVFameCart* cart, uint32_t address, uint8_t value, uint8_t* sramData); +uint32_t GBAVFameModifyRomAddress(struct GBAVFameCart* cart, uint32_t address, size_t romSize); +uint32_t GBAVFameGetPatternValue(uint32_t address, int bits); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/internal/gba/cart/vfame.h b/include/mgba/internal/gba/cart/vfame.h deleted file mode 100644 index 5bee28848..000000000 --- a/include/mgba/internal/gba/cart/vfame.h +++ /dev/null @@ -1,41 +0,0 @@ -/* Copyright (c) 2016 taizou - * - * 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/. */ - -// Support for copy protected unlicensed games from the company Vast Fame - -#ifndef GBA_VFAME_H -#define GBA_VFAME_H - -#include - -CXX_GUARD_START - -#define DIGIMON_SAPPHIRE_CHINESE_CRC32 0x793A328F - -enum GBAVFameCartType { - VFAME_NO = 0, - VFAME_STANDARD = 1, - VFAME_GEORGE = 2, - VFAME_ALTERNATE = 3, -}; - -struct GBAVFameCart { - enum GBAVFameCartType cartType; - int sramMode; - int romMode; - int8_t writeSequence[5]; - bool acceptingModeChange; -}; - -void GBAVFameInit(struct GBAVFameCart* cart); -void GBAVFameDetect(struct GBAVFameCart* cart, uint32_t* rom, size_t romSize, uint32_t crc32); -void GBAVFameSramWrite(struct GBAVFameCart* cart, uint32_t address, uint8_t value, uint8_t* sramData); -uint32_t GBAVFameModifyRomAddress(struct GBAVFameCart* cart, uint32_t address, size_t romSize); -uint32_t GBAVFameGetPatternValue(uint32_t address, int bits); - -CXX_GUARD_END - -#endif diff --git a/include/mgba/internal/gba/dma.h b/include/mgba/internal/gba/dma.h index 192b3995a..58d590948 100644 --- a/include/mgba/internal/gba/dma.h +++ b/include/mgba/internal/gba/dma.h @@ -48,6 +48,7 @@ struct GBADMA { uint32_t nextDest; int32_t nextCount; uint32_t when; + int32_t cycles; }; struct GBA; @@ -65,6 +66,7 @@ void GBADMARunHblank(struct GBA* gba, int32_t cycles); void GBADMARunVblank(struct GBA* gba, int32_t cycles); void GBADMARunDisplayStart(struct GBA* gba, int32_t cycles); void GBADMAUpdate(struct GBA* gba); +void GBADMARecalculateCycles(struct GBA* gba); CXX_GUARD_END diff --git a/include/mgba/internal/gba/memory.h b/include/mgba/internal/gba/memory.h index c88e1d092..1b32763fc 100644 --- a/include/mgba/internal/gba/memory.h +++ b/include/mgba/internal/gba/memory.h @@ -18,7 +18,7 @@ CXX_GUARD_START #include #include #include -#include +#include enum GBAMemoryRegion { GBA_REGION_BIOS = 0x0, @@ -108,8 +108,8 @@ struct GBAMemory { struct GBACartridgeHardware hw; struct GBASavedata savedata; - struct GBAVFameCart vfame; struct GBAMatrix matrix; + struct GBAUnlCart unl; struct GBACartEReader ereader; size_t romSize; uint32_t romMask; diff --git a/include/mgba/internal/gba/savedata.h b/include/mgba/internal/gba/savedata.h index 274241a76..e69d07834 100644 --- a/include/mgba/internal/gba/savedata.h +++ b/include/mgba/internal/gba/savedata.h @@ -91,6 +91,7 @@ struct GBASavedataRTCBuffer { }; void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf); +void GBASavedataReset(struct GBASavedata* savedata); void GBASavedataDeinit(struct GBASavedata* savedata); void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf, bool writeback); diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index 9bd21821a..6e9a3124a 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -186,12 +186,15 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | bit 3: Reserved * | bits 4 - 15: Light counter * | 0x002C0 - 0x002C0: Light sample - * | 0x002C1 - 0x002C3: Flags + * | 0x002C1: Flags * | bits 0 - 1: Tilt state machine * | bits 2 - 3: GB Player inputs posted - * | bits 4 - 8: GB Player transmit position - * | bits 9 - 23: Reserved - * 0x002C4 - 0x002C7: Game Boy Player next event + * | bits 4 - 7: GB Player transmit position + * | 0x002C2 - 0x002C3: Unlicensed cart flags + * | bits 0 - 4: Cartridge type + * | bits 5 - 7: Cartridge subtype + * | bits 8 - 15: Reserved + * 0x002C4 - 0x002C7: SIO next event * 0x002C8 - 0x002CB: Current DMA transfer word * 0x002CC - 0x002CF: Last DMA transfer PC * 0x002D0 - 0x002DF: Matrix memory command buffer @@ -230,8 +233,23 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | bits 15 - 31: Reserved * 0x00320 - 0x00323: Next IRQ event * 0x00324 - 0x00327: Interruptable BIOS stall cycles - * 0x00328 - 0x00367: Matrix memory mapping table - * 0x00368 - 0x0036F: Reserved (leave zero) + * 0x00328 - 0x0036F: Special cartridge state, one of: + * | Matrix Memory: + * | 0x00328 - 0x00367: Matrix memory mapping table + * | 0x00368 - 0x0036F: Reserved (leave zero) + * | Unlicensed multicart: + * | 0x00328: Bank value + * | 0x00329: Offset value + * | 0x0032A: Size value + * | 0x0032B: SRAM active value + * | 0x0032C: Unknown value + * | 0x0032D: Current size + * | 0x0032E - 0x0032F: Current bank/offset + * | 0x00330 - 0x00333: Next settle event + * | 0x00334 - 0x00337: Flags + * | bit 0: Is settling occuring? + * | bits 1 - 31: Reserved + * | 0x00338 - 0x0036F: Reserved (leave zero) * 0x00370 - 0x0037F: Audio FIFO A samples * 0x00380 - 0x0038F: Audio FIFO B samples * 0x00390 - 0x003CF: Audio rendered samples @@ -269,9 +287,15 @@ DECL_BITS(GBASerializedHWFlags1, LightCounter, 4, 12); DECL_BITFIELD(GBASerializedHWFlags2, uint8_t); DECL_BITS(GBASerializedHWFlags2, TiltState, 0, 2); DECL_BITS(GBASerializedHWFlags2, GbpInputsPosted, 2, 2); -DECL_BITS(GBASerializedHWFlags2, GbpTxPosition, 4, 5); +DECL_BITS(GBASerializedHWFlags2, GbpTxPosition, 4, 4); -DECL_BITFIELD(GBASerializedHWFlags3, uint16_t); +DECL_BITFIELD(GBASerializedUnlCartFlags, uint16_t); +DECL_BITS(GBASerializedUnlCartFlags, Type, 0, 5); +DECL_BITS(GBASerializedUnlCartFlags, Subtype, 5, 3); + +DECL_BITFIELD(GBASerializedMulticartFlags, uint32_t); +DECL_BIT(GBASerializedMulticartFlags, DustSettling, 0); +DECL_BIT(GBASerializedMulticartFlags, Locked, 1); DECL_BITFIELD(GBASerializedSavedataFlags, uint8_t); DECL_BITS(GBASerializedSavedataFlags, FlashState, 0, 2); @@ -287,6 +311,7 @@ DECL_BITS(GBASerializedMiscFlags, KeyIRQKeys, 4, 11); enum { GBA_SUBSYSTEM_VIDEO_RENDERER = 0, + GBA_SUBSYSTEM_SIO_DRIVER = 1, GBA_SUBSYSTEM_MAX, }; @@ -369,8 +394,8 @@ struct GBASerializedState { GBASerializedHWFlags1 flags1; uint8_t lightSample; GBASerializedHWFlags2 flags2; - GBASerializedHWFlags3 flags3; - uint32_t gbpNextEvent; + GBASerializedUnlCartFlags unlCartFlags; + uint32_t sioNextEvent; } hw; uint32_t dmaTransferRegister; @@ -406,8 +431,29 @@ struct GBASerializedState { uint32_t nextIrq; int32_t biosStall; - uint32_t matrixMappings[16]; - uint32_t reservedMatrix[2]; + union { + struct { + uint32_t mappings[16]; + uint32_t reserved[2]; + } matrix2; + struct { + uint8_t bank; + uint8_t offset; + uint8_t size; + uint8_t sramActive; + uint8_t unk; + uint8_t currentSize; + uint16_t currentOffset; + uint32_t settleNextEvent; + GBASerializedMulticartFlags flags; + } multicart; + struct { + int16_t sramMode; + int16_t romMode; + int8_t writeSequence[5]; + bool acceptingModeChange; + } vfame; + }; struct { int8_t chA[16]; diff --git a/include/mgba/internal/gba/sio.h b/include/mgba/internal/gba/sio.h index 260772f55..14a2136b8 100644 --- a/include/mgba/internal/gba/sio.h +++ b/include/mgba/internal/gba/sio.h @@ -16,8 +16,6 @@ CXX_GUARD_START #define MAX_GBAS 4 -extern const int GBASIOCyclesPerTransfer[4][MAX_GBAS]; - mLOG_DECLARE_CATEGORY(GBA_SIO); enum { @@ -54,37 +52,45 @@ DECL_BITS(GBASIOMultiplayer, Id, 4, 2); DECL_BIT(GBASIOMultiplayer, Error, 6); DECL_BIT(GBASIOMultiplayer, Busy, 7); DECL_BIT(GBASIOMultiplayer, Irq, 14); - -struct GBASIODriverSet { - struct GBASIODriver* normal; - struct GBASIODriver* multiplayer; - struct GBASIODriver* joybus; -}; +DECL_BITFIELD(GBASIORegisterRCNT, uint16_t); +DECL_BIT(GBASIORegisterRCNT, Sc, 0); +DECL_BIT(GBASIORegisterRCNT, Sd, 1); +DECL_BIT(GBASIORegisterRCNT, Si, 2); +DECL_BIT(GBASIORegisterRCNT, So, 3); +DECL_BIT(GBASIORegisterRCNT, ScDirection, 4); +DECL_BIT(GBASIORegisterRCNT, SdDirection, 5); +DECL_BIT(GBASIORegisterRCNT, SiDirection, 6); +DECL_BIT(GBASIORegisterRCNT, SoDirection, 7); struct GBASIO { struct GBA* p; enum GBASIOMode mode; - struct GBASIODriverSet drivers; - struct GBASIODriver* activeDriver; + struct GBASIODriver* driver; uint16_t rcnt; uint16_t siocnt; struct GBASIOPlayer gbp; + struct mTimingEvent completeEvent; }; void GBASIOInit(struct GBASIO* sio); void GBASIODeinit(struct GBASIO* sio); void GBASIOReset(struct GBASIO* sio); -void GBASIOSetDriverSet(struct GBASIO* sio, struct GBASIODriverSet* drivers); -void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver, enum GBASIOMode mode); +void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver); void GBASIOWriteRCNT(struct GBASIO* sio, uint16_t value); void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value); uint16_t GBASIOWriteRegister(struct GBASIO* sio, uint32_t address, uint16_t value); +int32_t GBASIOTransferCycles(enum GBASIOMode mode, uint16_t siocnt, int connected); + +void GBASIOMultiplayerFinishTransfer(struct GBASIO* sio, uint16_t data[4], uint32_t cyclesLate); +void GBASIONormal8FinishTransfer(struct GBASIO* sio, uint8_t data, uint32_t cyclesLate); +void GBASIONormal32FinishTransfer(struct GBASIO* sio, uint32_t data, uint32_t cyclesLate); + int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data); CXX_GUARD_END diff --git a/include/mgba/internal/gba/sio/gbp.h b/include/mgba/internal/gba/sio/gbp.h index 6713cdcaf..fe32cd8c1 100644 --- a/include/mgba/internal/gba/sio/gbp.h +++ b/include/mgba/internal/gba/sio/gbp.h @@ -21,7 +21,6 @@ struct GBASIOPlayer { struct GBA* p; unsigned inputsPosted; int txPosition; - struct mTimingEvent event; struct GBASIOPlayerKeyCallback callback; bool oldOpposingDirections; struct mKeyCallback* oldCallback; diff --git a/include/mgba/internal/gba/sio/lockstep.h b/include/mgba/internal/gba/sio/lockstep.h index 13c181039..0053e6b94 100644 --- a/include/mgba/internal/gba/sio/lockstep.h +++ b/include/mgba/internal/gba/sio/lockstep.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau +/* Copyright (c) 2013-2024 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 @@ -13,40 +13,82 @@ CXX_GUARD_START #include #include #include +#include +#include +#include -struct GBASIOLockstep { - struct mLockstep d; - struct GBASIOLockstepNode* players[MAX_GBAS]; - int attachedMulti; - int attachedNormal; +#define MAX_LOCKSTEP_EVENTS 8 - uint16_t multiRecv[MAX_GBAS]; - uint32_t normalRecv[MAX_GBAS]; +enum GBASIOLockstepEventType { + SIO_EV_ATTACH, + SIO_EV_DETACH, + SIO_EV_HARD_SYNC, + SIO_EV_MODE_SET, + SIO_EV_TRANSFER_START, }; -struct GBASIOLockstepNode { - struct GBASIODriver d; - struct GBASIOLockstep* p; - struct mTimingEvent event; +struct GBASIOLockstepCoordinator { + struct Table players; + Mutex mutex; - volatile int32_t nextEvent; - int32_t eventDiff; - bool normalSO; - int id; + unsigned nextId; + + unsigned attachedPlayers[MAX_GBAS]; + int nAttached; + uint32_t waiting; + + bool transferActive; + enum GBASIOMode transferMode; + + int32_t cycle; + int32_t nextHardSync; + + uint16_t multiData[4]; + uint32_t normalData[4]; +}; + +struct GBASIOLockstepEvent { + enum GBASIOLockstepEventType type; + int32_t timestamp; + struct GBASIOLockstepEvent* next; + int playerId; + union { + enum GBASIOMode mode; + int32_t finishCycle; + }; +}; + +struct GBASIOLockstepPlayer { + struct GBASIOLockstepDriver* driver; + int playerId; enum GBASIOMode mode; - bool transferFinished; -#ifndef NDEBUG - int transferId; - enum mLockstepPhase phase; -#endif + enum GBASIOMode otherModes[MAX_GBAS]; + bool asleep; + int32_t cycleOffset; + struct GBASIOLockstepEvent* queue; + bool dataReceived; + + struct GBASIOLockstepEvent buffer[MAX_LOCKSTEP_EVENTS]; + struct GBASIOLockstepEvent* freeList; }; -void GBASIOLockstepInit(struct GBASIOLockstep*); +struct GBASIOLockstepDriver { + struct GBASIODriver d; + struct GBASIOLockstepCoordinator* coordinator; + struct mTimingEvent event; + unsigned lockstepId; -void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode*); + struct mLockstepUser* user; +}; -bool GBASIOLockstepAttachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*); -void GBASIOLockstepDetachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*); +void GBASIOLockstepCoordinatorInit(struct GBASIOLockstepCoordinator*); +void GBASIOLockstepCoordinatorDeinit(struct GBASIOLockstepCoordinator*); + +void GBASIOLockstepCoordinatorAttach(struct GBASIOLockstepCoordinator*, struct GBASIOLockstepDriver*); +void GBASIOLockstepCoordinatorDetach(struct GBASIOLockstepCoordinator*, struct GBASIOLockstepDriver*); +size_t GBASIOLockstepCoordinatorAttached(struct GBASIOLockstepCoordinator*); + +void GBASIOLockstepDriverCreate(struct GBASIOLockstepDriver*, struct mLockstepUser*); CXX_GUARD_END diff --git a/include/mgba/script/macros.h b/include/mgba/script/macros.h index 70eed0af5..a1ef0d669 100644 --- a/include/mgba/script/macros.h +++ b/include/mgba/script/macros.h @@ -76,10 +76,14 @@ CXX_GUARD_START #define _mSCRIPT_FIELD_NAME(V) (V)->type->name #define _mSCRIPT_WRAPPED_FIELD_NAME(V) (V)->value.wrapped->type->name -#define _mSCRIPT_CALL_VOID(FUNCTION, NPARAMS) FUNCTION(_mCAT(mSCRIPT_ARG_NAMES_, NPARAMS)) +#define _mSCRIPT_CALL_VOID(FUNCTION, NPARAMS) \ + FUNCTION(_mCAT(mSCRIPT_ARG_NAMES_, NPARAMS)); \ + mScriptListClear(&frame->stack) + #define _mSCRIPT_CALL(RETURN, FUNCTION, NPARAMS) \ mSCRIPT_TYPE_C_ ## RETURN out = FUNCTION(_mCAT(mSCRIPT_ARG_NAMES_, NPARAMS)); \ - mSCRIPT_PUSH(&frame->returnValues, RETURN, out) + mScriptListClear(&frame->stack); \ + mSCRIPT_PUSH(&frame->stack, RETURN, out) #define mSCRIPT_DECLARE_STRUCT(STRUCT) \ extern const struct mScriptType mSTStruct_ ## STRUCT; \ @@ -249,8 +253,8 @@ CXX_GUARD_START }, #define _mSCRIPT_STRUCT_METHOD_POP(TYPE, S, NPARAMS, ...) \ - _mCALL(_mCAT(mSCRIPT_POP_, _mSUCC_ ## NPARAMS), &frame->arguments, _mCOMMA_ ## NPARAMS(S(TYPE), __VA_ARGS__)); \ - if (mScriptListSize(&frame->arguments)) { \ + _mCALL(_mCAT(mSCRIPT_POP_, _mSUCC_ ## NPARAMS), &frame->stack, _mCOMMA_ ## NPARAMS(S(TYPE), __VA_ARGS__)); \ + if (mScriptListSize(&frame->stack)) { \ return false; \ } @@ -326,11 +330,11 @@ CXX_GUARD_START static const struct mScriptFunctionOverload _mSTStructBindingOverloads_ ## TYPE ## _ ## NAME[mSCRIPT_OVERLOADS_MAX]; \ static bool _mSTStructBinding_ ## TYPE ## _ ## NAME(struct mScriptFrame* frame, void* ctx) { \ UNUSED(ctx); \ - const struct mScriptFunctionOverload* overload = mScriptFunctionFindOverload(_mSTStructBindingOverloads_ ## TYPE ## _ ## NAME, &frame->arguments); \ + const struct mScriptFunctionOverload* overload = mScriptFunctionFindOverload(_mSTStructBindingOverloads_ ## TYPE ## _ ## NAME, &frame->stack); \ if (!overload) { \ return false; \ } \ - if (!mScriptCoerceFrame(&overload->type->details.function.parameters, &frame->arguments, &frame->arguments)) { \ + if (!mScriptCoerceFrame(&overload->type->details.function.parameters, &frame->stack, &frame->stack)) { \ return false; \ } \ return overload->function->call(frame, overload->function->context); \ @@ -552,8 +556,8 @@ CXX_GUARD_START #define _mSCRIPT_BIND_N_FUNCTION(NAME, RETURN, FUNCTION, DEFAULTS, NPARAMS, ...) \ static bool _binding_ ## NAME(struct mScriptFrame* frame, void* ctx) { \ UNUSED(ctx); \ - _mCALL(mSCRIPT_POP_ ## NPARAMS, &frame->arguments, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \ - if (mScriptListSize(&frame->arguments)) { \ + _mCALL(mSCRIPT_POP_ ## NPARAMS, &frame->stack, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \ + if (mScriptListSize(&frame->stack)) { \ return false; \ } \ _mSCRIPT_CALL(RETURN, FUNCTION, NPARAMS); \ @@ -564,8 +568,8 @@ CXX_GUARD_START #define _mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, DEFAULTS, NPARAMS, ...) \ static bool _binding_ ## NAME(struct mScriptFrame* frame, void* ctx) { \ UNUSED(ctx); \ - _mCALL(mSCRIPT_POP_ ## NPARAMS, &frame->arguments, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \ - if (mScriptListSize(&frame->arguments)) { \ + _mCALL(mSCRIPT_POP_ ## NPARAMS, &frame->stack, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \ + if (mScriptListSize(&frame->stack)) { \ return false; \ } \ _mSCRIPT_CALL_VOID(FUNCTION, NPARAMS); \ diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index 5b07abe3b..684059d32 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -325,8 +325,7 @@ struct mScriptString { }; struct mScriptFrame { - struct mScriptList arguments; - struct mScriptList returnValues; + struct mScriptList stack; // TODO: Exception/failure codes }; diff --git a/res/mgba-qt.desktop b/res/mgba-qt.desktop index d5d98ab37..2dc96901c 100644 --- a/res/mgba-qt.desktop +++ b/res/mgba-qt.desktop @@ -10,3 +10,4 @@ Comment=Nintendo Game Boy Advance Emulator Categories=Game;Emulator; MimeType=application/x-gameboy-advance-rom;application/x-agb-rom;application/x-gba-rom; Keywords=emulator;Nintendo;advance;gba;Game Boy Advance; +StartupWMClass=mGBA diff --git a/res/scripts/input-display.lua b/res/scripts/input-display.lua new file mode 100644 index 000000000..e3db0340c --- /dev/null +++ b/res/scripts/input-display.lua @@ -0,0 +1,122 @@ +input_display = { + anchor = "top", + offset = { + x = 0, + y = 0, + } +} + +local state = { + drawButton = { + [0] = function(state) -- A + state.painter:drawCircle(27, 6, 4) + end, + [1] = function(state) -- B + state.painter:drawCircle(23, 8, 4) + end, + [2] = function(state) -- Select + state.painter:drawCircle(13, 11, 3) + end, + [3] = function(state) -- Start + state.painter:drawCircle(18, 11, 3) + end, + [4] = function(state) -- Right + state.painter:drawRectangle(9, 7, 4, 3) + end, + [5] = function(state) -- Left + state.painter:drawRectangle(2, 7, 4, 3) + end, + [6] = function(state) -- Up + state.painter:drawRectangle(6, 3, 3, 4) + end, + [7] = function(state) -- Down + state.painter:drawRectangle(6, 10, 3, 4) + end, + [8] = function(state) -- R + state.painter:drawRectangle(28, 0, 4, 3) + end, + [9] = function(state) -- L + state.painter:drawRectangle(0, 0, 4, 3) + end + }, + maxKey = { + [C.PLATFORM.GBA] = 9, + [C.PLATFORM.GB] = 7, + } +} +state.overlay = canvas:newLayer(32, 16) +state.painter = image.newPainter(state.overlay.image) +state.painter:setBlend(false) +state.painter:setFill(true) + +function state.update() + local keys = util.expandBitmask(emu:getKeys()) + local maxKey = state.maxKey[emu:platform()] + + for key = 0, maxKey do + if emu:getKey(key) ~= 0 then + state.painter:setFillColor(0x80FFFFFF) + else + state.painter:setFillColor(0x40404040) + end + state.drawButton[key](state) + end + state.overlay:update() +end + +function state.reset() + local endX = canvas:screenWidth() - 32 + local endY = canvas:screenHeight() - 16 + + local anchors = { + topLeft = { + x = 0, + y = 0 + }, + top = { + x = endX / 2, + y = 0 + }, + topRight = { + x = endX, + y = 0 + }, + left = { + x = 0, + y = endY / 2 + }, + center = { + x = endX / 2, + y = endY / 2 + }, + right = { + x = endX, + y = endY / 2 + }, + bottomLeft = { + x = 0, + y = endY + }, + bottom = { + x = endX / 2, + y = endY + }, + bottomRight = { + x = endX, + y = endY + }, + } + + local pos = anchors[input_display.anchor]; + pos.x = pos.x + input_display.offset.x; + pos.y = pos.y + input_display.offset.y; + + state.overlay:setPosition(pos.x, pos.y); + state.painter:setFillColor(0x40808080) + state.painter:drawRectangle(0, 0, 32, 16) + state.overlay:update() +end + +state.reset() +callbacks:add("frame", state.update) +callbacks:add("start", state.reset) diff --git a/res/shaders/gba-color.shader/gba-color.fs b/res/shaders/gba-color.shader/gba-color.fs index 09177ddfe..461228c20 100644 --- a/res/shaders/gba-color.shader/gba-color.fs +++ b/res/shaders/gba-color.shader/gba-color.fs @@ -1,34 +1,21 @@ +// Shader that replicates the LCD Colorspace from Gameboy Advance -- varying vec2 texCoord; +varying mat4 profile; uniform sampler2D tex; uniform vec2 texSize; uniform float darken_screen; -const float target_gamma = 2.2; -const float display_gamma = 2.5; -const float sat = 1.0; -const float lum = 0.99; -const float contrast = 1.0; -const vec3 bl = vec3(0.0, 0.0, 0.0); -const vec3 r = vec3(0.84, 0.09, 0.15); -const vec3 g = vec3(0.18, 0.67, 0.10); -const vec3 b = vec3(0.0, 0.26, 0.73); +const float target_gamma = 2.0; +const float display_gamma = 2.0; void main() { + // bring out our stored luminance value + float lum = profile[3].w; + + // our adjustments need to happen in linear gamma vec4 screen = pow(texture2D(tex, texCoord), vec4(target_gamma + darken_screen)).rgba; - vec4 avglum = vec4(0.5); - screen = mix(screen, avglum, (1.0 - contrast)); - - mat4 color = mat4( r.r, r.g, r.b, 0.0, - g.r, g.g, g.b, 0.0, - b.r, b.g, b.b, 0.0, - bl.r, bl.g, bl.b, 1.0); - - mat4 adjust = mat4( (1.0 - sat) * 0.3086 + sat, (1.0 - sat) * 0.3086, (1.0 - sat) * 0.3086, 1.0, - (1.0 - sat) * 0.6094, (1.0 - sat) * 0.6094 + sat, (1.0 - sat) * 0.6094, 1.0, - (1.0 - sat) * 0.0820, (1.0 - sat) * 0.0820, (1.0 - sat) * 0.0820 + sat, 1.0, - 0.0, 0.0, 0.0, 1.0); - color *= adjust; + screen = clamp(screen * lum, 0.0, 1.0); - screen = color * screen; - gl_FragColor = pow(screen, vec4(1.0 / display_gamma + (darken_screen * 0.125))); + screen = profile * screen; + gl_FragColor = pow(screen, vec4(1.0 / display_gamma)); } diff --git a/res/shaders/gba-color.shader/gba-color.vs b/res/shaders/gba-color.shader/gba-color.vs new file mode 100644 index 000000000..bbf406278 --- /dev/null +++ b/res/shaders/gba-color.shader/gba-color.vs @@ -0,0 +1,34 @@ +uniform int color_mode; +attribute vec4 position; +varying vec2 texCoord; +varying mat4 profile; + +const mat4 GBA_sRGB = mat4( + 0.80, 0.135, 0.195, 0.0, //red channel + 0.275, 0.64, 0.155, 0.0, //green channel + -0.075, 0.225, 0.65, 0.0, //blue channel + 0.0, 0.0, 0.0, 0.93 //alpha channel +); + +const mat4 GBA_DCI = mat4( + 0.685, 0.16, 0.20, 0.0, //red channel + 0.34, 0.629, 0.19, 0.0, //green channel + -0.025, 0.211, 0.61, 0.0, //blue channel + 0.0, 0.0, 0.0, 0.975 //alpha channel +); + +const mat4 GBA_Rec2020 = mat4( + 0.555, 0.1825, 0.20, 0.0, //red channel + 0.395, 0.61, 0.195, 0.0, //green channel + 0.05, 0.2075, 0.605, 0.0, //blue channel + 0.0, 0.0, 0.0, 1.0 //alpha channel +); + +void main() { + if (color_mode == 1) profile = GBA_sRGB; + else if (color_mode == 2) profile = GBA_DCI; + else if (color_mode == 3) profile = GBA_Rec2020; + + gl_Position = position; + texCoord = (position.st + vec2(1.0, 1.0)) * vec2(0.5, 0.5); +} diff --git a/res/shaders/gba-color.shader/manifest.ini b/res/shaders/gba-color.shader/manifest.ini index 8f9735aa3..ed16900cd 100644 --- a/res/shaders/gba-color.shader/manifest.ini +++ b/res/shaders/gba-color.shader/manifest.ini @@ -6,6 +6,7 @@ passes=1 [pass.0] fragmentShader=gba-color.fs +vertexShader=gba-color.vs blend=1 width=-1 height=-1 @@ -14,3 +15,10 @@ height=-1 type=float default=0.5 readableName=Darken Screen + +[pass.0.uniform.color_mode] +type=int +default=1 +min=1 +max=3 +readableName=Color Profile (1=sRGB, 2=DCI, 3=Rec2020) diff --git a/res/shaders/nso-gba-color.shader/manifest.ini b/res/shaders/nso-gba-color.shader/manifest.ini new file mode 100644 index 000000000..42faa25bd --- /dev/null +++ b/res/shaders/nso-gba-color.shader/manifest.ini @@ -0,0 +1,24 @@ +[shader] +name=NSO GBA Color +author=Pokefan531 and hunterk +description=Shader that replicates the Nintendo Switch Online's GBA color filter. +passes=1 + +[pass.0] +fragmentShader=nso-gba-color.fs +vertexShader=nso-gba-color.vs +blend=1 +width=-1 +height=-1 + +[pass.0.uniform.darken_screen] +type=float +default=0.8 +readableName=Darken Screen + +[pass.0.uniform.color_mode] +type=int +default=1 +min=1 +max=3 +readableName=Color Profile (1=sRGB, 2=DCI, 3=Rec2020) diff --git a/res/shaders/nso-gba-color.shader/nso-gba-color.fs b/res/shaders/nso-gba-color.shader/nso-gba-color.fs new file mode 100644 index 000000000..8d8a0ec92 --- /dev/null +++ b/res/shaders/nso-gba-color.shader/nso-gba-color.fs @@ -0,0 +1,21 @@ +// Shader that replicates the LCD Colorspace from Gameboy Advance -- +varying vec2 texCoord; +varying mat4 profile; +uniform sampler2D tex; +uniform vec2 texSize; + +uniform float darken_screen; +const float target_gamma = 2.2; +const float display_gamma = 2.2; + +void main() { + // bring out our stored luminance value + float lum = profile[3].w; + + // our adjustments need to happen in linear gamma + vec4 screen = pow(texture2D(tex, texCoord), vec4(target_gamma + darken_screen)).rgba; + + screen = clamp(screen * lum, 0.0, 1.0); + screen = profile * screen; + gl_FragColor = pow(screen, vec4(1.0 / display_gamma)); +} diff --git a/res/shaders/nso-gba-color.shader/nso-gba-color.vs b/res/shaders/nso-gba-color.shader/nso-gba-color.vs new file mode 100644 index 000000000..c9c89181b --- /dev/null +++ b/res/shaders/nso-gba-color.shader/nso-gba-color.vs @@ -0,0 +1,34 @@ +uniform int color_mode; +attribute vec4 position; +varying vec2 texCoord; +varying mat4 profile; + +const mat4 GBA_sRGB = mat4( + 0.865, 0.0575, 0.0575, 0.0, //red channel + 0.1225, 0.925, 0.1225, 0.0, //green channel + 0.0125, 0.0125, 0.82, 0.0, //blue channel + 0.0, 0.0, 0.0, 1.0 //alpha channel +); + +const mat4 GBA_DCI = mat4( + 0.72, 0.0875, 0.0725, 0.0, //red channel + 0.2675, 0.9, 0.185, 0.0, //green channel + 0.0125, 0.0125, 0.7425, 0.0, //blue channel + 0.0, 0.0, 0.0, 1.0 //alpha channel +); + +const mat4 GBA_Rec2020 = mat4( + 0.57, 0.115, 0.0725, 0.0, //red channel + 0.3825, 0.8625, 0.195, 0.0, //green channel + 0.0475, 0.0225, 0.7325, 0.0, //blue channel + 0.0, 0.0, 0.0, 1.0 //alpha channel +); + +void main() { + if (color_mode == 1) profile = GBA_sRGB; + else if (color_mode == 2) profile = GBA_DCI; + else if (color_mode == 3) profile = GBA_Rec2020; + + gl_Position = position; + texCoord = (position.st + vec2(1.0, 1.0)) * vec2(0.5, 0.5); +} diff --git a/src/arm/isa-thumb.c b/src/arm/isa-thumb.c index 0bc8fcb12..30fcfde9d 100644 --- a/src/arm/isa-thumb.c +++ b/src/arm/isa-thumb.c @@ -88,7 +88,7 @@ DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(LSR1, } THUMB_NEUTRAL_S( , , cpu->gprs[rd]);) -DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(ASR1, +DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(ASR1, if (!immediate) { cpu->cpsr.c = ARM_SIGN(cpu->gprs[rm]); if (cpu->cpsr.c) { diff --git a/src/core/cheats.c b/src/core/cheats.c index 53f71e907..2fc97c3ac 100644 --- a/src/core/cheats.c +++ b/src/core/cheats.c @@ -37,7 +37,7 @@ static uint32_t _patchMakeKey(struct mCheatPatch* patch) { patchKey >>= 2; break; default: - break; + break; } // TODO: More than one segment if (patch->segment > 0) { @@ -627,6 +627,9 @@ void mCheatAutosave(struct mCheatDevice* device) { if (!device->autosave) { return; } + if (!device->p->dirs.cheats) { + return; + } struct VFile* vf = mDirectorySetOpenSuffix(&device->p->dirs, device->p->dirs.cheats, ".cheats", O_WRONLY | O_CREAT | O_TRUNC); if (!vf) { return; diff --git a/src/core/core.c b/src/core/core.c index 20f9fdefc..ba7ef914c 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -239,20 +239,35 @@ bool mCoreAutoloadPatch(struct mCore* core) { if (!core->dirs.patch) { return false; } - return core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ups", O_RDONLY)) || - core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ips", O_RDONLY)) || - core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".bps", O_RDONLY)); + struct VFile* vf = NULL; + if (!vf) { + vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".bps", O_RDONLY); + } + if (!vf) { + vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ups", O_RDONLY); + } + if (!vf) { + vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ips", O_RDONLY); + } + if (!vf) { + return false; + } + bool result = core->loadPatch(core, vf); + vf->close(vf); + return result; } bool mCoreAutoloadCheats(struct mCore* core) { - bool success = true; + bool success = !!core->dirs.cheats; int cheatAuto; - if (!mCoreConfigGetIntValue(&core->config, "cheatAutoload", &cheatAuto) || cheatAuto) { + if (success && (!mCoreConfigGetIntValue(&core->config, "cheatAutoload", &cheatAuto) || cheatAuto)) { struct VFile* vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.cheats, ".cheats", O_RDONLY); if (vf) { struct mCheatDevice* device = core->cheatDevice(core); success = mCheatParseFile(device, vf); vf->close(vf); + } else { + success = false; } } if (!mCoreConfigGetIntValue(&core->config, "cheatAutosave", &cheatAuto) || cheatAuto) { diff --git a/src/core/directories.c b/src/core/directories.c index c4551b6c7..e0748f829 100644 --- a/src/core/directories.c +++ b/src/core/directories.c @@ -110,6 +110,9 @@ struct VFile* mDirectorySetOpenPath(struct mDirectorySet* dirs, const char* path } struct VFile* mDirectorySetOpenSuffix(struct mDirectorySet* dirs, struct VDir* dir, const char* suffix, int mode) { + if (!dir) { + return NULL; + } char name[PATH_MAX + 1] = ""; snprintf(name, sizeof(name) - 1, "%s%s", dirs->baseName, suffix); return dir->openFile(dir, name, mode); diff --git a/src/core/interface.c b/src/core/interface.c index 10d3433ab..5771d80e1 100644 --- a/src/core/interface.c +++ b/src/core/interface.c @@ -110,14 +110,14 @@ void mRTCGenericSourceInit(struct mRTCGenericSource* rtc, struct mCore* core) { rtc->d.deserialize = _rtcGenericDeserialize; } -static void mRumbleIntegratorReset(struct mRumble* rumble, bool enable) { +static void _mRumbleIntegratorReset(struct mRumble* rumble, bool enable) { struct mRumbleIntegrator* integrator = (struct mRumbleIntegrator*) rumble; integrator->state = enable; integrator->timeOn = 0; integrator->totalTime = 0; } -static void mRumbleIntegratorSetRumble(struct mRumble* rumble, bool enable, uint32_t sinceLast) { +static void _mRumbleIntegratorSetRumble(struct mRumble* rumble, bool enable, uint32_t sinceLast) { struct mRumbleIntegrator* integrator = (struct mRumbleIntegrator*) rumble; if (integrator->state) { @@ -127,7 +127,7 @@ static void mRumbleIntegratorSetRumble(struct mRumble* rumble, bool enable, uint integrator->state = enable; } -static void mRumbleIntegratorIntegrate(struct mRumble* rumble, uint32_t period) { +static void _mRumbleIntegratorIntegrate(struct mRumble* rumble, uint32_t period) { if (!period) { return; } @@ -144,7 +144,11 @@ static void mRumbleIntegratorIntegrate(struct mRumble* rumble, uint32_t period) void mRumbleIntegratorInit(struct mRumbleIntegrator* integrator) { memset(integrator, 0, sizeof(*integrator)); - integrator->d.reset = mRumbleIntegratorReset; - integrator->d.setRumble = mRumbleIntegratorSetRumble; - integrator->d.integrate = mRumbleIntegratorIntegrate; + integrator->d.reset = _mRumbleIntegratorReset; + integrator->d.setRumble = _mRumbleIntegratorSetRumble; + integrator->d.integrate = _mRumbleIntegratorIntegrate; +} + +void mRumbleIntegratorReset(struct mRumbleIntegrator* integrator) { + _mRumbleIntegratorReset(&integrator->d, false); } diff --git a/src/core/lockstep.c b/src/core/lockstep.c index 587cff2b5..c058a80fc 100644 --- a/src/core/lockstep.c +++ b/src/core/lockstep.c @@ -1,10 +1,14 @@ -/* Copyright (c) 2013-2016 Jeffrey Pfau +/* Copyright (c) 2013-2024 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 +#ifndef DISABLE_THREADING +#include +#endif + void mLockstepInit(struct mLockstep* lockstep) { lockstep->attached = 0; lockstep->transferActive = 0; @@ -19,4 +23,21 @@ void mLockstepDeinit(struct mLockstep* lockstep) { UNUSED(lockstep); } -// TODO: Migrate nodes +#ifndef DISABLE_THREADING +static void mLockstepThreadUserSleep(struct mLockstepUser* user) { + struct mLockstepThreadUser* lockstep = (struct mLockstepThreadUser*) user; + mCoreThreadWaitFromThread(lockstep->thread); +} + +static void mLockstepThreadUserWake(struct mLockstepUser* user) { + struct mLockstepThreadUser* lockstep = (struct mLockstepThreadUser*) user; + mCoreThreadStopWaiting(lockstep->thread); +} + +void mLockstepThreadUserInit(struct mLockstepThreadUser* lockstep, struct mCoreThread* thread) { + memset(lockstep, 0, sizeof(*lockstep)); + lockstep->d.sleep = mLockstepThreadUserSleep; + lockstep->d.wake = mLockstepThreadUserWake; + lockstep->thread = thread; +} +#endif diff --git a/src/core/scripting.c b/src/core/scripting.c index a00d5cbdc..9e46b619c 100644 --- a/src/core/scripting.c +++ b/src/core/scripting.c @@ -520,7 +520,7 @@ mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mCore, busWrite16, 2, U32, address, U16, va mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mCore, busWrite32, 2, U32, address, U32, value); // Register functions -mSCRIPT_DECLARE_STRUCT_METHOD(mCore, WSTR, readRegister, _mScriptCoreReadRegister, 1, CHARP, regName); +mSCRIPT_DECLARE_STRUCT_METHOD(mCore, WRAPPER, readRegister, _mScriptCoreReadRegister, 1, CHARP, regName); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mCore, writeRegister, _mScriptCoreWriteRegister, 2, CHARP, regName, S32, value); // Savestate functions @@ -1205,11 +1205,11 @@ static bool _callRotationCb(struct mScriptCoreAdapter* adapter, const char* cbNa struct mScriptValue* context = mScriptTableLookup(adapter->rotationCbTable, &mSCRIPT_MAKE_CHARP("context")); mScriptFrameInit(&frame); if (context) { - mScriptValueWrap(context, mScriptListAppend(&frame.arguments)); + mScriptValueWrap(context, mScriptListAppend(&frame.stack)); } bool ok = mScriptContextInvoke(adapter->context, cb, &frame); - if (ok && out && mScriptListSize(&frame.returnValues) == 1) { - if (!mScriptCast(mSCRIPT_TYPE_MS_F32, mScriptListGetPointer(&frame.returnValues, 0), out)) { + if (ok && out && mScriptListSize(&frame.stack) == 1) { + if (!mScriptCast(mSCRIPT_TYPE_MS_F32, mScriptListGetPointer(&frame.stack, 0), out)) { ok = false; } } @@ -1278,8 +1278,8 @@ static uint8_t _readLuminance(struct GBALuminanceSource* luminance) { mScriptFrameInit(&frame); bool ok = mScriptContextInvoke(adapter->context, adapter->luminanceCb, &frame); struct mScriptValue out = {0}; - if (ok && mScriptListSize(&frame.returnValues) == 1) { - if (!mScriptCast(mSCRIPT_TYPE_MS_U8, mScriptListGetPointer(&frame.returnValues, 0), &out)) { + if (ok && mScriptListSize(&frame.stack) == 1) { + if (!mScriptCast(mSCRIPT_TYPE_MS_U8, mScriptListGetPointer(&frame.stack, 0), &out)) { ok = false; } } diff --git a/src/core/thread.c b/src/core/thread.c index 2462407cc..2b16a03c7 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -656,7 +656,7 @@ void mCoreThreadContinue(struct mCoreThread* threadContext) { if (threadContext->impl->requested) { threadContext->impl->state = mTHREAD_REQUEST; } else { - threadContext->impl->state = mTHREAD_RUNNING; + threadContext->impl->state = mTHREAD_RUNNING; } ConditionWake(&threadContext->impl->stateOnThreadCond); } diff --git a/src/feature/ffmpeg/ffmpeg-decoder.c b/src/feature/ffmpeg/ffmpeg-decoder.c index 09539ca61..189fb312f 100644 --- a/src/feature/ffmpeg/ffmpeg-decoder.c +++ b/src/feature/ffmpeg/ffmpeg-decoder.c @@ -71,7 +71,7 @@ bool FFmpegDecoderOpen(struct FFmpegDecoder* decoder, const char* infile) { codec = avcodec_find_decoder(context->codec_id); if (!codec) { FFmpegDecoderClose(decoder); - return false; + return false; } if (avcodec_open2(context, codec, NULL) < 0) { FFmpegDecoderClose(decoder); diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index 5ee54a5e8..6202e0fcd 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -893,7 +893,7 @@ void FFmpegEncoderSetInputFrameRate(struct FFmpegEncoder* encoder, int numerator void FFmpegEncoderSetInputSampleRate(struct FFmpegEncoder* encoder, int sampleRate) { encoder->isampleRate = sampleRate; - if (encoder->resampleContext) { + if (encoder->resampleContext) { av_freep(&encoder->audioBuffer); #ifdef USE_LIBAVRESAMPLE avresample_close(encoder->resampleContext); diff --git a/src/feature/gui/gui-config.c b/src/feature/gui/gui-config.c index e110ef768..6214abc15 100644 --- a/src/feature/gui/gui-config.c +++ b/src/feature/gui/gui-config.c @@ -295,7 +295,7 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t test.v.s = mCoreConfigGetValue(&runner->config, item->data.v.s); if (test.v.s && strcmp(test.v.s, v->v.s) == 0) { item->state = j; - break; + break; } break; case GUI_VARIANT_POINTER: diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 035522f02..76e15cdad 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -782,6 +782,9 @@ void mGUILoadInputMaps(struct mGUIRunner* runner) { size_t i; for (i = 0; runner->keySources[i].id; ++i) { mInputMapLoad(&runner->params.keyMap, runner->keySources[i].id, mCoreConfigGetInput(&runner->config)); + if (runner->core) { + mInputMapLoad(&runner->core->inputMap, runner->keySources[i].id, mCoreConfigGetInput(&runner->config)); + } } } diff --git a/src/feature/updater.c b/src/feature/updater.c index e50ed0fa2..ea9cea3c1 100644 --- a/src/feature/updater.c +++ b/src/feature/updater.c @@ -46,7 +46,7 @@ static void _updateList(const char* key, const char* value, void* user) { if (strncmp("medusa.", key, 7) == 0) { dotLoc = strchr(&key[7], '.'); } else { - dotLoc = strchr(key, '.'); + dotLoc = strchr(key, '.'); } if (!dotLoc) { return; diff --git a/src/feature/video-logger.c b/src/feature/video-logger.c index 4044a4928..ac5ef1777 100644 --- a/src/feature/video-logger.c +++ b/src/feature/video-logger.c @@ -339,17 +339,17 @@ bool mVideoLoggerRendererRunInjected(struct mVideoLogger* logger) { channel->injecting = true; bool res = mVideoLoggerRendererRun(logger, false); channel->injecting = false; - return res; + return res; } void mVideoLoggerInjectionPoint(struct mVideoLogger* logger, enum mVideoLoggerInjectionPoint injectionPoint) { struct mVideoLogChannel* channel = logger->dataContext; - channel->injectionPoint = injectionPoint; + channel->injectionPoint = injectionPoint; } void mVideoLoggerIgnoreAfterInjection(struct mVideoLogger* logger, uint32_t mask) { struct mVideoLogChannel* channel = logger->dataContext; - channel->ignorePackets = mask; + channel->ignorePackets = mask; } static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) { diff --git a/src/gb/core.c b/src/gb/core.c index 2bfea28ef..b97e1ee25 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -152,7 +153,7 @@ static bool _GBCoreInit(struct mCore* core) { #ifdef ENABLE_VFS mDirectorySetInit(&core->dirs); #endif - + return true; } @@ -529,6 +530,15 @@ static void _GBCoreChecksum(const struct mCore* core, void* data, enum mCoreChec case mCHECKSUM_CRC32: memcpy(data, &gb->romCrc32, sizeof(gb->romCrc32)); break; + case mCHECKSUM_MD5: + if (gb->romVf) { + md5File(gb->romVf, data); + } else if (gb->memory.rom && gb->isPristine) { + md5Buffer(gb->memory.rom, gb->pristineRomSize, data); + } else { + md5Buffer("", 0, data); + } + break; } return; } diff --git a/src/gb/extra/proxy.c b/src/gb/extra/proxy.c index ba36c0090..c0c386f4c 100644 --- a/src/gb/extra/proxy.c +++ b/src/gb/extra/proxy.c @@ -267,7 +267,7 @@ void GBVideoProxyRendererDrawRange(struct GBVideoRenderer* renderer, int startX, _copyExtraState(proxyRenderer); proxyRenderer->backend->drawRange(proxyRenderer->backend, startX, endX, y); } - mVideoLoggerRendererDrawRange(proxyRenderer->logger, startX, endX, y); + mVideoLoggerRendererDrawRange(proxyRenderer->logger, startX, endX, y); } void GBVideoProxyRendererFinishScanline(struct GBVideoRenderer* renderer, int y) { diff --git a/src/gb/gb.c b/src/gb/gb.c index 67dea55b8..875cbf86e 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -152,7 +152,7 @@ bool GBLoadGBX(struct GBXMetadata* metadata, struct VFile* vf) { if (memcmp(footer, "MBC1", 4) == 0) { metadata->mapperVars.u8[0] = 5; } else if (memcmp(footer, "MB1M", 4) == 0) { - metadata->mapperVars.u8[0] = 4; + metadata->mapperVars.u8[0] = 4; } return true; } @@ -483,12 +483,10 @@ void GBApplyPatch(struct GB* gb, struct Patch* patch) { mappedMemoryFree(newRom, GB_SIZE_CART_MAX); return; } - if (gb->romVf) { + if (gb->romVf && gb->isPristine) { #ifndef FIXED_ROM_BUFFER gb->romVf->unmap(gb->romVf, gb->memory.rom, gb->pristineRomSize); #endif - gb->romVf->close(gb->romVf); - gb->romVf = NULL; } gb->isPristine = false; if (gb->memory.romBase == gb->memory.rom) { @@ -894,7 +892,7 @@ int GBValidModels(const uint8_t* bank0) { } else if (cart->cgb == 0xC0) { models = GB_MODEL_CGB; } else { - models = GB_MODEL_MGB; + models = GB_MODEL_MGB; } if (cart->sgb == 0x03 && cart->oldLicensee == 0x33) { models |= GB_MODEL_SGB; diff --git a/src/gb/mbc.c b/src/gb/mbc.c index c9ebed546..397b50088 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -163,7 +163,7 @@ static bool _isMulticart(const uint8_t* mem) { success = GBIsROM(vf); vf->close(vf); } - + return success; } diff --git a/src/gb/mbc/tama5.c b/src/gb/mbc/tama5.c index 505a35ed8..c4f0a1517 100644 --- a/src/gb/mbc/tama5.c +++ b/src/gb/mbc/tama5.c @@ -131,7 +131,7 @@ static void _latchTAMA6Rtc(struct mRTCSource* rtc, struct GBTAMA5State* tama5, t timerRegs[GBTAMA6_RTC_PA0_HOUR_10] = (diff % 24) / 10; } else { timerRegs[GBTAMA6_RTC_PA0_HOUR_1] = (diff % 12) % 10; - timerRegs[GBTAMA6_RTC_PA0_HOUR_10] = (diff % 12) / 10 + (diff / 12) * 2; + timerRegs[GBTAMA6_RTC_PA0_HOUR_10] = (diff % 12) / 10 + (diff / 12) * 2; } t /= 24; t += diff / 24; diff --git a/src/gb/mbc/unlicensed.c b/src/gb/mbc/unlicensed.c index 950ddf074..844c0d0cd 100644 --- a/src/gb/mbc/unlicensed.c +++ b/src/gb/mbc/unlicensed.c @@ -349,7 +349,7 @@ void _GBHitek(struct GB* gb, uint16_t address, uint8_t value) { break; case 0x300: // See hhugboy src/memory/mbc/MbcUnlHitek.cpp for commentary on this return - return; + return; } _GBMBC5(gb, address, value); } @@ -396,10 +396,10 @@ uint8_t _GBGGB81Read(struct GBMemory* memory, uint16_t address) { } void _GBLiCheng(struct GB* gb, uint16_t address, uint8_t value) { - if (address > 0x2100 && address < 0x3000) { - return; - } - _GBMBC5(gb, address, value); + if (address > 0x2100 && address < 0x3000) { + return; + } + _GBMBC5(gb, address, value); } void _GBSachen(struct GB* gb, uint16_t address, uint8_t value) { diff --git a/src/gb/memory.c b/src/gb/memory.c index e41050784..dccd91e08 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -1017,8 +1017,6 @@ void _pristineCow(struct GB* gb) { } if (gb->romVf) { gb->romVf->unmap(gb->romVf, gb->memory.rom, gb->memory.romSize); - gb->romVf->close(gb->romVf); - gb->romVf = NULL; } gb->memory.rom = newRom; GBMBCSwitchBank(gb, gb->memory.currentBank); diff --git a/src/gb/video.c b/src/gb/video.c index 38be72a5a..d52e43dee 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -755,7 +755,7 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) { video->ly = 0; video->p->memory.io[GB_REG_LY] = 0; video->renderer->writePalette(video->renderer, 0, video->dmgPalette[0]); - + mTimingDeschedule(&video->p->timing, &video->modeEvent); mTimingDeschedule(&video->p->timing, &video->frameEvent); mTimingSchedule(&video->p->timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH << 1); diff --git a/src/gba/CMakeLists.txt b/src/gba/CMakeLists.txt index 933ba3fac..71ab7bbb1 100644 --- a/src/gba/CMakeLists.txt +++ b/src/gba/CMakeLists.txt @@ -6,6 +6,7 @@ set(SOURCE_FILES cart/ereader.c cart/gpio.c cart/matrix.c + cart/unlicensed.c cart/vfame.c cheats.c cheats/codebreaker.c @@ -31,7 +32,6 @@ set(SOURCE_FILES sharkport.c sio.c sio/gbp.c - sio/joybus.c timer.c video.c) diff --git a/src/gba/audio.c b/src/gba/audio.c index a6ebafbae..ac394e995 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -309,6 +309,7 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) { if (GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_CUSTOM) { dma->when = mTimingCurrentTime(&audio->p->timing) - cycles; dma->nextCount = 4; + GBADMARecalculateCycles(audio->p); GBADMASchedule(audio->p, channel->dmaSource, dma); } } diff --git a/src/gba/bios.c b/src/gba/bios.c index caca48a0b..1ff5004d3 100644 --- a/src/gba/bios.c +++ b/src/gba/bios.c @@ -35,30 +35,6 @@ static int _mulWait(int32_t r) { } } -static void _SoftReset(struct GBA* gba) { - struct ARMCore* cpu = gba->cpu; - ARMSetPrivilegeMode(cpu, MODE_IRQ); - cpu->spsr.packed = 0; - cpu->gprs[ARM_LR] = 0; - cpu->gprs[ARM_SP] = GBA_SP_BASE_IRQ; - ARMSetPrivilegeMode(cpu, MODE_SUPERVISOR); - cpu->spsr.packed = 0; - cpu->gprs[ARM_LR] = 0; - cpu->gprs[ARM_SP] = GBA_SP_BASE_SUPERVISOR; - ARMSetPrivilegeMode(cpu, MODE_SYSTEM); - cpu->gprs[ARM_LR] = 0; - cpu->gprs[ARM_SP] = GBA_SP_BASE_SYSTEM; - int8_t flag = ((int8_t*) gba->memory.iwram)[0x7FFA]; - memset(((int8_t*) gba->memory.iwram) + GBA_SIZE_IWRAM - 0x200, 0, 0x200); - if (flag) { - cpu->gprs[ARM_PC] = GBA_BASE_EWRAM; - } else { - cpu->gprs[ARM_PC] = GBA_BASE_ROM0; - } - _ARMSetMode(cpu, MODE_ARM); - ARMWritePC(cpu); -} - static void _RegisterRamReset(struct GBA* gba) { uint32_t registers = gba->cpu->gprs[0]; struct ARMCore* cpu = gba->cpu; @@ -445,7 +421,7 @@ void GBASwi16(struct ARMCore* cpu, int immediate) { bool useStall = false; switch (immediate) { case GBA_SWI_SOFT_RESET: - _SoftReset(gba); + ARMRaiseSWI(cpu); break; case GBA_SWI_REGISTER_RAM_RESET: _RegisterRamReset(gba); diff --git a/src/gba/cart/gpio.c b/src/gba/cart/gpio.c index fde4e3714..a44bab294 100644 --- a/src/gba/cart/gpio.c +++ b/src/gba/cart/gpio.c @@ -47,6 +47,20 @@ void GBAHardwareInit(struct GBACartridgeHardware* hw, uint16_t* base) { GBAHardwareClear(hw); } +void GBAHardwareReset(struct GBACartridgeHardware* hw) { + hw->readWrite = GPIO_WRITE_ONLY; + hw->pinState = 0; + hw->direction = 0; + hw->lightCounter = 0; + hw->lightEdge = false; + hw->lightSample = 0xFF; + hw->gyroSample = 0; + hw->gyroEdge = 0; + hw->tiltX = 0xFFF; + hw->tiltY = 0xFFF; + hw->tiltState = 0; +} + void GBAHardwareClear(struct GBACartridgeHardware* hw) { hw->devices = HW_NONE | (hw->devices & HW_GB_PLAYER_DETECTION); hw->readWrite = GPIO_WRITE_ONLY; @@ -480,16 +494,16 @@ void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASeria STORE_16(hw->tiltY, 0, &state->hw.tiltSampleY); state->hw.lightSample = hw->lightSample; flags1 = GBASerializedHWFlags1SetLightEdge(flags1, hw->lightEdge); + flags1 = GBASerializedHWFlags1SetLightCounter(flags1, hw->lightCounter); STORE_16(flags1, 0, &state->hw.flags1); GBASerializedHWFlags2 flags2 = 0; flags2 = GBASerializedHWFlags2SetTiltState(flags2, hw->tiltState); - flags2 = GBASerializedHWFlags1SetLightCounter(flags2, hw->lightCounter); - // GBP stuff is only here for legacy reasons + // GBP/SIO stuff is only here for legacy reasons flags2 = GBASerializedHWFlags2SetGbpInputsPosted(flags2, hw->p->sio.gbp.inputsPosted); flags2 = GBASerializedHWFlags2SetGbpTxPosition(flags2, hw->p->sio.gbp.txPosition); - STORE_32(hw->p->sio.gbp.event.when - mTimingCurrentTime(&hw->p->timing), 0, &state->hw.gbpNextEvent); + STORE_32(hw->p->sio.completeEvent.when - mTimingCurrentTime(&hw->p->timing), 0, &state->hw.sioNextEvent); state->hw.flags2 = flags2; } @@ -502,6 +516,19 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer LOAD_16(hw->direction, 0, &state->hw.pinDirection); hw->devices = state->hw.devices; + if ((hw->devices & HW_GPIO) && hw->gpioBase) { + // TODO: This needs to update the pristine state somehow + if (hw->readWrite) { + STORE_16(hw->pinState, 0, hw->gpioBase); + STORE_16(hw->direction, 2, hw->gpioBase); + STORE_16(hw->readWrite, 4, hw->gpioBase); + } else { + hw->gpioBase[0] = 0; + hw->gpioBase[1] = 0; + hw->gpioBase[2] = 0; + } + } + LOAD_32(hw->rtc.bytesRemaining, 0, &state->hw.rtcBytesRemaining); LOAD_32(hw->rtc.transferStep, 0, &state->hw.rtcTransferStep); LOAD_32(hw->rtc.bitsRead, 0, &state->hw.rtcBitsRead); @@ -520,16 +547,16 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer hw->lightSample = state->hw.lightSample; hw->lightEdge = GBASerializedHWFlags1GetLightEdge(flags1); - // GBP stuff is only here for legacy reasons + // GBP/SIO stuff is only here for legacy reasons hw->p->sio.gbp.inputsPosted = GBASerializedHWFlags2GetGbpInputsPosted(state->hw.flags2); hw->p->sio.gbp.txPosition = GBASerializedHWFlags2GetGbpTxPosition(state->hw.flags2); uint32_t when; - LOAD_32(when, 0, &state->hw.gbpNextEvent); + LOAD_32(when, 0, &state->hw.sioNextEvent); if (hw->devices & HW_GB_PLAYER) { - GBASIOSetDriver(&hw->p->sio, &hw->p->sio.gbp.d, GBA_SIO_NORMAL_32); - if (hw->p->memory.io[GBA_REG(SIOCNT)] & 0x0080) { - mTimingSchedule(&hw->p->timing, &hw->p->sio.gbp.event, when); - } + GBASIOSetDriver(&hw->p->sio, &hw->p->sio.gbp.d); + } + if ((hw->p->memory.io[GBA_REG(SIOCNT)] & 0x0080) && when < 0x20000) { + mTimingSchedule(&hw->p->timing, &hw->p->sio.completeEvent, when); } } diff --git a/src/gba/cart/matrix.c b/src/gba/cart/matrix.c index c594d66b9..9ccbe8832 100644 --- a/src/gba/cart/matrix.c +++ b/src/gba/cart/matrix.c @@ -109,7 +109,7 @@ void GBAMatrixSerialize(const struct GBA* gba, struct GBASerializedState* state) int i; for (i = 0; i < 16; ++i) { - STORE_32(gba->memory.matrix.mappings[i], i << 2, state->matrixMappings); + STORE_32(gba->memory.matrix.mappings[i], i << 2, state->matrix2.mappings); } } @@ -121,7 +121,7 @@ void GBAMatrixDeserialize(struct GBA* gba, const struct GBASerializedState* stat int i; for (i = 0; i < 16; ++i) { - LOAD_32(gba->memory.matrix.mappings[i], i << 2, state->matrixMappings); + LOAD_32(gba->memory.matrix.mappings[i], i << 2, state->matrix2.mappings); gba->memory.matrix.paddr = gba->memory.matrix.mappings[i]; gba->memory.matrix.vaddr = i << 9; _remapMatrix(gba); diff --git a/src/gba/cart/unlicensed.c b/src/gba/cart/unlicensed.c new file mode 100644 index 000000000..a0f18fc5e --- /dev/null +++ b/src/gba/cart/unlicensed.c @@ -0,0 +1,255 @@ +/* Copyright (c) 2013-2024 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 + +#define MULTI_SETTLE 300 +#define MULTI_BLOCK 0x80000 +#define MULTI_BANK 0x2000000 + +enum GBMulticartCfgOffset { + GBA_MULTICART_CFG_BANK = 0x2, + GBA_MULTICART_CFG_OFFSET = 0x3, + GBA_MULTICART_CFG_SIZE = 0x4, + GBA_MULTICART_CFG_SRAM = 0x5, + GBA_MULTICART_CFG_UNK = 0x6, +}; + +static_assert(sizeof(((struct GBASerializedState*)(NULL))->vfame.writeSequence) == + sizeof(((struct GBAVFameCart*)(NULL))->writeSequence), "GBA savestate vfame writeSequence size mismatch"); + +static void _multicartSettle(struct mTiming* timing, void* context, uint32_t cyclesLate); + +void GBAUnlCartInit(struct GBA* gba) { + memset(&gba->memory.unl, 0, sizeof(gba->memory.unl)); +} + +void GBAUnlCartDetect(struct GBA* gba) { + if (!gba->memory.rom) { + return; + } + + struct GBACartridge* cart = (struct GBACartridge*) gba->memory.rom; + if (GBAVFameDetect(&gba->memory.unl.vfame, gba->memory.rom, gba->memory.romSize, gba->romCrc32)) { + gba->memory.unl.type = GBA_UNL_CART_VFAME; + return; + } + + if (memcmp(&cart->id, "AXVJ01", 6) == 0 || memcmp(&cart->id, "BI3P52", 6) == 0) { + if (gba->romVf && gba->romVf->size(gba->romVf) >= 0x04000000) { + // Bootleg multicart + // TODO: Identify a bit more precisely + gba->isPristine = false; + GBASavedataInitSRAM(&gba->memory.savedata); + gba->memory.unl.type = GBA_UNL_CART_MULTICART; + + gba->romVf->unmap(gba->romVf, gba->memory.rom, gba->memory.romSize); + gba->memory.unl.multi.fullSize = gba->romVf->size(gba->romVf); + gba->memory.unl.multi.rom = gba->romVf->map(gba->romVf, gba->memory.unl.multi.fullSize, MAP_READ); + gba->memory.rom = gba->memory.unl.multi.rom; + gba->memory.hw.gpioBase = NULL; + + gba->memory.unl.multi.settle.context = gba; + gba->memory.unl.multi.settle.callback = _multicartSettle; + gba->memory.unl.multi.settle.name = "GBA Unlicensed Multicart Settle"; + gba->memory.unl.multi.settle.priority = 0x71; + } + } +} + +void GBAUnlCartReset(struct GBA* gba) { + if (gba->memory.unl.type == GBA_UNL_CART_MULTICART) { + gba->memory.unl.multi.bank = 0; + gba->memory.unl.multi.offset = 0; + gba->memory.unl.multi.size = 0; + gba->memory.unl.multi.locked = false; + gba->memory.rom = gba->memory.unl.multi.rom; + gba->memory.romSize = GBA_SIZE_ROM0; + } +} + +void GBAUnlCartUnload(struct GBA* gba) { + if (gba->memory.unl.type == GBA_UNL_CART_MULTICART && gba->romVf) { + gba->romVf->unmap(gba->romVf, gba->memory.unl.multi.rom, gba->memory.unl.multi.size); + gba->memory.unl.multi.rom = NULL; + gba->memory.rom = NULL; + } +} + +void GBAUnlCartWriteSRAM(struct GBA* gba, uint32_t address, uint8_t value) { + struct GBAUnlCart* unl = &gba->memory.unl; + + switch (unl->type) { + case GBA_UNL_CART_VFAME: + GBAVFameSramWrite(&unl->vfame, address, value, gba->memory.savedata.data); + return; + case GBA_UNL_CART_MULTICART: + mLOG(GBA_MEM, DEBUG, "Multicart writing SRAM %06X:%02X", address, value); + switch (address) { + case GBA_MULTICART_CFG_BANK: + if (!unl->multi.locked) { + unl->multi.bank = value >> 4; + mTimingDeschedule(&gba->timing, &unl->multi.settle); + mTimingSchedule(&gba->timing, &unl->multi.settle, MULTI_SETTLE); + } + break; + case GBA_MULTICART_CFG_OFFSET: + if (!unl->multi.locked) { + unl->multi.offset = value; + mTimingDeschedule(&gba->timing, &unl->multi.settle); + mTimingSchedule(&gba->timing, &unl->multi.settle, MULTI_SETTLE); + if (unl->multi.offset & 0x80) { + unl->multi.locked = true; + } + } + break; + case GBA_MULTICART_CFG_SIZE: + unl->multi.size = 0x40 - (value & 0x3F); + if (!unl->multi.locked) { + mTimingDeschedule(&gba->timing, &unl->multi.settle); + mTimingSchedule(&gba->timing, &unl->multi.settle, MULTI_SETTLE); + } + break; + case GBA_MULTICART_CFG_SRAM: + if (value == 0 && unl->multi.sramActive) { + unl->multi.sramActive = false; + } else if (value == 1 && !unl->multi.sramActive) { + unl->multi.sramActive = true; + } + break; + case GBA_MULTICART_CFG_UNK: + // TODO: What does this do? + unl->multi.unk = value; + break; + default: + break; + } + break; + case GBA_UNL_CART_NONE: + break; + } + + gba->memory.savedata.data[address & (GBA_SIZE_SRAM - 1)] = value; +} + +void GBAUnlCartWriteROM(struct GBA* gba, uint32_t address, uint16_t value) { + struct GBAUnlCart* unl = &gba->memory.unl; + + switch (unl->type) { + case GBA_UNL_CART_VFAME: + case GBA_UNL_CART_NONE: + break; + case GBA_UNL_CART_MULTICART: + mLOG(GBA_MEM, STUB, "Unimplemented writing to ROM %07X:%04X", address, value); + break; + } +} + +static void _multicartSettle(struct mTiming* timing, void* context, uint32_t cyclesLate) { + UNUSED(timing); + UNUSED(cyclesLate); + struct GBA* gba = context; + mLOG(GBA_MEM, INFO, "Switching to bank %i offset %i, size %i", + gba->memory.unl.multi.bank, gba->memory.unl.multi.offset & 0x3F, gba->memory.unl.multi.size); + size_t offset = gba->memory.unl.multi.bank * (MULTI_BANK >> 2) + (gba->memory.unl.multi.offset & 0x3F) * (MULTI_BLOCK >> 2); + size_t size = gba->memory.unl.multi.size * MULTI_BLOCK; + if (offset * 4 >= gba->memory.unl.multi.fullSize || offset * 4 + size > gba->memory.unl.multi.fullSize) { + mLOG(GBA_MEM, GAME_ERROR, "Bank switch was out of bounds, %07" PRIz "X + %" PRIz "X > %07" PRIz "X", + offset * 4, size, gba->memory.unl.multi.fullSize); + return; + } + gba->memory.rom = gba->memory.unl.multi.rom + offset; + gba->memory.romSize = size; +} + +void GBAUnlCartSerialize(const struct GBA* gba, struct GBASerializedState* state) { + GBASerializedUnlCartFlags flags = 0; + GBASerializedMulticartFlags multiFlags = 0; + const struct GBAUnlCart* unl = &gba->memory.unl; + switch (unl->type) { + case GBA_UNL_CART_NONE: + return; + case GBA_UNL_CART_VFAME: + flags = GBASerializedUnlCartFlagsSetType(flags, GBA_UNL_CART_VFAME); + flags = GBASerializedUnlCartFlagsSetSubtype(flags, unl->vfame.cartType); + STORE_16(unl->vfame.sramMode, 0, &state->vfame.sramMode); + STORE_16(unl->vfame.romMode, 0, &state->vfame.romMode); + memcpy(state->vfame.writeSequence, unl->vfame.writeSequence, sizeof(state->vfame.writeSequence)); + state->vfame.acceptingModeChange = unl->vfame.acceptingModeChange; + break; + case GBA_UNL_CART_MULTICART: + flags = GBASerializedUnlCartFlagsSetType(0, GBA_UNL_CART_MULTICART); + state->multicart.bank = unl->multi.bank; + state->multicart.offset = unl->multi.offset; + state->multicart.size = unl->multi.size; + state->multicart.sramActive = unl->multi.sramActive; + state->multicart.unk = unl->multi.unk; + state->multicart.currentSize = gba->memory.romSize / MULTI_BLOCK; + multiFlags = GBASerializedMulticartFlagsSetLocked(flags, unl->multi.locked); + STORE_16((gba->memory.rom - unl->multi.rom) / 0x20000, 0, &state->multicart.currentOffset); + STORE_32(unl->multi.settle.when, 0, &state->multicart.settleNextEvent); + if (mTimingIsScheduled(&gba->timing, &unl->multi.settle)) { + multiFlags = GBASerializedMulticartFlagsFillDustSettling(multiFlags); + } + STORE_32(multiFlags, 0, &state->multicart.flags); + break; + } + STORE_32(flags, 0, &state->hw.unlCartFlags); +} + +void GBAUnlCartDeserialize(struct GBA* gba, const struct GBASerializedState* state) { + GBASerializedUnlCartFlags flags; + struct GBAUnlCart* unl = &gba->memory.unl; + + LOAD_32(flags, 0, &state->hw.unlCartFlags); + enum GBAUnlCartType type = GBASerializedUnlCartFlagsGetType(flags); + if (type != unl->type) { + mLOG(GBA_STATE, WARN, "Save state expects different bootleg type; not restoring bootleg state"); + return; + } + + uint32_t when; + uint32_t offset; + size_t size; + GBASerializedMulticartFlags multiFlags; + + switch (type) { + case GBA_UNL_CART_NONE: + return; + case GBA_UNL_CART_VFAME: + LOAD_16(unl->vfame.sramMode, 0, &state->vfame.sramMode); + LOAD_16(unl->vfame.romMode, 0, &state->vfame.romMode); + memcpy(unl->vfame.writeSequence, state->vfame.writeSequence, sizeof(state->vfame.writeSequence)); + unl->vfame.acceptingModeChange = state->vfame.acceptingModeChange; + return; + case GBA_UNL_CART_MULTICART: + unl->multi.bank = state->multicart.bank; + unl->multi.offset = state->multicart.offset; + unl->multi.size = state->multicart.size; + unl->multi.sramActive = state->multicart.sramActive; + unl->multi.unk = state->multicart.unk; + size = state->multicart.currentSize * MULTI_BLOCK; + LOAD_16(offset, 0, &state->multicart.currentOffset); + offset *= 0x20000; + if (offset * 4 >= gba->memory.unl.multi.fullSize || offset * 4 + size > gba->memory.unl.multi.fullSize) { + mLOG(GBA_STATE, WARN, "Multicart save state has corrupted ROM offset"); + } else { + gba->memory.romSize = size; + gba->memory.rom = unl->multi.rom + offset; + } + LOAD_32(multiFlags, 0, &state->multicart.flags); + unl->multi.locked = GBASerializedMulticartFlagsGetLocked(multiFlags); + if (GBASerializedMulticartFlagsIsDustSettling(multiFlags)) { + LOAD_32(when, 0, &state->multicart.settleNextEvent); + mTimingSchedule(&gba->timing, &unl->multi.settle, when); + } + break; + } +} diff --git a/src/gba/cart/vfame.c b/src/gba/cart/vfame.c index 260db35b8..a45a420c0 100644 --- a/src/gba/cart/vfame.c +++ b/src/gba/cart/vfame.c @@ -1,42 +1,50 @@ /* Copyright (c) 2016 taizou + * Copyright (c) 2013-2024 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 const uint8_t ADDRESS_REORDERING[4][16] = { - { 15, 14, 9, 1, 8, 10, 7, 3, 5, 11, 4, 0, 13, 12, 2, 6 }, - { 15, 7, 13, 5, 11, 6, 0, 9, 12, 2, 10, 14, 3, 1, 8, 4 }, - { 15, 0, 3, 12, 2, 4, 14, 13, 1, 8, 6, 7, 9, 5, 11, 10 } +#define DIGIMON_SAPPHIRE_CHINESE_CRC32 0x793A328F + +static const uint8_t ADDRESS_REORDERING[][3][16] = { + [VFAME_STANDARD] = { + { 15, 14, 9, 1, 8, 10, 7, 3, 5, 11, 4, 0, 13, 12, 2, 6 }, + { 15, 7, 13, 5, 11, 6, 0, 9, 12, 2, 10, 14, 3, 1, 8, 4 }, + { 15, 0, 3, 12, 2, 4, 14, 13, 1, 8, 6, 7, 9, 5, 11, 10 } + }, + [VFAME_GEORGE] = { + { 15, 7, 13, 1, 11, 10, 14, 9, 12, 2, 4, 0, 3, 5, 8, 6 }, + { 15, 14, 3, 12, 8, 4, 0, 13, 5, 11, 6, 7, 9, 1, 2, 10 }, + { 15, 0, 9, 5, 2, 6, 7, 3, 1, 8, 10, 14, 13, 12, 11, 4 } + }, + [VFAME_ALTERNATE] = { + { 15, 0, 13, 5, 8, 4, 7, 3, 1, 2, 10, 14, 9, 12, 11, 6 }, + { 15, 7, 9, 1, 2, 6, 14, 13, 12, 11, 4, 0, 3, 5, 8, 10 }, + { 15, 14, 3, 12, 11, 10, 0, 9, 5, 8, 6, 7, 13, 1, 2, 4 } + }, }; -static const uint8_t ADDRESS_REORDERING_GEORGE[4][16] = { - { 15, 7, 13, 1, 11, 10, 14, 9, 12, 2, 4, 0, 3, 5, 8, 6 }, - { 15, 14, 3, 12, 8, 4, 0, 13, 5, 11, 6, 7, 9, 1, 2, 10 }, - { 15, 0, 9, 5, 2, 6, 7, 3, 1, 8, 10, 14, 13, 12, 11, 4 } -}; -static const uint8_t ADDRESS_REORDERING_ALTERNATE[4][16] = { - { 15, 0, 13, 5, 8, 4, 7, 3, 1, 2, 10, 14, 9, 12, 11, 6 }, - { 15, 7, 9, 1, 2, 6, 14, 13, 12, 11, 4, 0, 3, 5, 8, 10 }, - { 15, 14, 3, 12, 11, 10, 0, 9, 5, 8, 6, 7, 13, 1, 2, 4 } -}; -static const uint8_t VALUE_REORDERING[4][16] = { - { 5, 4, 3, 2, 1, 0, 7, 6 }, - { 3, 2, 1, 0, 7, 6, 5, 4 }, - { 1, 0, 7, 6, 5, 4, 3, 2 } -}; -static const uint8_t VALUE_REORDERING_GEORGE[4][16] = { - { 3, 0, 7, 2, 1, 4, 5, 6 }, - { 1, 4, 3, 0, 5, 6, 7, 2 }, - { 5, 2, 1, 6, 7, 0, 3, 4 } -}; -static const uint8_t VALUE_REORDERING_ALTERNATE[4][16] = { - { 5, 4, 7, 2, 1, 0, 3, 6 }, - { 1, 2, 3, 0, 5, 6, 7, 4 }, - { 3, 0, 1, 6, 7, 4, 5, 2 } + +static const uint8_t VALUE_REORDERING[][3][8] = { + [VFAME_STANDARD] = { + { 5, 4, 3, 2, 1, 0, 7, 6 }, + { 3, 2, 1, 0, 7, 6, 5, 4 }, + { 1, 0, 7, 6, 5, 4, 3, 2 } + }, + [VFAME_GEORGE] = { + { 3, 0, 7, 2, 1, 4, 5, 6 }, + { 1, 4, 3, 0, 5, 6, 7, 2 }, + { 5, 2, 1, 6, 7, 0, 3, 4 } + }, + [VFAME_ALTERNATE] = { + { 5, 4, 7, 2, 1, 0, 3, 6 }, + { 1, 2, 3, 0, 5, 6, 7, 4 }, + { 3, 0, 1, 6, 7, 4, 5, 2 } + }, }; static const int8_t MODE_CHANGE_START_SEQUENCE[5] = { 0x99, 0x02, 0x05, 0x02, 0x03 }; @@ -52,25 +60,19 @@ static int8_t _modifySramValue(enum GBAVFameCartType type, uint8_t value, int mo static uint32_t _modifySramAddress(enum GBAVFameCartType type, uint32_t address, int mode); static int _reorderBits(uint32_t value, const uint8_t* reordering, int reorderLength); -void GBAVFameInit(struct GBAVFameCart* cart) { - cart->cartType = VFAME_NO; - cart->sramMode = -1; - cart->romMode = -1; - cart->acceptingModeChange = false; -} - -void GBAVFameDetect(struct GBAVFameCart* cart, uint32_t* rom, size_t romSize, uint32_t crc32) { - cart->cartType = VFAME_NO; - +bool GBAVFameDetect(struct GBAVFameCart* cart, uint32_t* rom, size_t romSize, uint32_t crc32) { // The initialisation code is also present & run in the dumps of Digimon Ruby & Sapphire from hacked/deprotected reprint carts, // which would break if run in "proper" VFame mode so we need to exclude those.. if (romSize == 0x2000000) { // the deprotected dumps are 32MB but no real VF games are this size - return; + return false; } + bool detected = false; + // Most games have the same init sequence in the same place // but LOTR/Mo Jie Qi Bing doesn't, probably because it's based on the Kiki KaiKai engine, so just detect based on its title if (memcmp(INIT_SEQUENCE, &rom[0x57], sizeof(INIT_SEQUENCE)) == 0 || memcmp("\0LORD\0WORD\0\0AKIJ", &((struct GBACartridge*) rom)->title, 16) == 0) { + detected = true; cart->cartType = VFAME_STANDARD; mLOG(GBA_MEM, INFO, "Vast Fame game detected"); } @@ -79,13 +81,23 @@ void GBAVFameDetect(struct GBAVFameCart* cart, uint32_t* rom, size_t romSize, ui // Their initialisation seems to be identical so the difference must be in the cart HW itself // Other undumped games may have similar differences if (memcmp("George Sango", &((struct GBACartridge*) rom)->title, 12) == 0) { + detected = true; cart->cartType = VFAME_GEORGE; mLOG(GBA_MEM, INFO, "George mode"); } else if (crc32 == DIGIMON_SAPPHIRE_CHINESE_CRC32) { // Chinese version of Digimon Sapphire; header is identical to the English version which uses the normal reordering // so we have to use some other way to detect it + detected = true; cart->cartType = VFAME_ALTERNATE; } + + if (detected) { + cart->sramMode = -1; + cart->romMode = -1; + cart->acceptingModeChange = false; + } + + return detected; } // This is not currently being used but would be called on ROM reads @@ -227,7 +239,6 @@ static uint32_t _patternRightShift2(uint32_t addr) { } void GBAVFameSramWrite(struct GBAVFameCart* cart, uint32_t address, uint8_t value, uint8_t* sramData) { - address &= 0x00FFFFFF; // A certain sequence of writes to SRAM FFF8->FFFC can enable or disable "mode change" mode // Currently unknown if these writes have to be sequential, or what happens if you write different values, if anything if (address >= 0xFFF8 && address <= 0xFFFC) { @@ -268,25 +279,14 @@ static uint32_t _modifySramAddress(enum GBAVFameCartType type, uint32_t address, mode &= 0x3; if (mode == 0) { return address; - } else if (type == VFAME_GEORGE) { - return _reorderBits(address, ADDRESS_REORDERING_GEORGE[mode - 1], 16); - } else if (type == VFAME_ALTERNATE) { - return _reorderBits(address, ADDRESS_REORDERING_ALTERNATE[mode - 1], 16); - } else { - return _reorderBits(address, ADDRESS_REORDERING[mode - 1], 16); } + return _reorderBits(address, ADDRESS_REORDERING[type][mode - 1], 16); } static int8_t _modifySramValue(enum GBAVFameCartType type, uint8_t value, int mode) { int reorderType = (mode & 0xF) >> 2; if (reorderType != 0) { - if (type == VFAME_GEORGE) { - value = _reorderBits(value, VALUE_REORDERING_GEORGE[reorderType - 1], 8); - } else if (type == VFAME_ALTERNATE) { - value = _reorderBits(value, VALUE_REORDERING_ALTERNATE[reorderType - 1], 8); - } else { - value = _reorderBits(value, VALUE_REORDERING[reorderType - 1], 8); - } + value = _reorderBits(value, VALUE_REORDERING[type][reorderType - 1], 8); } if (mode & 0x80) { value ^= 0xAA; diff --git a/src/gba/core.c b/src/gba/core.c index a6939406e..1d3fffb9e 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -30,6 +30,7 @@ #ifdef USE_ELF #include #endif +#include #include #include #include @@ -667,6 +668,9 @@ static size_t _GBACoreROMSize(const struct mCore* core) { if (gba->romVf) { return gba->romVf->size(gba->romVf); } + if (gba->mbVf) { + return gba->mbVf->size(gba->mbVf); + } return gba->pristineRomSize; } @@ -676,6 +680,19 @@ static void _GBACoreChecksum(const struct mCore* core, void* data, enum mCoreChe case mCHECKSUM_CRC32: memcpy(data, &gba->romCrc32, sizeof(gba->romCrc32)); break; + case mCHECKSUM_MD5: + if (gba->romVf) { + md5File(gba->romVf, data); + } else if (gba->mbVf) { + md5File(gba->mbVf, data); + } else if (gba->memory.rom && gba->isPristine) { + md5Buffer(gba->memory.rom, gba->pristineRomSize, data); + } else if (gba->memory.rom) { + md5Buffer(gba->memory.rom, gba->memory.romSize, data); + } else { + md5Buffer("", 0, data); + } + break; } return; } @@ -842,7 +859,21 @@ static bool _GBACoreLoadExtraState(struct mCore* core, const struct mStateExtdat if (type == gba->video.renderer->rendererId(gba->video.renderer)) { ok = gba->video.renderer->loadState(gba->video.renderer, (void*) ((uintptr_t) item.data + sizeof(uint32_t)), - item.size - sizeof(type)); + item.size - sizeof(type)) && ok; + } + } else if (item.data) { + ok = false; + } + } + if (gba->sio.driver && gba->sio.driver->driverId && gba->sio.driver->loadState && + mStateExtdataGet(extdata, EXTDATA_SUBSYSTEM_START + GBA_SUBSYSTEM_SIO_DRIVER, &item)) { + if ((uint32_t) item.size > sizeof(uint32_t)) { + uint32_t type; + LOAD_32(type, 0, item.data); + if (type == gba->sio.driver->driverId(gba->sio.driver)) { + ok = gba->sio.driver->loadState(gba->sio.driver, + (void*) ((uintptr_t) item.data + sizeof(uint32_t)), + item.size - sizeof(type)) && ok; } } else if (item.data) { ok = false; @@ -868,6 +899,27 @@ static bool _GBACoreSaveExtraState(struct mCore* core, struct mStateExtdata* ext } if (buffer) { free(buffer); + buffer = NULL; + } + size = 0; + + if (gba->sio.driver && gba->sio.driver->driverId && gba->sio.driver->saveState) { + gba->sio.driver->saveState(gba->sio.driver, &buffer, &size); + if (size > 0 && buffer) { + struct mStateExtdataItem item; + item.size = size + sizeof(uint32_t); + item.data = malloc(item.size); + item.clean = free; + uint32_t type = gba->sio.driver->driverId(gba->sio.driver); + STORE_32(type, 0, item.data); + memcpy((void*) ((uintptr_t) item.data + sizeof(uint32_t)), buffer, size); + mStateExtdataPut(extdata, EXTDATA_SUBSYSTEM_START + GBA_SUBSYSTEM_SIO_DRIVER, &item); + } + if (buffer) { + free(buffer); + buffer = NULL; + } + size = 0; } return true; @@ -927,9 +979,8 @@ static void _GBACoreSetPeripheral(struct mCore* core, int type, void* periph) { case mPERIPH_GBA_LUMINANCE: gba->luminanceSource = periph; break; - case mPERIPH_GBA_BATTLECHIP_GATE: - GBASIOSetDriver(&gba->sio, periph, GBA_SIO_MULTI); - GBASIOSetDriver(&gba->sio, periph, GBA_SIO_NORMAL_32); + case mPERIPH_GBA_LINK_PORT: + GBASIOSetDriver(&gba->sio, periph); break; default: return; diff --git a/src/gba/dma.c b/src/gba/dma.c index 290341361..e9044d26a 100644 --- a/src/gba/dma.c +++ b/src/gba/dma.c @@ -256,15 +256,21 @@ void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) { if (info->count == info->nextCount) { if (width == 4) { cycles += memory->waitstatesNonseq32[sourceRegion] + memory->waitstatesNonseq32[destRegion]; + info->cycles = memory->waitstatesSeq32[sourceRegion] + memory->waitstatesSeq32[destRegion]; } else { cycles += memory->waitstatesNonseq16[sourceRegion] + memory->waitstatesNonseq16[destRegion]; + info->cycles = memory->waitstatesSeq16[sourceRegion] + memory->waitstatesSeq16[destRegion]; } } else { - if (width == 4) { - cycles += memory->waitstatesSeq32[sourceRegion] + memory->waitstatesSeq32[destRegion]; - } else { - cycles += memory->waitstatesSeq16[sourceRegion] + memory->waitstatesSeq16[destRegion]; + // Crossed region boundary; recalculate cached cycles + if (UNLIKELY(!(source & 0x00FFFFFC) || !(dest & 0x00FFFFFC))) { + if (width == 4) { + info->cycles = memory->waitstatesSeq32[sourceRegion] + memory->waitstatesSeq32[destRegion]; + } else { + info->cycles = memory->waitstatesSeq16[sourceRegion] + memory->waitstatesSeq16[destRegion]; + } } + cycles += info->cycles; } info->when += cycles; @@ -281,7 +287,7 @@ void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) { memory->dmaTransferRegister = cpu->memory.load16(cpu, source, 0); memory->dmaTransferRegister |= memory->dmaTransferRegister << 16; } - if (destRegion == GBA_REGION_ROM2_EX) { + if (UNLIKELY(destRegion == GBA_REGION_ROM2_EX)) { if (memory->savedata.type == GBA_SAVEDATA_AUTODETECT) { mLOG(GBA_MEM, INFO, "Detected EEPROM savegame"); GBASavedataInitEEPROM(&memory->savedata); @@ -313,9 +319,11 @@ void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) { int i; for (i = 0; i < 4; ++i) { struct GBADMA* dma = &memory->dma[i]; - int32_t time = dma->when - info->when; - if (time < 0 && GBADMARegisterIsEnable(dma->reg) && dma->nextCount) { - dma->when = info->when; + if (GBADMARegisterIsEnable(dma->reg) && dma->nextCount) { + int32_t time = dma->when - info->when; + if (time < 0) { + dma->when = info->when; + } } } @@ -327,3 +335,22 @@ void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) { } GBADMAUpdate(gba); } + +void GBADMARecalculateCycles(struct GBA* gba) { + int i; + for (i = 0; i < 4; ++i) { + struct GBADMA* dma = &gba->memory.dma[i]; + if (!GBADMARegisterIsEnable(dma->reg)) { + continue; + } + + uint32_t width = GBADMARegisterGetWidth(dma->reg); + uint32_t sourceRegion = dma->nextSource >> BASE_OFFSET; + uint32_t destRegion = dma->nextDest >> BASE_OFFSET; + if (width) { + dma->cycles = gba->memory.waitstatesSeq32[sourceRegion] + gba->memory.waitstatesSeq32[destRegion]; + } else { + dma->cycles = gba->memory.waitstatesSeq16[sourceRegion] + gba->memory.waitstatesSeq16[destRegion]; + } + } +} diff --git a/src/gba/extra/battlechip.c b/src/gba/extra/battlechip.c index 3ab9f1403..d7bf35cce 100644 --- a/src/gba/extra/battlechip.c +++ b/src/gba/extra/battlechip.c @@ -33,28 +33,25 @@ enum { BATTLECHIP_CONTINUE = 0xFFFF, }; -static bool GBASIOBattlechipGateLoad(struct GBASIODriver* driver); -static uint16_t GBASIOBattlechipGateWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value); - -static void _battlechipTransfer(struct GBASIOBattlechipGate* gate); -static void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cyclesLate); +static bool GBASIOBattlechipGateInit(struct GBASIODriver* driver); +static uint16_t GBASIOBattlechipGateWriteSIOCNT(struct GBASIODriver* driver, uint16_t value); +static bool GBASIOBattlechipGateHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode); +static int GBASIOBattlechipGateConnectedDevices(struct GBASIODriver* driver); +static void GBASIOBattlechipGateFinishMultiplayer(struct GBASIODriver* driver, uint16_t data[4]); void GBASIOBattlechipGateCreate(struct GBASIOBattlechipGate* gate) { - gate->d.init = NULL; - gate->d.deinit = NULL; - gate->d.load = GBASIOBattlechipGateLoad; - gate->d.unload = NULL; - gate->d.writeRegister = GBASIOBattlechipGateWriteRegister; - - gate->event.context = gate; - gate->event.callback = _battlechipTransferEvent; - gate->event.priority = 0x80; + memset(&gate->d, 0, sizeof(gate->d)); + gate->d.init = GBASIOBattlechipGateInit; + gate->d.writeSIOCNT = GBASIOBattlechipGateWriteSIOCNT; + gate->d.handlesMode = GBASIOBattlechipGateHandlesMode; + gate->d.connectedDevices = GBASIOBattlechipGateConnectedDevices; + gate->d.finishMultiplayer = GBASIOBattlechipGateFinishMultiplayer; gate->chipId = 0; gate->flavor = GBA_FLAVOR_BATTLECHIP_GATE; } -bool GBASIOBattlechipGateLoad(struct GBASIODriver* driver) { +bool GBASIOBattlechipGateInit(struct GBASIODriver* driver) { struct GBASIOBattlechipGate* gate = (struct GBASIOBattlechipGate*) driver; gate->state = BATTLECHIP_STATE_SYNC; gate->data[0] = 0x00FE; @@ -62,58 +59,34 @@ bool GBASIOBattlechipGateLoad(struct GBASIODriver* driver) { return true; } -uint16_t GBASIOBattlechipGateWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) { - struct GBASIOBattlechipGate* gate = (struct GBASIOBattlechipGate*) driver; - switch (address) { - case GBA_REG_SIOCNT: - value &= ~0xC; - value |= 0x8; - if (value & 0x80) { - _battlechipTransfer(gate); - } - break; - case GBA_REG_SIOMLT_SEND: - break; - case GBA_REG_RCNT: - break; - default: - break; - } +uint16_t GBASIOBattlechipGateWriteSIOCNT(struct GBASIODriver* driver, uint16_t value) { + UNUSED(driver); + value &= ~0xC; + value |= 0x8; return value; } -void _battlechipTransfer(struct GBASIOBattlechipGate* gate) { - int32_t cycles; - if (gate->d.p->mode == GBA_SIO_NORMAL_32) { - cycles = GBA_ARM7TDMI_FREQUENCY / 0x40000; - } else { - cycles = GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(gate->d.p->siocnt)][1]; +static bool GBASIOBattlechipGateHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode) { + UNUSED(driver); + switch (mode) { + case GBA_SIO_NORMAL_32: + case GBA_SIO_MULTI: + return true; + default: + return false; } - mTimingDeschedule(&gate->d.p->p->timing, &gate->event); - mTimingSchedule(&gate->d.p->p->timing, &gate->event, cycles); } -void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cyclesLate) { - UNUSED(timing); - struct GBASIOBattlechipGate* gate = user; +static int GBASIOBattlechipGateConnectedDevices(struct GBASIODriver* driver) { + UNUSED(driver); + return 1; +} - if (gate->d.p->mode == GBA_SIO_NORMAL_32) { - gate->d.p->p->memory.io[GBA_REG(SIODATA32_LO)] = 0; - gate->d.p->p->memory.io[GBA_REG(SIODATA32_HI)] = 0; - gate->d.p->siocnt = GBASIONormalClearStart(gate->d.p->siocnt); - if (GBASIONormalIsIrq(gate->d.p->siocnt)) { - GBARaiseIRQ(gate->d.p->p, GBA_IRQ_SIO, cyclesLate); - } - return; - } +static void GBASIOBattlechipGateFinishMultiplayer(struct GBASIODriver* driver, uint16_t data[4]) { + struct GBASIOBattlechipGate* gate = (struct GBASIOBattlechipGate*) driver; uint16_t cmd = gate->d.p->p->memory.io[GBA_REG(SIOMLT_SEND)]; uint16_t reply = 0xFFFF; - gate->d.p->p->memory.io[GBA_REG(SIOMULTI0)] = cmd; - gate->d.p->p->memory.io[GBA_REG(SIOMULTI2)] = 0xFFFF; - gate->d.p->p->memory.io[GBA_REG(SIOMULTI3)] = 0xFFFF; - gate->d.p->siocnt = GBASIOMultiplayerClearBusy(gate->d.p->siocnt); - gate->d.p->siocnt = GBASIOMultiplayerSetId(gate->d.p->siocnt, 0); mLOG(GBA_BATTLECHIP, DEBUG, "Game: %04X (%i)", cmd, gate->state); @@ -146,7 +119,7 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle case 0xA3D0: // EXE 4 case 0xA6C0: - mLOG(GBA_BATTLECHIP, DEBUG, "Resync detected"); + mLOG(GBA_BATTLECHIP, DEBUG, "Resync detected"); gate->state = BATTLECHIP_STATE_SYNC; break; } @@ -191,9 +164,8 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle mLOG(GBA_BATTLECHIP, DEBUG, "Gate: %04X (%i)", reply, gate->state); ++gate->state; - gate->d.p->p->memory.io[GBA_REG(SIOMULTI1)] = reply; - - if (GBASIOMultiplayerIsIrq(gate->d.p->siocnt)) { - GBARaiseIRQ(gate->d.p->p, GBA_IRQ_SIO, cyclesLate); - } + data[0] = cmd; + data[1] = reply; + data[2] = 0xFFFF; + data[3] = 0xFFFF; } diff --git a/src/gba/extra/proxy.c b/src/gba/extra/proxy.c index ee611247b..e33c7b568 100644 --- a/src/gba/extra/proxy.c +++ b/src/gba/extra/proxy.c @@ -324,7 +324,7 @@ void GBAVideoProxyRendererSaveState(struct GBAVideoRenderer* renderer, void** st proxyRenderer->logger->stateSize = 0; } else { proxyRenderer->backend->saveState(proxyRenderer->backend, state, size); - } + } } void GBAVideoProxyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) { diff --git a/src/gba/gba.c b/src/gba/gba.c index aab2cd3ec..1b84038ae 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -140,6 +140,9 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) { void GBAUnloadROM(struct GBA* gba) { GBAMemoryClearAGBPrint(gba); + if (gba->memory.unl.type) { + GBAUnlCartUnload(gba); + } if (gba->memory.rom && !gba->isPristine) { if (gba->yankedRomSize) { gba->yankedRomSize = 0; @@ -263,8 +266,8 @@ void GBAReset(struct ARMCore* cpu) { // GB Player SIO control should not be engaged before detection, even if we already know it's GBP gba->memory.hw.devices &= ~HW_GB_PLAYER; - if (gba->sio.drivers.normal == &gba->sio.gbp.d) { - GBASIOSetDriver(&gba->sio, NULL, GBA_SIO_NORMAL_32); + if (gba->sio.driver == &gba->sio.gbp.d) { + GBASIOSetDriver(&gba->sio, NULL); } bool isELF = false; @@ -492,7 +495,7 @@ bool GBALoadROM(struct GBA* gba, struct VFile* vf) { gba->cpu->memory.setActiveRegion(gba->cpu, gba->cpu->gprs[ARM_PC]); } GBAHardwareInit(&gba->memory.hw, &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]); - GBAVFameDetect(&gba->memory.vfame, gba->memory.rom, gba->memory.romSize, gba->romCrc32); + GBAUnlCartDetect(gba); // TODO: error check return true; } @@ -557,16 +560,14 @@ void GBAApplyPatch(struct GBA* gba, struct Patch* patch) { mappedMemoryFree(newRom, GBA_SIZE_ROM0); return; } - if (gba->romVf) { + if (gba->memory.rom) { #ifndef FIXED_ROM_BUFFER if (!gba->isPristine) { - mappedMemoryFree(gba->memory.rom, GBA_SIZE_ROM0); + mappedMemoryFree(gba->memory.rom, gba->memory.romSize); } else { gba->romVf->unmap(gba->romVf, gba->memory.rom, gba->pristineRomSize); } #endif - gba->romVf->close(gba->romVf); - gba->romVf = NULL; } gba->isPristine = false; gba->memory.rom = newRom; diff --git a/src/gba/hle-bios.c b/src/gba/hle-bios.c index 841caf543..5b1acd3ba 100644 --- a/src/gba/hle-bios.c +++ b/src/gba/hle-bios.c @@ -20,7 +20,7 @@ const uint8_t hleBios[GBA_SIZE_BIOS] = { 0x00, 0x00, 0xa0, 0xe1, 0x00, 0x00, 0xa0, 0xe1, 0x04, 0x40, 0xbd, 0xe8, 0x93, 0xf0, 0x29, 0xe3, 0x00, 0x08, 0xbd, 0xe8, 0x0b, 0xf0, 0x69, 0xe1, 0x00, 0x58, 0xbd, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x20, 0xa0, 0xe3, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00, + 0x04, 0x20, 0xa0, 0xe3, 0x00, 0x00, 0x00, 0x00, 0xe4, 0x03, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00, 0xb4, 0x01, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00, 0xcc, 0x01, 0x00, 0x00, 0xc4, 0x01, 0x00, 0x00, 0x48, 0x03, 0x00, 0x00, 0x48, 0x03, 0x00, 0x00, 0x48, 0x03, 0x00, 0x00, 0x48, 0x03, 0x00, 0x00, @@ -75,16 +75,29 @@ const uint8_t hleBios[GBA_SIZE_BIOS] = { 0xf0, 0x07, 0xbd, 0xe8, 0x1e, 0xff, 0x2f, 0xe1, 0xb0, 0x01, 0x00, 0x00, 0x04, 0xb0, 0x5b, 0xe2, 0xfd, 0xff, 0xff, 0x8a, 0x1e, 0xff, 0x2f, 0xe1, 0xc2, 0xe3, 0xa0, 0xe3, 0x03, 0x10, 0x5e, 0xe4, 0x00, 0x00, 0x51, 0xe3, - 0x00, 0x10, 0xa0, 0x13, 0x1e, 0xff, 0x2f, 0x11, 0x1c, 0xe0, 0x9f, 0xe5, + 0x00, 0x10, 0xa0, 0x13, 0x05, 0x00, 0x00, 0x1a, 0x1c, 0xe0, 0x9f, 0xe5, 0x00, 0x10, 0x9e, 0xe5, 0x00, 0x00, 0x51, 0xe3, 0x00, 0x10, 0xa0, 0xe3, - 0x1e, 0xff, 0x2f, 0x11, 0xc0, 0xe0, 0x4e, 0xe2, 0x1e, 0xff, 0x2f, 0xe1, + 0x00, 0x00, 0x00, 0x1a, 0xc0, 0xe0, 0x4e, 0xe2, 0x1e, 0xff, 0x2f, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x29, 0xe1, 0xc0, 0x00, 0x00, 0x02, - 0x4c, 0xd0, 0x9f, 0xe5, 0x00, 0x50, 0x2d, 0xe9, 0x00, 0xc0, 0x4f, 0xe1, + 0xdc, 0xd0, 0x9f, 0xe5, 0x00, 0x50, 0x2d, 0xe9, 0x00, 0xc0, 0x4f, 0xe1, 0x00, 0xe0, 0x0f, 0xe1, 0x00, 0x50, 0x2d, 0xe9, 0x02, 0xe3, 0xa0, 0xe3, 0x9c, 0xc0, 0xde, 0xe5, 0xa5, 0x00, 0x5c, 0xe3, 0x04, 0x00, 0x00, 0x1a, 0xb4, 0xc0, 0xde, 0xe5, 0x80, 0x00, 0x1c, 0xe3, 0x04, 0xe0, 0x8f, 0xe2, - 0x20, 0xf0, 0x9f, 0x15, 0x20, 0xf0, 0x9f, 0x05, 0x14, 0xd0, 0x9f, 0xe5, + 0xb0, 0xf0, 0x9f, 0x15, 0xb0, 0xf0, 0x9f, 0x05, 0xa4, 0xd0, 0x9f, 0xe5, 0x10, 0xc0, 0x1d, 0xe5, 0x0c, 0xf0, 0x69, 0xe1, 0x00, 0x50, 0x3d, 0xe9, 0x04, 0xf0, 0x5e, 0xe2, 0x00, 0x00, 0x00, 0x00, 0x04, 0xe0, 0xa0, 0x03, - 0xf0, 0x7f, 0x00, 0x03, 0x00, 0x20, 0xfe, 0x09, 0x00, 0xc0, 0xff, 0x09 + 0x00, 0xf0, 0x69, 0xe3, 0x00, 0xe0, 0xa0, 0xe3, 0x8c, 0xd0, 0x9f, 0xe5, + 0x92, 0xf0, 0x21, 0xe3, 0x00, 0xf0, 0x69, 0xe3, 0x00, 0xe0, 0xa0, 0xe3, + 0x80, 0xd0, 0x9f, 0xe5, 0x93, 0xf0, 0x21, 0xe3, 0x00, 0xf0, 0x69, 0xe3, + 0x00, 0xe0, 0xa0, 0xe3, 0x74, 0xd0, 0x9f, 0xe5, 0x01, 0x03, 0xa0, 0xe3, + 0x02, 0x1c, 0x40, 0xe2, 0x06, 0x00, 0x50, 0xe5, 0x00, 0x20, 0xa0, 0xe3, + 0x00, 0x30, 0xa0, 0xe3, 0x00, 0x40, 0xa0, 0xe3, 0x00, 0x50, 0xa0, 0xe3, + 0x00, 0x60, 0xa0, 0xe3, 0x00, 0x70, 0xa0, 0xe3, 0x00, 0x80, 0xa0, 0xe3, + 0x00, 0x90, 0xa0, 0xe3, 0x00, 0xa0, 0xa0, 0xe3, 0x00, 0xb0, 0xa0, 0xe3, + 0x00, 0xc0, 0xa0, 0xe3, 0xfc, 0x03, 0xa1, 0xe8, 0x01, 0x03, 0x51, 0xe3, + 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x50, 0xe3, 0x00, 0x00, 0xa0, 0xe3, + 0x00, 0x10, 0xa0, 0xe3, 0x02, 0xe3, 0xa0, 0x03, 0x02, 0xe4, 0xa0, 0x13, + 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x29, 0xe1, + 0xf0, 0x7f, 0x00, 0x03, 0x00, 0x20, 0xfe, 0x09, 0x00, 0xc0, 0xff, 0x09, + 0x00, 0x7f, 0x00, 0x03, 0xa0, 0x7f, 0x00, 0x03, 0xe0, 0x7f, 0x00, 0x03 }; diff --git a/src/gba/hle-bios.s b/src/gba/hle-bios.s index 07f827b26..eccbaa143 100644 --- a/src/gba/hle-bios.s +++ b/src/gba/hle-bios.s @@ -129,7 +129,6 @@ subs pc, lr, #4 .word 0x03A0E004 @ Unimplemented -SoftReset: RegisterRamReset: Stop: GetBiosChecksum: @@ -318,13 +317,14 @@ mov lr, #0x8000003 ldrb r1, [lr], #-3 cmp r1, #0 movne r1, #0 -bxne lr +bne 1f ldr lr, =0x20000C0 ldr r1, [lr] cmp r1, #0 mov r1, #0 -bxne lr +bne 1f sub lr, #0xC0 +1: bx lr .word 0 .word 0xE129F000 @@ -357,3 +357,44 @@ ldmdb sp!, {r12, lr} subs pc, lr, #4 .word 0 .word 0x03A0E004 + +SoftReset: +msr spsr, #0 +mov lr, #0 +ldr sp, =0x03007F00 +msr cpsr_c, #0x92 +msr spsr, #0 +mov lr, #0 +ldr sp, =0x03007FA0 +msr cpsr_c, #0x93 +msr spsr, #0 +mov lr, #0 +ldr sp, =0x03007FE0 +mov r0, #0x04000000 +sub r1, r0, #0x200 +ldrb r0, [r0, #-6] +mov r2, #0 +mov r3, #0 +mov r4, #0 +mov r5, #0 +mov r6, #0 +mov r7, #0 +mov r8, #0 +mov r9, #0 +mov r10, #0 +mov r11, #0 +mov r12, #0 +1: +stmia r1!, {r2, r3, r4, r5, r6, r7, r8, r9} +cmp r1, #0x04000000 +bne 1b +cmp r0, #0 +mov r0, #0 +mov r1, #0 +moveq lr, #0x08000000 +movne lr, #0x02000000 +movs pc, lr +.word 0 +.word 0xE129F000 + +.ltorg diff --git a/src/gba/io.c b/src/gba/io.c index 397eb9253..cb6058f7f 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -214,8 +214,8 @@ static const int _isRSpecialRegister[GBA_REG(INTERNAL_MAX)] = { /* 10 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 11 */ 0, 0, 0, 0, 0, 0, 0, 0, /* SIO */ - /* 12 */ 1, 1, 1, 1, 1, 0, 0, 0, - /* 13 */ 1, 1, 1, 0, 0, 0, 0, 0, + /* 12 */ 1, 1, 1, 1, 0, 0, 0, 0, + /* 13 */ 1, 1, 0, 0, 0, 0, 0, 0, /* 14 */ 1, 0, 0, 0, 0, 0, 0, 0, /* 15 */ 1, 1, 1, 1, 1, 0, 0, 0, /* 16 */ 0, 0, 0, 0, 0, 0, 0, 0, @@ -326,17 +326,19 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { break; case GBA_REG_SOUND1CNT_HI: GBAAudioWriteSOUND1CNT_HI(&gba->audio, value); + value &= 0xFFC0; break; case GBA_REG_SOUND1CNT_X: GBAAudioWriteSOUND1CNT_X(&gba->audio, value); - value &= 0x47FF; + value &= 0x4000; break; case GBA_REG_SOUND2CNT_LO: GBAAudioWriteSOUND2CNT_LO(&gba->audio, value); + value &= 0xFFC0; break; case GBA_REG_SOUND2CNT_HI: GBAAudioWriteSOUND2CNT_HI(&gba->audio, value); - value &= 0x47FF; + value &= 0x4000; break; case GBA_REG_SOUND3CNT_LO: GBAAudioWriteSOUND3CNT_LO(&gba->audio, value); @@ -344,16 +346,15 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { break; case GBA_REG_SOUND3CNT_HI: GBAAudioWriteSOUND3CNT_HI(&gba->audio, value); - value &= 0xE03F; + value &= 0xE000; break; case GBA_REG_SOUND3CNT_X: GBAAudioWriteSOUND3CNT_X(&gba->audio, value); - // TODO: The low bits need to not be readable, but still 8-bit writable - value &= 0x47FF; + value &= 0x4000; break; case GBA_REG_SOUND4CNT_LO: GBAAudioWriteSOUND4CNT_LO(&gba->audio, value); - value &= 0xFF3F; + value &= 0xFF00; break; case GBA_REG_SOUND4CNT_HI: GBAAudioWriteSOUND4CNT_HI(&gba->audio, value); @@ -482,6 +483,7 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { // SIO case GBA_REG_SIOCNT: + value &= 0x7FFF; GBASIOWriteSIOCNT(&gba->sio, value); break; case GBA_REG_RCNT: @@ -585,9 +587,96 @@ void GBAIOWrite8(struct GBA* gba, uint32_t address, uint8_t value) { if (address > GBA_SIZE_IO) { return; } - uint16_t value16 = value << (8 * (address & 1)); - value16 |= (gba->memory.io[(address & (GBA_SIZE_IO - 1)) >> 1]) & ~(0xFF << (8 * (address & 1))); - GBAIOWrite(gba, address & 0xFFFFFFFE, value16); + uint16_t value16; + + switch (address) { + case GBA_REG_SOUND1CNT_HI: + GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing)); + GBAudioWriteNR11(&gba->audio.psg, value); + gba->memory.io[GBA_REG(SOUND1CNT_HI)] &= 0xFF00; + gba->memory.io[GBA_REG(SOUND1CNT_HI)] |= value & 0xC0; + break; + case GBA_REG_SOUND1CNT_HI + 1: + GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing)); + GBAudioWriteNR12(&gba->audio.psg, value); + gba->memory.io[GBA_REG(SOUND1CNT_HI)] &= 0x00C0; + gba->memory.io[GBA_REG(SOUND1CNT_HI)] |= value << 8; + break; + case GBA_REG_SOUND1CNT_X: + GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing)); + GBAudioWriteNR13(&gba->audio.psg, value); + break; + case GBA_REG_SOUND1CNT_X + 1: + GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing)); + GBAudioWriteNR14(&gba->audio.psg, value); + gba->memory.io[GBA_REG(SOUND1CNT_X)] = (value & 0x40) << 8; + break; + case GBA_REG_SOUND2CNT_LO: + GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing)); + GBAudioWriteNR21(&gba->audio.psg, value); + gba->memory.io[GBA_REG(SOUND2CNT_LO)] &= 0xFF00; + gba->memory.io[GBA_REG(SOUND2CNT_LO)] |= value & 0xC0; + break; + case GBA_REG_SOUND2CNT_LO + 1: + GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing)); + GBAudioWriteNR22(&gba->audio.psg, value); + gba->memory.io[GBA_REG(SOUND2CNT_LO)] &= 0x00C0; + gba->memory.io[GBA_REG(SOUND2CNT_LO)] |= value << 8; + break; + case GBA_REG_SOUND2CNT_HI: + GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing)); + GBAudioWriteNR23(&gba->audio.psg, value); + break; + case GBA_REG_SOUND2CNT_HI + 1: + GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing)); + GBAudioWriteNR24(&gba->audio.psg, value); + gba->memory.io[GBA_REG(SOUND2CNT_HI)] = (value & 0x40) << 8; + break; + case GBA_REG_SOUND3CNT_HI: + GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing)); + GBAudioWriteNR31(&gba->audio.psg, value); + break; + case GBA_REG_SOUND3CNT_HI + 1: + GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing)); + gba->audio.psg.ch3.volume = GBAudioRegisterBankVolumeGetVolumeGBA(value); + gba->memory.io[GBA_REG(SOUND3CNT_HI)] = (value & 0xE0) << 8; + break; + case GBA_REG_SOUND3CNT_X: + GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing)); + GBAudioWriteNR33(&gba->audio.psg, value); + break; + case GBA_REG_SOUND3CNT_X + 1: + GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing)); + GBAudioWriteNR34(&gba->audio.psg, value); + gba->memory.io[GBA_REG(SOUND3CNT_X)] = (value & 0x40) << 8; + break; + case GBA_REG_SOUND4CNT_LO: + GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing)); + GBAudioWriteNR41(&gba->audio.psg, value); + break; + case GBA_REG_SOUND4CNT_LO + 1: + GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing)); + GBAudioWriteNR42(&gba->audio.psg, value); + gba->memory.io[GBA_REG(SOUND4CNT_LO)] = value << 8; + break; + case GBA_REG_SOUND4CNT_HI: + GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing)); + GBAudioWriteNR43(&gba->audio.psg, value); + gba->memory.io[GBA_REG(SOUND4CNT_HI)] &= 0x4000; + gba->memory.io[GBA_REG(SOUND4CNT_HI)] |= value; + break; + case GBA_REG_SOUND4CNT_HI + 1: + GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing)); + GBAudioWriteNR44(&gba->audio.psg, value); + gba->memory.io[GBA_REG(SOUND4CNT_HI)] &= 0x00FF; + gba->memory.io[GBA_REG(SOUND4CNT_HI)] |= (value & 0x40) << 8; + break; + default: + value16 = value << (8 * (address & 1)); + value16 |= (gba->memory.io[(address & (GBA_SIZE_IO - 1)) >> 1]) & ~(0xFF << (8 * (address & 1))); + GBAIOWrite(gba, address & 0xFFFFFFFE, value16); + break; + } } void GBAIOWrite32(struct GBA* gba, uint32_t address, uint32_t value) { @@ -990,6 +1079,7 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) { LOAD_32(gba->dmaPC, 0, &state->dmaBlockPC); LOAD_32(gba->bus, 0, &state->bus); + GBADMARecalculateCycles(gba); GBADMAUpdate(gba); GBAHardwareDeserialize(&gba->memory.hw, state); } diff --git a/src/gba/memory.c b/src/gba/memory.c index 09d3e7a74..4f074657a 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -93,7 +93,7 @@ void GBAMemoryInit(struct GBA* gba) { gba->memory.iwram = &gba->memory.wram[GBA_SIZE_EWRAM >> 2]; GBADMAInit(gba); - GBAVFameInit(&gba->memory.vfame); + GBAUnlCartInit(gba); gba->memory.ereader.p = gba; gba->memory.ereader.dots = NULL; @@ -138,7 +138,18 @@ void GBAMemoryReset(struct GBA* gba) { mLOG(GBA_MEM, FATAL, "Could not map memory"); } + if (!gba->memory.rom) { + gba->isPristine = false; + } + + if (gba->memory.hw.devices & HW_GPIO) { + _pristineCow(gba); + } + + GBASavedataReset(&gba->memory.savedata); + GBAHardwareReset(&gba->memory.hw); GBADMAReset(gba); + GBAUnlCartReset(gba); memset(&gba->memory.matrix, 0, sizeof(gba->memory.matrix)); } @@ -411,7 +422,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { wait += waitstatesRegion[address >> BASE_OFFSET]; \ if ((address & (GBA_SIZE_ROM0 - 4)) < memory->romSize) { \ LOAD_32(value, address & (GBA_SIZE_ROM0 - 4), memory->rom); \ - } else if (memory->vfame.cartType) { \ + } else if (memory->unl.type == GBA_UNL_CART_VFAME) { \ value = GBAVFameGetPatternValue(address, 32); \ } else { \ mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load32: 0x%08X", address); \ @@ -576,7 +587,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; if ((address & (GBA_SIZE_ROM0 - 2)) < memory->romSize) { LOAD_16(value, address & (GBA_SIZE_ROM0 - 2), memory->rom); - } else if (memory->vfame.cartType) { + } else if (memory->unl.type == GBA_UNL_CART_VFAME) { value = GBAVFameGetPatternValue(address, 16); } else if ((address & (GBA_SIZE_ROM0 - 2)) >= AGB_PRINT_BASE) { uint32_t agbPrintAddr = address & 0x00FFFFFF; @@ -601,7 +612,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { value = GBACartEReaderRead(&memory->ereader, address); } else if ((address & (GBA_SIZE_ROM0 - 2)) < memory->romSize) { LOAD_16(value, address & (GBA_SIZE_ROM0 - 2), memory->rom); - } else if (memory->vfame.cartType) { + } else if (memory->unl.type == GBA_UNL_CART_VFAME) { value = GBAVFameGetPatternValue(address, 16); } else { mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address); @@ -692,7 +703,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) { value = ((uint8_t*) memory->rom)[address & (GBA_SIZE_ROM0 - 1)]; - } else if (memory->vfame.cartType) { + } else if (memory->unl.type == GBA_UNL_CART_VFAME) { value = GBAVFameGetPatternValue(address, 8); } else { mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load8: 0x%08X", address); @@ -802,14 +813,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { mLOG(GBA_MEM, STUB, "Unimplemented memory Store32: 0x%08X", address); #define STORE_SRAM \ - if (address & 0x3) { \ - mLOG(GBA_MEM, GAME_ERROR, "Unaligned SRAM Store32: 0x%08X", address); \ - } else { \ - GBAStore8(cpu, address, value, cycleCounter); \ - GBAStore8(cpu, address | 1, value, cycleCounter); \ - GBAStore8(cpu, address | 2, value, cycleCounter); \ - GBAStore8(cpu, address | 3, value, cycleCounter); \ - } + GBAStore8(cpu, address, value >> (8 * (address & 3)), cycleCounter); #define STORE_BAD \ mLOG(GBA_MEM, GAME_ERROR, "Bad memory Store32: 0x%08X", address); @@ -921,7 +925,7 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle break; case GBA_REGION_ROM0: if (IS_GPIO_REGISTER(address & 0xFFFFFE)) { - if (memory->hw.devices == HW_NONE) { + if (!(memory->hw.devices & HW_GPIO)) { mLOG(GBA_HW, WARN, "Write to GPIO address %08X on cartridge without GPIO", address); break; } @@ -969,6 +973,10 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle break; } } + if (memory->unl.type) { + GBAUnlCartWriteROM(gba, address & (GBA_SIZE_ROM0 - 1), value); + break; + } mLOG(GBA_MEM, GAME_ERROR, "Bad cartridge Store16: 0x%08X", address); break; case GBA_REGION_ROM2_EX: @@ -989,10 +997,9 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle case GBA_REGION_SRAM_MIRROR: if (address & 1) { mLOG(GBA_MEM, GAME_ERROR, "Unaligned SRAM Store16: 0x%08X", address); - break; + value >>= 8; } GBAStore8(cpu, address, value, cycleCounter); - GBAStore8(cpu, address | 1, value, cycleCounter); break; default: mLOG(GBA_MEM, GAME_ERROR, "Bad memory Store16: 0x%08X", address); @@ -1064,8 +1071,8 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo } else if (memory->savedata.type == GBA_SAVEDATA_FLASH512 || memory->savedata.type == GBA_SAVEDATA_FLASH1M) { GBASavedataWriteFlash(&memory->savedata, address, value); } else if (memory->savedata.type == GBA_SAVEDATA_SRAM) { - if (memory->vfame.cartType) { - GBAVFameSramWrite(&memory->vfame, address, value, memory->savedata.data); + if (memory->unl.type) { + GBAUnlCartWriteSRAM(gba, address & 0xFFFF, value); } else { memory->savedata.data[address & (GBA_SIZE_SRAM - 1)] = value; } @@ -1734,6 +1741,10 @@ void GBAAdjustWaitstates(struct GBA* gba, uint16_t parameters) { STORE_32(memory->agbPrintFuncBackup, AGB_PRINT_FLUSH_ADDR | base, memory->rom); } } + + if (gba->performingDMA) { + GBADMARecalculateCycles(gba); + } } void GBAAdjustEWRAMWaitstates(struct GBA* gba, uint16_t parameters) { @@ -1894,8 +1905,6 @@ void _pristineCow(struct GBA* gba) { } if (gba->romVf) { gba->romVf->unmap(gba->romVf, gba->memory.rom, gba->memory.romSize); - gba->romVf->close(gba->romVf); - gba->romVf = NULL; } gba->memory.rom = newRom; gba->memory.hw.gpioBase = &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]; diff --git a/src/gba/overrides.c b/src/gba/overrides.c index a63616e61..1e3e9e6fc 100644 --- a/src/gba/overrides.c +++ b/src/gba/overrides.c @@ -377,6 +377,12 @@ void GBAOverrideApplyDefaults(struct GBA* gba, const struct Configuration* overr struct GBACartridgeOverride override = { .idleLoop = GBA_IDLE_LOOP_NONE }; const struct GBACartridge* cart = (const struct GBACartridge*) gba->memory.rom; if (cart) { + if (gba->memory.unl.type == GBA_UNL_CART_MULTICART) { + override.savetype = GBA_SAVEDATA_SRAM; + GBAOverrideApply(gba, &override); + return; + } + memcpy(override.id, &cart->id, sizeof(override.id)); static const uint32_t pokemonTable[] = { diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 24fb092c7..dbff67e4e 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -539,7 +539,7 @@ static const char* const _renderWindow = "}\n" "bool test(vec3 circle, vec4 top, vec4 bottom) {\n" - " if (circle.z > 0) {\n" + " if (circle.z > 0.) {\n" " return distance(circle.xy, texCoord.xy) <= circle.z;\n" " }\n" " return crop(interpolate(top, bottom));\n" diff --git a/src/gba/savedata.c b/src/gba/savedata.c index 11be3e0ab..c0d2c889a 100644 --- a/src/gba/savedata.c +++ b/src/gba/savedata.c @@ -45,7 +45,7 @@ static void _ashesToAshes(struct mTiming* timing, void* user, uint32_t cyclesLat void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf) { savedata->type = GBA_SAVEDATA_AUTODETECT; - savedata->data = 0; + savedata->data = NULL; savedata->command = EEPROM_COMMAND_NULL; savedata->flashState = FLASH_STATE_RAW; savedata->vf = vf; @@ -63,6 +63,11 @@ void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf) { savedata->dust.callback = _ashesToAshes; } +void GBASavedataReset(struct GBASavedata* savedata) { + savedata->command = EEPROM_COMMAND_NULL; + savedata->flashState = FLASH_STATE_RAW; +} + void GBASavedataDeinit(struct GBASavedata* savedata) { if (savedata->vf) { size_t size = GBASavedataSize(savedata); @@ -376,7 +381,10 @@ uint8_t GBASavedataReadFlash(struct GBASavedata* savedata, uint16_t address) { } } if (mTimingIsScheduled(savedata->timing, &savedata->dust) && (address >> 12) == savedata->settling) { - return 0x5F; + // This should read Q7 XOR data bit 7 (data# polling), Q6 flipping + // every read (toggle bit), and /Q5 (error bit cleared), but implementing + // just data# polling is sufficient for games to figure it out + return (savedata->currentBank[address] ^ 0x80) & 0x80; } return savedata->currentBank[address]; } diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 1669eca3d..3866c0c80 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -32,8 +32,17 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) { STORE_64LE(gba->timing.globalCycles, 0, &state->globalCycles); if (gba->memory.rom) { - state->id = ((struct GBACartridge*) gba->memory.rom)->id; - memcpy(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)); + switch (gba->memory.unl.type) { + case GBA_UNL_CART_NONE: + case GBA_UNL_CART_VFAME: + state->id = ((struct GBACartridge*) gba->memory.rom)->id; + memcpy(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)); + break; + case GBA_UNL_CART_MULTICART: + state->id = ((struct GBACartridge*) gba->memory.unl.multi.rom)->id; + memcpy(state->title, ((struct GBACartridge*) gba->memory.unl.multi.rom)->title, sizeof(state->title)); + break; + } } else { state->id = 0; memset(state->title, 0, sizeof(state->title)); @@ -74,6 +83,7 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) { GBAMemorySerialize(&gba->memory, state); GBAIOSerialize(gba, state); + GBAUnlCartSerialize(gba, state); GBAVideoSerialize(&gba->video, state); GBAAudioSerialize(&gba->audio, state); GBASavedataSerialize(&gba->memory.savedata, state); @@ -106,9 +116,21 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { error = true; } } - if (gba->memory.rom && (state->id != ((struct GBACartridge*) gba->memory.rom)->id || memcmp(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)))) { - mLOG(GBA_STATE, WARN, "Savestate is for a different game"); - error = true; + if (gba->memory.rom) { + struct GBACartridge* cart; + switch (gba->memory.unl.type) { + case GBA_UNL_CART_NONE: + case GBA_UNL_CART_VFAME: + cart = (struct GBACartridge*) gba->memory.rom; + break; + case GBA_UNL_CART_MULTICART: + cart = (struct GBACartridge*) gba->memory.unl.multi.rom; + break; + } + if (state->id != cart->id || memcmp(state->title, cart->title, sizeof(state->title))) { + mLOG(GBA_STATE, WARN, "Savestate is for a different game"); + error = true; + } } else if (!gba->memory.rom && state->id != 0) { mLOG(GBA_STATE, WARN, "Savestate is for a game, but no game loaded"); error = true; @@ -159,6 +181,9 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { mLOG(GBA_STATE, WARN, "Savestate has unaligned PC and is probably corrupted"); gba->cpu->gprs[ARM_PC] &= ~1; } + + // Since this can remap the ROM, we need to do this before we reset the pipeline + GBAUnlCartDeserialize(gba, state); gba->memory.activeRegion = -1; gba->cpu->memory.setActiveRegion(gba->cpu, gba->cpu->gprs[ARM_PC]); if (state->biosPrefetch) { @@ -195,7 +220,7 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { if (GBASerializedMiscFlagsIsIrqPending(miscFlags)) { int32_t when; LOAD_32(when, 0, &state->nextIrq); - mTimingSchedule(&gba->timing, &gba->irqEvent, when); + mTimingSchedule(&gba->timing, &gba->irqEvent, when); } gba->cpuBlocked = GBASerializedMiscFlagsGetBlocked(miscFlags); gba->keysLast = GBASerializedMiscFlagsGetKeyIRQKeys(miscFlags); diff --git a/src/gba/sio.c b/src/gba/sio.c index 4ffeb6388..22b79675e 100644 --- a/src/gba/sio.c +++ b/src/gba/sio.c @@ -11,26 +11,14 @@ mLOG_DEFINE_CATEGORY(GBA_SIO, "GBA Serial I/O", "gba.sio"); -const int GBASIOCyclesPerTransfer[4][MAX_GBAS] = { +static const int GBASIOCyclesPerTransfer[4][MAX_GBAS] = { { 31976, 63427, 94884, 125829 }, { 8378, 16241, 24104, 31457 }, { 5750, 10998, 16241, 20972 }, { 3140, 5755, 8376, 10486 } }; -static struct GBASIODriver* _lookupDriver(struct GBASIO* sio, enum GBASIOMode mode) { - switch (mode) { - case GBA_SIO_NORMAL_8: - case GBA_SIO_NORMAL_32: - return sio->drivers.normal; - case GBA_SIO_MULTI: - return sio->drivers.multiplayer; - case GBA_SIO_JOYBUS: - return sio->drivers.joybus; - default: - return 0; - } -} +static void _sioFinish(struct mTiming* timing, void* user, uint32_t cyclesLate); static const char* _modeName(enum GBASIOMode mode) { switch (mode) { @@ -58,25 +46,36 @@ static void _switchMode(struct GBASIO* sio) { newMode = (enum GBASIOMode) (mode & 0xC); } if (newMode != sio->mode) { - if (sio->activeDriver && sio->activeDriver->unload) { - sio->activeDriver->unload(sio->activeDriver); - } if (sio->mode != (enum GBASIOMode) -1) { mLOG(GBA_SIO, DEBUG, "Switching mode from %s to %s", _modeName(sio->mode), _modeName(newMode)); } sio->mode = newMode; - sio->activeDriver = _lookupDriver(sio, sio->mode); - if (sio->activeDriver && sio->activeDriver->load) { - sio->activeDriver->load(sio->activeDriver); + if (sio->driver && sio->driver->setMode) { + sio->driver->setMode(sio->driver, newMode); + } + + int id = 0; + switch (newMode) { + case GBA_SIO_MULTI: + if (sio->driver && sio->driver->deviceId) { + id = sio->driver->deviceId(sio->driver); + } + sio->rcnt = GBASIORegisterRCNTSetSi(sio->rcnt, !!id); + break; + default: + // TODO + break; } } } void GBASIOInit(struct GBASIO* sio) { - sio->drivers.normal = 0; - sio->drivers.multiplayer = 0; - sio->drivers.joybus = 0; - sio->activeDriver = 0; + sio->driver = NULL; + + sio->completeEvent.context = sio; + sio->completeEvent.name = "GBA SIO Complete"; + sio->completeEvent.callback = _sioFinish; + sio->completeEvent.priority = 0x80; sio->gbp.p = sio->p; GBASIOPlayerInit(&sio->gbp); @@ -85,64 +84,28 @@ void GBASIOInit(struct GBASIO* sio) { } void GBASIODeinit(struct GBASIO* sio) { - if (sio->activeDriver && sio->activeDriver->unload) { - sio->activeDriver->unload(sio->activeDriver); - } - if (sio->drivers.multiplayer && sio->drivers.multiplayer->deinit) { - sio->drivers.multiplayer->deinit(sio->drivers.multiplayer); - } - if (sio->drivers.joybus && sio->drivers.joybus->deinit) { - sio->drivers.joybus->deinit(sio->drivers.joybus); - } - if (sio->drivers.normal && sio->drivers.normal->deinit) { - sio->drivers.normal->deinit(sio->drivers.normal); + if (sio->driver && sio->driver->deinit) { + sio->driver->deinit(sio->driver); } } void GBASIOReset(struct GBASIO* sio) { - if (sio->activeDriver && sio->activeDriver->unload) { - sio->activeDriver->unload(sio->activeDriver); + if (sio->driver && sio->driver->reset) { + sio->driver->reset(sio->driver); } sio->rcnt = RCNT_INITIAL; sio->siocnt = 0; sio->mode = -1; - sio->activeDriver = NULL; _switchMode(sio); GBASIOPlayerReset(&sio->gbp); } -void GBASIOSetDriverSet(struct GBASIO* sio, struct GBASIODriverSet* drivers) { - GBASIOSetDriver(sio, drivers->normal, GBA_SIO_NORMAL_8); - GBASIOSetDriver(sio, drivers->multiplayer, GBA_SIO_MULTI); - GBASIOSetDriver(sio, drivers->joybus, GBA_SIO_JOYBUS); -} - -void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver, enum GBASIOMode mode) { - struct GBASIODriver** driverLoc; - switch (mode) { - case GBA_SIO_NORMAL_8: - case GBA_SIO_NORMAL_32: - driverLoc = &sio->drivers.normal; - break; - case GBA_SIO_MULTI: - driverLoc = &sio->drivers.multiplayer; - break; - case GBA_SIO_JOYBUS: - driverLoc = &sio->drivers.joybus; - break; - default: - mLOG(GBA_SIO, ERROR, "Setting an unsupported SIO driver: %x", mode); - return; - } - if (*driverLoc) { - if ((*driverLoc)->unload) { - (*driverLoc)->unload(*driverLoc); - } - if ((*driverLoc)->deinit) { - (*driverLoc)->deinit(*driverLoc); - } +void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver) { + if (sio->driver && sio->driver->deinit) { + sio->driver->deinit(sio->driver); } + sio->driver = driver; if (driver) { driver->p = sio; @@ -154,48 +117,118 @@ void GBASIOSetDriver(struct GBASIO* sio, struct GBASIODriver* driver, enum GBASI } } } - if (sio->activeDriver == *driverLoc) { - sio->activeDriver = driver; - if (driver && driver->load) { - driver->load(driver); - } - } - *driverLoc = driver; } void GBASIOWriteRCNT(struct GBASIO* sio, uint16_t value) { - sio->rcnt &= 0xF; - sio->rcnt |= value & ~0xF; + sio->rcnt &= 0x1FF; + sio->rcnt |= value & 0xC000; _switchMode(sio); - if (sio->activeDriver && sio->activeDriver->writeRegister) { - sio->activeDriver->writeRegister(sio->activeDriver, GBA_REG_RCNT, value); + if (sio->driver && sio->driver->writeRCNT) { + switch (sio->mode) { + case GBA_SIO_GPIO: + sio->rcnt = (sio->driver->writeRCNT(sio->driver, value) & 0x01FF) | (sio->rcnt & 0xC000); + break; + default: + sio->rcnt = (sio->driver->writeRCNT(sio->driver, value) & 0x01F0) | (sio->rcnt & 0xC00F); + } + } else if (sio->mode == GBA_SIO_GPIO) { + sio->rcnt &= 0xC000; + sio->rcnt |= value & 0x1FF; + } else { + sio->rcnt &= 0xC00F; + sio->rcnt |= value & 0x1F0; } } +static void _startTransfer(struct GBASIO* sio) { + if (sio->driver && sio->driver->start) { + if (!sio->driver->start(sio->driver)) { + // Transfer completion is handled internally to the driver + return; + } + } + int connected = 0; + if (sio->driver && sio->driver->connectedDevices) { + connected = sio->driver->connectedDevices(sio->driver); + } + mTimingDeschedule(&sio->p->timing, &sio->completeEvent); + mTimingSchedule(&sio->p->timing, &sio->completeEvent, GBASIOTransferCycles(sio->mode, sio->siocnt, connected)); +} + void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) { if ((value ^ sio->siocnt) & 0x3000) { sio->siocnt = value & 0x3000; _switchMode(sio); } - if (sio->activeDriver && sio->activeDriver->writeRegister) { - value = sio->activeDriver->writeRegister(sio->activeDriver, GBA_REG_SIOCNT, value); + int id = 0; + int connected = 0; + bool handled = false; + if (sio->driver) { + handled = sio->driver->handlesMode(sio->driver, sio->mode); + if (handled) { + if (sio->driver->deviceId) { + id = sio->driver->deviceId(sio->driver); + } + connected = sio->driver->connectedDevices(sio->driver); + handled = !!sio->driver->writeSIOCNT; + } + } + + switch (sio->mode) { + case GBA_SIO_MULTI: + value &= 0xFF83; + value = GBASIOMultiplayerSetSlave(value, id || !connected); + value = GBASIOMultiplayerSetId(value, id); + value |= sio->siocnt & 0x00FC; + + // SC appears to float in multi mode when not doing a transfer. While + // it does spike at the end of a transfer, it appears to die down after + // around 20-30 microseconds. However, the docs on akkit.org + // (http://www.akkit.org/info/gba_comms.html) say this is high until + // a transfer starts and low while active. Further, the Mario Bros. + // multiplayer expects SC to be high in multi mode. This needs better + // investigation than I managed, apparently. + sio->rcnt = GBASIORegisterRCNTFillSc(sio->rcnt); + + if (GBASIOMultiplayerIsBusy(value) && !GBASIOMultiplayerIsBusy(sio->siocnt)) { + if (!id) { + sio->p->memory.io[GBA_REG(SIOMULTI0)] = 0xFFFF; + sio->p->memory.io[GBA_REG(SIOMULTI1)] = 0xFFFF; + sio->p->memory.io[GBA_REG(SIOMULTI2)] = 0xFFFF; + sio->p->memory.io[GBA_REG(SIOMULTI3)] = 0xFFFF; + sio->rcnt = GBASIORegisterRCNTClearSc(sio->rcnt); + _startTransfer(sio); + } else { + // TODO + } + } + break; + case GBA_SIO_NORMAL_8: + case GBA_SIO_NORMAL_32: + // This line is pulled up by the clock owner while the clock is idle. + // If there is no clock owner it's just hi-Z. + if (GBASIONormalGetSc(value)) { + sio->rcnt = GBASIORegisterRCNTFillSc(sio->rcnt); + } + if (GBASIONormalIsStart(value) && !GBASIONormalIsStart(sio->siocnt)) { + _startTransfer(sio); + } + break; + default: + // TODO + break; + } + if (handled) { + value = sio->driver->writeSIOCNT(sio->driver, value); } else { // Dummy drivers switch (sio->mode) { case GBA_SIO_NORMAL_8: case GBA_SIO_NORMAL_32: value = GBASIONormalFillSi(value); - if ((value & 0x0081) == 0x0081) { - if (GBASIONormalIsIrq(value)) { - // TODO: Test this on hardware to see if this is correct - GBARaiseIRQ(sio->p, GBA_IRQ_SIO, 0); - } - value = GBASIONormalClearStart(value); - } break; case GBA_SIO_MULTI: - value &= 0xFF83; - value |= 0xC; + value = GBASIOMultiplayerFillReady(value); break; default: // TODO @@ -206,22 +239,252 @@ void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) { } uint16_t GBASIOWriteRegister(struct GBASIO* sio, uint32_t address, uint16_t value) { - if (sio->activeDriver && sio->activeDriver->writeRegister) { - return sio->activeDriver->writeRegister(sio->activeDriver, address, value); + int id = 0; + if (sio->driver && sio->driver->deviceId) { + id = sio->driver->deviceId(sio->driver); } - // Dummy drivers + + bool handled = true; switch (sio->mode) { case GBA_SIO_JOYBUS: switch (address) { + case GBA_REG_SIODATA8: + mLOG(GBA_SIO, DEBUG, "JOY write: SIODATA8 (?) <- %04X", value); + break; case GBA_REG_JOYCNT: - return (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040); + mLOG(GBA_SIO, DEBUG, "JOY write: CNT <- %04X", value); + value = (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040); + break; case GBA_REG_JOYSTAT: - return (value & 0x0030) | (sio->p->memory.io[GBA_REG(JOYSTAT)] & ~0x30); + mLOG(GBA_SIO, DEBUG, "JOY write: STAT <- %04X", value); + value = (value & 0x0030) | (sio->p->memory.io[GBA_REG(JOYSTAT)] & ~0x30); + break; + case GBA_REG_JOY_TRANS_LO: + mLOG(GBA_SIO, DEBUG, "JOY write: TRANS_LO <- %04X", value); + break; + case GBA_REG_JOY_TRANS_HI: + mLOG(GBA_SIO, DEBUG, "JOY write: TRANS_HI <- %04X", value); + break; + default: + mLOG(GBA_SIO, GAME_ERROR, "JOY write: Unhandled %s <- %04X", GBAIORegisterNames[address >> 1], value); + handled = false; + break; } break; - default: - // TODO + case GBA_SIO_NORMAL_8: + switch (address) { + case GBA_REG_SIODATA8: + mLOG(GBA_SIO, DEBUG, "NORMAL8 %i write: SIODATA8 <- %04X", id, value); + break; + case GBA_REG_JOYCNT: + mLOG(GBA_SIO, DEBUG, "NORMAL8 %i write: JOYCNT (?) <- %04X", id, value); + value = (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040); + break; + default: + mLOG(GBA_SIO, GAME_ERROR, "NORMAL8 %i write: Unhandled %s <- %04X", id, GBAIORegisterNames[address >> 1], value); + handled = false; + break; + } break; + case GBA_SIO_NORMAL_32: + switch (address) { + case GBA_REG_SIODATA32_LO: + mLOG(GBA_SIO, DEBUG, "NORMAL32 %i write: SIODATA32_LO <- %04X", id, value); + break; + case GBA_REG_SIODATA32_HI: + mLOG(GBA_SIO, DEBUG, "NORMAL32 %i write: SIODATA32_HI <- %04X", id, value); + break; + case GBA_REG_SIODATA8: + mLOG(GBA_SIO, DEBUG, "NORMAL32 %i write: SIODATA8 (?) <- %04X", id, value); + break; + case GBA_REG_JOYCNT: + mLOG(GBA_SIO, DEBUG, "NORMAL32 %i write: JOYCNT (?) <- %04X", id, value); + value = (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040); + break; + default: + mLOG(GBA_SIO, GAME_ERROR, "NORMAL32 %i write: Unhandled %s <- %04X", id, GBAIORegisterNames[address >> 1], value); + handled = false; + break; + } + break; + case GBA_SIO_MULTI: + switch (address) { + case GBA_REG_SIOMLT_SEND: + mLOG(GBA_SIO, DEBUG, "MULTI %i write: SIOMLT_SEND <- %04X", id, value); + break; + case GBA_REG_JOYCNT: + mLOG(GBA_SIO, DEBUG, "MULTI %i write: JOYCNT (?) <- %04X", id, value); + value = (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040); + break; + default: + mLOG(GBA_SIO, GAME_ERROR, "MULTI %i write: Unhandled %s <- %04X", id, GBAIORegisterNames[address >> 1], value); + handled = false; + break; + } + break; + case GBA_SIO_UART: + switch (address) { + case GBA_REG_SIODATA8: + mLOG(GBA_SIO, DEBUG, "UART write: SIODATA8 <- %04X", value); + break; + case GBA_REG_JOYCNT: + mLOG(GBA_SIO, DEBUG, "UART write: JOYCNT (?) <- %04X", value); + value = (value & 0x0040) | (sio->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040); + break; + default: + mLOG(GBA_SIO, GAME_ERROR, "UART write: Unhandled %s <- %04X", GBAIORegisterNames[address >> 1], value); + handled = false; + break; + } + break; + case GBA_SIO_GPIO: + mLOG(GBA_SIO, STUB, "GPIO write: Unhandled %s <- %04X", GBAIORegisterNames[address >> 1], value); + handled = false; + break; + } + if (!handled) { + value = sio->p->memory.io[address >> 1]; } return value; } + +int32_t GBASIOTransferCycles(enum GBASIOMode mode, uint16_t siocnt, int connected) { + if (connected < 0 || connected >= MAX_GBAS) { + mLOG(GBA_SIO, ERROR, "Invalid device count %i", connected); + return 0; + } + + switch (mode) { + case GBA_SIO_MULTI: + return GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(siocnt)][connected]; + case GBA_SIO_NORMAL_8: + return 8 * GBA_ARM7TDMI_FREQUENCY / ((GBASIONormalIsInternalSc(siocnt) ? 2048 : 256) * 1024); + case GBA_SIO_NORMAL_32: + return 32 * GBA_ARM7TDMI_FREQUENCY / ((GBASIONormalIsInternalSc(siocnt) ? 2048 : 256) * 1024); + default: + mLOG(GBA_SIO, STUB, "No cycle count implemented for mode %s", _modeName(mode)); + break; + } + return 0; +} + +void GBASIOMultiplayerFinishTransfer(struct GBASIO* sio, uint16_t data[4], uint32_t cyclesLate) { + int id = 0; + if (sio->driver && sio->driver->deviceId) { + id = sio->driver->deviceId(sio->driver); + } + sio->p->memory.io[GBA_REG(SIOMULTI0)] = data[0]; + sio->p->memory.io[GBA_REG(SIOMULTI1)] = data[1]; + sio->p->memory.io[GBA_REG(SIOMULTI2)] = data[2]; + sio->p->memory.io[GBA_REG(SIOMULTI3)] = data[3]; + + sio->siocnt = GBASIOMultiplayerClearBusy(sio->siocnt); + sio->siocnt = GBASIOMultiplayerSetId(sio->siocnt, id); + + sio->rcnt = GBASIORegisterRCNTFillSc(sio->rcnt); + + if (GBASIOMultiplayerIsIrq(sio->siocnt)) { + GBARaiseIRQ(sio->p, GBA_IRQ_SIO, cyclesLate); + } +} + +void GBASIONormal8FinishTransfer(struct GBASIO* sio, uint8_t data, uint32_t cyclesLate) { + sio->siocnt = GBASIONormalClearStart(sio->siocnt); + sio->p->memory.io[GBA_REG(SIODATA8)] = data; + if (GBASIONormalIsIrq(sio->siocnt)) { + GBARaiseIRQ(sio->p, GBA_IRQ_SIO, cyclesLate); + } +} + +void GBASIONormal32FinishTransfer(struct GBASIO* sio, uint32_t data, uint32_t cyclesLate) { + sio->siocnt = GBASIONormalClearStart(sio->siocnt); + sio->p->memory.io[GBA_REG(SIODATA32_LO)] = data; + sio->p->memory.io[GBA_REG(SIODATA32_HI)] = data >> 16; + if (GBASIONormalIsIrq(sio->siocnt)) { + GBARaiseIRQ(sio->p, GBA_IRQ_SIO, cyclesLate); + } +} + +static void _sioFinish(struct mTiming* timing, void* user, uint32_t cyclesLate) { + UNUSED(timing); + struct GBASIO* sio = user; + union { + uint16_t multi[4]; + uint8_t normal8; + uint32_t normal32; + } data = {0}; + switch (sio->mode) { + case GBA_SIO_MULTI: + if (sio->driver && sio->driver->finishMultiplayer) { + sio->driver->finishMultiplayer(sio->driver, data.multi); + } + GBASIOMultiplayerFinishTransfer(sio, data.multi, cyclesLate); + break; + case GBA_SIO_NORMAL_8: + if (sio->driver && sio->driver->finishNormal8) { + data.normal8 = sio->driver->finishNormal8(sio->driver); + } + GBASIONormal8FinishTransfer(sio, data.normal8, cyclesLate); + break; + case GBA_SIO_NORMAL_32: + if (sio->driver && sio->driver->finishNormal32) { + data.normal32 = sio->driver->finishNormal32(sio->driver); + } + GBASIONormal32FinishTransfer(sio, data.normal32, cyclesLate); + break; + default: + // TODO + mLOG(GBA_SIO, STUB, "No dummy finish implemented for mode %s", _modeName(sio->mode)); + break; + } +} + +int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data) { + switch (command) { + case JOY_RESET: + sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_RESET; + if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) { + GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0); + } + // Fall through + case JOY_POLL: + data[0] = 0x00; + data[1] = 0x04; + data[2] = sio->p->p->memory.io[GBA_REG(JOYSTAT)]; + + mLOG(GBA_SIO, DEBUG, "JOY %s: %02X (%02X)", command == JOY_POLL ? "poll" : "reset", data[2], sio->p->p->memory.io[GBA_REG(JOYCNT)]); + return 3; + case JOY_RECV: + sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_RECV; + sio->p->p->memory.io[GBA_REG(JOYSTAT)] |= JOYSTAT_RECV; + + sio->p->p->memory.io[GBA_REG(JOY_RECV_LO)] = data[0] | (data[1] << 8); + sio->p->p->memory.io[GBA_REG(JOY_RECV_HI)] = data[2] | (data[3] << 8); + + data[0] = sio->p->p->memory.io[GBA_REG(JOYSTAT)]; + + mLOG(GBA_SIO, DEBUG, "JOY recv: %02X (%02X)", data[0], sio->p->p->memory.io[GBA_REG(JOYCNT)]); + + if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) { + GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0); + } + return 1; + case JOY_TRANS: + data[0] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_LO)]; + data[1] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_LO)] >> 8; + data[2] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_HI)]; + data[3] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_HI)] >> 8; + data[4] = sio->p->p->memory.io[GBA_REG(JOYSTAT)]; + + sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_TRANS; + sio->p->p->memory.io[GBA_REG(JOYSTAT)] &= ~JOYSTAT_TRANS; + + mLOG(GBA_SIO, DEBUG, "JOY trans: %02X%02X%02X%02X:%02X (%02X)", data[0], data[1], data[2], data[3], data[4], sio->p->p->memory.io[GBA_REG(JOYCNT)]); + + if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) { + GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0); + } + return 5; + } + return 0; +} diff --git a/src/gba/sio/dolphin.c b/src/gba/sio/dolphin.c index 896d17d3e..0bb783e76 100644 --- a/src/gba/sio/dolphin.c +++ b/src/gba/sio/dolphin.c @@ -23,18 +23,22 @@ enum { }; static bool GBASIODolphinInit(struct GBASIODriver* driver); -static bool GBASIODolphinLoad(struct GBASIODriver* driver); -static bool GBASIODolphinUnload(struct GBASIODriver* driver); +static void GBASIODolphinReset(struct GBASIODriver* driver); +static void GBASIODolphinSetMode(struct GBASIODriver* driver, enum GBASIOMode mode); +static bool GBASIODolphinHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode); +static int GBASIODolphinConnectedDevices(struct GBASIODriver* driver); static void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate); static int32_t _processCommand(struct GBASIODolphin* dol, uint32_t cyclesLate); static void _flush(struct GBASIODolphin* dol); void GBASIODolphinCreate(struct GBASIODolphin* dol) { - GBASIOJOYCreate(&dol->d); + memset(&dol->d, 0, sizeof(dol->d)); dol->d.init = GBASIODolphinInit; - dol->d.load = GBASIODolphinLoad; - dol->d.unload = GBASIODolphinUnload; + dol->d.reset = GBASIODolphinReset; + dol->d.setMode = GBASIODolphinSetMode; + dol->d.handlesMode = GBASIODolphinHandlesMode; + dol->d.connectedDevices = GBASIODolphinConnectedDevices; dol->event.context = dol; dol->event.name = "GB SIO Lockstep"; dol->event.callback = GBASIODolphinProcessEvents; @@ -94,26 +98,33 @@ bool GBASIODolphinConnect(struct GBASIODolphin* dol, const struct Address* addre static bool GBASIODolphinInit(struct GBASIODriver* driver) { struct GBASIODolphin* dol = (struct GBASIODolphin*) driver; - dol->active = false; dol->clockSlice = 0; dol->state = WAIT_FOR_FIRST_CLOCK; - _flush(dol); + GBASIODolphinReset(driver); return true; } -static bool GBASIODolphinLoad(struct GBASIODriver* driver) { +static void GBASIODolphinReset(struct GBASIODriver* driver) { struct GBASIODolphin* dol = (struct GBASIODolphin*) driver; - dol->active = true; + dol->active = false; _flush(dol); mTimingDeschedule(&dol->d.p->p->timing, &dol->event); mTimingSchedule(&dol->d.p->p->timing, &dol->event, 0); - return true; } -static bool GBASIODolphinUnload(struct GBASIODriver* driver) { +static void GBASIODolphinSetMode(struct GBASIODriver* driver, enum GBASIOMode mode) { struct GBASIODolphin* dol = (struct GBASIODolphin*) driver; - dol->active = false; - return true; + dol->active = mode == GBA_SIO_JOYBUS; +} + +static bool GBASIODolphinHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode) { + UNUSED(driver); + return mode == GBA_SIO_JOYBUS; +} + +static int GBASIODolphinConnectedDevices(struct GBASIODriver* driver) { + UNUSED(driver); + return 1; } void GBASIODolphinProcessEvents(struct mTiming* timing, void* context, uint32_t cyclesLate) { diff --git a/src/gba/sio/gbp.c b/src/gba/sio/gbp.c index d992ebe2e..977e67de4 100644 --- a/src/gba/sio/gbp.c +++ b/src/gba/sio/gbp.c @@ -13,8 +13,11 @@ #include static uint16_t _gbpRead(struct mKeyCallback*); -static uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value); -static void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate); +static uint16_t _gbpSioWriteSIOCNT(struct GBASIODriver* driver, uint16_t value); +static bool _gbpSioHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode); +static int _gbpSioConnectedDevices(struct GBASIODriver* driver); +static bool _gbpSioStart(struct GBASIODriver* driver); +static uint32_t _gbpSioFinishNormal32(struct GBASIODriver* driver); static const uint8_t _logoPalette[] = { 0xDF, 0xFF, 0x0C, 0x64, 0x0C, 0xE4, 0x2D, 0xE4, 0x4E, 0x64, 0x4E, 0xE4, 0x6E, 0xE4, 0xAF, 0x68, @@ -43,20 +46,17 @@ void GBASIOPlayerInit(struct GBASIOPlayer* gbp) { gbp->callback.d.readKeys = _gbpRead; gbp->callback.d.requireOpposingDirections = true; gbp->callback.p = gbp; - gbp->d.init = 0; - gbp->d.deinit = 0; - gbp->d.load = 0; - gbp->d.unload = 0; - gbp->d.writeRegister = _gbpSioWriteRegister; - gbp->event.context = gbp; - gbp->event.name = "GBA SIO Game Boy Player"; - gbp->event.callback = _gbpSioProcessEvents; - gbp->event.priority = 0x80; + memset(&gbp->d, 0, sizeof(gbp->d)); + gbp->d.writeSIOCNT = _gbpSioWriteSIOCNT; + gbp->d.handlesMode = _gbpSioHandlesMode; + gbp->d.connectedDevices = _gbpSioConnectedDevices; + gbp->d.start = _gbpSioStart; + gbp->d.finishNormal32 = _gbpSioFinishNormal32; } void GBASIOPlayerReset(struct GBASIOPlayer* gbp) { - if (gbp->p->sio.drivers.normal == &gbp->d) { - GBASIOSetDriver(&gbp->p->sio, NULL, GBA_SIO_NORMAL_32); + if (gbp->p->sio.driver == &gbp->d) { + GBASIOSetDriver(&gbp->p->sio, NULL); } } @@ -87,8 +87,9 @@ void GBASIOPlayerUpdate(struct GBA* gba) { gba->sio.gbp.inputsPosted = 0; gba->sio.gbp.oldCallback = gba->keyCallback; gba->keyCallback = &gba->sio.gbp.callback.d; - // TODO: Check if the SIO driver is actually used first - GBASIOSetDriver(&gba->sio, &gba->sio.gbp.d, GBA_SIO_NORMAL_32); + if (!gba->sio.driver) { + GBASIOSetDriver(&gba->sio, &gba->sio.gbp.d); + } } } @@ -100,35 +101,41 @@ uint16_t _gbpRead(struct mKeyCallback* callback) { return 0; } -uint16_t _gbpSioWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) { - struct GBASIOPlayer* gbp = (struct GBASIOPlayer*) driver; - if (address == GBA_REG_SIOCNT) { - if (value & 0x0080) { - uint32_t rx = gbp->p->memory.io[GBA_REG(SIODATA32_LO)] | (gbp->p->memory.io[GBA_REG(SIODATA32_HI)] << 16); - if (gbp->txPosition < 12 && gbp->txPosition > 0) { - // TODO: Check expected - } else if (gbp->txPosition >= 12) { - // 0x00 = Stop - // 0x11 = Hard Stop - // 0x22 = Start - if (gbp->p->rumble) { - int32_t currentTime = mTimingCurrentTime(&gbp->p->timing); - gbp->p->rumble->setRumble(gbp->p->rumble, (rx & 0x33) == 0x22, currentTime - gbp->p->lastRumble); - gbp->p->lastRumble = currentTime; - } - } - mTimingDeschedule(&gbp->p->timing, &gbp->event); - mTimingSchedule(&gbp->p->timing, &gbp->event, 2048); - } - value &= 0x78FB; - } - return value; +uint16_t _gbpSioWriteSIOCNT(struct GBASIODriver* driver, uint16_t value) { + UNUSED(driver); + return value & 0x78FB; } -void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) { - UNUSED(timing); - UNUSED(cyclesLate); - struct GBASIOPlayer* gbp = user; +bool _gbpSioStart(struct GBASIODriver* driver) { + struct GBASIOPlayer* gbp = (struct GBASIOPlayer*) driver; + uint32_t rx = gbp->p->memory.io[GBA_REG(SIODATA32_LO)] | (gbp->p->memory.io[GBA_REG(SIODATA32_HI)] << 16); + if (gbp->txPosition < 12 && gbp->txPosition > 0) { + // TODO: Check expected + } else if (gbp->txPosition >= 12) { + // 0x00 = Stop + // 0x11 = Hard Stop + // 0x22 = Start + if (gbp->p->rumble) { + int32_t currentTime = mTimingCurrentTime(&gbp->p->timing); + gbp->p->rumble->setRumble(gbp->p->rumble, (rx & 0x33) == 0x22, currentTime - gbp->p->lastRumble); + gbp->p->lastRumble = currentTime; + } + } + return true; +} + +static bool _gbpSioHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode) { + UNUSED(driver); + return mode == GBA_SIO_NORMAL_32; +} + +static int _gbpSioConnectedDevices(struct GBASIODriver* driver) { + UNUSED(driver); + return 1; +} + +uint32_t _gbpSioFinishNormal32(struct GBASIODriver* driver) { + struct GBASIOPlayer* gbp = (struct GBASIOPlayer*) driver; uint32_t tx = 0; int txPosition = gbp->txPosition; if (txPosition > 16) { @@ -139,11 +146,5 @@ void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLat } tx = _gbpTxData[txPosition]; ++gbp->txPosition; - gbp->p->memory.io[GBA_REG(SIODATA32_LO)] = tx; - gbp->p->memory.io[GBA_REG(SIODATA32_HI)] = tx >> 16; - if (GBASIONormalIsIrq(gbp->d.p->siocnt)) { - GBARaiseIRQ(gbp->p, GBA_IRQ_SIO, cyclesLate); - } - gbp->d.p->siocnt = GBASIONormalClearStart(gbp->d.p->siocnt); - gbp->p->memory.io[GBA_REG(SIOCNT)] = gbp->d.p->siocnt & ~0x0080; + return tx; } diff --git a/src/gba/sio/joybus.c b/src/gba/sio/joybus.c deleted file mode 100644 index 941527439..000000000 --- a/src/gba/sio/joybus.c +++ /dev/null @@ -1,92 +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 - -#include -#include - -static uint16_t GBASIOJOYWriteRegister(struct GBASIODriver* sio, uint32_t address, uint16_t value); - -void GBASIOJOYCreate(struct GBASIODriver* sio) { - sio->init = NULL; - sio->deinit = NULL; - sio->load = NULL; - sio->unload = NULL; - sio->writeRegister = GBASIOJOYWriteRegister; -} - -uint16_t GBASIOJOYWriteRegister(struct GBASIODriver* sio, uint32_t address, uint16_t value) { - switch (address) { - case GBA_REG_JOYCNT: - mLOG(GBA_SIO, DEBUG, "JOY write: CNT <- %04X", value); - return (value & 0x0040) | (sio->p->p->memory.io[GBA_REG(JOYCNT)] & ~(value & 0x7) & ~0x0040); - case GBA_REG_JOYSTAT: - mLOG(GBA_SIO, DEBUG, "JOY write: STAT <- %04X", value); - return (value & 0x0030) | (sio->p->p->memory.io[GBA_REG(JOYSTAT)] & ~0x30); - case GBA_REG_JOY_TRANS_LO: - mLOG(GBA_SIO, DEBUG, "JOY write: TRANS_LO <- %04X", value); - break; - case GBA_REG_JOY_TRANS_HI: - mLOG(GBA_SIO, DEBUG, "JOY write: TRANS_HI <- %04X", value); - break; - default: - mLOG(GBA_SIO, DEBUG, "JOY write: Unknown reg %03X <- %04X", address, value); - // Fall through - case GBA_REG_RCNT: - break; - } - return value; -} - -int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data) { - switch (command) { - case JOY_RESET: - sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_RESET; - if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) { - GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0); - } - // Fall through - case JOY_POLL: - data[0] = 0x00; - data[1] = 0x04; - data[2] = sio->p->p->memory.io[GBA_REG(JOYSTAT)]; - - mLOG(GBA_SIO, DEBUG, "JOY %s: %02X (%02X)", command == JOY_POLL ? "poll" : "reset", data[2], sio->p->p->memory.io[GBA_REG(JOYCNT)]); - return 3; - case JOY_RECV: - sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_RECV; - sio->p->p->memory.io[GBA_REG(JOYSTAT)] |= JOYSTAT_RECV; - - sio->p->p->memory.io[GBA_REG(JOY_RECV_LO)] = data[0] | (data[1] << 8); - sio->p->p->memory.io[GBA_REG(JOY_RECV_HI)] = data[2] | (data[3] << 8); - - data[0] = sio->p->p->memory.io[GBA_REG(JOYSTAT)]; - - mLOG(GBA_SIO, DEBUG, "JOY recv: %02X (%02X)", data[0], sio->p->p->memory.io[GBA_REG(JOYCNT)]); - - if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) { - GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0); - } - return 1; - case JOY_TRANS: - data[0] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_LO)]; - data[1] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_LO)] >> 8; - data[2] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_HI)]; - data[3] = sio->p->p->memory.io[GBA_REG(JOY_TRANS_HI)] >> 8; - data[4] = sio->p->p->memory.io[GBA_REG(JOYSTAT)]; - - sio->p->p->memory.io[GBA_REG(JOYCNT)] |= JOYCNT_TRANS; - sio->p->p->memory.io[GBA_REG(JOYSTAT)] &= ~JOYSTAT_TRANS; - - mLOG(GBA_SIO, DEBUG, "JOY trans: %02X%02X%02X%02X:%02X (%02X)", data[0], data[1], data[2], data[3], data[4], sio->p->p->memory.io[GBA_REG(JOYCNT)]); - - if (sio->p->p->memory.io[GBA_REG(JOYCNT)] & 0x40) { - GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0); - } - return 5; - } - return 0; -} diff --git a/src/gba/sio/lockstep.c b/src/gba/sio/lockstep.c index ba620911e..a429a31e2 100644 --- a/src/gba/sio/lockstep.c +++ b/src/gba/sio/lockstep.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau +/* Copyright (c) 2013-2024 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,566 +8,1056 @@ #include #include -#define LOCKSTEP_INCREMENT 2000 -#define LOCKSTEP_TRANSFER 512 +#define DRIVER_ID 0x6B636F4C +#define DRIVER_STATE_VERSION 1 +#define LOCKSTEP_INTERVAL 4096 +#define UNLOCKED_INTERVAL 4096 +#define HARD_SYNC_INTERVAL 0x80000 +#define TARGET(P) (1 << (P)) +#define TARGET_ALL 0xF +#define TARGET_PRIMARY 0x1 +#define TARGET_SECONDARY ((TARGET_ALL) & ~(TARGET_PRIMARY)) -static bool GBASIOLockstepNodeInit(struct GBASIODriver* driver); -static void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver); -static bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver); -static bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver); -static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value); -static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value); -static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* driver, uint32_t cyclesLate); -static void _finishTransfer(struct GBASIOLockstepNode* node); +DECL_BITFIELD(GBASIOLockstepSerializedFlags, uint32_t); +DECL_BITS(GBASIOLockstepSerializedFlags, DriverMode, 0, 3); +DECL_BITS(GBASIOLockstepSerializedFlags, NumEvents, 3, 4); +DECL_BIT(GBASIOLockstepSerializedFlags, Asleep, 7); +DECL_BIT(GBASIOLockstepSerializedFlags, DataReceived, 8); +DECL_BIT(GBASIOLockstepSerializedFlags, EventScheduled, 9); +DECL_BITS(GBASIOLockstepSerializedFlags, Player0Mode, 10, 3); +DECL_BITS(GBASIOLockstepSerializedFlags, Player1Mode, 13, 3); +DECL_BITS(GBASIOLockstepSerializedFlags, Player2Mode, 16, 3); +DECL_BITS(GBASIOLockstepSerializedFlags, Player3Mode, 19, 3); +DECL_BITS(GBASIOLockstepSerializedFlags, TransferMode, 28, 3); +DECL_BIT(GBASIOLockstepSerializedFlags, TransferActive, 31); -void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) { - lockstep->players[0] = 0; - lockstep->players[1] = 0; - lockstep->players[2] = 0; - lockstep->players[3] = 0; - lockstep->multiRecv[0] = 0xFFFF; - lockstep->multiRecv[1] = 0xFFFF; - lockstep->multiRecv[2] = 0xFFFF; - lockstep->multiRecv[3] = 0xFFFF; - lockstep->attachedMulti = 0; - lockstep->attachedNormal = 0; -} +DECL_BITFIELD(GBASIOLockstepSerializedEventFlags, uint32_t); +DECL_BITS(GBASIOLockstepSerializedEventFlags, Type, 0, 3); -void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) { - node->d.init = GBASIOLockstepNodeInit; - node->d.deinit = GBASIOLockstepNodeDeinit; - node->d.load = GBASIOLockstepNodeLoad; - node->d.unload = GBASIOLockstepNodeUnload; - node->d.writeRegister = 0; -} +struct GBASIOLockstepSerializedEvent { + int32_t timestamp; + int32_t playerId; + GBASIOLockstepSerializedEventFlags flags; + int32_t reserved[5]; + union { + int32_t mode; + int32_t finishCycle; + int32_t padding[4]; + }; +}; +static_assert(sizeof(struct GBASIOLockstepSerializedEvent) == 0x30, "GBA lockstep event savestate struct sized wrong"); -bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) { - if (lockstep->d.attached == MAX_GBAS) { - return false; - } - mLockstepLock(&lockstep->d); - lockstep->players[lockstep->d.attached] = node; - node->p = lockstep; - node->id = lockstep->d.attached; - node->normalSO = true; - node->transferFinished = true; - ++lockstep->d.attached; - mLockstepUnlock(&lockstep->d); - return true; -} +struct GBASIOLockstepSerializedState { + uint32_t version; + GBASIOLockstepSerializedFlags flags; + uint32_t reserved[2]; -void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) { - if (lockstep->d.attached == 0) { - return; - } - mLockstepLock(&lockstep->d); + struct { + int32_t nextEvent; + uint32_t reservedDriver[7]; + } driver; + + struct { + int32_t playerId; + int32_t cycleOffset; + uint32_t reservedPlayer[2]; + struct GBASIOLockstepSerializedEvent events[MAX_LOCKSTEP_EVENTS]; + } player; + + // playerId 0 only + struct { + int32_t cycle; + uint32_t waiting; + int32_t nextHardSync; + uint32_t reservedCoordinator[3]; + uint16_t multiData[4]; + uint32_t normalData[4]; + } coordinator; +}; +static_assert(offsetof(struct GBASIOLockstepSerializedState, driver) == 0x10, "GBA lockstep savestate driver offset wrong"); +static_assert(offsetof(struct GBASIOLockstepSerializedState, player) == 0x30, "GBA lockstep savestate player offset wrong"); +static_assert(offsetof(struct GBASIOLockstepSerializedState, coordinator) == 0x1C0, "GBA lockstep savestate coordinator offset wrong"); +static_assert(sizeof(struct GBASIOLockstepSerializedState) == 0x1F0, "GBA lockstep savestate struct sized wrong"); + +static bool GBASIOLockstepDriverInit(struct GBASIODriver* driver); +static void GBASIOLockstepDriverDeinit(struct GBASIODriver* driver); +static void GBASIOLockstepDriverReset(struct GBASIODriver* driver); +static uint32_t GBASIOLockstepDriverId(const struct GBASIODriver* driver); +static bool GBASIOLockstepDriverLoadState(struct GBASIODriver* driver, const void* state, size_t size); +static void GBASIOLockstepDriverSaveState(struct GBASIODriver* driver, void** state, size_t* size); +static void GBASIOLockstepDriverSetMode(struct GBASIODriver* driver, enum GBASIOMode mode); +static bool GBASIOLockstepDriverHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode); +static int GBASIOLockstepDriverConnectedDevices(struct GBASIODriver* driver); +static int GBASIOLockstepDriverDeviceId(struct GBASIODriver* driver); +static uint16_t GBASIOLockstepDriverWriteSIOCNT(struct GBASIODriver* driver, uint16_t value); +static uint16_t GBASIOLockstepDriverWriteRCNT(struct GBASIODriver* driver, uint16_t value); +static bool GBASIOLockstepDriverStart(struct GBASIODriver* driver); +static void GBASIOLockstepDriverFinishMultiplayer(struct GBASIODriver* driver, uint16_t data[4]); +static uint8_t GBASIOLockstepDriverFinishNormal8(struct GBASIODriver* driver); +static uint32_t GBASIOLockstepDriverFinishNormal32(struct GBASIODriver* driver); + +static void GBASIOLockstepCoordinatorWaitOnPlayers(struct GBASIOLockstepCoordinator*, struct GBASIOLockstepPlayer*); +static void GBASIOLockstepCoordinatorAckPlayer(struct GBASIOLockstepCoordinator*, struct GBASIOLockstepPlayer*); +static void GBASIOLockstepCoordinatorWakePlayers(struct GBASIOLockstepCoordinator*); + +static int32_t GBASIOLockstepTime(struct GBASIOLockstepPlayer*); +static void GBASIOLockstepPlayerWake(struct GBASIOLockstepPlayer*); +static void GBASIOLockstepPlayerSleep(struct GBASIOLockstepPlayer*); + +static void _advanceCycle(struct GBASIOLockstepCoordinator*, struct GBASIOLockstepPlayer*); +static void _removePlayer(struct GBASIOLockstepCoordinator*, struct GBASIOLockstepPlayer*); +static void _reconfigPlayers(struct GBASIOLockstepCoordinator*); +static int32_t _untilNextSync(struct GBASIOLockstepCoordinator*, struct GBASIOLockstepPlayer*); +static void _enqueueEvent(struct GBASIOLockstepCoordinator*, const struct GBASIOLockstepEvent*, uint32_t target); +static void _setData(struct GBASIOLockstepCoordinator*, uint32_t id, struct GBASIO* sio); +static void _setReady(struct GBASIOLockstepCoordinator*, struct GBASIOLockstepPlayer* activePlayer, int playerId, enum GBASIOMode mode); +static void _hardSync(struct GBASIOLockstepCoordinator*, struct GBASIOLockstepPlayer*); + +static void _lockstepEvent(struct mTiming*, void* context, uint32_t cyclesLate); + +static void _verifyAwake(struct GBASIOLockstepCoordinator* coordinator) { +#ifdef NDEBUG + UNUSED(coordinator); +#else int i; - for (i = 0; i < lockstep->d.attached; ++i) { - if (lockstep->players[i] != node) { + int asleep = 0; + for (i = 0; i < coordinator->nAttached; ++i) { + if (!coordinator->attachedPlayers[i]) { continue; } - for (++i; i < lockstep->d.attached; ++i) { - lockstep->players[i - 1] = lockstep->players[i]; - lockstep->players[i - 1]->id = i - 1; - } - --lockstep->d.attached; - lockstep->players[lockstep->d.attached] = NULL; - break; + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, coordinator->attachedPlayers[i]); + asleep += player->asleep; } - mLockstepUnlock(&lockstep->d); -} - -bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) { - struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; - node->d.p->siocnt = GBASIOMultiplayerSetSlave(node->d.p->siocnt, node->id > 0); - mLOG(GBA_SIO, DEBUG, "Lockstep %i: Node init", node->id); - node->event.context = node; - node->event.name = "GBA SIO Lockstep"; - node->event.callback = _GBASIOLockstepNodeProcessEvents; - node->event.priority = 0x80; - return true; -} - -void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver) { - UNUSED(driver); -} - -bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) { - struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; - node->nextEvent = 0; - node->eventDiff = 0; - mTimingSchedule(&driver->p->p->timing, &node->event, 0); - - mLockstepLock(&node->p->d); - - node->mode = driver->p->mode; - - switch (node->mode) { - case GBA_SIO_MULTI: - node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister; - ATOMIC_ADD(node->p->attachedMulti, 1); - node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, node->p->attachedMulti == node->p->d.attached); - if (node->id) { - node->d.p->rcnt |= 4; - node->d.p->siocnt = GBASIOMultiplayerFillSlave(node->d.p->siocnt); - - int try; - for (try = 0; try < 3; ++try) { - uint16_t masterSiocnt; - ATOMIC_LOAD(masterSiocnt, node->p->players[0]->d.p->siocnt); - if (ATOMIC_CMPXCHG(node->p->players[0]->d.p->siocnt, masterSiocnt, GBASIOMultiplayerClearSlave(masterSiocnt))) { - break; - } - } - } else { - node->d.p->rcnt &= ~4; - node->d.p->siocnt = GBASIOMultiplayerClearSlave(node->d.p->siocnt); - } - break; - case GBA_SIO_NORMAL_8: - case GBA_SIO_NORMAL_32: - if (ATOMIC_ADD(node->p->attachedNormal, 1) > node->id + 1 && node->id > 0) { - node->d.p->siocnt = GBASIONormalSetSi(node->d.p->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt)); - } else { - node->d.p->siocnt = GBASIONormalClearSi(node->d.p->siocnt); - } - node->d.writeRegister = GBASIOLockstepNodeNormalWriteRegister; - break; - default: - break; - } -#ifndef NDEBUG - node->phase = node->p->d.transferActive; - node->transferId = node->p->d.transferId; + mASSERT_DEBUG(!asleep || asleep < coordinator->nAttached); #endif +} - mLockstepUnlock(&node->p->d); +void GBASIOLockstepDriverCreate(struct GBASIOLockstepDriver* driver, struct mLockstepUser* user) { + memset(driver, 0, sizeof(*driver)); + driver->d.init = GBASIOLockstepDriverInit; + driver->d.deinit = GBASIOLockstepDriverDeinit; + driver->d.reset = GBASIOLockstepDriverReset; + driver->d.driverId = GBASIOLockstepDriverId; + driver->d.loadState = GBASIOLockstepDriverLoadState; + driver->d.saveState = GBASIOLockstepDriverSaveState; + driver->d.setMode = GBASIOLockstepDriverSetMode; + driver->d.handlesMode = GBASIOLockstepDriverHandlesMode; + driver->d.deviceId = GBASIOLockstepDriverDeviceId; + driver->d.connectedDevices = GBASIOLockstepDriverConnectedDevices; + driver->d.writeSIOCNT = GBASIOLockstepDriverWriteSIOCNT; + driver->d.writeRCNT = GBASIOLockstepDriverWriteRCNT; + driver->d.start = GBASIOLockstepDriverStart; + driver->d.finishMultiplayer = GBASIOLockstepDriverFinishMultiplayer; + driver->d.finishNormal8 = GBASIOLockstepDriverFinishNormal8; + driver->d.finishNormal32 = GBASIOLockstepDriverFinishNormal32; + driver->event.context = driver; + driver->event.callback = _lockstepEvent; + driver->event.name = "GBA SIO Lockstep"; + driver->event.priority = 0x80; + driver->user = user; +} +static bool GBASIOLockstepDriverInit(struct GBASIODriver* driver) { + GBASIOLockstepDriverReset(driver); return true; } -bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) { - struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; - - mLockstepLock(&node->p->d); - - node->mode = driver->p->mode; - switch (node->mode) { - case GBA_SIO_MULTI: - ATOMIC_SUB(node->p->attachedMulti, 1); - break; - case GBA_SIO_NORMAL_8: - case GBA_SIO_NORMAL_32: - ATOMIC_SUB(node->p->attachedNormal, 1); - break; - default: - break; +static void GBASIOLockstepDriverDeinit(struct GBASIODriver* driver) { + struct GBASIOLockstepDriver* lockstep = (struct GBASIOLockstepDriver*) driver; + struct GBASIOLockstepCoordinator* coordinator = lockstep->coordinator; + MutexLock(&coordinator->mutex); + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, lockstep->lockstepId); + if (player) { + _removePlayer(coordinator, player); } - - // Flush ongoing transfer - if (mTimingIsScheduled(&driver->p->p->timing, &node->event)) { - node->eventDiff -= node->event.when - mTimingCurrentTime(&driver->p->p->timing); - mTimingDeschedule(&driver->p->p->timing, &node->event); - } - - node->p->d.unload(&node->p->d, node->id); - - _finishTransfer(node); - - if (!node->id) { - ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE); - } - - // Invalidate SIO mode - node->mode = GBA_SIO_GPIO; - - mLockstepUnlock(&node->p->d); - - return true; + MutexUnlock(&coordinator->mutex); + mTimingDeschedule(&lockstep->d.p->p->timing, &lockstep->event); + lockstep->lockstepId = 0; } -static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) { - struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; +static void GBASIOLockstepDriverReset(struct GBASIODriver* driver) { + struct GBASIOLockstepDriver* lockstep = (struct GBASIOLockstepDriver*) driver; + struct GBASIOLockstepCoordinator* coordinator = lockstep->coordinator; + struct GBASIOLockstepPlayer* player; + if (!lockstep->lockstepId) { + unsigned id; + player = calloc(1, sizeof(*player)); + player->driver = lockstep; + player->mode = driver->p->mode; + player->playerId = -1; - mLockstepLock(&node->p->d); + int i; + for (i = 0; i < MAX_LOCKSTEP_EVENTS - 1; ++i) { + player->buffer[i].next = &player->buffer[i + 1]; + } + player->freeList = &player->buffer[0]; - if (address == GBA_REG_SIOCNT) { - mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04X", node->id, value); - - enum mLockstepPhase transferActive; - int attached; - ATOMIC_LOAD(transferActive, node->p->d.transferActive); - ATOMIC_LOAD(attached, node->p->d.attached); - - driver->p->siocnt = GBASIOMultiplayerSetSlave(driver->p->siocnt, node->id || attached < 2); - - if (value & 0x0080 && transferActive == TRANSFER_IDLE) { - if (!node->id && attached > 1 && GBASIOMultiplayerIsReady(node->d.p->siocnt)) { - mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id); - ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING); - ATOMIC_STORE(node->p->d.transferCycles, GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][node->p->d.attached - 1]); - - if (mTimingIsScheduled(&driver->p->p->timing, &node->event)) { - node->eventDiff -= node->event.when - mTimingCurrentTime(&driver->p->p->timing); - mTimingDeschedule(&driver->p->p->timing, &node->event); - } - mTimingSchedule(&driver->p->p->timing, &node->event, 0); + MutexLock(&coordinator->mutex); + while (true) { + if (coordinator->nextId == UINT_MAX) { + coordinator->nextId = 0; + } + ++coordinator->nextId; + id = coordinator->nextId; + if (!TableLookup(&coordinator->players, id)) { + TableInsert(&coordinator->players, id, player); + lockstep->lockstepId = id; + break; } } - value &= 0xFF83; - value |= driver->p->siocnt & 0x00FC; - } else if (address == GBA_REG_SIOMLT_SEND) { - mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOMLT_SEND <- %04X", node->id, value); + _reconfigPlayers(coordinator); + player->cycleOffset = mTimingCurrentTime(&driver->p->p->timing) - coordinator->cycle; + if (player->playerId != 0) { + struct GBASIOLockstepEvent event = { + .type = SIO_EV_ATTACH, + .playerId = player->playerId, + .timestamp = GBASIOLockstepTime(player), + }; + _enqueueEvent(coordinator, &event, TARGET_ALL & ~TARGET(player->playerId)); + } } else { - mLOG(GBA_SIO, STUB, "Lockstep %i: Unknown reg %03X <- %04X", node->id, address, value); + player = TableLookup(&coordinator->players, lockstep->lockstepId); + player->cycleOffset = mTimingCurrentTime(&driver->p->p->timing) - coordinator->cycle; } - mLockstepUnlock(&node->p->d); - - return value; -} - -static void _finishTransfer(struct GBASIOLockstepNode* node) { - if (node->transferFinished) { + if (mTimingIsScheduled(&lockstep->d.p->p->timing, &lockstep->event)) { + MutexUnlock(&coordinator->mutex); return; } - struct GBASIO* sio = node->d.p; - switch (node->mode) { - case GBA_SIO_MULTI: - sio->p->memory.io[GBA_REG(SIOMULTI0)] = node->p->multiRecv[0]; - sio->p->memory.io[GBA_REG(SIOMULTI1)] = node->p->multiRecv[1]; - sio->p->memory.io[GBA_REG(SIOMULTI2)] = node->p->multiRecv[2]; - sio->p->memory.io[GBA_REG(SIOMULTI3)] = node->p->multiRecv[3]; - sio->rcnt |= 1; - sio->siocnt = GBASIOMultiplayerClearBusy(sio->siocnt); - sio->siocnt = GBASIOMultiplayerSetId(sio->siocnt, node->id); - if (GBASIOMultiplayerIsIrq(sio->siocnt)) { - GBARaiseIRQ(sio->p, GBA_IRQ_SIO, 0); - } - break; - case GBA_SIO_NORMAL_8: - // TODO - sio->siocnt = GBASIONormalClearStart(sio->siocnt); - if (node->id) { - sio->siocnt = GBASIONormalSetSi(sio->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt)); - node->d.p->p->memory.io[GBA_REG(SIODATA8)] = node->p->normalRecv[node->id - 1] & 0xFF; - } else { - node->d.p->p->memory.io[GBA_REG(SIODATA8)] = 0xFFFF; - } - if (GBASIONormalIsIrq(sio->siocnt)) { - GBARaiseIRQ(sio->p, GBA_IRQ_SIO, 0); - } - break; - case GBA_SIO_NORMAL_32: - // TODO - sio->siocnt = GBASIONormalClearStart(sio->siocnt); - if (node->id) { - sio->siocnt = GBASIONormalSetSi(sio->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt)); - node->d.p->p->memory.io[GBA_REG(SIODATA32_LO)] = node->p->normalRecv[node->id - 1]; - node->d.p->p->memory.io[GBA_REG(SIODATA32_HI)] = node->p->normalRecv[node->id - 1] >> 16; - } else { - node->d.p->p->memory.io[GBA_REG(SIODATA32_LO)] = 0xFFFF; - node->d.p->p->memory.io[GBA_REG(SIODATA32_HI)] = 0xFFFF; - } - if (GBASIONormalIsIrq(sio->siocnt)) { - GBARaiseIRQ(sio->p, GBA_IRQ_SIO, 0); - } - break; - default: - break; + int32_t nextEvent; + _setReady(coordinator, player, player->playerId, player->mode); + if (TableSize(&coordinator->players) == 1) { + coordinator->cycle = mTimingCurrentTime(&lockstep->d.p->p->timing); + nextEvent = LOCKSTEP_INTERVAL; + } else { + _setReady(coordinator, player, 0, coordinator->transferMode); + nextEvent = _untilNextSync(lockstep->coordinator, player); } - node->transferFinished = true; -#ifndef NDEBUG - ++node->transferId; -#endif + MutexUnlock(&coordinator->mutex); + mTimingSchedule(&lockstep->d.p->p->timing, &lockstep->event, nextEvent); } -static int32_t _masterUpdate(struct GBASIOLockstepNode* node) { - bool needsToWait = false; - int i; +static uint32_t GBASIOLockstepDriverId(const struct GBASIODriver* driver) { + UNUSED(driver); + return DRIVER_ID; +} - enum mLockstepPhase transferActive; - int attachedMulti, attached; - - ATOMIC_LOAD(transferActive, node->p->d.transferActive); - ATOMIC_LOAD(attachedMulti, node->p->attachedMulti); - ATOMIC_LOAD(attached, node->p->d.attached); - - switch (transferActive) { - case TRANSFER_IDLE: - // If the master hasn't initiated a transfer, it can keep going. - node->nextEvent += LOCKSTEP_INCREMENT; - if (node->mode == GBA_SIO_MULTI) { - node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMulti == attached); - } - break; - case TRANSFER_STARTING: - // Start the transfer, but wait for the other GBAs to catch up - node->transferFinished = false; - switch (node->mode) { - case GBA_SIO_MULTI: - node->p->multiRecv[0] = node->d.p->p->memory.io[GBA_REG(SIOMLT_SEND)]; - node->d.p->p->memory.io[GBA_REG(SIOMULTI0)] = 0xFFFF; - node->d.p->p->memory.io[GBA_REG(SIOMULTI1)] = 0xFFFF; - node->d.p->p->memory.io[GBA_REG(SIOMULTI2)] = 0xFFFF; - node->d.p->p->memory.io[GBA_REG(SIOMULTI3)] = 0xFFFF; - node->p->multiRecv[1] = 0xFFFF; - node->p->multiRecv[2] = 0xFFFF; - node->p->multiRecv[3] = 0xFFFF; - break; - case GBA_SIO_NORMAL_8: - node->p->multiRecv[0] = 0xFFFF; - node->p->normalRecv[0] = node->d.p->p->memory.io[GBA_REG(SIODATA8)] & 0xFF; - break; - case GBA_SIO_NORMAL_32: - node->p->multiRecv[0] = 0xFFFF; - mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04X", node->id, node->d.p->p->memory.io[GBA_REG(SIODATA32_LO)]); - mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04X", node->id, node->d.p->p->memory.io[GBA_REG(SIODATA32_HI)]); - node->p->normalRecv[0] = node->d.p->p->memory.io[GBA_REG(SIODATA32_LO)]; - node->p->normalRecv[0] |= node->d.p->p->memory.io[GBA_REG(SIODATA32_HI)] << 16; - break; - default: - node->p->multiRecv[0] = 0xFFFF; - break; - } - needsToWait = true; - ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTED); - node->nextEvent += LOCKSTEP_TRANSFER; - break; - case TRANSFER_STARTED: - // All the other GBAs have caught up and are sleeping, we can all continue now - node->nextEvent += LOCKSTEP_TRANSFER; - ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHING); - break; - case TRANSFER_FINISHING: - // Finish the transfer - // We need to make sure the other GBAs catch up so they don't get behind - node->nextEvent += node->p->d.transferCycles - 1024; // Split the cycles to avoid waiting too long -#ifndef NDEBUG - ATOMIC_ADD(node->p->d.transferId, 1); -#endif - needsToWait = true; - ATOMIC_STORE(node->p->d.transferActive, TRANSFER_FINISHED); - break; - case TRANSFER_FINISHED: - // Everything's settled. We're done. - _finishTransfer(node); - node->nextEvent += LOCKSTEP_INCREMENT; - ATOMIC_STORE(node->p->d.transferActive, TRANSFER_IDLE); - break; +static unsigned _modeEnumToInt(enum GBASIOMode mode) { + switch ((int) mode) { + case -1: + default: + return 0; + case GBA_SIO_MULTI: + return 1; + case GBA_SIO_NORMAL_8: + return 2; + case GBA_SIO_NORMAL_32: + return 3; + case GBA_SIO_GPIO: + return 4; + case GBA_SIO_UART: + return 5; + case GBA_SIO_JOYBUS: + return 6; } - int mask = 0; - for (i = 1; i < node->p->d.attached; ++i) { - if (node->p->players[i]->mode == node->mode) { - mask |= 1 << i; +} + +static enum GBASIOMode _modeIntToEnum(unsigned mode) { + const enum GBASIOMode modes[8] = { + -1, GBA_SIO_MULTI, GBA_SIO_NORMAL_8, GBA_SIO_NORMAL_32, GBA_SIO_GPIO, GBA_SIO_UART, GBA_SIO_JOYBUS, -1 + }; + return modes[mode & 7]; +} + +static bool GBASIOLockstepDriverLoadState(struct GBASIODriver* driver, const void* data, size_t size) { + struct GBASIOLockstepDriver* lockstep = (struct GBASIOLockstepDriver*) driver; + struct GBASIOLockstepCoordinator* coordinator = lockstep->coordinator; + if (size != sizeof(struct GBASIOLockstepSerializedState)) { + mLOG(GBA_SIO, WARN, "Incorrect state size: expected %" PRIz "X, got %" PRIz "X", sizeof(struct GBASIOLockstepSerializedState), size); + return false; + } + const struct GBASIOLockstepSerializedState* state = data; + bool error = false; + uint32_t ucheck; + int32_t check; + LOAD_32LE(ucheck, 0, &state->version); + if (ucheck > DRIVER_STATE_VERSION) { + mLOG(GBA_SIO, WARN, "Invalid or too new save state: expected %u, got %u", DRIVER_STATE_VERSION, ucheck); + return false; + } + + MutexLock(&coordinator->mutex); + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, lockstep->lockstepId); + LOAD_32LE(check, 0, &state->player.playerId); + if (check != player->playerId) { + mLOG(GBA_SIO, WARN, "State is for different player: expected %d, got %d", player->playerId, check); + error = true; + goto out; + } + + GBASIOLockstepSerializedFlags flags = 0; + LOAD_32LE(flags, 0, &state->flags); + LOAD_32LE(player->cycleOffset, 0, &state->player.cycleOffset); + player->dataReceived = GBASIOLockstepSerializedFlagsGetDataReceived(flags); + player->mode = _modeIntToEnum(GBASIOLockstepSerializedFlagsGetDriverMode(flags)); + + player->otherModes[0] = _modeIntToEnum(GBASIOLockstepSerializedFlagsGetPlayer0Mode(flags)); + player->otherModes[1] = _modeIntToEnum(GBASIOLockstepSerializedFlagsGetPlayer1Mode(flags)); + player->otherModes[2] = _modeIntToEnum(GBASIOLockstepSerializedFlagsGetPlayer2Mode(flags)); + player->otherModes[3] = _modeIntToEnum(GBASIOLockstepSerializedFlagsGetPlayer3Mode(flags)); + + if (GBASIOLockstepSerializedFlagsGetEventScheduled(flags)) { + int32_t when; + LOAD_32LE(when, 0, &state->driver.nextEvent); + mTimingSchedule(&driver->p->p->timing, &lockstep->event, when); + } + + if (GBASIOLockstepSerializedFlagsGetAsleep(flags)) { + if (!player->asleep && player->driver->user->sleep) { + player->driver->user->sleep(player->driver->user); + } + player->asleep = true; + } else { + if (player->asleep && player->driver->user->wake) { + player->driver->user->wake(player->driver->user); + } + player->asleep = false; + } + + unsigned i; + for (i = 0; i < MAX_LOCKSTEP_EVENTS - 1; ++i) { + player->buffer[i].next = &player->buffer[i + 1]; + } + player->freeList = &player->buffer[0]; + player->queue = NULL; + + struct GBASIOLockstepEvent** lastEvent = &player->queue; + for (i = 0; i < GBASIOLockstepSerializedFlagsGetNumEvents(flags) && i < MAX_LOCKSTEP_EVENTS; ++i) { + struct GBASIOLockstepEvent* event = player->freeList; + const struct GBASIOLockstepSerializedEvent* stateEvent = &state->player.events[i]; + player->freeList = player->freeList->next; + *lastEvent = event; + lastEvent = &event->next; + + GBASIOLockstepSerializedEventFlags flags; + LOAD_32LE(flags, 0, &stateEvent->flags); + LOAD_32LE(event->timestamp, 0, &stateEvent->timestamp); + LOAD_32LE(event->playerId, 0, &stateEvent->playerId); + event->type = GBASIOLockstepSerializedEventFlagsGetType(flags); + switch (event->type) { + case SIO_EV_ATTACH: + case SIO_EV_DETACH: + case SIO_EV_HARD_SYNC: + break; + case SIO_EV_MODE_SET: + LOAD_32LE(event->mode, 0, &stateEvent->mode); + break; + case SIO_EV_TRANSFER_START: + LOAD_32LE(event->finishCycle, 0, &stateEvent->finishCycle); + break; } } - if (mask) { - if (needsToWait) { - if (!node->p->d.wait(&node->p->d, mask)) { - abort(); - } - } else { - node->p->d.signal(&node->p->d, mask); - } - } - // Tell the other GBAs they can continue up to where we were - node->p->d.addCycles(&node->p->d, 0, node->eventDiff); -#ifndef NDEBUG - node->phase = node->p->d.transferActive; -#endif - if (needsToWait) { + if (player->playerId == 0) { + LOAD_32LE(coordinator->cycle, 0, &state->coordinator.cycle); + LOAD_32LE(coordinator->waiting, 0, &state->coordinator.waiting); + LOAD_32LE(coordinator->nextHardSync, 0, &state->coordinator.nextHardSync); + for (i = 0; i < 4; ++i) { + LOAD_16LE(coordinator->multiData[i], 0, &state->coordinator.multiData[i]); + LOAD_32LE(coordinator->normalData[i], 0, &state->coordinator.normalData[i]); + } + coordinator->transferMode = _modeIntToEnum(GBASIOLockstepSerializedFlagsGetTransferMode(flags)); + coordinator->transferActive = GBASIOLockstepSerializedFlagsGetTransferActive(flags); + } +out: + MutexUnlock(&coordinator->mutex); + if (!error) { + mTimingInterrupt(&driver->p->p->timing); + } + return !error; +} + +static void GBASIOLockstepDriverSaveState(struct GBASIODriver* driver, void** stateOut, size_t* size) { + struct GBASIOLockstepDriver* lockstep = (struct GBASIOLockstepDriver*) driver; + struct GBASIOLockstepCoordinator* coordinator = lockstep->coordinator; + struct GBASIOLockstepSerializedState* state = calloc(1, sizeof(*state)); + + STORE_32LE(DRIVER_STATE_VERSION, 0, &state->version); + + STORE_32LE(lockstep->event.when - mTimingCurrentTime(&driver->p->p->timing), 0, &state->driver.nextEvent); + + MutexLock(&coordinator->mutex); + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, lockstep->lockstepId); + GBASIOLockstepSerializedFlags flags = 0; + STORE_32LE(player->playerId, 0, &state->player.playerId); + STORE_32LE(player->cycleOffset, 0, &state->player.cycleOffset); + flags = GBASIOLockstepSerializedFlagsSetAsleep(flags, player->asleep); + flags = GBASIOLockstepSerializedFlagsSetDataReceived(flags, player->dataReceived); + flags = GBASIOLockstepSerializedFlagsSetDriverMode(flags, _modeEnumToInt(player->mode)); + flags = GBASIOLockstepSerializedFlagsSetEventScheduled(flags, mTimingIsScheduled(&driver->p->p->timing, &lockstep->event)); + + flags = GBASIOLockstepSerializedFlagsSetPlayer0Mode(flags, _modeEnumToInt(player->otherModes[0])); + flags = GBASIOLockstepSerializedFlagsSetPlayer1Mode(flags, _modeEnumToInt(player->otherModes[1])); + flags = GBASIOLockstepSerializedFlagsSetPlayer2Mode(flags, _modeEnumToInt(player->otherModes[2])); + flags = GBASIOLockstepSerializedFlagsSetPlayer3Mode(flags, _modeEnumToInt(player->otherModes[3])); + + struct GBASIOLockstepEvent* event = player->queue; + size_t i; + for (i = 0; i < MAX_LOCKSTEP_EVENTS && event; ++i, event = event->next) { + struct GBASIOLockstepSerializedEvent* stateEvent = &state->player.events[i]; + GBASIOLockstepSerializedEventFlags flags = GBASIOLockstepSerializedEventFlagsSetType(0, event->type); + STORE_32LE(event->timestamp, 0, &stateEvent->timestamp); + STORE_32LE(event->playerId, 0, &stateEvent->playerId); + switch (event->type) { + case SIO_EV_ATTACH: + case SIO_EV_DETACH: + case SIO_EV_HARD_SYNC: + break; + case SIO_EV_MODE_SET: + STORE_32LE(event->mode, 0, &stateEvent->mode); + break; + case SIO_EV_TRANSFER_START: + STORE_32LE(event->finishCycle, 0, &stateEvent->finishCycle); + break; + } + STORE_32LE(flags, 0, &stateEvent->flags); + } + flags = GBASIOLockstepSerializedFlagsSetNumEvents(flags, i); + + if (player->playerId == 0) { + STORE_32LE(coordinator->cycle, 0, &state->coordinator.cycle); + STORE_32LE(coordinator->waiting, 0, &state->coordinator.waiting); + STORE_32LE(coordinator->nextHardSync, 0, &state->coordinator.nextHardSync); + for (i = 0; i < 4; ++i) { + STORE_16LE(coordinator->multiData[i], 0, &state->coordinator.multiData[i]); + STORE_32LE(coordinator->normalData[i], 0, &state->coordinator.normalData[i]); + } + flags = GBASIOLockstepSerializedFlagsSetTransferMode(flags, _modeEnumToInt(coordinator->transferMode)); + flags = GBASIOLockstepSerializedFlagsSetTransferActive(flags, coordinator->transferActive); + } + MutexUnlock(&lockstep->coordinator->mutex); + + STORE_32LE(flags, 0, &state->flags); + *stateOut = state; + *size = sizeof(*state); +} + +static void GBASIOLockstepDriverSetMode(struct GBASIODriver* driver, enum GBASIOMode mode) { + struct GBASIOLockstepDriver* lockstep = (struct GBASIOLockstepDriver*) driver; + struct GBASIOLockstepCoordinator* coordinator = lockstep->coordinator; + MutexLock(&coordinator->mutex); + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, lockstep->lockstepId); + if (mode != player->mode) { + player->mode = mode; + struct GBASIOLockstepEvent event = { + .type = SIO_EV_MODE_SET, + .playerId = player->playerId, + .timestamp = GBASIOLockstepTime(player), + .mode = mode, + }; + if (player->playerId == 0) { + mASSERT_DEBUG(!coordinator->transferActive); // TODO + coordinator->transferMode = mode; + GBASIOLockstepCoordinatorWaitOnPlayers(coordinator, player); + } + _setReady(coordinator, player, player->playerId, mode); + _enqueueEvent(coordinator, &event, TARGET_ALL & ~TARGET(player->playerId)); + } + MutexUnlock(&coordinator->mutex); +} + +static bool GBASIOLockstepDriverHandlesMode(struct GBASIODriver* driver, enum GBASIOMode mode) { + UNUSED(driver); + UNUSED(mode); + return true; +} + +static int GBASIOLockstepDriverConnectedDevices(struct GBASIODriver* driver) { + struct GBASIOLockstepDriver* lockstep = (struct GBASIOLockstepDriver*) driver; + struct GBASIOLockstepCoordinator* coordinator = lockstep->coordinator; + if (!lockstep->lockstepId) { return 0; } - return node->nextEvent; + MutexLock(&coordinator->mutex); + int attached = coordinator->nAttached - 1; + MutexUnlock(&coordinator->mutex); + return attached; } -static uint32_t _slaveUpdate(struct GBASIOLockstepNode* node) { - enum mLockstepPhase transferActive; - int attached; - int attachedMode; - - ATOMIC_LOAD(transferActive, node->p->d.transferActive); - ATOMIC_LOAD(attached, node->p->d.attached); - - if (node->mode == GBA_SIO_MULTI) { - ATOMIC_LOAD(attachedMode, node->p->attachedMulti); - node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, attachedMode == attached); - } else { - ATOMIC_LOAD(attachedMode, node->p->attachedNormal); +static int GBASIOLockstepDriverDeviceId(struct GBASIODriver* driver) { + struct GBASIOLockstepDriver* lockstep = (struct GBASIOLockstepDriver*) driver; + struct GBASIOLockstepCoordinator* coordinator = lockstep->coordinator; + int playerId = 0; + MutexLock(&coordinator->mutex); + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, lockstep->lockstepId); + if (player && player->playerId >= 0) { + playerId = player->playerId; } - bool signal = false; - switch (transferActive) { - case TRANSFER_IDLE: - if (attachedMode != attached) { - node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT); - } - break; - case TRANSFER_STARTING: - case TRANSFER_FINISHING: - break; - case TRANSFER_STARTED: - if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) { - break; - } - node->transferFinished = false; - switch (node->mode) { - case GBA_SIO_MULTI: - node->d.p->rcnt &= ~1; - node->p->multiRecv[node->id] = node->d.p->p->memory.io[GBA_REG(SIOMLT_SEND)]; - node->d.p->p->memory.io[GBA_REG(SIOMULTI0)] = 0xFFFF; - node->d.p->p->memory.io[GBA_REG(SIOMULTI1)] = 0xFFFF; - node->d.p->p->memory.io[GBA_REG(SIOMULTI2)] = 0xFFFF; - node->d.p->p->memory.io[GBA_REG(SIOMULTI3)] = 0xFFFF; - node->d.p->siocnt = GBASIOMultiplayerFillBusy(node->d.p->siocnt); - break; - case GBA_SIO_NORMAL_8: - node->p->multiRecv[node->id] = 0xFFFF; - node->p->normalRecv[node->id] = node->d.p->p->memory.io[GBA_REG(SIODATA8)] & 0xFF; - break; - case GBA_SIO_NORMAL_32: - node->p->multiRecv[node->id] = 0xFFFF; - node->p->normalRecv[node->id] = node->d.p->p->memory.io[GBA_REG(SIODATA32_LO)]; - node->p->normalRecv[node->id] |= node->d.p->p->memory.io[GBA_REG(SIODATA32_HI)] << 16; - break; - default: - node->p->multiRecv[node->id] = 0xFFFF; - break; - } - signal = true; - break; - case TRANSFER_FINISHED: - if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) { - break; - } - _finishTransfer(node); - signal = true; - break; - } -#ifndef NDEBUG - node->phase = node->p->d.transferActive; -#endif - if (signal) { - node->p->d.signal(&node->p->d, 1 << node->id); - } - - return 0; + MutexUnlock(&coordinator->mutex); + return playerId; } -static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLate) { - struct GBASIOLockstepNode* node = user; - mLockstepLock(&node->p->d); - - int32_t cycles = node->nextEvent; - node->nextEvent -= cyclesLate; - node->eventDiff += cyclesLate; - if (node->p->d.attached < 2) { - switch (node->mode) { - case GBA_SIO_MULTI: - cycles = GBASIOCyclesPerTransfer[GBASIOMultiplayerGetBaud(node->d.p->siocnt)][0]; - break; - case GBA_SIO_NORMAL_8: - case GBA_SIO_NORMAL_32: - if (node->nextEvent <= 0) { - cycles = _masterUpdate(node); - node->eventDiff = 0; - } - break; - default: - break; - } - } else if (node->nextEvent <= 0) { - if (!node->id) { - cycles = _masterUpdate(node); - } else { - cycles = _slaveUpdate(node); - cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff); - } - node->eventDiff = 0; - } - if (cycles > 0) { - node->nextEvent = 0; - node->eventDiff += cycles; - mTimingDeschedule(timing, &node->event); - mTimingSchedule(timing, &node->event, cycles); - } else { - node->d.p->p->earlyExit = true; - node->eventDiff += 1; - mTimingSchedule(timing, &node->event, 1); - } - - mLockstepUnlock(&node->p->d); -} - -static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) { - struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver; - - mLockstepLock(&node->p->d); - - if (address == GBA_REG_SIOCNT) { - mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04X", node->id, value); - int attached; - ATOMIC_LOAD(attached, node->p->attachedNormal); - value &= 0xFF8B; - if (node->id > 0) { - value = GBASIONormalSetSi(value, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt)); - } else { - value = GBASIONormalClearSi(value); - } - - enum mLockstepPhase transferActive; - ATOMIC_LOAD(transferActive, node->p->d.transferActive); - if (node->id < 3 && attached > node->id + 1 && transferActive == TRANSFER_IDLE) { - int try; - for (try = 0; try < 3; ++try) { - GBASIONormal nextSiocnct; - ATOMIC_LOAD(nextSiocnct, node->p->players[node->id + 1]->d.p->siocnt); - if (ATOMIC_CMPXCHG(node->p->players[node->id + 1]->d.p->siocnt, nextSiocnct, GBASIONormalSetSi(nextSiocnct, GBASIONormalGetIdleSo(value)))) { - break; - } - } - } - if ((value & 0x0081) == 0x0081) { - if (!node->id) { - // Frequency - int32_t cycles; - if (value & 2) { - cycles = 8 * 8; - } else { - cycles = 64 * 8; - } - if (value & 0x1000) { - cycles *= 4; - } - - if (transferActive == TRANSFER_IDLE) { - mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id); - ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING); - ATOMIC_STORE(node->p->d.transferCycles, cycles); - - if (mTimingIsScheduled(&driver->p->p->timing, &node->event)) { - node->eventDiff -= node->event.when - mTimingCurrentTime(&driver->p->p->timing); - mTimingDeschedule(&driver->p->p->timing, &node->event); - } - mTimingSchedule(&driver->p->p->timing, &node->event, 0); - } else { - value &= ~0x0080; - } - } else { - // TODO - } - } - } else if (address == GBA_REG_SIODATA32_LO) { - mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_LO <- %04X", node->id, value); - } else if (address == GBA_REG_SIODATA32_HI) { - mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA32_HI <- %04X", node->id, value); - } else if (address == GBA_REG_SIODATA8) { - mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIODATA8 <- %02X", node->id, value); - } - - mLockstepUnlock(&node->p->d); - +static uint16_t GBASIOLockstepDriverWriteSIOCNT(struct GBASIODriver* driver, uint16_t value) { + UNUSED(driver); + mLOG(GBA_SIO, DEBUG, "Lockstep: SIOCNT <- %04X", value); return value; } + +static uint16_t GBASIOLockstepDriverWriteRCNT(struct GBASIODriver* driver, uint16_t value) { + UNUSED(driver); + mLOG(GBA_SIO, DEBUG, "Lockstep: RCNT <- %04X", value); + return value; +} + +static bool GBASIOLockstepDriverStart(struct GBASIODriver* driver) { + struct GBASIOLockstepDriver* lockstep = (struct GBASIOLockstepDriver*) driver; + struct GBASIOLockstepCoordinator* coordinator = lockstep->coordinator; + bool ret = false; + MutexLock(&coordinator->mutex); + if (coordinator->transferActive) { + mLOG(GBA_SIO, ERROR, "Transfer restarted unexpectedly"); + goto out; + } + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, lockstep->lockstepId); + if (player->playerId != 0) { + mLOG(GBA_SIO, DEBUG, "Secondary player attempted to start transfer"); + goto out; + } + mLOG(GBA_SIO, DEBUG, "Transfer starting at %08X", coordinator->cycle); + memset(coordinator->multiData, 0xFF, sizeof(coordinator->multiData)); + _setData(coordinator, 0, player->driver->d.p); + + int32_t timestamp = GBASIOLockstepTime(player); + struct GBASIOLockstepEvent event = { + .type = SIO_EV_TRANSFER_START, + .timestamp = timestamp, + .finishCycle = timestamp + GBASIOTransferCycles(player->mode, player->driver->d.p->siocnt, coordinator->nAttached - 1), + }; + _enqueueEvent(coordinator, &event, TARGET_SECONDARY); + GBASIOLockstepCoordinatorWaitOnPlayers(coordinator, player); + coordinator->transferActive = true; + ret = true; +out: + MutexUnlock(&coordinator->mutex); + return ret; +} + +static void GBASIOLockstepDriverFinishMultiplayer(struct GBASIODriver* driver, uint16_t data[4]) { + struct GBASIOLockstepDriver* lockstep = (struct GBASIOLockstepDriver*) driver; + struct GBASIOLockstepCoordinator* coordinator = lockstep->coordinator; + MutexLock(&coordinator->mutex); + if (coordinator->transferMode == GBA_SIO_MULTI) { + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, lockstep->lockstepId); + if (!player->dataReceived) { + mLOG(GBA_SIO, WARN, "MULTI did not receive data. Are we running behind?"); + memset(data, 0xFF, sizeof(uint16_t) * 4); + } else { + mLOG(GBA_SIO, INFO, "MULTI transfer finished: %04X %04X %04X %04X", + coordinator->multiData[0], + coordinator->multiData[1], + coordinator->multiData[2], + coordinator->multiData[3]); + memcpy(data, coordinator->multiData, sizeof(uint16_t) * 4); + } + player->dataReceived = false; + if (player->playerId == 0) { + _hardSync(coordinator, player); + } + } + MutexUnlock(&coordinator->mutex); +} + +static uint8_t GBASIOLockstepDriverFinishNormal8(struct GBASIODriver* driver) { + struct GBASIOLockstepDriver* lockstep = (struct GBASIOLockstepDriver*) driver; + struct GBASIOLockstepCoordinator* coordinator = lockstep->coordinator; + uint8_t data = 0xFF; + MutexLock(&coordinator->mutex); + if (coordinator->transferMode == GBA_SIO_NORMAL_8) { + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, lockstep->lockstepId); + if (player->playerId > 0) { + if (!player->dataReceived) { + mLOG(GBA_SIO, WARN, "NORMAL did not receive data. Are we running behind?"); + } else { + data = coordinator->normalData[player->playerId - 1]; + mLOG(GBA_SIO, INFO, "NORMAL8 transfer finished: %02X", data); + } + } + player->dataReceived = false; + if (player->playerId == 0) { + _hardSync(coordinator, player); + } + } + MutexUnlock(&coordinator->mutex); + return data; +} + +static uint32_t GBASIOLockstepDriverFinishNormal32(struct GBASIODriver* driver) { + struct GBASIOLockstepDriver* lockstep = (struct GBASIOLockstepDriver*) driver; + struct GBASIOLockstepCoordinator* coordinator = lockstep->coordinator; + uint32_t data = 0xFFFFFFFF; + MutexLock(&coordinator->mutex); + if (coordinator->transferMode == GBA_SIO_NORMAL_32) { + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, lockstep->lockstepId); + if (player->playerId > 0) { + if (!player->dataReceived) { + mLOG(GBA_SIO, WARN, "Did not receive data. Are we running behind?"); + } else { + data = coordinator->normalData[player->playerId - 1]; + mLOG(GBA_SIO, INFO, "NORMAL32 transfer finished: %08X", data); + } + } + player->dataReceived = false; + if (player->playerId == 0) { + _hardSync(coordinator, player); + } + } + MutexUnlock(&coordinator->mutex); + return data; +} + +void GBASIOLockstepCoordinatorInit(struct GBASIOLockstepCoordinator* coordinator) { + memset(coordinator, 0, sizeof(*coordinator)); + MutexInit(&coordinator->mutex); + TableInit(&coordinator->players, 8, free); +} + +void GBASIOLockstepCoordinatorDeinit(struct GBASIOLockstepCoordinator* coordinator) { + MutexDeinit(&coordinator->mutex); + TableDeinit(&coordinator->players); +} + +void GBASIOLockstepCoordinatorAttach(struct GBASIOLockstepCoordinator* coordinator, struct GBASIOLockstepDriver* driver) { + if (driver->coordinator && driver->coordinator != coordinator) { + // TODO + abort(); + } + driver->coordinator = coordinator; +} + +void GBASIOLockstepCoordinatorDetach(struct GBASIOLockstepCoordinator* coordinator, struct GBASIOLockstepDriver* driver) { + if (driver->coordinator != coordinator) { + // TODO + abort(); + return; + } + MutexLock(&coordinator->mutex); + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, driver->lockstepId); + if (player) { + _removePlayer(coordinator, player); + } + MutexUnlock(&coordinator->mutex); + driver->coordinator = NULL; +} + +int32_t _untilNextSync(struct GBASIOLockstepCoordinator* coordinator, struct GBASIOLockstepPlayer* player) { + int32_t cycle = coordinator->cycle - GBASIOLockstepTime(player); + if (player->playerId == 0) { + if (coordinator->nAttached < 2) { + cycle += UNLOCKED_INTERVAL; + } else { + cycle += LOCKSTEP_INTERVAL; + } + } + return cycle; +} + +void _advanceCycle(struct GBASIOLockstepCoordinator* coordinator, struct GBASIOLockstepPlayer* player) { + int32_t newCycle = GBASIOLockstepTime(player); + mASSERT_DEBUG(newCycle - coordinator->cycle >= 0); + coordinator->nextHardSync -= newCycle - coordinator->cycle; + coordinator->cycle = newCycle; +} + +void _removePlayer(struct GBASIOLockstepCoordinator* coordinator, struct GBASIOLockstepPlayer* player) { + struct GBASIOLockstepEvent event = { + .type = SIO_EV_DETACH, + .playerId = player->playerId, + .timestamp = GBASIOLockstepTime(player), + }; + _enqueueEvent(coordinator, &event, TARGET_ALL & ~TARGET(player->playerId)); + + coordinator->waiting = 0; + coordinator->transferActive = false; + + TableRemove(&coordinator->players, player->driver->lockstepId); + _reconfigPlayers(coordinator); + + struct GBASIOLockstepPlayer* runner = TableLookup(&coordinator->players, coordinator->attachedPlayers[0]); + if (runner) { + GBASIOLockstepPlayerWake(runner); + } + _verifyAwake(coordinator); +} + +void _reconfigPlayers(struct GBASIOLockstepCoordinator* coordinator) { + size_t players = TableSize(&coordinator->players); + memset(coordinator->attachedPlayers, 0, sizeof(coordinator->attachedPlayers)); + if (players == 0) { + mLOG(GBA_SIO, WARN, "Reconfiguring player IDs with no players attached somehow?"); + } else if (players == 1) { + struct TableIterator iter; + mASSERT(TableIteratorStart(&coordinator->players, &iter)); + unsigned p0 = TableIteratorGetKey(&coordinator->players, &iter); + coordinator->attachedPlayers[0] = p0; + + struct GBASIOLockstepPlayer* player = TableIteratorGetValue(&coordinator->players, &iter); + coordinator->cycle = mTimingCurrentTime(&player->driver->d.p->p->timing); + coordinator->nextHardSync = HARD_SYNC_INTERVAL; + + if (player->playerId != 0) { + player->playerId = 0; + if (player->driver->user->playerIdChanged) { + player->driver->user->playerIdChanged(player->driver->user, player->playerId); + } + } + + if (!coordinator->transferActive) { + coordinator->transferMode = player->mode; + } + } else { + struct UIntList playerPreferences[MAX_GBAS]; + + int i; + for (i = 0; i < MAX_GBAS; ++i) { + UIntListInit(&playerPreferences[i], 4); + } + + // Collect the first four players' requested player IDs so we can sort through them later + int seen = 0; + struct TableIterator iter; + mASSERT(TableIteratorStart(&coordinator->players, &iter)); + do { + unsigned pid = TableIteratorGetKey(&coordinator->players, &iter); + struct GBASIOLockstepPlayer* player = TableIteratorGetValue(&coordinator->players, &iter); + int requested = MAX_GBAS - 1; + if (player->driver->user->requestedId) { + requested = player->driver->user->requestedId(player->driver->user); + } + if (requested < 0) { + continue; + } + if (requested >= MAX_GBAS) { + requested = MAX_GBAS - 1; + } + + *UIntListAppend(&playerPreferences[requested]) = pid; + ++seen; + } while (TableIteratorNext(&coordinator->players, &iter) && seen < MAX_GBAS); + + // Now sort each requested player ID to figure out who gets which ID + seen = 0; + for (i = 0; i < MAX_GBAS; ++i) { + int j; + for (j = 0; j <= i; ++j) { + while (UIntListSize(&playerPreferences[j]) && seen < MAX_GBAS) { + unsigned pid = *UIntListGetPointer(&playerPreferences[j], 0); + UIntListShift(&playerPreferences[j], 0, 1); + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, pid); + if (!player) { + mLOG(GBA_SIO, ERROR, "Player list appears to have changed unexpectedly. PID %u missing.", pid); + continue; + } + coordinator->attachedPlayers[seen] = pid; + if (player->playerId != seen) { + player->playerId = seen; + if (player->driver->user->playerIdChanged) { + player->driver->user->playerIdChanged(player->driver->user, player->playerId); + } + } + ++seen; + } + } + } + + for (i = 0; i < MAX_GBAS; ++i) { + UIntListDeinit(&playerPreferences[i]); + } + } + + int nAttached = 0; + size_t i; + for (i = 0; i < MAX_GBAS; ++i) { + unsigned pid = coordinator->attachedPlayers[i]; + if (!pid) { + continue; + } + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, pid); + if (!player) { + coordinator->attachedPlayers[i] = 0; + } else { + ++nAttached; + } + } + coordinator->nAttached = nAttached; +} + +static void _setData(struct GBASIOLockstepCoordinator* coordinator, uint32_t id, struct GBASIO* sio) { + switch (coordinator->transferMode) { + case GBA_SIO_MULTI: + coordinator->multiData[id] = sio->p->memory.io[GBA_REG(SIOMLT_SEND)]; + break; + case GBA_SIO_NORMAL_8: + coordinator->normalData[id] = sio->p->memory.io[GBA_REG(SIODATA8)]; + break; + case GBA_SIO_NORMAL_32: + coordinator->normalData[id] = sio->p->memory.io[GBA_REG(SIODATA32_LO)]; + coordinator->normalData[id] |= sio->p->memory.io[GBA_REG(SIODATA32_HI)] << 16; + break; + case GBA_SIO_UART: + case GBA_SIO_GPIO: + case GBA_SIO_JOYBUS: + mLOG(GBA_SIO, ERROR, "Unsupported mode %i in lockstep", coordinator->transferMode); + // TODO: Should we handle this or just abort? + break; + } +} + +void _setReady(struct GBASIOLockstepCoordinator* coordinator, struct GBASIOLockstepPlayer* activePlayer, int playerId, enum GBASIOMode mode) { + activePlayer->otherModes[playerId] = mode; + bool ready = true; + int i; + for (i = 0; ready && i < coordinator->nAttached; ++i) { + ready = activePlayer->otherModes[i] == activePlayer->mode; + } + if (activePlayer->mode == GBA_SIO_MULTI) { + struct GBASIO* sio = activePlayer->driver->d.p; + sio->siocnt = GBASIOMultiplayerSetReady(sio->siocnt, ready); + sio->rcnt = GBASIORegisterRCNTSetSd(sio->rcnt, ready); + } +} + +void _hardSync(struct GBASIOLockstepCoordinator* coordinator, struct GBASIOLockstepPlayer* player) { + mASSERT_DEBUG(player->playerId == 0); + struct GBASIOLockstepEvent event = { + .type = SIO_EV_HARD_SYNC, + .playerId = 0, + .timestamp = GBASIOLockstepTime(player), + }; + _enqueueEvent(coordinator, &event, TARGET_SECONDARY); + GBASIOLockstepCoordinatorWaitOnPlayers(coordinator, player); +} + +void _enqueueEvent(struct GBASIOLockstepCoordinator* coordinator, const struct GBASIOLockstepEvent* event, uint32_t target) { + mLOG(GBA_SIO, DEBUG, "Enqueuing event of type %X from %i for target %X at timestamp %X", + event->type, event->playerId, target, event->timestamp); + + int i; + for (i = 0; i < coordinator->nAttached; ++i) { + if (!(target & TARGET(i))) { + continue; + } + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, coordinator->attachedPlayers[i]); + mASSERT_LOG(GBA_SIO, player->freeList, "No free events"); + struct GBASIOLockstepEvent* newEvent = player->freeList; + player->freeList = newEvent->next; + + memcpy(newEvent, event, sizeof(*event)); + struct GBASIOLockstepEvent** previous = &player->queue; + struct GBASIOLockstepEvent* next = player->queue; + while (next) { + int32_t until = newEvent->timestamp - next->timestamp; + if (until < 0) { + break; + } + previous = &next->next; + next = next->next; + } + newEvent->next = next; + *previous = newEvent; + } +} + +void _lockstepEvent(struct mTiming* timing, void* context, uint32_t cyclesLate) { + struct GBASIOLockstepDriver* lockstep = context; + struct GBASIOLockstepCoordinator* coordinator = lockstep->coordinator; + MutexLock(&coordinator->mutex); + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, lockstep->lockstepId); + struct GBASIO* sio = player->driver->d.p; + mASSERT(player->playerId >= 0 && player->playerId < 4); + + bool wasDetach = false; + if (player->queue && player->queue->type == SIO_EV_DETACH) { + mLOG(GBA_SIO, DEBUG, "Player %i detached at timestamp %X, picking up the pieces", + player->queue->playerId, player->queue->timestamp); + wasDetach = true; + } + if (player->playerId == 0 && GBASIOLockstepTime(player) - coordinator->cycle >= 0) { + // We are the clock owner; advance the shared clock. However, if we just became + // the clock owner (by the previous one disconnecting) we might be slightly + // behind the shared clock. We should wait a bit if needed in that case. + _advanceCycle(coordinator, player); + if (!coordinator->transferActive) { + GBASIOLockstepCoordinatorWakePlayers(coordinator); + } + if (coordinator->nextHardSync < 0) { + if (!coordinator->waiting) { + _hardSync(coordinator, player); + } + coordinator->nextHardSync += HARD_SYNC_INTERVAL; + } + } + + int32_t nextEvent = _untilNextSync(coordinator, player); + while (true) { + struct GBASIOLockstepEvent* event = player->queue; + if (!event) { + break; + } + if (event->timestamp > GBASIOLockstepTime(player)) { + break; + } + player->queue = event->next; + struct GBASIOLockstepEvent reply = { + .playerId = player->playerId, + .timestamp = GBASIOLockstepTime(player), + }; + mLOG(GBA_SIO, DEBUG, "Got event of type %X from %i at timestamp %X", + event->type, event->playerId, event->timestamp); + switch (event->type) { + case SIO_EV_ATTACH: + _setReady(coordinator, player, event->playerId, -1); + if (player->playerId == 0) { + struct GBASIO* sio = player->driver->d.p; + sio->siocnt = GBASIOMultiplayerClearSlave(sio->siocnt); + } + reply.mode = player->mode; + reply.type = SIO_EV_MODE_SET; + _enqueueEvent(coordinator, &reply, TARGET(event->playerId)); + break; + case SIO_EV_HARD_SYNC: + GBASIOLockstepCoordinatorAckPlayer(coordinator, player); + break; + case SIO_EV_TRANSFER_START: + _setData(coordinator, player->playerId, sio); + nextEvent = event->finishCycle - GBASIOLockstepTime(player) - cyclesLate; + player->driver->d.p->siocnt |= 0x80; + mTimingDeschedule(&sio->p->timing, &sio->completeEvent); + mTimingSchedule(&sio->p->timing, &sio->completeEvent, nextEvent); + GBASIOLockstepCoordinatorAckPlayer(coordinator, player); + break; + case SIO_EV_MODE_SET: + _setReady(coordinator, player, event->playerId, event->mode); + if (event->playerId == 0) { + GBASIOLockstepCoordinatorAckPlayer(coordinator, player); + } + break; + case SIO_EV_DETACH: + _setReady(coordinator, player, event->playerId, -1); + _setReady(coordinator, player, player->playerId, player->mode); + reply.mode = player->mode; + reply.type = SIO_EV_MODE_SET; + _enqueueEvent(coordinator, &reply, ~TARGET(event->playerId)); + if (player->mode == GBA_SIO_MULTI) { + sio->siocnt = GBASIOMultiplayerSetId(sio->siocnt, player->playerId); + sio->siocnt = GBASIOMultiplayerSetSlave(sio->siocnt, player->playerId || coordinator->nAttached < 2); + } + wasDetach = true; + break; + } + event->next = player->freeList; + player->freeList = event; + } + if (player->queue && player->queue->timestamp - GBASIOLockstepTime(player) < nextEvent) { + nextEvent = player->queue->timestamp - GBASIOLockstepTime(player); + } + + if (player->playerId != 0 && nextEvent <= LOCKSTEP_INTERVAL) { + if (!player->queue || wasDetach) { + GBASIOLockstepPlayerSleep(player); + // XXX: Is there a better way to gain sync lock at the beginning? + if (nextEvent < 4) { + nextEvent = 4; + } + _verifyAwake(coordinator); + } + } + MutexUnlock(&coordinator->mutex); + + mASSERT_DEBUG(nextEvent > 0); + mTimingSchedule(timing, &lockstep->event, nextEvent); +} + +int32_t GBASIOLockstepTime(struct GBASIOLockstepPlayer* player) { + return mTimingCurrentTime(&player->driver->d.p->p->timing) - player->cycleOffset; +} + +void GBASIOLockstepCoordinatorWaitOnPlayers(struct GBASIOLockstepCoordinator* coordinator, struct GBASIOLockstepPlayer* player) { + mASSERT(!coordinator->waiting); + mASSERT(!player->asleep); + mASSERT(player->playerId == 0); + if (coordinator->nAttached < 2) { + return; + } + + _advanceCycle(coordinator, player); + mLOG(GBA_SIO, DEBUG, "Primary waiting for players to ack"); + coordinator->waiting = ((1 << coordinator->nAttached) - 1) & ~TARGET(player->playerId); + GBASIOLockstepPlayerSleep(player); + GBASIOLockstepCoordinatorWakePlayers(coordinator); + + _verifyAwake(coordinator); +} + +void GBASIOLockstepCoordinatorWakePlayers(struct GBASIOLockstepCoordinator* coordinator) { + int i; + for (i = 1; i < coordinator->nAttached; ++i) { + if (!coordinator->attachedPlayers[i]) { + continue; + } + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, coordinator->attachedPlayers[i]); + GBASIOLockstepPlayerWake(player); + } +} + +void GBASIOLockstepPlayerWake(struct GBASIOLockstepPlayer* player) { + if (!player->asleep) { + return; + } + player->asleep = false; + player->driver->user->wake(player->driver->user); +} + +void GBASIOLockstepCoordinatorAckPlayer(struct GBASIOLockstepCoordinator* coordinator, struct GBASIOLockstepPlayer* player) { + if (player->playerId == 0) { + return; + } + coordinator->waiting &= ~TARGET(player->playerId); + if (!coordinator->waiting) { + mLOG(GBA_SIO, DEBUG, "All players acked, waking primary"); + if (coordinator->transferActive) { + int i; + for (i = 0; i < coordinator->nAttached; ++i) { + if (!coordinator->attachedPlayers[i]) { + continue; + } + struct GBASIOLockstepPlayer* player = TableLookup(&coordinator->players, coordinator->attachedPlayers[i]); + player->dataReceived = true; + } + + coordinator->transferActive = false; + } + + struct GBASIOLockstepPlayer* runner = TableLookup(&coordinator->players, coordinator->attachedPlayers[0]); + GBASIOLockstepPlayerWake(runner); + } + GBASIOLockstepPlayerSleep(player); +} + +void GBASIOLockstepPlayerSleep(struct GBASIOLockstepPlayer* player) { + if (player->asleep) { + return; + } + player->asleep = true; + player->driver->user->sleep(player->driver->user); + player->driver->d.p->p->cpu->nextEvent = 0; + player->driver->d.p->p->earlyExit = true; +} + +size_t GBASIOLockstepCoordinatorAttached(struct GBASIOLockstepCoordinator* coordinator) { + size_t count; + MutexLock(&coordinator->mutex); + count = TableSize(&coordinator->players); + MutexUnlock(&coordinator->mutex); + return count; +} diff --git a/src/gba/video.c b/src/gba/video.c index 63e7115c1..749a29be0 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -70,7 +70,7 @@ void GBAVideoReset(struct GBAVideo* video) { } else { // TODO: Verify exact scanline on hardware video->vcount = 0x7E; - nextEvent = 117; + nextEvent = 120; } video->p->memory.io[GBA_REG(VCOUNT)] = video->vcount; diff --git a/src/platform/3ds/ctr-gpu.c b/src/platform/3ds/ctr-gpu.c index 4a95c4640..d153f84a0 100644 --- a/src/platform/3ds/ctr-gpu.c +++ b/src/platform/3ds/ctr-gpu.c @@ -160,7 +160,7 @@ void ctrActivateTexture(const C3D_Tex* texture) { .m = { // Rows are in the order w z y x, because ctrulib 0.0f, 0.0f, 0.0f, 1.0f / activeTexture->width, - 0.0f, 0.0f, 1.0f / activeTexture->height, 0.0f + 0.0f, 0.0f, 1.0f / activeTexture->height, 0.0f } }; C3D_FVUnifMtx2x4(GPU_GEOMETRY_SHADER, GSH_FVEC_textureMtx, &textureMtx); diff --git a/src/platform/example/client-server/server.c b/src/platform/example/client-server/server.c index 0dcec9b15..71472a05c 100644 --- a/src/platform/example/client-server/server.c +++ b/src/platform/example/client-server/server.c @@ -53,7 +53,7 @@ int main(int argc, char** argv) { SocketClose(sock); SocketSubsystemDeinit(); didFail = true; - goto cleanup; + goto cleanup; } // Run the server diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 232c4e6eb..d2a871546 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -834,7 +834,7 @@ static void _setupMaps(struct mCore* core) { void retro_reset(void) { core->reset(core); - mRumbleIntegratorInit(&rumble); + mRumbleIntegratorReset(&rumble); _setupMaps(core); } diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index b0cef1f0d..de8354c1c 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -253,7 +253,7 @@ static void mGLContextImageSize(struct VideoBackend* v, enum VideoLayer layer, i *height = context->layerDims[layer].height; } else { *width = context->imageSizes[layer].width; - *height = context->imageSizes[layer].height; + *height = context->imageSizes[layer].height; } } @@ -266,7 +266,7 @@ void mGLContextPostFrame(struct VideoBackend* v, enum VideoLayer layer, const vo context->activeTex ^= 1; glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]); } else { - glBindTexture(GL_TEXTURE_2D, context->layers[layer]); + glBindTexture(GL_TEXTURE_2D, context->layers[layer]); } int width = context->imageSizes[layer].width; diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index fa6716be9..e5ee98767 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -529,7 +529,7 @@ static void mGLES2ContextImageSize(struct VideoBackend* v, enum VideoLayer layer *height = context->layerDims[layer].height; } else { *width = context->imageSizes[layer].width; - *height = context->imageSizes[layer].height; + *height = context->imageSizes[layer].height; } } @@ -617,7 +617,7 @@ void mGLES2ShaderInit(struct mGLES2Shader* shader, const char* vs, const char* f if (shader->width > 0 && shader->height > 0) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, shader->width, shader->height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); } else { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); } glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, shader->tex, 0); diff --git a/src/platform/psp2/psp2-common.h b/src/platform/psp2/psp2-common.h index 7f34d1d10..dcb67b700 100644 --- a/src/platform/psp2/psp2-common.h +++ b/src/platform/psp2/psp2-common.h @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef PSP2_COMMON_H #define PSP2_COMMON_H - + #include #define PSP2_HORIZONTAL_PIXELS 960 diff --git a/src/platform/python/_builder.h b/src/platform/python/_builder.h index 0b51eb19d..c39fcf143 100644 --- a/src/platform/python/_builder.h +++ b/src/platform/python/_builder.h @@ -22,8 +22,7 @@ #define CXX_GUARD_END #define PYCPARSE -#define va_list void* - +typedef ... va_list; typedef int... time_t; typedef int... off_t; typedef ...* png_structp; @@ -46,7 +45,6 @@ void free(void*); #define PYEXPORT extern "Python+C" #include "platform/python/core.h" #include "platform/python/log.h" -#include "platform/python/sio.h" #include "platform/python/vfs-py.h" #undef PYEXPORT diff --git a/src/platform/python/_builder.py b/src/platform/python/_builder.py index 1defca537..f18a3c95e 100644 --- a/src/platform/python/_builder.py +++ b/src/platform/python/_builder.py @@ -45,7 +45,6 @@ ffi.set_source("mgba._pylib", """ #define PYEXPORT #include "platform/python/core.h" #include "platform/python/log.h" -#include "platform/python/sio.h" #include "platform/python/vfs-py.h" #undef PYEXPORT """, include_dirs=[incdir, srcdir], @@ -53,7 +52,7 @@ ffi.set_source("mgba._pylib", """ libraries=["mgba"], library_dirs=[bindir], runtime_library_dirs=[libdir], - sources=[os.path.join(pydir, path) for path in ["vfs-py.c", "core.c", "log.c", "sio.c"]]) + sources=[os.path.join(pydir, path) for path in ["vfs-py.c", "core.c", "log.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/mgba/gba.py b/src/platform/python/mgba/gba.py index 109de4d50..0c249cec5 100644 --- a/src/platform/python/mgba/gba.py +++ b/src/platform/python/mgba/gba.py @@ -52,72 +52,6 @@ class GBA(Core): super(GBA, self)._load() self.memory = GBAMemory(self._core, self._native.memory.romSize) - def attach_sio(self, link, mode=lib.GBA_SIO_MULTI): - self._sio.add(mode) - lib.GBASIOSetDriver(ffi.addressof(self._native.sio), link._native, mode) - - def __del__(self): - for mode in self._sio: - lib.GBASIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL, mode) - - -create_callback("GBASIOPythonDriver", "init") -create_callback("GBASIOPythonDriver", "deinit") -create_callback("GBASIOPythonDriver", "load") -create_callback("GBASIOPythonDriver", "unload") -create_callback("GBASIOPythonDriver", "writeRegister") - - -class GBASIODriver(object): - def __init__(self): - super(GBASIODriver, self).__init__() - - 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 write_register(self, address, value): - return value - - -class GBASIOJOYDriver(GBASIODriver): - RESET = lib.JOY_RESET - POLL = lib.JOY_POLL - TRANS = lib.JOY_TRANS - RECV = lib.JOY_RECV - - def __init__(self): - super(GBASIOJOYDriver, self).__init__() - - self._native = ffi.gc(lib.GBASIOJOYPythonDriverCreate(self._handle), lib.free) - - def send_command(self, cmd, data): - buffer = ffi.new('uint8_t[5]') - try: - buffer[0] = data[0] - buffer[1] = data[1] - buffer[2] = data[2] - buffer[3] = data[3] - buffer[4] = data[4] - except IndexError: - pass - - outlen = lib.GBASIOJOYSendCommand(self._native, cmd, buffer) - if outlen > 0 and outlen <= 5: - return bytes(buffer[0:outlen]) - return None - class GBAMemory(Memory): def __init__(self, core, romSize=lib.SIZE_CART0): diff --git a/src/platform/python/sio.c b/src/platform/python/sio.c deleted file mode 100644 index fbb407831..000000000 --- a/src/platform/python/sio.c +++ /dev/null @@ -1,88 +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 - -#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; -} - -struct GBASIODriver* GBASIOJOYPythonDriverCreate(void* pyobj) { - struct GBASIOPythonDriver* driver = malloc(sizeof(*driver)); - GBASIOJOYCreate(&driver->d); - driver->d.init = _pyGBASIOPythonDriverInitShim; - driver->d.deinit = _pyGBASIOPythonDriverDeinitShim; - driver->d.load = _pyGBASIOPythonDriverLoadShim; - driver->d.unload = _pyGBASIOPythonDriverUnloadShim; - - 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 deleted file mode 100644 index 23b059427..000000000 --- a/src/platform/python/sio.h +++ /dev/null @@ -1,42 +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/. */ -#ifdef M_CORE_GBA - -#include - -struct GBASIOPythonDriver { - struct GBASIODriver d; - void* pyobj; -}; - -struct GBASIODriver* GBASIOPythonDriverCreate(void* pyobj); -struct GBASIODriver* GBASIOJOYPythonDriverCreate(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/qt/BattleChipView.cpp b/src/platform/qt/BattleChipView.cpp index f7d6eb4c7..46b24e927 100644 --- a/src/platform/qt/BattleChipView.cpp +++ b/src/platform/qt/BattleChipView.cpp @@ -216,7 +216,7 @@ void BattleChipView::loadDeck() { error->show(); return; } - + QList newDeck; QStringList deck = ini.value("deck").toString().split(','); for (const auto& item : deck) { diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 041ad0830..3134512f2 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -59,7 +59,10 @@ if(APPLE) list(APPEND QT_DEFINES USE_SHARE_WIDGET) endif() - if(Qt6Widgets_VERSION) + # Allows building with Qt that was built with std::filesystem support, which requires macOS 15 + if(Qt6Widgets_VERSION AND MACOSX_SDK VERSION_GREATER_EQUAL 10.15) + set(MIN_VER 10.15) + elseif(Qt6Widgets_VERSION) set(MIN_VER 10.14) elseif(Qt5Widgets_VERSION MATCHES "^5.15") set(MIN_VER 10.13) @@ -74,8 +77,7 @@ if(APPLE) else() set(MIN_VER 10.8) endif() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=${MIN_VER}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=${MIN_VER}") + set(CMAKE_OSX_DEPLOYMENT_TARGET ${MIN_VER}) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") endif() @@ -427,6 +429,7 @@ set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${PR if(WIN32) set_target_properties(${BINARY_NAME}-qt PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") + target_link_libraries(${BINARY_NAME}-qt dwmapi) if(NOT MSVC) target_link_libraries(${BINARY_NAME}-qt -municode) endif() @@ -445,7 +448,7 @@ if(QT_STATIC) if(CMAKE_CROSSCOMPILING) set(QWINDOWS_DEPS ${QT}EventDispatcherSupport ${QT}FontDatabaseSupport ${QT}ThemeSupport ${QT}WindowsUIAutomationSupport) endif() - list(APPEND QT_LIBRARIES ${QT}::QWindowsIntegrationPlugin ${QWINDOWS_DEPS} amstrmid dwmapi uxtheme imm32 -static-libgcc -static-libstdc++) + list(APPEND QT_LIBRARIES ${QT}::QWindowsIntegrationPlugin ${QWINDOWS_DEPS} amstrmid uxtheme imm32 -static-libgcc -static-libstdc++) set_target_properties(${QT}::Core PROPERTIES INTERFACE_LINK_LIBRARIES "${QTPCRE};version;winmm;ssl;crypto;ws2_32;iphlpapi;crypt32;userenv;netapi32;wtsapi32") set_target_properties(${QT}::Gui PROPERTIES INTERFACE_LINK_LIBRARIES ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) elseif(APPLE) @@ -477,13 +480,21 @@ endif() if(APPLE) if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") get_target_property(QTCOCOA ${QT}::QCocoaIntegrationPlugin LOCATION) - get_target_property(COREAUDIO ${QT}::CoreAudioPlugin LOCATION) - get_target_property(QTAVFSERVICE ${QT}::AVFServicePlugin LOCATION) + if(${QT_V} LESS 6) + # QtMultimedia plugins were removed with Qt 6, skip checking for them + get_target_property(COREAUDIO ${QT}::CoreAudioPlugin LOCATION) + get_target_property(QTAVFSERVICE ${QT}::AVFServicePlugin LOCATION) + endif() + set(BUNDLE_PATH ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.app) target_sources(${BINARY_NAME}-qt PRIVATE "${PLUGINS}") + set_source_files_properties("${QTCOCOA}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) - set_source_files_properties("${COREAUDIO}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) - set_source_files_properties("${QTAVFSERVICE}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) + if(${QT_V} LESS 6) + set_source_files_properties("${COREAUDIO}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) + set_source_files_properties("${QTAVFSERVICE}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) + endif() + install(CODE " include(BundleUtilities) set(BU_CHMOD_BUNDLE_ITEMS ON) diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp index ccec60432..819b2479d 100644 --- a/src/platform/qt/ConfigController.cpp +++ b/src/platform/qt/ConfigController.cpp @@ -154,11 +154,12 @@ ConfigController::ConfigController(QObject* parent) mSubParserGraphicsInit(&m_subparsers[0], &m_graphicsOpts); m_subparsers[1].usage = "Frontend options:\n" - " --ecard FILE Scan an e-Reader card in the first loaded game\n" - " Can be passed multiple times for multiple cards\n" - " --mb FILE Boot a multiboot image with FILE inserted into the ROM slot" + " --ecard FILE Scan an e-Reader card in the first loaded game\n" + " Can be passed multiple times for multiple cards\n" + " --mb FILE Boot a multiboot image with FILE inserted into the ROM slot" #ifdef ENABLE_SCRIPTING - "\n --script FILE Script file to load on start" + "\n --script FILE Script file to load on start\n" + " Can be passed multiple times\n" #endif ; diff --git a/src/platform/qt/ConfigController.h b/src/platform/qt/ConfigController.h index 4c71c3943..4325a46ba 100644 --- a/src/platform/qt/ConfigController.h +++ b/src/platform/qt/ConfigController.h @@ -130,7 +130,7 @@ private: mGraphicsOpts m_graphicsOpts{}; std::array m_subparsers; bool m_parsed = false; - + QHash m_argvOptions; QHash m_optionSet; std::unique_ptr m_settings; diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 08b96bd81..2496f8039 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -423,8 +423,8 @@ bool CoreController::attachDolphin(const Address& address) { return false; } if (GBASIODolphinConnect(&m_dolphin, &address, 0, 0)) { - GBA* gba = static_cast(m_threadContext.core->board); - GBASIOSetDriver(&gba->sio, &m_dolphin.d, GBA_SIO_JOYBUS); + clearMultiplayerController(); + m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_LINK_PORT, &m_dolphin.d); return true; } return false; @@ -432,8 +432,8 @@ bool CoreController::attachDolphin(const Address& address) { void CoreController::detachDolphin() { if (platform() == mPLATFORM_GBA) { - GBA* gba = static_cast(m_threadContext.core->board); - GBASIOSetDriver(&gba->sio, nullptr, GBA_SIO_JOYBUS); + // TODO: Reattach to multiplayer controller + m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_LINK_PORT, NULL); } GBASIODolphinDestroy(&m_dolphin); } @@ -736,7 +736,7 @@ void CoreController::saveState(const QString& path, int flags) { vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size()); vf->close(vf); } - vf = VFileDevice::open(controller->m_statePath, O_WRONLY | O_CREAT | O_TRUNC); + vf = VFileDevice::open(controller->m_statePath, O_RDWR | O_CREAT | O_TRUNC); if (!vf) { return; } @@ -1094,7 +1094,7 @@ void CoreController::attachBattleChipGate() { Interrupter interrupter(this); clearMultiplayerController(); GBASIOBattlechipGateCreate(&m_battlechip); - m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_BATTLECHIP_GATE, &m_battlechip); + m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_LINK_PORT, &m_battlechip); } void CoreController::detachBattleChipGate() { @@ -1102,7 +1102,7 @@ void CoreController::detachBattleChipGate() { return; } Interrupter interrupter(this); - m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_BATTLECHIP_GATE, nullptr); + m_threadContext.core->setPeripheral(m_threadContext.core, mPERIPH_GBA_LINK_PORT, nullptr); } void CoreController::setBattleChipId(uint16_t id) { diff --git a/src/platform/qt/DebuggerConsoleController.cpp b/src/platform/qt/DebuggerConsoleController.cpp index dffd71bcf..716c31639 100644 --- a/src/platform/qt/DebuggerConsoleController.cpp +++ b/src/platform/qt/DebuggerConsoleController.cpp @@ -162,7 +162,7 @@ void DebuggerConsoleController::historyLoad() { if (line.endsWith("\r\n")) { line.chop(2); } else if (line.endsWith("\n")) { - line.chop(1); + line.chop(1); } history.append(QString::fromUtf8(line)); } diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index bb16b268e..ef525162f 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -62,7 +62,7 @@ public: virtual void setVideoProxy(std::shared_ptr proxy) { m_videoProxy = std::move(proxy); } std::shared_ptr videoProxy() { return m_videoProxy; } virtual VideoBackend* videoBackend(); - + signals: void drawingStarted(); void showCursor(); @@ -81,7 +81,7 @@ public slots: virtual void filter(bool filter); virtual void swapInterval(int interval) = 0; virtual void framePosted() = 0; - virtual void setShaders(struct VDir*) = 0; + virtual bool setShaders(struct VDir*) = 0; virtual void clearShaders() = 0; virtual void resizeContext() = 0; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 5b60eb46e..b3a7d62c8 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -192,6 +192,7 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) setAttribute(Qt::WA_NativeWindow); window()->windowHandle()->setFormat(format); windowHandle()->setSurfaceType(QSurface::OpenGLSurface); + windowHandle()->destroy(); windowHandle()->create(); #ifdef USE_SHARE_WIDGET @@ -314,7 +315,7 @@ bool DisplayGL::highestCompatible(QSurfaceFormat& format) { if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) { format.setVersion(1, 4); } else { - format.setVersion(1, 1); + format.setVersion(1, 1); } format.setOption(QSurfaceFormat::DeprecatedFunctions); if (DisplayGL::supportsFormat(format)) { @@ -461,8 +462,10 @@ void DisplayGL::framePosted() { QMetaObject::invokeMethod(m_painter.get(), "draw"); } -void DisplayGL::setShaders(struct VDir* shaders) { - QMetaObject::invokeMethod(m_painter.get(), "setShaders", Qt::BlockingQueuedConnection, Q_ARG(struct VDir*, shaders)); +bool DisplayGL::setShaders(struct VDir* shaders) { + bool success = false; + QMetaObject::invokeMethod(m_painter.get(), "setShaders", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, success), Q_ARG(struct VDir*, shaders)); + return success; } void DisplayGL::clearShaders() { @@ -995,10 +998,11 @@ void PainterGL::interrupt() { m_interrupter.interrupt(m_context); } -void PainterGL::setShaders(struct VDir* dir) { +bool PainterGL::setShaders(struct VDir* dir) { if (!supportsShaders()) { - return; + return false; } + bool success = false; #if defined(BUILD_GLES2) || defined(BUILD_GLES3) if (!m_started) { makeCurrent(); @@ -1008,7 +1012,9 @@ void PainterGL::setShaders(struct VDir* dir) { mGLES2ShaderDetach(reinterpret_cast(m_backend)); mGLES2ShaderFree(&m_shader); } - if (mGLES2ShaderLoad(&m_shader, dir)) { + + success = mGLES2ShaderLoad(&m_shader, dir); + if (success) { mGLES2ShaderAttach(reinterpret_cast(m_backend), static_cast(m_shader.passes), m_shader.nPasses); } @@ -1016,6 +1022,7 @@ void PainterGL::setShaders(struct VDir* dir) { m_gl->doneCurrent(); } #endif + return success; } void PainterGL::clearShaders() { diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 5b0aab2cd..18a7f7911 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -113,7 +113,7 @@ public slots: void filter(bool filter) override; void swapInterval(int interval) override; void framePosted() override; - void setShaders(struct VDir*) override; + bool setShaders(struct VDir*) override; void clearShaders() override; void resizeContext() override; void setVideoScale(int scale) override; @@ -184,7 +184,7 @@ public slots: void resizeContext(); void setBackgroundImage(const QImage&); - void setShaders(struct VDir*); + bool setShaders(struct VDir*); void clearShaders(); VideoShader* shaders(); QSize contentSize() const; diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index a6816a18b..8a3ec988e 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -41,7 +41,7 @@ public slots: void swapInterval(int) override {}; void filter(bool filter) override; void framePosted() override; - void setShaders(struct VDir*) override {} + bool setShaders(struct VDir*) override { return false; } void clearShaders() override {} void resizeContext() override; void setBackgroundImage(const QImage&) override; diff --git a/src/platform/qt/ForwarderController.cpp b/src/platform/qt/ForwarderController.cpp index 0934f1c69..abf2d27d0 100644 --- a/src/platform/qt/ForwarderController.cpp +++ b/src/platform/qt/ForwarderController.cpp @@ -160,7 +160,7 @@ void ForwarderController::gotManifest(QNetworkReply* reply) { mUpdaterGetUpdateForChannel(&context, platform.toUtf8().constData(), m_channel.toUtf8().constData(), &update); downloadBuild({bucket + update.path}); - mUpdaterDeinit(&context); + mUpdaterDeinit(&context); } void ForwarderController::downloadBuild(const QUrl& url) { @@ -174,7 +174,7 @@ void ForwarderController::downloadBuild(const QUrl& url) { .arg(extension)); if (!m_sourceFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { emit buildFailed(); - return; + return; } QNetworkReply* reply = GBAApp::app()->httpGet(url); diff --git a/src/platform/qt/ForwarderView.cpp b/src/platform/qt/ForwarderView.cpp index 922ef7c16..7261c0941 100644 --- a/src/platform/qt/ForwarderView.cpp +++ b/src/platform/qt/ForwarderView.cpp @@ -97,7 +97,7 @@ void ForwarderView::build() { if (m_ui.baseType->currentIndex() == 2) { m_controller.setBaseFilename(m_ui.baseFilename->text()); } else { - m_controller.clearBaseFilename(); + m_controller.clearBaseFilename(); } m_controller.startBuild(m_ui.outputFilename->text()); m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); @@ -235,14 +235,14 @@ void ForwarderView::updateProgress() { if (m_needsForwarderKit) { m_ui.progressBar->setValue(450 + m_downloadProgress * 50); } else { - m_ui.progressBar->setValue(m_downloadProgress * 100); + m_ui.progressBar->setValue(m_downloadProgress * 100); } break; case ForwarderController::BASE: if (m_needsForwarderKit) { m_ui.progressBar->setValue(500 + m_downloadProgress * 500); } else { - m_ui.progressBar->setValue(100 + m_downloadProgress * 900); + m_ui.progressBar->setValue(100 + m_downloadProgress * 900); } break; } diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp index fdc710c0c..90fc89170 100644 --- a/src/platform/qt/GBAApp.cpp +++ b/src/platform/qt/GBAApp.cpp @@ -370,7 +370,7 @@ void GBAApp::cleanupAfterUpdate() { void GBAApp::restartForUpdate() { QFileInfo updaterPath(m_updater.updateInfo().url.path()); QDir configDir(ConfigController::configDir()); - if (updaterPath.completeSuffix() == "exe") { + if (updaterPath.suffix() == "exe") { m_invokeOnExit = configDir.filePath(QLatin1String("update.exe")); } else { QFile updater(":/updater"); diff --git a/src/platform/qt/KeyEditor.cpp b/src/platform/qt/KeyEditor.cpp index 818b6c703..6ce94fc55 100644 --- a/src/platform/qt/KeyEditor.cpp +++ b/src/platform/qt/KeyEditor.cpp @@ -110,7 +110,9 @@ void KeyEditor::keyPressEvent(QKeyEvent* event) { m_key = Qt::Key_unknown; } m_lastKey.start(KEY_TIME); - setValue(ShortcutController::isModifierKey(event->key()) ? event->modifiers() : event->key() | event->modifiers()); + setValue(ShortcutController::isModifierKey(event->key()) ? + event->key() : + event->key() | (event->modifiers() & ~Qt::KeypadModifier)); } event->accept(); } diff --git a/src/platform/qt/MemorySearch.cpp b/src/platform/qt/MemorySearch.cpp index 34f87fe14..7a8446887 100644 --- a/src/platform/qt/MemorySearch.cpp +++ b/src/platform/qt/MemorySearch.cpp @@ -21,7 +21,7 @@ MemorySearch::MemorySearch(std::shared_ptr controller, QWidget* mCoreMemorySearchResultsInit(&m_results, 0); connect(m_ui.search, &QPushButton::clicked, this, &MemorySearch::search); - connect(m_ui.value, &QLineEdit::returnPressed, this, &MemorySearch::search); + connect(m_ui.value, &QLineEdit::returnPressed, this, &MemorySearch::search); connect(m_ui.searchWithin, &QPushButton::clicked, this, &MemorySearch::searchWithin); connect(m_ui.refresh, &QPushButton::clicked, this, &MemorySearch::refresh); connect(m_ui.numHex, &QPushButton::clicked, this, &MemorySearch::refresh); diff --git a/src/platform/qt/MultiplayerController.cpp b/src/platform/qt/MultiplayerController.cpp index 149df2386..e95498fad 100644 --- a/src/platform/qt/MultiplayerController.cpp +++ b/src/platform/qt/MultiplayerController.cpp @@ -7,6 +7,7 @@ #include "CoreController.h" #include "LogController.h" +#include "utils.h" #ifdef M_CORE_GBA #include @@ -27,8 +28,14 @@ MultiplayerController::Player::Player(CoreController* coreController) int MultiplayerController::Player::id() const { switch (controller->platform()) { #ifdef M_CORE_GBA - case mPLATFORM_GBA: - return node.gba->id; + case mPLATFORM_GBA: { + int id = node.gba->d.deviceId(&node.gba->d); + if (id >= 0) { + return id; + } else { + return preferredId; + } + } #endif #ifdef M_CORE_GB case mPLATFORM_GB: @@ -89,25 +96,7 @@ MultiplayerController::MultiplayerController() { switch (player->controller->platform()) { #ifdef M_CORE_GBA case mPLATFORM_GBA: - if (!id) { - for (int i = 1; i < controller->m_players.count(); ++i) { - player = controller->player(i); - if (player->node.gba->d.p->mode > GBA_SIO_MULTI) { - player->controller->setSync(true); - continue; - } - player->controller->setSync(false); - player->cyclesPosted += cycles; - if (player->awake < 1) { - player->node.gba->nextEvent += player->cyclesPosted; - } - mCoreThreadStopWaiting(player->controller->thread()); - player->awake = 1; - } - } else { - player->controller->setSync(true); - player->cyclesPosted += cycles; - } + abort(); break; #endif #ifdef M_CORE_GB @@ -169,7 +158,6 @@ MultiplayerController::MultiplayerController() { switch (player->controller->platform()) { #ifdef M_CORE_GBA case mPLATFORM_GBA: - player->cyclesPosted += reinterpret_cast(lockstep)->players[0]->eventDiff; break; #endif #ifdef M_CORE_GB @@ -184,7 +172,6 @@ MultiplayerController::MultiplayerController() { switch (player->controller->platform()) { #ifdef M_CORE_GBA case mPLATFORM_GBA: - player->node.gba->nextEvent += player->cyclesPosted; break; #endif #ifdef M_CORE_GB @@ -205,6 +192,9 @@ MultiplayerController::MultiplayerController() { MultiplayerController::~MultiplayerController() { mLockstepDeinit(&m_lockstep); + if (m_platform == mPLATFORM_GBA) { + GBASIOLockstepCoordinatorDeinit(&m_gbaCoordinator); + } } bool MultiplayerController::attachGame(CoreController* controller) { @@ -214,11 +204,12 @@ bool MultiplayerController::attachGame(CoreController* controller) { interrupters.append(p.controller); } - if (m_lockstep.attached == 0) { + bool doDelayedAttach = false; + if (m_platform == mPLATFORM_NONE) { switch (controller->platform()) { #ifdef M_CORE_GBA case mPLATFORM_GBA: - GBASIOLockstepInit(&m_gbaLockstep); + GBASIOLockstepCoordinatorInit(&m_gbaCoordinator); break; #endif #ifdef M_CORE_GB @@ -240,28 +231,50 @@ bool MultiplayerController::attachGame(CoreController* controller) { } Player player{controller}; + for (int i = 0; i < MAX_GBAS; ++i) { + if (m_claimedIds & (1 << i)) { + continue; + } + player.preferredId = i; + m_claimedIds |= 1 << i; + break; + } switch (controller->platform()) { #ifdef M_CORE_GBA case mPLATFORM_GBA: { - if (m_lockstep.attached >= MAX_GBAS) { + if (attached() >= MAX_GBAS) { return false; } - GBA* gba = static_cast(thread->core->board); + GBASIOLockstepDriver* node = new GBASIOLockstepDriver; + LockstepUser* user = new LockstepUser; + mLockstepThreadUserInit(user, thread); + user->controller = this; + user->pid = m_nextPid; + user->d.requestedId = [](mLockstepUser* ctx) { + mLockstepThreadUser* tctx = reinterpret_cast(ctx); + LockstepUser* user = static_cast(tctx); + MultiplayerController* controller = user->controller; + const auto iter = controller->m_pids.find(user->pid); + if (iter == controller->m_pids.end()) { + return -1; + } + const Player& p = iter.value(); + return p.preferredId; + }; - GBASIOLockstepNode* node = new GBASIOLockstepNode; - GBASIOLockstepNodeCreate(node); - GBASIOLockstepAttachNode(&m_gbaLockstep, node); + GBASIOLockstepDriverCreate(node, &user->d); player.node.gba = node; - GBASIOSetDriver(&gba->sio, &node->d, GBA_SIO_MULTI); - GBASIOSetDriver(&gba->sio, &node->d, GBA_SIO_NORMAL_32); + if (m_pids.size()) { + doDelayedAttach = true; + } break; } #endif #ifdef M_CORE_GB case mPLATFORM_GB: { - if (m_lockstep.attached >= 2) { + if (attached() >= 2) { return false; } @@ -271,6 +284,7 @@ bool MultiplayerController::attachGame(CoreController* controller) { GBSIOLockstepNodeCreate(node); GBSIOLockstepAttachNode(&m_gbLockstep, node); player.node.gb = node; + player.attached = true; GBSIOSetDriver(&gb->sio, &node->d); break; @@ -281,7 +295,7 @@ bool MultiplayerController::attachGame(CoreController* controller) { } QPair path(controller->path(), controller->baseDirectory()); - int claimed = m_claimed[path]; + int claimed = m_claimedSaves[path]; int saveId = 0; mCoreConfigGetIntValue(&controller->thread()->core->config, "savePlayerId", &saveId); @@ -304,12 +318,25 @@ bool MultiplayerController::attachGame(CoreController* controller) { } else { player.saveId = 1; } - m_claimed[path] |= 1 << (player.saveId - 1); + m_claimedSaves[path] |= 1 << (player.saveId - 1); m_pids.insert(m_nextPid, player); ++m_nextPid; fixOrder(); + if (doDelayedAttach) { + for (auto pid: m_players) { + Player& player = m_pids.find(pid).value(); + if (player.attached) { + continue; + } + struct mCore* core = player.controller->thread()->core; + GBASIOLockstepCoordinatorAttach(&m_gbaCoordinator, player.node.gba); + core->setPeripheral(core, mPERIPH_GBA_LINK_PORT, &player.node.gba->d); + player.attached = true; + } + } + emit gameAttached(); return true; } @@ -328,8 +355,7 @@ void MultiplayerController::detachGame(CoreController* controller) { for (int i = 0; i < m_players.count(); ++i) { Player* p = player(i); if (!p) { - LOG(QT, ERROR) << tr("Trying to detach a multiplayer player that's not attached"); - return; + continue; } CoreController* playerController = p->controller; if (playerController == controller) { @@ -337,17 +363,24 @@ void MultiplayerController::detachGame(CoreController* controller) { } interrupters.append(playerController); } + if (pid < 0) { + LOG(QT, WARN) << tr("Trying to detach a multiplayer player that's not attached"); + return; + } switch (controller->platform()) { #ifdef M_CORE_GBA case mPLATFORM_GBA: { GBA* gba = static_cast(thread->core->board); - GBASIOLockstepNode* node = reinterpret_cast(gba->sio.drivers.multiplayer); - GBASIOSetDriver(&gba->sio, nullptr, GBA_SIO_MULTI); - GBASIOSetDriver(&gba->sio, nullptr, GBA_SIO_NORMAL_32); - if (node) { - GBASIOLockstepDetachNode(&m_gbaLockstep, node); - delete node; + Player& p = m_pids.find(pid).value(); + GBASIODriver* node = gba->sio.driver; + if (node == &p.node.gba->d) { + thread->core->setPeripheral(thread->core, mPERIPH_GBA_LINK_PORT, NULL); } + if (p.attached) { + GBASIOLockstepCoordinatorDetach(&m_gbaCoordinator, p.node.gba); + } + delete reinterpret_cast(p.node.gba->user); + delete p.node.gba; break; } #endif @@ -371,16 +404,25 @@ void MultiplayerController::detachGame(CoreController* controller) { QPair path(controller->path(), controller->baseDirectory()); Player& p = m_pids.find(pid).value(); if (!p.saveId) { - LOG(QT, ERROR) << "Clearing invalid save ID"; + LOG(QT, WARN) << tr("Clearing invalid save ID"); } else { - m_claimed[path] &= ~(1 << (p.saveId - 1)); - if (!m_claimed[path]) { - m_claimed.remove(path); + m_claimedSaves[path] &= ~(1 << (p.saveId - 1)); + if (!m_claimedSaves[path]) { + m_claimedSaves.remove(path); } } + if (p.preferredId < 0) { + LOG(QT, WARN) << tr("Clearing invalid preferred ID"); + } else { + m_claimedIds &= ~(1 << p.preferredId); + } + m_pids.remove(pid); if (m_pids.size() == 0) { + if (m_platform == mPLATFORM_GBA) { + GBASIOLockstepCoordinatorDeinit(&m_gbaCoordinator); + } m_platform = mPLATFORM_NONE; } else { fixOrder(); @@ -417,8 +459,17 @@ int MultiplayerController::saveId(CoreController* controller) const { } int MultiplayerController::attached() { - int num; - num = m_lockstep.attached; + int num = 0; + switch (m_platform) { + case mPLATFORM_GB: + num = m_lockstep.attached; + break; + case mPLATFORM_GBA: + num = saturateCast(GBASIOLockstepCoordinatorAttached(&m_gbaCoordinator)); + break; + default: + break; + } return num; } @@ -453,12 +504,13 @@ void MultiplayerController::fixOrder() { switch (m_platform) { #ifdef M_CORE_GBA case mPLATFORM_GBA: - for (int pid : m_pids.keys()) { + // TODO: fix + /*for (int pid : m_pids.keys()) { Player& p = m_pids.find(pid).value(); GBA* gba = static_cast(p.controller->thread()->core->board); - GBASIOLockstepNode* node = reinterpret_cast(gba->sio.drivers.multiplayer); - m_players[node->id] = pid; - } + GBASIOLockstepDriver* node = reinterpret_cast(gba->sio.driver); + m_players[node->d.deviceId(&node->d)] = pid; + }*/ break; #endif #ifdef M_CORE_GB diff --git a/src/platform/qt/MultiplayerController.h b/src/platform/qt/MultiplayerController.h index b5582319f..666cb390b 100644 --- a/src/platform/qt/MultiplayerController.h +++ b/src/platform/qt/MultiplayerController.h @@ -49,7 +49,7 @@ signals: private: union Node { GBSIOLockstepNode* gb; - GBASIOLockstepNode* gba; + GBASIOLockstepDriver* gba; }; struct Player { Player(CoreController* controller); @@ -63,6 +63,12 @@ private: int32_t cyclesPosted = 0; unsigned waitMask = 0; int saveId = 1; + int preferredId = 0; + bool attached = false; + }; + struct LockstepUser : mLockstepThreadUser { + MultiplayerController* controller; + int pid; }; Player* player(int id); @@ -73,18 +79,20 @@ private: mLockstep m_lockstep; #ifdef M_CORE_GB GBSIOLockstep m_gbLockstep; -#endif -#ifdef M_CORE_GBA - GBASIOLockstep m_gbaLockstep; #endif }; +#ifdef M_CORE_GBA + GBASIOLockstepCoordinator m_gbaCoordinator; +#endif + mPlatform m_platform = mPLATFORM_NONE; int m_nextPid = 0; + int m_claimedIds = 0; QHash m_pids; QList m_players; QMutex m_lock; - QHash, int> m_claimed; + QHash, int> m_claimedSaves; }; } diff --git a/src/platform/qt/ROMInfo.cpp b/src/platform/qt/ROMInfo.cpp index 9f2537439..25aafeda8 100644 --- a/src/platform/qt/ROMInfo.cpp +++ b/src/platform/qt/ROMInfo.cpp @@ -24,6 +24,7 @@ ROMInfo::ROMInfo(std::shared_ptr controller, QWidget* parent) const NoIntroDB* db = GBAApp::app()->gameDB(); #endif uint32_t crc32 = 0; + uint8_t md5[16]{}; CoreController::Interrupter interrupter(controller); mCore* core = controller->thread()->core; @@ -39,6 +40,7 @@ ROMInfo::ROMInfo(std::shared_ptr controller, QWidget* parent) m_ui.version->setText(QString::number(info.version)); core->checksum(core, &crc32, mCHECKSUM_CRC32); + core->checksum(core, &md5, mCHECKSUM_MD5); m_ui.size->setText(QString::number(core->romSize(core)) + tr(" bytes")); @@ -63,6 +65,10 @@ ROMInfo::ROMInfo(std::shared_ptr controller, QWidget* parent) m_ui.name->setText(tr("(unknown)")); } + m_ui.md5->setText(QString::asprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + md5[0x0], md5[0x1], md5[0x2], md5[0x3], md5[0x4], md5[0x5], md5[0x6], md5[0x7], + md5[0x8], md5[0x9], md5[0xA], md5[0xB], md5[0xC], md5[0xD], md5[0xE], md5[0xF])); + QString savePath = controller->savePath(); if (!savePath.isEmpty()) { m_ui.savefile->setText(savePath); diff --git a/src/platform/qt/ROMInfo.ui b/src/platform/qt/ROMInfo.ui index 655d4810f..a4a179ca8 100644 --- a/src/platform/qt/ROMInfo.ui +++ b/src/platform/qt/ROMInfo.ui @@ -6,154 +6,186 @@ 0 0 - 178 - 198 + 180 + 298 ROM Info - + QLayout::SetFixedSize - - QFormLayout::FieldsStayAtSizeHint - - - - Game name: - - - - - - - {NAME} - - - true - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + File information + + + + + Game name: + + + + + + + {NAME} + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + File size: + + + + + + + {SIZE} + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + CRC32: + + + + + + + {CRC} + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + MD5 + + + + + + + {MD5} + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Save file: + + + + + + + {SAVEFILE} + + + + - - - Internal name: - - - - - - - {TITLE} - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Game ID: - - - - - - - {ID} - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Maker Code: - - - - - - - {MAKER} - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Revision: - - - - - - - {VERSION} - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - File size: - - - - - - - {SIZE} - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - CRC32: - - - - - - - {CRC} - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Save file: - - - - - - - {SAVEFILE} + + + ROM header + + + + + Internal name: + + + + + + + {TITLE} + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Game ID: + + + + + + + {ID} + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Maker Code: + + + + + + + {MAKER} + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Revision: + + + + + + + {VERSION} + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + diff --git a/src/platform/qt/SaveConverter.cpp b/src/platform/qt/SaveConverter.cpp index 974f31af0..205a7563d 100644 --- a/src/platform/qt/SaveConverter.cpp +++ b/src/platform/qt/SaveConverter.cpp @@ -86,18 +86,18 @@ void SaveConverter::refreshInputTypes() { m_validSaves.clear(); m_ui.inputType->clear(); if (m_ui.inputFile->text().isEmpty()) { - m_ui.inputType->addItem(tr("No file selected")); + m_ui.inputType->addItem(tr("No file selected")); m_ui.inputType->setEnabled(false); return; } std::shared_ptr vf = std::make_shared(m_ui.inputFile->text(), QIODevice::ReadOnly); if (!vf->isOpen()) { - m_ui.inputType->addItem(tr("Could not open file")); + m_ui.inputType->addItem(tr("Could not open file")); m_ui.inputType->setEnabled(false); return; } - + detectFromSavestate(*vf); detectFromSize(vf); detectFromHeaders(vf); @@ -108,7 +108,7 @@ void SaveConverter::refreshInputTypes() { if (m_validSaves.count()) { m_ui.inputType->setEnabled(true); } else { - m_ui.inputType->addItem(tr("No valid formats found")); + m_ui.inputType->addItem(tr("No valid formats found")); m_ui.inputType->setEnabled(false); } } @@ -127,7 +127,7 @@ void SaveConverter::refreshOutputTypes() { if (m_validOutputs.count()) { m_ui.outputType->setEnabled(true); } else { - m_ui.outputType->addItem(tr("No valid conversions found")); + m_ui.outputType->addItem(tr("No valid conversions found")); m_ui.outputType->setEnabled(false); } checkCanConvert(); @@ -494,21 +494,21 @@ SaveConverter::AnnotatedSave::operator QString() const { if (size & 0xFF) { typeFormat = QCoreApplication::translate("QGBA::SaveConverter", "%1 SRAM + RTC"); } else { - typeFormat = QCoreApplication::translate("QGBA::SaveConverter", "%1 SRAM"); + typeFormat = QCoreApplication::translate("QGBA::SaveConverter", "%1 SRAM"); } break; case GB_MBC2: if (size == 0x100) { typeFormat = QCoreApplication::translate("QGBA::SaveConverter", "packed MBC2"); } else { - typeFormat = QCoreApplication::translate("QGBA::SaveConverter", "unpacked MBC2"); + typeFormat = QCoreApplication::translate("QGBA::SaveConverter", "unpacked MBC2"); } break; case GB_MBC6: if (size == GB_SIZE_MBC6_FLASH) { typeFormat = QCoreApplication::translate("QGBA::SaveConverter", "MBC6 flash"); } else if (size > GB_SIZE_MBC6_FLASH) { - typeFormat = QCoreApplication::translate("QGBA::SaveConverter", "MBC6 combined SRAM + flash"); + typeFormat = QCoreApplication::translate("QGBA::SaveConverter", "MBC6 combined SRAM + flash"); } else { typeFormat = QCoreApplication::translate("QGBA::SaveConverter", "MBC6 SRAM"); } diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 66c125466..a72bf0d7b 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -357,7 +357,7 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC }); QLocale englishLocale("en"); - m_ui.languages->addItem(englishLocale.nativeLanguageName(), englishLocale); + m_ui.languages->addItem("English", englishLocale); QDir ts(":/translations/"); for (auto& name : ts.entryList()) { if (!name.endsWith(".qm") || !name.startsWith(binaryName)) { @@ -367,7 +367,17 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC if (locale.language() == QLocale::English) { continue; } - m_ui.languages->addItem(locale.nativeLanguageName(), locale); + QString endonym = locale.nativeLanguageName(); + // Manualy handle some cases that Qt can't seem to do properly + if (locale.language() == QLocale::Spanish) { + // Qt insists this is called español de España, regardless of if we set the country + endonym = u8"Español"; + } else if (locale.language() == QLocale::Portuguese && locale.country() == QLocale::Brazil) { + // Qt insists that Brazilian Portuguese is just called Português + endonym = u8"Português brasileiro"; + } + endonym[0] = endonym[0].toUpper(); + m_ui.languages->addItem(endonym, locale); if (locale.bcp47Name() == QLocale().bcp47Name()) { m_ui.languages->setCurrentIndex(m_ui.languages->count() - 1); } @@ -397,29 +407,42 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC shortcutView->setController(shortcutController); shortcutView->setInputController(inputController); addPage(tr("Shortcuts"), shortcutView, Page::SHORTCUTS); + +#if defined(BUILD_GLES2) || defined(USE_EPOXY) + m_dummyShader = new QLabel(tr("Shaders are not supported when the display driver is not OpenGL.\n\n" + "If it is set to OpenGL and you still see this, your graphics card or drivers may be too old.")); + m_dummyShader->setWordWrap(true); + m_dummyShader->setAlignment(Qt::AlignCenter); + addPage(tr("Shaders"), m_dummyShader, Page::SHADERS); +#endif } SettingsView::~SettingsView() { -#if defined(BUILD_GL) || defined(BUILD_GLES2) - setShaderSelector(nullptr); +#if defined(BUILD_GLES2) || defined(USE_EPOXY) + if (m_shader) { + m_shader->setParent(nullptr); + } #endif } void SettingsView::setShaderSelector(ShaderSelector* shaderSelector) { -#if defined(BUILD_GL) || defined(BUILD_GLES2) - if (m_shader) { - auto items = m_ui.tabs->findItems(tr("Shaders"), Qt::MatchFixedString); - for (const auto& item : items) { - m_ui.tabs->removeItemWidget(item); - } - m_ui.stackedWidget->removeWidget(m_shader); - m_shader->setParent(nullptr); +#if defined(BUILD_GLES2) || defined(USE_EPOXY) + auto items = m_ui.tabs->findItems(tr("Shaders"), Qt::MatchFixedString); + for (QListWidgetItem* item : items) { + delete item; + } + if (!m_shader) { + m_ui.stackedWidget->removeWidget(m_dummyShader); + } else { + QObject::disconnect(m_shader, nullptr, this, nullptr); } m_shader = shaderSelector; + QObject::connect(this, &SettingsView::saveSettingsRequested, m_shader, &ShaderSelector::saveSettings); + QObject::connect(m_ui.buttonBox, &QDialogButtonBox::rejected, m_shader, &ShaderSelector::revert); if (shaderSelector) { - m_ui.stackedWidget->addWidget(m_shader); - m_ui.tabs->addItem(tr("Shaders")); - connect(m_ui.buttonBox, &QDialogButtonBox::accepted, m_shader, &ShaderSelector::saved); + addPage(tr("Shaders"), m_shader, Page::SHADERS); + } else { + addPage(tr("Shaders"), m_dummyShader, Page::SHADERS); } #endif } @@ -579,7 +602,6 @@ void SettingsView::updateConfig() { if (displayDriver != m_controller->getQtOption("displayDriver")) { m_controller->setQtOption("displayDriver", displayDriver); Display::setDriver(static_cast(displayDriver.toInt())); - setShaderSelector(nullptr); emit displayDriverChanged(); } @@ -675,6 +697,8 @@ void SettingsView::updateConfig() { saveSetting("gb.colors", gbColors); #endif + emit saveSettingsRequested(); + m_controller->write(); emit pathsChanged(); diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h index 010b4ef0d..1e45ec87a 100644 --- a/src/platform/qt/SettingsView.h +++ b/src/platform/qt/SettingsView.h @@ -51,8 +51,6 @@ public: SettingsView(ConfigController* controller, InputController* inputController, ShortcutController* shortcutController, LogController* logController, QWidget* parent = nullptr); ~SettingsView(); - void setShaderSelector(ShaderSelector* shaderSelector); - signals: void biosLoaded(int platform, const QString&); void audioDriverChanged(); @@ -63,9 +61,11 @@ signals: void pathsChanged(); void languageChanged(); void libraryCleared(); + void saveSettingsRequested(); public slots: void selectPage(Page); + void setShaderSelector(ShaderSelector* shaderSelector); private slots: void selectBios(QLineEdit*); @@ -81,6 +81,7 @@ private: ConfigController* m_controller; InputController* m_input; ShaderSelector* m_shader = nullptr; + QLabel* m_dummyShader; LogConfigModel m_logModel; QTimer m_checkTimer; diff --git a/src/platform/qt/ShaderSelector.cpp b/src/platform/qt/ShaderSelector.cpp index 44bca3855..204ad7252 100644 --- a/src/platform/qt/ShaderSelector.cpp +++ b/src/platform/qt/ShaderSelector.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -49,6 +50,28 @@ ShaderSelector::~ShaderSelector() { clear(); } +void ShaderSelector::saveSettings() { + QString oldPath = m_config->getOption("shader"); + if (oldPath != m_shaderPath) { + if (m_shaderPath.isEmpty()) { + clearShader(true); + } else { + loadShader(m_shaderPath, true); + } + } + emit saveSettingsRequested(); +} + +void ShaderSelector::revert() { + QString shaderPath = m_config->getOption("shader"); + if (shaderPath.isEmpty()) { + clearShader(); + } else { + loadShader(shaderPath); + emit reset(); + } +} + void ShaderSelector::clear() { m_ui.shaderName->setText(tr("No shader active")); m_ui.description->clear(); @@ -66,7 +89,8 @@ void ShaderSelector::selectShader() { #if !defined(USE_LIBZIP) && !defined(USE_MINIZIP) QString name = GBAApp::app()->getOpenDirectoryName(this, tr("Load shader"), path.absolutePath()); #else - QString name = GBAApp::app()->getOpenFileName(this, tr("Load shader"), "mGBA Shaders (*.shader)", path.absolutePath()); + QString filters = QStringLiteral("%1 (*.shader manifest.ini)").arg(tr("mGBA Shaders")); + QString name = GBAApp::app()->getOpenFileName(this, tr("Load shader"), filters, path.absolutePath()); #endif if (!name.isNull()) { loadShader(name); @@ -74,25 +98,39 @@ void ShaderSelector::selectShader() { } } -void ShaderSelector::loadShader(const QString& path) { - VDir* shader = VFileDevice::openDir(path); - if (!shader) { - shader = VFileDevice::openArchive(path); +void ShaderSelector::loadShader(const QString& path, bool saveToSettings) { + static const QString manifestIni = "/manifest.ini"; + QString shaderPath = path; + if (shaderPath.endsWith(manifestIni)) { + shaderPath.chop(manifestIni.length()); } + VDir* shader = VFileDevice::openDir(shaderPath); if (!shader) { - return; + shader = VFileDevice::openArchive(shaderPath); + } + bool error = !shader || !m_display->setShaders(shader); + if (!error) { + if (saveToSettings) { + m_config->setOption("shader", shaderPath); + } + m_shaderPath = shaderPath; + refreshShaders(); + } + if (shader) { + shader->close(shader); + } + if (error) { + QMessageBox::warning(this, tr("Error loading shader"), tr("The shader \"%1\" could not be loaded successfully.").arg(shaderPath)); } - m_display->setShaders(shader); - shader->close(shader); - m_shaderPath = path; - m_config->setOption("shader", m_shaderPath); } -void ShaderSelector::clearShader() { +void ShaderSelector::clearShader(bool saveToSettings) { m_display->clearShaders(); - refreshShaders(); m_shaderPath = ""; - m_config->setOption("shader", m_shaderPath); + if (saveToSettings) { + m_config->setOption("shader", m_shaderPath); + } + refreshShaders(); } void ShaderSelector::refreshShaders() { @@ -117,7 +155,7 @@ void ShaderSelector::refreshShaders() { m_ui.author->clear(); } - disconnect(this, &ShaderSelector::saved, 0, 0); + disconnect(this, &ShaderSelector::saveSettingsRequested, 0, 0); disconnect(this, &ShaderSelector::reset, 0, 0); disconnect(this, &ShaderSelector::resetToDefault, 0, 0); @@ -156,7 +194,7 @@ void ShaderSelector::addUniform(QGridLayout* settings, const QString& section, c connect(f, static_cast(&QDoubleSpinBox::valueChanged), [value](double v) { *value = v; }); - connect(this, &ShaderSelector::saved, [this, section, name, f]() { + connect(this, &ShaderSelector::saveSettingsRequested, [this, section, name, f]() { m_config->setQtOption(name, f->value(), section); }); connect(this, &ShaderSelector::reset, [this, section, name, f]() { @@ -190,7 +228,7 @@ void ShaderSelector::addUniform(QGridLayout* settings, const QString& section, c connect(i, static_cast(&QSpinBox::valueChanged), [value](int v) { *value = v; }); - connect(this, &ShaderSelector::saved, [this, section, name, i]() { + connect(this, &ShaderSelector::saveSettingsRequested, [this, section, name, i]() { m_config->setQtOption(name, i->value(), section); }); connect(this, &ShaderSelector::reset, [this, section, name, i]() { @@ -269,10 +307,6 @@ void ShaderSelector::buttonPressed(QAbstractButton* button) { case QDialogButtonBox::Reset: emit reset(); break; - case QDialogButtonBox::Ok: - emit saved(); - close(); - break; case QDialogButtonBox::RestoreDefaults: emit resetToDefault(); break; diff --git a/src/platform/qt/ShaderSelector.h b/src/platform/qt/ShaderSelector.h index 4dc9214af..4ba074236 100644 --- a/src/platform/qt/ShaderSelector.h +++ b/src/platform/qt/ShaderSelector.h @@ -28,17 +28,19 @@ public: ~ShaderSelector(); public slots: + void saveSettings(); void refreshShaders(); void clear(); + void revert(); private slots: void selectShader(); - void loadShader(const QString& path); - void clearShader(); + void loadShader(const QString& path, bool saveToSettings = false); + void clearShader(bool saveToSettings = false); void buttonPressed(QAbstractButton*); signals: - void saved(); + void saveSettingsRequested(); void reset(); void resetToDefault(); diff --git a/src/platform/qt/TilePainter.cpp b/src/platform/qt/TilePainter.cpp index b8c8075e6..88f7b51b1 100644 --- a/src/platform/qt/TilePainter.cpp +++ b/src/platform/qt/TilePainter.cpp @@ -81,7 +81,7 @@ void TilePainter::setTileCount(int tiles) { int w = width() / m_size; int h = (tiles + w - 1) * m_size / w; setMinimumSize(m_size, h - (h % m_size)); - } else { + } else { int w = minimumSize().width() / m_size; if (!w) { w = 1; diff --git a/src/platform/qt/TileView.cpp b/src/platform/qt/TileView.cpp index dbfc15531..6ba409a0a 100644 --- a/src/platform/qt/TileView.cpp +++ b/src/platform/qt/TileView.cpp @@ -132,7 +132,7 @@ void TileView::updateTilesGBA(bool force) { if (m_ui.tilesBg->isChecked()) { m_ui.tiles->setTileCount(1024); } else if (m_ui.tilesObj->isChecked()) { - m_ui.tiles->setTileCount(512); + m_ui.tiles->setTileCount(512); } else { m_ui.tiles->setTileCount(1536); } @@ -165,7 +165,7 @@ void TileView::updateTilesGBA(bool force) { if (m_ui.tilesBg->isChecked()) { m_ui.tiles->setTileCount(2048); } else if (m_ui.tilesObj->isChecked()) { - m_ui.tiles->setTileCount(1024); + m_ui.tiles->setTileCount(1024); } else { m_ui.tiles->setTileCount(3072); } diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 010dde782..bc956fcc4 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -14,6 +14,10 @@ #include #include +#ifdef Q_OS_WIN +#include +#endif + #ifdef USE_SQLITE3 #include "ArchiveInspector.h" #include "library/LibraryController.h" @@ -123,7 +127,7 @@ Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWi if (value.toBool()) { attachWidget(m_libraryView); } else { - attachWidget(m_screenWidget); + attachWidget(m_screenWidget); } } }, this); @@ -132,8 +136,8 @@ Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWi ConfigOption* showFilenameInLibrary = m_config->addOption("showFilenameInLibrary"); showFilenameInLibrary->connect([this](const QVariant& value) { m_libraryView->setShowFilename(value.toBool()); - }, this); - m_config->updateOption("showFilenameInLibrary"); + }, this); + m_config->updateOption("showFilenameInLibrary"); ConfigOption* libraryStyle = m_config->addOption("libraryStyle"); libraryStyle->connect([this](const QVariant& value) { m_libraryView->setViewStyle(static_cast(value.toInt())); @@ -546,6 +550,7 @@ void Window::openSettingsWindow(SettingsView::Page page) { #ifdef USE_SQLITE3 connect(settingsWindow, &SettingsView::libraryCleared, m_libraryView, &LibraryController::clear); #endif + connect(this, &Window::shaderSelectorAdded, settingsWindow, &SettingsView::setShaderSelector); openView(settingsWindow); settingsWindow->selectPage(page); } @@ -717,6 +722,11 @@ void Window::showEvent(QShowEvent* event) { return; } m_wasOpened = true; +#ifdef Q_OS_WIN + HWND hwnd = reinterpret_cast(winId()); + DWM_WINDOW_CORNER_PREFERENCE cornerPref = DWMWCP_DONOTROUND; + DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPref, sizeof(cornerPref)); +#endif if (m_initialSize.isValid()) { resizeFrame(m_initialSize); } @@ -1041,7 +1051,12 @@ void Window::reloadDisplayDriver() { } #if defined(BUILD_GL) || defined(BUILD_GLES2) m_shaderView.reset(); - m_shaderView = std::make_unique(m_display.get(), m_config); + if (m_display->supportsShaders()) { + m_shaderView = std::make_unique(m_display.get(), m_config); + emit shaderSelectorAdded(m_shaderView.get()); + } else { + emit shaderSelectorAdded(nullptr); + } #endif connect(m_display.get(), &QGBA::Display::hideCursor, [this]() { @@ -1071,7 +1086,12 @@ void Window::reloadDisplayDriver() { m_display->setMinimumSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); #endif - m_display->setBackgroundImage(QImage{m_config->getOption("backgroundImage")}); + QString backgroundImage = m_config->getOption("backgroundImage"); + if (backgroundImage.isEmpty()) { + m_display->setBackgroundImage(QImage{}); + } else { + m_display->setBackgroundImage(QImage{backgroundImage}); + } if (!proxy) { proxy = std::make_shared(); @@ -1402,7 +1422,7 @@ void Window::setupMenu(QMenuBar* menubar) { #endif m_actions.addAction(tr("About..."), "about", openTView(), "file")->setRole(Action::Role::ABOUT); - m_actions.addAction(tr("E&xit"), "quit", static_cast(this), &QWidget::close, "file", QKeySequence::Quit)->setRole(Action::Role::QUIT); + m_actions.addAction(tr("E&xit"), "quit", &QApplication::quit, "file", QKeySequence::Quit)->setRole(Action::Role::QUIT); m_actions.addMenu(tr("&Emulation"), "emu"); addGameAction(tr("&Reset"), "reset", &CoreController::reset, "emu", QKeySequence("Ctrl+R")); @@ -1963,7 +1983,12 @@ void Window::setupOptions() { ConfigOption* backgroundImage = m_config->addOption("backgroundImage"); backgroundImage->connect([this](const QVariant& value) { if (m_display) { - m_display->setBackgroundImage(QImage{value.toString()}); + QString backgroundImage = value.toString(); + if (backgroundImage.isEmpty()) { + m_display->setBackgroundImage(QImage{}); + } else { + m_display->setBackgroundImage(QImage{backgroundImage}); + } } }, this); m_config->updateOption("backgroundImage"); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index f91e3ff30..cb467f89d 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -73,6 +73,7 @@ signals: void startDrawing(); void shutdown(); void paused(bool); + void shaderSelectorAdded(ShaderSelector*); public slots: void setController(CoreController* controller, const QString& fname); diff --git a/src/platform/qt/library/LibraryTree.cpp b/src/platform/qt/library/LibraryTree.cpp index 37d07e3e7..c9029e963 100644 --- a/src/platform/qt/library/LibraryTree.cpp +++ b/src/platform/qt/library/LibraryTree.cpp @@ -181,7 +181,7 @@ void LibraryTree::rebuildTree() { QHash pathNodes; if (m_currentStyle == LibraryStyle::STYLE_TREE) { - for (const QString& folder : m_pathNodes.keys()) { + for (const QString& folder : m_pathNodes.keys()) { QTreeWidgetItem* i = new LibraryTreeItem; pathNodes.insert(folder, i); i->setText(0, folder.section("/", -1)); diff --git a/src/platform/qt/scripting/ScriptingTextBuffer.cpp b/src/platform/qt/scripting/ScriptingTextBuffer.cpp index fe85eda0e..c209adcd5 100644 --- a/src/platform/qt/scripting/ScriptingTextBuffer.cpp +++ b/src/platform/qt/scripting/ScriptingTextBuffer.cpp @@ -49,28 +49,71 @@ void ScriptingTextBuffer::setBufferName(const QString& name) { emit bufferNameChanged(name); } +void ScriptingTextBuffer::lineBreak() { + bool nextBlockExists = m_shim.cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, 1); + if (!nextBlockExists) { + m_shim.cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor, 1); + m_shim.cursor.insertBlock(); + } +} + +void ScriptingTextBuffer::carriageReturn() { + m_shim.cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor, 1); +} + +void ScriptingTextBuffer::tab() { + QTextCursor& cursor = m_shim.cursor; + int column = cursor.positionInBlock(); + int move = tabStop - (column % tabStop) + 1; + if (column + move >= m_dims.width()) { + lineBreak(); + } else if (column + move <= cursor.block().length()) { + cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, move - 1); + } else { + move = column + move - cursor.block().length(); + cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor, 1); + cursor.insertText(QString(move, ' ')); + } +} + +void ScriptingTextBuffer::insertString(const QString& text) { + QTextCursor& cursor = m_shim.cursor; + if (cursor.positionInBlock() >= m_dims.width()) { + lineBreak(); + } + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, text.length()); + cursor.insertText(text); +} + void ScriptingTextBuffer::print(const QString& text) { QMutexLocker locker(&m_mutex); - QString split(text); - m_shim.cursor.beginEditBlock(); - while (m_shim.cursor.positionInBlock() + split.length() > m_dims.width()) { - int cut = m_dims.width() - m_shim.cursor.positionInBlock(); - if (!m_shim.cursor.atBlockEnd()) { - m_shim.cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + + QTextCursor& cursor = m_shim.cursor; + QString toInsert; + + for (const QChar& ch : text) { + int column = cursor.positionInBlock(); + if (ch == '\t' || ch == '\n' || ch == '\r' || column + toInsert.length() >= m_dims.width()) { + insertString(toInsert); + toInsert.clear(); } - m_shim.cursor.insertText(split.left(cut)); - if (m_shim.cursor.atEnd()) { - m_shim.cursor.insertBlock(); - } else { - m_shim.cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, 1); + switch (ch.unicode()) { + case '\t': + tab(); + break; + case '\n': + lineBreak(); + break; + case '\r': + carriageReturn(); + break; + default: + toInsert += ch; } - split = split.mid(cut); } - if (!m_shim.cursor.atBlockEnd()) { - m_shim.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, split.length()); + if (!toInsert.isEmpty()) { + insertString(toInsert); } - m_shim.cursor.insertText(split); - m_shim.cursor.endEditBlock(); } void ScriptingTextBuffer::clear() { @@ -107,7 +150,7 @@ void ScriptingTextBuffer::moveCursor(const QPoint& pos) { m_shim.cursor.insertBlock(); } } else { - m_shim.cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, y); + m_shim.cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, y); } int x = pos.x(); diff --git a/src/platform/qt/scripting/ScriptingTextBuffer.h b/src/platform/qt/scripting/ScriptingTextBuffer.h index b4a9c5b83..717a5d284 100644 --- a/src/platform/qt/scripting/ScriptingTextBuffer.h +++ b/src/platform/qt/scripting/ScriptingTextBuffer.h @@ -36,6 +36,8 @@ signals: void bufferNameChanged(const QString&); private: + enum { tabStop = 4 }; + struct ScriptingBufferShim : public mScriptTextBuffer { ScriptingTextBuffer* p; QTextCursor cursor; @@ -44,6 +46,11 @@ private: QMutex m_mutex; QString m_name; + void lineBreak(); + void carriageReturn(); + void tab(); + void insertString(const QString& text); + static void init(struct mScriptTextBuffer*, const char* name); static void deinit(struct mScriptTextBuffer*); diff --git a/src/platform/qt/ts/mgba-es_419.ts b/src/platform/qt/ts/mgba-es.ts similarity index 99% rename from src/platform/qt/ts/mgba-es_419.ts rename to src/platform/qt/ts/mgba-es.ts index 9311b210d..57defd38a 100644 --- a/src/platform/qt/ts/mgba-es_419.ts +++ b/src/platform/qt/ts/mgba-es.ts @@ -1,6 +1,6 @@ - + QGBA diff --git a/src/platform/sdl/CMakeLists.txt b/src/platform/sdl/CMakeLists.txt index d62c74694..5246f959b 100644 --- a/src/platform/sdl/CMakeLists.txt +++ b/src/platform/sdl/CMakeLists.txt @@ -23,7 +23,7 @@ if (SDL_VERSION EQUAL "2") endif() if(APPLE) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.7") + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.7") endif() set(SDLMAIN_LIBRARY ${SDL2MAIN_LIBRARY}) endif() diff --git a/src/platform/switch/main.c b/src/platform/switch/main.c index 1f61cc279..6cd7bc3b8 100644 --- a/src/platform/switch/main.c +++ b/src/platform/switch/main.c @@ -296,7 +296,7 @@ static void _setup(struct mGUIRunner* runner) { } _updateRenderer(runner, fakeBool); - mRumbleIntegratorInit(&rumble.d); + mRumbleIntegratorReset(&rumble.d); runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble.d.d); runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation); runner->core->setAVStream(runner->core, &stream); @@ -363,13 +363,10 @@ static void _gameLoaded(struct mGUIRunner* runner) { runner->core->reloadConfigOption(runner->core, "videoScale", &runner->config); } } - - mRumbleIntegratorInit(&rumble.d); } static void _gameUnloaded(struct mGUIRunner* runner) { UNUSED(runner); - mRumbleIntegratorInit(&rumble.d); HidVibrationValue values[4]; memcpy(&values[0], &vibrationStop, sizeof(rumble.value)); memcpy(&values[1], &vibrationStop, sizeof(rumble.value)); @@ -812,6 +809,7 @@ static void hidSetup(void) { padConfigureInput(1, HidNpadStyleSet_NpadStandard); padInitializeDefault(&pad); + mRumbleIntegratorInit(&rumble.d); rumble.d.setRumble = _setRumble; rumble.value.freq_low = 120.0; rumble.value.freq_high = 180.0; diff --git a/src/script/context.c b/src/script/context.c index d9afd55a1..0d770dfde 100644 --- a/src/script/context.c +++ b/src/script/context.c @@ -266,7 +266,7 @@ void mScriptContextTriggerCallback(struct mScriptContext* context, const char* c if (fn) { mScriptFrameInit(&frame); if (args) { - mScriptListCopy(&frame.arguments, args); + mScriptListCopy(&frame.stack, args); } mScriptContextInvoke(context, fn, &frame); mScriptFrameDeinit(&frame); @@ -481,7 +481,7 @@ bool mScriptInvoke(const struct mScriptValue* val, struct mScriptFrame* frame) { return false; } const struct mScriptTypeFunction* signature = &val->type->details.function; - if (!mScriptCoerceFrame(&signature->parameters, &frame->arguments, &frame->arguments)) { + if (!mScriptCoerceFrame(&signature->parameters, &frame->stack, &frame->stack)) { return false; } const struct mScriptFunction* fn = val->value.opaque; diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index f6bdabea2..f4f4026e9 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -912,12 +912,12 @@ void _luaError(struct mScriptEngineContextLua* luaContext) { if (ok) { struct mScriptFrame frame; mScriptFrameInit(&frame); - struct mScriptValue* this = mScriptListAppend(&frame.arguments); + struct mScriptValue* this = mScriptListAppend(&frame.stack); this->type = console->type; this->refs = mSCRIPT_VALUE_UNREF; this->flags = 0; this->value.opaque = console->value.opaque; - mSCRIPT_PUSH(&frame.arguments, CHARP, luaContext->lastError); + mSCRIPT_PUSH(&frame.stack, CHARP, luaContext->lastError); ok = mScriptInvoke(&error, &frame); mScriptFrameDeinit(&frame); } @@ -1115,7 +1115,7 @@ void _autofreeFrame(struct mScriptContext* context, struct mScriptList* frame) { bool _luaInvoke(struct mScriptEngineContextLua* luaContext, struct mScriptFrame* frame) { int nargs = 0; if (frame) { - nargs = mScriptListSize(&frame->arguments); + nargs = mScriptListSize(&frame->stack); } if (luaContext->lastError) { @@ -1127,9 +1127,12 @@ bool _luaInvoke(struct mScriptEngineContextLua* luaContext, struct mScriptFrame* return false; } - if (frame && !_luaPushFrame(luaContext, luaContext->lua, &frame->arguments)) { - mScriptContextDeactivate(luaContext->d.context); - return false; + if (frame) { + if (!_luaPushFrame(luaContext, luaContext->lua, &frame->stack)) { + mScriptContextDeactivate(luaContext->d.context); + return false; + } + mScriptListClear(&frame->stack); } lua_pushliteral(luaContext->lua, "mCtx"); @@ -1143,7 +1146,7 @@ bool _luaInvoke(struct mScriptEngineContextLua* luaContext, struct mScriptFrame* if (ret == LUA_ERRRUN) { luaContext->lastError = strdup(lua_tostring(luaContext->lua, -1)); lua_pop(luaContext->lua, 1); - + _luaError(luaContext); } mScriptContextDeactivate(luaContext->d.context); @@ -1151,7 +1154,7 @@ bool _luaInvoke(struct mScriptEngineContextLua* luaContext, struct mScriptFrame* return false; } - if (frame && !_luaPopFrame(luaContext, luaContext->lua, &frame->returnValues)) { + if (frame && !_luaPopFrame(luaContext, luaContext->lua, &frame->stack)) { mScriptContextDrainPool(luaContext->d.context); return false; } @@ -1191,8 +1194,8 @@ int _luaThunk(lua_State* lua) { struct mScriptEngineContextLua* luaContext = _luaGetContext(lua); struct mScriptFrame frame; mScriptFrameInit(&frame); - if (!_luaPopFrame(luaContext, lua, &frame.arguments)) { - _freeFrame(&frame.arguments); + if (!_luaPopFrame(luaContext, lua, &frame.stack)) { + _freeFrame(&frame.stack); mScriptContextDrainPool(luaContext->d.context); mScriptFrameDeinit(&frame); luaL_traceback(lua, lua, "Error calling function (translating arguments into runtime)", 1); @@ -1200,7 +1203,7 @@ int _luaThunk(lua_State* lua) { } struct mScriptValue* fn = lua_touserdata(lua, lua_upvalueindex(1)); - _autofreeFrame(luaContext->d.context, &frame.arguments); + _autofreeFrame(luaContext->d.context, &frame.stack); if (!fn || !mScriptContextInvoke(luaContext->d.context, fn, &frame)) { mScriptContextDrainPool(luaContext->d.context); mScriptFrameDeinit(&frame); @@ -1208,7 +1211,7 @@ int _luaThunk(lua_State* lua) { return lua_error(lua); } - bool ok = _luaPushFrame(luaContext, lua, &frame.returnValues); + bool ok = _luaPushFrame(luaContext, lua, &frame.stack); mScriptContextDrainPool(luaContext->d.context); mScriptFrameDeinit(&frame); if (!ok) { diff --git a/src/script/test/classes.c b/src/script/test/classes.c index e0ce39d47..1b688cae4 100644 --- a/src/script/test/classes.c +++ b/src/script/test/classes.c @@ -519,106 +519,106 @@ M_TEST_DEFINE(testAStatic) { assert_true(mScriptObjectGet(&sval, "i0", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 1); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "i1", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); - mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S32, 1); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 2); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "ic0", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, CS(TestA), &s); + mSCRIPT_PUSH(&frame.stack, CS(TestA), &s); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 1); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "ic0", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 1); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "ic1", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, CS(TestA), &s); - mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.stack, CS(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S32, 1); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 2); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "ic1", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); - mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S32, 1); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 2); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "v0", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); assert_true(mScriptInvoke(&val, &frame)); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "i0", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 2); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "ic0", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, CS(TestA), &s); + mSCRIPT_PUSH(&frame.stack, CS(TestA), &s); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 2); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "v1", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); - mSCRIPT_PUSH(&frame.arguments, S32, 2); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S32, 2); assert_true(mScriptInvoke(&val, &frame)); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "v2", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); - mSCRIPT_PUSH(&frame.arguments, S32, 1); - mSCRIPT_PUSH(&frame.arguments, S32, -2); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S32, 1); + mSCRIPT_PUSH(&frame.stack, S32, -2); assert_true(mScriptInvoke(&val, &frame)); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "v2", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); - mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S32, 1); assert_true(mScriptInvoke(&val, &frame)); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "i0", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 4); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "ic0", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, CS(TestA), &s); + mSCRIPT_PUSH(&frame.stack, CS(TestA), &s); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 4); mScriptFrameDeinit(&frame); @@ -659,93 +659,93 @@ M_TEST_DEFINE(testADynamic) { assert_true(mScriptObjectGet(&sval, "ifn0", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 1); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "ifn1", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); - mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S32, 1); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 2); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "icfn0", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, CS(TestA), &s); + mSCRIPT_PUSH(&frame.stack, CS(TestA), &s); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 1); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "icfn0", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 1); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "icfn1", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, CS(TestA), &s); - mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.stack, CS(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S32, 1); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 2); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "icfn1", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); - mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S32, 1); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 2); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "vfn0", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); assert_true(mScriptInvoke(&val, &frame)); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "ifn0", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 2); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "icfn0", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, CS(TestA), &s); + mSCRIPT_PUSH(&frame.stack, CS(TestA), &s); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 2); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "vfn1", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); - mSCRIPT_PUSH(&frame.arguments, S32, 2); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S32, 2); assert_true(mScriptInvoke(&val, &frame)); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "ifn0", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestA), &s); + mSCRIPT_PUSH(&frame.stack, S(TestA), &s); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 4); mScriptFrameDeinit(&frame); assert_true(mScriptObjectGet(&sval, "icfn0", &val)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, CS(TestA), &s); + mSCRIPT_PUSH(&frame.stack, CS(TestA), &s); assert_true(mScriptInvoke(&val, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &rval)); + assert_true(mScriptPopS32(&frame.stack, &rval)); assert_int_equal(rval, 4); mScriptFrameDeinit(&frame); @@ -1228,16 +1228,16 @@ M_TEST_DEFINE(testOverloadsBasic) { assert_true(mScriptObjectGet(&sval, "call", &fn)); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestI), &s); - mSCRIPT_PUSH(&frame.arguments, U32, 1); + mSCRIPT_PUSH(&frame.stack, S(TestI), &s); + mSCRIPT_PUSH(&frame.stack, U32, 1); assert_true(mScriptInvoke(&fn, &frame)); mScriptFrameDeinit(&frame); assert_int_equal(s.num, 1); assert_null(s.str); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(TestI), &s); - mSCRIPT_PUSH(&frame.arguments, CHARP, "called"); + mSCRIPT_PUSH(&frame.stack, S(TestI), &s); + mSCRIPT_PUSH(&frame.stack, CHARP, "called"); assert_true(mScriptInvoke(&fn, &frame)); mScriptFrameDeinit(&frame); assert_int_equal(s.num, 1); diff --git a/src/script/test/lua.c b/src/script/test/lua.c index 49c5521d7..388d79084 100644 --- a/src/script/test/lua.c +++ b/src/script/test/lua.c @@ -328,10 +328,10 @@ M_TEST_DEFINE(callLuaFunc) { struct mScriptFrame frame; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.stack, S32, 1); assert_true(mScriptInvoke(fn, &frame)); int64_t val; - assert_true(mScriptPopS64(&frame.returnValues, &val)); + assert_true(mScriptPopS64(&frame.stack, &val)); assert_int_equal(val, 2); mScriptFrameDeinit(&frame); @@ -342,10 +342,10 @@ M_TEST_DEFINE(callLuaFunc) { assert_int_equal(fn->type->base, mSCRIPT_TYPE_FUNCTION); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S32, 1); - mSCRIPT_PUSH(&frame.arguments, S32, 2); + mSCRIPT_PUSH(&frame.stack, S32, 1); + mSCRIPT_PUSH(&frame.stack, S32, 2); assert_true(mScriptInvoke(fn, &frame)); - assert_true(mScriptPopS64(&frame.returnValues, &val)); + assert_true(mScriptPopS64(&frame.stack, &val)); assert_int_equal(val, 3); mScriptFrameDeinit(&frame); diff --git a/src/script/test/types.c b/src/script/test/types.c index 67b6fba9a..51420eecd 100644 --- a/src/script/test/types.c +++ b/src/script/test/types.c @@ -111,7 +111,7 @@ M_TEST_DEFINE(voidArgs) { mScriptFrameInit(&frame); assert_true(mScriptInvoke(&boundVoidOne, &frame)); int32_t val; - assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_true(mScriptPopS32(&frame.stack, &val)); assert_int_equal(val, 1); mScriptFrameDeinit(&frame); } @@ -119,7 +119,7 @@ M_TEST_DEFINE(voidArgs) { M_TEST_DEFINE(voidFunc) { struct mScriptFrame frame; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.stack, S32, 1); assert_true(mScriptInvoke(&boundDiscard, &frame)); mScriptFrameDeinit(&frame); } @@ -127,10 +127,10 @@ M_TEST_DEFINE(voidFunc) { M_TEST_DEFINE(identityFunctionS32) { struct mScriptFrame frame; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.stack, S32, 1); assert_true(mScriptInvoke(&boundIdentityInt, &frame)); int32_t val; - assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_true(mScriptPopS32(&frame.stack, &val)); assert_int_equal(val, 1); mScriptFrameDeinit(&frame); } @@ -138,10 +138,10 @@ M_TEST_DEFINE(identityFunctionS32) { M_TEST_DEFINE(identityFunctionS64) { struct mScriptFrame frame; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S64, 1); + mSCRIPT_PUSH(&frame.stack, S64, 1); assert_true(mScriptInvoke(&boundIdentityInt64, &frame)); int64_t val; - assert_true(mScriptPopS64(&frame.returnValues, &val)); + assert_true(mScriptPopS64(&frame.stack, &val)); assert_int_equal(val, 1); mScriptFrameDeinit(&frame); } @@ -149,10 +149,10 @@ M_TEST_DEFINE(identityFunctionS64) { M_TEST_DEFINE(identityFunctionF32) { struct mScriptFrame frame; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, F32, 3.125f); + mSCRIPT_PUSH(&frame.stack, F32, 3.125f); assert_true(mScriptInvoke(&boundIdentityFloat, &frame)); float val; - assert_true(mScriptPopF32(&frame.returnValues, &val)); + assert_true(mScriptPopF32(&frame.stack, &val)); assert_float_equal(val, 3.125f, 0.f); mScriptFrameDeinit(&frame); } @@ -161,10 +161,10 @@ M_TEST_DEFINE(identityFunctionStruct) { struct mScriptFrame frame; struct Test v = {}; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(Test), &v); + mSCRIPT_PUSH(&frame.stack, S(Test), &v); assert_true(mScriptInvoke(&boundIdentityStruct, &frame)); struct Test* val; - assert_true(mScriptPopPointer(&frame.returnValues, (void**) &val)); + assert_true(mScriptPopPointer(&frame.stack, (void**) &val)); assert_ptr_equal(val, &v); mScriptFrameDeinit(&frame); } @@ -172,11 +172,11 @@ M_TEST_DEFINE(identityFunctionStruct) { M_TEST_DEFINE(addS32) { struct mScriptFrame frame; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S32, 1); - mSCRIPT_PUSH(&frame.arguments, S32, 2); + mSCRIPT_PUSH(&frame.stack, S32, 1); + mSCRIPT_PUSH(&frame.stack, S32, 2); assert_true(mScriptInvoke(&boundAddInts, &frame)); int32_t val; - assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_true(mScriptPopS32(&frame.stack, &val)); assert_int_equal(val, 3); mScriptFrameDeinit(&frame); } @@ -186,17 +186,17 @@ M_TEST_DEFINE(addS32Defaults) { int32_t val; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S32, 1); - mSCRIPT_PUSH(&frame.arguments, S32, 2); + mSCRIPT_PUSH(&frame.stack, S32, 1); + mSCRIPT_PUSH(&frame.stack, S32, 2); assert_true(mScriptInvoke(&boundAddIntWithDefaults, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_true(mScriptPopS32(&frame.stack, &val)); assert_int_equal(val, 3); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.stack, S32, 1); assert_true(mScriptInvoke(&boundAddIntWithDefaults, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_true(mScriptPopS32(&frame.stack, &val)); assert_int_equal(val, 1); mScriptFrameDeinit(&frame); @@ -208,11 +208,11 @@ M_TEST_DEFINE(addS32Defaults) { M_TEST_DEFINE(subS32) { struct mScriptFrame frame; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S32, 2); - mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.stack, S32, 2); + mSCRIPT_PUSH(&frame.stack, S32, 1); assert_true(mScriptInvoke(&boundSubInts, &frame)); int32_t val; - assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_true(mScriptPopS32(&frame.stack, &val)); assert_int_equal(val, 1); mScriptFrameDeinit(&frame); } @@ -227,8 +227,8 @@ M_TEST_DEFINE(wrongArgCountLo) { M_TEST_DEFINE(wrongArgCountHi) { struct mScriptFrame frame; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S32, 1); - mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.stack, S32, 1); + mSCRIPT_PUSH(&frame.stack, S32, 1); assert_false(mScriptInvoke(&boundIdentityInt, &frame)); mScriptFrameDeinit(&frame); } @@ -236,7 +236,7 @@ M_TEST_DEFINE(wrongArgCountHi) { M_TEST_DEFINE(wrongArgType) { struct mScriptFrame frame; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.stack, S32, 1); assert_false(mScriptInvoke(&boundIdentityStruct, &frame)); mScriptFrameDeinit(&frame); } @@ -252,55 +252,55 @@ M_TEST_DEFINE(wrongPopType) { bool b; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S32, 0); - assert_false(mScriptPopU32(&frame.arguments, &u32)); - assert_false(mScriptPopF32(&frame.arguments, &f32)); - assert_false(mScriptPopBool(&frame.arguments, &b)); + mSCRIPT_PUSH(&frame.stack, S32, 0); + assert_false(mScriptPopU32(&frame.stack, &u32)); + assert_false(mScriptPopF32(&frame.stack, &f32)); + assert_false(mScriptPopBool(&frame.stack, &b)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S64, 0); - assert_false(mScriptPopU64(&frame.arguments, &u64)); - assert_false(mScriptPopF64(&frame.arguments, &f64)); - assert_false(mScriptPopBool(&frame.arguments, &b)); + mSCRIPT_PUSH(&frame.stack, S64, 0); + assert_false(mScriptPopU64(&frame.stack, &u64)); + assert_false(mScriptPopF64(&frame.stack, &f64)); + assert_false(mScriptPopBool(&frame.stack, &b)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, U32, 0); - assert_false(mScriptPopS32(&frame.arguments, &s32)); - assert_false(mScriptPopF32(&frame.arguments, &f32)); - assert_false(mScriptPopBool(&frame.arguments, &b)); + mSCRIPT_PUSH(&frame.stack, U32, 0); + assert_false(mScriptPopS32(&frame.stack, &s32)); + assert_false(mScriptPopF32(&frame.stack, &f32)); + assert_false(mScriptPopBool(&frame.stack, &b)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, U64, 0); - assert_false(mScriptPopS64(&frame.arguments, &s64)); - assert_false(mScriptPopF64(&frame.arguments, &f64)); - assert_false(mScriptPopBool(&frame.arguments, &b)); + mSCRIPT_PUSH(&frame.stack, U64, 0); + assert_false(mScriptPopS64(&frame.stack, &s64)); + assert_false(mScriptPopF64(&frame.stack, &f64)); + assert_false(mScriptPopBool(&frame.stack, &b)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, F32, 0); - assert_false(mScriptPopS32(&frame.arguments, &s32)); - assert_false(mScriptPopU32(&frame.arguments, &u32)); - assert_false(mScriptPopBool(&frame.arguments, &b)); + mSCRIPT_PUSH(&frame.stack, F32, 0); + assert_false(mScriptPopS32(&frame.stack, &s32)); + assert_false(mScriptPopU32(&frame.stack, &u32)); + assert_false(mScriptPopBool(&frame.stack, &b)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, F64, 0); - assert_false(mScriptPopS64(&frame.arguments, &s64)); - assert_false(mScriptPopU64(&frame.arguments, &u64)); - assert_false(mScriptPopBool(&frame.arguments, &b)); + mSCRIPT_PUSH(&frame.stack, F64, 0); + assert_false(mScriptPopS64(&frame.stack, &s64)); + assert_false(mScriptPopU64(&frame.stack, &u64)); + assert_false(mScriptPopBool(&frame.stack, &b)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, BOOL, 0); - assert_false(mScriptPopS32(&frame.arguments, &s32)); - assert_false(mScriptPopU32(&frame.arguments, &u32)); - assert_false(mScriptPopS64(&frame.arguments, &s64)); - assert_false(mScriptPopU64(&frame.arguments, &u64)); - assert_false(mScriptPopF32(&frame.arguments, &f32)); - assert_false(mScriptPopF64(&frame.arguments, &f64)); + mSCRIPT_PUSH(&frame.stack, BOOL, 0); + assert_false(mScriptPopS32(&frame.stack, &s32)); + assert_false(mScriptPopU32(&frame.stack, &u32)); + assert_false(mScriptPopS64(&frame.stack, &s64)); + assert_false(mScriptPopU64(&frame.stack, &u64)); + assert_false(mScriptPopF32(&frame.stack, &f32)); + assert_false(mScriptPopF64(&frame.stack, &f64)); mScriptFrameDeinit(&frame); } @@ -314,33 +314,33 @@ M_TEST_DEFINE(wrongPopSize) { double f64; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S32, 0); - assert_false(mScriptPopS64(&frame.arguments, &s64)); + mSCRIPT_PUSH(&frame.stack, S32, 0); + assert_false(mScriptPopS64(&frame.stack, &s64)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S64, 0); - assert_false(mScriptPopS32(&frame.arguments, &s32)); + mSCRIPT_PUSH(&frame.stack, S64, 0); + assert_false(mScriptPopS32(&frame.stack, &s32)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, U32, 0); - assert_false(mScriptPopU64(&frame.arguments, &u64)); + mSCRIPT_PUSH(&frame.stack, U32, 0); + assert_false(mScriptPopU64(&frame.stack, &u64)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, U64, 0); - assert_false(mScriptPopU32(&frame.arguments, &u32)); + mSCRIPT_PUSH(&frame.stack, U64, 0); + assert_false(mScriptPopU32(&frame.stack, &u32)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, F32, 0); - assert_false(mScriptPopF64(&frame.arguments, &f64)); + mSCRIPT_PUSH(&frame.stack, F32, 0); + assert_false(mScriptPopF64(&frame.stack, &f64)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, F64, 0); - assert_false(mScriptPopF32(&frame.arguments, &f32)); + mSCRIPT_PUSH(&frame.stack, F64, 0); + assert_false(mScriptPopF32(&frame.stack, &f32)); mScriptFrameDeinit(&frame); } @@ -370,63 +370,63 @@ M_TEST_DEFINE(wrongConst) { mScriptClassInit(mSCRIPT_TYPE_MS_CS(Test)->details.cls); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(Test), &a); + mSCRIPT_PUSH(&frame.stack, S(Test), &a); signature.entries[0] = mSCRIPT_TYPE_MS_S(Test); - assert_true(mScriptCoerceFrame(&signature, &frame.arguments, &frame.arguments)); + assert_true(mScriptCoerceFrame(&signature, &frame.stack, &frame.stack)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, CS(Test), &a); + mSCRIPT_PUSH(&frame.stack, CS(Test), &a); signature.entries[0] = mSCRIPT_TYPE_MS_CS(Test); - assert_true(mScriptCoerceFrame(&signature, &frame.arguments, &frame.arguments)); + assert_true(mScriptCoerceFrame(&signature, &frame.stack, &frame.stack)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(Test), &a); + mSCRIPT_PUSH(&frame.stack, S(Test), &a); signature.entries[0] = mSCRIPT_TYPE_MS_CS(Test); - assert_true(mScriptCoerceFrame(&signature, &frame.arguments, &frame.arguments)); + assert_true(mScriptCoerceFrame(&signature, &frame.stack, &frame.stack)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, CS(Test), &a); + mSCRIPT_PUSH(&frame.stack, CS(Test), &a); signature.entries[0] = mSCRIPT_TYPE_MS_S(Test); - assert_false(mScriptCoerceFrame(&signature, &frame.arguments, &frame.arguments)); + assert_false(mScriptCoerceFrame(&signature, &frame.stack, &frame.stack)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(Test), &a); - assert_true(mScriptPopSTest(&frame.arguments, &b)); + mSCRIPT_PUSH(&frame.stack, S(Test), &a); + assert_true(mScriptPopSTest(&frame.stack, &b)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(Test), &a); - assert_false(mScriptPopCSTest(&frame.arguments, &cb)); + mSCRIPT_PUSH(&frame.stack, S(Test), &a); + assert_false(mScriptPopCSTest(&frame.stack, &cb)); signature.entries[0] = mSCRIPT_TYPE_MS_CS(Test); - assert_true(mScriptCoerceFrame(&signature, &frame.arguments, &frame.arguments)); - assert_true(mScriptPopCSTest(&frame.arguments, &cb)); + assert_true(mScriptCoerceFrame(&signature, &frame.stack, &frame.stack)); + assert_true(mScriptPopCSTest(&frame.stack, &cb)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, CS(Test), &a); - assert_false(mScriptPopSTest(&frame.arguments, &b)); + mSCRIPT_PUSH(&frame.stack, CS(Test), &a); + assert_false(mScriptPopSTest(&frame.stack, &b)); signature.entries[0] = mSCRIPT_TYPE_MS_S(Test); - assert_false(mScriptCoerceFrame(&signature, &frame.arguments, &frame.arguments)); - assert_false(mScriptPopSTest(&frame.arguments, &b)); + assert_false(mScriptCoerceFrame(&signature, &frame.stack, &frame.stack)); + assert_false(mScriptPopSTest(&frame.stack, &b)); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, CS(Test), &a); - assert_true(mScriptPopCSTest(&frame.arguments, &cb)); + mSCRIPT_PUSH(&frame.stack, CS(Test), &a); + assert_true(mScriptPopCSTest(&frame.stack, &cb)); mScriptFrameDeinit(&frame); } M_TEST_DEFINE(coerceToFloat) { struct mScriptFrame frame; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.stack, S32, 1); assert_true(mScriptInvoke(&boundIdentityFloat, &frame)); float val; - assert_true(mScriptPopF32(&frame.returnValues, &val)); + assert_true(mScriptPopF32(&frame.stack, &val)); assert_float_equal(val, 1.f, 0.f); mScriptFrameDeinit(&frame); } @@ -434,10 +434,10 @@ M_TEST_DEFINE(coerceToFloat) { M_TEST_DEFINE(coerceFromFloat) { struct mScriptFrame frame; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, F32, 1.25f); + mSCRIPT_PUSH(&frame.stack, F32, 1.25f); assert_true(mScriptInvoke(&boundIdentityInt, &frame)); int val; - assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_true(mScriptPopS32(&frame.stack, &val)); assert_int_equal(val, 1); mScriptFrameDeinit(&frame); } @@ -539,10 +539,10 @@ M_TEST_DEFINE(coerceFromBool) { M_TEST_DEFINE(coerceWiden) { struct mScriptFrame frame; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S32, -1); + mSCRIPT_PUSH(&frame.stack, S32, -1); assert_true(mScriptInvoke(&boundIdentityInt64, &frame)); int64_t val; - assert_true(mScriptPopS64(&frame.returnValues, &val)); + assert_true(mScriptPopS64(&frame.stack, &val)); assert_true(val == -1LL); mScriptFrameDeinit(&frame); } @@ -550,10 +550,10 @@ M_TEST_DEFINE(coerceWiden) { M_TEST_DEFINE(coerceNarrow) { struct mScriptFrame frame; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S64, -1); + mSCRIPT_PUSH(&frame.stack, S64, -1); assert_true(mScriptInvoke(&boundIdentityInt, &frame)); int32_t val; - assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_true(mScriptPopS32(&frame.stack, &val)); assert_true(val == -1); mScriptFrameDeinit(&frame); } @@ -1244,10 +1244,10 @@ M_TEST_DEFINE(hashTableString) { M_TEST_DEFINE(stringIsHello) { struct mScriptFrame frame; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, CHARP, "hello"); + mSCRIPT_PUSH(&frame.stack, CHARP, "hello"); assert_true(mScriptInvoke(&boundIsHello, &frame)); int val; - assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_true(mScriptPopS32(&frame.stack, &val)); assert_int_equal(val, 1); mScriptFrameDeinit(&frame); } @@ -1255,10 +1255,10 @@ M_TEST_DEFINE(stringIsHello) { M_TEST_DEFINE(stringIsNotHello) { struct mScriptFrame frame; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, CHARP, "world"); + mSCRIPT_PUSH(&frame.stack, CHARP, "world"); assert_true(mScriptInvoke(&boundIsHello, &frame)); int val; - assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_true(mScriptPopS32(&frame.stack, &val)); assert_int_equal(val, 0); mScriptFrameDeinit(&frame); } @@ -1271,33 +1271,33 @@ M_TEST_DEFINE(invokeList) { mScriptListInit(&list, 0); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, LIST, &list); + mSCRIPT_PUSH(&frame.stack, LIST, &list); assert_true(mScriptInvoke(&boundIsSequential, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_true(mScriptPopS32(&frame.stack, &val)); assert_int_equal(val, 1); mScriptFrameDeinit(&frame); *mScriptListAppend(&list) = mSCRIPT_MAKE_S32(1); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, LIST, &list); + mSCRIPT_PUSH(&frame.stack, LIST, &list); assert_true(mScriptInvoke(&boundIsSequential, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_true(mScriptPopS32(&frame.stack, &val)); assert_int_equal(val, 1); mScriptFrameDeinit(&frame); *mScriptListAppend(&list) = mSCRIPT_MAKE_S32(2); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, LIST, &list); + mSCRIPT_PUSH(&frame.stack, LIST, &list); assert_true(mScriptInvoke(&boundIsSequential, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_true(mScriptPopS32(&frame.stack, &val)); assert_int_equal(val, 1); mScriptFrameDeinit(&frame); *mScriptListAppend(&list) = mSCRIPT_MAKE_S32(4); mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, LIST, &list); + mSCRIPT_PUSH(&frame.stack, LIST, &list); assert_true(mScriptInvoke(&boundIsSequential, &frame)); - assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_true(mScriptPopS32(&frame.stack, &val)); assert_int_equal(val, 0); mScriptFrameDeinit(&frame); @@ -1309,14 +1309,14 @@ M_TEST_DEFINE(nullString) { bool res; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, CHARP, "hi"); + mSCRIPT_PUSH(&frame.stack, CHARP, "hi"); assert_true(mScriptInvoke(&boundIsNullCharp, &frame)); - assert_true(mScriptPopBool(&frame.returnValues, &res)); + assert_true(mScriptPopBool(&frame.stack, &res)); assert_false(res); - mSCRIPT_PUSH(&frame.arguments, CHARP, NULL); + mSCRIPT_PUSH(&frame.stack, CHARP, NULL); assert_true(mScriptInvoke(&boundIsNullCharp, &frame)); - assert_true(mScriptPopBool(&frame.returnValues, &res)); + assert_true(mScriptPopBool(&frame.stack, &res)); assert_true(res); mScriptFrameDeinit(&frame); @@ -1328,14 +1328,14 @@ M_TEST_DEFINE(nullStruct) { bool res; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, S(Test), &v); + mSCRIPT_PUSH(&frame.stack, S(Test), &v); assert_true(mScriptInvoke(&boundIsNullStruct, &frame)); - assert_true(mScriptPopBool(&frame.returnValues, &res)); + assert_true(mScriptPopBool(&frame.stack, &res)); assert_false(res); - mSCRIPT_PUSH(&frame.arguments, S(Test), NULL); + mSCRIPT_PUSH(&frame.stack, S(Test), NULL); assert_true(mScriptInvoke(&boundIsNullStruct, &frame)); - assert_true(mScriptPopBool(&frame.returnValues, &res)); + assert_true(mScriptPopBool(&frame.stack, &res)); assert_true(res); mScriptFrameDeinit(&frame); diff --git a/src/script/types.c b/src/script/types.c index be58a43bf..799c6fb71 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -1118,13 +1118,11 @@ bool mScriptTableIteratorLookup(struct mScriptValue* table, struct TableIterator } void mScriptFrameInit(struct mScriptFrame* frame) { - mScriptListInit(&frame->arguments, 4); - mScriptListInit(&frame->returnValues, 1); + mScriptListInit(&frame->stack, 4); } void mScriptFrameDeinit(struct mScriptFrame* frame) { - mScriptListDeinit(&frame->returnValues); - mScriptListDeinit(&frame->arguments); + mScriptListDeinit(&frame->stack); } struct mScriptValue* mScriptLambdaCreate0(struct mScriptValue* fn, struct mScriptList* args) { @@ -1150,15 +1148,15 @@ struct mScriptValue* mScriptLambdaCreate0(struct mScriptValue* fn, struct mScrip } bool _callLambda0(struct mScriptFrame* frame, void* context) { - if (mScriptListSize(&frame->arguments)) { + if (mScriptListSize(&frame->stack)) { return false; } struct mScriptLambda* lambda = context; struct mScriptFrame subframe; mScriptFrameInit(&subframe); - mScriptListCopy(&subframe.arguments, &lambda->arguments); + mScriptListCopy(&subframe.stack, &lambda->arguments); bool ok = mScriptInvoke(lambda->fn, &subframe); - if (mScriptListSize(&subframe.returnValues)) { + if (mScriptListSize(&subframe.stack)) { ok = false; } mScriptFrameDeinit(&subframe); @@ -1399,17 +1397,17 @@ bool mScriptObjectGet(struct mScriptValue* obj, const char* member, struct mScri } struct mScriptFrame frame; mScriptFrameInit(&frame); - struct mScriptValue* this = mScriptListAppend(&frame.arguments); + struct mScriptValue* this = mScriptListAppend(&frame.stack); this->type = obj->type; this->refs = mSCRIPT_VALUE_UNREF; this->flags = 0; this->value.opaque = obj->value.opaque; - mSCRIPT_PUSH(&frame.arguments, CHARP, member); - if (!mScriptInvoke(&getMember, &frame) || mScriptListSize(&frame.returnValues) != 1) { + mSCRIPT_PUSH(&frame.stack, CHARP, member); + if (!mScriptInvoke(&getMember, &frame) || mScriptListSize(&frame.stack) != 1) { mScriptFrameDeinit(&frame); return false; } - memcpy(val, mScriptListGetPointer(&frame.returnValues, 0), sizeof(*val)); + memcpy(val, mScriptListGetPointer(&frame.stack, 0), sizeof(*val)); mScriptFrameDeinit(&frame); return true; } @@ -1443,7 +1441,7 @@ static struct mScriptClassMember* _findSetter(const struct mScriptTypeClass* cls if (m) { return m; } - + switch (type->base) { case mSCRIPT_TYPE_SINT: if (type->size < 2) { @@ -1547,14 +1545,14 @@ bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScri } struct mScriptFrame frame; mScriptFrameInit(&frame); - struct mScriptValue* this = mScriptListAppend(&frame.arguments); + struct mScriptValue* this = mScriptListAppend(&frame.stack); this->type = obj->type; this->refs = mSCRIPT_VALUE_UNREF; this->flags = 0; this->value.opaque = obj->value.opaque; - mSCRIPT_PUSH(&frame.arguments, CHARP, member); - mScriptValueWrap(val, mScriptListAppend(&frame.arguments)); - if (!mScriptInvoke(&setMember, &frame) || mScriptListSize(&frame.returnValues) != 0) { + mSCRIPT_PUSH(&frame.stack, CHARP, member); + mScriptValueWrap(val, mScriptListAppend(&frame.stack)); + if (!mScriptInvoke(&setMember, &frame) || mScriptListSize(&frame.stack) != 0) { mScriptFrameDeinit(&frame); return false; } @@ -1665,7 +1663,7 @@ void mScriptObjectFree(struct mScriptValue* value) { if (_accessRawMember(value->type->details.cls->free, value->value.opaque, value->type->isConst, &deinitMember)) { struct mScriptFrame frame; mScriptFrameInit(&frame); - mSCRIPT_PUSH(&frame.arguments, WRAPPER, value); + mSCRIPT_PUSH(&frame.stack, WRAPPER, value); mScriptInvoke(&deinitMember, &frame); mScriptFrameDeinit(&frame); } diff --git a/src/tools/docgen.c b/src/tools/docgen.c index cdabb472c..e2c444ed2 100644 --- a/src/tools/docgen.c +++ b/src/tools/docgen.c @@ -362,7 +362,7 @@ bool call(struct mScriptValue* obj, const char* method, struct mScriptFrame* fra if (!mScriptObjectGet(obj, method, &fn)) { return false; } - mSCRIPT_PUSH(&frame->arguments, WRAPPER, obj); + mSCRIPT_PUSH(&frame->stack, WRAPPER, obj); return mScriptInvoke(&fn, frame); } @@ -394,12 +394,12 @@ void explainCore(struct mCore* core) { mScriptFrameInit(&frame); call(value, "base", &frame); - mScriptPopU32(&frame.returnValues, &baseVal); + mScriptPopU32(&frame.stack, &baseVal); mScriptFrameDeinit(&frame); mScriptFrameInit(&frame); call(value, "name", &frame); - shortName = mScriptValueUnwrap(mScriptListGetPointer(&frame.returnValues, 0)); + shortName = mScriptValueUnwrap(mScriptListGetPointer(&frame.stack, 0)); mScriptFrameDeinit(&frame); fprintf(out, " base: 0x%x\n", baseVal); diff --git a/src/tools/font-sdf.c b/src/tools/font-sdf.c index e28c34eaf..0ddcdc645 100644 --- a/src/tools/font-sdf.c +++ b/src/tools/font-sdf.c @@ -24,7 +24,7 @@ void createSdf(const struct mImage* src, struct mImage* dst, int x, int y, int w if (mImageGetPixel(src, i, j) & 0xFFFFFF) { mImageSetPixelRaw(dst, i, j, 0xFF); } else { - mImageSetPixelRaw(dst, i, j, 1); + mImageSetPixelRaw(dst, i, j, 1); } } } diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 66cd6ca47..2f4ba2dcd 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -6,6 +6,7 @@ set(BASE_SOURCE_FILES formatting.c gbk-table.c hash.c + md5.c string.c table.c vector.c @@ -41,6 +42,7 @@ set(TEST_FILES test/circle-buffer.c test/color.c test/geometry.c + test/hash.c test/image.c test/sfo.c test/string-parser.c diff --git a/src/util/audio-resampler.c b/src/util/audio-resampler.c index 3b1848190..554f4744b 100644 --- a/src/util/audio-resampler.c +++ b/src/util/audio-resampler.c @@ -60,7 +60,7 @@ void mAudioResamplerSetSource(struct mAudioResampler* resampler, struct mAudioBu void mAudioResamplerSetDestination(struct mAudioResampler* resampler, struct mAudioBuffer* destination, double rate) { resampler->destination = destination; - resampler->destRate = rate; + resampler->destRate = rate; } size_t mAudioResamplerProcess(struct mAudioResampler* resampler) { diff --git a/src/util/crc32.c b/src/util/crc32.c index 24f611111..7c1fcda55 100644 --- a/src/util/crc32.c +++ b/src/util/crc32.c @@ -103,7 +103,7 @@ uint32_t doCrc32(const void* buf, size_t size) { #ifndef HAVE_CRC32 uint32_t crc32(uint32_t crc, const void* buf, size_t size) { const uint8_t* p = buf; - + crc = ~crc; for (size_t i = 0; i < size; ++i) { crc = crc32Table[(crc ^ p[i]) & 0xFF] ^ (crc >> 8); diff --git a/src/util/hash.c b/src/util/hash.c index ed070e86c..1ac0b56e2 100644 --- a/src/util/hash.c +++ b/src/util/hash.c @@ -69,9 +69,9 @@ uint32_t hash32(const void* key, size_t len, uint32_t seed) { k1 *= c1; k1 = ROTL32(k1, 15); k1 *= c2; - + h1 ^= k1; - h1 = ROTL32(h1, 13); + h1 = ROTL32(h1, 13); h1 = h1 * 5 + 0xe6546b64; } @@ -105,4 +105,4 @@ uint32_t hash32(const void* key, size_t len, uint32_t seed) { h1 = fmix32(h1); return h1; -} +} diff --git a/src/util/md5.c b/src/util/md5.c new file mode 100644 index 000000000..9eaa4903b --- /dev/null +++ b/src/util/md5.c @@ -0,0 +1,228 @@ +/* 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/. + * + * Based on https://github.com/Zunawe/md5-c + * Originally released under the Unlicense + * + * Derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm + */ +#include + +#include + +/* + * Constants defined by the MD5 algorithm + */ +#define A 0x67452301 +#define B 0xEFCDAB89 +#define C 0x98BADCFE +#define D 0x10325476 + +static const uint32_t S[] = { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 }; + +static const uint32_t K[] = { 0xD76AA478, 0xE8C7B756, 0x242070DB, 0xC1BDCEEE, + 0xF57C0FAF, 0x4787C62A, 0xA8304613, 0xFD469501, + 0x698098D8, 0x8B44F7AF, 0xFFFF5BB1, 0x895CD7BE, + 0x6B901122, 0xFD987193, 0xA679438E, 0x49B40821, + 0xF61E2562, 0xC040B340, 0x265E5A51, 0xE9B6C7AA, + 0xD62F105D, 0x02441453, 0xD8A1E681, 0xE7D3FBC8, + 0x21E1CDE6, 0xC33707D6, 0xF4D50D87, 0x455A14ED, + 0xA9E3E905, 0xFCEFA3F8, 0x676F02D9, 0x8D2A4C8A, + 0xFFFA3942, 0x8771F681, 0x6D9D6122, 0xFDE5380C, + 0xA4BEEA44, 0x4BDECFA9, 0xF6BB4B60, 0xBEBFBC70, + 0x289B7EC6, 0xEAA127FA, 0xD4EF3085, 0x04881D05, + 0xD9D4D039, 0xE6DB99E5, 0x1FA27CF8, 0xC4AC5665, + 0xF4292244, 0x432AFF97, 0xAB9423A7, 0xFC93A039, + 0x655B59C3, 0x8F0CCC92, 0xFFEFF47D, 0x85845DD1, + 0x6FA87E4F, 0xFE2CE6E0, 0xA3014314, 0x4E0811A1, + 0xF7537E82, 0xBD3AF235, 0x2AD7D2BB, 0xEB86D391 }; + +/* + * Padding used to make the size (in bits) of the input congruent to 448 mod 512 + */ +static const uint8_t PADDING[] = { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* + * Bit-manipulation functions defined by the MD5 algorithm + */ +#define F(X, Y, Z) (((X) & (Y)) | (~(X) & (Z))) +#define G(X, Y, Z) (((X) & (Z)) | ((Y) & ~(Z))) +#define H(X, Y, Z) ((X) ^ (Y) ^ (Z)) +#define I(X, Y, Z) ((Y) ^ ((X) | ~(Z))) + +/* + * Rotates a 32-bit word left by n bits + */ +static uint32_t rotateLeft(uint32_t x, uint32_t n) { + return (x << n) | (x >> (32 - n)); +} + +/* + * Step on 512 bits of input with the main MD5 algorithm. + */ +static void md5Step(uint32_t* buffer, const uint32_t* input) { + uint32_t AA = buffer[0]; + uint32_t BB = buffer[1]; + uint32_t CC = buffer[2]; + uint32_t DD = buffer[3]; + + uint32_t E; + + unsigned j; + + for (unsigned i = 0; i < 64; ++i) { + switch (i / 16) { + case 0: + E = F(BB, CC, DD); + j = i & 0xF; + break; + case 1: + E = G(BB, CC, DD); + j = ((i * 5) + 1) & 0xF; + break; + case 2: + E = H(BB, CC, DD); + j = ((i * 3) + 5) & 0xF; + break; + default: + E = I(BB, CC, DD); + j = (i * 7) & 0xF; + break; + } + + uint32_t temp = DD; + DD = CC; + CC = BB; + BB += rotateLeft(AA + E + K[i] + input[j], S[i]); + AA = temp; + } + + buffer[0] += AA; + buffer[1] += BB; + buffer[2] += CC; + buffer[3] += DD; +} + +/* + * Initialize a context + */ +void md5Init(struct MD5Context* ctx) { + memset(ctx, 0, sizeof(*ctx)); + + ctx->buffer[0] = A; + ctx->buffer[1] = B; + ctx->buffer[2] = C; + ctx->buffer[3] = D; +} + +/* + * Add some amount of input to the context + * + * If the input fills out a block of 512 bits, apply the algorithm (md5Step) + * and save the result in the buffer. Also updates the overall size. + */ +void md5Update(struct MD5Context* ctx, const void* input, size_t len) { + uint32_t buffer[16]; + unsigned offset = ctx->size & 0x3F; + const uint8_t* inputBuffer = input; + ctx->size += len; + + // Copy each byte in input_buffer into the next space in our context input + unsigned i; + for (i = 0; i < len; ++i) { + ctx->input[offset] = inputBuffer[i]; + + // If we've filled our context input, copy it into our local array input + // then reset the offset to 0 and fill in a new buffer. + // Every time we fill out a chunk, we run it through the algorithm + // to enable some back and forth between cpu and i/o + if (offset < 0x3F) { + ++offset; + continue; + } + + unsigned j; + for (j = 0; j < 16; ++j) { + // Convert to little-endian + // The local variable `input` our 512-bit chunk separated into 32-bit words + // we can use in calculations + LOAD_32LE(buffer[j], j * 4, ctx->input); + } + md5Step(ctx->buffer, buffer); + offset = 0; + } +} + +/* + * Pad the current input to get to 448 bits, append the size in bits to the very end, + * and save the result of the final iteration into digest. + */ +void md5Finalize(struct MD5Context* ctx) { + uint32_t input[16]; + int offset = ctx->size & 0x3F; + unsigned paddingLength = offset < 56 ? 56 - offset : (56 + 64) - offset; + + // Fill in the padding and undo the changes to size that resulted from the update + md5Update(ctx, PADDING, paddingLength); + ctx->size -= paddingLength; + + // Do a final update (internal to this function) + // Last two 32-bit words are the two halves of the size (converted from bytes to bits) + unsigned j; + for (j = 0; j < 14; ++j) { + LOAD_32LE(input[j], j * 4, ctx->input); + } + input[14] = (uint32_t) (ctx->size * 8); + input[15] = (uint32_t) ((ctx->size * 8ULL) >> 32); + + md5Step(ctx->buffer, input); + + // Move the result into digest (convert from little-endian) + unsigned i; + for (i = 0; i < 4; ++i) { + STORE_32LE(ctx->buffer[i], i * 4, ctx->digest); + } +} + +void md5Buffer(const void* input, size_t len, uint8_t* result) { + struct MD5Context ctx; + md5Init(&ctx); + md5Update(&ctx, input, len); + md5Finalize(&ctx); + memcpy(result, ctx.digest, sizeof(ctx.digest)); +} + +bool md5File(struct VFile* vf, uint8_t* result) { + struct MD5Context ctx; + uint8_t buffer[2048]; + md5Init(&ctx); + + ssize_t read; + ssize_t position = vf->seek(vf, 0, SEEK_CUR); + if (vf->seek(vf, 0, SEEK_SET) < 0) { + return false; + } + while ((read = vf->read(vf, buffer, sizeof(buffer))) > 0) { + md5Update(&ctx, buffer, read); + } + vf->seek(vf, position, SEEK_SET); + if (read < 0) { + return false; + } + md5Finalize(&ctx); + memcpy(result, ctx.digest, sizeof(ctx.digest)); + return true; +} diff --git a/src/util/sfo.c b/src/util/sfo.c index 97c4e3853..47d5f2702 100644 --- a/src/util/sfo.c +++ b/src/util/sfo.c @@ -91,7 +91,7 @@ bool SfoAddStrValue(struct Table* sfo, const char* name, const char* value) { HashTableInsert(sfo, name, entry); } entry->type = PSF_TYPE_STR; - entry->data.str = value; + entry->data.str = value; return true; } diff --git a/src/util/test/hash.c b/src/util/test/hash.c new file mode 100644 index 000000000..6d014435b --- /dev/null +++ b/src/util/test/hash.c @@ -0,0 +1,130 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include +#include + +M_TEST_DEFINE(emptyCrc32) { + uint8_t buffer[1] = {0}; + assert_int_equal(doCrc32(buffer, 0), 0); +} + +M_TEST_DEFINE(newlineCrc32) { +uint8_t buffer[1] = { '\n' }; + assert_int_equal(doCrc32(buffer, 1), 0x32D70693); +} + +M_TEST_DEFINE(helloWorldCrc32) { + const char* buffer = "Hello, world!"; + assert_int_equal(doCrc32(buffer, strlen(buffer)), 0xEBE6C6E6); +} + +#ifndef HAVE_CRC32 +M_TEST_DEFINE(stagedCrc32) { + uint8_t buffer[1] = { '\n\n' }; + assert_int_equal(crc32(0, buffer, 1), 0x32D70693); + assert_int_equal(crc32(0, buffer, 2), 0x09304EBD); + assert_int_equal(crc32(0x32D70693, buffer, 1), 0x09304EBD); +} +#endif + +M_TEST_DEFINE(emptyMd5) { + uint8_t buffer[1] = {0}; + uint8_t digest[0x10] = {0}; + md5Buffer(buffer, 0, digest); + assert_memory_equal(digest, ((uint8_t[]) { + 0xD4, 0x1D, 0x8C, 0xD9, 0x8F, 0x00, 0xB2, 0x04, + 0xE9, 0x80, 0x09, 0x98, 0xEC, 0xF8, 0x42, 0x7E + }), 16); +} + +M_TEST_DEFINE(newlineMd5) { + uint8_t buffer[1] = { '\n' }; + uint8_t digest[0x10] = {0}; + md5Buffer(buffer, 1, digest); + assert_memory_equal(digest, ((uint8_t[]) { + 0x68, 0xB3, 0x29, 0xDA, 0x98, 0x93, 0xE3, 0x40, + 0x99, 0xC7, 0xD8, 0xAD, 0x5C, 0xB9, 0xC9, 0x40 + }), 16); +} + +M_TEST_DEFINE(fullBlockMd5) { + uint8_t buffer[56] = { + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + }; + uint8_t digest[0x10] = {0}; + md5Buffer(buffer, 56, digest); + assert_memory_equal(digest, ((uint8_t[]) { + 0xA3, 0x31, 0x42, 0x53, 0x78, 0x54, 0xFE, 0xE2, + 0xAF, 0xD6, 0xCF, 0xF4, 0xC5, 0xA1, 0xDD, 0x39 + }), 16); +} + +M_TEST_DEFINE(overflowBlockMd5) { + uint8_t buffer[57] = { + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x0a, + }; + uint8_t digest[0x10] = {0}; + md5Buffer(buffer, 57, digest); + assert_memory_equal(digest, ((uint8_t[]) { + 0xBA, 0x49, 0x77, 0x29, 0x15, 0x5B, 0x13, 0x5D, + 0xBA, 0x27, 0xF3, 0xD8, 0x53, 0xCF, 0xD2, 0x1A + }), 16); +} + +M_TEST_DEFINE(twoBlockMd5) { + uint8_t buffer[120] = { + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + }; + uint8_t digest[0x10] = {0}; + md5Buffer(buffer, 120, digest); + assert_memory_equal(digest, ((uint8_t[]) { + 0xB5, 0x68, 0xA7, 0x7E, 0xD4, 0xC2, 0x39, 0xFB, + 0x4B, 0x74, 0xD7, 0x5B, 0xDB, 0xFD, 0x94, 0x93 + }), 16); +} + +M_TEST_SUITE_DEFINE(Hashes, + cmocka_unit_test(emptyCrc32), + cmocka_unit_test(newlineCrc32), + cmocka_unit_test(helloWorldCrc32), +#ifndef HAVE_CRC32 + cmocka_unit_test(stagedCrc32), +#endif + cmocka_unit_test(emptyMd5), + cmocka_unit_test(newlineMd5), + cmocka_unit_test(fullBlockMd5), + cmocka_unit_test(overflowBlockMd5), + cmocka_unit_test(twoBlockMd5), +)