From e63c2985fc9e5a65985e08e8601d6311f3205ad4 Mon Sep 17 00:00:00 2001 From: Arisotura Date: Sun, 25 Sep 2022 21:33:17 +0200 Subject: [PATCH] modernize camera_betterer --- .github/azure-workflows/build-mac-arm64.yml | 26 - .github/azure-workflows/build-mac-x86_64.yml | 24 - .github/workflows/build-macos-universal.yml | 73 + .github/workflows/build-windows.yml | 2 +- CMakeLists.txt | 126 +- README.md | 90 +- cmake/FixInterfaceIncludes.cmake | 28 + res/melon.plist.in | 4 +- res/melon.rc.in | 8 +- src/ARMJIT.cpp | 29 +- src/ARMJIT.h | 1 + src/ARMJIT_RegisterCache.h | 3 +- src/CMakeLists.txt | 228 +- src/CP15.cpp | 3 + src/DMA.h | 3 +- src/DMA_Timings.h | 12 +- src/DSi.cpp | 156 +- src/DSi.h | 2 + src/DSi_AES.h | 1 + src/DSi_Camera.h | 1 + src/DSi_DSP.cpp | 20 + src/DSi_DSP.h | 8 +- src/DSi_I2C.h | 1 + src/DSi_NAND.cpp | 49 +- src/DSi_NAND.h | 4 +- src/DSi_NDMA.h | 1 + src/DSi_NWifi.h | 1 + src/DSi_SD.cpp | 11 +- src/DSi_SD.h | 4 +- src/DSi_SPI_TSC.h | 3 + src/FIFO.h | 1 + src/GPU.cpp | 7 + src/GPU_OpenGL.cpp | 4 +- src/NDS.cpp | 123 +- src/NDS.h | 1 + src/NDSCart.cpp | 19 +- src/NDSCart.h | 1 + src/Platform.h | 17 +- src/ROMList.h | 2 + src/SPI.cpp | 55 +- src/SPU.cpp | 6 + src/SPU.h | 2 + src/Wifi.cpp | 1434 +++- src/Wifi.h | 34 +- src/WifiAP.cpp | 14 +- src/WifiAP.h | 3 +- src/frontend/qt_sdl/AudioSettingsDialog.cpp | 14 + src/frontend/qt_sdl/AudioSettingsDialog.ui | 11 +- src/frontend/qt_sdl/CMakeLists.txt | 104 +- src/frontend/qt_sdl/Config.cpp | 367 +- src/frontend/qt_sdl/Config.h | 6 +- .../qt_sdl/FirmwareSettingsDialog.cpp | 20 +- src/frontend/qt_sdl/FirmwareSettingsDialog.h | 1 - src/frontend/qt_sdl/FirmwareSettingsDialog.ui | 13 +- .../qt_sdl/InputConfig/InputConfigDialog.cpp | 7 + .../qt_sdl/InputConfig/InputConfigDialog.ui | 145 +- src/frontend/qt_sdl/InputConfig/MapButton.h | 27 +- src/frontend/qt_sdl/LAN_PCap.cpp | 12 +- src/frontend/qt_sdl/LocalMP.cpp | 634 ++ src/frontend/qt_sdl/LocalMP.h | 45 + src/frontend/qt_sdl/MPSettingsDialog.cpp | 73 + src/frontend/qt_sdl/MPSettingsDialog.h | 65 + src/frontend/qt_sdl/MPSettingsDialog.ui | 142 + src/frontend/qt_sdl/OSD.cpp | 4 +- src/frontend/qt_sdl/PathSettingsDialog.cpp | 7 + src/frontend/qt_sdl/PathSettingsDialog.ui | 87 +- src/frontend/qt_sdl/Platform.cpp | 266 +- .../PowerManagement/PowerManagementDialog.cpp | 7 + .../PowerManagement/PowerManagementDialog.ui | 79 +- src/frontend/qt_sdl/ROMInfoDialog.cpp | 12 +- src/frontend/qt_sdl/ROMInfoDialog.ui | 65 +- src/frontend/qt_sdl/ROMManager.cpp | 12 + src/frontend/qt_sdl/TitleManagerDialog.cpp | 19 +- src/frontend/qt_sdl/TitleManagerDialog.h | 2 +- src/frontend/qt_sdl/WifiSettingsDialog.cpp | 5 +- src/frontend/qt_sdl/WifiSettingsDialog.ui | 151 +- src/frontend/qt_sdl/main.cpp | 348 +- src/frontend/qt_sdl/main.h | 32 +- src/frontend/qt_sdl/sem_timedwait.cpp | 488 ++ src/frontend/qt_sdl/sem_timedwait.h | 8 + src/melonDLDI.h | 2 + src/teakra/CMakeLists.txt | 1 + src/teakra/externals/catch/catch.hpp | 6999 +++++++++++++---- src/teakra/src/interpreter.h | 1 + tools/mac-libs.rb | 28 +- tools/mac-universal.py | 118 + 86 files changed, 9697 insertions(+), 3375 deletions(-) delete mode 100644 .github/azure-workflows/build-mac-arm64.yml delete mode 100644 .github/azure-workflows/build-mac-x86_64.yml create mode 100644 .github/workflows/build-macos-universal.yml create mode 100644 cmake/FixInterfaceIncludes.cmake create mode 100644 src/frontend/qt_sdl/LocalMP.cpp create mode 100644 src/frontend/qt_sdl/LocalMP.h create mode 100644 src/frontend/qt_sdl/MPSettingsDialog.cpp create mode 100644 src/frontend/qt_sdl/MPSettingsDialog.h create mode 100644 src/frontend/qt_sdl/MPSettingsDialog.ui create mode 100644 src/frontend/qt_sdl/sem_timedwait.cpp create mode 100644 src/frontend/qt_sdl/sem_timedwait.h create mode 100755 tools/mac-universal.py diff --git a/.github/azure-workflows/build-mac-arm64.yml b/.github/azure-workflows/build-mac-arm64.yml deleted file mode 100644 index 721c6acc..00000000 --- a/.github/azure-workflows/build-mac-arm64.yml +++ /dev/null @@ -1,26 +0,0 @@ -trigger: -- master - -pool: - name: Default - demands: - - agent.name -equals MacStadium-ARM64-Mac - -workspace: - clean: all - -steps: -- script: mkdir $(Pipeline.Workspace)/build - displayName: 'Create build environment' - -- script: arch -arm64 cmake $(Build.SourcesDirectory) -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6);$(brew --prefix libarchive)" -DMACOS_BUNDLE_LIBS=ON -DMACOS_BUILD_DMG=ON -DUSE_QT6=ON - displayName: 'Configure' - workingDirectory: $(Pipeline.Workspace)/build - -- script: arch -arm64 make -j$(sysctl -n hw.logicalcpu) - displayName: 'Make' - workingDirectory: $(Pipeline.Workspace)/build - -- publish: $(Pipeline.Workspace)/build/melonDS.dmg - artifact: melonDS.dmg - diff --git a/.github/azure-workflows/build-mac-x86_64.yml b/.github/azure-workflows/build-mac-x86_64.yml deleted file mode 100644 index 3151c256..00000000 --- a/.github/azure-workflows/build-mac-x86_64.yml +++ /dev/null @@ -1,24 +0,0 @@ -trigger: -- master - -pool: - vmImage: macOS-10.15 - -steps: -- script: brew install llvm sdl2 qt@6 libslirp libarchive libepoxy - displayName: 'Install dependencies' - -- script: mkdir $(Pipeline.Workspace)/build - displayName: 'Create build environment' - -- script: cmake $(Build.SourcesDirectory) -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6);$(brew --prefix libarchive)" -DMACOS_BUNDLE_LIBS=ON -DMACOS_BUILD_DMG=ON -DUSE_QT6=ON - displayName: 'Configure' - workingDirectory: $(Pipeline.Workspace)/build - -- script: make -j$(sysctl -n hw.logicalcpu) - displayName: 'Make' - workingDirectory: $(Pipeline.Workspace)/build - -- publish: $(Pipeline.Workspace)/build/melonDS.dmg - artifact: melonDS.dmg - diff --git a/.github/workflows/build-macos-universal.yml b/.github/workflows/build-macos-universal.yml new file mode 100644 index 00000000..0f0089f3 --- /dev/null +++ b/.github/workflows/build-macos-universal.yml @@ -0,0 +1,73 @@ +name: CMake Build (macOS Universal) + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + prepare: + runs-on: [self-hosted, macOS, ARM64] + + steps: + - name: Clean workspace + run: rm -rf ${{runner.workspace}}/build + + - uses: actions/checkout@v3 + + + build-arm64: + needs: prepare + runs-on: [self-hosted, macOS, ARM64] + env: + homebrew_prefix: /opt/homebrew + + steps: + - name: Create build directory + run: mkdir -p ${{runner.workspace}}/build/arm64 + + - name: Configure + working-directory: ${{runner.workspace}}/build/arm64 + run: arch -arm64 ${{env.homebrew_prefix}}/bin/cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PREFIX_PATH="${{env.homebrew_prefix}}/opt/qt@6;${{env.homebrew_prefix}}/opt/libarchive" -DPKG_CONFIG_EXECUTABLE=${{env.homebrew_prefix}}/bin/pkg-config -DMACOS_BUNDLE_LIBS=ON -DUSE_QT6=ON + + - name: Make + working-directory: ${{runner.workspace}}/build/arm64 + run: arch -arm64 make -j$(sysctl -n hw.logicalcpu) + + build-x86_64: + needs: prepare + runs-on: [self-hosted, macOS, ARM64] + env: + homebrew_prefix: /usr/local + + steps: + - name: Create build directory + run: mkdir -p ${{runner.workspace}}/build/x86_64 + + - name: Configure + working-directory: ${{runner.workspace}}/build/x86_64 + run: arch -x86_64 ${{env.homebrew_prefix}}/bin/cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PREFIX_PATH="${{env.homebrew_prefix}}/opt/qt@6;${{env.homebrew_prefix}}/opt/libarchive" -DPKG_CONFIG_EXECUTABLE=${{env.homebrew_prefix}}/bin/pkg-config -DMACOS_BUNDLE_LIBS=ON -DUSE_QT6=ON + + - name: Make + working-directory: ${{runner.workspace}}/build/x86_64 + run: arch -x86_64 make -j$(sysctl -n hw.logicalcpu) + + universal-binary: + needs: [build-arm64, build-x86_64] + runs-on: [self-hosted, macOS, ARM64] + + steps: + - name: Merge binaries + run: $GITHUB_WORKSPACE/tools/mac-universal.py ${{runner.workspace}}/build/arm64/melonDS.app ${{runner.workspace}}/build/x86_64/melonDS.app ${{runner.workspace}}/build/universal/melonDS.app + + - name: Create DMG + run: hdiutil create -fs HFS+ -volname melonDS -srcfolder ${{runner.workspace}}/build/universal/melonDS.app -ov -format UDBZ ${{runner.workspace}}/build/universal/melonDS.dmg + + - uses: actions/upload-artifact@v3 + with: + name: macOS-universal + path: ${{runner.workspace}}/build/universal/melonDS.dmg + diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index a4026a7b..bf44f6de 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -35,7 +35,7 @@ jobs: - name: Configure working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_STATIC=ON -DQT5_STATIC_DIR=C:/tools/msys64/mingw64/qt5-static + run: cmake $GITHUB_WORKSPACE -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_STATIC=ON -DCMAKE_PREFIX_PATH=C:/tools/msys64/mingw64/qt5-static - name: Make working-directory: ${{runner.workspace}}/build diff --git a/CMakeLists.txt b/CMakeLists.txt index 60dac082..297f71d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,43 +1,38 @@ -cmake_minimum_required(VERSION 3.13) +cmake_minimum_required(VERSION 3.15) -include(CheckSymbolExists) -include(CheckLibraryExists) - -cmake_policy(VERSION 3.13) +cmake_policy(VERSION 3.15) if (POLICY CMP0076) cmake_policy(SET CMP0076 NEW) endif() +set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) + +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) + +project(melonDS + VERSION 0.9.4 + DESCRIPTION "DS emulator, sorta" + HOMEPAGE_URL "https://melonds.kuribo64.net" + LANGUAGES C CXX) + +include(CheckSymbolExists) +include(CheckLibraryExists) +include(CMakeDependentOption) +include(CheckIPOSupported) + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") -project(melonDS CXX) - set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -set(MELONDS_VERSION "0.9.4") -add_compile_definitions(MELONDS_VERSION="${MELONDS_VERSION}") -string(REPLACE "." ";" VERSION_LIST ${MELONDS_VERSION}) -# For the melon.rc file used on Windows -list(GET VERSION_LIST 0 MELONDS_VERSION_MAJOR) -list(GET VERSION_LIST 1 MELONDS_VERSION_MINOR) -# Check if melonDS version is three digits or two digits -list(LENGTH VERSION_LIST MELONDS_VER_LEN) -if (${MELONDS_VER_LEN} GREATER 2) - list(GET VERSION_LIST 2 MELONDS_VERSION_PATCH) -else() - set(MELONDS_VERSION_PATCH 0) -endif() +add_compile_definitions(MELONDS_VERSION="${melonDS_VERSION}") - -check_library_exists(m pow "" LIBM) -if(LIBM) - link_libraries(m) -endif() - -if (NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release) +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() function(detect_architecture symbol arch) @@ -61,74 +56,41 @@ detect_architecture("__i386__" x86) detect_architecture("__arm__" ARM) detect_architecture("__aarch64__" ARM64) -if (ARCHITECTURE STREQUAL x86_64 OR ARCHITECTURE STREQUAL ARM64) - option(ENABLE_JIT "Enable x64 JIT recompiler" ON) -endif() - -if (ENABLE_JIT) - add_definitions(-DJIT_ENABLED) - - option(ENABLE_JIT_PROFILING "Enable JIT profiling with VTune" OFF) - - if (ENABLE_JIT_PROFILING) - include(cmake/FindVTune.cmake) - add_definitions(-DJIT_PROFILING_ENABLED) - endif() -endif() - -if (CMAKE_BUILD_TYPE STREQUAL Release) - option(ENABLE_LTO "Enable link-time optimization" ON) -else() - option(ENABLE_LTO "Enable link-time optimization" OFF) -endif() - +cmake_dependent_option(ENABLE_JIT "Enable JIT recompiler" ON + "ARCHITECTURE STREQUAL x86_64 OR ARCHITECTURE STREQUAL ARM64" OFF) +cmake_dependent_option(ENABLE_JIT_PROFILING "Enable JIT profiling with VTune" OFF "ENABLE_JIT" OFF) option(ENABLE_OGLRENDERER "Enable OpenGL renderer" ON) -if (ENABLE_OGLRENDERER) - add_definitions(-DOGLRENDERER_ENABLED) +check_ipo_supported(RESULT IPO_SUPPORTED) +cmake_dependent_option(ENABLE_LTO_RELEASE "Enable link-time optimizations for release builds" ON "IPO_SUPPORTED" OFF) +cmake_dependent_option(ENABLE_LTO "Enable link-time optimizations" OFF "IPO_SUPPORTED" OFF) + +if (ENABLE_LTO_RELEASE) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE) endif() -if (CMAKE_BUILD_TYPE STREQUAL Debug) - add_compile_options(-Og) +if (ENABLE_LTO) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) endif() -if (CMAKE_BUILD_TYPE STREQUAL Release) - add_compile_options(-O3) - if (NOT APPLE) - add_link_options(-s) - endif() +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Og") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") +set(CMAKE_C_FLAGS_RELEASE "-O3") +set(CMAKE_CXX_FLAGS_RELEASE "-O3") + +if (NOT APPLE) + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -s") endif() if (WIN32) - option(BUILD_STATIC "Statically link dependencies" OFF) + option(BUILD_STATIC "Statically link dependencies" OFF) endif() if (BUILD_STATIC AND WIN32) set(CMAKE_FIND_LIBRARY_SUFFIXES .a) endif() -if (ENABLE_LTO) - if (WIN32 OR APPLE) - add_compile_options(-flto) - add_link_options(-flto) - else() - add_compile_options(-flto -fPIC) - add_link_options(-flto -fuse-linker-plugin -pie) - endif() - if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(CMAKE_AR "gcc-ar") - set(CMAKE_RANLIB "gcc-ranlib") - elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - find_program(LLD NAMES ld.lld ld64.lld lld-link) - if (NOT LLD STREQUAL "LLD-NOTFOUND") - add_link_options(-fuse-ld=lld) - endif() - if (NOT APPLE) - set(CMAKE_AR "llvm-ar") - set(CMAKE_RANLIB "llvm-ranlib") - endif() - endif() -endif() +set(CMAKE_POSITION_INDEPENDENT_CODE ON) find_program(CCACHE "ccache") if (CCACHE) @@ -142,5 +104,5 @@ option(BUILD_QT_SDL "Build Qt/SDL frontend" ON) add_subdirectory(src) if (BUILD_QT_SDL) - add_subdirectory(src/frontend/qt_sdl) + add_subdirectory(src/frontend/qt_sdl) endif() diff --git a/README.md b/README.md index 028acad8..340f5c3b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

+

melonDS

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

@@ -34,69 +34,75 @@ As for the rest, the interface should be pretty straightforward. If you have a q ## How to build -### Linux: +### Linux +1. Install dependencies: + * Ubuntu 22.04: `sudo apt install cmake libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qtbase5-dev libslirp-dev libarchive-dev libepoxy-dev` + * Older Ubuntu: `sudo apt install cmake libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default libslirp-dev libarchive-dev libepoxy-dev` + * Arch Linux: `sudo pacman -S base-devel cmake git libpcap sdl2 qt5-base libslirp libarchive libepoxy` +3. Download the melonDS repository and prepare: + ```bash + git clone https://github.com/melonDS-emu/melonDS + cd melonDS + ``` -1. Install dependencies: `sudo apt install cmake libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default libslirp-dev libarchive-dev libepoxy-dev` -2. Download the melonDS repository and prepare: - ```bash - git clone https://github.com/Arisotura/melonDS - cd melonDS - mkdir build && cd build - ``` 3. Compile: - ```bash - cmake .. - make -j$(nproc --all) - ``` - -### Windows: + ```bash + cmake -B build + cmake --build build -j$(nproc --all) + ``` +### Windows 1. Install [MSYS2](https://www.msys2.org/) 2. Open the **MSYS2 MinGW 64-bit** terminal 3. Update the packages using `pacman -Syu` and reopen the terminal if it asks you to -4. Download the melonDS repository and prepare: - ```bash - git clone https://github.com/Arisotura/melonDS - cd melonDS - mkdir build && cd build - ``` +4. Install git to clone the repository + ```bash + pacman -S git + ``` +5. Download the melonDS repository and prepare: + ```bash + git clone https://github.com/melonDS-emu/melonDS + cd melonDS + ``` #### Dynamic builds (with DLLs) -5. Install dependencies: `pacman -S git make mingw-w64-x86_64-{cmake,mesa,SDL2,toolchain,qt5,libslirp,libarchive,libepoxy}` +5. Install dependencies: `pacman -S make mingw-w64-x86_64-{cmake,mesa,SDL2,toolchain,qt5,libslirp,libarchive,libepoxy}` 6. Compile: ```bash - cmake .. -G "MSYS Makefiles" - make -j$(nproc --all) + cmake -B build -G "MSYS Makefiles" + cmake --build build -j$(nproc --all) + cd build ../tools/msys-dist.sh ``` If everything went well, melonDS and the libraries it needs should now be in the `dist` folder. #### Static builds (without DLLs, standalone executable) -5. Install dependencies: `pacman -S git make mingw-w64-x86_64-{cmake,mesa,SDL2,toolchain,qt5-static,libslirp,libarchive,libepoxy}` +5. Install dependencies: `pacman -S make mingw-w64-x86_64-{cmake,mesa,SDL2,toolchain,qt5-static,libslirp,libarchive,libepoxy}` 6. Compile: ```bash - cmake .. -G 'MSYS Makefiles' -DBUILD_STATIC=ON -DQT5_STATIC_DIR=/mingw64/qt5-static - make -j$(nproc --all) - mkdir dist && cp melonDS.exe dist + cmake -B build -G 'MSYS Makefiles' -DBUILD_STATIC=ON -DCMAKE_PREFIX_PATH=/mingw64/qt5-static + cmake --build build -j$(nproc --all) ``` -If everything went well, melonDS should now be in the `dist` folder. +If everything went well, melonDS should now be in the `build` folder. -### macOS: +### macOS 1. Install the [Homebrew Package Manager](https://brew.sh) 2. Install dependencies: `brew install git pkg-config cmake sdl2 qt@6 libslirp libarchive libepoxy` 3. Download the melonDS repository and prepare: - ```zsh - git clone https://github.com/Arisotura/melonDS - cd melonDS - mkdir build && cd build - ``` + ```zsh + git clone https://github.com/melonDS-emu/melonDS + cd melonDS + ``` 4. Compile: ```zsh - cmake .. -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6);$(brew --prefix libarchive)" -DUSE_QT6=ON -DMACOS_BUNDLE_LIBS=ON - make -j$(sysctl -n hw.logicalcpu) + cmake -B build -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6);$(brew --prefix libarchive)" -DUSE_QT6=ON + cmake --build build -j$(sysctl -n hw.logicalcpu) ``` -If everything went well, melonDS.app should now be in the current directory. +If everything went well, melonDS.app should now be in the `build` directory. + +#### Self-contained app bundle +If you want an app bundle that can be distributed to other computers without needing to install dependencies through Homebrew, you can additionally run ` +../tools/mac-bundle.rb melonDS.app` after the build is completed, or add `-DMACOS_BUNDLE_LIBS=ON` to the first CMake command. - ## TODO LIST * better DSi emulation diff --git a/cmake/FixInterfaceIncludes.cmake b/cmake/FixInterfaceIncludes.cmake new file mode 100644 index 00000000..513c1117 --- /dev/null +++ b/cmake/FixInterfaceIncludes.cmake @@ -0,0 +1,28 @@ +# The entire codebase quite reasonably does things like #include or +# CMake apparently doesn't think you should be doing this, so just includes $PREFIX/include/packagename for a given +# package as include directories when using `target_link_libraries` with an imported target, this hacky function fixes +# that up so includes can keep working as they always did but we can still use fancy imported targets. +# This is stupid. + +function(fix_interface_includes) + foreach (target ${ARGN}) + set(NEW_DIRS) + get_target_property(DIRS "${target}" INTERFACE_INCLUDE_DIRECTORIES) + + if (NOT DIRS) + continue() + endif() + + foreach (DIR ${DIRS}) + get_filename_component(PARENT_DIR "${DIR}" DIRECTORY) + + if (PARENT_DIR MATCHES "include$") + list(APPEND NEW_DIRS "${PARENT_DIR}") + endif() + endforeach() + + list(APPEND DIRS ${NEW_DIRS}) + set_target_properties("${target}" PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${DIRS}") + endforeach() +endfunction() + diff --git a/res/melon.plist.in b/res/melon.plist.in index 1057c474..20d385a0 100644 --- a/res/melon.plist.in +++ b/res/melon.plist.in @@ -13,9 +13,9 @@ CFBundlePackageType APPL CFBundleVersion - ${MELONDS_VERSION} + ${melonDS_VERSION} CFBundleShortVersionString - ${MELONDS_VERSION} + ${melonDS_VERSION} NSHumanReadableCopyright Licensed under GPLv3 NSHighResolutionCapable diff --git a/res/melon.rc.in b/res/melon.rc.in index 3851813c..27c7cbdf 100644 --- a/res/melon.rc.in +++ b/res/melon.rc.in @@ -6,8 +6,8 @@ //include version information in .exe, modify these values to match your needs 1 VERSIONINFO -FILEVERSION ${MELONDS_VERSION_MAJOR},${MELONDS_VERSION_MINOR},${MELONDS_VERSION_PATCH},0 -PRODUCTVERSION ${MELONDS_VERSION_MAJOR},${MELONDS_VERSION_MINOR},${MELONDS_VERSION_PATCH},0 +FILEVERSION ${melonDS_VERSION_MAJOR},${melonDS_VERSION_MINOR},${melonDS_VERSION_PATCH},0 +PRODUCTVERSION ${melonDS_VERSION_MAJOR},${melonDS_VERSION_MINOR},${melonDS_VERSION_PATCH},0 FILETYPE VFT_APP { BLOCK "StringFileInfo" @@ -15,14 +15,14 @@ FILETYPE VFT_APP BLOCK "040904E4" { VALUE "CompanyName", "Melon Factory of Kuribo64" - VALUE "FileVersion", "${MELONDS_VERSION}" + VALUE "FileVersion", "${melonDS_VERSION}" VALUE "FileDescription", "melonDS emulator" VALUE "InternalName", "SDnolem" VALUE "LegalCopyright", "2016-2022 melonDS team" VALUE "LegalTrademarks", "" VALUE "OriginalFilename", "zafkflzdasd.exe" VALUE "ProductName", "melonDS" - VALUE "ProductVersion", "${MELONDS_VERSION}" + VALUE "ProductVersion", "${melonDS_VERSION}" } } BLOCK "VarFileInfo" diff --git a/src/ARMJIT.cpp b/src/ARMJIT.cpp index 99e5a3ff..32f20d57 100644 --- a/src/ARMJIT.cpp +++ b/src/ARMJIT.cpp @@ -1086,11 +1086,34 @@ void InvalidateByAddr(u32 localAddr) void CheckAndInvalidateITCM() { - for (u32 i = 0; i < ITCMPhysicalSize; i+=16) + for (u32 i = 0; i < ITCMPhysicalSize; i+=512) { - if (CodeIndexITCM[i / 512].Code & (1 << ((i & 0x1FF) / 16))) + if (CodeIndexITCM[i / 512].Code) { - InvalidateByAddr(i | (ARMJIT_Memory::memregion_ITCM << 27)); + // maybe using bitscan would be better here? + // The thing is that in densely populated sets + // The old fashioned way can actually be faster + for (u32 j = 0; j < 512; j += 16) + { + if (CodeIndexITCM[i / 512].Code & (1 << ((j & 0x1FF) / 16))) + InvalidateByAddr((i+j) | (ARMJIT_Memory::memregion_ITCM << 27)); + } + } + } +} + +void CheckAndInvalidateWVRAM(int bank) +{ + u32 start = bank == 1 ? 0x20000 : 0; + for (u32 i = start; i < start+0x20000; i+=512) + { + if (CodeIndexARM7WVRAM[i / 512].Code) + { + for (u32 j = 0; j < 512; j += 16) + { + if (CodeIndexARM7WVRAM[i / 512].Code & (1 << ((j & 0x1FF) / 16))) + InvalidateByAddr((i+j) | (ARMJIT_Memory::memregion_VWRAM << 27)); + } } } } diff --git a/src/ARMJIT.h b/src/ARMJIT.h index ec2161a5..97c79cd9 100644 --- a/src/ARMJIT.h +++ b/src/ARMJIT.h @@ -44,6 +44,7 @@ void DeInit(); void Reset(); void CheckAndInvalidateITCM(); +void CheckAndInvalidateWVRAM(int bank); void InvalidateByAddr(u32 pseudoPhysical); diff --git a/src/ARMJIT_RegisterCache.h b/src/ARMJIT_RegisterCache.h index 603147ed..e0c4a157 100644 --- a/src/ARMJIT_RegisterCache.h +++ b/src/ARMJIT_RegisterCache.h @@ -20,6 +20,7 @@ #define ARMJIT_REGCACHE_H #include "ARMJIT.h" +#include "ARMJIT_Internal.h" // TODO: replace this in the future #include "dolphin/BitSet.h" @@ -41,7 +42,7 @@ public: { for (int i = 0; i < 16; i++) Mapping[i] = (Reg)-1; - + PCAllocatableAsSrc = ~(pcAllocatableAsSrc ? 0 : (1 << 15)); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7288b540..cdb05871 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,141 +1,145 @@ -project(core) - set (CMAKE_CXX_STANDARD 17) -add_library(core STATIC - ARCodeFile.cpp - AREngine.cpp - ARM.cpp - ARM_InstrTable.h - ARMInterpreter.cpp - ARMInterpreter_ALU.cpp - ARMInterpreter_Branch.cpp - ARMInterpreter_LoadStore.cpp - CP15.cpp - CRC32.cpp - DMA.cpp - DMA_Timings.h - DSi.cpp - DSi_AES.cpp - DSi_Camera.cpp - DSi_DSP.cpp - DSi_I2C.cpp - DSi_NAND.cpp - DSi_NDMA.cpp - DSi_NWifi.cpp - DSi_SD.cpp - DSi_SPI_TSC.cpp - FATStorage.cpp - FIFO.h - GBACart.cpp - GPU.cpp - GPU2D.cpp - GPU2D_Soft.cpp - GPU3D.cpp - GPU3D_Soft.cpp - melonDLDI.h - NDS.cpp - NDSCart.cpp - Platform.h - ROMList.h - FreeBIOS.h - RTC.cpp - Savestate.cpp - SPI.cpp - SPU.cpp - types.h - version.h - Wifi.cpp - WifiAP.cpp +include(FixInterfaceIncludes) - fatfs/diskio.c - fatfs/ff.c - fatfs/ffsystem.c - fatfs/ffunicode.c - fatfs/ffconf.h - - sha1/sha1.c - tiny-AES-c/aes.c - xxhash/xxhash.c -) +add_library(core STATIC + ARCodeFile.cpp + AREngine.cpp + ARM.cpp + ARM_InstrTable.h + ARMInterpreter.cpp + ARMInterpreter_ALU.cpp + ARMInterpreter_Branch.cpp + ARMInterpreter_LoadStore.cpp + CP15.cpp + CRC32.cpp + DMA.cpp + DMA_Timings.h + DSi.cpp + DSi_AES.cpp + DSi_Camera.cpp + DSi_DSP.cpp + DSi_I2C.cpp + DSi_NAND.cpp + DSi_NDMA.cpp + DSi_NWifi.cpp + DSi_SD.cpp + DSi_SPI_TSC.cpp + FATStorage.cpp + FIFO.h + GBACart.cpp + GPU.cpp + GPU2D.cpp + GPU2D_Soft.cpp + GPU3D.cpp + GPU3D_Soft.cpp + melonDLDI.h + NDS.cpp + NDSCart.cpp + Platform.h + ROMList.h + FreeBIOS.h + RTC.cpp + Savestate.cpp + SPI.cpp + SPU.cpp + types.h + version.h + Wifi.cpp + WifiAP.cpp + + fatfs/diskio.c + fatfs/ff.c + fatfs/ffsystem.c + fatfs/ffunicode.c + fatfs/ffconf.h + + sha1/sha1.c + tiny-AES-c/aes.c + xxhash/xxhash.c) if (ENABLE_OGLRENDERER) - target_sources(core PRIVATE - GPU_OpenGL.cpp - GPU_OpenGL_shaders.h - GPU3D_OpenGL.cpp - GPU3D_OpenGL_shaders.h - OpenGLSupport.cpp - ) + target_sources(core PRIVATE + GPU_OpenGL.cpp + GPU_OpenGL_shaders.h + GPU3D_OpenGL.cpp + GPU3D_OpenGL_shaders.h + OpenGLSupport.cpp) endif() if (ENABLE_JIT) - enable_language(ASM) + enable_language(ASM) - target_sources(core PRIVATE - ARM_InstrInfo.cpp + target_sources(core PRIVATE + ARM_InstrInfo.cpp - ARMJIT.cpp - ARMJIT_Memory.cpp + ARMJIT.cpp + ARMJIT_Memory.cpp - dolphin/CommonFuncs.cpp - ) + dolphin/CommonFuncs.cpp) - if (ARCHITECTURE STREQUAL x86_64) - target_sources(core PRIVATE - dolphin/x64ABI.cpp - dolphin/x64CPUDetect.cpp - dolphin/x64Emitter.cpp + if (ARCHITECTURE STREQUAL x86_64) + target_sources(core PRIVATE + dolphin/x64ABI.cpp + dolphin/x64CPUDetect.cpp + dolphin/x64Emitter.cpp - ARMJIT_x64/ARMJIT_Compiler.cpp - ARMJIT_x64/ARMJIT_ALU.cpp - ARMJIT_x64/ARMJIT_LoadStore.cpp - ARMJIT_x64/ARMJIT_Branch.cpp + ARMJIT_x64/ARMJIT_Compiler.cpp + ARMJIT_x64/ARMJIT_ALU.cpp + ARMJIT_x64/ARMJIT_LoadStore.cpp + ARMJIT_x64/ARMJIT_Branch.cpp - ARMJIT_x64/ARMJIT_Linkage.S - ) - endif() - if (ARCHITECTURE STREQUAL ARM64) - target_sources(core PRIVATE - dolphin/Arm64Emitter.cpp - dolphin/MathUtil.cpp + ARMJIT_x64/ARMJIT_Linkage.S) + endif() + if (ARCHITECTURE STREQUAL ARM64) + target_sources(core PRIVATE + dolphin/Arm64Emitter.cpp + dolphin/MathUtil.cpp - ARMJIT_A64/ARMJIT_Compiler.cpp - ARMJIT_A64/ARMJIT_ALU.cpp - ARMJIT_A64/ARMJIT_LoadStore.cpp - ARMJIT_A64/ARMJIT_Branch.cpp + ARMJIT_A64/ARMJIT_Compiler.cpp + ARMJIT_A64/ARMJIT_ALU.cpp + ARMJIT_A64/ARMJIT_LoadStore.cpp + ARMJIT_A64/ARMJIT_Branch.cpp - ARMJIT_A64/ARMJIT_Linkage.S - ) - endif() + ARMJIT_A64/ARMJIT_Linkage.S) + endif() endif() add_subdirectory(teakra EXCLUDE_FROM_ALL) -target_link_libraries(core teakra) +target_link_libraries(core PRIVATE teakra) +find_library(m MATH_LIBRARY) + +if (MATH_LIBRARY) + target_link_libraries(core PRIVATE ${MATH_LIBRARY}) +endif() if (ENABLE_OGLRENDERER) find_package(PkgConfig REQUIRED) - pkg_check_modules(EPOXY REQUIRED epoxy) + pkg_check_modules(Epoxy REQUIRED IMPORTED_TARGET epoxy) + fix_interface_includes(PkgConfig::Epoxy) - target_include_directories(core PRIVATE ${EPOXY_INCLUDE_DIRS}) - if (WIN32) - target_link_libraries(core ole32 comctl32 ws2_32 ${EPOXY_LIBRARIES}) - elseif (APPLE) - target_link_libraries(core ${EPOXY_LIBRARIES}) - else() - target_link_libraries(core rt ${EPOXY_LIBRARIES}) + target_link_libraries(core PUBLIC PkgConfig::Epoxy) + + target_compile_definitions(core PUBLIC OGLRENDERER_ENABLED) +endif() + +if (ENABLE_JIT) + target_compile_definitions(core PUBLIC JIT_ENABLED) + + + if (ENABLE_JIT_PROFILING) + include(cmake/FindVTune.cmake) + add_definitions(-DJIT_PROFILING_ENABLED) endif() -else() - if (WIN32) - target_link_libraries(core ole32 comctl32 ws2_32) - elseif (APPLE) - target_link_libraries(core) - else() - target_link_libraries(core rt) - endif() +endif() + +if (WIN32) + target_link_libraries(core PRIVATE ole32 comctl32 ws2_32) +elseif(NOT APPLE) + target_link_libraries(core PRIVATE rt) endif() if (ENABLE_JIT_PROFILING) - target_link_libraries(core jitprofiling) + target_link_libraries(core PRIVATE jitprofiling) endif() diff --git a/src/CP15.cpp b/src/CP15.cpp index 2548ecec..4fe91cf5 100644 --- a/src/CP15.cpp +++ b/src/CP15.cpp @@ -134,6 +134,9 @@ void ARMv5::UpdateITCMSetting() if (CP15Control & (1<<18)) { ITCMSize = 0x200 << ((ITCMSetting >> 1) & 0x1F); +#ifdef JIT_ENABLED + FastBlockLookupSize = 0; +#endif } else { diff --git a/src/DMA.h b/src/DMA.h index 7866631e..ad194c11 100644 --- a/src/DMA.h +++ b/src/DMA.h @@ -20,6 +20,7 @@ #define DMA_H #include "types.h" +#include "Savestate.h" class DMA { @@ -96,7 +97,7 @@ private: bool IsGXFIFODMA; u32 MRAMBurstCount; - u8* MRAMBurstTable; + const u8* MRAMBurstTable; }; #endif diff --git a/src/DMA_Timings.h b/src/DMA_Timings.h index 1283751b..4281c783 100644 --- a/src/DMA_Timings.h +++ b/src/DMA_Timings.h @@ -19,6 +19,8 @@ #ifndef DMA_TIMINGS_H #define DMA_TIMINGS_H +#include "types.h" + namespace DMATiming { @@ -43,9 +45,9 @@ namespace DMATiming // setting. Timings are such that the nonseq setting only matters for the first // access, and minor edge cases (like the last of a 0x20000-byte block). -u8 MRAMDummy[1] = {0}; +constexpr u8 MRAMDummy[1] = {0}; -u8 MRAMRead16Bursts[][256] = +constexpr u8 MRAMRead16Bursts[][256] = { // main RAM to regular 16bit or 32bit bus (similar) {7, 3, 2, 2, 2, 2, 2, 2, 2, 2, @@ -119,7 +121,7 @@ u8 MRAMRead16Bursts[][256] = 0}, }; -u8 MRAMRead32Bursts[][256] = +constexpr u8 MRAMRead32Bursts[][256] = { // main RAM to regular 16bit bus {9, 4, 3, 3, 3, 3, 3, 3, 3, 3, @@ -178,7 +180,7 @@ u8 MRAMRead32Bursts[][256] = 0}, }; -u8 MRAMWrite16Bursts[][256] = +constexpr u8 MRAMWrite16Bursts[][256] = { // regular 16bit or 32bit bus to main RAM (similar) {8, 2, 2, 2, 2, 2, 2, 2, 2, 2, @@ -209,7 +211,7 @@ u8 MRAMWrite16Bursts[][256] = 0}, }; -u8 MRAMWrite32Bursts[][256] = +constexpr u8 MRAMWrite32Bursts[][256] = { // regular 16bit bus to main RAM {9, 4, 4, 4, 4, 4, 4, 4, 4, 4, diff --git a/src/DSi.cpp b/src/DSi.cpp index cde86bae..02172612 100644 --- a/src/DSi.cpp +++ b/src/DSi.cpp @@ -162,7 +162,7 @@ void Reset() SCFG_Clock7 = 0x0187; SCFG_EXT[0] = 0x8307F100; SCFG_EXT[1] = 0x93FFFB06; - SCFG_MC = 0x0010;//0x0011; + SCFG_MC = 0x0010 | (~((u32)NDSCart::CartInserted)&1);//0x0011; SCFG_RST = 0; DSi_DSP::SetRstLine(false); @@ -251,6 +251,14 @@ void DoSavestate(Savestate* file) SDIO->DoSavestate(file); } +void SetCartInserted(bool inserted) +{ + if (inserted) + SCFG_MC &= ~1; + else + SCFG_MC |= 1; +} + void DecryptModcryptArea(u32 offset, u32 size, u8* iv) { AES_ctx ctx; @@ -506,30 +514,24 @@ void SetupDirectBoot() ARM9Write32(0x02FFE000+i, tmp); } - FILE* nand = Platform::OpenLocalFile(Platform::GetConfigString(Platform::DSi_NANDPath), "r+b"); - if (nand) + if (DSi_NAND::Init(&DSi::ARM7iBIOS[0x8308])) { - if (DSi_NAND::Init(nand, &DSi::ARM7iBIOS[0x8308])) - { - u8 userdata[0x1B0]; - DSi_NAND::ReadUserData(userdata); - for (u32 i = 0; i < 0x128; i+=4) - ARM9Write32(0x02000400+i, *(u32*)&userdata[0x88+i]); + u8 userdata[0x1B0]; + DSi_NAND::ReadUserData(userdata); + for (u32 i = 0; i < 0x128; i+=4) + ARM9Write32(0x02000400+i, *(u32*)&userdata[0x88+i]); - u8 hwinfoS[0xA4]; - u8 hwinfoN[0x9C]; - DSi_NAND::ReadHardwareInfo(hwinfoS, hwinfoN); + u8 hwinfoS[0xA4]; + u8 hwinfoN[0x9C]; + DSi_NAND::ReadHardwareInfo(hwinfoS, hwinfoN); - for (u32 i = 0; i < 0x14; i+=4) - ARM9Write32(0x02000600+i, *(u32*)&hwinfoN[0x88+i]); + for (u32 i = 0; i < 0x14; i+=4) + ARM9Write32(0x02000600+i, *(u32*)&hwinfoN[0x88+i]); - for (u32 i = 0; i < 0x18; i+=4) - ARM9Write32(0x02FFFD68+i, *(u32*)&hwinfoS[0x88+i]); + for (u32 i = 0; i < 0x18; i+=4) + ARM9Write32(0x02FFFD68+i, *(u32*)&hwinfoS[0x88+i]); - DSi_NAND::DeInit(); - } - - fclose(nand); + DSi_NAND::DeInit(); } u8 nwifiver = SPI_Firmware::GetNWifiVersion(); @@ -702,19 +704,14 @@ bool LoadNAND() { printf("Loading DSi NAND\n"); - FILE* nand = Platform::OpenLocalFile(Platform::GetConfigString(Platform::DSi_NANDPath), "r+b"); - if (!nand) - { - printf("Failed to open DSi NAND\n"); - return false; - } - - if (!DSi_NAND::Init(nand, &DSi::ARM7iBIOS[0x8308])) + if (!DSi_NAND::Init(&DSi::ARM7iBIOS[0x8308])) { printf("Failed to load DSi NAND\n"); return false; } + FILE* nand = DSi_NAND::GetFile(); + // Make sure NWRAM is accessible. // The Bits are set to the startup values in Reset() and we might // still have them on default (0) or some bits cleared by the previous @@ -2641,6 +2638,9 @@ u8 ARM7IORead8(u32 addr) case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return (ConsoleID >> 48) & 0xFF; case 0x04004D07: if (SCFG_BIOS & (1<<10)) return 0; return ConsoleID >> 56; case 0x04004D08: return 0; + + case 0x4004700: return DSi_DSP::SNDExCnt; + case 0x4004701: return DSi_DSP::SNDExCnt >> 8; } return NDS::ARM7IORead8(addr); @@ -2673,6 +2673,8 @@ u16 ARM7IORead16(u32 addr) case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return (ConsoleID >> 32) & 0xFFFF; case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return ConsoleID >> 48; case 0x04004D08: return 0; + + case 0x4004700: return DSi_DSP::SNDExCnt; } if (addr >= 0x04004800 && addr < 0x04004A00) @@ -2744,6 +2746,10 @@ u32 ARM7IORead32(u32 addr) case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return ConsoleID & 0xFFFFFFFF; case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return ConsoleID >> 32; case 0x04004D08: return 0; + + case 0x4004700: + printf("32-Bit SNDExCnt read? %08X\n", NDS::ARM7->R[15]); + return DSi_DSP::SNDExCnt; } if (addr >= 0x04004800 && addr < 0x04004A00) @@ -2791,6 +2797,46 @@ void ARM7IOWrite8(u32 addr, u8 val) case 0x04004500: DSi_I2C::WriteData(val); return; case 0x04004501: DSi_I2C::WriteCnt(val); return; + + case 0x4004700: + DSi_DSP::WriteSNDExCnt((u16)val | (DSi_DSP::SNDExCnt & 0xFF00)); + return; + case 0x4004701: + DSi_DSP::WriteSNDExCnt(((u16)val << 8) | (DSi_DSP::SNDExCnt & 0x00FF)); + return; + } + + if (addr >= 0x04004420 && addr < 0x04004430) + { + u32 shift = (addr&3)*8; + addr -= 0x04004420; + addr &= ~3; + DSi_AES::WriteIV(addr, (u32)val << shift, 0xFF << shift); + return; + } + if (addr >= 0x04004430 && addr < 0x04004440) + { + u32 shift = (addr&3)*8; + addr -= 0x04004430; + addr &= ~3; + DSi_AES::WriteMAC(addr, (u32)val << shift, 0xFF << shift); + return; + } + if (addr >= 0x04004440 && addr < 0x04004500) + { + u32 shift = (addr&3)*8; + addr -= 0x04004440; + addr &= ~3; + + int n = 0; + while (addr >= 0x30) { addr -= 0x30; n++; } + + switch (addr >> 4) + { + case 0: DSi_AES::WriteKeyNormal(n, addr&0xF, (u32)val << shift, 0xFF << shift); return; + case 1: DSi_AES::WriteKeyX(n, addr&0xF, (u32)val << shift, 0xFF << shift); return; + case 2: DSi_AES::WriteKeyY(n, addr&0xF, (u32)val << shift, 0xFF << shift); return; + } } return NDS::ARM7IOWrite8(addr, val); @@ -2822,12 +2868,51 @@ void ARM7IOWrite16(u32 addr, u16 val) case 0x04004062: if (!(SCFG_EXT[1] & (1 << 31))) /* no access to SCFG Registers if disabled*/ return; - u32 tmp = MBK[0][8]; - tmp &= ~(0xffff << ((addr % 4) * 8)); - tmp |= (val << ((addr % 4) * 8)); - MBK[0][8] = tmp & 0x00FFFF0F; - MBK[1][8] = MBK[0][8]; + { + u32 tmp = MBK[0][8]; + tmp &= ~(0xffff << ((addr % 4) * 8)); + tmp |= (val << ((addr % 4) * 8)); + MBK[0][8] = tmp & 0x00FFFF0F; + MBK[1][8] = MBK[0][8]; + } return; + + case 0x4004700: + DSi_DSP::WriteSNDExCnt(val); + return; + } + + if (addr >= 0x04004420 && addr < 0x04004430) + { + u32 shift = (addr&1)*16; + addr -= 0x04004420; + addr &= ~1; + DSi_AES::WriteIV(addr, (u32)val << shift, 0xFFFF << shift); + return; + } + if (addr >= 0x04004430 && addr < 0x04004440) + { + u32 shift = (addr&1)*16; + addr -= 0x04004430; + addr &= ~1; + DSi_AES::WriteMAC(addr, (u32)val << shift, 0xFFFF << shift); + return; + } + if (addr >= 0x04004440 && addr < 0x04004500) + { + u32 shift = (addr&1)*16; + addr -= 0x04004440; + addr &= ~1; + + int n = 0; + while (addr >= 0x30) { addr -= 0x30; n++; } + + switch (addr >> 4) + { + case 0: DSi_AES::WriteKeyNormal(n, addr&0xF, (u32)val << shift, 0xFFFF << shift); return; + case 1: DSi_AES::WriteKeyX(n, addr&0xF, (u32)val << shift, 0xFFFF << shift); return; + case 2: DSi_AES::WriteKeyY(n, addr&0xF, (u32)val << shift, 0xFFFF << shift); return; + } } if (addr >= 0x04004800 && addr < 0x04004A00) @@ -2927,6 +3012,11 @@ void ARM7IOWrite32(u32 addr, u32 val) case 0x04004400: DSi_AES::WriteCnt(val); return; case 0x04004404: DSi_AES::WriteBlkCnt(val); return; case 0x04004408: DSi_AES::WriteInputFIFO(val); return; + + case 0x4004700: + printf("32-Bit SNDExCnt write? %08X %08X\n", val, NDS::ARM7->R[15]); + DSi_DSP::WriteSNDExCnt(val); + return; } if (addr >= 0x04004420 && addr < 0x04004430) diff --git a/src/DSi.h b/src/DSi.h index 14c13672..4ccddc02 100644 --- a/src/DSi.h +++ b/src/DSi.h @@ -59,6 +59,8 @@ void Reset(); void DoSavestate(Savestate* file); +void SetCartInserted(bool inserted); + void SetupDirectBoot(); void SoftReset(); diff --git a/src/DSi_AES.h b/src/DSi_AES.h index 48fca88e..d8ef98a4 100644 --- a/src/DSi_AES.h +++ b/src/DSi_AES.h @@ -20,6 +20,7 @@ #define DSI_AES_H #include "types.h" +#include "Savestate.h" namespace DSi_AES { diff --git a/src/DSi_Camera.h b/src/DSi_Camera.h index 12cec38a..75e97f27 100644 --- a/src/DSi_Camera.h +++ b/src/DSi_Camera.h @@ -20,6 +20,7 @@ #define DSI_CAMERA_H #include "types.h" +#include "Savestate.h" namespace DSi_CamModule { diff --git a/src/DSi_DSP.cpp b/src/DSi_DSP.cpp index c889aff9..0525366e 100644 --- a/src/DSi_DSP.cpp +++ b/src/DSi_DSP.cpp @@ -27,6 +27,9 @@ namespace DSi_DSP { +// not sure whether to not rather put it somewhere else +u16 SNDExCnt; + Teakra::Teakra* TeakraCore; bool SCFG_RST; @@ -151,6 +154,8 @@ void Reset() TeakraCore->Reset(); NDS::CancelEvent(NDS::Event_DSi_DSP); + + SNDExCnt = 0; } bool IsRstReleased() @@ -548,6 +553,21 @@ void Write32(u32 addr, u32 val) Write16(addr, val & 0xFFFF); } +void WriteSNDExCnt(u16 val) +{ + // it can be written even in NDS mode + + // mic frequency can only be changed if it was disabled + // before the write + if (SNDExCnt & 0x8000) + { + val &= ~0x2000; + val |= SNDExCnt & 0x2000; + } + + SNDExCnt = val & 0xE00F; +} + void Run(u32 cycles) { if (!IsDSPCoreEnabled()) diff --git a/src/DSi_DSP.h b/src/DSi_DSP.h index ade88bac..5d3427f3 100644 --- a/src/DSi_DSP.h +++ b/src/DSi_DSP.h @@ -19,13 +19,17 @@ #ifndef DSI_DSP_H #define DSI_DSP_H +#include "types.h" +#include "Savestate.h" + // TODO: for actual sound output // * audio callbacks -// * SNDEXCNT namespace DSi_DSP { +extern u16 SNDExCnt; + extern u16 DSP_PDATA; extern u16 DSP_PADR; extern u16 DSP_PCFG; @@ -62,6 +66,8 @@ void Write16(u32 addr, u16 val); u32 Read32(u32 addr); void Write32(u32 addr, u32 val); +void WriteSNDExCnt(u16 val); + // NOTE: checks SCFG_CLK9 void Run(u32 cycles); diff --git a/src/DSi_I2C.h b/src/DSi_I2C.h index 7350fb5a..48c8e884 100644 --- a/src/DSi_I2C.h +++ b/src/DSi_I2C.h @@ -20,6 +20,7 @@ #define DSI_I2C_H #include "types.h" +#include "Savestate.h" namespace DSi_BPTWL { diff --git a/src/DSi_NAND.cpp b/src/DSi_NAND.cpp index e24ed313..912fee42 100644 --- a/src/DSi_NAND.cpp +++ b/src/DSi_NAND.cpp @@ -49,8 +49,48 @@ UINT FF_ReadNAND(BYTE* buf, LBA_t sector, UINT num); UINT FF_WriteNAND(BYTE* buf, LBA_t sector, UINT num); -bool Init(FILE* nandfile, u8* es_keyY) +bool Init(u8* es_keyY) { + CurFile = nullptr; + + std::string nandpath = Platform::GetConfigString(Platform::DSi_NANDPath); + std::string instnand = nandpath + Platform::InstanceFileSuffix(); + + FILE* nandfile = Platform::OpenLocalFile(instnand, "r+b"); + if ((!nandfile) && (Platform::InstanceID() > 0)) + { + FILE* orig = Platform::OpenLocalFile(nandpath, "rb"); + if (!orig) + { + printf("Failed to open DSi NAND\n"); + return false; + } + + fseek(orig, 0, SEEK_END); + long len = ftell(orig); + fseek(orig, 0, SEEK_SET); + + nandfile = Platform::OpenLocalFile(instnand, "w+b"); + if (nandfile) + { + u8* tmpbuf = new u8[0x10000]; + for (long i = 0; i < len; i+=0x10000) + { + long blklen = 0x10000; + if ((i+blklen) > len) blklen = len-i; + + fread(tmpbuf, blklen, 1, orig); + fwrite(tmpbuf, blklen, 1, nandfile); + } + delete[] tmpbuf; + } + + fclose(orig); + fclose(nandfile); + + nandfile = Platform::OpenLocalFile(instnand, "r+b"); + } + if (!nandfile) return false; @@ -138,10 +178,17 @@ void DeInit() f_unmount("0:"); ff_disk_close(); + if (CurFile) fclose(CurFile); CurFile = nullptr; } +FILE* GetFile() +{ + return CurFile; +} + + void GetIDs(u8* emmc_cid, u64& consoleid) { memcpy(emmc_cid, eMMC_CID, 16); diff --git a/src/DSi_NAND.h b/src/DSi_NAND.h index 6feb2d0f..a23e62f6 100644 --- a/src/DSi_NAND.h +++ b/src/DSi_NAND.h @@ -34,9 +34,11 @@ enum TitleData_BannerSav, }; -bool Init(FILE* nand, u8* es_keyY); +bool Init(u8* es_keyY); void DeInit(); +FILE* GetFile(); + void GetIDs(u8* emmc_cid, u64& consoleid); void ReadHardwareInfo(u8* dataS, u8* dataN); diff --git a/src/DSi_NDMA.h b/src/DSi_NDMA.h index 563c33f9..b1ea4c96 100644 --- a/src/DSi_NDMA.h +++ b/src/DSi_NDMA.h @@ -20,6 +20,7 @@ #define DSI_NDMA_H #include "types.h" +#include "Savestate.h" class DSi_NDMA { diff --git a/src/DSi_NWifi.h b/src/DSi_NWifi.h index 14c790a2..ffd56476 100644 --- a/src/DSi_NWifi.h +++ b/src/DSi_NWifi.h @@ -21,6 +21,7 @@ #include "DSi_SD.h" #include "FIFO.h" +#include "Savestate.h" class DSi_NWifi : public DSi_SDDevice { diff --git a/src/DSi_SD.cpp b/src/DSi_SD.cpp index 8879a26a..e603347a 100644 --- a/src/DSi_SD.cpp +++ b/src/DSi_SD.cpp @@ -136,7 +136,10 @@ void DSi_SDHost::Reset() else sd = nullptr; - mmc = new DSi_MMCStorage(this, true, Platform::GetConfigString(Platform::DSi_NANDPath)); + std::string nandpath = Platform::GetConfigString(Platform::DSi_NANDPath); + std::string instnand = nandpath + Platform::InstanceFileSuffix(); + + mmc = new DSi_MMCStorage(this, true, instnand); mmc->SetCID(DSi::eMMC_CID); Ports[0] = sd; @@ -477,16 +480,14 @@ u16 DSi_SDHost::Read(u32 addr) { if (Ports[0]) // basic check of whether the SD card is inserted { - ret |= 0x0030; + ret |= 0x0020; if (!Ports[0]->ReadOnly) ret |= 0x0080; } - else - ret |= 0x0008; } else { // SDIO wifi is always inserted, I guess - ret |= 0x00B0; + ret |= 0x00A0; } return ret; } diff --git a/src/DSi_SD.h b/src/DSi_SD.h index 1d966068..5f6dbcd3 100644 --- a/src/DSi_SD.h +++ b/src/DSi_SD.h @@ -19,10 +19,10 @@ #ifndef DSI_SD_H #define DSI_SD_H -#include +#include #include "FIFO.h" #include "FATStorage.h" - +#include "Savestate.h" class DSi_SDDevice; diff --git a/src/DSi_SPI_TSC.h b/src/DSi_SPI_TSC.h index 4d25c1ca..7a3acf43 100644 --- a/src/DSi_SPI_TSC.h +++ b/src/DSi_SPI_TSC.h @@ -19,6 +19,9 @@ #ifndef DSI_SPI_TSC #define DSI_SPI_TSC +#include "types.h" +#include "Savestate.h" + namespace DSi_SPI_TSC { diff --git a/src/FIFO.h b/src/FIFO.h index 68ab126e..80d9b8a0 100644 --- a/src/FIFO.h +++ b/src/FIFO.h @@ -20,6 +20,7 @@ #define FIFO_H #include "types.h" +#include "Savestate.h" template class FIFO diff --git a/src/GPU.cpp b/src/GPU.cpp index 47a69cca..f54d771c 100644 --- a/src/GPU.cpp +++ b/src/GPU.cpp @@ -21,6 +21,10 @@ #include "NDS.h" #include "GPU.h" +#ifdef JIT_ENABLED +#include "ARMJIT.h" +#endif + #include "GPU2D_Soft.h" namespace GPU @@ -653,6 +657,9 @@ void MapVRAM_CD(u32 bank, u8 cnt) VRAMMap_ARM7[ofs] |= bankmask; memset(VRAMDirty[bank].Data, 0xFF, sizeof(VRAMDirty[bank].Data)); VRAMSTAT |= (1 << (bank-2)); +#ifdef JIT_ENABLED + ARMJIT::CheckAndInvalidateWVRAM(ofs); +#endif break; case 3: // texture diff --git a/src/GPU_OpenGL.cpp b/src/GPU_OpenGL.cpp index 2fc97d57..79adf7a2 100644 --- a/src/GPU_OpenGL.cpp +++ b/src/GPU_OpenGL.cpp @@ -153,14 +153,14 @@ void GLCompositor::SetRenderSettings(RenderSettings& settings) glBindTexture(GL_TEXTURE_2D, CompScreenOutputTex[i]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ScreenW, ScreenH, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); // fill the padding - u8 zeroPixels[ScreenW*2*scale*4]; - memset(zeroPixels, 0, sizeof(zeroPixels)); + u8* zeroPixels = (u8*) calloc(1, ScreenW*2*scale*4); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192*scale, ScreenW, 2*scale, GL_RGBA, GL_UNSIGNED_BYTE, zeroPixels); GLenum fbassign[] = {GL_COLOR_ATTACHMENT0}; glBindFramebuffer(GL_FRAMEBUFFER, CompScreenOutputFB[i]); glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, CompScreenOutputTex[i], 0); glDrawBuffers(1, fbassign); + free(zeroPixels); } glBindFramebuffer(GL_FRAMEBUFFER, 0); diff --git a/src/NDS.cpp b/src/NDS.cpp index 029870a2..9c6a7f03 100644 --- a/src/NDS.cpp +++ b/src/NDS.cpp @@ -176,6 +176,7 @@ bool RunningGame; void DivDone(u32 param); void SqrtDone(u32 param); void RunTimer(u32 tid, s32 cycles); +void UpdateWifiTimings(); void SetWifiWaitCnt(u16 val); void SetGBASlotTimings(); @@ -892,9 +893,7 @@ bool DoSavestate(Savestate* file) InitTimings(); SetGBASlotTimings(); - u16 tmp = WifiWaitCnt; - WifiWaitCnt = 0xFFFF; - SetWifiWaitCnt(tmp); // force timing table update + UpdateWifiTimings(); } for (int i = 0; i < 8; i++) @@ -918,6 +917,9 @@ bool DoSavestate(Savestate* file) if (!file->Saving) { GPU::SetPowerCnt(PowerControl9); + + SPU::SetPowerCnt(PowerControl7 & 0x0001); + Wifi::SetPowerCnt(PowerControl7 & 0x0002); } #ifdef JIT_ENABLED @@ -1198,6 +1200,25 @@ void ScheduleEvent(u32 id, bool periodic, s32 delay, void (*func)(u32), u32 para Reschedule(evt->Timestamp); } +void ScheduleEvent(u32 id, u64 timestamp, void (*func)(u32), u32 param) +{ + if (SchedListMask & (1<Timestamp = timestamp; + evt->Func = func; + evt->Param = param; + + SchedListMask |= (1<Timestamp); +} + void CancelEvent(u32 id) { SchedListMask &= ~(1<>3) & 0x3], (val & 0x20) ? 4 : 10); + } + else + { + SetARM7RegionTimings(0x04800, 0x04808, Mem7_Wifi0, 32, 1, 1); + SetARM7RegionTimings(0x04808, 0x04810, Mem7_Wifi1, 32, 1, 1); + } +} + void SetWifiWaitCnt(u16 val) { if (WifiWaitCnt == val) return; WifiWaitCnt = val; - - const int ntimings[4] = {10, 8, 6, 18}; - SetARM7RegionTimings(0x04800, 0x04808, Mem7_Wifi0, 16, ntimings[val & 0x3], (val & 0x4) ? 4 : 6); - SetARM7RegionTimings(0x04808, 0x04810, Mem7_Wifi1, 16, ntimings[(val>>3) & 0x3], (val & 0x20) ? 4 : 10); + UpdateWifiTimings(); } void SetGBASlotTimings() @@ -1956,8 +1991,8 @@ void debug(u32 param) //for (int i = 0; i < 9; i++) // printf("VRAM %c: %02X\n", 'A'+i, GPU::VRAMCNT[i]); - /*FILE* - shit = fopen("debug/construct.bin", "wb"); + FILE* + shit = fopen("debug/inazuma.bin", "wb"); fwrite(ARM9->ITCM, 0x8000, 1, shit); for (u32 i = 0x02000000; i < 0x02400000; i+=4) { @@ -1969,9 +2004,14 @@ void debug(u32 param) u32 val = ARM7Read32(i); fwrite(&val, 4, 1, shit); } - fclose(shit);*/ + for (u32 i = 0x06000000; i < 0x06040000; i+=4) + { + u32 val = ARM7Read32(i); + fwrite(&val, 4, 1, shit); + } + fclose(shit); - FILE* + /*FILE* shit = fopen("debug/camera9.bin", "wb"); for (u32 i = 0x02000000; i < 0x04000000; i+=4) { @@ -1985,7 +2025,7 @@ void debug(u32 param) u32 val = DSi::ARM7Read32(i); fwrite(&val, 4, 1, shit); } - fclose(shit); + fclose(shit);*/ } @@ -2411,6 +2451,7 @@ u8 ARM7Read8(u32 addr) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return 0; if (addr & 0x1) return Wifi::Read(addr-1) >> 8; return Wifi::Read(addr) & 0xFF; } @@ -2475,6 +2516,7 @@ u16 ARM7Read16(u32 addr) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return 0; return Wifi::Read(addr); } break; @@ -2538,6 +2580,7 @@ u32 ARM7Read32(u32 addr) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return 0; return Wifi::Read(addr) | (Wifi::Read(addr+2) << 16); } break; @@ -2629,7 +2672,8 @@ void ARM7Write8(u32 addr, u8 val) return; } - if (ARM7->R[15] > 0x00002F30) // ARM7 BIOS bug + //if (ARM7->R[15] > 0x00002F30) // ARM7 BIOS bug + if (addr >= 0x01000000) printf("unknown arm7 write8 %08X %02X @ %08X\n", addr, val, ARM7->R[15]); } @@ -2677,6 +2721,7 @@ void ARM7Write16(u32 addr, u16 val) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return; Wifi::Write(addr, val); return; } @@ -2706,7 +2751,8 @@ void ARM7Write16(u32 addr, u16 val) return; } - printf("unknown arm7 write16 %08X %04X @ %08X\n", addr, val, ARM7->R[15]); + if (addr >= 0x01000000) + printf("unknown arm7 write16 %08X %04X @ %08X\n", addr, val, ARM7->R[15]); } void ARM7Write32(u32 addr, u32 val) @@ -2753,6 +2799,7 @@ void ARM7Write32(u32 addr, u32 val) case 0x04800000: if (addr < 0x04810000) { + if (!(PowerControl7 & (1<<1))) return; Wifi::Write(addr, val & 0xFFFF); Wifi::Write(addr+2, val >> 16); return; @@ -2786,7 +2833,8 @@ void ARM7Write32(u32 addr, u32 val) return; } - printf("unknown arm7 write32 %08X %08X @ %08X\n", addr, val, ARM7->R[15]); + if (addr >= 0x01000000) + printf("unknown arm7 write32 %08X %08X @ %08X\n", addr, val, ARM7->R[15]); } bool ARM7GetMemRegion(u32 addr, bool write, MemRegion* region) @@ -2946,7 +2994,8 @@ u8 ARM9IORead8(u32 addr) return (u8)(emuID[idx]); } - printf("unknown ARM9 IO read8 %08X %08X\n", addr, ARM9->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM9 IO read8 %08X %08X\n", addr, ARM9->R[15]); return 0; } @@ -3092,7 +3141,8 @@ u16 ARM9IORead16(u32 addr) return GPU3D::Read16(addr); } - printf("unknown ARM9 IO read16 %08X %08X\n", addr, ARM9->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM9 IO read16 %08X %08X\n", addr, ARM9->R[15]); return 0; } @@ -3235,7 +3285,8 @@ u32 ARM9IORead32(u32 addr) return GPU3D::Read32(addr); } - printf("unknown ARM9 IO read32 %08X %08X\n", addr, ARM9->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM9 IO read32 %08X %08X\n", addr, ARM9->R[15]); return 0; } @@ -3763,6 +3814,7 @@ u8 ARM7IORead8(u32 addr) case 0x04000241: return WRAMCnt; case 0x04000300: return PostFlag7; + case 0x04000304: return PowerControl7; } if (addr >= 0x04000400 && addr < 0x04000520) @@ -3770,7 +3822,8 @@ u8 ARM7IORead8(u32 addr) return SPU::Read8(addr); } - printf("unknown ARM7 IO read8 %08X %08X\n", addr, ARM7->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM7 IO read8 %08X %08X\n", addr, ARM7->R[15]); return 0; } @@ -3845,7 +3898,9 @@ u16 ARM7IORead16(u32 addr) case 0x040001C2: return SPI::ReadData(); case 0x04000204: return ExMemCnt[1]; - case 0x04000206: return WifiWaitCnt; + case 0x04000206: + if (!(PowerControl7 & (1<<1))) return 0; + return WifiWaitCnt; case 0x04000208: return IME[1]; case 0x04000210: return IE[1] & 0xFFFF; @@ -3861,7 +3916,8 @@ u16 ARM7IORead16(u32 addr) return SPU::Read16(addr); } - printf("unknown ARM7 IO read16 %08X %08X\n", addr, ARM7->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM7 IO read16 %08X %08X\n", addr, ARM7->R[15]); return 0; } @@ -3927,6 +3983,7 @@ u32 ARM7IORead32(u32 addr) case 0x04000210: return IE[1]; case 0x04000214: return IF[1]; + case 0x04000304: return PowerControl7; case 0x04000308: return ARM7BIOSProt; case 0x04100000: @@ -3960,7 +4017,8 @@ u32 ARM7IORead32(u32 addr) return SPU::Read32(addr); } - printf("unknown ARM7 IO read32 %08X %08X\n", addr, ARM7->R[15]); + if ((addr & 0xFFFFF000) != 0x04004000) + printf("unknown ARM7 IO read32 %08X %08X\n", addr, ARM7->R[15]); return 0; } @@ -4155,6 +4213,7 @@ void ARM7IOWrite16(u32 addr, u16 val) return; } case 0x04000206: + if (!(PowerControl7 & (1<<1))) return; SetWifiWaitCnt(val); return; @@ -4170,7 +4229,15 @@ void ARM7IOWrite16(u32 addr, u16 val) PostFlag7 = val & 0x01; return; - case 0x04000304: PowerControl7 = val; return; + case 0x04000304: + { + u16 change = PowerControl7 ^ val; + PowerControl7 = val & 0x0003; + SPU::SetPowerCnt(val & 0x0001); + Wifi::SetPowerCnt(val & 0x0002); + if (change & 0x0002) UpdateWifiTimings(); + } + return; case 0x04000308: if (ARM7BIOSProt == 0) @@ -4292,7 +4359,15 @@ void ARM7IOWrite32(u32 addr, u32 val) case 0x04000210: IE[1] = val; UpdateIRQ(1); return; case 0x04000214: IF[1] &= ~val; UpdateIRQ(1); return; - case 0x04000304: PowerControl7 = val & 0xFFFF; return; + case 0x04000304: + { + u16 change = PowerControl7 ^ val; + PowerControl7 = val & 0x0003; + SPU::SetPowerCnt(val & 0x0001); + Wifi::SetPowerCnt(val & 0x0002); + if (change & 0x0002) UpdateWifiTimings(); + } + return; case 0x04000308: if (ARM7BIOSProt == 0) diff --git a/src/NDS.h b/src/NDS.h index df3c41c8..824c2bc9 100644 --- a/src/NDS.h +++ b/src/NDS.h @@ -264,6 +264,7 @@ void CamInputFrame(int cam, u32* data, int width, int height, bool rgb); void MicInputFrame(s16* data, int samples); void ScheduleEvent(u32 id, bool periodic, s32 delay, void (*func)(u32), u32 param); +void ScheduleEvent(u32 id, u64 timestamp, void (*func)(u32), u32 param); void CancelEvent(u32 id); void debug(u32 p); diff --git a/src/NDSCart.cpp b/src/NDSCart.cpp index fe02cb17..cdc26ef1 100644 --- a/src/NDSCart.cpp +++ b/src/NDSCart.cpp @@ -1584,6 +1584,9 @@ bool LoadROM(const u8* romdata, u32 romlen) if (CartInserted) EjectCart(); + memset(&Header, 0, sizeof(Header)); + memset(&Banner, 0, sizeof(Banner)); + CartROMSize = 0x200; while (CartROMSize < romlen) CartROMSize <<= 1; @@ -1602,7 +1605,15 @@ bool LoadROM(const u8* romdata, u32 romlen) memcpy(CartROM, romdata, romlen); memcpy(&Header, CartROM, sizeof(Header)); - memcpy(&Banner, CartROM + Header.BannerOffset, sizeof(Banner)); + + u8 unitcode = Header.UnitCode; + bool dsi = (unitcode & 0x02) != 0; + + size_t bannersize = dsi ? 0x23C0 : 0xA40; + if (Header.BannerOffset >= 0x200 && Header.BannerOffset < (CartROMSize - bannersize)) + { + memcpy(&Banner, CartROM + Header.BannerOffset, bannersize); + } printf("Game code: %.4s\n", Header.GameCode); @@ -1611,9 +1622,6 @@ bool LoadROM(const u8* romdata, u32 romlen) (u32)Header.GameCode[1] << 8 | (u32)Header.GameCode[0]; - u8 unitcode = Header.UnitCode; - bool dsi = (unitcode & 0x02) != 0; - u32 arm9base = Header.ARM9ROMOffset; bool homebrew = (arm9base < 0x4000) || (gamecode == 0x23232323); @@ -1679,6 +1687,7 @@ bool LoadROM(const u8* romdata, u32 romlen) } CartInserted = true; + DSi::SetCartInserted(true); u32 irversion = 0; if ((gamecode & 0xFF) == 'I') @@ -1738,6 +1747,8 @@ void EjectCart() CartROMSize = 0; CartID = 0; + DSi::SetCartInserted(false); + // CHECKME: does an eject imply anything for the ROM/SPI transfer registers? } diff --git a/src/NDSCart.h b/src/NDSCart.h index bbeb2ff9..10286ac3 100644 --- a/src/NDSCart.h +++ b/src/NDSCart.h @@ -22,6 +22,7 @@ #include #include "types.h" +#include "Savestate.h" #include "NDS_Header.h" #include "FATStorage.h" diff --git a/src/Platform.h b/src/Platform.h index d0562712..f2997ef8 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -32,6 +32,10 @@ void DeInit(); void StopEmu(); +// instance ID, for local multiplayer +int InstanceID(); +std::string InstanceFileSuffix(); + // configuration values enum ConfigEntry @@ -77,7 +81,6 @@ enum ConfigEntry Firm_Color, Firm_Message, Firm_MAC, - Firm_RandomizeMAC, AudioBitrate, }; @@ -158,8 +161,16 @@ void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen // packet type: DS-style TX header (12 bytes) + original 802.11 frame bool MP_Init(); void MP_DeInit(); -int MP_SendPacket(u8* data, int len); -int MP_RecvPacket(u8* data, bool block); +void MP_Begin(); +void MP_End(); +int MP_SendPacket(u8* data, int len, u64 timestamp); +int MP_RecvPacket(u8* data, u64* timestamp); +int MP_SendCmd(u8* data, int len, u64 timestamp); +int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid); +int MP_SendAck(u8* data, int len, u64 timestamp); +int MP_RecvHostPacket(u8* data, u64* timestamp); +u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask); + // LAN comm interface // packet type: Ethernet (802.3) diff --git a/src/ROMList.h b/src/ROMList.h index 8ed666b7..ab231129 100644 --- a/src/ROMList.h +++ b/src/ROMList.h @@ -19,6 +19,8 @@ #ifndef ROMLIST_H #define ROMLIST_H +#include "types.h" + struct ROMListEntry { u32 GameCode; diff --git a/src/SPI.cpp b/src/SPI.cpp index 6ecb86c4..e990b3a7 100644 --- a/src/SPI.cpp +++ b/src/SPI.cpp @@ -215,7 +215,8 @@ void LoadDefaultFirmware() // wifi access points // TODO: WFC ID?? - FILE* f = Platform::OpenLocalFile("wfcsettings.bin", "rb"); + FILE* f = Platform::OpenLocalFile("wfcsettings.bin"+Platform::InstanceFileSuffix(), "rb"); + if (!f) f = Platform::OpenLocalFile("wfcsettings.bin", "rb"); if (f) { u32 apdata = userdata - 0xA00; @@ -259,7 +260,7 @@ void LoadDefaultFirmware() } } -void LoadFirmwareFromFile(FILE* f) +void LoadFirmwareFromFile(FILE* f, bool makecopy) { fseek(f, 0, SEEK_END); @@ -271,7 +272,9 @@ void LoadFirmwareFromFile(FILE* f) fread(Firmware, 1, FirmwareLength, f); // take a backup - std::string fwBackupPath = FirmwarePath + ".bak"; + std::string fwBackupPath; + if (!makecopy) fwBackupPath = FirmwarePath + ".bak"; + else fwBackupPath = FirmwarePath; FILE* bf = Platform::OpenLocalFile(fwBackupPath, "rb"); if (!bf) { @@ -333,15 +336,24 @@ void Reset() else FirmwarePath = Platform::GetConfigString(Platform::FirmwarePath); + bool makecopy = false; + std::string origpath = FirmwarePath; + FirmwarePath += Platform::InstanceFileSuffix(); + FILE* f = Platform::OpenLocalFile(FirmwarePath, "rb"); if (!f) + { + f = Platform::OpenLocalFile(origpath, "rb"); + makecopy = true; + } + if (!f) { printf("Firmware not found! Generating default firmware.\n"); FirmwarePath = ""; } else { - LoadFirmwareFromFile(f); + LoadFirmwareFromFile(f, makecopy); fclose(f); } } @@ -385,28 +397,28 @@ void Reset() *(u16*)&Firmware[userdata+0x72] = CRC16(&Firmware[userdata], 0x70, 0xFFFF); - if (firmoverride) + //if (firmoverride) { u8 mac[6]; - bool rep; + bool rep = false; - if (Platform::GetConfigBool(Platform::Firm_RandomizeMAC)) - { - mac[0] = 0x00; - mac[1] = 0x09; - mac[2] = 0xBF; - mac[3] = rand()&0xFF; - mac[4] = rand()&0xFF; - mac[5] = rand()&0xFF; - rep = true; - } - else - { + memcpy(mac, &Firmware[0x36], 6); + + if (firmoverride) rep = Platform::GetConfigArray(Platform::Firm_MAC, mac); + + int inst = Platform::InstanceID(); + if (inst > 0) + { + rep = true; + mac[3] += inst; + mac[4] += inst*0x44; + mac[5] += inst*0x10; } if (rep) { + mac[0] &= 0xFC; // ensure the MAC isn't a broadcast MAC memcpy(&Firmware[0x36], mac, 6); *(u16*)&Firmware[0x2A] = CRC16(&Firmware[0x2C], *(u16*)&Firmware[0x2C], 0x0000); @@ -593,7 +605,12 @@ void Write(u8 val, u32 hold) } else { - FILE* f = Platform::OpenLocalFile("wfcsettings.bin", "wb"); + char wfcfile[50] = {0}; + int inst = Platform::InstanceID(); + if (inst > 0) snprintf(wfcfile, 49, "wfcsettings.bin", Platform::InstanceID()); + else strncpy(wfcfile, "wfcsettings.bin", 49); + + FILE* f = Platform::OpenLocalFile(wfcfile, "wb"); if (f) { u32 cutoff = 0x7F400 & FirmwareMask; diff --git a/src/SPU.cpp b/src/SPU.cpp index cba05586..9f245a20 100644 --- a/src/SPU.cpp +++ b/src/SPU.cpp @@ -184,6 +184,12 @@ void DoSavestate(Savestate* file) } +void SetPowerCnt(u32 val) +{ + // TODO +} + + void SetInterpolation(int type) { InterpType = type; diff --git a/src/SPU.h b/src/SPU.h index 1e20c117..1f28c2f8 100644 --- a/src/SPU.h +++ b/src/SPU.h @@ -31,6 +31,8 @@ void Stop(); void DoSavestate(Savestate* file); +void SetPowerCnt(u32 val); + // 0=none 1=linear 2=cosine 3=cubic void SetInterpolation(int type); diff --git a/src/Wifi.cpp b/src/Wifi.cpp index 4e3bc17d..c2614e73 100644 --- a/src/Wifi.cpp +++ b/src/Wifi.cpp @@ -26,43 +26,59 @@ #include "ARM.h" #include "GPU.h" + namespace Wifi { //#define WIFI_LOG printf #define WIFI_LOG(...) {} +#define PRINT_MAC(pf, mac) printf("%s: %02X:%02X:%02X:%02X:%02X:%02X\n", pf, (mac)[0], (mac)[1], (mac)[2], (mac)[3], (mac)[4], (mac)[5]); + u8 RAM[0x2000]; u16 IO[0x1000>>1]; #define IOPORT(x) IO[(x)>>1] +#define IOPORT8(x) ((u8*)IO)[x] + +// destination MACs for MP frames +const u8 MPCmdMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x00}; +const u8 MPReplyMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x10}; +const u8 MPAckMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x03}; + +const int kTimerInterval = 8; +const u32 kTimeCheckMask = ~(kTimerInterval - 1); + +bool Enabled; +bool PowerOn; + +s32 TimerError; u16 Random; +// general, always-on microsecond counter +u64 USTimestamp; + u64 USCounter; u64 USCompare; bool BlockBeaconIRQ14; u32 CmdCounter; -u16 BBCnt; -u8 BBWrite; u8 BBRegs[0x100]; u8 BBRegsRO[0x100]; u8 RFVersion; -u16 RFCnt; -u16 RFData1; -u16 RFData2; u32 RFRegs[0x40]; struct TXSlot { + bool Valid; u16 Addr; u16 Length; u8 Rate; u8 CurPhase; - u32 CurPhaseTime; + int CurPhaseTime; u32 HalfwordTimeMask; }; @@ -70,16 +86,17 @@ TXSlot TXSlots[6]; u8 RXBuffer[2048]; u32 RXBufferPtr; -u32 RXTime; +int RXTime; u32 RXHalfwordTimeMask; -u16 RXEndAddr; u32 ComStatus; // 0=waiting for packets 1=receiving 2=sending u32 TXCurSlot; u32 RXCounter; int MPReplyTimer; -int MPNumReplies; +u16 MPClientMask, MPClientFail; + +u8 MPClientReplies[15*1024]; bool MPInited; bool LANInited; @@ -87,6 +104,11 @@ bool LANInited; int USUntilPowerOn; bool ForcePowerOn; +// MULTIPLAYER SYNC APPARATUS +bool IsMPClient; +u64 NextSync; // for clients: timestamp for next sync point +u64 RXTimestamp; + // multiplayer host TX sequence: // 1. preamble // 2. IRQ7 @@ -112,21 +134,26 @@ bool ForcePowerOn; // 4 = switching from TX to RX // 5 = MP host data sent, waiting for replies (RFPINS=0x0084) // 6 = RX -// 7 = ?? +// 7 = switching from RX reply to TX ack // 8 = MP client sending reply, MP host sending ack (RFPINS=0x0046) // 9 = idle // wifi TODO: -// * power saving -// * RXSTAT, multiplay reply errors +// * RXSTAT // * TX errors (if applicable) bool Init() { - MPInited = false; - LANInited = false; + //MPInited = false; + //LANInited = false; + + Platform::MP_Init(); + MPInited = true; + + Platform::LAN_Init(); + LANInited = true; WifiAP::Init(); @@ -148,6 +175,9 @@ void Reset() memset(RAM, 0, 0x2000); memset(IO, 0, 0x1000); + Enabled = false; + PowerOn = false; + Random = 1; memset(BBRegs, 0, 0x100); @@ -200,19 +230,39 @@ void Reset() memset(&IOPORT(0x018), 0xFF, 6); memset(&IOPORT(0x020), 0xFF, 6); + // TODO: find out what the initial values are + IOPORT(W_PowerUS) = 0x0001; + + USTimestamp = 0; + USCounter = 0; USCompare = 0; BlockBeaconIRQ14 = false; + memset(TXSlots, 0, sizeof(TXSlots)); ComStatus = 0; TXCurSlot = -1; RXCounter = 0; + memset(RXBuffer, 0, sizeof(RXBuffer)); + RXBufferPtr = 0; + RXTime = 0; + RXHalfwordTimeMask = 0xFFFFFFFF; + MPReplyTimer = 0; - MPNumReplies = 0; + MPClientMask = 0; + MPClientFail = 0; + memset(MPClientReplies, 0, sizeof(MPClientReplies)); CmdCounter = 0; + USUntilPowerOn = 0; + ForcePowerOn = false; + + IsMPClient = false; + NextSync = 0; + RXTimestamp = 0; + WifiAP::Reset(); } @@ -228,8 +278,13 @@ void DoSavestate(Savestate* file) file->VarArray(RAM, 0x2000); file->VarArray(IO, 0x1000); + file->Bool32(&Enabled); + file->Bool32(&PowerOn); + file->Var16(&Random); + file->Var32((u32*)&TimerError); + file->VarArray(BBRegs, 0x100); file->VarArray(BBRegsRO, 0x100); @@ -240,17 +295,107 @@ void DoSavestate(Savestate* file) file->Var64(&USCompare); file->Bool32(&BlockBeaconIRQ14); + file->Var32(&CmdCounter); + + file->Var64(&USTimestamp); + + for (int i = 0; i < 6; i++) + { + TXSlot* slot = &TXSlots[i]; + + file->Bool32(&slot->Valid); + file->Var16(&slot->Addr); + file->Var16(&slot->Length); + file->Var8(&slot->Rate); + file->Var8(&slot->CurPhase); + file->Var32((u32*)&slot->CurPhaseTime); + file->Var32(&slot->HalfwordTimeMask); + } + + file->VarArray(RXBuffer, sizeof(RXBuffer)); + file->Var32(&RXBufferPtr); + file->Var32((u32*)&RXTime); + file->Var32(&RXHalfwordTimeMask); + file->Var32(&ComStatus); file->Var32(&TXCurSlot); file->Var32(&RXCounter); file->Var32((u32*)&MPReplyTimer); - file->Var32((u32*)&MPNumReplies); + file->Var16(&MPClientMask); + file->Var16(&MPClientFail); - file->Var32(&CmdCounter); + file->VarArray(MPClientReplies, sizeof(MPClientReplies)); + + file->Var32((u32*)&USUntilPowerOn); + file->Bool32(&ForcePowerOn); + + file->Bool32(&IsMPClient); + file->Var64(&NextSync); + file->Var64(&RXTimestamp); } +void ScheduleTimer(bool first) +{ + if (first) TimerError = 0; + + s32 cycles = 33513982 * kTimerInterval; + cycles -= TimerError; + s32 delay = (cycles + 999999) / 1000000; + TimerError = (delay * 1000000) - cycles; + + NDS::ScheduleEvent(NDS::Event_Wifi, !first, delay, USTimer, 0); +} + +void UpdatePowerOn() +{ + bool on = Enabled; + + if (NDS::ConsoleType == 1) + { + // TODO for DSi: + // * W_POWER_US doesn't work (atleast on DWM-W024) + // * other registers like GPIO_WIFI may also control wifi power/clock + // * turning wifi off via POWCNT2 while sending breaks further attempts at sending frames + } + else + { + on = on && ((IOPORT(W_PowerUS) & 0x1) == 0); + } + + if (on == PowerOn) + return; + + PowerOn = on; + if (on) + { + printf("WIFI: ON\n"); + + ScheduleTimer(true); + + Platform::MP_Begin(); + } + else + { + printf("WIFI: OFF\n"); + + NDS::CancelEvent(NDS::Event_Wifi); + + Platform::MP_End(); + } +} + +void SetPowerCnt(u32 val) +{ + Enabled = val & (1<<1); + UpdatePowerOn(); +} + + +void PowerDown(); +void StartTX_Beacon(); + void SetIRQ(u32 irq) { u32 oldflags = IOPORT(W_IF) & IOPORT(W_IE); @@ -269,7 +414,8 @@ void SetIRQ13() if (!(IOPORT(W_PowerTX) & 0x0002)) { IOPORT(0x034) = 0x0002; - // TODO: 03C + //PowerDown(); + // FIXME!! IOPORT(W_RFPins) = 0x0046; IOPORT(W_RFStatus) = 9; } @@ -318,21 +464,33 @@ void SetIRQ15() void SetStatus(u32 status) { - // TODO, eventually: states 2/4, also find out what state 7 is + // TODO, eventually: states 2/4/7 u16 rfpins[10] = {0x04, 0x84, 0, 0x46, 0, 0x84, 0x87, 0, 0x46, 0x04}; IOPORT(W_RFStatus) = status; IOPORT(W_RFPins) = rfpins[status]; } -bool MACEqual(u8* a, u8* b) +void PowerDown() +{ + IOPORT(W_TXReqRead) &= ~0x000F; + IOPORT(W_PowerState) |= 0x0200; + + // if the RF hardware is powered down while still sending or receiving, + // the current frame is completed before going idle + if (!ComStatus) + { + SetStatus(9); + } +} + + +bool MACEqual(const u8* a, const u8* b) { return (*(u32*)&a[0] == *(u32*)&b[0]) && (*(u16*)&a[4] == *(u16*)&b[4]); } -// TODO: set RFSTATUS/RFPINS - int PreambleLen(int rate) { if (rate == 1) return 192; @@ -340,6 +498,16 @@ int PreambleLen(int rate) return 192; } +u32 NumClients(u16 bitmask) +{ + u32 ret = 0; + for (int i = 1; i < 16; i++) + { + if (bitmask & (1<Addr + 0x4]; @@ -347,6 +515,19 @@ void IncrementTXCount(TXSlot* slot) *(u16*)&RAM[slot->Addr + 0x4] = cnt; } +void ReportMPReplyErrors(u16 clientfail) +{ + // TODO: do these trigger any IRQ? + + for (int i = 1; i < 16; i++) + { + if (!(clientfail & (1<Addr = (IOPORT(W_TXSlotCmd) & 0x0FFF) << 1; slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; @@ -401,9 +583,11 @@ void StartTX_Beacon() IOPORT(W_TXBusy) |= 0x0010; } -// TODO eventually: there is a small delay to firing TX void FireTX() { + if (!(IOPORT(W_RXCnt) & 0x8000)) + return; + u16 txbusy = IOPORT(W_TXBusy); u16 txreq = IOPORT(W_TXReqRead); @@ -443,39 +627,6 @@ void FireTX() } } -void SendMPReply(u16 clienttime, u16 clientmask) -{ - TXSlot* slot = &TXSlots[5]; - - // mark the last packet as success. dunno what the MSB is, it changes. - if (IOPORT(W_TXSlotReply2) & 0x8000) - *(u16*)&RAM[slot->Addr] = 0x0001; - - IOPORT(W_TXSlotReply2) = IOPORT(W_TXSlotReply1); - IOPORT(W_TXSlotReply1) = 0; - - // this seems to be set upon IRQ0 - // TODO: how does it behave if the packet addr is changed before it gets sent? (maybe just not possible) - if (IOPORT(W_TXSlotReply2) & 0x8000) - { - slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; - //*(u16*)&RAM[slot->Addr + 0x4] = 0x0001; - IncrementTXCount(slot); - } - - u16 clientnum = 0; - for (int i = 1; i < IOPORT(W_AIDLow); i++) - { - if (clientmask & (1<CurPhase = 0; - slot->CurPhaseTime = 16 + ((clienttime + 10) * clientnum); - - IOPORT(W_TXBusy) |= 0x0080; -} - void SendMPDefaultReply() { u8 reply[12 + 32]; @@ -488,25 +639,92 @@ void SendMPDefaultReply() // TODO reply[0x8] = 0x14; - *(u16*)&reply[0xC + 0x00] = 0x0158; - *(u16*)&reply[0xC + 0x02] = 0x00F0;//0; // TODO?? - *(u16*)&reply[0xC + 0x04] = IOPORT(W_BSSID0); - *(u16*)&reply[0xC + 0x06] = IOPORT(W_BSSID1); - *(u16*)&reply[0xC + 0x08] = IOPORT(W_BSSID2); - *(u16*)&reply[0xC + 0x0A] = IOPORT(W_MACAddr0); - *(u16*)&reply[0xC + 0x0C] = IOPORT(W_MACAddr1); - *(u16*)&reply[0xC + 0x0E] = IOPORT(W_MACAddr2); - *(u16*)&reply[0xC + 0x10] = 0x0903; - *(u16*)&reply[0xC + 0x12] = 0x00BF; - *(u16*)&reply[0xC + 0x14] = 0x1000; - *(u16*)&reply[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; - *(u32*)&reply[0xC + 0x18] = 0; + *(u16*)&reply[0xC + 0x00] = 0x0158; + *(u16*)&reply[0xC + 0x02] = 0x00F0;//0; // TODO?? + *(u16*)&reply[0xC + 0x04] = IOPORT(W_BSSID0); + *(u16*)&reply[0xC + 0x06] = IOPORT(W_BSSID1); + *(u16*)&reply[0xC + 0x08] = IOPORT(W_BSSID2); + *(u16*)&reply[0xC + 0x0A] = IOPORT(W_MACAddr0); + *(u16*)&reply[0xC + 0x0C] = IOPORT(W_MACAddr1); + *(u16*)&reply[0xC + 0x0E] = IOPORT(W_MACAddr2); + *(u16*)&reply[0xC + 0x10] = 0x0903; + *(u16*)&reply[0xC + 0x12] = 0x00BF; + *(u16*)&reply[0xC + 0x14] = 0x1000; + *(u16*)&reply[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; + *(u32*)&reply[0xC + 0x18] = 0; - int txlen = Platform::MP_SendPacket(reply, 12+28); - WIFI_LOG("wifi: sent %d/40 bytes of MP default reply\n", txlen); + int txlen = Platform::MP_SendReply(reply, 12+28, USTimestamp, IOPORT(W_AIDLow)); + WIFI_LOG("wifi: sent %d/40 bytes of MP default reply\n", txlen); } -void SendMPAck() +void SendMPReply(u16 clienttime, u16 clientmask) +{ + TXSlot* slot = &TXSlots[5]; + + // mark the last packet as success. dunno what the MSB is, it changes. + //if (slot->Valid) + if (IOPORT(W_TXSlotReply2) & 0x8000) + *(u16*)&RAM[slot->Addr] = 0x0001; + + // CHECKME!! + // can the transfer rate for MP replies be set, or is it determined from the CMD transfer rate? + // how does it work for default empty replies? + slot->Rate = 2; + + IOPORT(W_TXSlotReply2) = IOPORT(W_TXSlotReply1); + IOPORT(W_TXSlotReply1) = 0; + + if (!(IOPORT(W_TXSlotReply2) & 0x8000)) + { + slot->Valid = false; + } + else + { + slot->Valid = true; + + slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; + slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; + + // the packet is entirely ignored if it lasts longer than the maximum reply time + u32 duration = PreambleLen(slot->Rate) + (slot->Length * (slot->Rate==2 ? 4:8)); + if (duration > clienttime) + slot->Valid = false; + } + + //if (RAM[slot->Addr+4] > 0) + // printf("REPLY RETRY COUNTER %d (%04X)\n", RAM[slot->Addr+4], IOPORT(W_TXSlotReply2)); + + // this seems to be set upon IRQ0 + // TODO: how does it behave if the packet addr is changed before it gets sent? (maybe just not possible) + if (slot->Valid) + { + //*(u16*)&RAM[slot->Addr + 0x4] = 0x0001; + IncrementTXCount(slot); + + slot->CurPhase = 0; + int txlen = Platform::MP_SendReply(&RAM[slot->Addr], 12 + slot->Length, USTimestamp, IOPORT(W_AIDLow)); + WIFI_LOG("wifi: sent %d/%d bytes of MP reply\n", txlen, 12 + slot->Length); + } + else + { + slot->CurPhase = 10; + + SendMPDefaultReply(); + } + + u16 clientnum = 0; + for (int i = 1; i < IOPORT(W_AIDLow); i++) + { + if (clientmask & (1<CurPhaseTime = 16 + ((clienttime + 10) * clientnum) + PreambleLen(slot->Rate); + + IOPORT(W_TXBusy) |= 0x0080; +} + +void SendMPAck(u16 clientfail) { u8 ack[12 + 32]; @@ -516,41 +734,32 @@ void SendMPAck() if (TXSlots[1].Rate == 2) ack[0x8] = 0x14; else ack[0x8] = 0xA; - *(u16*)&ack[0xC + 0x00] = 0x0218; - *(u16*)&ack[0xC + 0x02] = 0; - *(u16*)&ack[0xC + 0x04] = 0x0903; - *(u16*)&ack[0xC + 0x06] = 0x00BF; - *(u16*)&ack[0xC + 0x08] = 0x0300; - *(u16*)&ack[0xC + 0x0A] = IOPORT(W_BSSID0); - *(u16*)&ack[0xC + 0x0C] = IOPORT(W_BSSID1); - *(u16*)&ack[0xC + 0x0E] = IOPORT(W_BSSID2); - *(u16*)&ack[0xC + 0x10] = IOPORT(W_MACAddr0); - *(u16*)&ack[0xC + 0x12] = IOPORT(W_MACAddr1); - *(u16*)&ack[0xC + 0x14] = IOPORT(W_MACAddr2); - *(u16*)&ack[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; - *(u16*)&ack[0xC + 0x18] = 0x0033; // ??? - *(u16*)&ack[0xC + 0x1A] = 0; - *(u32*)&ack[0xC + 0x1C] = 0; + *(u16*)&ack[0xC + 0x00] = 0x0218; + *(u16*)&ack[0xC + 0x02] = 0; + *(u16*)&ack[0xC + 0x04] = 0x0903; + *(u16*)&ack[0xC + 0x06] = 0x00BF; + *(u16*)&ack[0xC + 0x08] = 0x0300; + *(u16*)&ack[0xC + 0x0A] = IOPORT(W_BSSID0); + *(u16*)&ack[0xC + 0x0C] = IOPORT(W_BSSID1); + *(u16*)&ack[0xC + 0x0E] = IOPORT(W_BSSID2); + *(u16*)&ack[0xC + 0x10] = IOPORT(W_MACAddr0); + *(u16*)&ack[0xC + 0x12] = IOPORT(W_MACAddr1); + *(u16*)&ack[0xC + 0x14] = IOPORT(W_MACAddr2); + *(u16*)&ack[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; + *(u16*)&ack[0xC + 0x18] = 0x0033; // ??? + *(u16*)&ack[0xC + 0x1A] = clientfail; + *(u32*)&ack[0xC + 0x1C] = 0; - int txlen = Platform::MP_SendPacket(ack, 12+32); - WIFI_LOG("wifi: sent %d/44 bytes of MP ack, %d %d\n", txlen, ComStatus, RXTime); + int txlen = Platform::MP_SendAck(ack, 12+32, USTimestamp); + WIFI_LOG("wifi: sent %d/44 bytes of MP ack, %d %d\n", txlen, ComStatus, RXTime); } -u32 NumClients(u16 bitmask) -{ - u32 ret = 0; - for (int i = 1; i < 16; i++) - { - if (bitmask & (1<CurPhaseTime--; + slot->CurPhaseTime -= kTimerInterval; if (slot->CurPhaseTime > 0) { if (slot->CurPhase == 1) @@ -560,19 +769,28 @@ bool ProcessTX(TXSlot* slot, int num) } else if (slot->CurPhase == 2) { - MPReplyTimer--; - if (MPReplyTimer == 0 && MPNumReplies > 0) + MPReplyTimer -= kTimerInterval; + if (MPReplyTimer <= 0 && MPClientMask != 0) { - if (CheckRX(true)) + int nclient = 1; + while (!(MPClientMask & (1 << nclient))) nclient++; + + u32 curclient = 1 << nclient; + + /*if (CheckRX(1)) { - ComStatus |= 0x1; + // we received a reply, mark it as such + // TODO: is any received packet considered a good reply? + // hardware probably requires a specific frame-control and/or destination MAC + + MPClientFail &= ~curclient; } + else printf("REPLY %04X NOT RECEIVED\n");*/ + if (!(MPClientFail & curclient)) + MPClientReplyRX(nclient); - // TODO: properly handle reply errors - // also, if the reply is too big to fit within its window, what happens? - - MPReplyTimer = 10 + IOPORT(W_CmdReplyTime); - MPNumReplies--; + MPReplyTimer += 10 + IOPORT(W_CmdReplyTime); + MPClientMask &= ~curclient; } } @@ -592,31 +810,16 @@ bool ProcessTX(TXSlot* slot, int num) SetStatus(8); - // if no reply is configured, send a default empty reply - if (!(IOPORT(W_TXSlotReply2) & 0x8000)) - { - SendMPDefaultReply(); + //slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; + //slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; - slot->Addr = 0; - slot->Length = 28; - slot->Rate = 2; // TODO - slot->CurPhase = 4; - slot->CurPhaseTime = 28*4; - slot->HalfwordTimeMask = 0xFFFFFFFF; - IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; - break; - } - - slot->Addr = (IOPORT(W_TXSlotReply2) & 0x0FFF) << 1; - slot->Length = *(u16*)&RAM[slot->Addr + 0xA] & 0x3FFF; + /*u8 rate = RAM[slot->Addr + 0x8]; + if (rate == 0x14) slot->Rate = 2; + else slot->Rate = 1;*/ // TODO: duration should be set by hardware // doesn't seem to be important //RAM[slot->Addr + 0xC + 2] = 0x00F0; - - u8 rate = RAM[slot->Addr + 0x8]; - if (rate == 0x14) slot->Rate = 2; - else slot->Rate = 1; } else SetStatus(3); @@ -625,17 +828,32 @@ bool ProcessTX(TXSlot* slot, int num) if (slot->Rate == 2) { len *= 4; - slot->HalfwordTimeMask = 0x7; + slot->HalfwordTimeMask = 0x7 & kTimeCheckMask; } else { len *= 8; - slot->HalfwordTimeMask = 0xF; + slot->HalfwordTimeMask = 0xF & kTimeCheckMask; } slot->CurPhase = 1; slot->CurPhaseTime = len; + u16 framectl = *(u16*)&RAM[slot->Addr + 0xC]; + if (framectl & (1<<14)) + { + // WEP frame + // TODO: what happens when sending a WEP frame while WEP processing is off? + // TODO: some form of actual WEP processing? + // for now we just set the WEP FCS to a nonzero value, because some games require it + + if (IOPORT(W_WEPCnt) & (1<<15)) + { + u32 wep_fcs = (slot->Addr + 0xC + slot->Length - 7) & ~0x1; + *(u32*)&RAM[wep_fcs] = 0x22334466; + } + } + u64 oldts; if (num == 4) { @@ -644,28 +862,66 @@ bool ProcessTX(TXSlot* slot, int num) *(u64*)&RAM[slot->Addr + 0xC + 24] = USCounter; } - //u32 noseqno = 0; - //if (num == 1) noseqno = (IOPORT(W_TXSlotCmd) & 0x4000); + u32 noseqno = 0; + if (num == 1) noseqno = (IOPORT(W_TXSlotCmd) & 0x4000); - //if (!noseqno) + if (!noseqno) { *(u16*)&RAM[slot->Addr + 0xC + 22] = IOPORT(W_TXSeqNo) << 4; IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; } + if ((num != 5) && (RAM[slot->Addr+4] > 0)) + printf("SLOT %d RETRY COUNTER %d\n", RAM[slot->Addr+4]); + // set TX addr IOPORT(W_RXTXAddr) = slot->Addr >> 1; - // send - int txlen = Platform::MP_SendPacket(&RAM[slot->Addr], 12 + slot->Length); - WIFI_LOG("wifi: sent %d/%d bytes of slot%d packet, addr=%04X, framectl=%04X, %04X %04X\n", - txlen, slot->Length+12, num, slot->Addr, *(u16*)&RAM[slot->Addr + 0xC], - *(u16*)&RAM[slot->Addr + 0x24], *(u16*)&RAM[slot->Addr + 0x26]); + if (num == 1) + { + // send + int txlen = Platform::MP_SendCmd(&RAM[slot->Addr], 12 + slot->Length, USTimestamp); + WIFI_LOG("wifi: sent %d/%d bytes of slot%d packet, addr=%04X, framectl=%04X, %04X %04X\n", + txlen, slot->Length+12, num, slot->Addr, *(u16*)&RAM[slot->Addr + 0xC], + *(u16*)&RAM[slot->Addr + 0x24], *(u16*)&RAM[slot->Addr + 0x26]); + } + else if (num == 5) + { + // send + /*int txlen = Platform::MP_SendReply(&RAM[slot->Addr], 12 + slot->Length, USTimestamp, IOPORT(W_AIDLow)); + WIFI_LOG("wifi: sent %d/%d bytes of slot%d packet, addr=%04X, framectl=%04X, %04X %04X\n", + txlen, slot->Length+12, num, slot->Addr, *(u16*)&RAM[slot->Addr + 0xC], + *(u16*)&RAM[slot->Addr + 0x24], *(u16*)&RAM[slot->Addr + 0x26]);*/ + } + else //if (num != 5) + { + // send + int txlen = Platform::MP_SendPacket(&RAM[slot->Addr], 12 + slot->Length, USTimestamp); + WIFI_LOG("wifi: sent %d/%d bytes of slot%d packet, addr=%04X, framectl=%04X, %04X %04X\n", + txlen, slot->Length+12, num, slot->Addr, *(u16*)&RAM[slot->Addr + 0xC], + *(u16*)&RAM[slot->Addr + 0x24], *(u16*)&RAM[slot->Addr + 0x26]); + } // if the packet is being sent via LOC1..3, send it to the AP // any packet sent via CMD/REPLY/BEACON isn't going to have much use outside of local MP if (num == 0 || num == 2 || num == 3) + { + if ((framectl & 0x00FF) == 0x0010) + { + u16 aid = *(u16*)&RAM[slot->Addr + 0xC + 24 + 4]; + if (aid) printf("[HOST] syncing client %04X, sync=%016llX\n", aid, USTimestamp); + } + else if ((framectl & 0x00FF) == 0x00C0) + { + if (IsMPClient) + { + printf("[CLIENT] deauth\n"); + IsMPClient = false; + } + } + WifiAP::SendPacket(&RAM[slot->Addr], 12 + slot->Length); + } if (num == 4) { @@ -674,10 +930,25 @@ bool ProcessTX(TXSlot* slot, int num) } break; + case 10: // preamble done (default empty MP reply) + { + SetIRQ(7); + SetStatus(8); + + //SendMPDefaultReply(); + + //slot->Addr = 0; + //slot->Length = 28; + slot->CurPhase = 4; + slot->CurPhaseTime = 28*4; + slot->HalfwordTimeMask = 0xFFFFFFFF; + } + break; + case 1: // transmit done { - // for the MP reply slot, this is set later - if (num != 5) + // for the MP CMD and reply slots, this is set later + if (num != 1 && num != 5) *(u16*)&RAM[slot->Addr] = 0x0001; RAM[slot->Addr + 5] = 0; @@ -690,12 +961,21 @@ bool ProcessTX(TXSlot* slot, int num) } SetStatus(5); - u16 clientmask = *(u16*)&RAM[slot->Addr + 12 + 24 + 2]; - MPNumReplies = NumClients(clientmask); - MPReplyTimer = 16; + u16 clientmask = *(u16*)&RAM[slot->Addr + 12 + 24 + 2] & 0xFFFE; + //MPNumReplies = NumClients(clientmask); + MPReplyTimer = 16 + PreambleLen(slot->Rate); + MPClientMask = clientmask; + MPClientFail = clientmask; + u16 res = 0; + if (clientmask) + res = Platform::MP_RecvReplies(MPClientReplies, USTimestamp, clientmask); + MPClientFail &= ~res; + + // TODO: 112 likely includes the ack preamble, which needs adjusted + // for long-preamble settings slot->CurPhase = 2; - slot->CurPhaseTime = 112 + ((10 + IOPORT(W_CmdReplyTime)) * MPNumReplies); + slot->CurPhaseTime = 112 + ((10 + IOPORT(W_CmdReplyTime)) * NumClients(clientmask)); break; } @@ -750,7 +1030,10 @@ bool ProcessTX(TXSlot* slot, int num) if (slot->Rate == 2) slot->CurPhaseTime = 32 * 4; else slot->CurPhaseTime = 32 * 8; - SendMPAck(); + ReportMPReplyErrors(MPClientFail); + + // send + SendMPAck(MPClientFail); slot->CurPhase = 3; } @@ -762,11 +1045,15 @@ bool ProcessTX(TXSlot* slot, int num) IOPORT(W_TXBusy) &= ~(1<<1); IOPORT(W_TXSlotCmd) &= 0x7FFF; // confirmed - // seems this is set to indicate which clients failed to reply - *(u16*)&RAM[slot->Addr + 0x2] = 0; + if (!MPClientFail) + *(u16*)&RAM[slot->Addr] = 0x0001; + else + *(u16*)&RAM[slot->Addr] = 0x0005; + + // this is set to indicate which clients failed to reply + *(u16*)&RAM[slot->Addr + 0x2] = MPClientFail; IncrementTXCount(slot); - SetIRQ(12); IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; if (IOPORT(W_TXStatCnt) & 0x2000) @@ -776,12 +1063,19 @@ bool ProcessTX(TXSlot* slot, int num) } SetStatus(1); + // TODO: retry the whole cycle if some clients failed to respond + // AND if there is enough time left in CMDCOUNT + // (games seem to always configure CMDCOUNT such that there is no time for retries) + SetIRQ(12); + FireTX(); } return true; case 4: // MP default reply transfer finished { + IOPORT(W_TXSeqNo) = (IOPORT(W_TXSeqNo) + 1) & 0x0FFF; + IOPORT(W_TXBusy) &= ~0x80; SetStatus(1); FireTX(); @@ -804,126 +1098,21 @@ inline void IncrementRXAddr(u16& addr, u16 inc = 2) } } -bool CheckRX(bool block) +void StartRX() { - if (!(IOPORT(W_RXCnt) & 0x8000)) - return false; - - if (IOPORT(W_RXBufBegin) == IOPORT(W_RXBufEnd)) - return false; - - u16 framelen; - u16 framectl; - u8 txrate; - bool bssidmatch; - u16 rxflags; - - for (;;) - { - int rxlen = Platform::MP_RecvPacket(RXBuffer, block); - if (rxlen == 0) rxlen = WifiAP::RecvPacket(RXBuffer); - if (rxlen == 0) return false; - if (rxlen < 12+24) continue; - - framelen = *(u16*)&RXBuffer[10]; - if (framelen != rxlen-12) - { - printf("bad frame length\n"); - continue; - } - framelen -= 4; - - framectl = *(u16*)&RXBuffer[12+0]; - txrate = RXBuffer[8]; - - u32 a_src, a_dst, a_bss; - rxflags = 0x0010; - switch (framectl & 0x000C) - { - case 0x0000: // management - a_src = 10; - a_dst = 4; - a_bss = 16; - if ((framectl & 0x00F0) == 0x0080) - rxflags |= 0x0001; - break; - - case 0x0004: // control - printf("blarg\n"); - continue; - - case 0x0008: // data - switch (framectl & 0x0300) - { - case 0x0000: // STA to STA - a_src = 10; - a_dst = 4; - a_bss = 16; - break; - case 0x0100: // STA to DS - a_src = 10; - a_dst = 16; - a_bss = 4; - break; - case 0x0200: // DS to STA - a_src = 16; - a_dst = 4; - a_bss = 10; - break; - case 0x0300: // DS to DS - printf("blarg\n"); - continue; - } - // TODO: those also trigger on other framectl values - // like 0208 -> C - framectl &= 0xE7FF; - if (framectl == 0x0228) rxflags |= 0x000C; // MP host frame - else if (framectl == 0x0218) rxflags |= 0x000D; // MP ack frame - else if (framectl == 0x0118) rxflags |= 0x000E; // MP reply frame - else if (framectl == 0x0158) rxflags |= 0x000F; // empty MP reply frame - else rxflags |= 0x0008; - break; - } - - if (MACEqual(&RXBuffer[12 + a_src], (u8*)&IOPORT(W_MACAddr0))) - continue; // oops. we received a packet we just sent. - - bssidmatch = MACEqual(&RXBuffer[12 + a_bss], (u8*)&IOPORT(W_BSSID0)); - //if (!(IOPORT(W_BSSID0) & 0x0001) && !(RXBuffer[12 + a_bss] & 0x01) && - if (!MACEqual(&RXBuffer[12 + a_dst], (u8*)&IOPORT(W_MACAddr0)) && - !(RXBuffer[12 + a_dst] & 0x01)) - { - printf("received packet %04X but it didn't pass the MAC check\n", framectl); - continue; - } - - break; - } - - WIFI_LOG("wifi: received packet FC:%04X SN:%04X CL:%04X RXT:%d CMT:%d\n", - framectl, *(u16*)&RXBuffer[12+4+6+6+6], *(u16*)&RXBuffer[12+4+6+6+6+2+2], framelen*4, IOPORT(W_CmdReplyTime)); - - // make RX header - - if (bssidmatch) rxflags |= 0x8000; - - *(u16*)&RXBuffer[0] = rxflags; - *(u16*)&RXBuffer[2] = 0x0040; // ??? - *(u16*)&RXBuffer[6] = txrate; - *(u16*)&RXBuffer[8] = framelen; - *(u16*)&RXBuffer[10] = 0x4080; // min/max RSSI. dunno - + u16 framelen = *(u16*)&RXBuffer[8]; RXTime = framelen; + u16 txrate = *(u16*)&RXBuffer[6]; if (txrate == 0x14) { RXTime *= 4; - RXHalfwordTimeMask = 0x7; + RXHalfwordTimeMask = 0x7 & kTimeCheckMask; } else { RXTime *= 8; - RXHalfwordTimeMask = 0xF; + RXHalfwordTimeMask = 0xF & kTimeCheckMask; } u16 addr = IOPORT(W_RXBufWriteCursor) << 1; @@ -934,6 +1123,443 @@ bool CheckRX(bool block) SetIRQ(6); SetStatus(6); + ComStatus |= 1; +} + +void FinishRX() +{ + ComStatus &= ~0x1; + RXCounter = 0; + + if (!ComStatus) + { + if (IOPORT(W_PowerState) & 0x0300) + SetStatus(9); + else + SetStatus(1); + } + + // TODO: RX stats + + u16 framectl = *(u16*)&RXBuffer[12]; + + // check the frame's destination address + // note: the hardware always checks the first address field, regardless of the frame type/etc + // similarly, the second address field is used to send acks to non-broadcast frames + + u8* dstmac = &RXBuffer[12 + 4]; + if (!(dstmac[0] & 0x01)) + { + if (!MACEqual(dstmac, (u8*)&IOPORT(W_MACAddr0))) + return; + } + + // reject the frame if it's a WEP frame and WEP is off + // TODO: check if sending WEP frames with WEP off works at all? + + if (framectl & (1<<14)) + { + if (!(IOPORT(W_WEPCnt) & (1<<15))) + return; + } + + // apply RX filtering + // TODO: + // * RXFILTER bits 0, 9, 10, 12 not fully understood + // * port 0D8 also affects reception of frames + // * MP CMD frames with a duplicate sequence number are ignored + + u16 rxflags = 0x0010; + + switch ((framectl >> 2) & 0x3) + { + case 0: // management + { + u8* bssid = &RXBuffer[12 + 16]; + if (MACEqual(bssid, (u8*)&IOPORT(W_BSSID0))) + rxflags |= 0x8000; + + u16 subtype = (framectl >> 4) & 0xF; + if (subtype == 0x8) // beacon + { + if (!(rxflags & 0x8000)) + { + if (!(IOPORT(W_RXFilter) & (1<<0))) + return; + } + + rxflags |= 0x0001; + } + else if ((subtype <= 0x5) || + (subtype >= 0xA && subtype <= 0xC)) + { + if (!(rxflags & 0x8000)) + { + // CHECKME! + if (!(IOPORT(W_RXFilter) & (3<<9))) + return; + } + } + } + break; + + case 1: // control + { + if ((framectl & 0xF0) == 0xA0) // PS-poll + { + u8* bssid = &RXBuffer[12 + 4]; + if (MACEqual(bssid, (u8*)&IOPORT(W_BSSID0))) + rxflags |= 0x8000; + + if (!(rxflags & 0x8000)) + { + if (!(IOPORT(W_RXFilter) & (1<<11))) + return; + } + + rxflags |= 0x0005; + } + else + return; + } + break; + + case 2: // data + { + u16 fromto = (framectl >> 8) & 0x3; + if (IOPORT(W_RXFilter2) & (1<> 4) & 0xF) + { + case 0x0: break; + + case 0x1: + if ((rxflags & 0xF) == 0xD) + { + if (!(rxfilter & (1<<7))) return; + } + else if ((rxflags & 0xF) != 0xE) + { + if (!(rxfilter & (1<<1))) return; + } + break; + + case 0x2: + if ((rxflags & 0xF) != 0xC) + { + if (!(rxfilter & (1<<2))) return; + } + break; + + case 0x3: + if (!(rxfilter & (1<<3))) return; + break; + + case 0x4: break; + + case 0x5: + if ((rxflags & 0xF) == 0xF) + { + if (!(rxfilter & (1<<8))) return; + } + else + { + if (!(rxfilter & (1<<4))) return; + } + break; + + case 0x6: + if (!(rxfilter & (1<<5))) return; + break; + + case 0x7: + if (!(rxfilter & (1<<6))) return; + break; + + default: + return; + } + } + break; + } + + // build the RX header + + u16 headeraddr = IOPORT(W_RXBufWriteCursor) << 1; + *(u16*)&RAM[headeraddr] = rxflags; + IncrementRXAddr(headeraddr); + *(u16*)&RAM[headeraddr] = 0x0040; // ??? + IncrementRXAddr(headeraddr, 4); + *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[6]; // TX rate + IncrementRXAddr(headeraddr); + *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[8]; // frame length + IncrementRXAddr(headeraddr); + *(u16*)&RAM[headeraddr] = 0x4080; // RSSI + + // signal successful reception + + u16 addr = IOPORT(W_RXTXAddr) << 1; + if (addr & 0x2) IncrementRXAddr(addr); + IOPORT(W_RXBufWriteCursor) = (addr & ~0x3) >> 1; + + SetIRQ(0); + + if ((rxflags & 0x800F) == 0x800C) + { + // reply to CMD frames + + u16 clientmask = *(u16*)&RXBuffer[0xC + 26]; + if (IOPORT(W_AIDLow) && (clientmask & (1 << IOPORT(W_AIDLow)))) + { + SendMPReply(*(u16*)&RXBuffer[0xC + 24], clientmask); + } + else + { + // send a blank + // this is just so the host can have something to receive, instead of hitting a timeout + // in the case this client wasn't ready to send a reply + // TODO: also send this if we have RX disabled + + Platform::MP_SendReply(nullptr, 0, USTimestamp, 0); + } + } + else if ((rxflags & 0x800F) == 0x8001) + { + // when receiving a beacon with the right BSSID, the beacon's timestamp + // is copied to USCOUNTER + + u32 len = *(u16*)&RXBuffer[8]; + u16 txrate = *(u16*)&RXBuffer[6]; + len *= ((txrate==0x14) ? 4 : 8); + len -= 76; // CHECKME: is this offset fixed? + + u64 timestamp = *(u64*)&RXBuffer[12 + 24]; + timestamp += (u64)len; + + USCounter = timestamp; + } +} + +void MPClientReplyRX(int client) +{ + if (IOPORT(W_PowerState) & 0x0300) + return; + + if (!(IOPORT(W_RXCnt) & 0x8000)) + return; + + if (IOPORT(W_RXBufBegin) == IOPORT(W_RXBufEnd)) + return; + + int framelen; + u8 txrate; + + u8* reply = &MPClientReplies[(client-1)*1024]; + framelen = *(u16*)&reply[10]; + + txrate = reply[8]; + + // TODO: what are the maximum crop values? + u16 framectl = *(u16*)&reply[12]; + if (framectl & (1<<14)) + { + framelen -= (IOPORT(W_RXLenCrop) >> 7) & 0x1FE; + if (framelen > 24) memmove(&RXBuffer[12+24], &RXBuffer[12+28], framelen); + } + else + framelen -= (IOPORT(W_RXLenCrop) << 1) & 0x1FE; + + if (framelen < 0) framelen = 0; + + // TODO rework RX system so we don't need this (by reading directly into MPClientReplies) + memcpy(RXBuffer, reply, 12+framelen); + + *(u16*)&RXBuffer[6] = txrate; + *(u16*)&RXBuffer[8] = framelen; + + RXTimestamp = 0; + StartRX(); +} + +bool CheckRX(int type) // 0=regular 1=MP replies 2=MP host frames +{ + if (IOPORT(W_PowerState) & 0x0300) + return false; + + if (!(IOPORT(W_RXCnt) & 0x8000)) + return false; + + if (IOPORT(W_RXBufBegin) == IOPORT(W_RXBufEnd)) + return false; + + int rxlen; + int framelen; + u16 framectl; + u8 txrate; + u64 timestamp; + + for (;;) + { + timestamp = 0; + + if (type == 0) + { + rxlen = Platform::MP_RecvPacket(RXBuffer, ×tamp); + if (rxlen <= 0) + rxlen = WifiAP::RecvPacket(RXBuffer); + } + else + { + rxlen = Platform::MP_RecvHostPacket(RXBuffer, ×tamp); + if (rxlen < 0) + { + // host is gone + // TODO: make this more resilient + IsMPClient = false; + } + } + + if (rxlen <= 0) return false; + if (rxlen < 12+24) continue; + + framelen = *(u16*)&RXBuffer[10]; + if (framelen != rxlen-12) + { + printf("bad frame length %d/%d\n", framelen, rxlen-12); + continue; + } + + framectl = *(u16*)&RXBuffer[12+0]; + txrate = RXBuffer[8]; + + // TODO: what are the maximum crop values? + if (framectl & (1<<14)) + { + framelen -= (IOPORT(W_RXLenCrop) >> 7) & 0x1FE; + if (framelen > 24) memmove(&RXBuffer[12+24], &RXBuffer[12+28], framelen); + } + else + framelen -= (IOPORT(W_RXLenCrop) << 1) & 0x1FE; + + if (framelen < 0) framelen = 0; + + break; + } + + WIFI_LOG("wifi: received packet FC:%04X SN:%04X CL:%04X RXT:%d CMT:%d\n", + framectl, *(u16*)&RXBuffer[12+4+6+6+6], *(u16*)&RXBuffer[12+4+6+6+6+2+2], framelen*4, IOPORT(W_CmdReplyTime)); + + *(u16*)&RXBuffer[6] = txrate; + *(u16*)&RXBuffer[8] = framelen; + + bool macgood = (RXBuffer[12 + 4] & 0x01) || MACEqual(&RXBuffer[12 + 4], (u8*)&IOPORT(W_MACAddr0)); + + if (((framectl & 0x00FF) == 0x0010) && timestamp && macgood) + { + // if receiving an association response: get the sync value from the host + + u16 aid = *(u16*)&RXBuffer[12+24+4]; + + if (aid) + { + printf("[CLIENT %01X] host sync=%016llX\n", aid&0xF, timestamp); + + IsMPClient = true; + USTimestamp = timestamp; + NextSync = RXTimestamp + (framelen * (txrate==0x14 ? 4:8)); + } + + RXTimestamp = 0; + StartRX(); + } + else if (((framectl & 0x00FF) == 0x00C0) && timestamp && macgood && IsMPClient) + { + IsMPClient = false; + NextSync = 0; + + RXTimestamp = 0; + StartRX(); + } + else if (macgood && IsMPClient) + { + // if we are being a MP client, we need to delay this frame until we reach the + // timestamp it came with + // we also need to determine how far we can run after having received this frame + + RXTimestamp = timestamp; + if (RXTimestamp < USTimestamp) RXTimestamp = USTimestamp; + NextSync = RXTimestamp + (framelen * (txrate==0x14 ? 4:8)); + + if (MACEqual(&RXBuffer[12 + 4], MPCmdMAC)) + { + u16 clienttime = *(u16*)&RXBuffer[12+24]; + u16 clientmask = *(u16*)&RXBuffer[12+26]; + + // include the MP reply time window + NextSync += 112 + ((clienttime + 10) * NumClients(clientmask)); + } + } + else + { + // otherwise, just start receiving this frame now + + RXTimestamp = 0; + StartRX(); + } + return true; } @@ -942,7 +1568,7 @@ void MSTimer() { if (IOPORT(W_USCompareCnt)) { - if (USCounter == USCompare) + if ((USCounter & ~0x3FF) == USCompare) { BlockBeaconIRQ14 = false; SetIRQ14(0); @@ -964,16 +1590,34 @@ void MSTimer() void USTimer(u32 param) { - WifiAP::USTimer(); + USTimestamp += kTimerInterval; + + if (IsMPClient && (!ComStatus)) + { + if (RXTimestamp && (USTimestamp >= RXTimestamp)) + { + RXTimestamp = 0; + StartRX(); + } + + if (USTimestamp >= NextSync) + { + // TODO: not do this every tick if it fails to receive a frame! + CheckRX(2); + } + } + + if (!(USTimestamp & 0x3FF & kTimeCheckMask)) + WifiAP::MSTimer(); bool switchOffPowerSaving = false; if (USUntilPowerOn < 0) { - USUntilPowerOn++; + USUntilPowerOn += kTimerInterval; - switchOffPowerSaving = USUntilPowerOn == 0 && (IOPORT(W_PowerUnk) & 0x0001 || ForcePowerOn); + switchOffPowerSaving = (USUntilPowerOn >= 0) && (IOPORT(W_PowerUnk) & 0x0001 || ForcePowerOn); } - if (USUntilPowerOn == 0 && (IOPORT(W_PowerState) & 0x0002 || switchOffPowerSaving)) + if ((USUntilPowerOn >= 0) && (IOPORT(W_PowerState) & 0x0002 || switchOffPowerSaving)) { IOPORT(W_PowerState) = 0; IOPORT(W_RFPins) = 1; @@ -983,35 +1627,50 @@ void USTimer(u32 param) if (IOPORT(W_USCountCnt)) { - USCounter++; + USCounter += kTimerInterval; u32 uspart = (USCounter & 0x3FF); if (IOPORT(W_USCompareCnt)) { u32 beaconus = (IOPORT(W_BeaconCount1) << 10) | (0x3FF - uspart); - if (beaconus == IOPORT(W_PreBeacon)) SetIRQ15(); + if ((beaconus & kTimeCheckMask) == (IOPORT(W_PreBeacon) & kTimeCheckMask)) + SetIRQ15(); } - if (!uspart) MSTimer(); + if (!(uspart & kTimeCheckMask)) + MSTimer(); } if (IOPORT(W_CmdCountCnt) & 0x0001) { if (CmdCounter > 0) { - CmdCounter--; + if (CmdCounter < kTimerInterval) + CmdCounter = 0; + else + CmdCounter -= kTimerInterval; } } if (IOPORT(W_ContentFree) != 0) - IOPORT(W_ContentFree)--; - - if (!(IOPORT(W_PowerState) & 0x300)) { - if (ComStatus == 0) + if (IOPORT(W_ContentFree) < kTimerInterval) + IOPORT(W_ContentFree) = 0; + else + IOPORT(W_ContentFree) -= kTimerInterval; + } + + if (ComStatus == 0) + { + u16 txbusy = IOPORT(W_TXBusy); + if (txbusy) { - u16 txbusy = IOPORT(W_TXBusy); - if (txbusy) + if (IOPORT(W_PowerState) & 0x0300) + { + ComStatus = 0; + TXCurSlot = -1; + } + else { ComStatus = 0x2; if (txbusy & 0x0080) TXCurSlot = 5; @@ -1021,105 +1680,94 @@ void USTimer(u32 param) else if (txbusy & 0x0002) TXCurSlot = 1; else if (txbusy & 0x0001) TXCurSlot = 0; } + } + else + { + if ((!IsMPClient) || (USTimestamp > NextSync)) + { + if ((!(RXCounter & 0x1FF & kTimeCheckMask)) && (!ComStatus)) + { + CheckRX(0); + } + } + + RXCounter += kTimerInterval; + } + } + + if (ComStatus & 0x2) + { + bool finished = ProcessTX(&TXSlots[TXCurSlot], TXCurSlot); + if (finished) + { + if (IOPORT(W_PowerState) & 0x0300) + { + IOPORT(W_TXBusy) = 0; + SetStatus(9); + } + + // transfer finished, see if there's another slot to do + // checkme: priority order of beacon/reply + // TODO: for CMD, check CMDCOUNT + u16 txbusy = IOPORT(W_TXBusy); + if (txbusy & 0x0080) TXCurSlot = 5; + else if (txbusy & 0x0010) TXCurSlot = 4; + else if (txbusy & 0x0008) TXCurSlot = 3; + else if (txbusy & 0x0004) TXCurSlot = 2; + else if (txbusy & 0x0002) TXCurSlot = 1; + else if (txbusy & 0x0001) TXCurSlot = 0; else { - if ((!(RXCounter & 0x1FF))) - { - if (CheckRX(false)) - ComStatus = 0x1; - } - - RXCounter++; + TXCurSlot = -1; + ComStatus = 0; + RXCounter = 0; } } - - if (ComStatus & 0x2) + } + if (ComStatus & 0x1) + { + RXTime -= kTimerInterval; + if (!(RXTime & RXHalfwordTimeMask)) { - bool finished = ProcessTX(&TXSlots[TXCurSlot], TXCurSlot); - if (finished) + u16 addr = IOPORT(W_RXTXAddr) << 1; + if (addr < 0x1FFF) *(u16*)&RAM[addr] = *(u16*)&RXBuffer[RXBufferPtr]; + + IncrementRXAddr(addr); + IOPORT(W_RXTXAddr) = addr >> 1; + RXBufferPtr += 2; + + if (RXTime <= 0) // finished receiving { - // transfer finished, see if there's another slot to do - // checkme: priority order of beacon/reply - // TODO: for CMD, check CMDCOUNT - u16 txbusy = IOPORT(W_TXBusy); - if (txbusy & 0x0080) TXCurSlot = 5; - else if (txbusy & 0x0010) TXCurSlot = 4; - else if (txbusy & 0x0008) TXCurSlot = 3; - else if (txbusy & 0x0004) TXCurSlot = 2; - else if (txbusy & 0x0002) TXCurSlot = 1; - else if (txbusy & 0x0001) TXCurSlot = 0; - else - { - TXCurSlot = -1; - ComStatus = 0; - RXCounter = 0; - } + FinishRX(); } - } - if (ComStatus & 0x1) - { - RXTime--; - if (!(RXTime & RXHalfwordTimeMask)) + else if (addr == (IOPORT(W_RXBufReadCursor) << 1)) { - u16 addr = IOPORT(W_RXTXAddr) << 1; - if (addr < 0x1FFF) *(u16*)&RAM[addr] = *(u16*)&RXBuffer[RXBufferPtr]; + // TODO: properly check the crossing of the read cursor + // (for example, if it is outside of the RX buffer) - IncrementRXAddr(addr); - RXBufferPtr += 2; - - if (RXTime == 0) // finished receiving + printf("wifi: RX buffer full (buf=%04X/%04X rd=%04X wr=%04X rxtx=%04X power=%04X com=%d rxcnt=%04X filter=%04X/%04X frame=%04X/%04X len=%d)\n", + (IOPORT(W_RXBufBegin)>>1)&0xFFF, (IOPORT(W_RXBufEnd)>>1)&0xFFF, + IOPORT(W_RXBufReadCursor), IOPORT(W_RXBufWriteCursor), + IOPORT(W_RXTXAddr), IOPORT(W_PowerState), ComStatus, + IOPORT(W_RXCnt), IOPORT(W_RXFilter), IOPORT(W_RXFilter2), + *(u16*)&RXBuffer[0], *(u16*)&RXBuffer[12], *(u16*)&RXBuffer[8]); + RXTime = 0; + SetStatus(1); + if (TXCurSlot == 0xFFFFFFFF) { - if (addr & 0x2) IncrementRXAddr(addr); - - // copy the RX header - u16 headeraddr = IOPORT(W_RXBufWriteCursor) << 1; - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[0]; IncrementRXAddr(headeraddr); - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[2]; IncrementRXAddr(headeraddr, 4); - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[6]; IncrementRXAddr(headeraddr); - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[8]; IncrementRXAddr(headeraddr); - *(u16*)&RAM[headeraddr] = *(u16*)&RXBuffer[10]; - - IOPORT(W_RXBufWriteCursor) = (addr & ~0x3) >> 1; - - SetIRQ(0); - SetStatus(1); - - WIFI_LOG("wifi: finished receiving packet %04X\n", *(u16*)&RXBuffer[12]); - ComStatus &= ~0x1; RXCounter = 0; - - if ((RXBuffer[0] & 0x0F) == 0x0C) - { - u16 clientmask = *(u16*)&RXBuffer[0xC + 26]; - if (IOPORT(W_AIDLow) && (RXBuffer[0xC + 4] & 0x01) && (clientmask & (1 << IOPORT(W_AIDLow)))) - { - SendMPReply(*(u16*)&RXBuffer[0xC + 24], *(u16*)&RXBuffer[0xC + 26]); - } - } } - - if (addr == (IOPORT(W_RXBufReadCursor) << 1)) + // TODO: proper error management + if ((!ComStatus) && (IOPORT(W_PowerState) & 0x0300)) { - printf("wifi: RX buffer full\n"); - RXTime = 0; - SetStatus(1); - if (TXCurSlot == 0xFFFFFFFF) - { - ComStatus &= ~0x1; - RXCounter = 0; - } - // TODO: proper error management + SetStatus(9); } - - IOPORT(W_RXTXAddr) = addr >> 1; } } } - // TODO: make it more accurate, eventually - // in the DS, the wifi system has its own 22MHz clock and doesn't use the system clock - NDS::ScheduleEvent(NDS::Event_Wifi, true, 33, USTimer, 0); + ScheduleTimer(false); } @@ -1157,15 +1805,13 @@ void RFTransfer_Type3() } -// TODO: wifi waitstates - u16 Read(u32 addr) -{//printf("WIFI READ %08X\n", addr); +{ if (addr >= 0x04810000) return 0; addr &= 0x7FFE; - //printf("WIFI: read %08X\n", addr); + if (addr >= 0x4000 && addr < 0x6000) { return *(u16*)&RAM[addr & 0x1FFE]; @@ -1213,7 +1859,6 @@ u16 Read(u32 addr) if (activeread) { u32 rdaddr = IOPORT(W_RXBufReadAddr); - u16 ret = *(u16*)&RAM[rdaddr]; rdaddr += 2; @@ -1243,6 +1888,20 @@ u16 Read(u32 addr) case W_TXBusy: return IOPORT(W_TXBusy) & 0x001F; // no bit for MP replies. odd + + case W_CMDStat0: + case W_CMDStat1: + case W_CMDStat2: + case W_CMDStat3: + case W_CMDStat4: + case W_CMDStat5: + case W_CMDStat6: + case W_CMDStat7: + { + u16 ret = IOPORT(addr&0xFFF); + IOPORT(addr&0xFFF) = 0; + return ret; + } } //printf("WIFI: read %08X\n", addr); @@ -1250,12 +1909,12 @@ u16 Read(u32 addr) } void Write(u32 addr, u16 val) -{//printf("WIFI WRITE %08X %04X\n", addr, val); +{ if (addr >= 0x04810000) return; addr &= 0x7FFE; - //printf("WIFI: write %08X %04X\n", addr, val); + if (addr >= 0x4000 && addr < 0x6000) { *(u16*)&RAM[addr & 0x1FFE] = val; @@ -1290,9 +1949,7 @@ void Write(u32 addr, u16 val) { //printf("mode reset shutdown %08x\n", NDS::ARM7->R[15]); IOPORT(0x27C) = 0x000A; - IOPORT(W_RFPins) = 0x0004; - IOPORT(W_RFStatus) = 9; - IOPORT(W_PowerState) |= 0x200; + PowerDown(); } if (val & 0x2000) @@ -1353,6 +2010,13 @@ void Write(u32 addr, u16 val) printf("wifi: force-setting IF %04X\n", val); return; + case W_AIDLow: + IOPORT(W_AIDLow) = val & 0x000F; + return; + case W_AIDFull: + IOPORT(W_AIDFull) = val & 0x07FF; + return; + case W_PowerState: //printf("writing power state %x %08x\n", val, NDS::ARM7->R[15]); IOPORT(W_PowerState) |= val & 0x0002; @@ -1376,6 +2040,7 @@ void Write(u32 addr, u16 val) return; case W_PowerForce: //if ((val&0x8001)==0x8000) printf("WIFI: forcing power %04X\n", val); + val &= 0x8001; //printf("writing power force %x %08x\n", val, NDS::ARM7->R[15]); if (val == 0x8001) @@ -1384,8 +2049,7 @@ void Write(u32 addr, u16 val) IOPORT(0x034) = 0x0002; IOPORT(W_PowerState) = 0x0200; IOPORT(W_TXReqRead) = 0; - IOPORT(W_RFPins) = 0x0046; - IOPORT(W_RFStatus) = 9; + PowerDown(); } if (val == 1 && IOPORT(W_PowerState) & 0x0002) { @@ -1403,29 +2067,9 @@ void Write(u32 addr, u16 val) } break; case W_PowerUS: - // schedule timer event when the clock is enabled - // TODO: check whether this resets USCOUNT (and also which other events can reset it) - if ((IOPORT(W_PowerUS) & 0x0001) && !(val & 0x0001)) - { - printf("WIFI ON\n"); - NDS::ScheduleEvent(NDS::Event_Wifi, false, 33, USTimer, 0); - if (!MPInited) - { - Platform::MP_Init(); - MPInited = true; - } - if (!LANInited) - { - Platform::LAN_Init(); - LANInited = true; - } - } - else if (!(IOPORT(W_PowerUS) & 0x0001) && (val & 0x0001)) - { - printf("WIFI OFF\n"); - NDS::CancelEvent(NDS::Event_Wifi); - } - break; + IOPORT(W_PowerUS) = val & 0x0003; + UpdatePowerOn(); + return; case W_PowerUnk: val &= 0x0003; //printf("writing power unk %x\n", val); @@ -1486,6 +2130,10 @@ void Write(u32 addr, u16 val) IOPORT(W_TXSlotReply2) = IOPORT(W_TXSlotReply1); IOPORT(W_TXSlotReply1) = 0; } + if (val & 0x8000) + { + FireTX(); + } val &= 0xFF0E; if (val & 0x7FFF) printf("wifi: unknown RXCNT bits set %04X\n", val); break; @@ -1570,6 +2218,7 @@ void Write(u32 addr, u16 val) case W_TXSlotCmd: // checkme: is it possible to cancel a queued transfer that hasn't started yet // by clearing bit15 here? + // TODO: "W_TXBUF_CMD.Bit15 can be set ONLY while W_CMD_COUNT is non-zero." IOPORT(addr&0xFFF) = val; FireTX(); return; @@ -1597,13 +2246,12 @@ void Write(u32 addr, u16 val) case 0x214: case 0x268: return; - + default: //printf("WIFI unk: write %08X %04X\n", addr, val); break; } - //printf("WIFI: write %08X %04X\n", addr, val); IOPORT(addr&0xFFF) = val; } diff --git a/src/Wifi.h b/src/Wifi.h index 2e156738..b9594f45 100644 --- a/src/Wifi.h +++ b/src/Wifi.h @@ -93,6 +93,7 @@ enum W_CmdTotalTime = 0x0C0, W_CmdReplyTime = 0x0C4, W_RXFilter = 0x0D0, + W_RXLenCrop = 0x0DA, W_RXFilter2 = 0x0E0, W_USCountCnt = 0x0E8, @@ -136,12 +137,43 @@ enum W_TXErrorCount = 0x1C0, W_RXCount = 0x1C4, + W_CMDStat0 = 0x1D0, + W_CMDStat1 = 0x1D2, + W_CMDStat2 = 0x1D4, + W_CMDStat3 = 0x1D6, + W_CMDStat4 = 0x1D8, + W_CMDStat5 = 0x1DA, + W_CMDStat6 = 0x1DC, + W_CMDStat7 = 0x1DE, + W_TXSeqNo = 0x210, W_RFStatus = 0x214, W_IFSet = 0x21C, W_RXTXAddr = 0x268, }; +enum +{ + Event_RXCheck = 0, + Event_IRQ15, + Event_MSTimer, + Event_RFWakeup, + Event_RX, + Event_TX, + Event_MPClientSync, + Event_RF, + Event_BB, + + Event_MAX +}; + +struct SchedEvent +{ + void (*Func)(u32 param); + u64 Timestamp; + u32 Param; +}; + extern bool MPInited; @@ -151,7 +183,7 @@ void DeInit(); void Reset(); void DoSavestate(Savestate* file); -void StartTX_Beacon(); +void SetPowerCnt(u32 val); void USTimer(u32 param); diff --git a/src/WifiAP.cpp b/src/WifiAP.cpp index e083c74d..53516396 100644 --- a/src/WifiAP.cpp +++ b/src/WifiAP.cpp @@ -91,7 +91,7 @@ void DeInit() void Reset() { // random starting point for the counter - USCounter = 0x428888017ULL; + USCounter = 0x428888000ULL; SeqNo = 0x0120; BeaconDue = false; @@ -115,18 +115,6 @@ bool MACIsBroadcast(u8* a) } -void USTimer() -{ - USCounter++; - - u32 chk = (u32)USCounter; - if (!(chk & 0x1FFFF)) - { - // send beacon every 128ms - BeaconDue = true; - } -} - void MSTimer() { USCounter += 0x400; diff --git a/src/WifiAP.h b/src/WifiAP.h index 187f16bd..e5ca1ed7 100644 --- a/src/WifiAP.h +++ b/src/WifiAP.h @@ -19,6 +19,8 @@ #ifndef WIFIAP_H #define WIFIAP_H +#include "types.h" + namespace WifiAP { @@ -31,7 +33,6 @@ bool Init(); void DeInit(); void Reset(); -void USTimer(); void MSTimer(); // packet format: 12-byte TX header + original 802.11 frame diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.cpp b/src/frontend/qt_sdl/AudioSettingsDialog.cpp index a9b3ade7..4beefaf3 100644 --- a/src/frontend/qt_sdl/AudioSettingsDialog.cpp +++ b/src/frontend/qt_sdl/AudioSettingsDialog.cpp @@ -67,6 +67,20 @@ AudioSettingsDialog::AudioSettingsDialog(QWidget* parent) : QDialog(parent), ui( bool iswav = (Config::MicInputType == 3); ui->txtMicWavPath->setEnabled(iswav); ui->btnMicWavBrowse->setEnabled(iswav); + + int inst = Platform::InstanceID(); + if (inst > 0) + { + ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1)); + ui->cbInterpolation->setEnabled(false); + ui->cbBitrate->setEnabled(false); + for (QAbstractButton* btn : grpMicMode->buttons()) + btn->setEnabled(false); + ui->txtMicWavPath->setEnabled(false); + ui->btnMicWavBrowse->setEnabled(false); + } + else + ui->lblInstanceNum->hide(); } AudioSettingsDialog::~AudioSettingsDialog() diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.ui b/src/frontend/qt_sdl/AudioSettingsDialog.ui index d7cfadd6..8fc38d92 100644 --- a/src/frontend/qt_sdl/AudioSettingsDialog.ui +++ b/src/frontend/qt_sdl/AudioSettingsDialog.ui @@ -7,7 +7,7 @@ 0 0 482 - 256 + 301 @@ -23,6 +23,13 @@ QLayout::SetFixedSize + + + + Configuring settings for instance X + + + @@ -76,7 +83,7 @@ - <html><head/><body><p>The bitrate of audio playback. If set to "Automatic" this will be 10-bit for DS mode and 16-bit for DSi mode.</p></body></html> + <html><head/><body><p>The bitrate of audio playback. If set to "Automatic" this will be 10-bit for DS mode and 16-bit for DSi mode.</p></body></html> diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index d211dd6d..c9ba47fe 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -1,10 +1,12 @@ -project(qt_sdl) +include(CMakeDependentOption) -SET(SOURCES_QT_SDL +include(FixInterfaceIncludes) + +set(SOURCES_QT_SDL main.cpp main_shaders.h CheatsDialog.cpp - Config.cpp + Config.cpp EmuSettingsDialog.cpp PowerManagement/PowerManagementDialog.cpp PowerManagement/resources/battery.qrc @@ -16,6 +18,7 @@ SET(SOURCES_QT_SDL AudioSettingsDialog.cpp FirmwareSettingsDialog.cpp PathSettingsDialog.cpp + MPSettingsDialog.cpp WifiSettingsDialog.cpp InterfaceSettingsDialog.cpp ROMInfoDialog.cpp @@ -24,13 +27,13 @@ SET(SOURCES_QT_SDL Input.cpp LAN_PCap.cpp LAN_Socket.cpp + LocalMP.cpp OSD.cpp OSD_shaders.h font.h Platform.cpp QPathInput.h - ROMManager.cpp - SaveManager.cpp + CameraManager.cpp ArchiveUtil.h @@ -42,7 +45,7 @@ SET(SOURCES_QT_SDL ../mic_blow.h ${CMAKE_SOURCE_DIR}/res/melon.qrc -) + ) if (APPLE) option(USE_QT6 "Build using Qt 6 instead of 5" ON) @@ -56,29 +59,11 @@ if (WIN32) endif() if (USE_QT6) - if (BUILD_STATIC AND QT6_STATIC_DIR) - set(QT6_STATIC_BASE ${QT6_STATIC_DIR}/lib/cmake/Qt6) - set(Qt6_DIR ${QT6_STATIC_BASE}) - set(Qt6Core_DIR ${QT6_STATIC_BASE}Core) - set(Qt6Gui_DIR ${QT6_STATIC_BASE}Gui) - set(Qt6Widgets_DIR ${QT6_STATIC_BASE}Widgets) - set(Qt6Network_DIR ${QT6_STATIC_BASE}Network) set(Qt6Multimedia_DIR ${QT6_STATIC_BASE}Multimedia) - set(Qt6OpenGL_DIR ${QT6_STATIC_BASE}OpenGL) - set(Qt6OpenGLWidgets_DIR ${QT6_STATIC_BASE}OpenGLWidgets) - endif() find_package(Qt6 COMPONENTS Core Gui Widgets Network Multimedia OpenGL OpenGLWidgets REQUIRED) set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Multimedia Qt6::OpenGL Qt6::OpenGLWidgets) else() - if (BUILD_STATIC AND QT5_STATIC_DIR) - set(QT5_STATIC_BASE ${QT5_STATIC_DIR}/lib/cmake/Qt5) - set(Qt5_DIR ${QT5_STATIC_BASE}) - set(Qt5Core_DIR ${QT5_STATIC_BASE}Core) - set(Qt5Gui_DIR ${QT5_STATIC_BASE}Gui) - set(Qt5Widgets_DIR ${QT5_STATIC_BASE}Widgets) - set(Qt5Network_DIR ${QT5_STATIC_BASE}Network) set(Qt5Multimedia_DIR ${QT5_STATIC_BASE}Multimedia) - endif() find_package(Qt5 COMPONENTS Core Gui Widgets Network Multimedia REQUIRED) set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network Qt5::Multimedia) endif() @@ -87,66 +72,53 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) -find_package(Threads REQUIRED) -find_package(PkgConfig REQUIRED) -find_package(Iconv REQUIRED) -pkg_check_modules(SDL2 REQUIRED sdl2) -pkg_check_modules(SLIRP REQUIRED slirp) -pkg_check_modules(LIBARCHIVE REQUIRED libarchive) -add_compile_definitions(ARCHIVE_SUPPORT_ENABLED) - -if (WIN32 AND (CMAKE_BUILD_TYPE STREQUAL Release)) - add_executable(melonDS WIN32 ${SOURCES_QT_SDL}) -else() - add_executable(melonDS ${SOURCES_QT_SDL}) +if (BUILD_STATIC) + list(APPEND PKG_CONFIG_EXECUTABLE "--static") endif() -target_link_libraries(melonDS ${CMAKE_THREAD_LIBS_INIT}) +find_package(Threads REQUIRED) +find_package(PkgConfig REQUIRED) +pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2) +pkg_check_modules(Slirp REQUIRED IMPORTED_TARGET slirp) +pkg_check_modules(LibArchive REQUIRED IMPORTED_TARGET libarchive) -target_include_directories(melonDS PRIVATE ${SDL2_INCLUDE_DIRS} ${SDL2_PREFIX}/include ${SLIRP_INCLUDE_DIRS} ${LIBARCHIVE_INCLUDE_DIRS}) -target_link_directories(melonDS PRIVATE ${SDL2_LIBRARY_DIRS} ${SLIRP_LIBRARY_DIRS}) -target_link_directories(melonDS PRIVATE ${LIBARCHIVE_LIBRARY_DIRS}) +fix_interface_includes(PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive) + +add_compile_definitions(ARCHIVE_SUPPORT_ENABLED) + +add_executable(melonDS ${SOURCES_QT_SDL}) + +if (BUILD_STATIC) + qt_import_plugins(melonDS INCLUDE Qt::QSvgPlugin) + target_link_options(melonDS PRIVATE -static) +endif() target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../..") -target_link_libraries(melonDS core) - -if (BUILD_STATIC) - target_link_libraries(melonDS -static ${SDL2_STATIC_LIBRARIES} ${SLIRP_STATIC_LIBRARIES} ${LIBARCHIVE_STATIC_LIBRARIES}) - qt_import_plugins(melonDS INCLUDE Qt::QSvgPlugin) -else() - target_link_libraries(melonDS ${SDL2_LIBRARIES} ${SLIRP_LIBRARIES} ${LIBARCHIVE_LIBRARIES}) -endif() - -if (NOT Iconv_IS_BUILT_IN) - target_link_libraries(melonDS ${Iconv_LIBRARIES}) -endif() +target_link_libraries(melonDS PRIVATE core) +target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive) +target_link_libraries(melonDS PRIVATE ${QT_LINK_LIBS} ${CMAKE_DL_LIBS}) if (UNIX) option(PORTABLE "Make a portable build that looks for its configuration in the current directory" OFF) - target_link_libraries(melonDS ${QT_LINK_LIBS}) - if (CMAKE_SYSTEM_NAME STREQUAL "Linux") - target_link_libraries(melonDS dl) - endif() elseif (WIN32) option(PORTABLE "Make a portable build that looks for its configuration in the current directory" ON) + configure_file("${CMAKE_SOURCE_DIR}/res/melon.rc.in" "${CMAKE_SOURCE_DIR}/melon.rc") target_sources(melonDS PUBLIC "${CMAKE_SOURCE_DIR}/melon.rc") - target_link_libraries(melonDS comctl32 d2d1 dwrite uxtheme ws2_32 iphlpapi gdi32) - if (BUILD_STATIC) - target_link_libraries(melonDS imm32 winmm version setupapi -static z zstd ${QT_LINK_LIBS}) - else() - target_link_libraries(melonDS ${QT_LINK_LIBS}) - endif() + target_link_libraries(melonDS PRIVATE ws2_32 iphlpapi) + set_target_properties(melonDS PROPERTIES LINK_FLAGS_DEBUG "-mconsole") endif() if (PORTABLE) - add_definitions(-DPORTABLE) + target_compile_definitions(melonDS PRIVATE PORTABLE) endif() if (APPLE) + target_sources(melonDS PRIVATE sem_timedwait.cpp) + # Copy icon into the bundle set(RESOURCE_FILES "${CMAKE_SOURCE_DIR}/res/melon.icns") target_sources(melonDS PUBLIC "${RESOURCE_FILES}") @@ -156,7 +128,7 @@ if (APPLE) MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/melon.plist.in OUTPUT_NAME melonDS RESOURCE "${RESOURCE_FILES}") - + option(MACOS_BUNDLE_LIBS "Bundle libraries with the app on macOS" OFF) option(MACOS_BUILD_DMG "Build DMG image of the macOS application bundle" OFF) @@ -174,8 +146,8 @@ endif() if (UNIX AND NOT APPLE) foreach(SIZE 16 32 48 64 128 256) install(FILES ${CMAKE_SOURCE_DIR}/res/icon/melon_${SIZE}x${SIZE}.png - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/${SIZE}x${SIZE}/apps - RENAME net.kuribo64.melonDS.png) + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/${SIZE}x${SIZE}/apps + RENAME net.kuribo64.melonDS.png) endforeach() install(FILES ${CMAKE_SOURCE_DIR}/res/net.kuribo64.melonDS.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications) diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index ac04b5da..7f11dd9f 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -106,9 +106,10 @@ int FirmwareBirthdayDay; int FirmwareFavouriteColour; std::string FirmwareMessage; std::string FirmwareMAC; -bool RandomizeMAC; -bool SocketBindAnyAddr; +int MPAudioMode; +int MPRecvTimeout; + std::string LANDevice; bool DirectLAN; @@ -143,211 +144,206 @@ CameraConfig Camera[2]; const char* kConfigFile = "melonDS.ini"; +const char* kUniqueConfigFile = "melonDS.%d.ini"; ConfigEntry ConfigFile[] = { - {"Key_A", 0, &KeyMapping[0], -1}, - {"Key_B", 0, &KeyMapping[1], -1}, - {"Key_Select", 0, &KeyMapping[2], -1}, - {"Key_Start", 0, &KeyMapping[3], -1}, - {"Key_Right", 0, &KeyMapping[4], -1}, - {"Key_Left", 0, &KeyMapping[5], -1}, - {"Key_Up", 0, &KeyMapping[6], -1}, - {"Key_Down", 0, &KeyMapping[7], -1}, - {"Key_R", 0, &KeyMapping[8], -1}, - {"Key_L", 0, &KeyMapping[9], -1}, - {"Key_X", 0, &KeyMapping[10], -1}, - {"Key_Y", 0, &KeyMapping[11], -1}, + {"Key_A", 0, &KeyMapping[0], -1, true}, + {"Key_B", 0, &KeyMapping[1], -1, true}, + {"Key_Select", 0, &KeyMapping[2], -1, true}, + {"Key_Start", 0, &KeyMapping[3], -1, true}, + {"Key_Right", 0, &KeyMapping[4], -1, true}, + {"Key_Left", 0, &KeyMapping[5], -1, true}, + {"Key_Up", 0, &KeyMapping[6], -1, true}, + {"Key_Down", 0, &KeyMapping[7], -1, true}, + {"Key_R", 0, &KeyMapping[8], -1, true}, + {"Key_L", 0, &KeyMapping[9], -1, true}, + {"Key_X", 0, &KeyMapping[10], -1, true}, + {"Key_Y", 0, &KeyMapping[11], -1, true}, - {"Joy_A", 0, &JoyMapping[0], -1}, - {"Joy_B", 0, &JoyMapping[1], -1}, - {"Joy_Select", 0, &JoyMapping[2], -1}, - {"Joy_Start", 0, &JoyMapping[3], -1}, - {"Joy_Right", 0, &JoyMapping[4], -1}, - {"Joy_Left", 0, &JoyMapping[5], -1}, - {"Joy_Up", 0, &JoyMapping[6], -1}, - {"Joy_Down", 0, &JoyMapping[7], -1}, - {"Joy_R", 0, &JoyMapping[8], -1}, - {"Joy_L", 0, &JoyMapping[9], -1}, - {"Joy_X", 0, &JoyMapping[10], -1}, - {"Joy_Y", 0, &JoyMapping[11], -1}, + {"Joy_A", 0, &JoyMapping[0], -1, true}, + {"Joy_B", 0, &JoyMapping[1], -1, true}, + {"Joy_Select", 0, &JoyMapping[2], -1, true}, + {"Joy_Start", 0, &JoyMapping[3], -1, true}, + {"Joy_Right", 0, &JoyMapping[4], -1, true}, + {"Joy_Left", 0, &JoyMapping[5], -1, true}, + {"Joy_Up", 0, &JoyMapping[6], -1, true}, + {"Joy_Down", 0, &JoyMapping[7], -1, true}, + {"Joy_R", 0, &JoyMapping[8], -1, true}, + {"Joy_L", 0, &JoyMapping[9], -1, true}, + {"Joy_X", 0, &JoyMapping[10], -1, true}, + {"Joy_Y", 0, &JoyMapping[11], -1, true}, - {"HKKey_Lid", 0, &HKKeyMapping[HK_Lid], -1}, - {"HKKey_Mic", 0, &HKKeyMapping[HK_Mic], -1}, - {"HKKey_Pause", 0, &HKKeyMapping[HK_Pause], -1}, - {"HKKey_Reset", 0, &HKKeyMapping[HK_Reset], -1}, - {"HKKey_FastForward", 0, &HKKeyMapping[HK_FastForward], -1}, - {"HKKey_FastForwardToggle", 0, &HKKeyMapping[HK_FastForwardToggle], -1}, - {"HKKey_FullscreenToggle", 0, &HKKeyMapping[HK_FullscreenToggle], -1}, - {"HKKey_SwapScreens", 0, &HKKeyMapping[HK_SwapScreens], -1}, - {"HKKey_SolarSensorDecrease", 0, &HKKeyMapping[HK_SolarSensorDecrease], -1}, - {"HKKey_SolarSensorIncrease", 0, &HKKeyMapping[HK_SolarSensorIncrease], -1}, - {"HKKey_FrameStep", 0, &HKKeyMapping[HK_FrameStep], -1}, + {"HKKey_Lid", 0, &HKKeyMapping[HK_Lid], -1, true}, + {"HKKey_Mic", 0, &HKKeyMapping[HK_Mic], -1, true}, + {"HKKey_Pause", 0, &HKKeyMapping[HK_Pause], -1, true}, + {"HKKey_Reset", 0, &HKKeyMapping[HK_Reset], -1, true}, + {"HKKey_FastForward", 0, &HKKeyMapping[HK_FastForward], -1, true}, + {"HKKey_FastForwardToggle", 0, &HKKeyMapping[HK_FastForwardToggle], -1, true}, + {"HKKey_FullscreenToggle", 0, &HKKeyMapping[HK_FullscreenToggle], -1, true}, + {"HKKey_SwapScreens", 0, &HKKeyMapping[HK_SwapScreens], -1, true}, + {"HKKey_SolarSensorDecrease", 0, &HKKeyMapping[HK_SolarSensorDecrease], -1, true}, + {"HKKey_SolarSensorIncrease", 0, &HKKeyMapping[HK_SolarSensorIncrease], -1, true}, + {"HKKey_FrameStep", 0, &HKKeyMapping[HK_FrameStep], -1, true}, - {"HKJoy_Lid", 0, &HKJoyMapping[HK_Lid], -1}, - {"HKJoy_Mic", 0, &HKJoyMapping[HK_Mic], -1}, - {"HKJoy_Pause", 0, &HKJoyMapping[HK_Pause], -1}, - {"HKJoy_Reset", 0, &HKJoyMapping[HK_Reset], -1}, - {"HKJoy_FastForward", 0, &HKJoyMapping[HK_FastForward], -1}, - {"HKJoy_FastForwardToggle", 0, &HKJoyMapping[HK_FastForwardToggle], -1}, - {"HKJoy_FullscreenToggle", 0, &HKJoyMapping[HK_FullscreenToggle], -1}, - {"HKJoy_SwapScreens", 0, &HKJoyMapping[HK_SwapScreens], -1}, - {"HKJoy_SolarSensorDecrease", 0, &HKJoyMapping[HK_SolarSensorDecrease], -1}, - {"HKJoy_SolarSensorIncrease", 0, &HKJoyMapping[HK_SolarSensorIncrease], -1}, - {"HKJoy_FrameStep", 0, &HKJoyMapping[HK_FrameStep], -1}, + {"HKJoy_Lid", 0, &HKJoyMapping[HK_Lid], -1, true}, + {"HKJoy_Mic", 0, &HKJoyMapping[HK_Mic], -1, true}, + {"HKJoy_Pause", 0, &HKJoyMapping[HK_Pause], -1, true}, + {"HKJoy_Reset", 0, &HKJoyMapping[HK_Reset], -1, true}, + {"HKJoy_FastForward", 0, &HKJoyMapping[HK_FastForward], -1, true}, + {"HKJoy_FastForwardToggle", 0, &HKJoyMapping[HK_FastForwardToggle], -1, true}, + {"HKJoy_FullscreenToggle", 0, &HKJoyMapping[HK_FullscreenToggle], -1, true}, + {"HKJoy_SwapScreens", 0, &HKJoyMapping[HK_SwapScreens], -1, true}, + {"HKJoy_SolarSensorDecrease", 0, &HKJoyMapping[HK_SolarSensorDecrease], -1, true}, + {"HKJoy_SolarSensorIncrease", 0, &HKJoyMapping[HK_SolarSensorIncrease], -1, true}, + {"HKJoy_FrameStep", 0, &HKJoyMapping[HK_FrameStep], -1, true}, - {"JoystickID", 0, &JoystickID, 0}, + {"JoystickID", 0, &JoystickID, 0, true}, - {"WindowWidth", 0, &WindowWidth, 256}, - {"WindowHeight", 0, &WindowHeight, 384}, - {"WindowMax", 1, &WindowMaximized, false}, + {"WindowWidth", 0, &WindowWidth, 256, true}, + {"WindowHeight", 0, &WindowHeight, 384, true}, + {"WindowMax", 1, &WindowMaximized, false, true}, - {"ScreenRotation", 0, &ScreenRotation, 0}, - {"ScreenGap", 0, &ScreenGap, 0}, - {"ScreenLayout", 0, &ScreenLayout, 0}, - {"ScreenSwap", 1, &ScreenSwap, false}, - {"ScreenSizing", 0, &ScreenSizing, 0}, - {"IntegerScaling", 1, &IntegerScaling, false}, - {"ScreenAspectTop",0, &ScreenAspectTop,0}, - {"ScreenAspectBot",0, &ScreenAspectBot,0}, - {"ScreenFilter", 1, &ScreenFilter, true}, + {"ScreenRotation", 0, &ScreenRotation, 0, true}, + {"ScreenGap", 0, &ScreenGap, 0, true}, + {"ScreenLayout", 0, &ScreenLayout, 0, true}, + {"ScreenSwap", 1, &ScreenSwap, false, true}, + {"ScreenSizing", 0, &ScreenSizing, 0, true}, + {"IntegerScaling", 1, &IntegerScaling, false, true}, + {"ScreenAspectTop",0, &ScreenAspectTop,0, true}, + {"ScreenAspectBot",0, &ScreenAspectBot,0, true}, + {"ScreenFilter", 1, &ScreenFilter, true, true}, - {"ScreenUseGL", 1, &ScreenUseGL, false}, - {"ScreenVSync", 1, &ScreenVSync, false}, - {"ScreenVSyncInterval", 0, &ScreenVSyncInterval, 1}, + {"ScreenUseGL", 1, &ScreenUseGL, false, false}, + {"ScreenVSync", 1, &ScreenVSync, false, false}, + {"ScreenVSyncInterval", 0, &ScreenVSyncInterval, 1, false}, - {"3DRenderer", 0, &_3DRenderer, 0}, - {"Threaded3D", 1, &Threaded3D, true}, + {"3DRenderer", 0, &_3DRenderer, 0, false}, + {"Threaded3D", 1, &Threaded3D, true, false}, - {"GL_ScaleFactor", 0, &GL_ScaleFactor, 1}, - {"GL_BetterPolygons", 1, &GL_BetterPolygons, false}, + {"GL_ScaleFactor", 0, &GL_ScaleFactor, 1, false}, + {"GL_BetterPolygons", 1, &GL_BetterPolygons, false, false}, - {"LimitFPS", 1, &LimitFPS, true}, + {"LimitFPS", 1, &LimitFPS, true, false}, {"AudioSync", 1, &AudioSync, false}, - {"ShowOSD", 1, &ShowOSD, true}, + {"ShowOSD", 1, &ShowOSD, true, false}, - {"ConsoleType", 0, &ConsoleType, 0}, - {"DirectBoot", 1, &DirectBoot, true}, + {"ConsoleType", 0, &ConsoleType, 0, false}, + {"DirectBoot", 1, &DirectBoot, true, false}, #ifdef JIT_ENABLED - {"JIT_Enable", 1, &JIT_Enable, false}, - {"JIT_MaxBlockSize", 0, &JIT_MaxBlockSize, 32}, - {"JIT_BranchOptimisations", 1, &JIT_BranchOptimisations, true}, - {"JIT_LiteralOptimisations", 1, &JIT_LiteralOptimisations, true}, + {"JIT_Enable", 1, &JIT_Enable, false, false}, + {"JIT_MaxBlockSize", 0, &JIT_MaxBlockSize, 32, false}, + {"JIT_BranchOptimisations", 1, &JIT_BranchOptimisations, true, false}, + {"JIT_LiteralOptimisations", 1, &JIT_LiteralOptimisations, true, false}, #ifdef __APPLE__ - {"JIT_FastMemory", 1, &JIT_FastMemory, false}, + {"JIT_FastMemory", 1, &JIT_FastMemory, false, false}, #else - {"JIT_FastMemory", 1, &JIT_FastMemory, true}, + {"JIT_FastMemory", 1, &JIT_FastMemory, true, false}, #endif #endif - {"ExternalBIOSEnable", 1, &ExternalBIOSEnable, false}, + {"ExternalBIOSEnable", 1, &ExternalBIOSEnable, false, false}, - {"BIOS9Path", 2, &BIOS9Path, (std::string)""}, - {"BIOS7Path", 2, &BIOS7Path, (std::string)""}, - {"FirmwarePath", 2, &FirmwarePath, (std::string)""}, + {"BIOS9Path", 2, &BIOS9Path, (std::string)"", false}, + {"BIOS7Path", 2, &BIOS7Path, (std::string)"", false}, + {"FirmwarePath", 2, &FirmwarePath, (std::string)"", false}, - {"DSiBIOS9Path", 2, &DSiBIOS9Path, (std::string)""}, - {"DSiBIOS7Path", 2, &DSiBIOS7Path, (std::string)""}, - {"DSiFirmwarePath", 2, &DSiFirmwarePath, (std::string)""}, - {"DSiNANDPath", 2, &DSiNANDPath, (std::string)""}, + {"DSiBIOS9Path", 2, &DSiBIOS9Path, (std::string)"", false}, + {"DSiBIOS7Path", 2, &DSiBIOS7Path, (std::string)"", false}, + {"DSiFirmwarePath", 2, &DSiFirmwarePath, (std::string)"", false}, + {"DSiNANDPath", 2, &DSiNANDPath, (std::string)"", false}, - {"DLDIEnable", 1, &DLDIEnable, false}, - {"DLDISDPath", 2, &DLDISDPath, (std::string)"dldi.bin"}, - {"DLDISize", 0, &DLDISize, 0}, - {"DLDIReadOnly", 1, &DLDIReadOnly, false}, - {"DLDIFolderSync", 1, &DLDIFolderSync, false}, - {"DLDIFolderPath", 2, &DLDIFolderPath, (std::string)""}, + {"DLDIEnable", 1, &DLDIEnable, false, false}, + {"DLDISDPath", 2, &DLDISDPath, (std::string)"dldi.bin", false}, + {"DLDISize", 0, &DLDISize, 0, false}, + {"DLDIReadOnly", 1, &DLDIReadOnly, false, false}, + {"DLDIFolderSync", 1, &DLDIFolderSync, false, false}, + {"DLDIFolderPath", 2, &DLDIFolderPath, (std::string)"", false}, - {"DSiSDEnable", 1, &DSiSDEnable, false}, - {"DSiSDPath", 2, &DSiSDPath, (std::string)"dsisd.bin"}, - {"DSiSDSize", 0, &DSiSDSize, 0}, - {"DSiSDReadOnly", 1, &DSiSDReadOnly, false}, - {"DSiSDFolderSync", 1, &DSiSDFolderSync, false}, - {"DSiSDFolderPath", 2, &DSiSDFolderPath, (std::string)""}, + {"DSiSDEnable", 1, &DSiSDEnable, false, false}, + {"DSiSDPath", 2, &DSiSDPath, (std::string)"dsisd.bin", false}, + {"DSiSDSize", 0, &DSiSDSize, 0, false}, + {"DSiSDReadOnly", 1, &DSiSDReadOnly, false, false}, + {"DSiSDFolderSync", 1, &DSiSDFolderSync, false, false}, + {"DSiSDFolderPath", 2, &DSiSDFolderPath, (std::string)"", false}, - {"FirmwareOverrideSettings", 1, &FirmwareOverrideSettings, false}, - {"FirmwareUsername", 2, &FirmwareUsername, (std::string)"melonDS"}, - {"FirmwareLanguage", 0, &FirmwareLanguage, 1}, - {"FirmwareBirthdayMonth", 0, &FirmwareBirthdayMonth, 1}, - {"FirmwareBirthdayDay", 0, &FirmwareBirthdayDay, 1}, - {"FirmwareFavouriteColour", 0, &FirmwareFavouriteColour, 0}, - {"FirmwareMessage", 2, &FirmwareMessage, (std::string)""}, - {"FirmwareMAC", 2, &FirmwareMAC, (std::string)""}, - {"RandomizeMAC", 1, &RandomizeMAC, false}, + {"FirmwareOverrideSettings", 1, &FirmwareOverrideSettings, false, true}, + {"FirmwareUsername", 2, &FirmwareUsername, (std::string)"melonDS", true}, + {"FirmwareLanguage", 0, &FirmwareLanguage, 1, true}, + {"FirmwareBirthdayMonth", 0, &FirmwareBirthdayMonth, 1, true}, + {"FirmwareBirthdayDay", 0, &FirmwareBirthdayDay, 1, true}, + {"FirmwareFavouriteColour", 0, &FirmwareFavouriteColour, 0, true}, + {"FirmwareMessage", 2, &FirmwareMessage, (std::string)"", true}, + {"FirmwareMAC", 2, &FirmwareMAC, (std::string)"", true}, - {"SockBindAnyAddr", 1, &SocketBindAnyAddr, false}, - {"LANDevice", 2, &LANDevice, (std::string)""}, - {"DirectLAN", 1, &DirectLAN, false}, + {"MPAudioMode", 0, &MPAudioMode, 1, false}, + {"MPRecvTimeout", 0, &MPRecvTimeout, 25, false}, - {"SavStaRelocSRAM", 1, &SavestateRelocSRAM, false}, + {"LANDevice", 2, &LANDevice, (std::string)"", false}, + {"DirectLAN", 1, &DirectLAN, false, false}, - {"AudioInterp", 0, &AudioInterp, 0}, - {"AudioBitrate", 0, &AudioBitrate, 0}, - {"AudioVolume", 0, &AudioVolume, 256}, - {"MicInputType", 0, &MicInputType, 1}, - {"MicWavPath", 2, &MicWavPath, (std::string)""}, + {"SavStaRelocSRAM", 1, &SavestateRelocSRAM, false, false}, - {"LastROMFolder", 2, &LastROMFolder, (std::string)""}, + {"AudioInterp", 0, &AudioInterp, 0, false}, + {"AudioBitrate", 0, &AudioBitrate, 0, false}, + {"AudioVolume", 0, &AudioVolume, 256, true}, + {"MicInputType", 0, &MicInputType, 1, false}, + {"MicWavPath", 2, &MicWavPath, (std::string)"", false}, - {"RecentROM_0", 2, &RecentROMList[0], (std::string)""}, - {"RecentROM_1", 2, &RecentROMList[1], (std::string)""}, - {"RecentROM_2", 2, &RecentROMList[2], (std::string)""}, - {"RecentROM_3", 2, &RecentROMList[3], (std::string)""}, - {"RecentROM_4", 2, &RecentROMList[4], (std::string)""}, - {"RecentROM_5", 2, &RecentROMList[5], (std::string)""}, - {"RecentROM_6", 2, &RecentROMList[6], (std::string)""}, - {"RecentROM_7", 2, &RecentROMList[7], (std::string)""}, - {"RecentROM_8", 2, &RecentROMList[8], (std::string)""}, - {"RecentROM_9", 2, &RecentROMList[9], (std::string)""}, + {"LastROMFolder", 2, &LastROMFolder, (std::string)"", true}, - {"SaveFilePath", 2, &SaveFilePath, (std::string)""}, - {"SavestatePath", 2, &SavestatePath, (std::string)""}, - {"CheatFilePath", 2, &CheatFilePath, (std::string)""}, + {"RecentROM_0", 2, &RecentROMList[0], (std::string)"", true}, + {"RecentROM_1", 2, &RecentROMList[1], (std::string)"", true}, + {"RecentROM_2", 2, &RecentROMList[2], (std::string)"", true}, + {"RecentROM_3", 2, &RecentROMList[3], (std::string)"", true}, + {"RecentROM_4", 2, &RecentROMList[4], (std::string)"", true}, + {"RecentROM_5", 2, &RecentROMList[5], (std::string)"", true}, + {"RecentROM_6", 2, &RecentROMList[6], (std::string)"", true}, + {"RecentROM_7", 2, &RecentROMList[7], (std::string)"", true}, + {"RecentROM_8", 2, &RecentROMList[8], (std::string)"", true}, + {"RecentROM_9", 2, &RecentROMList[9], (std::string)"", true}, - {"EnableCheats", 1, &EnableCheats, false}, + {"SaveFilePath", 2, &SaveFilePath, (std::string)"", true}, + {"SavestatePath", 2, &SavestatePath, (std::string)"", true}, + {"CheatFilePath", 2, &CheatFilePath, (std::string)"", true}, - {"MouseHide", 1, &MouseHide, false}, - {"MouseHideSeconds", 0, &MouseHideSeconds, 5}, - {"PauseLostFocus", 1, &PauseLostFocus, false}, + {"EnableCheats", 1, &EnableCheats, false, true}, - {"DSBatteryLevelOkay", 1, &DSBatteryLevelOkay, true}, - {"DSiBatteryLevel", 0, &DSiBatteryLevel, 0xF}, - {"DSiBatteryCharging", 1, &DSiBatteryCharging, true}, + {"MouseHide", 1, &MouseHide, false, false}, + {"MouseHideSeconds", 0, &MouseHideSeconds, 5, false}, + {"PauseLostFocus", 1, &PauseLostFocus, false, false}, + {"DSBatteryLevelOkay", 1, &DSBatteryLevelOkay, true, true}, + {"DSiBatteryLevel", 0, &DSiBatteryLevel, 0xF, true}, + {"DSiBatteryCharging", 1, &DSiBatteryCharging, true, true}, // TODO!! // we need a more elegant way to deal with this - {"Camera0_InputType", 0, &Camera[0].InputType, 0}, - {"Camera0_ImagePath", 2, &Camera[0].ImagePath, (std::string)""}, - {"Camera0_CamDeviceName", 2, &Camera[0].CamDeviceName, (std::string)""}, - {"Camera0_XFlip", 1, &Camera[0].XFlip, false}, - {"Camera1_InputType", 0, &Camera[1].InputType, 0}, - {"Camera1_ImagePath", 2, &Camera[1].ImagePath, (std::string)""}, - {"Camera1_CamDeviceName", 2, &Camera[1].CamDeviceName, (std::string)""}, - {"Camera1_XFlip", 1, &Camera[1].XFlip, false}, + {"Camera0_InputType", 0, &Camera[0].InputType, 0, false}, + {"Camera0_ImagePath", 2, &Camera[0].ImagePath, (std::string)"", false}, + {"Camera0_CamDeviceName", 2, &Camera[0].CamDeviceName, (std::string)"", false}, + {"Camera0_XFlip", 1, &Camera[0].XFlip, false, false}, + {"Camera1_InputType", 0, &Camera[1].InputType, 0, false}, + {"Camera1_ImagePath", 2, &Camera[1].ImagePath, (std::string)"", false}, + {"Camera1_CamDeviceName", 2, &Camera[1].CamDeviceName, (std::string)"", false}, + {"Camera1_XFlip", 1, &Camera[1].XFlip, false, false}, - {"", -1, nullptr, 0} + {"", -1, nullptr, 0, false} }; -void Load() +void LoadFile(int inst) { - ConfigEntry* entry = &ConfigFile[0]; - for (;;) + FILE* f; + if (inst > 0) { - if (!entry->Value) break; - - switch (entry->Type) - { - case 0: *(int*)entry->Value = std::get(entry->Default); break; - case 1: *(bool*)entry->Value = std::get(entry->Default); break; - case 2: *(std::string*)entry->Value = std::get(entry->Default); break; - } - - entry++; + char name[100] = {0}; + snprintf(name, 99, kUniqueConfigFile, inst+1); + f = Platform::OpenLocalFile(name, "r"); } + else + f = Platform::OpenLocalFile(kConfigFile, "r"); - FILE* f = Platform::OpenLocalFile(kConfigFile, "r"); if (!f) return; char linebuf[1024]; @@ -362,13 +358,13 @@ void Load() entryname[31] = '\0'; if (ret < 2) continue; - ConfigEntry* entry = &ConfigFile[0]; - for (;;) + for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) { - if (!entry->Value) break; - if (!strncmp(entry->Name, entryname, 32)) { + if ((inst > 0) && (!entry->InstanceUnique)) + break; + switch (entry->Type) { case 0: *(int*)entry->Value = strtol(entryval, NULL, 10); break; @@ -378,23 +374,52 @@ void Load() break; } - - entry++; } } fclose(f); } +void Load() +{ + + for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) + { + switch (entry->Type) + { + case 0: *(int*)entry->Value = std::get(entry->Default); break; + case 1: *(bool*)entry->Value = std::get(entry->Default); break; + case 2: *(std::string*)entry->Value = std::get(entry->Default); break; + } + } + + LoadFile(0); + + int inst = Platform::InstanceID(); + if (inst > 0) + LoadFile(inst); +} + void Save() { - FILE* f = Platform::OpenLocalFile(kConfigFile, "w"); + int inst = Platform::InstanceID(); + + FILE* f; + if (inst > 0) + { + char name[100] = {0}; + snprintf(name, 99, kUniqueConfigFile, inst+1); + f = Platform::OpenLocalFile(name, "w"); + } + else + f = Platform::OpenLocalFile(kConfigFile, "w"); + if (!f) return; - ConfigEntry* entry = &ConfigFile[0]; - for (;;) + for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) { - if (!entry->Value) break; + if ((inst > 0) && (!entry->InstanceUnique)) + continue; switch (entry->Type) { @@ -402,8 +427,6 @@ void Save() case 1: fprintf(f, "%s=%d\r\n", entry->Name, *(bool*)entry->Value ? 1:0); break; case 2: fprintf(f, "%s=%s\r\n", entry->Name, (*(std::string*)entry->Value).c_str()); break; } - - entry++; } fclose(f); diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h index 7a0b0ca0..6ccae5f4 100644 --- a/src/frontend/qt_sdl/Config.h +++ b/src/frontend/qt_sdl/Config.h @@ -58,6 +58,7 @@ struct ConfigEntry int Type; // 0=int 1=bool 2=string void* Value; // pointer to the value variable std::variant Default; + bool InstanceUnique; // whether the setting can exist individually for each instance in multiplayer }; struct CameraConfig @@ -149,9 +150,10 @@ extern int FirmwareBirthdayDay; extern int FirmwareFavouriteColour; extern std::string FirmwareMessage; extern std::string FirmwareMAC; -extern bool RandomizeMAC; -extern bool SocketBindAnyAddr; +extern int MPAudioMode; +extern int MPRecvTimeout; + extern std::string LANDevice; extern bool DirectLAN; diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp index 754cc8a5..ffca5676 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp @@ -18,6 +18,7 @@ #include +#include "Platform.h" #include "Config.h" #include "FirmwareSettingsDialog.h" @@ -64,10 +65,14 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent ui->overrideFirmwareBox->setChecked(Config::FirmwareOverrideSettings); ui->txtMAC->setText(QString::fromStdString(Config::FirmwareMAC)); - ui->cbRandomizeMAC->setChecked(Config::RandomizeMAC); on_overrideFirmwareBox_toggled(); - on_cbRandomizeMAC_toggled(); + + int inst = Platform::InstanceID(); + if (inst > 0) + ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1)); + else + ui->lblInstanceNum->hide(); } FirmwareSettingsDialog::~FirmwareSettingsDialog() @@ -135,7 +140,6 @@ void FirmwareSettingsDialog::done(int r) std::string newMessage = ui->messageEdit->text().toStdString(); std::string newMAC = ui->txtMAC->text().toStdString(); - bool newRandomizeMAC = ui->cbRandomizeMAC->isChecked(); if ( newOverride != Config::FirmwareOverrideSettings || newName != Config::FirmwareUsername @@ -144,8 +148,7 @@ void FirmwareSettingsDialog::done(int r) || newBirthdayDay != Config::FirmwareBirthdayDay || newBirthdayMonth != Config::FirmwareBirthdayMonth || newMessage != Config::FirmwareMessage - || newMAC != Config::FirmwareMAC - || newRandomizeMAC != Config::RandomizeMAC) + || newMAC != Config::FirmwareMAC) { if (RunningSomething && QMessageBox::warning(this, "Reset necessary to apply changes", @@ -163,7 +166,6 @@ void FirmwareSettingsDialog::done(int r) Config::FirmwareMessage = newMessage; Config::FirmwareMAC = newMAC; - Config::RandomizeMAC = newRandomizeMAC; Config::Save(); @@ -210,9 +212,3 @@ void FirmwareSettingsDialog::on_overrideFirmwareBox_toggled() ui->grpUserSettings->setDisabled(disable); ui->grpWifiSettings->setDisabled(disable); } - -void FirmwareSettingsDialog::on_cbRandomizeMAC_toggled() -{ - bool disable = ui->cbRandomizeMAC->isChecked(); - ui->txtMAC->setDisabled(disable); -} diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.h b/src/frontend/qt_sdl/FirmwareSettingsDialog.h index 97bf5c07..b3695e2f 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.h +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.h @@ -124,7 +124,6 @@ private slots: void on_cbxBirthdayMonth_currentIndexChanged(int idx); void on_overrideFirmwareBox_toggled(); - void on_cbRandomizeMAC_toggled(); private: bool verifyMAC(); diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.ui b/src/frontend/qt_sdl/FirmwareSettingsDialog.ui index a97689cf..37146296 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.ui +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.ui @@ -7,7 +7,7 @@ 0 0 511 - 342 + 357 @@ -23,6 +23,13 @@ QLayout::SetFixedSize + + + + Configuring settings for instance X + + + @@ -144,9 +151,9 @@ - + - Randomize + (leave empty to use default MAC) diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp index 697e983a..92a01867 100644 --- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp +++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp @@ -24,6 +24,7 @@ #include #include "types.h" +#include "Platform.h" #include "Config.h" #include "MapButton.h" @@ -123,6 +124,12 @@ InputConfigDialog::InputConfigDialog(QWidget* parent) : QDialog(parent), ui(new } setupKeypadPage(); + + int inst = Platform::InstanceID(); + if (inst > 0) + ui->lblInstanceNum->setText(QString("Configuring mappings for instance %1").arg(inst+1)); + else + ui->lblInstanceNum->hide(); } InputConfigDialog::~InputConfigDialog() diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui index 15cb683d..0db61b15 100644 --- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui +++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui @@ -7,7 +7,7 @@ 0 0 770 - 719 + 678 @@ -20,7 +20,7 @@ QLayout::SetFixedSize - + Qt::Horizontal @@ -30,49 +30,7 @@ - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Joystick: - - - - - - - - 0 - 0 - - - - <html><head/><body><p>Selects which joystick will be used for joystick input, if any is present.</p></body></html> - - - - - - + 0 @@ -167,7 +125,7 @@ - 76 + 70 0 @@ -258,7 +216,7 @@ - 76 + 70 0 @@ -384,7 +342,7 @@ - 76 + 70 0 @@ -464,7 +422,7 @@ - 76 + 70 0 @@ -523,7 +481,7 @@ - 76 + 70 0 @@ -615,7 +573,7 @@ - 76 + 70 0 @@ -698,7 +656,7 @@ - 76 + 70 0 @@ -757,7 +715,7 @@ - 76 + 70 0 @@ -882,7 +840,7 @@ - 76 + 70 0 @@ -962,7 +920,7 @@ - 76 + 70 0 @@ -1021,7 +979,7 @@ - 76 + 70 0 @@ -1113,7 +1071,7 @@ - 76 + 70 0 @@ -1289,7 +1247,7 @@ - 76 + 70 0 @@ -1441,7 +1399,7 @@ - 76 + 70 0 @@ -1521,7 +1479,7 @@ - 76 + 70 0 @@ -1580,7 +1538,7 @@ - 76 + 70 0 @@ -1672,7 +1630,7 @@ - 76 + 70 0 @@ -1814,7 +1772,7 @@ - 76 + 70 0 @@ -1894,7 +1852,7 @@ - 76 + 70 0 @@ -1953,7 +1911,7 @@ - 76 + 70 0 @@ -2045,7 +2003,7 @@ - 76 + 70 0 @@ -2128,7 +2086,7 @@ - 76 + 70 0 @@ -2187,7 +2145,7 @@ - 76 + 70 0 @@ -2251,7 +2209,7 @@ - 76 + 70 0 @@ -2321,6 +2279,55 @@ + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Joystick: + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Selects which joystick will be used for joystick input, if any is present.</p></body></html> + + + + + + + + + Configuring mappings for instance X + + + diff --git a/src/frontend/qt_sdl/InputConfig/MapButton.h b/src/frontend/qt_sdl/InputConfig/MapButton.h index ca210f9f..afefed79 100644 --- a/src/frontend/qt_sdl/InputConfig/MapButton.h +++ b/src/frontend/qt_sdl/InputConfig/MapButton.h @@ -163,6 +163,7 @@ public: setCheckable(true); setText(mappingText()); + setFocusPolicy(Qt::StrongFocus); //Fixes binding keys in macOS connect(this, &JoyMapButton::clicked, this, &JoyMapButton::onClick); @@ -245,19 +246,21 @@ protected: Sint16 axisval = SDL_JoystickGetAxis(joy, i); int diff = abs(axisval - axesRest[i]); - if (axesRest[i] < -16384 && axisval >= 0) + if (diff >= 16384) { - *mapping = (oldmap & 0xFFFF) | 0x10000 | (2 << 20) | (i << 24); - click(); - return; - } - else if (diff > 16384) - { - int axistype; - if (axisval > 0) axistype = 0; - else axistype = 1; + if (axesRest[i] < -16384) // Trigger + { + *mapping = (oldmap & 0xFFFF) | 0x10000 | (2 << 20) | (i << 24); + } + else // Analog stick + { + int axistype; + if (axisval > 0) axistype = 0; + else axistype = 1; + + *mapping = (oldmap & 0xFFFF) | 0x10000 | (axistype << 20) | (i << 24); + } - *mapping = (oldmap & 0xFFFF) | 0x10000 | (axistype << 20) | (i << 24); click(); return; } @@ -352,4 +355,4 @@ private: int axesRest[16]; }; -#endif // MAPBUTTON_H \ No newline at end of file +#endif // MAPBUTTON_H diff --git a/src/frontend/qt_sdl/LAN_PCap.cpp b/src/frontend/qt_sdl/LAN_PCap.cpp index 57223eb8..86c218f1 100644 --- a/src/frontend/qt_sdl/LAN_PCap.cpp +++ b/src/frontend/qt_sdl/LAN_PCap.cpp @@ -114,6 +114,12 @@ bool TryLoadPCap(void* lib) bool Init(bool open_adapter) { + PCapAdapter = NULL; + PacketLen = 0; + RXNum = 0; + + NumAdapters = 0; + // TODO: how to deal with cases where an adapter is unplugged or changes config?? if (!PCapLib) { @@ -142,12 +148,6 @@ bool Init(bool open_adapter) } } - PCapAdapter = NULL; - PacketLen = 0; - RXNum = 0; - - NumAdapters = 0; - char errbuf[PCAP_ERRBUF_SIZE]; int ret; diff --git a/src/frontend/qt_sdl/LocalMP.cpp b/src/frontend/qt_sdl/LocalMP.cpp new file mode 100644 index 00000000..fb7ef7ad --- /dev/null +++ b/src/frontend/qt_sdl/LocalMP.cpp @@ -0,0 +1,634 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include +#include +#include + +#ifdef __WIN32__ + #include +#else + #include + #include + #include + #ifdef __APPLE__ + #include "sem_timedwait.h" + #endif +#endif + +#include +#include + +#include "Config.h" +#include "LocalMP.h" + + +namespace LocalMP +{ + +u32 MPUniqueID; +u8 PacketBuffer[2048]; + +struct MPQueueHeader +{ + u16 NumInstances; + u16 InstanceBitmask; // bitmask of all instances present + u16 ConnectedBitmask; // bitmask of which instances are ready to send/receive packets + u32 PacketWriteOffset; + u32 ReplyWriteOffset; + u16 MPHostInstanceID; // instance ID from which the last CMD frame was sent + u16 MPReplyBitmask; // bitmask of which clients replied in time +}; + +struct MPPacketHeader +{ + u32 Magic; + u32 SenderID; + u32 Type; // 0=regular 1=CMD 2=reply 3=ack + u32 Length; + u64 Timestamp; +}; + +struct MPSync +{ + u32 Magic; + u32 SenderID; + u16 ClientMask; + u16 Type; + u64 Timestamp; +}; + +QSharedMemory* MPQueue; +int InstanceID; +u32 PacketReadOffset; +u32 ReplyReadOffset; + +const u32 kQueueSize = 0x20000; +const u32 kMaxFrameSize = 0x800; +const u32 kPacketStart = sizeof(MPQueueHeader); +const u32 kReplyStart = kQueueSize / 2; +const u32 kPacketEnd = kReplyStart; +const u32 kReplyEnd = kQueueSize; + +int RecvTimeout; + +int LastHostID; + + +// we need to come up with our own abstraction layer for named semaphores +// because QSystemSemaphore doesn't support waiting with a timeout +// and, as such, is unsuitable to our needs + +#ifdef __WIN32__ + +bool SemInited[32]; +HANDLE SemPool[32]; + +void SemPoolInit() +{ + for (int i = 0; i < 32; i++) + { + SemPool[i] = INVALID_HANDLE_VALUE; + SemInited[i] = false; + } +} + +void SemDeinit(int num); + +void SemPoolDeinit() +{ + for (int i = 0; i < 32; i++) + SemDeinit(i); +} + +bool SemInit(int num) +{ + if (SemInited[num]) + return true; + + char semname[64]; + sprintf(semname, "Local\\melonNIFI_Sem%02d", num); + + HANDLE sem = CreateSemaphore(nullptr, 0, 64, semname); + SemPool[num] = sem; + SemInited[num] = true; + return sem != INVALID_HANDLE_VALUE; +} + +void SemDeinit(int num) +{ + if (SemPool[num] != INVALID_HANDLE_VALUE) + { + CloseHandle(SemPool[num]); + SemPool[num] = INVALID_HANDLE_VALUE; + } + + SemInited[num] = false; +} + +bool SemPost(int num) +{ + SemInit(num); + return ReleaseSemaphore(SemPool[num], 1, nullptr) != 0; +} + +bool SemWait(int num, int timeout) +{ + return WaitForSingleObject(SemPool[num], timeout) == WAIT_OBJECT_0; +} + +void SemReset(int num) +{ + while (WaitForSingleObject(SemPool[num], 0) == WAIT_OBJECT_0); +} + +#else + +bool SemInited[32]; +sem_t* SemPool[32]; + +void SemPoolInit() +{ + for (int i = 0; i < 32; i++) + { + SemPool[i] = SEM_FAILED; + SemInited[i] = false; + } +} + +void SemDeinit(int num); + +void SemPoolDeinit() +{ + for (int i = 0; i < 32; i++) + SemDeinit(i); +} + +bool SemInit(int num) +{ + if (SemInited[num]) + return true; + + char semname[64]; + sprintf(semname, "/melonNIFI_Sem%02d", num); + + sem_t* sem = sem_open(semname, O_CREAT, 0644, 0); + SemPool[num] = sem; + SemInited[num] = true; + return sem != SEM_FAILED; +} + +void SemDeinit(int num) +{ + if (SemPool[num] != SEM_FAILED) + { + sem_close(SemPool[num]); + SemPool[num] = SEM_FAILED; + } + + SemInited[num] = false; +} + +bool SemPost(int num) +{ + SemInit(num); + return sem_post(SemPool[num]) == 0; +} + +bool SemWait(int num, int timeout) +{ + if (!timeout) + return sem_trywait(SemPool[num]) == 0; + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_nsec += timeout * 1000000; + long sec = ts.tv_nsec / 1000000000; + ts.tv_nsec -= sec * 1000000000; + ts.tv_sec += sec; + + return sem_timedwait(SemPool[num], &ts) == 0; +} + +void SemReset(int num) +{ + while (sem_trywait(SemPool[num]) == 0); +} + +#endif + + +bool Init() +{ + MPQueue = new QSharedMemory("melonNIFI"); + + if (!MPQueue->attach()) + { + printf("MP sharedmem doesn't exist. creating\n"); + if (!MPQueue->create(kQueueSize)) + { + printf("MP sharedmem create failed :(\n"); + return false; + } + + MPQueue->lock(); + memset(MPQueue->data(), 0, MPQueue->size()); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + header->PacketWriteOffset = kPacketStart; + header->ReplyWriteOffset = kReplyStart; + MPQueue->unlock(); + } + + MPQueue->lock(); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + + u16 mask = header->InstanceBitmask; + for (int i = 0; i < 16; i++) + { + if (!(mask & (1<InstanceBitmask |= (1<ConnectedBitmask |= (1 << i); + break; + } + } + header->NumInstances++; + + PacketReadOffset = header->PacketWriteOffset; + ReplyReadOffset = header->ReplyWriteOffset; + + MPQueue->unlock(); + + // prepare semaphores + // semaphores 0-15: regular frames; semaphore I is posted when instance I needs to process a new frame + // semaphores 16-31: MP replies; semaphore I is posted when instance I needs to process a new MP reply + + SemPoolInit(); + SemInit(InstanceID); + SemInit(16+InstanceID); + + LastHostID = -1; + + printf("MP comm init OK, instance ID %d\n", InstanceID); + + RecvTimeout = 25; + + return true; +} + +void DeInit() +{ + MPQueue->lock(); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + header->ConnectedBitmask &= ~(1 << InstanceID); + header->InstanceBitmask &= ~(1 << InstanceID); + header->NumInstances--; + MPQueue->unlock(); + + SemPoolDeinit(); + + MPQueue->detach(); + delete MPQueue; +} + +void SetRecvTimeout(int timeout) +{ + RecvTimeout = timeout; +} + +void Begin() +{ + MPQueue->lock(); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + PacketReadOffset = header->PacketWriteOffset; + ReplyReadOffset = header->ReplyWriteOffset; + SemReset(InstanceID); + SemReset(16+InstanceID); + header->ConnectedBitmask |= (1 << InstanceID); + MPQueue->unlock(); +} + +void End() +{ + MPQueue->lock(); + MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); + //SemReset(InstanceID); + //SemReset(16+InstanceID); + header->ConnectedBitmask &= ~(1 << InstanceID); + MPQueue->unlock(); +} + +void FIFORead(int fifo, void* buf, int len) +{ + u8* data = (u8*)MPQueue->data(); + + u32 offset, start, end; + if (fifo == 0) + { + offset = PacketReadOffset; + start = kPacketStart; + end = kPacketEnd; + } + else + { + offset = ReplyReadOffset; + start = kReplyStart; + end = kReplyEnd; + } + + if ((offset + len) >= end) + { + u32 part1 = end - offset; + memcpy(buf, &data[offset], part1); + memcpy(&((u8*)buf)[part1], &data[start], len - part1); + offset = start + len - part1; + } + else + { + memcpy(buf, &data[offset], len); + offset += len; + } + + if (fifo == 0) PacketReadOffset = offset; + else ReplyReadOffset = offset; +} + +void FIFOWrite(int fifo, void* buf, int len) +{ + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + + u32 offset, start, end; + if (fifo == 0) + { + offset = header->PacketWriteOffset; + start = kPacketStart; + end = kPacketEnd; + } + else + { + offset = header->ReplyWriteOffset; + start = kReplyStart; + end = kReplyEnd; + } + + if ((offset + len) >= end) + { + u32 part1 = end - offset; + memcpy(&data[offset], buf, part1); + memcpy(&data[start], &((u8*)buf)[part1], len - part1); + offset = start + len - part1; + } + else + { + memcpy(&data[offset], buf, len); + offset += len; + } + + if (fifo == 0) header->PacketWriteOffset = offset; + else header->ReplyWriteOffset = offset; +} + +int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp) +{ + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + + u16 mask = header->ConnectedBitmask; + + // TODO: check if the FIFO is full! + + MPPacketHeader pktheader; + pktheader.Magic = 0x4946494E; + pktheader.SenderID = InstanceID; + pktheader.Type = type; + pktheader.Length = len; + pktheader.Timestamp = timestamp; + + type &= 0xFFFF; + int nfifo = (type == 2) ? 1 : 0; + FIFOWrite(nfifo, &pktheader, sizeof(pktheader)); + if (len) + FIFOWrite(nfifo, packet, len); + + if (type == 1) + { + // NOTE: this is not guarded against, say, multiple multiplay games happening on the same machine + // we would need to pass the packet's SenderID through the wifi module for that + header->MPHostInstanceID = InstanceID; + header->MPReplyBitmask = 0; + ReplyReadOffset = header->ReplyWriteOffset; + SemReset(16 + InstanceID); + } + else if (type == 2) + { + header->MPReplyBitmask |= (1 << InstanceID); + } + + MPQueue->unlock(); + + if (type == 2) + { + SemPost(16 + header->MPHostInstanceID); + } + else + { + for (int i = 0; i < 16; i++) + { + if (mask & (1<lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + + MPPacketHeader pktheader; + FIFORead(0, &pktheader, sizeof(pktheader)); + + if (pktheader.Magic != 0x4946494E) + { + printf("PACKET FIFO OVERFLOW\n"); + PacketReadOffset = header->PacketWriteOffset; + SemReset(InstanceID); + MPQueue->unlock(); + return 0; + } + + if (pktheader.SenderID == InstanceID) + { + // skip this packet + PacketReadOffset += pktheader.Length; + if (PacketReadOffset >= kPacketEnd) + PacketReadOffset += kPacketStart - kPacketEnd; + + MPQueue->unlock(); + continue; + } + + if (pktheader.Length) + { + FIFORead(0, packet, pktheader.Length); + + if (pktheader.Type == 1) + LastHostID = pktheader.SenderID; + } + + if (timestamp) *timestamp = pktheader.Timestamp; + MPQueue->unlock(); + return pktheader.Length; + } +} + +int SendPacket(u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(0, packet, len, timestamp); +} + +int RecvPacket(u8* packet, u64* timestamp) +{ + return RecvPacketGeneric(packet, false, timestamp); +} + + +int SendCmd(u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(1, packet, len, timestamp); +} + +int SendReply(u8* packet, int len, u64 timestamp, u16 aid) +{ + return SendPacketGeneric(2 | (aid<<16), packet, len, timestamp); +} + +int SendAck(u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(3, packet, len, timestamp); +} + +int RecvHostPacket(u8* packet, u64* timestamp) +{ + if (LastHostID != -1) + { + // check if the host is still connected + + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + u16 curinstmask = header->ConnectedBitmask; + MPQueue->unlock(); + + if (!(curinstmask & (1 << LastHostID))) + return -1; + } + + return RecvPacketGeneric(packet, true, timestamp); +} + +u16 RecvReplies(u8* packets, u64 timestamp, u16 aidmask) +{ + u16 ret = 0; + u16 myinstmask = (1 << InstanceID); + u16 curinstmask; + + { + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + curinstmask = header->ConnectedBitmask; + MPQueue->unlock(); + } + + // if all clients have left: return early + if ((myinstmask & curinstmask) == curinstmask) + return 0; + + for (;;) + { + if (!SemWait(16+InstanceID, RecvTimeout)) + { + // no more replies available + return ret; + } + + MPQueue->lock(); + u8* data = (u8*)MPQueue->data(); + MPQueueHeader* header = (MPQueueHeader*)&data[0]; + + MPPacketHeader pktheader; + FIFORead(1, &pktheader, sizeof(pktheader)); + + if (pktheader.Magic != 0x4946494E) + { + printf("REPLY FIFO OVERFLOW\n"); + ReplyReadOffset = header->ReplyWriteOffset; + SemReset(16+InstanceID); + MPQueue->unlock(); + return 0; + } + + if ((pktheader.SenderID == InstanceID) || // packet we sent out (shouldn't happen, but hey) + (pktheader.Timestamp < (timestamp - 32))) // stale packet + { + // skip this packet + ReplyReadOffset += pktheader.Length; + if (ReplyReadOffset >= kReplyEnd) + ReplyReadOffset += kReplyStart - kReplyEnd; + + MPQueue->unlock(); + continue; + } + + if (pktheader.Length) + { + u32 aid = (pktheader.Type >> 16); + FIFORead(1, &packets[(aid-1)*1024], pktheader.Length); + ret |= (1 << aid); + } + + myinstmask |= (1 << pktheader.SenderID); + if (((myinstmask & curinstmask) == curinstmask) || + ((ret & aidmask) == aidmask)) + { + // all the clients have sent their reply + + MPQueue->unlock(); + return ret; + } + + MPQueue->unlock(); + } +} + +} + diff --git a/src/frontend/qt_sdl/LocalMP.h b/src/frontend/qt_sdl/LocalMP.h new file mode 100644 index 00000000..51dfcb93 --- /dev/null +++ b/src/frontend/qt_sdl/LocalMP.h @@ -0,0 +1,45 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef LOCALMP_H +#define LOCALMP_H + +#include "types.h" + +namespace LocalMP +{ + +bool Init(); +void DeInit(); + +void SetRecvTimeout(int timeout); + +void Begin(); +void End(); + +int SendPacket(u8* data, int len, u64 timestamp); +int RecvPacket(u8* data, u64* timestamp); +int SendCmd(u8* data, int len, u64 timestamp); +int SendReply(u8* data, int len, u64 timestamp, u16 aid); +int SendAck(u8* data, int len, u64 timestamp); +int RecvHostPacket(u8* data, u64* timestamp); +u16 RecvReplies(u8* data, u64 timestamp, u16 aidmask); + +} + +#endif // LOCALMP_H diff --git a/src/frontend/qt_sdl/MPSettingsDialog.cpp b/src/frontend/qt_sdl/MPSettingsDialog.cpp new file mode 100644 index 00000000..e3114220 --- /dev/null +++ b/src/frontend/qt_sdl/MPSettingsDialog.cpp @@ -0,0 +1,73 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include +#include + +#include "types.h" +#include "Platform.h" +#include "Config.h" + +#include "LAN_Socket.h" +#include "LAN_PCap.h" +#include "Wifi.h" + +#include "MPSettingsDialog.h" +#include "ui_MPSettingsDialog.h" + + +MPSettingsDialog* MPSettingsDialog::currentDlg = nullptr; + +extern bool RunningSomething; + + +MPSettingsDialog::MPSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MPSettingsDialog) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + grpAudioMode = new QButtonGroup(this); + grpAudioMode->addButton(ui->rbAudioAll, 0); + grpAudioMode->addButton(ui->rbAudioOneOnly, 1); + grpAudioMode->addButton(ui->rbAudioActiveOnly, 2); + grpAudioMode->button(Config::MPAudioMode)->setChecked(true); + + ui->sbReceiveTimeout->setValue(Config::MPRecvTimeout); +} + +MPSettingsDialog::~MPSettingsDialog() +{ + delete ui; +} + +void MPSettingsDialog::done(int r) +{ + if (r == QDialog::Accepted) + { + Config::MPAudioMode = grpAudioMode->checkedId(); + Config::MPRecvTimeout = ui->sbReceiveTimeout->value(); + + Config::Save(); + } + + QDialog::done(r); + + closeDlg(); +} + +// diff --git a/src/frontend/qt_sdl/MPSettingsDialog.h b/src/frontend/qt_sdl/MPSettingsDialog.h new file mode 100644 index 00000000..fe917e89 --- /dev/null +++ b/src/frontend/qt_sdl/MPSettingsDialog.h @@ -0,0 +1,65 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef MPSETTINGSDIALOG_H +#define MPSETTINGSDIALOG_H + +#include +#include + +namespace Ui { class MPSettingsDialog; } +class MPSettingsDialog; + +class MPSettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit MPSettingsDialog(QWidget* parent); + ~MPSettingsDialog(); + + static MPSettingsDialog* currentDlg; + static MPSettingsDialog* openDlg(QWidget* parent) + { + if (currentDlg) + { + currentDlg->activateWindow(); + return currentDlg; + } + + currentDlg = new MPSettingsDialog(parent); + currentDlg->open(); + return currentDlg; + } + static void closeDlg() + { + currentDlg = nullptr; + } + +private slots: + void done(int r); + + // + +private: + Ui::MPSettingsDialog* ui; + + QButtonGroup* grpAudioMode; +}; + +#endif // MPSETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/MPSettingsDialog.ui b/src/frontend/qt_sdl/MPSettingsDialog.ui new file mode 100644 index 00000000..bce0fc94 --- /dev/null +++ b/src/frontend/qt_sdl/MPSettingsDialog.ui @@ -0,0 +1,142 @@ + + + MPSettingsDialog + + + + 0 + 0 + 466 + 202 + + + + Multiplayer settings - melonDS + + + + + + Audio output + + + + + + Instance 1 only + + + + + + + All instances + + + + + + + Active instance only + + + + + + + + + + Network + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + 1000 + + + + + + + Data reception timeout: + + + + + + + + 1 + 0 + + + + milliseconds + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + MPSettingsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MPSettingsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/frontend/qt_sdl/OSD.cpp b/src/frontend/qt_sdl/OSD.cpp index 68b1b783..6f060a97 100644 --- a/src/frontend/qt_sdl/OSD.cpp +++ b/src/frontend/qt_sdl/OSD.cpp @@ -146,7 +146,7 @@ void LayoutText(const char* text, u32* width, u32* height, int* breaks) u32 w = 0; u32 h = 14; u32 totalw = 0; - u32 maxw = mainWindow->panel->width() - (kOSDMargin*2); + u32 maxw = mainWindow->panelWidget->width() - (kOSDMargin*2); int lastbreak = -1; int numbrk = 0; u16* ptr; @@ -236,7 +236,7 @@ void RenderText(u32 color, const char* text, Item* item) memset(item->Bitmap, 0, w*h*sizeof(u32)); u32 x = 0, y = 1; - u32 maxw = mainWindow->panel->width() - (kOSDMargin*2); + u32 maxw = mainWindow->panelWidget->width() - (kOSDMargin*2); int curline = 0; u16* ptr; diff --git a/src/frontend/qt_sdl/PathSettingsDialog.cpp b/src/frontend/qt_sdl/PathSettingsDialog.cpp index 7fa517d3..286032e9 100644 --- a/src/frontend/qt_sdl/PathSettingsDialog.cpp +++ b/src/frontend/qt_sdl/PathSettingsDialog.cpp @@ -22,6 +22,7 @@ #include "types.h" #include "Config.h" +#include "Platform.h" #include "PathSettingsDialog.h" #include "ui_PathSettingsDialog.h" @@ -43,6 +44,12 @@ PathSettingsDialog::PathSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne ui->txtSaveFilePath->setText(QString::fromStdString(Config::SaveFilePath)); ui->txtSavestatePath->setText(QString::fromStdString(Config::SavestatePath)); ui->txtCheatFilePath->setText(QString::fromStdString(Config::CheatFilePath)); + + int inst = Platform::InstanceID(); + if (inst > 0) + ui->lblInstanceNum->setText(QString("Configuring paths for instance %1").arg(inst+1)); + else + ui->lblInstanceNum->hide(); } PathSettingsDialog::~PathSettingsDialog() diff --git a/src/frontend/qt_sdl/PathSettingsDialog.ui b/src/frontend/qt_sdl/PathSettingsDialog.ui index 95f5acc9..295b1c44 100644 --- a/src/frontend/qt_sdl/PathSettingsDialog.ui +++ b/src/frontend/qt_sdl/PathSettingsDialog.ui @@ -7,49 +7,63 @@ 0 0 439 - 166 + 185 Path settings - melonDS - - - - true + + + + Cheat files path: - + Browse... - + Savestates path: + + + + + + + + + + true + + + + + + + Browse... + + + + true - - - - Leave a path blank to use the current ROM's path. - - - - + Qt::Horizontal @@ -59,35 +73,14 @@ - - - - Cheat files path: - - - - - - - Browse... - - - - + Browse... - - - - true - - - - + Save files path: @@ -95,9 +88,23 @@ - + - + Leave a path blank to use the current ROM's path. + + + + + + + true + + + + + + + Configuring paths for instance X diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index 2a3dde55..14e4f587 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -20,28 +20,7 @@ #include #include -#ifdef __WIN32__ - #define NTDDI_VERSION 0x06000000 // GROSS FUCKING HACK - #include - #include - //#include // FUCK THAT SHIT - #include - #include - #include - #define dup _dup - #define socket_t SOCKET - #define sockaddr_t SOCKADDR -#else - #include - #include - #include - #include - - #define socket_t int - #define sockaddr_t struct sockaddr - #define closesocket close -#endif - +#include #include #include #include @@ -49,6 +28,7 @@ #include #include #include +#include #include "Platform.h" #include "Config.h" @@ -56,11 +36,7 @@ #include "CameraManager.h" #include "LAN_Socket.h" #include "LAN_PCap.h" -#include - -#ifndef INVALID_SOCKET - #define INVALID_SOCKET (socket_t)-1 -#endif +#include "LocalMP.h" std::string EmuDirectory; @@ -73,11 +49,63 @@ extern CameraManager* camManager[2]; namespace Platform { -socket_t MPSocket; -sockaddr_t MPSendAddr; -u8 PacketBuffer[2048]; +QSharedMemory* IPCBuffer = nullptr; +int IPCInstanceID; -#define NIFI_VER 1 +void IPCInit() +{ + IPCInstanceID = 0; + + IPCBuffer = new QSharedMemory("melonIPC"); + + if (!IPCBuffer->attach()) + { + printf("IPC sharedmem doesn't exist. creating\n"); + if (!IPCBuffer->create(1024)) + { + printf("IPC sharedmem create failed :(\n"); + delete IPCBuffer; + IPCBuffer = nullptr; + return; + } + + IPCBuffer->lock(); + memset(IPCBuffer->data(), 0, IPCBuffer->size()); + IPCBuffer->unlock(); + } + + IPCBuffer->lock(); + u8* data = (u8*)IPCBuffer->data(); + u16 mask = *(u16*)&data[0]; + for (int i = 0; i < 16; i++) + { + if (!(mask & (1<unlock(); + + printf("IPC: instance ID %d\n", IPCInstanceID); +} + +void IPCDeInit() +{ + if (IPCBuffer) + { + IPCBuffer->lock(); + u8* data = (u8*)IPCBuffer->data(); + *(u16*)&data[0] &= ~(1<unlock(); + + IPCBuffer->detach(); + delete IPCBuffer; + } + + IPCBuffer = nullptr; +} void Init(int argc, char** argv) @@ -113,10 +141,13 @@ void Init(int argc, char** argv) confdir = config.absolutePath() + "/melonDS/"; EmuDirectory = confdir.toStdString(); #endif + + IPCInit(); } void DeInit() { + IPCDeInit(); } @@ -126,6 +157,22 @@ void StopEmu() } +int InstanceID() +{ + return IPCInstanceID; +} + +std::string InstanceFileSuffix() +{ + int inst = IPCInstanceID; + if (inst == 0) return ""; + + char suffix[16] = {0}; + snprintf(suffix, 15, ".%d", inst+1); + return suffix; +} + + int GetConfigInt(ConfigEntry entry) { const int imgsizes[] = {0, 256, 512, 1024, 2048, 4096}; @@ -172,7 +219,6 @@ bool GetConfigBool(ConfigEntry entry) case DSiSD_ReadOnly: return Config::DSiSDReadOnly != 0; case DSiSD_FolderSync: return Config::DSiSDFolderSync != 0; - case Firm_RandomizeMAC: return Config::RandomizeMAC != 0; case Firm_OverrideSettings: return Config::FirmwareOverrideSettings != 0; } @@ -394,146 +440,60 @@ void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen } + bool MP_Init() { - int opt_true = 1; - int res; - -#ifdef __WIN32__ - WSADATA wsadata; - if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0) - { - return false; - } -#endif // __WIN32__ - - MPSocket = socket(AF_INET, SOCK_DGRAM, 0); - if (MPSocket < 0) - { - return false; - } - - res = setsockopt(MPSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt_true, sizeof(int)); - if (res < 0) - { - closesocket(MPSocket); - MPSocket = INVALID_SOCKET; - return false; - } - -#if defined(BSD) || defined(__APPLE__) - res = setsockopt(MPSocket, SOL_SOCKET, SO_REUSEPORT, (const char*)&opt_true, sizeof(int)); - if (res < 0) - { - closesocket(MPSocket); - MPSocket = INVALID_SOCKET; - return false; - } -#endif - - sockaddr_t saddr; - saddr.sa_family = AF_INET; - *(u32*)&saddr.sa_data[2] = htonl(Config::SocketBindAnyAddr ? INADDR_ANY : INADDR_LOOPBACK); - *(u16*)&saddr.sa_data[0] = htons(7064); - res = bind(MPSocket, &saddr, sizeof(sockaddr_t)); - if (res < 0) - { - closesocket(MPSocket); - MPSocket = INVALID_SOCKET; - return false; - } - - res = setsockopt(MPSocket, SOL_SOCKET, SO_BROADCAST, (const char*)&opt_true, sizeof(int)); - if (res < 0) - { - closesocket(MPSocket); - MPSocket = INVALID_SOCKET; - return false; - } - - MPSendAddr.sa_family = AF_INET; - *(u32*)&MPSendAddr.sa_data[2] = htonl(INADDR_BROADCAST); - *(u16*)&MPSendAddr.sa_data[0] = htons(7064); - - return true; + return LocalMP::Init(); } void MP_DeInit() { - if (MPSocket >= 0) - closesocket(MPSocket); - -#ifdef __WIN32__ - WSACleanup(); -#endif // __WIN32__ + return LocalMP::DeInit(); } -int MP_SendPacket(u8* data, int len) +void MP_Begin() { - if (MPSocket < 0) - return 0; - - if (len > 2048-8) - { - printf("MP_SendPacket: error: packet too long (%d)\n", len); - return 0; - } - - *(u32*)&PacketBuffer[0] = htonl(0x4946494E); // NIFI - PacketBuffer[4] = NIFI_VER; - PacketBuffer[5] = 0; - *(u16*)&PacketBuffer[6] = htons(len); - memcpy(&PacketBuffer[8], data, len); - - int slen = sendto(MPSocket, (const char*)PacketBuffer, len+8, 0, &MPSendAddr, sizeof(sockaddr_t)); - if (slen < 8) return 0; - return slen - 8; + return LocalMP::Begin(); } -int MP_RecvPacket(u8* data, bool block) +void MP_End() { - if (MPSocket < 0) - return 0; + return LocalMP::End(); +} - fd_set fd; - struct timeval tv; +int MP_SendPacket(u8* data, int len, u64 timestamp) +{ + return LocalMP::SendPacket(data, len, timestamp); +} - FD_ZERO(&fd); - FD_SET(MPSocket, &fd); - tv.tv_sec = 0; - tv.tv_usec = block ? 5000 : 0; +int MP_RecvPacket(u8* data, u64* timestamp) +{ + return LocalMP::RecvPacket(data, timestamp); +} - if (!select(MPSocket+1, &fd, 0, 0, &tv)) - { - return 0; - } +int MP_SendCmd(u8* data, int len, u64 timestamp) +{ + return LocalMP::SendCmd(data, len, timestamp); +} - sockaddr_t fromAddr; - socklen_t fromLen = sizeof(sockaddr_t); - int rlen = recvfrom(MPSocket, (char*)PacketBuffer, 2048, 0, &fromAddr, &fromLen); - if (rlen < 8+24) - { - return 0; - } - rlen -= 8; +int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid) +{ + return LocalMP::SendReply(data, len, timestamp, aid); +} - if (ntohl(*(u32*)&PacketBuffer[0]) != 0x4946494E) - { - return 0; - } +int MP_SendAck(u8* data, int len, u64 timestamp) +{ + return LocalMP::SendAck(data, len, timestamp); +} - if (PacketBuffer[4] != NIFI_VER) - { - return 0; - } +int MP_RecvHostPacket(u8* data, u64* timestamp) +{ + return LocalMP::RecvHostPacket(data, timestamp); +} - if (ntohs(*(u16*)&PacketBuffer[6]) != rlen) - { - return 0; - } - - memcpy(data, &PacketBuffer[8], rlen); - return rlen; +u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask) +{ + return LocalMP::RecvReplies(data, timestamp, aidmask); } diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp index 499c176a..89f74e5f 100644 --- a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp +++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp @@ -23,6 +23,7 @@ #include "DSi_I2C.h" #include "NDS.h" #include "Config.h" +#include "Platform.h" #include "types.h" @@ -65,6 +66,12 @@ PowerManagementDialog::PowerManagementDialog(QWidget* parent) : QDialog(parent), } ui->sliderDSiBatteryLevel->setValue(dsiBatterySliderPos); + int inst = Platform::InstanceID(); + if (inst > 0) + ui->lblInstanceNum->setText(QString("Setting battery levels for instance %1").arg(inst+1)); + else + ui->lblInstanceNum->hide(); + inited = true; } diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui index e0e7c6e8..77af2254 100644 --- a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui +++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui @@ -7,7 +7,7 @@ 0 0 562 - 279 + 288 @@ -23,37 +23,7 @@ QLayout::SetFixedSize - - - - DS Battery - - - - - - Low - - - - - - - Battery Level - - - - - - - Okay - - - - - - - + Qt::Horizontal @@ -63,7 +33,7 @@ - + DSi Battery @@ -219,6 +189,49 @@ + + + + DS Battery + + + + + + Low + + + + + + + Battery Level + + + + + + + Okay + + + + + + + + + + + 0 + 0 + + + + Configuring settings for instance X + + + diff --git a/src/frontend/qt_sdl/ROMInfoDialog.cpp b/src/frontend/qt_sdl/ROMInfoDialog.cpp index 0a71dc8b..e82ec4be 100644 --- a/src/frontend/qt_sdl/ROMInfoDialog.cpp +++ b/src/frontend/qt_sdl/ROMInfoDialog.cpp @@ -75,12 +75,12 @@ ROMInfoDialog::ROMInfoDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ROMI ui->iconTitle->setText(QString::fromUtf16(NDSCart::Banner.EnglishTitle)); - ui->japaneseTitle->setText(QString::fromUtf16(NDSCart::Banner.JapaneseTitle, 128)); - ui->englishTitle->setText(QString::fromUtf16(NDSCart::Banner.EnglishTitle, 128)); - ui->frenchTitle->setText(QString::fromUtf16(NDSCart::Banner.FrenchTitle, 128)); - ui->germanTitle->setText(QString::fromUtf16(NDSCart::Banner.GermanTitle, 128)); - ui->italianTitle->setText(QString::fromUtf16(NDSCart::Banner.ItalianTitle, 128)); - ui->spanishTitle->setText(QString::fromUtf16(NDSCart::Banner.SpanishTitle, 128)); + ui->japaneseTitle->setText(QString::fromUtf16(NDSCart::Banner.JapaneseTitle)); + ui->englishTitle->setText(QString::fromUtf16(NDSCart::Banner.EnglishTitle)); + ui->frenchTitle->setText(QString::fromUtf16(NDSCart::Banner.FrenchTitle)); + ui->germanTitle->setText(QString::fromUtf16(NDSCart::Banner.GermanTitle)); + ui->italianTitle->setText(QString::fromUtf16(NDSCart::Banner.ItalianTitle)); + ui->spanishTitle->setText(QString::fromUtf16(NDSCart::Banner.SpanishTitle)); if (NDSCart::Banner.Version > 1) ui->chineseTitle->setText(QString::fromUtf16(NDSCart::Banner.ChineseTitle)); diff --git a/src/frontend/qt_sdl/ROMInfoDialog.ui b/src/frontend/qt_sdl/ROMInfoDialog.ui index 0c65cab3..1c9d844b 100644 --- a/src/frontend/qt_sdl/ROMInfoDialog.ui +++ b/src/frontend/qt_sdl/ROMInfoDialog.ui @@ -6,8 +6,8 @@ 0 0 - 427 - 434 + 559 + 532 @@ -22,12 +22,6 @@ - - - 0 - 0 - - Titles @@ -350,12 +344,6 @@ - - - 0 - 0 - - Filesystem @@ -441,12 +429,6 @@ - - - 0 - 0 - - General info @@ -668,7 +650,7 @@ - + @@ -742,43 +724,11 @@ - - - - Qt::Horizontal - - - - 55 - 20 - - - - - + Qt::Horizontal - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - @@ -788,6 +738,13 @@ + + + + Qt::Horizontal + + + diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/ROMManager.cpp index 304862eb..716a4543 100644 --- a/src/frontend/qt_sdl/ROMManager.cpp +++ b/src/frontend/qt_sdl/ROMManager.cpp @@ -326,6 +326,7 @@ bool LoadState(std::string filename) std::string savefile = filename.substr(LastSep(filename)+1); savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile); + savefile += Platform::InstanceFileSuffix(); NDSSave->SetPath(savefile, true); } @@ -350,6 +351,7 @@ bool SaveState(std::string filename) { std::string savefile = filename.substr(LastSep(filename)+1); savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile); + savefile += Platform::InstanceFileSuffix(); NDSSave->SetPath(savefile, false); } @@ -432,6 +434,7 @@ void Reset() { std::string oldsave = NDSSave->GetPath(); std::string newsave = GetAssetPath(false, Config::SaveFilePath, ".sav"); + newsave += Platform::InstanceFileSuffix(); if (oldsave != newsave) NDSSave->SetPath(newsave, false); } @@ -440,6 +443,7 @@ void Reset() { std::string oldsave = GBASave->GetPath(); std::string newsave = GetAssetPath(true, Config::SaveFilePath, ".sav"); + newsave += Platform::InstanceFileSuffix(); if (oldsave != newsave) GBASave->SetPath(newsave, false); } @@ -562,7 +566,11 @@ bool LoadROM(QStringList filepath, bool reset) u8* savedata = nullptr; std::string savname = GetAssetPath(false, Config::SaveFilePath, ".sav"); + std::string origsav = savname; + savname += Platform::InstanceFileSuffix(); + FILE* sav = Platform::OpenFile(savname, "rb", true); + if (!sav) sav = Platform::OpenFile(origsav, "rb", true); if (sav) { fseek(sav, 0, SEEK_END); @@ -711,7 +719,11 @@ bool LoadGBAROM(QStringList filepath) u8* savedata = nullptr; std::string savname = GetAssetPath(true, Config::SaveFilePath, ".sav"); + std::string origsav = savname; + savname += Platform::InstanceFileSuffix(); + FILE* sav = Platform::OpenFile(savname, "rb", true); + if (!sav) sav = Platform::OpenFile(origsav, "rb", true); if (sav) { fseek(sav, 0, SEEK_END); diff --git a/src/frontend/qt_sdl/TitleManagerDialog.cpp b/src/frontend/qt_sdl/TitleManagerDialog.cpp index a4c9cfd0..8087ee65 100644 --- a/src/frontend/qt_sdl/TitleManagerDialog.cpp +++ b/src/frontend/qt_sdl/TitleManagerDialog.cpp @@ -31,7 +31,7 @@ #include "ui_TitleImportDialog.h" -FILE* TitleManagerDialog::curNAND = nullptr; +bool TitleManagerDialog::NANDInited = false; TitleManagerDialog* TitleManagerDialog::currentDlg = nullptr; extern std::string EmuDirectory; @@ -136,6 +136,8 @@ void TitleManagerDialog::createTitleItem(u32 category, u32 titleid) bool TitleManagerDialog::openNAND() { + NANDInited = false; + FILE* bios7i = Platform::OpenLocalFile(Config::DSiBIOS7Path, "rb"); if (!bios7i) return false; @@ -145,28 +147,21 @@ bool TitleManagerDialog::openNAND() fread(es_keyY, 16, 1, bios7i); fclose(bios7i); - curNAND = Platform::OpenLocalFile(Config::DSiNANDPath, "r+b"); - if (!curNAND) - return false; - - if (!DSi_NAND::Init(curNAND, es_keyY)) + if (!DSi_NAND::Init(es_keyY)) { - fclose(curNAND); - curNAND = nullptr; return false; } + NANDInited = true; return true; } void TitleManagerDialog::closeNAND() { - if (curNAND) + if (NANDInited) { DSi_NAND::DeInit(); - - fclose(curNAND); - curNAND = nullptr; + NANDInited = false; } } diff --git a/src/frontend/qt_sdl/TitleManagerDialog.h b/src/frontend/qt_sdl/TitleManagerDialog.h index 201e5e8b..cba70470 100644 --- a/src/frontend/qt_sdl/TitleManagerDialog.h +++ b/src/frontend/qt_sdl/TitleManagerDialog.h @@ -45,7 +45,7 @@ public: explicit TitleManagerDialog(QWidget* parent); ~TitleManagerDialog(); - static FILE* curNAND; + static bool NANDInited; static bool openNAND(); static void closeNAND(); diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.cpp b/src/frontend/qt_sdl/WifiSettingsDialog.cpp index 19cece6f..9bf265e9 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.cpp +++ b/src/frontend/qt_sdl/WifiSettingsDialog.cpp @@ -50,12 +50,12 @@ WifiSettingsDialog::WifiSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - LAN_Socket::Init(); haspcap = LAN_PCap::Init(false); ui->rbDirectMode->setText("Direct mode (requires " PCAP_NAME " and ethernet connection)"); - ui->cbBindAnyAddr->setChecked(Config::SocketBindAnyAddr); + ui->lblAdapterMAC->setText("(none)"); + ui->lblAdapterIP->setText("(none)"); int sel = 0; for (int i = 0; i < LAN_PCap::NumAdapters; i++) @@ -88,7 +88,6 @@ void WifiSettingsDialog::done(int r) if (r == QDialog::Accepted) { - Config::SocketBindAnyAddr = ui->cbBindAnyAddr->isChecked(); Config::DirectLAN = ui->rbDirectMode->isChecked(); int sel = ui->cbxDirectAdapter->currentIndex(); diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.ui b/src/frontend/qt_sdl/WifiSettingsDialog.ui index 08970595..444e1d5f 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.ui +++ b/src/frontend/qt_sdl/WifiSettingsDialog.ui @@ -7,7 +7,7 @@ 0 0 572 - 273 + 217 @@ -26,92 +26,10 @@ - Local + Network mode - - - <html><head/><body><p>Enabling this allows (theoretically) playing local multiplayer games over a local network. It may or may not help make for a better connection in general.</p></body></html> - - - Bind socket to any address - - - - - - - - - - Online - - - - - - Direct mode settings - - - - - - Network adapter: - - - - - - - - 0 - 0 - - - - - 300 - 0 - - - - <html><head/><body><p>Selects the network adapter through which to route network traffic under direct mode.</p></body></html> - - - - - - - MAC address: - - - - - - - [PLACEHOLDER] - - - - - - - IP address: - - - - - - - [PLACEHOLDER] - - - - - - - <html><head/><body><p>Indirect mode uses libslirp. It requires no extra setup and is easy to use.</p></body></html> @@ -121,7 +39,7 @@ - + <html><head/><body><p>Direct mode directly routes network traffic to the host network. It is the most reliable, but requires an ethernet connection.</p><p><br/></p><p>Non-direct mode uses a layer of emulation to get around this, but is more prone to problems.</p></body></html> @@ -134,6 +52,69 @@ + + + + Direct mode settings + + + + + + Network adapter: + + + + + + + + 0 + 0 + + + + + 300 + 0 + + + + <html><head/><body><p>Selects the network adapter through which to route network traffic under direct mode.</p></body></html> + + + + + + + MAC address: + + + + + + + [PLACEHOLDER] + + + + + + + IP address: + + + + + + + [PLACEHOLDER] + + + + + + diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 213a09da..22edab11 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -58,6 +59,7 @@ #include "AudioSettingsDialog.h" #include "FirmwareSettingsDialog.h" #include "PathSettingsDialog.h" +#include "MPSettingsDialog.h" #include "WifiSettingsDialog.h" #include "InterfaceSettingsDialog.h" #include "ROMInfoDialog.h" @@ -78,6 +80,7 @@ #include "SPU.h" #include "Wifi.h" #include "Platform.h" +#include "LocalMP.h" #include "Config.h" #include "Savestate.h" @@ -103,6 +106,7 @@ bool videoSettingsDirty; SDL_AudioDeviceID audioDevice; int audioFreq; +bool audioMuted; SDL_cond* audioSync; SDL_mutex* audioSyncLock; @@ -115,6 +119,15 @@ s16* micWavBuffer; CameraManager* camManager[2]; +const struct { int id; float ratio; const char* label; } aspectRatios[] = +{ + { 0, 1, "4:3 (native)" }, + { 4, (5.f / 3) / (4.f / 3), "5:3 (3DS)"}, + { 1, (16.f / 9) / (4.f / 3), "16:9" }, + { 2, (21.f / 9) / (4.f / 3), "21:9" }, + { 3, 0, "window" } +}; + void micCallback(void* data, Uint8* stream, int len); @@ -134,7 +147,7 @@ void audioCallback(void* data, Uint8* stream, int len) SDL_CondSignal(audioSync); SDL_UnlockMutex(audioSyncLock); - if (num_in < 1) + if ((num_in < 1) || audioMuted) { memset(stream, 0, len*sizeof(s16)*2); return; @@ -154,6 +167,23 @@ void audioCallback(void* data, Uint8* stream, int len) Frontend::AudioOut_Resample(buf_in, num_in, (s16*)stream, len, Config::AudioVolume); } +void audioMute() +{ + int inst = Platform::InstanceID(); + audioMuted = false; + + switch (Config::MPAudioMode) + { + case 1: // only instance 1 + if (inst > 0) audioMuted = true; + break; + + case 2: // only currently focused instance + if (!mainWindow->isActiveWindow()) audioMuted = true; + break; + } +} + void micOpen() { @@ -326,7 +356,7 @@ EmuThread::EmuThread(QObject* parent) : QThread(parent) EmuPause = 0; RunningSomething = false; - connect(this, SIGNAL(windowUpdate()), mainWindow->panel, SLOT(repaint())); + connect(this, SIGNAL(windowUpdate()), mainWindow->panelWidget, SLOT(repaint())); connect(this, SIGNAL(windowTitleChange(QString)), mainWindow, SLOT(onTitleUpdate(QString))); connect(this, SIGNAL(windowEmuStart()), mainWindow, SLOT(onEmuStart())); connect(this, SIGNAL(windowEmuStop()), mainWindow, SLOT(onEmuStop())); @@ -334,7 +364,7 @@ EmuThread::EmuThread(QObject* parent) : QThread(parent) connect(this, SIGNAL(windowEmuReset()), mainWindow->actReset, SLOT(trigger())); connect(this, SIGNAL(windowEmuFrameStep()), mainWindow->actFrameStep, SLOT(trigger())); connect(this, SIGNAL(windowLimitFPSChange()), mainWindow->actLimitFramerate, SLOT(trigger())); - connect(this, SIGNAL(screenLayoutChange()), mainWindow->panel, SLOT(onScreenLayoutChanged())); + connect(this, SIGNAL(screenLayoutChange()), mainWindow->panelWidget, SLOT(onScreenLayoutChanged())); connect(this, SIGNAL(windowFullscreenToggle()), mainWindow, SLOT(onFullscreenToggled())); connect(this, SIGNAL(swapScreensToggle()), mainWindow->actScreenSwap, SLOT(trigger())); @@ -683,7 +713,11 @@ printf("PROULON\n"); if (winUpdateFreq < 1) winUpdateFreq = 1; - sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); + int inst = Platform::InstanceID(); + if (inst == 0) + sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); + else + sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1); changeWindowTitle(melontitle); } } @@ -698,7 +732,11 @@ printf("PROULON\n"); EmuStatus = EmuRunning; - sprintf(melontitle, "melonDS " MELONDS_VERSION); + int inst = Platform::InstanceID(); + if (inst == 0) + sprintf(melontitle, "melonDS " MELONDS_VERSION); + else + sprintf(melontitle, "melonDS (%d)", inst+1); changeWindowTitle(melontitle); SDL_Delay(75); @@ -789,19 +827,39 @@ bool EmuThread::emuIsActive() return (RunningSomething == 1); } +ScreenHandler::ScreenHandler(QWidget* widget) +{ + widget->setMouseTracking(true); + widget->setAttribute(Qt::WA_AcceptTouchEvents); + QTimer* mouseTimer = setupMouseTimer(); + widget->connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) widget->setCursor(Qt::BlankCursor);}); +} + +ScreenHandler::~ScreenHandler() +{ + mouseTimer->stop(); +} void ScreenHandler::screenSetupLayout(int w, int h) { int sizing = Config::ScreenSizing; if (sizing == 3) sizing = autoScreenSizing; - float aspectRatios[] = + float aspectTop, aspectBot; + + for (auto ratio : aspectRatios) { - 1.f, - (16.f/9)/(4.f/3), - (21.f/9)/(4.f/3), - ((float)w/h)/(4.f/3) - }; + if (ratio.id == Config::ScreenAspectTop) + aspectTop = ratio.ratio; + if (ratio.id == Config::ScreenAspectBot) + aspectBot = ratio.ratio; + } + + if (aspectTop == 0) + aspectTop = (float) w / h; + + if (aspectBot == 0) + aspectBot = (float) w / h; Frontend::SetupScreenLayout(w, h, Config::ScreenLayout, @@ -810,8 +868,8 @@ void ScreenHandler::screenSetupLayout(int w, int h) Config::ScreenGap, Config::IntegerScaling != 0, Config::ScreenSwap != 0, - aspectRatios[Config::ScreenAspectTop], - aspectRatios[Config::ScreenAspectBot]); + aspectTop, + aspectBot); numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind); } @@ -819,11 +877,16 @@ void ScreenHandler::screenSetupLayout(int w, int h) QSize ScreenHandler::screenGetMinSize(int factor = 1) { bool isHori = (Config::ScreenRotation == 1 || Config::ScreenRotation == 3); - int gap = Config::ScreenGap; + int gap = Config::ScreenGap * factor; int w = 256 * factor; int h = 192 * factor; + if (Config::ScreenSizing == 4 || Config::ScreenSizing == 5) + { + return QSize(w, h); + } + if (Config::ScreenLayout == 0) // natural { if (isHori) @@ -959,7 +1022,7 @@ void ScreenHandler::screenHandleTouch(QTouchEvent* event) void ScreenHandler::showCursor() { - mainWindow->panel->setCursor(Qt::ArrowCursor); + mainWindow->panelWidget->setCursor(Qt::ArrowCursor); mouseTimer->start(); } @@ -973,7 +1036,7 @@ QTimer* ScreenHandler::setupMouseTimer() return mouseTimer; } -ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent) +ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent), ScreenHandler(this) { screen[0] = QImage(256, 192, QImage::Format_RGB32); screen[1] = QImage(256, 192, QImage::Format_RGB32); @@ -981,17 +1044,12 @@ ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent) screenTrans[0].reset(); screenTrans[1].reset(); - touching = false; - - setAttribute(Qt::WA_AcceptTouchEvents); - OSD::Init(nullptr); } ScreenPanelNative::~ScreenPanelNative() { OSD::DeInit(nullptr); - mouseTimer->stop(); } void ScreenPanelNative::setupScreenLayout() @@ -1090,17 +1148,11 @@ void ScreenPanelNative::onScreenLayoutChanged() } -ScreenPanelGL::ScreenPanelGL(QWidget* parent) : QOpenGLWidget(parent) -{ - touching = false; - - setAttribute(Qt::WA_AcceptTouchEvents); -} +ScreenPanelGL::ScreenPanelGL(QWidget* parent) : QOpenGLWidget(parent), ScreenHandler(this) +{} ScreenPanelGL::~ScreenPanelGL() { - mouseTimer->stop(); - makeCurrent(); OSD::DeInit(this); @@ -1356,6 +1408,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) setWindowTitle("melonDS " MELONDS_VERSION); setAttribute(Qt::WA_DeleteOnClose); setAcceptDrops(true); + setFocusPolicy(Qt::ClickFocus); + + int inst = Platform::InstanceID(); QMenuBar* menubar = new QMenuBar(); { @@ -1488,19 +1543,30 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actEnableCheats->setCheckable(true); connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats); - actSetupCheats = menu->addAction("Setup cheat codes"); - actSetupCheats->setMenuRole(QAction::NoRole); - connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); + //if (inst == 0) + { + actSetupCheats = menu->addAction("Setup cheat codes"); + actSetupCheats->setMenuRole(QAction::NoRole); + connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); - menu->addSeparator(); - actROMInfo = menu->addAction("ROM info"); - connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); + menu->addSeparator(); + actROMInfo = menu->addAction("ROM info"); + connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); - actRAMInfo = menu->addAction("RAM search"); - connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo); + actRAMInfo = menu->addAction("RAM search"); + connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo); - actTitleManager = menu->addAction("Manage DSi titles"); - connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); + actTitleManager = menu->addAction("Manage DSi titles"); + connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); + } + + { + menu->addSeparator(); + QMenu* submenu = menu->addMenu("Multiplayer"); + + actMPNewInstance = submenu->addAction("Launch new instance"); + connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance); + } } { QMenu* menu = menubar->addMenu("Config"); @@ -1509,7 +1575,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); #ifdef __APPLE__ - QAction* actPreferences = menu->addAction("Preferences..."); + actPreferences = menu->addAction("Preferences..."); connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); actPreferences->setMenuRole(QAction::PreferencesRole); #endif @@ -1526,15 +1592,18 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actAudioSettings = menu->addAction("Audio settings"); connect(actAudioSettings, &QAction::triggered, this, &MainWindow::onOpenAudioSettings); + actMPSettings = menu->addAction("Multiplayer settings"); + connect(actMPSettings, &QAction::triggered, this, &MainWindow::onOpenMPSettings); + actWifiSettings = menu->addAction("Wifi settings"); connect(actWifiSettings, &QAction::triggered, this, &MainWindow::onOpenWifiSettings); - actInterfaceSettings = menu->addAction("Interface settings"); - connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings); - actFirmwareSettings = menu->addAction("Firmware settings"); connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings); + actInterfaceSettings = menu->addAction("Interface settings"); + connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings); + actPathSettings = menu->addAction("Path settings"); connect(actPathSettings, &QAction::triggered, this, &MainWindow::onOpenPathSettings); @@ -1638,34 +1707,34 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { QMenu* submenu = menu->addMenu("Aspect ratio"); grpScreenAspectTop = new QActionGroup(submenu); - - const char* aspectRatiosTop[] = {"Top 4:3 (native)", "Top 16:9", "Top 21:9", "Top window"}; - - for (int i = 0; i < 4; i++) - { - actScreenAspectTop[i] = submenu->addAction(QString(aspectRatiosTop[i])); - actScreenAspectTop[i]->setActionGroup(grpScreenAspectTop); - actScreenAspectTop[i]->setData(QVariant(i)); - actScreenAspectTop[i]->setCheckable(true); - } - - connect(grpScreenAspectTop, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspectTop); - - submenu->addSeparator(); - grpScreenAspectBot = new QActionGroup(submenu); + actScreenAspectTop = new QAction*[sizeof(aspectRatios) / sizeof(aspectRatios[0])]; + actScreenAspectBot = new QAction*[sizeof(aspectRatios) / sizeof(aspectRatios[0])]; - const char* aspectRatiosBot[] = {"Bottom 4:3 (native)", "Bottom 16:9", "Bottom 21:9", "Bottom window"}; - - for (int i = 0; i < 4; i++) + for (int i = 0; i < 2; i++) { - actScreenAspectBot[i] = submenu->addAction(QString(aspectRatiosBot[i])); - actScreenAspectBot[i]->setActionGroup(grpScreenAspectBot); - actScreenAspectBot[i]->setData(QVariant(i)); - actScreenAspectBot[i]->setCheckable(true); - } + QActionGroup* group = grpScreenAspectTop; + QAction** actions = actScreenAspectTop; - connect(grpScreenAspectBot, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspectBot); + if (i == 1) + { + group = grpScreenAspectBot; + submenu->addSeparator(); + actions = actScreenAspectBot; + } + + for (int j = 0; j < sizeof(aspectRatios) / sizeof(aspectRatios[0]); j++) + { + auto ratio = aspectRatios[j]; + QString label = QString("%1 %2").arg(i ? "Bottom" : "Top", ratio.label); + actions[j] = submenu->addAction(label); + actions[j]->setActionGroup(group); + actions[j]->setData(QVariant(ratio.id)); + actions[j]->setCheckable(true); + } + + connect(group, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspect); + } } actScreenFiltering = menu->addAction("Screen filtering"); @@ -1690,6 +1759,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) resize(Config::WindowWidth, Config::WindowHeight); + if (Config::FirmwareUsername == "Arisotura") + actMPNewInstance->setText("Fart"); + #ifdef Q_OS_MAC QPoint screenCenter = screen()->availableGeometry().center(); QRect frameGeo = frameGeometry(); @@ -1756,8 +1828,13 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actScreenSwap->setChecked(Config::ScreenSwap); - actScreenAspectTop[Config::ScreenAspectTop]->setChecked(true); - actScreenAspectBot[Config::ScreenAspectBot]->setChecked(true); + for (int i = 0; i < sizeof(aspectRatios) / sizeof(aspectRatios[0]); i++) + { + if (Config::ScreenAspectTop == aspectRatios[i].id) + actScreenAspectTop[i]->setChecked(true); + if (Config::ScreenAspectBot == aspectRatios[i].id) + actScreenAspectBot[i]->setChecked(true); + } actScreenFiltering->setChecked(Config::ScreenFilter); actShowOSD->setChecked(Config::ShowOSD); @@ -1765,11 +1842,18 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actLimitFramerate->setChecked(Config::LimitFPS); actAudioSync->setChecked(Config::AudioSync); + if (inst > 0) + { + actEmuSettings->setEnabled(false); + actVideoSettings->setEnabled(false); + actMPSettings->setEnabled(false); + actWifiSettings->setEnabled(false); + actInterfaceSettings->setEnabled(false); - - const QList cameras = QCameraInfo::availableCameras(); - for (const QCameraInfo &cameraInfo : cameras) - printf("CAMERAFAZIL: %s\n", cameraInfo.deviceName().toStdString().c_str()); +#ifdef __APPLE__ + actPreferences->setEnabled(false); +#endif // __APPLE__ + } } MainWindow::~MainWindow() @@ -1780,17 +1864,13 @@ void MainWindow::createScreenPanel() { hasOGL = (Config::ScreenUseGL != 0) || (Config::_3DRenderer != 0); - QTimer* mouseTimer; - if (hasOGL) { - panelGL = new ScreenPanelGL(this); + ScreenPanelGL* panelGL = new ScreenPanelGL(this); panelGL->show(); panel = panelGL; - panelGL->setMouseTracking(true); - mouseTimer = panelGL->setupMouseTimer(); - connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) panelGL->setCursor(Qt::BlankCursor);}); + panelWidget = panelGL; if (!panelGL->isValid()) hasOGL = false; @@ -1807,17 +1887,14 @@ void MainWindow::createScreenPanel() if (!hasOGL) { - panelNative = new ScreenPanelNative(this); + ScreenPanelNative* panelNative = new ScreenPanelNative(this); panel = panelNative; - panel->show(); - - panelNative->setMouseTracking(true); - mouseTimer = panelNative->setupMouseTimer(); - connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) panelNative->setCursor(Qt::BlankCursor);}); + panelWidget = panelNative; + panelWidget->show(); } - setCentralWidget(panel); + setCentralWidget(panelWidget); - connect(this, SIGNAL(screenLayoutChange()), panel, SLOT(onScreenLayoutChanged())); + connect(this, SIGNAL(screenLayoutChange()), panelWidget, SLOT(onScreenLayoutChanged())); emit screenLayoutChange(); } @@ -1825,7 +1902,7 @@ QOpenGLContext* MainWindow::getOGLContext() { if (!hasOGL) return nullptr; - QOpenGLWidget* glpanel = (QOpenGLWidget*)panel; + QOpenGLWidget* glpanel = dynamic_cast(panel); return glpanel->context(); } @@ -1967,6 +2044,16 @@ void MainWindow::dropEvent(QDropEvent* event) } } +void MainWindow::focusInEvent(QFocusEvent* event) +{ + audioMute(); +} + +void MainWindow::focusOutEvent(QFocusEvent* event) +{ + audioMute(); +} + void MainWindow::onAppStateChanged(Qt::ApplicationState state) { if (state == Qt::ApplicationInactive) @@ -2639,6 +2726,23 @@ void MainWindow::onOpenTitleManager() TitleManagerDialog* dlg = TitleManagerDialog::openDlg(this); } +void MainWindow::onMPNewInstance() +{ + //QProcess::startDetached(QApplication::applicationFilePath()); + QProcess newinst; + newinst.setProgram(QApplication::applicationFilePath()); + newinst.setArguments(QApplication::arguments().mid(1, QApplication::arguments().length()-1)); + +#ifdef __WIN32__ + newinst.setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args) + { + args->flags |= CREATE_NEW_CONSOLE; + }); +#endif + + newinst.startDetached(); +} + void MainWindow::onOpenEmuSettings() { emuThread->emuPause(); @@ -2779,6 +2883,22 @@ void MainWindow::onAudioSettingsFinished(int res) micOpen(); } +void MainWindow::onOpenMPSettings() +{ + emuThread->emuPause(); + + MPSettingsDialog* dlg = MPSettingsDialog::openDlg(this); + connect(dlg, &MPSettingsDialog::finished, this, &MainWindow::onMPSettingsFinished); +} + +void MainWindow::onMPSettingsFinished(int res) +{ + audioMute(); + LocalMP::SetRecvTimeout(Config::MPRecvTimeout); + + emuThread->emuUnpause(); +} + void MainWindow::onOpenWifiSettings() { emuThread->emuPause(); @@ -2789,12 +2909,6 @@ void MainWindow::onOpenWifiSettings() void MainWindow::onWifiSettingsFinished(int res) { - if (Wifi::MPInited) - { - Platform::MP_DeInit(); - Platform::MP_Init(); - } - Platform::LAN_DeInit(); Platform::LAN_Init(); @@ -2814,10 +2928,7 @@ void MainWindow::onOpenInterfaceSettings() void MainWindow::onUpdateMouseTimer() { - if (hasOGL) - panelGL->mouseTimer->setInterval(Config::MouseHideSeconds*1000); - else - panelNative->mouseTimer->setInterval(Config::MouseHideSeconds*1000); + panel->mouseTimer->setInterval(Config::MouseHideSeconds*1000); } void MainWindow::onInterfaceSettingsFinished(int res) @@ -2833,8 +2944,8 @@ void MainWindow::onChangeSavestateSRAMReloc(bool checked) void MainWindow::onChangeScreenSize() { int factor = ((QAction*)sender())->data().toInt(); - QSize diff = size() - panel->size(); - resize(dynamic_cast(panel)->screenGetMinSize(factor) + diff); + QSize diff = size() - panelWidget->size(); + resize(panel->screenGetMinSize(factor) + diff); } void MainWindow::onChangeScreenRotation(QAction* act) @@ -2892,18 +3003,19 @@ void MainWindow::onChangeScreenSizing(QAction* act) emit screenLayoutChange(); } -void MainWindow::onChangeScreenAspectTop(QAction* act) +void MainWindow::onChangeScreenAspect(QAction* act) { int aspect = act->data().toInt(); - Config::ScreenAspectTop = aspect; + QActionGroup* group = act->actionGroup(); - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenAspectBot(QAction* act) -{ - int aspect = act->data().toInt(); - Config::ScreenAspectBot = aspect; + if (group == grpScreenAspectTop) + { + Config::ScreenAspectTop = aspect; + } + else + { + Config::ScreenAspectBot = aspect; + } emit screenLayoutChange(); } @@ -3006,16 +3118,10 @@ void MainWindow::onUpdateVideoSettings(bool glchange) emuThread->emuPause(); if (hasOGL) - { emuThread->deinitOpenGL(); - delete panelGL; - } - else - { - delete panelNative; - } + delete panel; createScreenPanel(); - connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(repaint())); + connect(emuThread, SIGNAL(windowUpdate()), panelWidget, SLOT(repaint())); if (hasOGL) emuThread->initOpenGL(); } @@ -3121,6 +3227,7 @@ int main(int argc, char** argv) format.setSwapInterval(0); QSurfaceFormat::setDefaultFormat(format); + audioMuted = false; audioSync = SDL_CreateCond(); audioSyncLock = SDL_CreateMutex(); @@ -3176,6 +3283,8 @@ int main(int argc, char** argv) emuThread->start(); emuThread->emuPause(); + audioMute(); + QObject::connect(&melon, &QApplication::applicationStateChanged, mainWindow, &MainWindow::onAppStateChanged); if (argc > 1) @@ -3221,7 +3330,7 @@ int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdsho { int argc = 0; wchar_t** argv_w = CommandLineToArgvW(GetCommandLineW(), &argc); - char* nullarg = ""; + char nullarg[] = {'\0'}; char** argv = new char*[argc]; for (int i = 0; i < argc; i++) @@ -3236,7 +3345,8 @@ int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdsho if (argv_w) LocalFree(argv_w); - /*if (AttachConsole(ATTACH_PARENT_PROCESS)) + //if (AttachConsole(ATTACH_PARENT_PROCESS)) + /*if (AllocConsole()) { freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index 2c3078c3..515ddd77 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -101,7 +101,8 @@ class ScreenHandler Q_GADGET public: - virtual ~ScreenHandler() {} + ScreenHandler(QWidget* widget); + virtual ~ScreenHandler(); QTimer* setupMouseTimer(); void updateMouseTimer(); QTimer* mouseTimer; @@ -121,7 +122,7 @@ protected: int screenKind[Frontend::MaxScreenTransforms]; int numScreens; - bool touching; + bool touching = false; void showCursor(); }; @@ -133,7 +134,7 @@ class ScreenPanelNative : public QWidget, public ScreenHandler public: explicit ScreenPanelNative(QWidget* parent); - ~ScreenPanelNative(); + virtual ~ScreenPanelNative(); protected: void paintEvent(QPaintEvent* event) override; @@ -163,7 +164,7 @@ class ScreenPanelGL : public QOpenGLWidget, public ScreenHandler, protected QOpe public: explicit ScreenPanelGL(QWidget* parent); - ~ScreenPanelGL(); + virtual ~ScreenPanelGL(); protected: void initializeGL() override; @@ -225,6 +226,9 @@ protected: void dragEnterEvent(QDragEnterEvent* event) override; void dropEvent(QDropEvent* event) override; + void focusInEvent(QFocusEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; + signals: void screenLayoutChange(); @@ -254,6 +258,7 @@ private slots: void onROMInfo(); void onRAMInfo(); void onOpenTitleManager(); + void onMPNewInstance(); void onOpenEmuSettings(); void onEmuSettingsDialogFinished(int res); @@ -267,6 +272,8 @@ private slots: void onOpenPathSettings(); void onUpdateAudioSettings(); void onAudioSettingsFinished(int res); + void onOpenMPSettings(); + void onMPSettingsFinished(int res); void onOpenWifiSettings(); void onWifiSettingsFinished(int res); void onFirmwareSettingsFinished(int res); @@ -281,8 +288,7 @@ private slots: void onChangeScreenLayout(QAction* act); void onChangeScreenSwap(bool checked); void onChangeScreenSizing(QAction* act); - void onChangeScreenAspectTop(QAction* act); - void onChangeScreenAspectBot(QAction* act); + void onChangeScreenAspect(QAction* act); void onChangeIntegerScaling(bool checked); void onChangeScreenFiltering(bool checked); void onChangeShowOSD(bool checked); @@ -318,9 +324,8 @@ private: bool oldMax; public: - QWidget* panel; - ScreenPanelGL* panelGL; - ScreenPanelNative* panelNative; + ScreenHandler* panel; + QWidget* panelWidget; QAction* actOpenROM; QAction* actBootFirmware; @@ -346,13 +351,18 @@ public: QAction* actROMInfo; QAction* actRAMInfo; QAction* actTitleManager; + QAction* actMPNewInstance; QAction* actEmuSettings; +#ifdef __APPLE__ + QAction* actPreferences; +#endif QAction* actPowerManagement; QAction* actInputConfig; QAction* actVideoSettings; QAction* actCameraSettings; QAction* actAudioSettings; + QAction* actMPSettings; QAction* actWifiSettings; QAction* actFirmwareSettings; QAction* actPathSettings; @@ -370,9 +380,9 @@ public: QAction* actScreenSizing[6]; QAction* actIntegerScaling; QActionGroup* grpScreenAspectTop; - QAction* actScreenAspectTop[4]; + QAction** actScreenAspectTop; QActionGroup* grpScreenAspectBot; - QAction* actScreenAspectBot[4]; + QAction** actScreenAspectBot; QAction* actScreenFiltering; QAction* actShowOSD; QAction* actLimitFramerate; diff --git a/src/frontend/qt_sdl/sem_timedwait.cpp b/src/frontend/qt_sdl/sem_timedwait.cpp new file mode 100644 index 00000000..38b3c16f --- /dev/null +++ b/src/frontend/qt_sdl/sem_timedwait.cpp @@ -0,0 +1,488 @@ +/* + * s e m _ t i m e d w a i t + * + * Function: + * Implements a version of sem_timedwait(). + * + * Description: + * Not all systems implement sem_timedwait(), which is a version of + * sem_wait() with a timeout. Mac OS X is one example, at least up to + * and including version 10.6 (Leopard). If such a function is needed, + * this code provides a reasonable implementation, which I think is + * compatible with the standard version, although possibly less + * efficient. It works by creating a thread that interrupts a normal + * sem_wait() call after the specified timeout. + * + * Call: + * + * The Linux man pages say: + * + * #include + * + * int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); + * + * sem_timedwait() is the same as sem_wait(), except that abs_timeout + * specifies a limit on the amount of time that the call should block if + * the decrement cannot be immediately performed. The abs_timeout argument + * points to a structure that specifies an absolute timeout in seconds and + * nanoseconds since the Epoch (00:00:00, 1 January 1970). This structure + * is defined as follows: + * + * struct timespec { + * time_t tv_sec; Seconds + * long tv_nsec; Nanoseconds [0 .. 999999999] + * }; + * + * If the timeout has already expired by the time of the call, and the + * semaphore could not be locked immediately, then sem_timedwait() fails + * with a timeout error (errno set to ETIMEDOUT). + * If the operation can be performed immediately, then sem_timedwait() + * never fails with a timeout error, regardless of the value of abs_timeout. + * Furthermore, the validity of abs_timeout is not checked in this case. + * + * Limitations: + * + * The mechanism used involves sending a SIGUSR2 signal to the thread + * calling sem_timedwait(). The handler for this signal is set to a null + * routine which does nothing, and with any flags for the signal + * (eg SA_RESTART) cleared. Note that this effective disabling of the + * SIGUSR2 signal is a side-effect of using this routine, and means it + * may not be a completely transparent plug-in replacement for a + * 'normal' sig_timedwait() call. Since OS X does not declare the + * sem_timedwait() call in its standard include files, the relevant + * declaration (shown above in the man pages extract) will probably have + * to be added to any code that uses this. + * + * Compiling: + * This compiles and runs cleanly on OS X (10.6) with gcc with the + * -Wall -ansi -pedantic flags. On Linux, using -ansi causes a sweep of + * compiler complaints about the timespec structure, but it compiles + * and works fine with just -Wall -pedantic. (Since Linux provides + * sem_timedwait() anyway, this really isn't needed on Linux.) However, + * since Linux provides sem_timedwait anyway, the sem_timedwait() + * code in this file is only compiled on OS X, and is a null on other + * systems. + * + * Testing: + * This file contains a test program that exercises the sem_timedwait + * code. It is compiled if the pre-processor variable TEST is defined. + * For more details, see the comments for the test routine at the end + * of the file. + * + * Author: Keith Shortridge, AAO. + * + * History: + * 8th Sep 2009. Original version. KS. + * 24th Sep 2009. Added test that the calling thread still exists before + * trying to set the timed-out flag. KS. + * 2nd Oct 2009. No longer restores the original SIGUSR2 signal handler. + * See comments in the body of the code for more details. + * Prototypes for now discontinued internal routines removed. + * 12th Aug 2010. Added the cleanup handler, so that this code no longer + * leaks resources if the calling thread is cancelled. KS. + * 21st Sep 2011. Added copyright notice below. Modified header comments + * to describe the use of SIGUSR2 more accurately in the + * light of the 2/10/09 change above. Now undefs DEBUG + * before defining it, to avoid any possible clash. KS. + * 14th Feb 2012. Tidied out a number of TABs that had got into the + * code. KS. + * 6th May 2013. Copyright notice modified to one based on the MIT licence, + * which is more permissive than the previous notice. KS. + * + * Copyright (c) Australian Astronomical Observatory (AAO), (2013). + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifdef __APPLE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sem_timedwait.h" + +/* Some useful definitions - TRUE, FALSE, and DEBUG */ + +#undef TRUE +#define TRUE 1 +#undef FALSE +#define FALSE 0 +#undef DEBUG +#define DEBUG printf + +/* A structure of type timeoutDetails is passed to the thread used to + * implement the timeout. + */ + +typedef struct { + struct timespec delay; /* Specifies the delay, relative to now */ + pthread_t callingThread; /* The thread doing the sem_wait call */ + volatile short *timedOutShort; /* Address of a flag set to indicate that + * the timeout was triggered. */ +} timeoutDetails; + +/* A structure of type cleanupDetails is passed to the thread cleanup + * routine which is called at the end of the routine or if the thread calling + * it is cancelled. + */ + +typedef struct { + pthread_t *threadIdAddr; /* Address of the variable that holds + * the Id of the timeout thread. */ + struct sigaction *sigHandlerAddr; /* Address of the old signal action + * handler. */ + volatile short *timedOutShort; /* Address of a flag set to indicate that + * the timeout was triggered. */ +} cleanupDetails; + +/* Forward declarations of internal routines */ + +static void* timeoutThreadMain (void* passedPtr); +static int triggerSignal (int Signal, pthread_t Thread); +static void ignoreSignal (int Signal); +static void timeoutThreadCleanup (void* passedPtr); + +/* -------------------------------------------------------------------------- */ +/* + * s e m _ t i m e d w a i t + * + * This is the main code for the sem_timedwait() implementation. + */ + +int sem_timedwait ( + sem_t *sem, + const struct timespec *abs_timeout) +{ + int result = 0; /* Code returned by this routine 0 or -1 */ + + /* "Under no circumstances shall the function fail if the semaphore + * can be locked immediately". So we try to get it quickly to see if we + * can avoid all the timeout overheads. + */ + + if (sem_trywait(sem) == 0) { + + /* Yes, got it immediately. */ + + result = 0; + + } else { + + /* No, we've got to do it with a sem_wait() call and a thread to run + * the timeout. First, work out the time from now to the specified + * timeout, which we will pass to the timeout thread in a way that can + * be used to pass to nanosleep(). So we need this in seconds and + * nanoseconds. Along the way, we check for an invalid passed time, + * and for one that's already expired. + */ + + if ((abs_timeout->tv_nsec < 0) || (abs_timeout->tv_nsec > 1000000000)) { + + /* Passed time is invalid */ + + result = -1; + errno = EINVAL; + + } else { + + struct timeval currentTime; /* Time now */ + long secsToWait,nsecsToWait; /* Seconds and nsec to delay */ + gettimeofday (¤tTime,NULL); + secsToWait = abs_timeout->tv_sec - currentTime.tv_sec; + nsecsToWait = (abs_timeout->tv_nsec - (currentTime.tv_usec * 1000)); + while (nsecsToWait < 0) { + nsecsToWait += 1000000000; + secsToWait--; + } + if ((secsToWait < 0) || ((secsToWait == 0) && (nsecsToWait < 0))) { + + /* Time has passed. Report an immediate timeout. */ + + result = -1; + errno = ETIMEDOUT; + + } else { + + /* We're going to have to do a sem_wait() with a timeout thread. + * The thread will wait the specified time, then will issue a + * SIGUSR2 signal that will interrupt the sem_wait() call. + * We pass the thread the id of the current thread, the delay, + * and the address of a flag to set on a timeout, so we can + * distinguish an interrupt caused by the timeout thread from + * one caused by some other signal. + */ + + volatile short timedOut; /* Flag to set on timeout */ + timeoutDetails details; /* All the stuff the thread must know */ + struct sigaction oldSignalAction; /* Current signal setting */ + pthread_t timeoutThread; /* Id of timeout thread */ + cleanupDetails cleaningDetails; /* What the cleanup routine needs */ + int oldCancelState; /* Previous cancellation state */ + int ignoreCancelState; /* Used in call, but ignored */ + int createStatus; /* Status of pthread_create() call */ + + /* If the current thread is cancelled (and CML does do this) + * we don't want to leave our timer thread running - if we've + * started the thread we want to make sure we join it in order + * to release its resources. So we set a cleanup handler to + * do this. We pass it the address of the structure that will + * hold all it needs to know. While we set all this up, + * we prevent ourselves being cancelled, so all this data is + * coherent. + */ + + pthread_setcancelstate (PTHREAD_CANCEL_DISABLE,&oldCancelState); + timeoutThread = (pthread_t) 0; + cleaningDetails.timedOutShort = &timedOut; + cleaningDetails.threadIdAddr = &timeoutThread; + cleaningDetails.sigHandlerAddr = &oldSignalAction; + pthread_cleanup_push (timeoutThreadCleanup,&cleaningDetails); + + /* Set up the details for the thread. Clear the timeout flag, + * record the current SIGUSR2 action settings so we can restore + * them later. + */ + + details.delay.tv_sec = secsToWait; + details.delay.tv_nsec = nsecsToWait; + details.callingThread = pthread_self(); + details.timedOutShort = &timedOut; + timedOut = FALSE; + sigaction (SIGUSR2,NULL,&oldSignalAction); + + /* Start up the timeout thread. Once we've done that, we can + * restore the previous cancellation state. + */ + + createStatus = pthread_create(&timeoutThread,NULL, + timeoutThreadMain, (void*)&details); + pthread_setcancelstate (oldCancelState,&ignoreCancelState); + + if (createStatus < 0) { + + /* Failed to create thread. errno will already be set properly */ + + result = -1; + + } else { + + /* Thread created OK. This is where we wait for the semaphore. + */ + + if (sem_wait(sem) == 0) { + + /* Got the semaphore OK. We return zero, and all's well. */ + + result = 0; + + } else { + + /* If we got a -1 error from sem_wait(), it may be because + * it was interrupted by a timeout, or failed for some + * other reason. We check for the expected timeout + * condition, which is an 'interrupted' status and the + * timeout flag set by the timeout thread. We report that as + * a timeout error. Anything else is some other error and + * errno is already set properly. + */ + + result = -1; + if (errno == EINTR) { + if (timedOut) errno = ETIMEDOUT; + } + } + + } + + /* The cleanup routine - timeoutThreadCleanup() - packages up + * any tidying up that is needed, including joining with the + * timer thread. This will be called if the current thread is + * cancelled, but we need it to happen anyway, so we set the + * execute flag true here as we remove it from the list of + * cleanup routines to be called. So normally, this line amounts + * to calling timeoutThreadCleanup(). + */ + + pthread_cleanup_pop (TRUE); + } + } + } + return (result); +} + +/* -------------------------------------------------------------------------- */ +/* + * t i m e o u t T h r e a d C l e a n u p + * + * This internal routine tidies up at the end of a sem_timedwait() call. + * It is set as a cleanup routine for the current thread (not the timer + * thread) so it is executed even if the thread is cancelled. This is + * important, as we need to tidy up the timeout thread. If we took the + * semaphore (in other words, if we didn't timeout) then the timer thread + * will still be running, sitting in its nanosleep() call, and we need + * to cancel it. If the timer thread did signal a timeout then it will + * now be closing down. In either case, we need to join it (using a call + * to pthread_join()) or its resources will never be released. + * The single argument is a pointer to a cleanupDetails structure that has + * all the routine needs to know. + */ + +static void timeoutThreadCleanup (void* passedPtr) +{ + /* Get what we need from the structure we've been passed. */ + + cleanupDetails *detailsPtr = (cleanupDetails*) passedPtr; + short timedOut = *(detailsPtr->timedOutShort); + pthread_t timeoutThread = *(detailsPtr->threadIdAddr); + + /* If we created the thread, stop it - doesn't matter if it's no longer + * running, pthread_cancel can handle that. We make sure we wait for it + * to complete, because it is this pthread_join() call that releases any + * memory the thread may have allocated. Note that cancelling a thread is + * generally not a good idea, because of the difficulty of cleaning up + * after it, but this is a very simple thread that does nothing but call + * nanosleep(), and that we can cancel quite happily. + */ + + if (!timedOut) pthread_cancel(timeoutThread); + pthread_join(timeoutThread,NULL); + + /* The code originally restored the old action handler, which generally + * was the default handler that caused the task to exit. Just occasionally, + * there seem to be cases where the signal is still queued and ready to + * trigger even though the thread that presumably sent it off just before + * it was cancelled has finished. I had thought that once we'd joined + * that thread, we could be sure of not seeing the signal, but that seems + * not to be the case, and so restoring a handler that will allow the task + * to crash is not a good idea, and so the line below has been commented + * out. + * + * sigaction (SIGUSR2,detailsPtr->sigHandlerAddr,NULL); + */ +} + +/* -------------------------------------------------------------------------- */ +/* + * t i m e o u t T h r e a d M a i n + * + * This internal routine is the main code for the timeout thread. + * The single argument is a pointer to a timeoutDetails structure that has + * all the thread needs to know - thread to signal, delay time, and the + * address of a flag to set if it triggers a timeout. + */ + +static void* timeoutThreadMain (void* passedPtr) +{ + void* Return = (void*) 0; + + /* We grab all the data held in the calling thread right now. In some + * cases, we find that the calling thread has vanished and released + * its memory, including the details structure, by the time the timeout + * expires, and then we get an access violation when we try to set the + * 'timed out' flag. + */ + + timeoutDetails details = *((timeoutDetails*) passedPtr); + struct timespec requestedDelay = details.delay; + + /* We do a nanosleep() for the specified delay, and then trigger a + * timeout. Note that we allow for the case where the nanosleep() is + * interrupted, and restart it for the remaining time. If the + * thread that is doing the sem_wait() call gets the semaphore, it + * will cancel this thread, which is fine as we aren't doing anything + * other than a sleep and a signal. + */ + + for (;;) { + struct timespec remainingDelay; + if (nanosleep (&requestedDelay,&remainingDelay) == 0) { + break; + } else if (errno == EINTR) { + requestedDelay = remainingDelay; + } else { + Return = (void*) errno; + break; + } + } + + /* We've completed the delay without being cancelled, so we now trigger + * the timeout by sending a signal to the calling thread. And that's it, + * although we set the timeout flag first to indicate that it was us + * that interrupted the sem_wait() call. One precaution: before we + * try to set the timed-out flag, make sure the calling thread still + * exists - this may not be the case if things are closing down a bit + * messily. We check this quickly using a zero test signal. + */ + + if (pthread_kill(details.callingThread,0) == 0) { + *(details.timedOutShort) = TRUE; + if (triggerSignal (SIGUSR2,details.callingThread) < 0) { + Return = (void*) errno; + } + } + + return Return; +} + +/* -------------------------------------------------------------------------- */ +/* + * t r i g g e r S i g n a l + * + * This is a general purpose routine that sends a specified signal to + * a specified thread, setting up a signal handler that does nothing, + * and then giving the signal. The only effect will be to interrupt any + * operation that is currently blocking - in this case, we expect this to + * be a sem_wait() call. + */ + +static int triggerSignal (int Signal, pthread_t Thread) +{ + int Result = 0; + struct sigaction SignalDetails; + SignalDetails.sa_handler = ignoreSignal; + SignalDetails.sa_flags = 0; + (void) sigemptyset(&SignalDetails.sa_mask); + if ((Result = sigaction(Signal,&SignalDetails,NULL)) == 0) { + Result = pthread_kill(Thread,Signal); + } + return Result; +} + +/* -------------------------------------------------------------------------- */ +/* + * i g n o r e S i g n a l + * + * And this is the signal handler that does nothing. (It clears its argument, + * but this has no effect and prevents a compiler warning about an unused + * argument.) + */ + +static void ignoreSignal (int Signal) { + Signal = 0; +} + +#endif diff --git a/src/frontend/qt_sdl/sem_timedwait.h b/src/frontend/qt_sdl/sem_timedwait.h new file mode 100644 index 00000000..42ae201e --- /dev/null +++ b/src/frontend/qt_sdl/sem_timedwait.h @@ -0,0 +1,8 @@ +#ifndef __SEM_TIMEDWAIT_H +#define __SEM_TIMEDWAIT_H + +#ifdef __APPLE__ +int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); +#endif + +#endif diff --git a/src/melonDLDI.h b/src/melonDLDI.h index 351bc158..261f798b 100644 --- a/src/melonDLDI.h +++ b/src/melonDLDI.h @@ -19,6 +19,8 @@ #ifndef MELONDLDI_H #define MELONDLDI_H +#include "types.h" + const u8 melonDLDI[] = { 0xED, 0xA5, 0x8D, 0xBF, 0x20, 0x43, 0x68, 0x69, 0x73, 0x68, 0x6D, 0x00, 0x01, 0x09, 0x00, 0x00, diff --git a/src/teakra/CMakeLists.txt b/src/teakra/CMakeLists.txt index 046f8c45..e4ecb1af 100644 --- a/src/teakra/CMakeLists.txt +++ b/src/teakra/CMakeLists.txt @@ -58,6 +58,7 @@ else() -pedantic -pedantic-errors -Wfatal-errors + -Wno-error=maybe-uninitialized -Wno-missing-braces -Wno-unused-parameter) diff --git a/src/teakra/externals/catch/catch.hpp b/src/teakra/externals/catch/catch.hpp index b1b2411d..71c863ab 100644 --- a/src/teakra/externals/catch/catch.hpp +++ b/src/teakra/externals/catch/catch.hpp @@ -1,9 +1,9 @@ /* - * Catch v2.5.0 - * Generated: 2018-11-26 20:46:12.165372 + * Catch v2.13.8 + * Generated: 2022-01-03 21:20:09.589503 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2018 Two Blue Cubes Ltd. All rights reserved. + * Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -14,8 +14,8 @@ #define CATCH_VERSION_MAJOR 2 -#define CATCH_VERSION_MINOR 5 -#define CATCH_VERSION_PATCH 0 +#define CATCH_VERSION_MINOR 13 +#define CATCH_VERSION_PATCH 8 #ifdef __clang__ # pragma clang system_header @@ -36,10 +36,11 @@ # pragma clang diagnostic ignored "-Wcovered-switch-default" # endif #elif defined __GNUC__ - // GCC likes to warn on REQUIREs, and we cannot suppress them - // locally because g++'s support for _Pragma is lacking in older, - // still supported, versions -# pragma GCC diagnostic ignored "-Wparentheses" + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details + # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wunused-variable" # pragma GCC diagnostic ignored "-Wpadded" @@ -65,13 +66,16 @@ #if !defined(CATCH_CONFIG_IMPL_ONLY) // start catch_platform.h +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html #ifdef __APPLE__ -# include -# if TARGET_OS_OSX == 1 -# define CATCH_PLATFORM_MAC -# elif TARGET_OS_IPHONE == 1 -# define CATCH_PLATFORM_IPHONE -# endif +# include +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +# define CATCH_PLATFORM_MAC +# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +# define CATCH_PLATFORM_IPHONE +# endif #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX @@ -131,30 +135,51 @@ namespace Catch { #endif -#if defined(CATCH_CPP17_OR_GREATER) -# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) + +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) + #endif -#ifdef __clang__ +#if defined(__clang__) -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic push" ) \ - _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ - _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") -# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic pop" ) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic push" ) \ - _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) -# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic pop" ) +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +# if !defined(__ibmxl__) && !defined(__CUDACC__) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ +# endif -# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ - _Pragma( "clang diagnostic push" ) \ - _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) -# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS \ - _Pragma( "clang diagnostic pop" ) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) #endif // __clang__ @@ -179,6 +204,7 @@ namespace Catch { // Android somehow still does not support std::to_string #if defined(__ANDROID__) # define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE #endif //////////////////////////////////////////////////////////////////////////////// @@ -203,20 +229,16 @@ namespace Catch { // some versions of cygwin (most) do not support std::to_string. Use the libstd check. // https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 # if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ - && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) -# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING # endif #endif // __CYGWIN__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ -#ifdef _MSC_VER - -# if _MSC_VER >= 1900 // Visual Studio 2015 or newer -# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS -# endif +#if defined(_MSC_VER) // Universal Windows platform does not support SEH // Or console colours (or console at all...) @@ -226,13 +248,25 @@ namespace Catch { # define CATCH_INTERNAL_CONFIG_WINDOWS_SEH # endif +# if !defined(__clang__) // Handle Clang masquerading for msvc + // MSVC traditional preprocessor needs some workaround for __VA_ARGS__ // _MSVC_TRADITIONAL == 0 means new conformant preprocessor // _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor -# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) -# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -# endif +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL +// Only do this if we're not using clang on Windows, which uses `diagnostic push` & `diagnostic pop` +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) +# endif // __clang__ + +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC #endif // _MSC_VER //////////////////////////////////////////////////////////////////////////////// @@ -265,30 +299,56 @@ namespace Catch { #endif //////////////////////////////////////////////////////////////////////////////// -// Check if string_view is available and usable -// The check is split apart to work around v140 (VS2015) preprocessor issue... -#if defined(__has_include) -#if __has_include() && defined(CATCH_CPP17_OR_GREATER) -# define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW -#endif + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_CONFIG_COLOUR_NONE #endif -//////////////////////////////////////////////////////////////////////////////// -// Check if variant is available and usable +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include #if defined(__has_include) -# if __has_include() && defined(CATCH_CPP17_OR_GREATER) -# if defined(__clang__) && (__clang_major__ < 8) - // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 - // fix should be in clang 8, workaround in libstdc++ 8.2 -# include -# if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) -# define CATCH_CONFIG_NO_CPP17_VARIANT -# else -# define CATCH_INTERNAL_CONFIG_CPP17_VARIANT -# endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) -# endif // defined(__clang__) && (__clang_major__ < 8) -# endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) -#endif // __has_include + // Check if string_view is available and usable + #if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # include + # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) #if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) # define CATCH_CONFIG_COUNTER @@ -309,8 +369,8 @@ namespace Catch { # define CATCH_CONFIG_CPP11_TO_STRING #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) -# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) @@ -321,6 +381,10 @@ namespace Catch { # define CATCH_CONFIG_CPP17_VARIANT #endif +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + #if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) # define CATCH_INTERNAL_CONFIG_NEW_CAPTURE #endif @@ -337,17 +401,53 @@ namespace Catch { # define CATCH_CONFIG_POLYFILL_ISNAN #endif +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) +# define CATCH_CONFIG_ANDROID_LOGWRITE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif #if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS -# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS -# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS -# define CATCH_INTERNAL_UNSUPPRESS_UNUSED_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif + +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #endif #if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) @@ -407,12 +507,12 @@ namespace Catch { line( _line ) {} - SourceLineInfo( SourceLineInfo const& other ) = default; - SourceLineInfo( SourceLineInfo && ) = default; - SourceLineInfo& operator = ( SourceLineInfo const& ) = default; - SourceLineInfo& operator = ( SourceLineInfo && ) = default; + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; - bool empty() const noexcept; + bool empty() const noexcept { return file[0] == '\0'; } bool operator == ( SourceLineInfo const& other ) const noexcept; bool operator < ( SourceLineInfo const& other ) const noexcept; @@ -453,9 +553,10 @@ namespace Catch { } // end namespace Catch #define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION // end catch_tag_alias_autoregistrar.h // start catch_test_registry.h @@ -463,7 +564,6 @@ namespace Catch { // start catch_interfaces_testcase.h #include -#include namespace Catch { @@ -474,8 +574,6 @@ namespace Catch { virtual ~ITestInvoker(); }; - using ITestCasePtr = std::shared_ptr; - class TestCase; struct IConfig; @@ -485,6 +583,7 @@ namespace Catch { virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; }; + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); std::vector const& getAllTestCasesSorted( IConfig const& config ); @@ -497,55 +596,30 @@ namespace Catch { #include #include #include +#include namespace Catch { - class StringData; - /// A non-owning string class (similar to the forthcoming std::string_view) /// Note that, because a StringRef may be a substring of another string, - /// it may not be null terminated. c_str() must return a null terminated - /// string, however, and so the StringRef will internally take ownership - /// (taking a copy), if necessary. In theory this ownership is not externally - /// visible - but it does mean (substring) StringRefs should not be shared between - /// threads. + /// it may not be null terminated. class StringRef { public: using size_type = std::size_t; + using const_iterator = const char*; private: - friend struct StringRefTestAccess; - - char const* m_start; - size_type m_size; - - char* m_data = nullptr; - - void takeOwnership(); - static constexpr char const* const s_empty = ""; - public: // construction/ assignment - StringRef() noexcept - : StringRef( s_empty, 0 ) - {} + char const* m_start = s_empty; + size_type m_size = 0; - StringRef( StringRef const& other ) noexcept - : m_start( other.m_start ), - m_size( other.m_size ) - {} - - StringRef( StringRef&& other ) noexcept - : m_start( other.m_start ), - m_size( other.m_size ), - m_data( other.m_data ) - { - other.m_data = nullptr; - } + public: // construction + constexpr StringRef() noexcept = default; StringRef( char const* rawChars ) noexcept; - StringRef( char const* rawChars, size_type size ) noexcept + constexpr StringRef( char const* rawChars, size_type size ) noexcept : m_start( rawChars ), m_size( size ) {} @@ -555,99 +629,64 @@ namespace Catch { m_size( stdString.size() ) {} - ~StringRef() noexcept { - delete[] m_data; + explicit operator std::string() const { + return std::string(m_start, m_size); } - auto operator = ( StringRef const &other ) noexcept -> StringRef& { - delete[] m_data; - m_data = nullptr; - m_start = other.m_start; - m_size = other.m_size; - return *this; - } - - operator std::string() const; - - void swap( StringRef& other ) noexcept; - public: // operators auto operator == ( StringRef const& other ) const noexcept -> bool; - auto operator != ( StringRef const& other ) const noexcept -> bool; + auto operator != (StringRef const& other) const noexcept -> bool { + return !(*this == other); + } - auto operator[] ( size_type index ) const noexcept -> char; + auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } public: // named queries - auto empty() const noexcept -> bool { + constexpr auto empty() const noexcept -> bool { return m_size == 0; } - auto size() const noexcept -> size_type { + constexpr auto size() const noexcept -> size_type { return m_size; } - auto numberOfCharacters() const noexcept -> size_type; + // Returns the current start pointer. If the StringRef is not + // null-terminated, throws std::domain_exception auto c_str() const -> char const*; public: // substrings and searches - auto substr( size_type start, size_type size ) const noexcept -> StringRef; + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + auto substr( size_type start, size_type length ) const noexcept -> StringRef; - // Returns the current start pointer. - // Note that the pointer can change when if the StringRef is a substring - auto currentData() const noexcept -> char const*; + // Returns the current start pointer. May not be null-terminated. + auto data() const noexcept -> char const*; - private: // ownership queries - may not be consistent between calls - auto isOwned() const noexcept -> bool; - auto isSubstring() const noexcept -> bool; + constexpr auto isNullTerminated() const noexcept -> bool { + return m_start[m_size] == '\0'; + } + + public: // iterators + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } }; - auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string; - auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string; - auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string; - auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; - inline auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { return StringRef( rawChars, size ); } - } // namespace Catch -inline auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { +constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { return Catch::StringRef( rawChars, size ); } // end catch_stringref.h -// start catch_type_traits.hpp - - -namespace Catch{ - -#ifdef CATCH_CPP17_OR_GREATER - template - inline constexpr auto is_unique = std::true_type{}; - - template - inline constexpr auto is_unique = std::bool_constant< - (!std::is_same_v && ...) && is_unique - >{}; -#else - -template -struct is_unique : std::true_type{}; - -template -struct is_unique : std::integral_constant -::value - && is_unique::value - && is_unique::value ->{}; - -#endif -} - -// end catch_type_traits.hpp // start catch_preprocessor.hpp @@ -699,21 +738,224 @@ struct is_unique : std::integral_constant #define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ #define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ #define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) #define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) -#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name, __VA_ARGS__) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name,...) Name " - " #__VA_ARGS__ -#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME(Name,...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) #else -// MSVC is adding extra space and needs more calls to properly remove () -#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME3(Name,...) Name " -" #__VA_ARGS__ -#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME1(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME2(Name, __VA_ARGS__) -#define INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME(Name, ...) INTERNAL_CATCH_TEMPLATE_UNIQUE_NAME1(Name, INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template class...> struct TemplateTypeList{};\ + template class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + template\ + struct append;\ + template\ + struct rewrap;\ + template class, typename...>\ + struct create;\ + template class, typename>\ + struct convert;\ + \ + template \ + struct append { using type = T; };\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ + template< template class L1, typename...E1, typename...Rest>\ + struct append, TypeList, Rest...> { using type = L1; };\ + \ + template< template class Container, template class List, typename...elems>\ + struct rewrap, List> { using type = TypeList>; };\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ + \ + template