diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..8b206540 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +# Vendored Dependencies +src/frontend/glad/** linguist-vendored +src/frontend/qt_sdl/gif-h/** linguist-vendored +src/frontend/qt_sdl/toml/** linguist-vendored +src/net/libslirp/** linguist-vendored +src/net/pcap/** linguist-vendored +src/sha1/** linguist-vendored +src/teakra/** linguist-vendored +src/tiny-AES-c/** linguist-vendored +src/xxhash/** linguist-vendored + +# A handful of custom files embedded in the vendored dependencies + +## Ad-hoc CMakeLists.txt for melonDS +src/net/libslirp/src/CMakeLists.txt -linguist-vendored + +## glib stub +src/net/libslirp/src/glib/** -linguist-vendored diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml deleted file mode 100644 index be4494e8..00000000 --- a/.github/workflows/build-appimage.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: AppImage - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - build: - - runs-on: ubuntu-20.04 - - steps: - - uses: actions/checkout@v1 - - name: Install dependencies - run: | - sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list - sudo apt update - sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev libqt5multimedia5-plugins qt5-default qtbase5-private-dev qtmultimedia5-dev libslirp0 libslirp-dev libarchive-dev zstd libzstd-dev --allow-downgrades - - name: Create build environment - run: mkdir ${{runner.workspace}}/build - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE - - name: Make - working-directory: ${{runner.workspace}}/build - run: | - make -j$(nproc --all) - - name: Prepare AppDir for AppImage - working-directory: ${{runner.workspace}}/build - run: | - make install DESTDIR=AppDir - mv ./AppDir/usr/local/bin ./AppDir/usr/bin - mv ./AppDir/usr/local/share ./AppDir/usr/share - rm -rf ./AppDir/usr/local - - name: Prepare necessary Tools for building the AppImage - working-directory: ${{runner.workspace}}/build - run: | - wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage - wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage - chmod a+x linuxdeploy-x86_64.AppImage - chmod a+x linuxdeploy-plugin-qt-x86_64.AppImage - - name: Build the AppImage - working-directory: ${{runner.workspace}}/build - run: | - ./linuxdeploy-x86_64.AppImage --appdir AppDir --plugin qt --output appimage - mkdir dist - cp ./melonDS*.AppImage ./dist - - uses: actions/upload-artifact@v1 - with: - name: melonDS-appimage-x86_64 - path: ${{runner.workspace}}/build/dist diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 6d5693a1..f47b3a4a 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -4,10 +4,16 @@ on: push: branches: - master + - ci/vcpkg-update pull_request: branches: - master +env: + MELONDS_GIT_BRANCH: ${{ github.ref }} + MELONDS_GIT_HASH: ${{ github.sha }} + MELONDS_BUILD_PROVIDER: GitHub Actions + jobs: build-macos: strategy: @@ -15,24 +21,25 @@ jobs: arch: [x86_64, arm64] name: ${{ matrix.arch }} - runs-on: macos-13 + runs-on: macos-14 steps: - name: Check out sources uses: actions/checkout@v3 - name: Install dependencies for package building run: | - brew install autoconf automake autoconf-archive libtool && pip3 install setuptools + brew install autoconf automake autoconf-archive libtool python-setuptools - name: Set up CMake uses: lukka/get-cmake@latest - name: Set up vcpkg uses: lukka/run-vcpkg@v11 with: - vcpkgGitCommitId: c8696863d371ab7f46e213d8f5ca923c4aef2a00 + vcpkgGitCommitId: 10b7a178346f3f0abef60cecd5130e295afd8da4 - name: Build uses: lukka/run-cmake@v10 with: configurePreset: release-mac-${{ matrix.arch }} buildPreset: release-mac-${{ matrix.arch }} + configurePresetAdditionalArgs: "['-DMELONDS_EMBED_BUILD_INFO=ON']" - name: Compress app bundle shell: bash run: | @@ -43,11 +50,13 @@ jobs: with: name: macOS-${{ matrix.arch }} path: macOS-${{ matrix.arch }}.zip + retention-days: 1 universal-binary: name: Universal binary needs: [build-macos] runs-on: macos-13 + continue-on-error: true steps: - name: Download x86_64 uses: actions/download-artifact@v4 @@ -74,11 +83,10 @@ jobs: with: name: macOS-universal path: macOS-universal.zip - - name: Clean up architecture-specific artifacts - uses: geekyeggo/delete-artifact@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - failOnError: false - name: | - macOS-x86_64 - macOS-arm64 +# - name: Clean up architecture-specific artifacts +# uses: geekyeggo/delete-artifact@v4 +# with: +# failOnError: false +# name: | +# macOS-x86_64 +# macOS-arm64 diff --git a/.github/workflows/build-ubuntu-aarch64.yml b/.github/workflows/build-ubuntu-aarch64.yml deleted file mode 100644 index 43f4d8b6..00000000 --- a/.github/workflows/build-ubuntu-aarch64.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Ubuntu - -on: - push: - branches: - - master - pull_request: - branches: - - master - -env: - BUILD_TYPE: Release - -jobs: - build: - name: aarch64 - runs-on: ubuntu-20.04 - container: ubuntu:20.04 - - steps: - - name: Prepare system - shell: bash - run: | - apt update - apt -y full-upgrade - apt -y install git - - name: Check out source - uses: actions/checkout@v1 - - name: Install dependencies - shell: bash - run: | - dpkg --add-architecture arm64 - sh -c "sed \"s|^deb \([a-z\.:/]*\) \([a-z\-]*\) \(.*\)$|deb [arch=amd64] \1 \2 \3\ndeb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports \2 \3|\" /etc/apt/sources.list > /etc/apt/sources.list.new" - rm /etc/apt/sources.list - mv /etc/apt/sources.list{.new,} - apt update - DEBIAN_FRONTEND=noninteractive apt install -y {gcc-10,g++-10,pkg-config}-aarch64-linux-gnu {libsdl2,qtbase5,qtbase5-private,qtmultimedia5,libslirp,libarchive,libzstd}-dev:arm64 zstd:arm64 cmake extra-cmake-modules dpkg-dev - - name: Configure - shell: bash - run: | - CC=aarch64-linux-gnu-gcc-10 CXX=aarch64-linux-gnu-g++-10 cmake -DPKG_CONFIG_EXECUTABLE=/usr/bin/aarch64-linux-gnu-pkg-config $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -B build - - name: Make - shell: bash - run: | - cmake --build build -j$(nproc --all) - mkdir dist - cp build/melonDS dist - - uses: actions/upload-artifact@v1 - with: - name: melonDS-ubuntu-aarch64 - path: dist diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 438fddd0..044d01ee 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -8,31 +8,84 @@ on: branches: - master -jobs: - build: - name: x86_64 +env: + MELONDS_GIT_BRANCH: ${{ github.ref }} + MELONDS_GIT_HASH: ${{ github.sha }} + MELONDS_BUILD_PROVIDER: GitHub Actions - runs-on: ubuntu-20.04 +jobs: + build-x86_64: + name: x86_64 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 + name: Check out sources - name: Install dependencies run: | sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list sudo apt update - sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default qtbase5-private-dev qtmultimedia5-dev libslirp0 libslirp-dev libarchive-dev zstd libzstd-dev --allow-downgrades - - name: Create build environment - run: mkdir ${{runner.workspace}}/build + sudo apt install --allow-downgrades cmake ninja-build extra-cmake-modules libpcap0.8-dev libsdl2-dev libenet-dev \ + qt6-{base,base-private,multimedia}-dev libqt6svg6-dev libarchive-dev libzstd-dev libfuse2 - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE - - name: Make - working-directory: ${{runner.workspace}}/build + run: cmake -B build -G Ninja -DUSE_QT6=ON -DCMAKE_INSTALL_PREFIX=/usr -DMELONDS_EMBED_BUILD_INFO=ON + - name: Build run: | - make -j$(nproc --all) - mkdir dist - cp melonDS dist - - uses: actions/upload-artifact@v1 + cmake --build build + DESTDIR=AppDir cmake --install build + - uses: actions/upload-artifact@v4 with: name: melonDS-ubuntu-x86_64 - path: ${{runner.workspace}}/build/dist + path: AppDir/usr/bin/melonDS + - name: Fetch AppImage tools + run: | + wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage + chmod a+x linuxdeploy-*.AppImage + - name: Build the AppImage + env: + QMAKE: /usr/lib/qt6/bin/qmake + run: | + ./linuxdeploy-x86_64.AppImage --appdir AppDir --plugin qt --output appimage + - uses: actions/upload-artifact@v4 + with: + name: melonDS-appimage-x86_64 + path: melonDS*.AppImage + + build-aarch64: + name: aarch64 + runs-on: ubuntu-latest + container: ubuntu:22.04 + + steps: + - name: Prepare system + shell: bash + run: | + dpkg --add-architecture arm64 + sh -c "sed \"s|^deb \([a-z\.:/]*\) \([a-z\-]*\) \(.*\)$|deb [arch=amd64] \1 \2 \3\ndeb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports \2 \3|\" /etc/apt/sources.list > /etc/apt/sources.list.new" + rm /etc/apt/sources.list + mv /etc/apt/sources.list{.new,} + apt update + apt -y full-upgrade + apt -y install git {gcc-12,g++-12}-aarch64-linux-gnu cmake ninja-build extra-cmake-modules \ + {libsdl2,qt6-{base,base-private,multimedia},libqt6svg6,libarchive,libzstd,libenet}-dev:arm64 \ + pkg-config dpkg-dev + - name: Check out source + uses: actions/checkout@v4 + - name: Configure + shell: bash + run: | + cmake -B build -G Ninja \ + -DPKG_CONFIG_EXECUTABLE=/usr/bin/aarch64-linux-gnu-pkg-config \ + -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc-12 \ + -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++-12 \ + -DUSE_QT6=ON \ + -DMELONDS_EMBED_BUILD_INFO=ON + - name: Build + shell: bash + run: | + cmake --build build + - uses: actions/upload-artifact@v4 + with: + name: melonDS-ubuntu-aarch64 + path: build/melonDS diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index e6846da7..c3350b4d 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -4,40 +4,40 @@ on: push: branches: - master + - ci/* pull_request: branches: - master env: - BUILD_TYPE: Release + MELONDS_GIT_BRANCH: ${{ github.ref }} + MELONDS_GIT_HASH: ${{ github.sha }} + MELONDS_BUILD_PROVIDER: GitHub Actions jobs: build: - runs-on: windows-latest - defaults: run: shell: msys2 {0} steps: - - uses: actions/checkout@v1 - - uses: msys2/setup-msys2@v2 + - name: Check out sources + uses: actions/checkout@v3 + - name: Set up MSYS2 + uses: msys2/setup-msys2@v2 with: - msystem: MINGW64 - update: true - - - name: Install dependencies - run: pacman -Sq --noconfirm git pkgconf mingw-w64-x86_64-{cmake,SDL2,qt5-static,libslirp,libarchive,toolchain} - + msystem: ucrt64 + update: true + pacboy: gcc:p cmake:p ninja:p make:p + - name: Set up vcpkg + uses: lukka/run-vcpkg@v11 + with: + vcpkgGitCommitId: 10b7a178346f3f0abef60cecd5130e295afd8da4 - name: Configure - working-directory: ${{runner.workspace}} - run: cmake -B build $GITHUB_WORKSPACE -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 - run: cmake --build . - - - uses: actions/upload-artifact@v1 + run: cmake --preset=release-mingw-x86_64 -DMELONDS_EMBED_BUILD_INFO=ON + - name: Build + run: cmake --build --preset=release-mingw-x86_64 + - uses: actions/upload-artifact@v4 with: name: melonDS-windows-x86_64 - path: ${{runner.workspace}}\build\melonDS.exe + path: .\build\release-mingw-x86_64\melonDS.exe diff --git a/CMakeLists.txt b/CMakeLists.txt index 97dfc5bd..55bf825f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,8 @@ endif() set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) -set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) +set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/DefaultBuildFlags.cmake") option(USE_VCPKG "Use vcpkg for dependency packages" OFF) if (USE_VCPKG) @@ -25,6 +26,9 @@ include(CheckLibraryExists) include(CMakeDependentOption) include(CheckIPOSupported) +include(SetupCCache) +include(Sanitizers) + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") set(CMAKE_C_STANDARD 11) @@ -33,8 +37,6 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -add_compile_definitions(MELONDS_VERSION="${melonDS_VERSION}") - 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") @@ -78,14 +80,6 @@ if (ENABLE_LTO) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) endif() -if (CMAKE_CXX_COMPILER_ID STREQUAL GNU) - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Og") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") -endif() - -string(REPLACE "-O2" "-O3" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") -string(REPLACE "-O2" "-O3" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") - if (NOT APPLE) set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -s") endif() @@ -100,13 +94,6 @@ endif() set(CMAKE_POSITION_INDEPENDENT_CODE ON) -find_program(CCACHE "ccache") -if (CCACHE) - message(STATUS "Using CCache to speed up compilation") - set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE}) - set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) -endif() - option(ENABLE_GDBSTUB "Enable GDB stub" ON) if (ENABLE_GDBSTUB) add_definitions(-DGDBSTUB_ENABLED) diff --git a/CMakePresets.json b/CMakePresets.json index e14eda24..2144417b 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -20,6 +20,23 @@ } } }, + { + "name": "release-mingw-x86_64", + "inherits": "release-vcpkg", + "displayName": "Windows MinGW release (x86_64)", + "binaryDir": "${sourceDir}/build/release-mingw-x86_64", + "generator": "Ninja", + "cacheVariables": { + "USE_QT6": { + "type": "BOOL", + "value": "ON" + }, + "BUILD_STATIC": { + "type": "BOOL", + "value": "ON" + } + } + }, { "name": "release-mac-x86_64", "inherits": "release-vcpkg", @@ -44,6 +61,10 @@ "name": "release-vcpkg", "configurePreset": "release-vcpkg" }, + { + "name": "release-mingw-x86_64", + "configurePreset": "release-mingw-x86_64" + }, { "name": "release-mac-x86_64", "configurePreset": "release-mac-x86_64" @@ -85,4 +106,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/README.md b/README.md index de494305..eb8b1358 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,11 @@ +
- - - - + + +

DS emulator, sorta @@ -35,9 +35,9 @@ As for the rest, the interface should be pretty straightforward. If you have a q ### Linux 1. Install dependencies: - * Ubuntu 22.04: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qtbase5-dev qtbase5-private-dev qtmultimedia5-dev libslirp-dev libarchive-dev libzstd-dev` - * Older Ubuntu: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default qtbase5-private-dev qtmultimedia5-dev libslirp-dev libarchive-dev libzstd-dev` - * Arch Linux: `sudo pacman -S base-devel cmake extra-cmake-modules git libpcap sdl2 qt5-base qt5-multimedia libslirp libarchive zstd` + * Ubuntu 22.04: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qtbase5-dev qtbase5-private-dev qtmultimedia5-dev libqt5svg5-dev libarchive-dev libenet-dev libzstd-dev` + * Older Ubuntu: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default qtbase5-private-dev qtmultimedia5-dev libqt5svg5-dev libarchive-dev libenet-dev libzstd-dev` + * Arch Linux: `sudo pacman -S base-devel cmake extra-cmake-modules git libpcap sdl2 qt5-base qt5-multimedia qt5-svg libarchive enet zstd` 3. Download the melonDS repository and prepare: ```bash git clone https://github.com/melonDS-emu/melonDS @@ -64,7 +64,7 @@ As for the rest, the interface should be pretty straightforward. If you have a q cd melonDS ``` #### Dynamic builds (with DLLs) -5. Install dependencies: `pacman -S mingw-w64-x86_64-{cmake,SDL2,toolchain,qt5-base,qt5-svg,qt5-multimedia,qt5-tools,libslirp,libarchive,zstd}` +5. Install dependencies: `pacman -S mingw-w64-x86_64-{cmake,SDL2,toolchain,qt5-base,qt5-svg,qt5-multimedia,qt5-svg,qt5-tools,libarchive,enet,zstd}` 6. Compile: ```bash cmake -B build @@ -75,7 +75,7 @@ As for the rest, the interface should be pretty straightforward. If you have a q 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 mingw-w64-x86_64-{cmake,SDL2,toolchain,qt5-static,libslirp,libarchive,zstd}` +5. Install dependencies: `pacman -S mingw-w64-x86_64-{cmake,SDL2,toolchain,qt5-static,libarchive,enet,zstd}` 6. Compile: ```bash cmake -B build -DBUILD_STATIC=ON -DCMAKE_PREFIX_PATH=/mingw64/qt5-static @@ -85,7 +85,7 @@ If everything went well, melonDS should now be in the `build` folder. ### macOS 1. Install the [Homebrew Package Manager](https://brew.sh) -2. Install dependencies: `brew install git pkg-config cmake sdl2 qt@6 libslirp libarchive zstd` +2. Install dependencies: `brew install git pkg-config cmake sdl2 qt@6 libarchive enet zstd` 3. Download the melonDS repository and prepare: ```zsh git clone https://github.com/melonDS-emu/melonDS @@ -93,14 +93,14 @@ If everything went well, melonDS should now be in the `build` folder. ``` 4. Compile: ```zsh - cmake -B build -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6);$(brew --prefix libarchive)" -DUSE_QT6=ON + cmake -B build -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6);$(brew --prefix libarchive)" cmake --build build -j$(sysctl -n hw.logicalcpu) ``` 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. +../tools/mac-libs.rb .` after the build is completed, or add `-DMACOS_BUNDLE_LIBS=ON` to the first CMake command. ## TODO LIST diff --git a/cmake/ConfigureVcpkg.cmake b/cmake/ConfigureVcpkg.cmake index be8f0590..c1eb522d 100644 --- a/cmake/ConfigureVcpkg.cmake +++ b/cmake/ConfigureVcpkg.cmake @@ -4,10 +4,12 @@ set(_DEFAULT_VCPKG_ROOT "${CMAKE_SOURCE_DIR}/vcpkg") set(VCPKG_ROOT "${_DEFAULT_VCPKG_ROOT}" CACHE STRING "The path to the vcpkg repository") if (VCPKG_ROOT STREQUAL "${_DEFAULT_VCPKG_ROOT}") - file(LOCK "${_DEFAULT_VCPKG_ROOT}" DIRECTORY GUARD FILE) + if (APPLE) # this doesn't work on non-macOS + file(LOCK "${_DEFAULT_VCPKG_ROOT}" DIRECTORY GUARD FILE) + endif() FetchContent_Declare(vcpkg GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git" - GIT_TAG 2023.12.12 + GIT_TAG 2024.10.21 SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg") FetchContent_MakeAvailable(vcpkg) endif() @@ -16,6 +18,23 @@ set(VCPKG_OVERLAY_TRIPLETS "${CMAKE_SOURCE_DIR}/cmake/overlay-triplets") option(USE_RECOMMENDED_TRIPLETS "Use the recommended triplets that are used for official builds" ON) +# Duplicated here because it needs to be set before project() +if (NOT WIN32) + option(USE_QT6 "Build using Qt 6 instead of 5" ON) +else() + option(USE_QT6 "Build using Qt 6 instead of 5" OFF) +endif() + +# Since the Linux build pulls in glib anyway, we can just use upstream libslirp +if (UNIX AND NOT APPLE) + option(USE_SYSTEM_LIBSLIRP "Use system libslirp instead of the bundled version" ON) +endif() + +if (NOT USE_QT6) + list(APPEND VCPKG_MANIFEST_FEATURES qt5) + set(VCPKG_MANIFEST_NO_DEFAULT_FEATURES ON) +endif() + if (CMAKE_OSX_ARCHITECTURES MATCHES ";") message(FATAL_ERROR "macOS universal builds are not supported. Build them individually and combine afterwards instead.") endif() @@ -47,7 +66,15 @@ if (USE_RECOMMENDED_TRIPLETS) elseif(WIN32) # TODO Windows arm64 if possible set(_CAN_TARGET_AS_HOST ON) - set(_WANTED_TRIPLET x64-mingw-static) + set(_WANTED_TRIPLET x64-mingw-static-release) + elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL Linux) + # Can't really detect cross compiling here. + set(_CAN_TARGET_AS_HOST ON) + if (_HOST_PROCESSOR STREQUAL x86_64) + set(_WANTED_TRIPLET x64-linux-release) + elseif(_HOST_PROCESSOR STREQUAL "aarch64") + set(_WANTED_TRIPLET arm64-linux-release) + endif() endif() # Don't override it if the user set something else diff --git a/cmake/DefaultBuildFlags.cmake b/cmake/DefaultBuildFlags.cmake new file mode 100644 index 00000000..683767b3 --- /dev/null +++ b/cmake/DefaultBuildFlags.cmake @@ -0,0 +1,9 @@ +if (CMAKE_C_COMPILER_ID STREQUAL GNU) + set(CMAKE_C_FLAGS_DEBUG_INIT "-g -Og") +endif() +if (CMAKE_CXX_COMPILER_ID STREQUAL GNU) + set(CMAKE_CXX_FLAGS_DEBUG_INIT "-g -Og") +endif() + +string(REPLACE "-O2" "-O3" CMAKE_C_FLAGS_RELEASE_INIT "${CMAKE_C_FLAGS_RELEASE_INIT}") +string(REPLACE "-O2" "-O3" CMAKE_CXX_FLAGS_RELEASE_INIT "${CMAKE_CXX_FLAGS_RELEASE_INIT}") diff --git a/cmake/FindENet.cmake b/cmake/FindENet.cmake new file mode 100644 index 00000000..f9044c30 --- /dev/null +++ b/cmake/FindENet.cmake @@ -0,0 +1,48 @@ +# - Try to find enet +# Once done this will define +# +# ENET_FOUND - system has enet +# ENET_INCLUDE_DIRS - the enet include directory +# ENET_LIBRARIES - the libraries needed to use enet +# +# $ENETDIR is an environment variable used for finding enet. +# +# Borrowed from The Mana World +# http://themanaworld.org/ +# +# Several changes and additions by Fabian 'x3n' Landau +# Lots of simplifications by Adrian Friedli +# > www.orxonox.net < + +FIND_PATH(ENET_INCLUDE_DIRS enet/enet.h + PATHS + $ENV{ENETDIR} + /usr/local + /usr + PATH_SUFFIXES include +) + +FIND_LIBRARY(ENET_LIBRARY + NAMES enet + PATHS + $ENV{ENETDIR} + /usr/local + /usr + PATH_SUFFIXES lib +) + +# handle the QUIETLY and REQUIRED arguments and set ENET_FOUND to TRUE if +# all listed variables are TRUE +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(ENet DEFAULT_MSG ENET_LIBRARY ENET_INCLUDE_DIRS) + +IF (ENET_FOUND) + IF(WIN32) + SET(WINDOWS_ENET_DEPENDENCIES "ws2_32;winmm") + SET(ENET_LIBRARIES ${ENET_LIBRARY} ${WINDOWS_ENET_DEPENDENCIES}) + ELSE(WIN32) + SET(ENET_LIBRARIES ${ENET_LIBRARY}) + ENDIF(WIN32) +ENDIF (ENET_FOUND) + +MARK_AS_ADVANCED(ENET_LIBRARY ENET_LIBRARIES ENET_INCLUDE_DIRS) diff --git a/cmake/FixInterfaceIncludes.cmake b/cmake/FixInterfaceIncludes.cmake index 513c1117..5c285d7a 100644 --- a/cmake/FixInterfaceIncludes.cmake +++ b/cmake/FixInterfaceIncludes.cmake @@ -19,6 +19,13 @@ function(fix_interface_includes) if (PARENT_DIR MATCHES "include$") list(APPEND NEW_DIRS "${PARENT_DIR}") endif() + + # HACK + # The libarchive pkg-config file in MSYS2 seems to include a UNIX-style path for its + # include directory and CMake doesn't like that. + if (WIN32 AND MINGW AND target STREQUAL PkgConfig::LibArchive) + list(FILTER DIRS EXCLUDE REGEX "^/[^.]+64/.*") + endif() endforeach() list(APPEND DIRS ${NEW_DIRS}) diff --git a/cmake/Sanitizers.cmake b/cmake/Sanitizers.cmake new file mode 100644 index 00000000..9c09da28 --- /dev/null +++ b/cmake/Sanitizers.cmake @@ -0,0 +1,8 @@ +set(SANITIZE "" CACHE STRING "Sanitizers to enable.") + +string(REGEX MATCHALL "[^,]+" ENABLED_SANITIZERS "${SANITIZE}") + +foreach(SANITIZER ${ENABLED_SANITIZERS}) + add_compile_options("-fsanitize=${SANITIZER}") + add_link_options("-fsanitize=${SANITIZER}") +endforeach() \ No newline at end of file diff --git a/cmake/SetupCCache.cmake b/cmake/SetupCCache.cmake new file mode 100644 index 00000000..72388bf8 --- /dev/null +++ b/cmake/SetupCCache.cmake @@ -0,0 +1,19 @@ +include(FindPackageMessage) + +find_program(CCACHE "ccache") + +cmake_dependent_option(USE_CCACHE "Use CCache to speed up repeated builds." ON CCACHE OFF) + +if (NOT CCACHE OR NOT USE_CCACHE) + return() +endif() + +# Fedora, and probably also Red Hat-based distros in general, use CCache by default if it's installed on the system. +# We'll try to detect this here, and exit if that's the case. +# Trying to launch ccache with ccache as we'd otherwise do seems to cause build issues. +if (CMAKE_C_COMPILER MATCHES "ccache" OR CMAKE_CXX_COMPILER MATCHES "ccache") + return() +endif() + +find_package_message(CCache "Using CCache to speed up compilation" "${USE_CCACHE}") +set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE}") \ No newline at end of file diff --git a/cmake/overlay-triplets/x64-mingw-static-release.cmake b/cmake/overlay-triplets/x64-mingw-static-release.cmake new file mode 100644 index 00000000..19c2aeb0 --- /dev/null +++ b/cmake/overlay-triplets/x64-mingw-static-release.cmake @@ -0,0 +1,7 @@ +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) +set(VCPKG_ENV_PASSTHROUGH PATH) +set(VCPKG_BUILD_TYPE release) + +set(VCPKG_CMAKE_SYSTEM_NAME MinGW) diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..be75f57f --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1729665710, + "narHash": "sha256-AlcmCXJZPIlO5dmFzV3V2XF6x/OpNWUV8Y/FMPGd8Z4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2768c7d042a37de65bb1b5b3268fc987e534c49d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..8d500c03 --- /dev/null +++ b/flake.nix @@ -0,0 +1,100 @@ +{ + description = "Nintendo DS emulator"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + inherit (pkgs.lib) cmakeBool optionals makeLibraryPath; + inherit (pkgs.stdenv) isLinux isDarwin; + + revision = with self; if sourceInfo?dirtyRev + then sourceInfo.dirtyRev + else sourceInfo.rev; + shortRevision = with self; if sourceInfo?dirtyShortRev + then sourceInfo.dirtyShortRev + else sourceInfo.shortRev; + + melonDS = pkgs.qt6.qtbase.stdenv.mkDerivation { + pname = "melonDS"; + version = "0.9.5-${shortRevision}"; + src = ./.; + + nativeBuildInputs = with pkgs; [ + cmake + ninja + pkg-config + qt6.wrapQtAppsHook + ]; + + buildInputs = (with pkgs; [ + qt6.qtbase + qt6.qtmultimedia + SDL2 + zstd + libarchive + libGL + libslirp + enet + ]) ++ optionals (!isDarwin) (with pkgs; [ + kdePackages.extra-cmake-modules + qt6.qtwayland + wayland + ]); + + cmakeFlags = [ + (cmakeBool "USE_QT6" true) + (cmakeBool "USE_SYSTEM_LIBSLIRP" true) + (cmakeBool "MELONDS_EMBED_BUILD_INFO" true) + ]; + + env.MELONDS_GIT_HASH = revision; + env.MELONDS_GIT_BRANCH = "(unknown)"; + env.MELONDS_BUILD_PROVIDER = "Nix"; + + qtWrapperArgs = optionals isLinux [ + "--prefix LD_LIBRARY_PATH : ${makeLibraryPath [ pkgs.libpcap pkgs.wayland ]}" + ] ++ optionals isDarwin [ + "--prefix DYLD_LIBRARY_PATH : ${makeLibraryPath [ pkgs.libpcap ]}" + ]; + + passthru = { + exePath = if isDarwin then + "/Applications/melonDS.app/Contents/MacOS/melonDS" + else "/bin/melonDS"; + }; + }; + in { + packages.default = melonDS; + apps.default = flake-utils.lib.mkApp { + drv = self.packages.${system}.default; + }; + devShells = { + default = pkgs.mkShell.override { stdenv = pkgs.qt6.qtbase.stdenv; } { + inputsFrom = [ self.packages.${system}.default ]; + }; + + # Shell for building static melonDS release builds with vcpkg + # Use mkShellNoCC to ensure Nix's gcc/clang and stdlib isn't used + vcpkg = pkgs.mkShellNoCC { + packages = with pkgs; [ + autoconf + autoconf-archive + automake + cmake + cups.dev # Needed by qtbase despite not enabling print support + git + iconv.dev + libtool + ninja + pkg-config + ]; + }; + }; + } + ); +} diff --git a/res/melon.qrc b/res/melon.qrc index 38915bbf..3c5824d6 100644 --- a/res/melon.qrc +++ b/res/melon.qrc @@ -2,5 +2,6 @@ icon/melon_256x256.png + melon.svg diff --git a/src/ARCodeFile.cpp b/src/ARCodeFile.cpp index 602a2e7b..a98f5e50 100644 --- a/src/ARCodeFile.cpp +++ b/src/ARCodeFile.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -33,17 +33,26 @@ ARCodeFile::ARCodeFile(const std::string& filename) { Filename = filename; - Error = false; - - Categories.clear(); - if (!Load()) Error = true; } -ARCodeFile::~ARCodeFile() +std::vector ARCodeFile::GetCodes() const noexcept { - Categories.clear(); + if (Error) + return {}; + + std::vector codes; + + for (const ARCodeCat& cat : Categories) + { + for (const ARCode& code : cat.Codes) + { + codes.push_back(code); + } + } + + return codes; } bool ARCodeFile::Load() diff --git a/src/ARCodeFile.h b/src/ARCodeFile.h index 11e71efe..04f9e4f4 100644 --- a/src/ARCodeFile.h +++ b/src/ARCodeFile.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -48,14 +48,16 @@ class ARCodeFile { public: ARCodeFile(const std::string& filename); - ~ARCodeFile(); + ~ARCodeFile() noexcept = default; - bool Error; + [[nodiscard]] std::vector GetCodes() const noexcept; + + bool Error = false; bool Load(); bool Save(); - ARCodeCatList Categories; + ARCodeCatList Categories {}; private: std::string Filename; diff --git a/src/AREngine.cpp b/src/AREngine.cpp index c7d49fe6..bdda5863 100644 --- a/src/AREngine.cpp +++ b/src/AREngine.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -31,7 +31,6 @@ using Platform::LogLevel; AREngine::AREngine(melonDS::NDS& nds) : NDS(nds) { - CodeFile = nullptr; } #define case16(x) \ @@ -388,19 +387,12 @@ void AREngine::RunCheat(const ARCode& arcode) void AREngine::RunCheats() { - if (!CodeFile) return; + if (Cheats.empty()) return; - for (ARCodeCatList::iterator i = CodeFile->Categories.begin(); i != CodeFile->Categories.end(); i++) + for (const ARCode& code : Cheats) { - ARCodeCat& cat = *i; - - for (ARCodeList::iterator j = cat.Codes.begin(); j != cat.Codes.end(); j++) - { - ARCode& code = *j; - - if (code.Enabled) - RunCheat(code); - } + if (code.Enabled) + RunCheat(code); } } } diff --git a/src/AREngine.h b/src/AREngine.h index 21044676..e73fc98e 100644 --- a/src/AREngine.h +++ b/src/AREngine.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -19,6 +19,7 @@ #ifndef ARENGINE_H #define ARENGINE_H +#include #include "ARCodeFile.h" namespace melonDS @@ -29,14 +30,13 @@ class AREngine public: AREngine(melonDS::NDS& nds); - ARCodeFile* GetCodeFile() { return CodeFile; } - void SetCodeFile(ARCodeFile* file) { CodeFile = file; } - + std::vector Cheats {}; +private: + friend class ARM; void RunCheats(); void RunCheat(const ARCode& arcode); -private: + melonDS::NDS& NDS; - ARCodeFile* CodeFile; // AR code file - frontend is responsible for managing this }; } diff --git a/src/ARM.cpp b/src/ARM.cpp index c2f6a6c2..b7b703da 100644 --- a/src/ARM.cpp +++ b/src/ARM.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -110,6 +110,7 @@ const u32 ARM::ConditionTable[16] = ARM::ARM(u32 num, bool jit, std::optional gdb, melonDS::NDS& nds) : #ifdef GDBSTUB_ENABLED GdbStub(this, gdb ? (num ? gdb->PortARM7 : gdb->PortARM9) : 0), + BreakOnStartup(gdb ? (num ? gdb->ARM7BreakOnStartup : gdb->ARM9BreakOnStartup) : false), #endif Num(num), // well uh NDS(nds) @@ -582,9 +583,11 @@ void ARM::CheckGdbIncoming() GdbCheckA(); } +template void ARMv5::Execute() { - GdbCheckB(); + if constexpr (mode == CPUExecuteMode::InterpreterGDB) + GdbCheckB(); if (Halted) { @@ -607,231 +610,125 @@ void ARMv5::Execute() while (NDS.ARM9Timestamp < NDS.ARM9Target) { - if (CPSR & 0x20) // THUMB +#ifdef JIT_ENABLED + if constexpr (mode == CPUExecuteMode::JIT) { - GdbCheckC(); + u32 instrAddr = R[15] - ((CPSR&0x20)?2:4); - // prefetch - R[15] += 2; - CurInstr = NextInstr[0]; - NextInstr[0] = NextInstr[1]; - if (R[15] & 0x2) { NextInstr[1] >>= 16; CodeCycles = 0; } - else NextInstr[1] = CodeRead32(R[15], false); - - // actually execute - u32 icode = (CurInstr >> 6) & 0x3FF; - ARMInterpreter::THUMBInstrTable[icode](this); - } - else - { - GdbCheckC(); - - // prefetch - R[15] += 4; - CurInstr = NextInstr[0]; - NextInstr[0] = NextInstr[1]; - NextInstr[1] = CodeRead32(R[15], false); - - // actually execute - if (CheckCondition(CurInstr >> 28)) - { - u32 icode = ((CurInstr >> 4) & 0xF) | ((CurInstr >> 16) & 0xFF0); - ARMInterpreter::ARMInstrTable[icode](this); - } - else if ((CurInstr & 0xFE000000) == 0xFA000000) - { - ARMInterpreter::A_BLX_IMM(this); - } - else - AddCycles_C(); - } - - // TODO optimize this shit!!! - if (Halted) - { - if (Halted == 1 && NDS.ARM9Timestamp < NDS.ARM9Target) + if ((instrAddr < FastBlockLookupStart || instrAddr >= (FastBlockLookupStart + FastBlockLookupSize)) + && !NDS.JIT.SetupExecutableRegion(0, instrAddr, FastBlockLookup, FastBlockLookupStart, FastBlockLookupSize)) { NDS.ARM9Timestamp = NDS.ARM9Target; + Log(LogLevel::Error, "ARMv5 PC in non executable region %08X\n", R[15]); + return; } - break; - } - /*if (NDS::IF[0] & NDS::IE[0]) - { - if (NDS::IME[0] & 0x1) - TriggerIRQ(); - }*/ - if (IRQ) TriggerIRQ(); - NDS.ARM9Timestamp += Cycles; - Cycles = 0; - } + JitBlockEntry block = NDS.JIT.LookUpBlock(0, FastBlockLookup, + instrAddr - FastBlockLookupStart, instrAddr); + if (block) + ARM_Dispatch(this, block); + else + NDS.JIT.CompileBlock(this); - if (Halted == 2) - Halted = 0; -} - -#ifdef JIT_ENABLED -void ARMv5::ExecuteJIT() -{ - if (Halted) - { - if (Halted == 2) - { - Halted = 0; - } - else if (NDS.HaltInterrupted(0)) - { - Halted = 0; - if (NDS.IME[0] & 0x1) - TriggerIRQ(); - } - else - { - NDS.ARM9Timestamp = NDS.ARM9Target; - return; - } - } - - while (NDS.ARM9Timestamp < NDS.ARM9Target) - { - u32 instrAddr = R[15] - ((CPSR&0x20)?2:4); - - if ((instrAddr < FastBlockLookupStart || instrAddr >= (FastBlockLookupStart + FastBlockLookupSize)) - && !NDS.JIT.SetupExecutableRegion(0, instrAddr, FastBlockLookup, FastBlockLookupStart, FastBlockLookupSize)) - { - NDS.ARM9Timestamp = NDS.ARM9Target; - Log(LogLevel::Error, "ARMv5 PC in non executable region %08X\n", R[15]); - return; - } - - JitBlockEntry block = NDS.JIT.LookUpBlock(0, FastBlockLookup, - instrAddr - FastBlockLookupStart, instrAddr); - if (block) - ARM_Dispatch(this, block); - else - NDS.JIT.CompileBlock(this); - - if (StopExecution) - { - // this order is crucial otherwise idle loops waiting for an IRQ won't function - if (IRQ) - TriggerIRQ(); - - if (Halted || IdleLoop) + if (StopExecution) { - if ((Halted == 1 || IdleLoop) && NDS.ARM9Timestamp < NDS.ARM9Target) + // this order is crucial otherwise idle loops waiting for an IRQ won't function + if (IRQ) + TriggerIRQ(); + + if (Halted || IdleLoop) { - Cycles = 0; - NDS.ARM9Timestamp = NDS.ARM9Target; + if ((Halted == 1 || IdleLoop) && NDS.ARM9Timestamp < NDS.ARM9Target) + { + Cycles = 0; + NDS.ARM9Timestamp = NDS.ARM9Target; + } + IdleLoop = 0; + break; } - IdleLoop = 0; - break; } } - - NDS.ARM9Timestamp += Cycles; - Cycles = 0; - } - - if (Halted == 2) - Halted = 0; -} + else #endif - -void ARMv4::Execute() -{ - GdbCheckB(); - - if (Halted) - { - if (Halted == 2) { - Halted = 0; - } - else if (NDS.HaltInterrupted(1)) - { - Halted = 0; - if (NDS.IME[1] & 0x1) - TriggerIRQ(); - } - else - { - NDS.ARM7Timestamp = NDS.ARM7Target; - return; - } - } - - while (NDS.ARM7Timestamp < NDS.ARM7Target) - { - if (CPSR & 0x20) // THUMB - { - GdbCheckC(); - - // prefetch - R[15] += 2; - CurInstr = NextInstr[0]; - NextInstr[0] = NextInstr[1]; - NextInstr[1] = CodeRead16(R[15]); - - // actually execute - u32 icode = (CurInstr >> 6); - ARMInterpreter::THUMBInstrTable[icode](this); - } - else - { - GdbCheckC(); - - // prefetch - R[15] += 4; - CurInstr = NextInstr[0]; - NextInstr[0] = NextInstr[1]; - NextInstr[1] = CodeRead32(R[15]); - - // actually execute - if (CheckCondition(CurInstr >> 28)) + if (CPSR & 0x20) // THUMB { - u32 icode = ((CurInstr >> 4) & 0xF) | ((CurInstr >> 16) & 0xFF0); - ARMInterpreter::ARMInstrTable[icode](this); + if constexpr (mode == CPUExecuteMode::InterpreterGDB) + GdbCheckC(); + + // prefetch + R[15] += 2; + CurInstr = NextInstr[0]; + NextInstr[0] = NextInstr[1]; + if (R[15] & 0x2) { NextInstr[1] >>= 16; CodeCycles = 0; } + else NextInstr[1] = CodeRead32(R[15], false); + + // actually execute + u32 icode = (CurInstr >> 6) & 0x3FF; + ARMInterpreter::THUMBInstrTable[icode](this); } else - AddCycles_C(); - } - - // TODO optimize this shit!!! - if (Halted) - { - if (Halted == 1 && NDS.ARM7Timestamp < NDS.ARM7Target) { - NDS.ARM7Timestamp = NDS.ARM7Target; - } - break; - } - /*if (NDS::IF[1] & NDS::IE[1]) - { - if (NDS::IME[1] & 0x1) - TriggerIRQ(); - }*/ - if (IRQ) TriggerIRQ(); + if constexpr (mode == CPUExecuteMode::InterpreterGDB) + GdbCheckC(); - NDS.ARM7Timestamp += Cycles; + // prefetch + R[15] += 4; + CurInstr = NextInstr[0]; + NextInstr[0] = NextInstr[1]; + NextInstr[1] = CodeRead32(R[15], false); + + // actually execute + if (CheckCondition(CurInstr >> 28)) + { + u32 icode = ((CurInstr >> 4) & 0xF) | ((CurInstr >> 16) & 0xFF0); + ARMInterpreter::ARMInstrTable[icode](this); + } + else if ((CurInstr & 0xFE000000) == 0xFA000000) + { + ARMInterpreter::A_BLX_IMM(this); + } + else + AddCycles_C(); + } + + // TODO optimize this shit!!! + if (Halted) + { + if (Halted == 1 && NDS.ARM9Timestamp < NDS.ARM9Target) + { + NDS.ARM9Timestamp = NDS.ARM9Target; + } + break; + } + /*if (NDS::IF[0] & NDS::IE[0]) + { + if (NDS::IME[0] & 0x1) + TriggerIRQ(); + }*/ + if (IRQ) TriggerIRQ(); + + } + + NDS.ARM9Timestamp += Cycles; Cycles = 0; } if (Halted == 2) Halted = 0; - - if (Halted == 4) - { - assert(NDS.ConsoleType == 1); - auto& dsi = dynamic_cast(NDS); - dsi.SoftReset(); - Halted = 2; - } } - +template void ARMv5::Execute(); +template void ARMv5::Execute(); #ifdef JIT_ENABLED -void ARMv4::ExecuteJIT() +template void ARMv5::Execute(); +#endif + +template +void ARMv4::Execute() { + if constexpr (mode == CPUExecuteMode::InterpreterGDB) + GdbCheckB(); + if (Halted) { if (Halted == 2) @@ -853,38 +750,97 @@ void ARMv4::ExecuteJIT() while (NDS.ARM7Timestamp < NDS.ARM7Target) { - u32 instrAddr = R[15] - ((CPSR&0x20)?2:4); - - if ((instrAddr < FastBlockLookupStart || instrAddr >= (FastBlockLookupStart + FastBlockLookupSize)) - && !NDS.JIT.SetupExecutableRegion(1, instrAddr, FastBlockLookup, FastBlockLookupStart, FastBlockLookupSize)) +#ifdef JIT_ENABLED + if constexpr (mode == CPUExecuteMode::JIT) { - NDS.ARM7Timestamp = NDS.ARM7Target; - Log(LogLevel::Error, "ARMv4 PC in non executable region %08X\n", R[15]); - return; - } + u32 instrAddr = R[15] - ((CPSR&0x20)?2:4); - JitBlockEntry block = NDS.JIT.LookUpBlock(1, FastBlockLookup, - instrAddr - FastBlockLookupStart, instrAddr); - if (block) - ARM_Dispatch(this, block); - else - NDS.JIT.CompileBlock(this); - - if (StopExecution) - { - if (IRQ) - TriggerIRQ(); - - if (Halted || IdleLoop) + if ((instrAddr < FastBlockLookupStart || instrAddr >= (FastBlockLookupStart + FastBlockLookupSize)) + && !NDS.JIT.SetupExecutableRegion(1, instrAddr, FastBlockLookup, FastBlockLookupStart, FastBlockLookupSize)) { - if ((Halted == 1 || IdleLoop) && NDS.ARM7Timestamp < NDS.ARM7Target) + NDS.ARM7Timestamp = NDS.ARM7Target; + Log(LogLevel::Error, "ARMv4 PC in non executable region %08X\n", R[15]); + return; + } + + JitBlockEntry block = NDS.JIT.LookUpBlock(1, FastBlockLookup, + instrAddr - FastBlockLookupStart, instrAddr); + if (block) + ARM_Dispatch(this, block); + else + NDS.JIT.CompileBlock(this); + + if (StopExecution) + { + if (IRQ) + TriggerIRQ(); + + if (Halted || IdleLoop) + { + if ((Halted == 1 || IdleLoop) && NDS.ARM7Timestamp < NDS.ARM7Target) + { + Cycles = 0; + NDS.ARM7Timestamp = NDS.ARM7Target; + } + IdleLoop = 0; + break; + } + } + } + else +#endif + { + if (CPSR & 0x20) // THUMB + { + if constexpr (mode == CPUExecuteMode::InterpreterGDB) + GdbCheckC(); + + // prefetch + R[15] += 2; + CurInstr = NextInstr[0]; + NextInstr[0] = NextInstr[1]; + NextInstr[1] = CodeRead16(R[15]); + + // actually execute + u32 icode = (CurInstr >> 6); + ARMInterpreter::THUMBInstrTable[icode](this); + } + else + { + if constexpr (mode == CPUExecuteMode::InterpreterGDB) + GdbCheckC(); + + // prefetch + R[15] += 4; + CurInstr = NextInstr[0]; + NextInstr[0] = NextInstr[1]; + NextInstr[1] = CodeRead32(R[15]); + + // actually execute + if (CheckCondition(CurInstr >> 28)) + { + u32 icode = ((CurInstr >> 4) & 0xF) | ((CurInstr >> 16) & 0xFF0); + ARMInterpreter::ARMInstrTable[icode](this); + } + else + AddCycles_C(); + } + + // TODO optimize this shit!!! + if (Halted) + { + if (Halted == 1 && NDS.ARM7Timestamp < NDS.ARM7Target) { - Cycles = 0; NDS.ARM7Timestamp = NDS.ARM7Target; } - IdleLoop = 0; break; } + /*if (NDS::IF[1] & NDS::IE[1]) + { + if (NDS::IME[1] & 0x1) + TriggerIRQ(); + }*/ + if (IRQ) TriggerIRQ(); } NDS.ARM7Timestamp += Cycles; @@ -902,6 +858,11 @@ void ARMv4::ExecuteJIT() Halted = 2; } } + +template void ARMv4::Execute(); +template void ARMv4::Execute(); +#ifdef JIT_ENABLED +template void ARMv4::Execute(); #endif void ARMv5::FillPipeline() diff --git a/src/ARM.h b/src/ARM.h index 1e0b71b8..b652e74d 100644 --- a/src/ARM.h +++ b/src/ARM.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -43,6 +43,15 @@ enum RWFlags_ForceUser = (1<<21), }; +enum class CPUExecuteMode : u32 +{ + Interpreter, + InterpreterGDB, +#ifdef JIT_ENABLED + JIT +#endif +}; + struct GDBArgs; class ARMJIT; class GPU; @@ -75,10 +84,6 @@ public: } void NocashPrint(u32 addr) noexcept; - virtual void Execute() = 0; -#ifdef JIT_ENABLED - virtual void ExecuteJIT() = 0; -#endif bool CheckCondition(u32 code) const { @@ -241,10 +246,8 @@ public: void PrefetchAbort(); void DataAbort(); - void Execute() override; -#ifdef JIT_ENABLED - void ExecuteJIT() override; -#endif + template + void Execute(); // all code accesses are forced nonseq 32bit u32 CodeRead32(u32 addr, bool branch); @@ -383,10 +386,8 @@ public: void JumpTo(u32 addr, bool restorecpsr = false) override; - void Execute() override; -#ifdef JIT_ENABLED - void ExecuteJIT() override; -#endif + template + void Execute(); u16 CodeRead16(u32 addr) { diff --git a/src/ARMInterpreter.cpp b/src/ARMInterpreter.cpp index a1da1b86..e9973380 100644 --- a/src/ARMInterpreter.cpp +++ b/src/ARMInterpreter.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -38,7 +38,7 @@ void A_UNK(ARM* cpu) { Log(LogLevel::Warn, "undefined ARM%d instruction %08X @ %08X\n", cpu->Num?7:9, cpu->CurInstr, cpu->R[15]-8); #ifdef GDBSTUB_ENABLED - cpu->GdbStub.Enter(true, Gdb::TgtStatus::FaultInsn, cpu->R[15]-8); + cpu->GdbStub.Enter(cpu->GdbStub.IsConnected(), Gdb::TgtStatus::FaultInsn, cpu->R[15]-8); #endif //for (int i = 0; i < 16; i++) printf("R%d: %08X\n", i, cpu->R[i]); //NDS::Halt(); @@ -56,7 +56,7 @@ void T_UNK(ARM* cpu) { Log(LogLevel::Warn, "undefined THUMB%d instruction %04X @ %08X\n", cpu->Num?7:9, cpu->CurInstr, cpu->R[15]-4); #ifdef GDBSTUB_ENABLED - cpu->GdbStub.Enter(true, Gdb::TgtStatus::FaultInsn, cpu->R[15]-4); + cpu->GdbStub.Enter(cpu->GdbStub.IsConnected(), Gdb::TgtStatus::FaultInsn, cpu->R[15]-4); #endif //NDS::Halt(); u32 oldcpsr = cpu->CPSR; diff --git a/src/ARMInterpreter.h b/src/ARMInterpreter.h index cff4821a..1066ac69 100644 --- a/src/ARMInterpreter.h +++ b/src/ARMInterpreter.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARMInterpreter_ALU.cpp b/src/ARMInterpreter_ALU.cpp index 315d59d0..167e184e 100644 --- a/src/ARMInterpreter_ALU.cpp +++ b/src/ARMInterpreter_ALU.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARMInterpreter_ALU.h b/src/ARMInterpreter_ALU.h index 6998b637..58d8165c 100644 --- a/src/ARMInterpreter_ALU.h +++ b/src/ARMInterpreter_ALU.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARMInterpreter_Branch.cpp b/src/ARMInterpreter_Branch.cpp index 015f5682..623be41a 100644 --- a/src/ARMInterpreter_Branch.cpp +++ b/src/ARMInterpreter_Branch.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARMInterpreter_Branch.h b/src/ARMInterpreter_Branch.h index 51a561c1..e3d16776 100644 --- a/src/ARMInterpreter_Branch.h +++ b/src/ARMInterpreter_Branch.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARMInterpreter_LoadStore.cpp b/src/ARMInterpreter_LoadStore.cpp index 91acaacc..f7c24312 100644 --- a/src/ARMInterpreter_LoadStore.cpp +++ b/src/ARMInterpreter_LoadStore.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -430,9 +430,9 @@ void A_LDM(ARM* cpu) } } + u32 pc = 0; if (cpu->CurInstr & (1<<15)) { - u32 pc; if (preinc) base += 4; if (first) cpu->DataRead32 (base, &pc); else cpu->DataRead32S(base, &pc); @@ -440,13 +440,8 @@ void A_LDM(ARM* cpu) if (cpu->Num == 1) pc &= ~0x1; - - cpu->JumpTo(pc, cpu->CurInstr & (1<<22)); } - if ((cpu->CurInstr & (1<<22)) && !(cpu->CurInstr & (1<<15))) - cpu->UpdateMode((cpu->CPSR&~0x1F)|0x10, cpu->CPSR, true); - if (cpu->CurInstr & (1<<21)) { // post writeback @@ -466,6 +461,12 @@ void A_LDM(ARM* cpu) cpu->R[baseid] = wbbase; } + if ((cpu->CurInstr & (1<<22)) && !(cpu->CurInstr & (1<<15))) + cpu->UpdateMode((cpu->CPSR&~0x1F)|0x10, cpu->CPSR, true); + + if (cpu->CurInstr & (1<<15)) + cpu->JumpTo(pc, cpu->CurInstr & (1<<22)); + cpu->AddCycles_CDI(); } diff --git a/src/ARMInterpreter_LoadStore.h b/src/ARMInterpreter_LoadStore.h index 32d6e4d2..62828194 100644 --- a/src/ARMInterpreter_LoadStore.h +++ b/src/ARMInterpreter_LoadStore.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARMJIT.cpp b/src/ARMJIT.cpp index c3fcba26..1ebcce8e 100644 --- a/src/ARMJIT.cpp +++ b/src/ARMJIT.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARMJIT.h b/src/ARMJIT.h index 7619f234..a228a4dd 100644 --- a/src/ARMJIT.h +++ b/src/ARMJIT.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARMJIT_A64/ARMJIT_ALU.cpp b/src/ARMJIT_A64/ARMJIT_ALU.cpp index b25bcaa3..cb777500 100644 --- a/src/ARMJIT_A64/ARMJIT_ALU.cpp +++ b/src/ARMJIT_A64/ARMJIT_ALU.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2022 melonDS team, RSDuck + Copyright 2016-2024 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_A64/ARMJIT_Branch.cpp b/src/ARMJIT_A64/ARMJIT_Branch.cpp index 92717e91..f9c2e0c5 100644 --- a/src/ARMJIT_A64/ARMJIT_Branch.cpp +++ b/src/ARMJIT_A64/ARMJIT_Branch.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2022 melonDS team, RSDuck + Copyright 2016-2024 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_A64/ARMJIT_Compiler.cpp b/src/ARMJIT_A64/ARMJIT_Compiler.cpp index 7981ed67..f05de448 100644 --- a/src/ARMJIT_A64/ARMJIT_Compiler.cpp +++ b/src/ARMJIT_A64/ARMJIT_Compiler.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2022 melonDS team, RSDuck + Copyright 2016-2024 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_A64/ARMJIT_Compiler.h b/src/ARMJIT_A64/ARMJIT_Compiler.h index 2b0048a9..a7b567f6 100644 --- a/src/ARMJIT_A64/ARMJIT_Compiler.h +++ b/src/ARMJIT_A64/ARMJIT_Compiler.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2022 melonDS team, RSDuck + Copyright 2016-2024 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_A64/ARMJIT_Linkage.S b/src/ARMJIT_A64/ARMJIT_Linkage.S index b73905bd..9c360ec0 100644 --- a/src/ARMJIT_A64/ARMJIT_Linkage.S +++ b/src/ARMJIT_A64/ARMJIT_Linkage.S @@ -1,5 +1,5 @@ /* - Copyright 2016-2022 melonDS team, RSDuck + Copyright 2016-2024 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_A64/ARMJIT_LoadStore.cpp b/src/ARMJIT_A64/ARMJIT_LoadStore.cpp index e108b7b4..6d2c4276 100644 --- a/src/ARMJIT_A64/ARMJIT_LoadStore.cpp +++ b/src/ARMJIT_A64/ARMJIT_LoadStore.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2022 melonDS team, RSDuck + Copyright 2016-2024 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_Compiler.h b/src/ARMJIT_Compiler.h index ff4f8ff7..46cce5b0 100644 --- a/src/ARMJIT_Compiler.h +++ b/src/ARMJIT_Compiler.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2022 melonDS team, RSDuck + Copyright 2016-2024 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_Internal.h b/src/ARMJIT_Internal.h index 8429bade..5b393903 100644 --- a/src/ARMJIT_Internal.h +++ b/src/ARMJIT_Internal.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2022 melonDS team, RSDuck + Copyright 2016-2024 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_Memory.cpp b/src/ARMJIT_Memory.cpp index c8969aee..51e022d1 100644 --- a/src/ARMJIT_Memory.cpp +++ b/src/ARMJIT_Memory.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARMJIT_Memory.h b/src/ARMJIT_Memory.h index d36f6032..88e647d5 100644 --- a/src/ARMJIT_Memory.h +++ b/src/ARMJIT_Memory.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARMJIT_RegisterCache.h b/src/ARMJIT_RegisterCache.h index e5f28dd6..d2680731 100644 --- a/src/ARMJIT_RegisterCache.h +++ b/src/ARMJIT_RegisterCache.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2022 melonDS team, RSDuck + Copyright 2016-2024 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_x64/ARMJIT_ALU.cpp b/src/ARMJIT_x64/ARMJIT_ALU.cpp index 69449ff9..8f7a1b22 100644 --- a/src/ARMJIT_x64/ARMJIT_ALU.cpp +++ b/src/ARMJIT_x64/ARMJIT_ALU.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARMJIT_x64/ARMJIT_Branch.cpp b/src/ARMJIT_x64/ARMJIT_Branch.cpp index f7a01f48..c32e2b73 100644 --- a/src/ARMJIT_x64/ARMJIT_Branch.cpp +++ b/src/ARMJIT_x64/ARMJIT_Branch.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARMJIT_x64/ARMJIT_Compiler.cpp b/src/ARMJIT_x64/ARMJIT_Compiler.cpp index b18837f3..ba6c0fb4 100644 --- a/src/ARMJIT_x64/ARMJIT_Compiler.cpp +++ b/src/ARMJIT_x64/ARMJIT_Compiler.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARMJIT_x64/ARMJIT_Compiler.h b/src/ARMJIT_x64/ARMJIT_Compiler.h index 941d8924..3965e882 100644 --- a/src/ARMJIT_x64/ARMJIT_Compiler.h +++ b/src/ARMJIT_x64/ARMJIT_Compiler.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARMJIT_x64/ARMJIT_GenOffsets.cpp b/src/ARMJIT_x64/ARMJIT_GenOffsets.cpp index e2a74eee..e4812c0a 100644 --- a/src/ARMJIT_x64/ARMJIT_GenOffsets.cpp +++ b/src/ARMJIT_x64/ARMJIT_GenOffsets.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2022 melonDS team, RSDuck + Copyright 2016-2024 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARMJIT_x64/ARMJIT_Linkage.S b/src/ARMJIT_x64/ARMJIT_Linkage.S index 023f6e7b..18596003 100644 --- a/src/ARMJIT_x64/ARMJIT_Linkage.S +++ b/src/ARMJIT_x64/ARMJIT_Linkage.S @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARMJIT_x64/ARMJIT_LoadStore.cpp b/src/ARMJIT_x64/ARMJIT_LoadStore.cpp index 8520bebc..219c7271 100644 --- a/src/ARMJIT_x64/ARMJIT_LoadStore.cpp +++ b/src/ARMJIT_x64/ARMJIT_LoadStore.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARMJIT_x64/ARMJIT_Offsets.h b/src/ARMJIT_x64/ARMJIT_Offsets.h index 9d2a9522..738fc4ea 100644 --- a/src/ARMJIT_x64/ARMJIT_Offsets.h +++ b/src/ARMJIT_x64/ARMJIT_Offsets.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2022 melonDS team, RSDuck + Copyright 2016-2024 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/ARM_InstrInfo.cpp b/src/ARM_InstrInfo.cpp index d53c88f0..58838307 100644 --- a/src/ARM_InstrInfo.cpp +++ b/src/ARM_InstrInfo.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARM_InstrInfo.h b/src/ARM_InstrInfo.h index 13f66b09..fe4095b4 100644 --- a/src/ARM_InstrInfo.h +++ b/src/ARM_InstrInfo.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/ARM_InstrTable.h b/src/ARM_InstrTable.h index 7cdad66d..8213c2e0 100644 --- a/src/ARM_InstrTable.h +++ b/src/ARM_InstrTable.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/Args.h b/src/Args.h index d836b643..2c405e29 100644 --- a/src/Args.h +++ b/src/Args.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -69,6 +69,10 @@ struct JITArgs bool FastMemory = true; }; +using ARM9BIOSImage = std::array; +using ARM7BIOSImage = std::array; +using DSiBIOSImage = std::array; + struct GDBArgs { u16 PortARM7 = 0; @@ -95,11 +99,11 @@ struct NDSArgs /// NDS ARM9 BIOS to install. /// Defaults to FreeBIOS, which is not compatible with DSi mode. - std::array ARM9BIOS = bios_arm9_bin; + std::unique_ptr ARM9BIOS = std::make_unique(bios_arm9_bin); /// NDS ARM7 BIOS to install. /// Defaults to FreeBIOS, which is not compatible with DSi mode. - std::array ARM7BIOS = bios_arm7_bin; + std::unique_ptr ARM7BIOS = std::make_unique(bios_arm7_bin); /// Firmware image to install. /// Defaults to generated NDS firmware. @@ -131,8 +135,8 @@ struct NDSArgs /// Contains no virtual methods, so there's no vtable. struct DSiArgs final : public NDSArgs { - std::array ARM9iBIOS = BrokenBIOS; - std::array ARM7iBIOS = BrokenBIOS; + std::unique_ptr ARM9iBIOS = std::make_unique(BrokenBIOS); + std::unique_ptr ARM7iBIOS = std::make_unique(BrokenBIOS); /// NAND image to install. /// Required, there is no default value. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index afabc03f..1f947d11 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,6 +35,8 @@ add_library(core STATIC GPU2D_Soft.cpp GPU3D.cpp GPU3D_Soft.cpp + GPU3D_Texcache.cpp + GPU3D_Texcache.h melonDLDI.h NDS.cpp NDSCart.cpp @@ -52,7 +54,6 @@ add_library(core STATIC types.h Utils.cpp Utils.h - version.h Wifi.cpp WifiAP.cpp @@ -79,6 +80,9 @@ if (ENABLE_OGLRENDERER) GPU_OpenGL.cpp GPU_OpenGL_shaders.h GPU3D_OpenGL.cpp + GPU3D_Compute.cpp + GPU3D_TexcacheOpenGL.cpp + GPU3D_TexcacheOpenGL.h GPU3D_OpenGL_shaders.h OpenGLSupport.cpp) @@ -123,6 +127,24 @@ if (ENABLE_JIT) endif() endif() +set(MELONDS_VERSION_SUFFIX "$ENV{MELONDS_VERSION_SUFFIX}" CACHE STRING "Suffix to add to displayed melonDS version") +option(MELONDS_EMBED_BUILD_INFO "Embed detailed build info into the binary" OFF) +set(MELONDS_GIT_BRANCH "$ENV{MELONDS_GIT_BRANCH}" CACHE STRING "The Git branch used for this build") +set(MELONDS_GIT_HASH "$ENV{MELONDS_GIT_HASH}" CACHE STRING "The hash of the Git commit") +set(MELONDS_BUILD_PROVIDER "$ENV{MELONDS_BUILD_PROVIDER}" CACHE STRING "The name of the provider of this build") + +if (MELONDS_EMBED_BUILD_INFO) + target_compile_definitions(core PUBLIC MELONDS_EMBED_BUILD_INFO) + if (NOT MELONDS_GIT_BRANCH OR NOT MELONDS_GIT_HASH OR NOT MELONDS_BUILD_PROVIDER) + message(FATAL_ERROR "When embedding build information, all fields must be filled out. See src/CMakeLists.txt.") + endif() +endif() + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.h.in" "${CMAKE_CURRENT_BINARY_DIR}/version.h") +target_sources(core PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/version.h") +target_include_directories(core PUBLIC "${CMAKE_CURRENT_BINARY_DIR}") + +set(BUILD_SHARED_LIBS OFF) add_subdirectory(teakra EXCLUDE_FROM_ALL) # Workaround for building teakra with -O0 on Windows either failing or hanging forever target_compile_options(teakra PRIVATE "$<$:-Og>") @@ -130,7 +152,9 @@ target_link_libraries(core PRIVATE teakra) if (NOT MSVC) # MSVC has its own compiler flag syntax; if we ever support it, - # be sure to silence any equivalent warnings there. + # be sure to add equivalent flags here. + + target_compile_options(core PUBLIC -fwrapv) target_compile_options(core PRIVATE "$<$:-Wno-invalid-offsetof>") # These warnings are excessive, and are only triggered in the ARMJIT code @@ -154,11 +178,13 @@ endif() if (WIN32) target_link_libraries(core PRIVATE ole32 comctl32 wsock32 ws2_32) -elseif(NOT APPLE) +elseif(NOT APPLE AND NOT HAIKU) check_library_exists(rt shm_open "" NEED_LIBRT) if (NEED_LIBRT) target_link_libraries(core PRIVATE rt) endif() +elseif(HAIKU) + target_link_libraries(core PRIVATE network) endif() if (ENABLE_JIT_PROFILING) diff --git a/src/CP15.cpp b/src/CP15.cpp index 58137fdd..c271e180 100644 --- a/src/CP15.cpp +++ b/src/CP15.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -186,10 +186,14 @@ void ARMv5::UpdatePURegion(u32 n) return; } - u32 start = rgn >> 12; - u32 sz = 2 << ((rgn >> 1) & 0x1F); - u32 end = start + (sz >> 12); - // TODO: check alignment of start + // notes: + // * min size of a pu region is 4KiB (12 bits) + // * size is calculated as size + 1, but the 12 lsb of address space are ignored, therefore we need it as size + 1 - 12, or size - 11 + // * pu regions are aligned based on their size + u32 size = std::max((int)((rgn>>1) & 0x1F) - 11, 0); // obtain the size, subtract 11 and clamp to a min of 0. + u32 start = ((rgn >> 12) >> size) << size; // determine the start offset, and use shifts to force alignment with a multiple of the size. + u32 end = start + (1<> 4) & 0xF, val, val & 1 ? "enabled" : "disabled", val & 0xFFFFF000, - (val & 0xFFFFF000) + (2 << ((val & 0x3E) >> 1)) + (val & 0x3E) >> 1 ); Log(LogLevel::Debug, "%s", log_output); // Some implementations of Log imply a newline, so we build up the line before printing it diff --git a/src/CRC32.cpp b/src/CRC32.cpp index 0756c034..82fe467f 100644 --- a/src/CRC32.cpp +++ b/src/CRC32.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/CRC32.h b/src/CRC32.h index 11879057..90fb8057 100644 --- a/src/CRC32.h +++ b/src/CRC32.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/DMA.cpp b/src/DMA.cpp index 717b38fa..80cd592c 100644 --- a/src/DMA.cpp +++ b/src/DMA.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -21,6 +21,7 @@ #include "DSi.h" #include "DMA.h" #include "GPU.h" +#include "GPU3D.h" #include "DMA_Timings.h" #include "Platform.h" diff --git a/src/DMA.h b/src/DMA.h index e0e3be15..354f4495 100644 --- a/src/DMA.h +++ b/src/DMA.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/DMA_Timings.cpp b/src/DMA_Timings.cpp index 912e4e2e..a51fedfb 100644 --- a/src/DMA_Timings.cpp +++ b/src/DMA_Timings.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/DMA_Timings.h b/src/DMA_Timings.h index 63dc4676..38206235 100644 --- a/src/DMA_Timings.h +++ b/src/DMA_Timings.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/DSi.cpp b/src/DSi.cpp index c929c6d2..00ed8da0 100644 --- a/src/DSi.cpp +++ b/src/DSi.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -70,8 +70,28 @@ const u32 NDMAModes[] = 0xFF, // wifi / GBA cart slot (TODO) }; -DSi::DSi(DSiArgs&& args) noexcept : - NDS(std::move(args), 1), +/*DSi::DSi() noexcept : + DSi( + DSiArgs { + NDSArgs { + nullptr, + nullptr, + bios_arm9_bin, + bios_arm7_bin, + Firmware(0), + }, + nullptr, + nullptr, + nullptr, + nullptr, + false + } + ) +{ +}*/ + +DSi::DSi(DSiArgs&& args, void* userdata) noexcept : + NDS(std::move(args), 1, userdata), NDMAs { DSi_NDMA(0, 0, *this), DSi_NDMA(0, 1, *this), @@ -82,8 +102,8 @@ DSi::DSi(DSiArgs&& args) noexcept : DSi_NDMA(1, 2, *this), DSi_NDMA(1, 3, *this), }, - ARM7iBIOS(args.ARM7iBIOS), - ARM9iBIOS(args.ARM9iBIOS), + ARM7iBIOS(*args.ARM7iBIOS), + ARM9iBIOS(*args.ARM9iBIOS), DSP(*this), SDMMC(*this, std::move(args.NANDImage), std::move(args.DSiSDCard)), SDIO(*this), diff --git a/src/DSi.h b/src/DSi.h index 1d010e0f..23a2460c 100644 --- a/src/DSi.h +++ b/src/DSi.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -130,7 +130,8 @@ public: void ARM7IOWrite32(u32 addr, u32 val) override; public: - DSi(DSiArgs&& args) noexcept; + DSi(DSiArgs&& args, void* userdata = nullptr) noexcept; + //DSi() noexcept; ~DSi() noexcept override; DSi(const DSi&) = delete; DSi& operator=(const DSi&) = delete; diff --git a/src/DSi_AES.cpp b/src/DSi_AES.cpp index 379dea13..36fe2892 100644 --- a/src/DSi_AES.cpp +++ b/src/DSi_AES.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/DSi_AES.h b/src/DSi_AES.h index d83c870e..f3b79868 100644 --- a/src/DSi_AES.h +++ b/src/DSi_AES.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -28,7 +28,7 @@ namespace melonDS { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wattributes" -#if defined(__GNUC__) && (__GNUC__ >= 11) // gcc 11.* +#if defined(__GNUC__) && (__GNUC__ >= 11) && defined(__SIZEOF_INT128__) // gcc 11.* // NOTE: Yes, the compiler does *not* recognize this code pattern, so it is indeed an optimization. __attribute((always_inline)) static void Bswap128(void* Dst, const void* Src) { diff --git a/src/DSi_Camera.cpp b/src/DSi_Camera.cpp index a1cdbe0a..b1d60d04 100644 --- a/src/DSi_Camera.cpp +++ b/src/DSi_Camera.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -410,7 +410,7 @@ void DSi_Camera::DoSavestate(Savestate* file) void DSi_Camera::Reset() { - Platform::Camera_Stop(Num); + Platform::Camera_Stop(Num, DSi.UserData); DataPos = 0; RegAddr = 0; @@ -435,7 +435,7 @@ void DSi_Camera::Reset() void DSi_Camera::Stop() { - Platform::Camera_Stop(Num); + Platform::Camera_Stop(Num, DSi.UserData); } bool DSi_Camera::IsActivated() const @@ -474,7 +474,7 @@ void DSi_Camera::StartTransfer() FrameFormat = 0; } - Platform::Camera_CaptureFrame(Num, FrameBuffer, 640, 480, true); + Platform::Camera_CaptureFrame(Num, FrameBuffer, 640, 480, true, DSi.UserData); } bool DSi_Camera::TransferDone() const @@ -655,8 +655,8 @@ void DSi_Camera::I2C_WriteReg(u16 addr, u16 val) StandbyCnt = val; //printf("CAM%d STBCNT=%04X (%04X)\n", Num, StandbyCnt, val); bool isactive = IsActivated(); - if (isactive && !wasactive) Platform::Camera_Start(Num); - else if (wasactive && !isactive) Platform::Camera_Stop(Num); + if (isactive && !wasactive) Platform::Camera_Start(Num, DSi.UserData); + else if (wasactive && !isactive) Platform::Camera_Stop(Num, DSi.UserData); } return; case 0x001A: @@ -665,8 +665,8 @@ void DSi_Camera::I2C_WriteReg(u16 addr, u16 val) MiscCnt = val & 0x0B7B; //printf("CAM%d MISCCNT=%04X (%04X)\n", Num, MiscCnt, val); bool isactive = IsActivated(); - if (isactive && !wasactive) Platform::Camera_Start(Num); - else if (wasactive && !isactive) Platform::Camera_Stop(Num); + if (isactive && !wasactive) Platform::Camera_Start(Num, DSi.UserData); + else if (wasactive && !isactive) Platform::Camera_Stop(Num, DSi.UserData); } return; diff --git a/src/DSi_Camera.h b/src/DSi_Camera.h index 363cea43..604e06ac 100644 --- a/src/DSi_Camera.h +++ b/src/DSi_Camera.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/DSi_I2C.cpp b/src/DSi_I2C.cpp index 28f98dc8..f28562e9 100644 --- a/src/DSi_I2C.cpp +++ b/src/DSi_I2C.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/DSi_I2C.h b/src/DSi_I2C.h index 5dfeebd0..3102ffeb 100644 --- a/src/DSi_I2C.h +++ b/src/DSi_I2C.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -87,6 +87,7 @@ public: void DoSavestate(Savestate* file) override; u8 GetBootFlag() const; + void SetBootFlag(u8 boot) noexcept { Registers[0x70] = boot; } bool GetBatteryCharging() const; void SetBatteryCharging(bool charging); diff --git a/src/DSi_NAND.cpp b/src/DSi_NAND.cpp index 8da02540..a6b6c566 100644 --- a/src/DSi_NAND.cpp +++ b/src/DSi_NAND.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/DSi_NAND.h b/src/DSi_NAND.h index 104845d5..7af434d9 100644 --- a/src/DSi_NAND.h +++ b/src/DSi_NAND.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/DSi_NDMA.cpp b/src/DSi_NDMA.cpp index fe1f0ba7..452ac6e6 100644 --- a/src/DSi_NDMA.cpp +++ b/src/DSi_NDMA.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -22,6 +22,7 @@ #include "DSi_NDMA.h" #include "GPU.h" #include "DSi_AES.h" +#include "GPU3D.h" namespace melonDS { diff --git a/src/DSi_NDMA.h b/src/DSi_NDMA.h index fb34dbdf..9f8e6706 100644 --- a/src/DSi_NDMA.h +++ b/src/DSi_NDMA.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/DSi_NWifi.cpp b/src/DSi_NWifi.cpp index a6177dec..9827bdbe 100644 --- a/src/DSi_NWifi.cpp +++ b/src/DSi_NWifi.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -1334,7 +1334,7 @@ void DSi_NWifi::WMI_SendPacket(u16 len) } printf("\n");*/ - Platform::LAN_SendPacket(LANBuffer, lan_len); + Platform::Net_SendPacket(LANBuffer, lan_len, DSi.UserData); } void DSi_NWifi::SendWMIEvent(u8 ep, u16 id, u8* data, u32 len) @@ -1442,20 +1442,25 @@ void DSi_NWifi::CheckRX() if (!Mailbox[8].CanFit(2048)) return; - int rxlen = Platform::LAN_RecvPacket(LANBuffer); - if (rxlen > 0) + int rxlen = Platform::Net_RecvPacket(LANBuffer, DSi.UserData); + while (rxlen > 0) { - //printf("WMI packet recv %04X %04X %04X\n", *(u16*)&LANBuffer[0], *(u16*)&LANBuffer[2], *(u16*)&LANBuffer[4]); // check destination MAC if (*(u32*)&LANBuffer[0] != 0xFFFFFFFF || *(u16*)&LANBuffer[4] != 0xFFFF) { if (memcmp(&LANBuffer[0], &EEPROM[0x00A], 6)) - return; + { + rxlen = Platform::Net_RecvPacket(LANBuffer, DSi.UserData); + continue; + } } // check source MAC, in case we get a packet we just sent out if (!memcmp(&LANBuffer[6], &EEPROM[0x00A], 6)) - return; + { + rxlen = Platform::Net_RecvPacket(LANBuffer, DSi.UserData); + continue; + } // packet is good @@ -1502,6 +1507,7 @@ void DSi_NWifi::CheckRX() Mailbox[8].Write(LANBuffer[14+i]); DrainRXBuffer(); + return; } } diff --git a/src/DSi_NWifi.h b/src/DSi_NWifi.h index 39e9459c..84ac8a49 100644 --- a/src/DSi_NWifi.h +++ b/src/DSi_NWifi.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/DSi_SD.cpp b/src/DSi_SD.cpp index 72fe3756..c600bc76 100644 --- a/src/DSi_SD.cpp +++ b/src/DSi_SD.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/DSi_SD.h b/src/DSi_SD.h index 29620dc5..5d376600 100644 --- a/src/DSi_SD.h +++ b/src/DSi_SD.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/DSi_SPI_TSC.cpp b/src/DSi_SPI_TSC.cpp index d515db9f..dbb60c10 100644 --- a/src/DSi_SPI_TSC.cpp +++ b/src/DSi_SPI_TSC.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/DSi_SPI_TSC.h b/src/DSi_SPI_TSC.h index d1a71063..f20f7ac1 100644 --- a/src/DSi_SPI_TSC.h +++ b/src/DSi_SPI_TSC.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/DSi_TMD.h b/src/DSi_TMD.h index f07b3d1c..5ea91a6f 100644 --- a/src/DSi_TMD.h +++ b/src/DSi_TMD.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/FATIO.cpp b/src/FATIO.cpp index 233014e4..aea33ee6 100644 --- a/src/FATIO.cpp +++ b/src/FATIO.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/FATIO.h b/src/FATIO.h index 6d8aa499..f8184885 100644 --- a/src/FATIO.h +++ b/src/FATIO.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/FATStorage.cpp b/src/FATStorage.cpp index 0f1bf235..200c99d5 100644 --- a/src/FATStorage.cpp +++ b/src/FATStorage.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -110,6 +110,7 @@ bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len) res = f_mount(&fs, "0:", 1); if (res != FR_OK) { + f_unmount("0:"); ff_disk_close(); return false; } @@ -146,6 +147,7 @@ u32 FATStorage::ReadFile(const std::string& path, u32 start, u32 len, u8* data) res = f_mount(&fs, "0:", 1); if (res != FR_OK) { + f_unmount("0:"); ff_disk_close(); return false; } @@ -1144,6 +1146,7 @@ bool FATStorage::Save() res = f_mount(&fs, "0:", 1); if (res != FR_OK) { + f_unmount("0:"); ff_disk_close(); return false; } diff --git a/src/FATStorage.h b/src/FATStorage.h index 00628461..030b765b 100644 --- a/src/FATStorage.h +++ b/src/FATStorage.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/FIFO.h b/src/FIFO.h index 026c2c7f..5fc04832 100644 --- a/src/FIFO.h +++ b/src/FIFO.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -24,6 +24,7 @@ namespace melonDS { + template class FIFO { @@ -191,5 +192,121 @@ private: u32 ReadPos, WritePos; }; +template +class RingBuffer +{ +public: + void Clear() + { + NumOccupied = 0; + ReadPos = 0; + WritePos = 0; + memset(Buffer, 0, Size); + } + + + void DoSavestate(Savestate* file) + { + file->Var32(&NumOccupied); + file->Var32(&ReadPos); + file->Var32(&WritePos); + + file->VarArray(Buffer, Size); + } + + + bool Write(const void* data, u32 len) + { + if (!CanFit(len)) return false; + + if ((WritePos + len) >= Size) + { + u32 part1 = Size - WritePos; + memcpy(&Buffer[WritePos], data, part1); + if (len > part1) + memcpy(Buffer, &((u8*)data)[part1], len - part1); + WritePos = len - part1; + } + else + { + memcpy(&Buffer[WritePos], data, len); + WritePos += len; + } + + NumOccupied += len; + + return true; + } + + bool Read(void* data, u32 len) + { + if (NumOccupied < len) return false; + + u32 readpos = ReadPos; + if ((readpos + len) >= Size) + { + u32 part1 = Size - readpos; + memcpy(data, &Buffer[readpos], part1); + if (len > part1) + memcpy(&((u8*)data)[part1], Buffer, len - part1); + ReadPos = len - part1; + } + else + { + memcpy(data, &Buffer[readpos], len); + ReadPos += len; + } + + NumOccupied -= len; + return true; + } + + bool Peek(void* data, u32 offset, u32 len) + { + if (NumOccupied < len) return false; + + u32 readpos = ReadPos + offset; + if (readpos >= Size) readpos -= Size; + + if ((readpos + len) >= Size) + { + u32 part1 = Size - readpos; + memcpy(data, &Buffer[readpos], part1); + if (len > part1) + memcpy(&((u8*)data)[part1], Buffer, len - part1); + } + else + { + memcpy(data, &Buffer[readpos], len); + } + + return true; + } + + bool Skip(u32 len) + { + if (NumOccupied < len) return false; + + ReadPos += len; + if (ReadPos >= Size) + ReadPos -= Size; + + NumOccupied -= len; + return true; + } + + u32 Level() const { return NumOccupied; } + bool IsEmpty() const { return NumOccupied == 0; } + bool IsFull() const { return NumOccupied >= Size; } + + bool CanFit(u32 num) const { return ((NumOccupied + num) <= Size); } + +private: + u8 Buffer[Size] = {0}; + u32 NumOccupied = 0; + u32 ReadPos = 0, WritePos = 0; +}; + } + #endif diff --git a/src/GBACart.cpp b/src/GBACart.cpp index 1be50e75..a62aca6b 100644 --- a/src/GBACart.cpp +++ b/src/GBACart.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -95,17 +95,18 @@ u32 CartCommon::GetSaveMemoryLength() const return 0; } -CartGame::CartGame(const u8* rom, u32 len, const u8* sram, u32 sramlen, GBACart::CartType type) : - CartGame(CopyToUnique(rom, len), len, CopyToUnique(sram, sramlen), sramlen, type) +CartGame::CartGame(const u8* rom, u32 len, const u8* sram, u32 sramlen, void* userdata, GBACart::CartType type) : + CartGame(CopyToUnique(rom, len), len, CopyToUnique(sram, sramlen), sramlen, userdata, type) { } -CartGame::CartGame(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen, GBACart::CartType type) : +CartGame::CartGame(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen, void* userdata, GBACart::CartType type) : CartCommon(type), ROM(std::move(rom)), ROMLength(len), SRAM(std::move(sram)), - SRAMLength(sramlen) + SRAMLength(sramlen), + UserData(userdata) { if (SRAM && SRAMLength) { @@ -170,7 +171,7 @@ void CartGame::DoSavestate(Savestate* file) file->Var8((u8*)&SRAMType); if ((!file->Saving) && SRAM) - Platform::WriteGBASave(SRAM.get(), SRAMLength, 0, SRAMLength); + Platform::WriteGBASave(SRAM.get(), SRAMLength, 0, SRAMLength, UserData); } void CartGame::SetupSave(u32 type) @@ -223,7 +224,7 @@ void CartGame::SetSaveMemory(const u8* savedata, u32 savelen) u32 len = std::min(savelen, SRAMLength); memcpy(SRAM.get(), savedata, len); - Platform::WriteGBASave(savedata, len, 0, len); + Platform::WriteGBASave(savedata, len, 0, len, UserData); } u16 CartGame::ROMRead(u32 addr) const @@ -464,7 +465,7 @@ void CartGame::SRAMWrite_FLASH(u32 addr, u8 val) u32 start_addr = addr + 0x10000 * SRAMFlashState.bank; memset((u8*)&SRAM[start_addr], 0xFF, 0x1000); - Platform::WriteGBASave(SRAM.get(), SRAMLength, start_addr, 0x1000); + Platform::WriteGBASave(SRAM.get(), SRAMLength, start_addr, 0x1000, UserData); } SRAMFlashState.state = 0; SRAMFlashState.cmd = 0; @@ -523,18 +524,18 @@ void CartGame::SRAMWrite_SRAM(u32 addr, u8 val) *(u8*)&SRAM[addr] = val; // TODO: optimize this!! - Platform::WriteGBASave(SRAM.get(), SRAMLength, addr, 1); + Platform::WriteGBASave(SRAM.get(), SRAMLength, addr, 1, UserData); } } -CartGameSolarSensor::CartGameSolarSensor(const u8* rom, u32 len, const u8* sram, u32 sramlen) : - CartGameSolarSensor(CopyToUnique(rom, len), len, CopyToUnique(sram, sramlen), sramlen) +CartGameSolarSensor::CartGameSolarSensor(const u8* rom, u32 len, const u8* sram, u32 sramlen, void* userdata) : + CartGameSolarSensor(CopyToUnique(rom, len), len, CopyToUnique(sram, sramlen), sramlen, userdata) { } -CartGameSolarSensor::CartGameSolarSensor(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen) : - CartGame(std::move(rom), len, std::move(sram), sramlen, CartType::GameSolarSensor) +CartGameSolarSensor::CartGameSolarSensor(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen, void* userdata) : + CartGame(std::move(rom), len, std::move(sram), sramlen, userdata, CartType::GameSolarSensor) { } @@ -581,6 +582,11 @@ int CartGameSolarSensor::SetInput(int num, bool pressed) return -1; } +void CartGameSolarSensor::SetLightLevel(u8 level) noexcept +{ + LightLevel = std::clamp(level, 0, 10); +} + void CartGameSolarSensor::ProcessGPIO() { if (GPIO.data & 4) return; // Boktai chip select @@ -680,7 +686,45 @@ void CartRAMExpansion::ROMWrite(u32 addr, u16 val) } } -GBACartSlot::GBACartSlot(std::unique_ptr&& cart) noexcept : Cart(std::move(cart)) +CartRumblePak::CartRumblePak(void* userdata) : + CartCommon(RumblePak), + UserData(userdata) +{ +} + +CartRumblePak::~CartRumblePak() = default; + +void CartRumblePak::Reset() +{ + RumbleState = 0; +} + +void CartRumblePak::DoSavestate(Savestate* file) +{ + CartCommon::DoSavestate(file); + file->Var16(&RumbleState); +} + +u16 CartRumblePak::ROMRead(u32 addr) const +{ + // A1 is pulled low on a real Rumble Pak, so return the + // necessary detection value here, + // and let the existing open bus implementation take care of the rest + return 0xFFFD; +} + +void CartRumblePak::ROMWrite(u32 addr, u16 val) +{ + addr &= 0x01FFFFFF; + if (RumbleState != val) + { + Platform::Addon_RumbleStop(UserData); + RumbleState = val; + Platform::Addon_RumbleStart(16, UserData); + } +} + +GBACartSlot::GBACartSlot(melonDS::NDS& nds, std::unique_ptr&& cart) noexcept : NDS(nds), Cart(std::move(cart)) { } @@ -723,24 +767,24 @@ void GBACartSlot::DoSavestate(Savestate* file) noexcept if (Cart) Cart->DoSavestate(file); } -std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen) +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, void* userdata) { - return ParseROM(std::move(romdata), romlen, nullptr, 0); + return ParseROM(std::move(romdata), romlen, nullptr, 0, userdata); } -std::unique_ptr ParseROM(const u8* romdata, u32 romlen, const u8* sramdata, u32 sramlen) +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, const u8* sramdata, u32 sramlen, void* userdata) { auto [romcopy, romcopylen] = PadToPowerOf2(romdata, romlen); - return ParseROM(std::move(romcopy), romcopylen, CopyToUnique(sramdata, sramlen), sramlen); + return ParseROM(std::move(romcopy), romcopylen, CopyToUnique(sramdata, sramlen), sramlen, userdata); } -std::unique_ptr ParseROM(const u8* romdata, u32 romlen) +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, void* userdata) { - return ParseROM(romdata, romlen, nullptr, 0); + return ParseROM(romdata, romlen, nullptr, 0, userdata); } -std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::unique_ptr&& sramdata, u32 sramlen) +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::unique_ptr&& sramdata, u32 sramlen, void* userdata) { if (romdata == nullptr) { @@ -773,9 +817,9 @@ std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen std::unique_ptr cart; if (solarsensor) - cart = std::make_unique(std::move(cartrom), cartromsize, std::move(sramdata), sramlen); + cart = std::make_unique(std::move(cartrom), cartromsize, std::move(sramdata), sramlen, userdata); else - cart = std::make_unique(std::move(cartrom), cartromsize, std::move(sramdata), sramlen); + cart = std::make_unique(std::move(cartrom), cartromsize, std::move(sramdata), sramlen, userdata); cart->Reset(); @@ -794,7 +838,7 @@ void GBACartSlot::SetCart(std::unique_ptr&& cart) noexcept if (!Cart) { - Log(LogLevel::Info, "Ejected GBA cart"); + Log(LogLevel::Info, "Ejected GBA cart\n"); return; } @@ -820,13 +864,16 @@ void GBACartSlot::SetSaveMemory(const u8* savedata, u32 savelen) noexcept } } -void GBACartSlot::LoadAddon(int type) noexcept +void GBACartSlot::LoadAddon(void* userdata, int type) noexcept { switch (type) { case GBAAddon_RAMExpansion: Cart = std::make_unique(); break; + case GBAAddon_RumblePak: + Cart = std::make_unique(userdata); + break; default: Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type); @@ -875,4 +922,4 @@ void GBACartSlot::SRAMWrite(u32 addr, u8 val) noexcept } -} \ No newline at end of file +} diff --git a/src/GBACart.h b/src/GBACart.h index 493bf6b8..726a234d 100644 --- a/src/GBACart.h +++ b/src/GBACart.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -32,6 +32,7 @@ enum CartType Game = 0x101, GameSolarSensor = 0x102, RAMExpansion = 0x201, + RumblePak = 0x202, }; // CartCommon -- base code shared by all cart types @@ -72,8 +73,8 @@ private: class CartGame : public CartCommon { public: - CartGame(const u8* rom, u32 len, const u8* sram, u32 sramlen, GBACart::CartType type = GBACart::CartType::Game); - CartGame(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen, GBACart::CartType type = GBACart::CartType::Game); + CartGame(const u8* rom, u32 len, const u8* sram, u32 sramlen, void* userdata, GBACart::CartType type = GBACart::CartType::Game); + CartGame(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen, void* userdata, GBACart::CartType type = GBACart::CartType::Game); ~CartGame() override; u32 Checksum() const override; @@ -104,6 +105,8 @@ protected: u8 SRAMRead_SRAM(u32 addr); void SRAMWrite_SRAM(u32 addr, u8 val); + void* UserData; + std::unique_ptr ROM; u32 ROMLength; @@ -147,14 +150,16 @@ private: class CartGameSolarSensor : public CartGame { public: - CartGameSolarSensor(const u8* rom, u32 len, const u8* sram, u32 sramlen); - CartGameSolarSensor(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen); + CartGameSolarSensor(const u8* rom, u32 len, const u8* sram, u32 sramlen, void* userdata); + CartGameSolarSensor(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen, void* userdata); void Reset() override; void DoSavestate(Savestate* file) override; int SetInput(int num, bool pressed) override; + void SetLightLevel(u8 level) noexcept; + [[nodiscard]] u8 GetLightLevel() const noexcept { return LightLevel; } protected: void ProcessGPIO() override; @@ -187,6 +192,25 @@ private: u16 RAMEnable = 0; }; +// CartRumblePak -- DS Rumble Pak (used in various NDS games) +class CartRumblePak : public CartCommon +{ +public: + CartRumblePak(void* userdata); + ~CartRumblePak() override; + + void Reset() override; + + void DoSavestate(Savestate* file) override; + + u16 ROMRead(u32 addr) const override; + void ROMWrite(u32 addr, u16 val) override; + +private: + void* UserData; + u16 RumbleState = 0; +}; + // possible inputs for GBA carts that might accept user input enum { @@ -197,7 +221,7 @@ enum class GBACartSlot { public: - GBACartSlot(std::unique_ptr&& cart = nullptr) noexcept; + GBACartSlot(melonDS::NDS& nds, std::unique_ptr&& cart = nullptr) noexcept; ~GBACartSlot() noexcept = default; void Reset() noexcept; void DoSavestate(Savestate* file) noexcept; @@ -217,7 +241,7 @@ public: [[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); } [[nodiscard]] const CartCommon* GetCart() const noexcept { return Cart.get(); } - void LoadAddon(int type) noexcept; + void LoadAddon(void* userdata, int type) noexcept; /// @return The cart that was in the cart slot if any, /// or \c nullptr if the cart slot was empty. @@ -258,6 +282,7 @@ public: /// if a cart is loaded and supports SRAM, otherwise zero. [[nodiscard]] u32 GetSaveMemoryLength() const noexcept { return Cart ? Cart->GetSaveMemoryLength() : 0; } private: + melonDS::NDS& NDS; std::unique_ptr Cart = nullptr; u16 OpenBusDecay = 0; }; @@ -270,9 +295,9 @@ private: /// @param romlen The length of the ROM data in bytes. /// @returns A \c GBACart::CartCommon object representing the parsed ROM, /// or \c nullptr if the ROM data couldn't be parsed. -std::unique_ptr ParseROM(const u8* romdata, u32 romlen); -std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen); -std::unique_ptr ParseROM(const u8* romdata, u32 romlen, const u8* sramdata, u32 sramlen); +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, void* userdata = nullptr); +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, void* userdata = nullptr); +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, const u8* sramdata, u32 sramlen, void* userdata = nullptr); /// @param romdata The ROM data to parse. Will be moved-from. /// @param romlen Length of romdata in bytes. @@ -282,7 +307,7 @@ std::unique_ptr ParseROM(const u8* romdata, u32 romlen, const u8* sr /// May be zero, in which case the cart will have no save data. /// @return Unique pointer to the parsed GBA cart, /// or \c nullptr if there was an error. -std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::unique_ptr&& sramdata, u32 sramlen); +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::unique_ptr&& sramdata, u32 sramlen, void* userdata = nullptr); } diff --git a/src/GPU.cpp b/src/GPU.cpp index f23e641e..f24d8ab5 100644 --- a/src/GPU.cpp +++ b/src/GPU.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -23,7 +23,7 @@ #include "ARMJIT.h" #include "GPU2D_Soft.h" -#include "GPU3D_Soft.h" +#include "GPU3D.h" namespace melonDS { diff --git a/src/GPU.h b/src/GPU.h index 780d5e01..5c373ca8 100644 --- a/src/GPU.h +++ b/src/GPU.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -499,6 +499,17 @@ public: OAMDirty |= 1 << (addr / 1024); } + template + inline T ReadVRAMFlat_Texture(u32 addr) const + { + return *(T*)&VRAMFlat_Texture[addr & 0x7FFFF]; + } + template + inline T ReadVRAMFlat_TexPal(u32 addr) const + { + return *(T*)&VRAMFlat_TexPal[addr & 0x1FFFF]; + } + void SetPowerCnt(u32 val) noexcept; void StartFrame() noexcept; diff --git a/src/GPU2D.cpp b/src/GPU2D.cpp index e0aa630d..e76e85c1 100644 --- a/src/GPU2D.cpp +++ b/src/GPU2D.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -20,6 +20,7 @@ #include #include "NDS.h" #include "GPU.h" +#include "GPU3D.h" namespace melonDS { @@ -386,6 +387,14 @@ void Unit::Write16(u32 addr, u16 val) if (!Num) GPU.GPU3D.SetRenderXPos(val); break; + case 0x064: + CaptureCnt = (CaptureCnt & 0xFFFF0000) | (val & 0xEF3F1F1F); + return; + + case 0x066: + CaptureCnt = (CaptureCnt & 0xFFFF) | ((val << 16) & 0xEF3F1F1F); + return; + case 0x068: DispFIFO[DispFIFOWritePtr] = val; return; diff --git a/src/GPU2D.h b/src/GPU2D.h index e87167cb..f56167a1 100644 --- a/src/GPU2D.h +++ b/src/GPU2D.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/GPU2D_Soft.cpp b/src/GPU2D_Soft.cpp index e01d3665..6ad2cd3e 100644 --- a/src/GPU2D_Soft.cpp +++ b/src/GPU2D_Soft.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -18,7 +18,7 @@ #include "GPU2D_Soft.h" #include "GPU.h" -#include "GPU3D_OpenGL.h" +#include "GPU3D.h" namespace melonDS { @@ -254,7 +254,11 @@ void SoftRenderer::DrawScanline(u32 line, Unit* unit) if (GPU.GPU3D.IsRendererAccelerated()) { - dst[256*3] = masterBrightness | (CurUnit->DispCnt & 0x30000); + u32 xpos = GPU.GPU3D.GetRenderXPos(); + + dst[256*3] = masterBrightness | + (CurUnit->DispCnt & 0x30000) | + (xpos << 24) | ((xpos & 0x100) << 15); return; } @@ -1503,7 +1507,7 @@ void SoftRenderer::ApplySpriteMosaicX() u32* objLine = OBJLine[CurUnit->Num]; - u8* curOBJXMosaicTable = MosaicTable[CurUnit->OBJMosaicSize[1]].data(); + u8* curOBJXMosaicTable = MosaicTable[CurUnit->OBJMosaicSize[0]].data(); u32 lastcolor = objLine[0]; diff --git a/src/GPU2D_Soft.h b/src/GPU2D_Soft.h index befb67f6..d9942f61 100644 --- a/src/GPU2D_Soft.h +++ b/src/GPU2D_Soft.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/GPU3D.cpp b/src/GPU3D.cpp index 47abae2f..4a1426aa 100644 --- a/src/GPU3D.cpp +++ b/src/GPU3D.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -24,6 +24,7 @@ #include "FIFO.h" #include "GPU3D_Soft.h" #include "Platform.h" +#include "GPU3D.h" namespace melonDS { @@ -191,7 +192,7 @@ void GPU3D::Reset() noexcept CmdStallQueue.Clear(); - ZeroDotWLimit = 0; // CHECKME + ZeroDotWLimit = 0xFFFFFF; GXStat = 0; @@ -273,6 +274,8 @@ void GPU3D::Reset() noexcept memset(MatEmission, 0, sizeof(MatSpecular)); UseShininessTable = false; + // Shininess table seems to be uninitialized garbage, at least on n3dsxl hw? + // Also doesn't seem to be cleared properly unless the system is fully powered off? memset(ShininessTable, 0, sizeof(ShininessTable)); PolygonAttr = 0; @@ -1278,7 +1281,7 @@ void GPU3D::SubmitPolygon() noexcept { Vertex* vtx = poly->Vertices[i]; - if (vtx->FinalPosition[1] < ytop || (vtx->FinalPosition[1] == ytop && vtx->FinalPosition[0] < xtop)) + if (vtx->FinalPosition[1] < ytop) { xtop = vtx->FinalPosition[0]; ytop = vtx->FinalPosition[1]; @@ -1458,67 +1461,86 @@ void GPU3D::CalculateLighting() noexcept TexCoords[1] = RawTexCoords[1] + (((s64)Normal[0]*TexMatrix[1] + (s64)Normal[1]*TexMatrix[5] + (s64)Normal[2]*TexMatrix[9]) >> 21); } - s32 normaltrans[3]; - normaltrans[0] = (Normal[0]*VecMatrix[0] + Normal[1]*VecMatrix[4] + Normal[2]*VecMatrix[8]) >> 12; - normaltrans[1] = (Normal[0]*VecMatrix[1] + Normal[1]*VecMatrix[5] + Normal[2]*VecMatrix[9]) >> 12; - normaltrans[2] = (Normal[0]*VecMatrix[2] + Normal[1]*VecMatrix[6] + Normal[2]*VecMatrix[10]) >> 12; - - VertexColor[0] = MatEmission[0]; - VertexColor[1] = MatEmission[1]; - VertexColor[2] = MatEmission[2]; + s32 normaltrans[3]; // should be 1 bit sign 10 bits frac + normaltrans[0] = ((Normal[0]*VecMatrix[0] + Normal[1]*VecMatrix[4] + Normal[2]*VecMatrix[8]) << 9) >> 21; + normaltrans[1] = ((Normal[0]*VecMatrix[1] + Normal[1]*VecMatrix[5] + Normal[2]*VecMatrix[9]) << 9) >> 21; + normaltrans[2] = ((Normal[0]*VecMatrix[2] + Normal[1]*VecMatrix[6] + Normal[2]*VecMatrix[10]) << 9) >> 21; s32 c = 0; + u32 vtxbuff[3] = + { + (u32)MatEmission[0] << 14, + (u32)MatEmission[1] << 14, + (u32)MatEmission[2] << 14 + }; for (int i = 0; i < 4; i++) { if (!(CurPolygonAttr & (1<1) - // according to some hardware tests - // * diffuse level is saturated to 255 - // * shininess level mirrors back to 0 and is ANDed with 0xFF, that before being squared - // TODO: check how it behaves when the computed shininess is >=0x200 + // (credit to azusa for working out most of the details of the diff. algorithm, and essentially the entire spec. algorithm) + + // calculate dot product + // bottom 9 bits are discarded after multiplying and before adding + s32 dot = ((LightDirection[i][0]*normaltrans[0]) >> 9) + + ((LightDirection[i][1]*normaltrans[1]) >> 9) + + ((LightDirection[i][2]*normaltrans[2]) >> 9); - s32 difflevel = (-(LightDirection[i][0]*normaltrans[0] + - LightDirection[i][1]*normaltrans[1] + - LightDirection[i][2]*normaltrans[2])) >> 10; - if (difflevel < 0) difflevel = 0; - else if (difflevel > 255) difflevel = 255; + s32 shinelevel; + if (dot > 0) + { + // -- diffuse lighting -- + + // convert dot to signed 11 bit int + // then we truncate the result of the multiplications to an unsigned 20 bits before adding to the vtx color + s32 diffdot = (dot << 21) >> 21; + vtxbuff[0] += (MatDiffuse[0] * LightColor[i][0] * diffdot) & 0xFFFFF; + vtxbuff[1] += (MatDiffuse[1] * LightColor[i][1] * diffdot) & 0xFFFFF; + vtxbuff[2] += (MatDiffuse[2] * LightColor[i][2] * diffdot) & 0xFFFFF; - s32 shinelevel = -(((LightDirection[i][0]>>1)*normaltrans[0] + - (LightDirection[i][1]>>1)*normaltrans[1] + - ((LightDirection[i][2]-0x200)>>1)*normaltrans[2]) >> 10); - if (shinelevel < 0) shinelevel = 0; - else if (shinelevel > 255) shinelevel = (0x100 - shinelevel) & 0xFF; - shinelevel = ((shinelevel * shinelevel) >> 7) - 0x100; // really (2*shinelevel*shinelevel)-1 - if (shinelevel < 0) shinelevel = 0; + // -- specular lighting -- + + // reuse the dot product from diffuse lighting + dot += normaltrans[2]; + // convert to s11, then square it, and truncate to 10 bits + dot = (dot << 21) >> 21; + dot = ((dot * dot) >> 10) & 0x3FF; + + // multiply dot and reciprocal, the subtract '1' + shinelevel = ((dot * SpecRecip[i]) >> 8) - (1<<9); + + if (shinelevel < 0) shinelevel = 0; + else + { + // sign extend to convert to signed 14 bit integer + shinelevel = (shinelevel << 18) >> 18; + if (shinelevel < 0) shinelevel = 0; // for some reason there seems to be a redundant check for <0? + else if (shinelevel > 0x1FF) shinelevel = 0x1FF; + } + } + else shinelevel = 0; + + // convert shinelevel to use for lookup in the shininess table if enabled. if (UseShininessTable) { - // checkme - shinelevel >>= 1; + shinelevel >>= 2; shinelevel = ShininessTable[shinelevel]; + shinelevel <<= 1; } - VertexColor[0] += ((MatSpecular[0] * LightColor[i][0] * shinelevel) >> 13); - VertexColor[0] += ((MatDiffuse[0] * LightColor[i][0] * difflevel) >> 13); - VertexColor[0] += ((MatAmbient[0] * LightColor[i][0]) >> 5); - - VertexColor[1] += ((MatSpecular[1] * LightColor[i][1] * shinelevel) >> 13); - VertexColor[1] += ((MatDiffuse[1] * LightColor[i][1] * difflevel) >> 13); - VertexColor[1] += ((MatAmbient[1] * LightColor[i][1]) >> 5); - - VertexColor[2] += ((MatSpecular[2] * LightColor[i][2] * shinelevel) >> 13); - VertexColor[2] += ((MatDiffuse[2] * LightColor[i][2] * difflevel) >> 13); - VertexColor[2] += ((MatAmbient[2] * LightColor[i][2]) >> 5); - - if (VertexColor[0] > 31) VertexColor[0] = 31; - if (VertexColor[1] > 31) VertexColor[1] = 31; - if (VertexColor[2] > 31) VertexColor[2] = 31; + // Note: ambient seems to be a plain bitshift + vtxbuff[0] += ((MatSpecular[0] * shinelevel) + (MatAmbient[0] << 9)) * LightColor[i][0]; + vtxbuff[1] += ((MatSpecular[1] * shinelevel) + (MatAmbient[1] << 9)) * LightColor[i][1]; + vtxbuff[2] += ((MatSpecular[2] * shinelevel) + (MatAmbient[2] << 9)) * LightColor[i][2]; c++; } + VertexColor[0] = (vtxbuff[0] >> 14 > 31) ? 31 : (vtxbuff[0] >> 14); + VertexColor[1] = (vtxbuff[1] >> 14 > 31) ? 31 : (vtxbuff[1] >> 14); + VertexColor[2] = (vtxbuff[2] >> 14 > 31) ? 31 : (vtxbuff[2] >> 14); + if (c < 1) c = 1; NormalPipeline = 7; AddCycles(c); @@ -2011,9 +2033,15 @@ void GPU3D::ExecuteCommand() noexcept dir[0] = (s16)((entry.Param & 0x000003FF) << 6) >> 6; dir[1] = (s16)((entry.Param & 0x000FFC00) >> 4) >> 6; dir[2] = (s16)((entry.Param & 0x3FF00000) >> 14) >> 6; - LightDirection[l][0] = (dir[0]*VecMatrix[0] + dir[1]*VecMatrix[4] + dir[2]*VecMatrix[8]) >> 12; - LightDirection[l][1] = (dir[0]*VecMatrix[1] + dir[1]*VecMatrix[5] + dir[2]*VecMatrix[9]) >> 12; - LightDirection[l][2] = (dir[0]*VecMatrix[2] + dir[1]*VecMatrix[6] + dir[2]*VecMatrix[10]) >> 12; + // the order of operations here is very specific: discard bottom 12 bits -> negate -> then sign extend to convert to 11 bit signed int + // except for when used to calculate the specular reciprocal; then it's: sign extend -> discard lsb -> negate. + LightDirection[l][0] = (-((dir[0]*VecMatrix[0] + dir[1]*VecMatrix[4] + dir[2]*VecMatrix[8] ) >> 12) << 21) >> 21; + LightDirection[l][1] = (-((dir[0]*VecMatrix[1] + dir[1]*VecMatrix[5] + dir[2]*VecMatrix[9] ) >> 12) << 21) >> 21; + LightDirection[l][2] = (-((dir[0]*VecMatrix[2] + dir[1]*VecMatrix[6] + dir[2]*VecMatrix[10]) >> 12) << 21) >> 21; + s32 den = -(((dir[0]*VecMatrix[2] + dir[1]*VecMatrix[6] + dir[2]*VecMatrix[10]) << 9) >> 21) + (1<<9); + + if (den == 0) SpecRecip[l] = 0; + else SpecRecip[l] = (1<<18) / den; } AddCycles(5); break; diff --git a/src/GPU3D.h b/src/GPU3D.h index 4a5fe6e0..d10df55f 100644 --- a/src/GPU3D.h +++ b/src/GPU3D.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -197,7 +197,7 @@ public: FIFO CmdStallQueue {}; - u32 ZeroDotWLimit = 0; + u32 ZeroDotWLimit = 0xFFFFFF; u32 GXStat = 0; @@ -286,6 +286,7 @@ public: s16 Normal[3] {}; s16 LightDirection[4][3] {}; + s32 SpecRecip[4] {}; u8 LightColor[4][3] {}; u8 MatDiffuse[3] {}; u8 MatAmbient[3] {}; @@ -349,7 +350,14 @@ public: virtual void RestartFrame(GPU& gpu) {}; virtual u32* GetLine(int line) = 0; virtual void Blit(const GPU& gpu) {}; + + virtual void SetupAccelFrame() {} virtual void PrepareCaptureFrame() {} + virtual void BindOutputTexture(int buffer) {} + + virtual bool NeedsShaderCompile() { return false; } + virtual void ShaderCompileStep(int& current, int& count) {} + protected: Renderer3D(bool Accelerated); }; diff --git a/src/GPU3D_Compute.cpp b/src/GPU3D_Compute.cpp new file mode 100644 index 00000000..346a6a53 --- /dev/null +++ b/src/GPU3D_Compute.cpp @@ -0,0 +1,1137 @@ +/* + Copyright 2016-2024 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 "GPU3D_Compute.h" + +#include + +#include "OpenGLSupport.h" + +#include "GPU3D_Compute_shaders.h" + +namespace melonDS +{ + +ComputeRenderer::ComputeRenderer(GLCompositor&& compositor) + : Renderer3D(true), Texcache(TexcacheOpenGLLoader()), CurGLCompositor(std::move(compositor)) +{} + +bool ComputeRenderer::CompileShader(GLuint& shader, const std::string& source, const std::initializer_list& defines) +{ + std::string shaderName; + std::string shaderSource; + shaderSource += "#version 430 core\n"; + for (const char* define : defines) + { + shaderSource += "#define "; + shaderSource += define; + shaderSource += '\n'; + shaderName += define; + shaderName += ','; + } + shaderSource += "#define ScreenWidth "; + shaderSource += std::to_string(ScreenWidth); + shaderSource += "\n#define ScreenHeight "; + shaderSource += std::to_string(ScreenHeight); + shaderSource += "\n#define MaxWorkTiles "; + shaderSource += std::to_string(MaxWorkTiles); + + shaderSource += ComputeRendererShaders::Common; + shaderSource += source; + + return OpenGL::CompileComputeProgram(shader, shaderSource.c_str(), shaderName.c_str()); +} + +void ComputeRenderer::ShaderCompileStep(int& current, int& count) +{ + current = ShaderStepIdx; + ShaderStepIdx++; + count = 33; + switch (current) + { + case 0: + CompileShader(ShaderInterpXSpans[0], ComputeRendererShaders::InterpSpans, {"InterpSpans", "ZBuffer"}); + return; + case 1: + CompileShader(ShaderInterpXSpans[1], ComputeRendererShaders::InterpSpans, {"InterpSpans", "WBuffer"}); + return; + case 2: + CompileShader(ShaderBinCombined, ComputeRendererShaders::BinCombined, {"BinCombined"}); + return; + case 3: + CompileShader(ShaderDepthBlend[0], ComputeRendererShaders::DepthBlend, {"DepthBlend", "ZBuffer"}); + return; + case 4: + CompileShader(ShaderDepthBlend[1], ComputeRendererShaders::DepthBlend, {"DepthBlend", "WBuffer"}); + return; + case 5: + CompileShader(ShaderRasteriseNoTexture[0], ComputeRendererShaders::Rasterise, {"Rasterise", "ZBuffer", "NoTexture"}); + return; + case 6: + CompileShader(ShaderRasteriseNoTexture[1], ComputeRendererShaders::Rasterise, {"Rasterise", "WBuffer", "NoTexture"}); + return; + case 7: + CompileShader(ShaderRasteriseNoTextureToon[0], ComputeRendererShaders::Rasterise, {"Rasterise", "ZBuffer", "NoTexture", "Toon"}); + return; + case 8: + CompileShader(ShaderRasteriseNoTextureToon[1], ComputeRendererShaders::Rasterise, {"Rasterise", "WBuffer", "NoTexture", "Toon"}); + return; + case 9: + CompileShader(ShaderRasteriseNoTextureHighlight[0], ComputeRendererShaders::Rasterise, {"Rasterise", "ZBuffer", "NoTexture", "Highlight"}); + return; + case 10: + CompileShader(ShaderRasteriseNoTextureHighlight[1], ComputeRendererShaders::Rasterise, {"Rasterise", "WBuffer", "NoTexture", "Highlight"}); + return; + case 11: + CompileShader(ShaderRasteriseUseTextureDecal[0], ComputeRendererShaders::Rasterise, {"Rasterise", "ZBuffer", "UseTexture", "Decal"}); + return; + case 12: + CompileShader(ShaderRasteriseUseTextureDecal[1], ComputeRendererShaders::Rasterise, {"Rasterise", "WBuffer", "UseTexture", "Decal"}); + return; + case 13: + CompileShader(ShaderRasteriseUseTextureModulate[0], ComputeRendererShaders::Rasterise, {"Rasterise", "ZBuffer", "UseTexture", "Modulate"}); + return; + case 14: + CompileShader(ShaderRasteriseUseTextureModulate[1], ComputeRendererShaders::Rasterise, {"Rasterise", "WBuffer", "UseTexture", "Modulate"}); + return; + case 15: + CompileShader(ShaderRasteriseUseTextureToon[0], ComputeRendererShaders::Rasterise, {"Rasterise", "ZBuffer", "UseTexture", "Toon"}); + return; + case 16: + CompileShader(ShaderRasteriseUseTextureToon[1], ComputeRendererShaders::Rasterise, {"Rasterise", "WBuffer", "UseTexture", "Toon"}); + return; + case 17: + CompileShader(ShaderRasteriseUseTextureHighlight[0], ComputeRendererShaders::Rasterise, {"Rasterise", "ZBuffer", "UseTexture", "Highlight"}); + return; + case 18: + CompileShader(ShaderRasteriseUseTextureHighlight[1], ComputeRendererShaders::Rasterise, {"Rasterise", "WBuffer", "UseTexture", "Highlight"}); + return; + case 19: + CompileShader(ShaderRasteriseShadowMask[0], ComputeRendererShaders::Rasterise, {"Rasterise", "ZBuffer", "ShadowMask"}); + return; + case 20: + CompileShader(ShaderRasteriseShadowMask[1], ComputeRendererShaders::Rasterise, {"Rasterise", "WBuffer", "ShadowMask"}); + return; + case 21: + CompileShader(ShaderClearCoarseBinMask, ComputeRendererShaders::ClearCoarseBinMask, {"ClearCoarseBinMask"}); + return; + case 22: + CompileShader(ShaderClearIndirectWorkCount, ComputeRendererShaders::ClearIndirectWorkCount, {"ClearIndirectWorkCount"}); + return; + case 23: + CompileShader(ShaderCalculateWorkListOffset, ComputeRendererShaders::CalcOffsets, {"CalculateWorkOffsets"}); + return; + case 24: + CompileShader(ShaderSortWork, ComputeRendererShaders::SortWork, {"SortWork"}); + return; + case 25: + CompileShader(ShaderFinalPass[0], ComputeRendererShaders::FinalPass, {"FinalPass"}); + return; + case 26: + CompileShader(ShaderFinalPass[1], ComputeRendererShaders::FinalPass, {"FinalPass", "EdgeMarking"}); + return; + case 27: + CompileShader(ShaderFinalPass[2], ComputeRendererShaders::FinalPass, {"FinalPass", "Fog"}); + return; + case 28: + CompileShader(ShaderFinalPass[3], ComputeRendererShaders::FinalPass, {"FinalPass", "EdgeMarking", "Fog"}); + return; + case 29: + CompileShader(ShaderFinalPass[4], ComputeRendererShaders::FinalPass, {"FinalPass", "AntiAliasing"}); + return; + case 30: + CompileShader(ShaderFinalPass[5], ComputeRendererShaders::FinalPass, {"FinalPass", "AntiAliasing", "EdgeMarking"}); + return; + case 31: + CompileShader(ShaderFinalPass[6], ComputeRendererShaders::FinalPass, {"FinalPass", "AntiAliasing", "Fog"}); + return; + case 32: + CompileShader(ShaderFinalPass[7], ComputeRendererShaders::FinalPass, {"FinalPass", "AntiAliasing", "EdgeMarking", "Fog"}); + return; + default: + __builtin_unreachable(); + return; + } +} + +void blah(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) +{ + printf("%s\n", message); +} + +std::unique_ptr ComputeRenderer::New() +{ + std::optional compositor = GLCompositor::New(); + if (!compositor) + return nullptr; + + std::unique_ptr result = std::unique_ptr(new ComputeRenderer(std::move(*compositor))); + + //glDebugMessageCallback(blah, NULL); + //glEnable(GL_DEBUG_OUTPUT); + glGenBuffers(1, &result->YSpanSetupMemory); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, result->YSpanSetupMemory); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(SpanSetupY)*MaxYSpanSetups, nullptr, GL_DYNAMIC_DRAW); + + glGenBuffers(1, &result->RenderPolygonMemory); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, result->RenderPolygonMemory); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(RenderPolygon)*2048, nullptr, GL_DYNAMIC_DRAW); + + glGenBuffers(1, &result->XSpanSetupMemory); + glGenBuffers(1, &result->BinResultMemory); + glGenBuffers(1, &result->FinalTileMemory); + glGenBuffers(1, &result->YSpanIndicesTextureMemory); + glGenBuffers(tilememoryLayer_Num, result->TileMemory); + glGenBuffers(1, &result->WorkDescMemory); + + glGenTextures(1, &result->YSpanIndicesTexture); + glGenTextures(1, &result->LowResFramebuffer); + glBindTexture(GL_TEXTURE_2D, result->LowResFramebuffer); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8UI, 256, 192); + + glGenBuffers(1, &result->MetaUniformMemory); + glBindBuffer(GL_UNIFORM_BUFFER, result->MetaUniformMemory); + glBufferData(GL_UNIFORM_BUFFER, sizeof(MetaUniform), nullptr, GL_DYNAMIC_DRAW); + + glGenSamplers(9, result->Samplers); + for (u32 j = 0; j < 3; j++) + { + for (u32 i = 0; i < 3; i++) + { + const GLenum translateWrapMode[3] = {GL_CLAMP_TO_EDGE, GL_REPEAT, GL_MIRRORED_REPEAT}; + glSamplerParameteri(result->Samplers[i+j*3], GL_TEXTURE_WRAP_S, translateWrapMode[i]); + glSamplerParameteri(result->Samplers[i+j*3], GL_TEXTURE_WRAP_T, translateWrapMode[j]); + glSamplerParameteri(result->Samplers[i+j*3], GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glSamplerParameterf(result->Samplers[i+j*3], GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + } + + glGenBuffers(1, &result->PixelBuffer); + glBindBuffer(GL_PIXEL_PACK_BUFFER, result->PixelBuffer); + glBufferData(GL_PIXEL_PACK_BUFFER, 256*192*4, NULL, GL_DYNAMIC_READ); + + return result; +} + +ComputeRenderer::~ComputeRenderer() +{ + Texcache.Reset(); + + glDeleteBuffers(1, &YSpanSetupMemory); + glDeleteBuffers(1, &RenderPolygonMemory); + glDeleteBuffers(1, &XSpanSetupMemory); + glDeleteBuffers(1, &BinResultMemory); + glDeleteBuffers(tilememoryLayer_Num, TileMemory); + glDeleteBuffers(1, &WorkDescMemory); + glDeleteBuffers(1, &FinalTileMemory); + glDeleteBuffers(1, &YSpanIndicesTextureMemory); + glDeleteTextures(1, &YSpanIndicesTexture); + glDeleteTextures(1, &Framebuffer); + glDeleteBuffers(1, &MetaUniformMemory); + + glDeleteSamplers(9, Samplers); + glDeleteBuffers(1, &PixelBuffer); +} + +void ComputeRenderer::DeleteShaders() +{ + std::initializer_list allPrograms = + { + ShaderInterpXSpans[0], + ShaderInterpXSpans[1], + ShaderBinCombined, + ShaderDepthBlend[0], + ShaderDepthBlend[1], + ShaderRasteriseNoTexture[0], + ShaderRasteriseNoTexture[1], + ShaderRasteriseNoTextureToon[0], + ShaderRasteriseNoTextureToon[1], + ShaderRasteriseNoTextureHighlight[0], + ShaderRasteriseNoTextureHighlight[1], + ShaderRasteriseUseTextureDecal[0], + ShaderRasteriseUseTextureDecal[1], + ShaderRasteriseUseTextureModulate[0], + ShaderRasteriseUseTextureModulate[1], + ShaderRasteriseUseTextureToon[0], + ShaderRasteriseUseTextureToon[1], + ShaderRasteriseUseTextureHighlight[0], + ShaderRasteriseUseTextureHighlight[1], + ShaderRasteriseShadowMask[0], + ShaderRasteriseShadowMask[1], + ShaderClearCoarseBinMask, + ShaderClearIndirectWorkCount, + ShaderCalculateWorkListOffset, + ShaderSortWork, + ShaderFinalPass[0], + ShaderFinalPass[1], + ShaderFinalPass[2], + ShaderFinalPass[3], + ShaderFinalPass[4], + ShaderFinalPass[5], + ShaderFinalPass[6], + ShaderFinalPass[7], + }; + for (GLuint program : allPrograms) + glDeleteProgram(program); +} + +void ComputeRenderer::Reset(GPU& gpu) +{ + Texcache.Reset(); +} + +void ComputeRenderer::SetRenderSettings(int scale, bool highResolutionCoordinates) +{ + CurGLCompositor.SetScaleFactor(scale); + + if (ScaleFactor != -1) + { + DeleteShaders(); + } + + ShaderStepIdx = 0; + + ScaleFactor = scale; + ScreenWidth = 256 * ScaleFactor; + ScreenHeight = 192 * ScaleFactor; + + TilesPerLine = ScreenWidth/TileSize; + TileLines = ScreenHeight/TileSize; + + HiresCoordinates = highResolutionCoordinates; + + MaxWorkTiles = TilesPerLine*TileLines*16; + + for (int i = 0; i < tilememoryLayer_Num; i++) + { + glBindBuffer(GL_SHADER_STORAGE_BUFFER, TileMemory[i]); + glBufferData(GL_SHADER_STORAGE_BUFFER, 4*TileSize*TileSize*MaxWorkTiles, nullptr, GL_DYNAMIC_DRAW); + } + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, FinalTileMemory); + glBufferData(GL_SHADER_STORAGE_BUFFER, 4*3*2*ScreenWidth*ScreenHeight, nullptr, GL_DYNAMIC_DRAW); + + int binResultSize = sizeof(BinResultHeader) + + TilesPerLine*TileLines*CoarseBinStride*4 // BinnedMaskCoarse + + TilesPerLine*TileLines*BinStride*4 // BinnedMask + + TilesPerLine*TileLines*BinStride*4; // WorkOffsets + glBindBuffer(GL_SHADER_STORAGE_BUFFER, BinResultMemory); + glBufferData(GL_SHADER_STORAGE_BUFFER, binResultSize, nullptr, GL_DYNAMIC_DRAW); + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, WorkDescMemory); + glBufferData(GL_SHADER_STORAGE_BUFFER, MaxWorkTiles*2*4*2, nullptr, GL_DYNAMIC_DRAW); + + if (Framebuffer != 0) + glDeleteTextures(1, &Framebuffer); + glGenTextures(1, &Framebuffer); + glBindTexture(GL_TEXTURE_2D, Framebuffer); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, ScreenWidth, ScreenHeight); + + // eh those are pretty bad guesses + // though real hw shouldn't be eable to render all 2048 polygons on every line either + int maxYSpanIndices = 64*2048 * ScaleFactor; + YSpanIndices.resize(maxYSpanIndices); + + glBindBuffer(GL_TEXTURE_BUFFER, YSpanIndicesTextureMemory); + glBufferData(GL_TEXTURE_BUFFER, maxYSpanIndices*2*4, nullptr, GL_DYNAMIC_DRAW); + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, XSpanSetupMemory); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(SpanSetupX)*maxYSpanIndices, nullptr, GL_DYNAMIC_DRAW); + + glBindTexture(GL_TEXTURE_BUFFER, YSpanIndicesTexture); + glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA16UI, YSpanIndicesTextureMemory); +} + +void ComputeRenderer::VCount144(GPU& gpu) +{ + +} + +void ComputeRenderer::SetupAttrs(SpanSetupY* span, Polygon* poly, int from, int to) +{ + span->Z0 = poly->FinalZ[from]; + span->W0 = poly->FinalW[from]; + span->Z1 = poly->FinalZ[to]; + span->W1 = poly->FinalW[to]; + span->ColorR0 = poly->Vertices[from]->FinalColor[0]; + span->ColorG0 = poly->Vertices[from]->FinalColor[1]; + span->ColorB0 = poly->Vertices[from]->FinalColor[2]; + span->ColorR1 = poly->Vertices[to]->FinalColor[0]; + span->ColorG1 = poly->Vertices[to]->FinalColor[1]; + span->ColorB1 = poly->Vertices[to]->FinalColor[2]; + span->TexcoordU0 = poly->Vertices[from]->TexCoords[0]; + span->TexcoordV0 = poly->Vertices[from]->TexCoords[1]; + span->TexcoordU1 = poly->Vertices[to]->TexCoords[0]; + span->TexcoordV1 = poly->Vertices[to]->TexCoords[1]; +} + +void ComputeRenderer::SetupYSpanDummy(RenderPolygon* rp, SpanSetupY* span, Polygon* poly, int vertex, int side, s32 positions[10][2]) +{ + s32 x0 = positions[vertex][0]; + if (side) + { + span->DxInitial = -0x40000; + x0--; + } + else + { + span->DxInitial = 0; + } + + span->X0 = span->X1 = x0; + span->XMin = x0; + span->XMax = x0; + span->Y0 = span->Y1 = positions[vertex][1]; + + if (span->XMin < rp->XMin) + { + rp->XMin = span->XMin; + rp->XMinY = span->Y0; + } + if (span->XMax > rp->XMax) + { + rp->XMax = span->XMax; + rp->XMaxY = span->Y0; + } + + span->Increment = 0; + + span->I0 = span->I1 = span->IRecip = 0; + span->Linear = true; + + span->XCovIncr = 0; + + span->IsDummy = true; + + SetupAttrs(span, poly, vertex, vertex); +} + +void ComputeRenderer::SetupYSpan(RenderPolygon* rp, SpanSetupY* span, Polygon* poly, int from, int to, int side, s32 positions[10][2]) +{ + span->X0 = positions[from][0]; + span->X1 = positions[to][0]; + span->Y0 = positions[from][1]; + span->Y1 = positions[to][1]; + + SetupAttrs(span, poly, from, to); + + s32 minXY, maxXY; + bool negative = false; + if (span->X1 > span->X0) + { + span->XMin = span->X0; + span->XMax = span->X1-1; + + minXY = span->Y0; + maxXY = span->Y1; + } + else if (span->X1 < span->X0) + { + span->XMin = span->X1; + span->XMax = span->X0-1; + negative = true; + + minXY = span->Y1; + maxXY = span->Y0; + } + else + { + span->XMin = span->X0; + if (side) span->XMin--; + span->XMax = span->XMin; + + // doesn't matter for completely vertical slope + minXY = span->Y0; + maxXY = span->Y0; + } + + if (span->XMin < rp->XMin) + { + rp->XMin = span->XMin; + rp->XMinY = minXY; + } + if (span->XMax > rp->XMax) + { + rp->XMax = span->XMax; + rp->XMaxY = maxXY; + } + + span->IsDummy = false; + + s32 xlen = span->XMax+1 - span->XMin; + s32 ylen = span->Y1 - span->Y0; + + // slope increment has a 18-bit fractional part + // note: for some reason, x/y isn't calculated directly, + // instead, 1/y is calculated and then multiplied by x + // TODO: this is still not perfect (see for example x=169 y=33) + if (ylen == 0) + { + span->Increment = 0; + } + else if (ylen == xlen) + { + span->Increment = 0x40000; + } + else + { + s32 yrecip = (1<<18) / ylen; + span->Increment = (span->X1-span->X0) * yrecip; + if (span->Increment < 0) span->Increment = -span->Increment; + } + + bool xMajor = (span->Increment > 0x40000); + + if (side) + { + // right + + if (xMajor) + span->DxInitial = negative ? (0x20000 + 0x40000) : (span->Increment - 0x20000); + else if (span->Increment != 0) + span->DxInitial = negative ? 0x40000 : 0; + else + span->DxInitial = -0x40000; + } + else + { + // left + + if (xMajor) + span->DxInitial = negative ? ((span->Increment - 0x20000) + 0x40000) : 0x20000; + else if (span->Increment != 0) + span->DxInitial = negative ? 0x40000 : 0; + else + span->DxInitial = 0; + } + + if (xMajor) + { + if (side) + { + span->I0 = span->X0 - 1; + span->I1 = span->X1 - 1; + } + else + { + span->I0 = span->X0; + span->I1 = span->X1; + } + + // used for calculating AA coverage + span->XCovIncr = (ylen << 10) / xlen; + } + else + { + span->I0 = span->Y0; + span->I1 = span->Y1; + } + + if (span->I0 != span->I1) + span->IRecip = (1<<30) / (span->I1 - span->I0); + else + span->IRecip = 0; + + span->Linear = (span->W0 == span->W1) && !(span->W0 & 0x7E) && !(span->W1 & 0x7E); + + if ((span->W0 & 0x1) && !(span->W1 & 0x1)) + { + span->W0n = (span->W0 - 1) >> 1; + span->W0d = (span->W0 + 1) >> 1; + span->W1d = span->W1 >> 1; + } + else + { + span->W0n = span->W0 >> 1; + span->W0d = span->W0 >> 1; + span->W1d = span->W1 >> 1; + } +} + +struct Variant +{ + GLuint Texture, Sampler; + u16 Width, Height; + u8 BlendMode; + + bool operator==(const Variant& other) + { + return Texture == other.Texture && Sampler == other.Sampler && BlendMode == other.BlendMode; + } +}; + +/* + Antialiasing + W-Buffer + With Texture + 0 + 1, 3 + 2 + without Texture + 2 + 0, 1, 3 + + => 20 Shader + 1x Shadow Mask +*/ + +void ComputeRenderer::RenderFrame(GPU& gpu) +{ + assert(!NeedsShaderCompile()); + if (!Texcache.Update(gpu) && gpu.GPU3D.RenderFrameIdentical) + { + return; + } + + int numYSpans = 0; + int numSetupIndices = 0; + + /* + Some games really like to spam small textures, often + to store the data like PPU tiles. E.g. Shantae + or some Mega Man game. Fortunately they are usually kind + enough to not vary the texture size all too often (usually + they just use 8x8 or 16x for everything). + + This is the reason we have this whole mess where textures of + the same size are put into array textures. This allows + to increase the batch size. + Less variance between each Variant hah! + */ + u32 numVariants = 0, prevVariant, prevTexLayer; + Variant variants[MaxVariants]; + + bool enableTextureMaps = gpu.GPU3D.RenderDispCnt & (1<<0); + + for (int i = 0; i < gpu.GPU3D.RenderNumPolygons; i++) + { + Polygon* polygon = gpu.GPU3D.RenderPolygonRAM[i]; + + u32 nverts = polygon->NumVertices; + u32 vtop = polygon->VTop, vbot = polygon->VBottom; + + u32 curVL = vtop, curVR = vtop; + u32 nextVL, nextVR; + + RenderPolygons[i].FirstXSpan = numSetupIndices; + RenderPolygons[i].Attr = polygon->Attr; + + bool foundVariant = false; + if (i > 0) + { + // if the whole texture attribute matches + // the texture layer will also match + Polygon* prevPolygon = gpu.GPU3D.RenderPolygonRAM[i - 1]; + foundVariant = prevPolygon->TexParam == polygon->TexParam + && prevPolygon->TexPalette == polygon->TexPalette + && (prevPolygon->Attr & 0x30) == (polygon->Attr & 0x30) + && prevPolygon->IsShadowMask == polygon->IsShadowMask; + } + + if (!foundVariant) + { + Variant variant; + variant.BlendMode = polygon->IsShadowMask ? 4 : ((polygon->Attr >> 4) & 0x3); + variant.Texture = 0; + variant.Sampler = 0; + u32* textureLastVariant = nullptr; + // we always need to look up the texture to get the layer of the array texture + if (enableTextureMaps && (polygon->TexParam >> 26) & 0x7) + { + Texcache.GetTexture(gpu, polygon->TexParam, polygon->TexPalette, variant.Texture, prevTexLayer, textureLastVariant); + bool wrapS = (polygon->TexParam >> 16) & 1; + bool wrapT = (polygon->TexParam >> 17) & 1; + bool mirrorS = (polygon->TexParam >> 18) & 1; + bool mirrorT = (polygon->TexParam >> 19) & 1; + variant.Sampler = Samplers[(wrapS ? (mirrorS ? 2 : 1) : 0) + (wrapT ? (mirrorT ? 2 : 1) : 0) * 3]; + + if (*textureLastVariant < numVariants && variants[*textureLastVariant] == variant) + { + foundVariant = true; + prevVariant = *textureLastVariant; + } + } + + if (!foundVariant) + { + for (int j = numVariants - 1; j >= 0; j--) + { + if (variants[j] == variant) + { + foundVariant = true; + prevVariant = j; + goto foundVariant; + } + } + + prevVariant = numVariants; + variants[numVariants] = variant; + variants[numVariants].Width = TextureWidth(polygon->TexParam); + variants[numVariants].Height = TextureHeight(polygon->TexParam); + numVariants++; + assert(numVariants <= MaxVariants); + foundVariant:; + + if (textureLastVariant) + *textureLastVariant = prevVariant; + } + } + RenderPolygons[i].Variant = prevVariant; + RenderPolygons[i].TextureLayer = (float)prevTexLayer; + + if (polygon->FacingView) + { + nextVL = curVL + 1; + if (nextVL >= nverts) nextVL = 0; + nextVR = curVR - 1; + if ((s32)nextVR < 0) nextVR = nverts - 1; + } + else + { + nextVL = curVL - 1; + if ((s32)nextVL < 0) nextVL = nverts - 1; + nextVR = curVR + 1; + if (nextVR >= nverts) nextVR = 0; + } + + s32 scaledPositions[10][2]; + s32 ytop = ScreenHeight, ybot = 0; + for (int i = 0; i < polygon->NumVertices; i++) + { + if (HiresCoordinates) + { + scaledPositions[i][0] = (polygon->Vertices[i]->HiresPosition[0] * ScaleFactor) >> 4; + scaledPositions[i][1] = (polygon->Vertices[i]->HiresPosition[1] * ScaleFactor) >> 4; + } + else + { + scaledPositions[i][0] = polygon->Vertices[i]->FinalPosition[0] * ScaleFactor; + scaledPositions[i][1] = polygon->Vertices[i]->FinalPosition[1] * ScaleFactor; + } + ytop = std::min(scaledPositions[i][1], ytop); + ybot = std::max(scaledPositions[i][1], ybot); + } + RenderPolygons[i].YTop = ytop; + RenderPolygons[i].YBot = ybot; + RenderPolygons[i].XMin = ScreenWidth; + RenderPolygons[i].XMax = 0; + + if (ybot == ytop) + { + vtop = 0; vbot = 0; + + RenderPolygons[i].YBot++; + + int j = 1; + if (scaledPositions[j][0] < scaledPositions[vtop][0]) vtop = j; + if (scaledPositions[j][0] > scaledPositions[vbot][0]) vbot = j; + + j = nverts - 1; + if (scaledPositions[j][0] < scaledPositions[vtop][0]) vtop = j; + if (scaledPositions[j][0] > scaledPositions[vbot][0]) vbot = j; + + assert(numYSpans < MaxYSpanSetups); + u32 curSpanL = numYSpans; + SetupYSpanDummy(&RenderPolygons[i], &YSpanSetups[numYSpans++], polygon, vtop, 0, scaledPositions); + assert(numYSpans < MaxYSpanSetups); + u32 curSpanR = numYSpans; + SetupYSpanDummy(&RenderPolygons[i], &YSpanSetups[numYSpans++], polygon, vbot, 1, scaledPositions); + + YSpanIndices[numSetupIndices].PolyIdx = i; + YSpanIndices[numSetupIndices].SpanIdxL = curSpanL; + YSpanIndices[numSetupIndices].SpanIdxR = curSpanR; + YSpanIndices[numSetupIndices].Y = ytop; + numSetupIndices++; + } + else + { + u32 curSpanL = numYSpans; + assert(numYSpans < MaxYSpanSetups); + SetupYSpan(&RenderPolygons[i], &YSpanSetups[numYSpans++], polygon, curVL, nextVL, 0, scaledPositions); + u32 curSpanR = numYSpans; + assert(numYSpans < MaxYSpanSetups); + SetupYSpan(&RenderPolygons[i], &YSpanSetups[numYSpans++], polygon, curVR, nextVR, 1, scaledPositions); + + for (u32 y = ytop; y < ybot; y++) + { + if (y >= scaledPositions[nextVL][1] && curVL != polygon->VBottom) + { + while (y >= scaledPositions[nextVL][1] && curVL != polygon->VBottom) + { + curVL = nextVL; + if (polygon->FacingView) + { + nextVL = curVL + 1; + if (nextVL >= nverts) + nextVL = 0; + } + else + { + nextVL = curVL - 1; + if ((s32)nextVL < 0) + nextVL = nverts - 1; + } + } + + + assert(numYSpans < MaxYSpanSetups); + curSpanL = numYSpans; + SetupYSpan(&RenderPolygons[i], &YSpanSetups[numYSpans++], polygon, curVL, nextVL, 0, scaledPositions); + } + if (y >= scaledPositions[nextVR][1] && curVR != polygon->VBottom) + { + while (y >= scaledPositions[nextVR][1] && curVR != polygon->VBottom) + { + curVR = nextVR; + if (polygon->FacingView) + { + nextVR = curVR - 1; + if ((s32)nextVR < 0) + nextVR = nverts - 1; + } + else + { + nextVR = curVR + 1; + if (nextVR >= nverts) + nextVR = 0; + } + } + + assert(numYSpans < MaxYSpanSetups); + curSpanR = numYSpans; + SetupYSpan(&RenderPolygons[i] ,&YSpanSetups[numYSpans++], polygon, curVR, nextVR, 1, scaledPositions); + } + + YSpanIndices[numSetupIndices].PolyIdx = i; + YSpanIndices[numSetupIndices].SpanIdxL = curSpanL; + YSpanIndices[numSetupIndices].SpanIdxR = curSpanR; + YSpanIndices[numSetupIndices].Y = y; + numSetupIndices++; + } + } + + //printf("polygon min max %d %d | %d %d\n", RenderPolygons[i].XMin, RenderPolygons[i].XMinY, RenderPolygons[i].XMax, RenderPolygons[i].XMaxY); + } + + /*for (u32 i = 0; i < RenderNumPolygons; i++) + { + if (RenderPolygons[i].Variant >= numVariants) + { + printf("blarb2 %d %d %d\n", RenderPolygons[i].Variant, i, RenderNumPolygons); + } + //assert(RenderPolygons[i].Variant < numVariants); + }*/ + + if (numYSpans > 0) + { + glBindBuffer(GL_SHADER_STORAGE_BUFFER, YSpanSetupMemory); + glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(SpanSetupY)*numYSpans, YSpanSetups); + + glBindBuffer(GL_TEXTURE_BUFFER, YSpanIndicesTextureMemory); + glBufferSubData(GL_TEXTURE_BUFFER, 0, numSetupIndices*4*2, YSpanIndices.data()); + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, RenderPolygonMemory); + glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, gpu.GPU3D.RenderNumPolygons*sizeof(RenderPolygon), RenderPolygons); + // we haven't accessed image data yet, so we don't need to invalidate anything + } + + //printf("found via %d %d %d of %d\n", foundviatexcache, foundviaprev, numslow, RenderNumPolygons); + + // bind everything + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, RenderPolygonMemory); + + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, XSpanSetupMemory); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, YSpanSetupMemory); + + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, FinalTileMemory); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, BinResultMemory); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, WorkDescMemory); + + MetaUniform meta; + meta.DispCnt = gpu.GPU3D.RenderDispCnt; + meta.NumPolygons = gpu.GPU3D.RenderNumPolygons; + meta.NumVariants = numVariants; + meta.AlphaRef = gpu.GPU3D.RenderAlphaRef; + { + u32 r = (gpu.GPU3D.RenderClearAttr1 << 1) & 0x3E; if (r) r++; + u32 g = (gpu.GPU3D.RenderClearAttr1 >> 4) & 0x3E; if (g) g++; + u32 b = (gpu.GPU3D.RenderClearAttr1 >> 9) & 0x3E; if (b) b++; + u32 a = (gpu.GPU3D.RenderClearAttr1 >> 16) & 0x1F; + meta.ClearColor = r | (g << 8) | (b << 16) | (a << 24); + meta.ClearDepth = ((gpu.GPU3D.RenderClearAttr2 & 0x7FFF) * 0x200) + 0x1FF; + meta.ClearAttr = gpu.GPU3D.RenderClearAttr1 & 0x3F008000; + } + for (u32 i = 0; i < 32; i++) + { + u32 color = gpu.GPU3D.RenderToonTable[i]; + u32 r = (color << 1) & 0x3E; + u32 g = (color >> 4) & 0x3E; + u32 b = (color >> 9) & 0x3E; + if (r) r++; + if (g) g++; + if (b) b++; + + meta.ToonTable[i*4+0] = r | (g << 8) | (b << 16); + } + for (u32 i = 0; i < 34; i++) + { + meta.ToonTable[i*4+1] = gpu.GPU3D.RenderFogDensityTable[i]; + } + for (u32 i = 0; i < 8; i++) + { + u32 color = gpu.GPU3D.RenderEdgeTable[i]; + u32 r = (color << 1) & 0x3E; + u32 g = (color >> 4) & 0x3E; + u32 b = (color >> 9) & 0x3E; + if (r) r++; + if (g) g++; + if (b) b++; + + meta.ToonTable[i*4+2] = r | (g << 8) | (b << 16); + } + meta.FogOffset = gpu.GPU3D.RenderFogOffset; + meta.FogShift = gpu.GPU3D.RenderFogShift; + { + u32 fogR = (gpu.GPU3D.RenderFogColor << 1) & 0x3E; if (fogR) fogR++; + u32 fogG = (gpu.GPU3D.RenderFogColor >> 4) & 0x3E; if (fogG) fogG++; + u32 fogB = (gpu.GPU3D.RenderFogColor >> 9) & 0x3E; if (fogB) fogB++; + u32 fogA = (gpu.GPU3D.RenderFogColor >> 16) & 0x1F; + meta.FogColor = fogR | (fogG << 8) | (fogB << 16) | (fogA << 24); + } + + glBindBuffer(GL_UNIFORM_BUFFER, MetaUniformMemory); + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(MetaUniform), &meta); + glBindBufferBase(GL_UNIFORM_BUFFER, 0, MetaUniformMemory); + + glUseProgram(ShaderClearCoarseBinMask); + glDispatchCompute(TilesPerLine*TileLines/32, 1, 1); + + bool wbuffer = false; + if (numYSpans > 0) + { + wbuffer = gpu.GPU3D.RenderPolygonRAM[0]->WBuffer; + + glUseProgram(ShaderClearIndirectWorkCount); + glDispatchCompute((numVariants+31)/32, 1, 1); + + // calculate x-spans + glBindImageTexture(0, YSpanIndicesTexture, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA16UI); + glUseProgram(ShaderInterpXSpans[wbuffer]); + glDispatchCompute((numSetupIndices + 31) / 32, 1, 1); + glMemoryBarrier(GL_SHADER_STORAGE_BUFFER); + + // bin polygons + glUseProgram(ShaderBinCombined); + glDispatchCompute(((gpu.GPU3D.RenderNumPolygons + 31) / 32), ScreenWidth/CoarseTileW, ScreenHeight/CoarseTileH); + glMemoryBarrier(GL_SHADER_STORAGE_BUFFER); + + // calculate list offsets + glUseProgram(ShaderCalculateWorkListOffset); + glDispatchCompute((numVariants + 31) / 32, 1, 1); + glMemoryBarrier(GL_SHADER_STORAGE_BUFFER); + + // sort shader work + glUseProgram(ShaderSortWork); + glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, BinResultMemory); + glDispatchComputeIndirect(offsetof(BinResultHeader, SortWorkWorkCount)); + glMemoryBarrier(GL_SHADER_STORAGE_BUFFER); + + glActiveTexture(GL_TEXTURE0); + + for (int i = 0; i < tilememoryLayer_Num; i++) + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2+i, TileMemory[i]); + + // rasterise + { + bool highLightMode = gpu.GPU3D.RenderDispCnt & (1<<1); + + GLuint shadersNoTexture[] = + { + ShaderRasteriseNoTexture[wbuffer], + ShaderRasteriseNoTexture[wbuffer], + highLightMode + ? ShaderRasteriseNoTextureHighlight[wbuffer] + : ShaderRasteriseNoTextureToon[wbuffer], + ShaderRasteriseNoTexture[wbuffer], + ShaderRasteriseShadowMask[wbuffer] + }; + GLuint shadersUseTexture[] = + { + ShaderRasteriseUseTextureModulate[wbuffer], + ShaderRasteriseUseTextureDecal[wbuffer], + highLightMode + ? ShaderRasteriseUseTextureHighlight[wbuffer] + : ShaderRasteriseUseTextureToon[wbuffer], + ShaderRasteriseUseTextureDecal[wbuffer], + ShaderRasteriseShadowMask[wbuffer] + }; + + GLuint prevShader = 0; + s32 prevTexture = 0, prevSampler = 0; + for (int i = 0; i < numVariants; i++) + { + GLuint shader = 0; + if (variants[i].Texture == 0) + { + shader = shadersNoTexture[variants[i].BlendMode]; + } + else + { + shader = shadersUseTexture[variants[i].BlendMode]; + if (variants[i].Texture != prevTexture) + { + glBindTexture(GL_TEXTURE_2D_ARRAY, variants[i].Texture); + prevTexture = variants[i].Texture; + } + if (variants[i].Sampler != prevSampler) + { + glBindSampler(0, variants[i].Sampler); + prevSampler = variants[i].Sampler; + } + } + assert(shader != 0); + if (shader != prevShader) + { + glUseProgram(shader); + prevShader = shader; + } + + glUniform1ui(UniformIdxCurVariant, i); + glUniform2f(UniformIdxTextureSize, 1.f / variants[i].Width, 1.f / variants[i].Height); + glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, BinResultMemory); + glDispatchComputeIndirect(offsetof(BinResultHeader, VariantWorkCount) + i*4*4); + } + } + } + glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); + + // compose final image + glUseProgram(ShaderDepthBlend[wbuffer]); + glDispatchCompute(ScreenWidth/TileSize, ScreenHeight/TileSize, 1); + glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); + + glBindImageTexture(0, Framebuffer, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); + glBindImageTexture(1, LowResFramebuffer, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8UI); + u32 finalPassShader = 0; + if (gpu.GPU3D.RenderDispCnt & (1<<4)) + finalPassShader |= 0x4; + if (gpu.GPU3D.RenderDispCnt & (1<<7)) + finalPassShader |= 0x2; + if (gpu.GPU3D.RenderDispCnt & (1<<5)) + finalPassShader |= 0x1; + + glUseProgram(ShaderFinalPass[finalPassShader]); + glDispatchCompute(ScreenWidth/32, ScreenHeight, 1); + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); + + glBindSampler(0, 0); + + /*u64 starttime = armGetSystemTick(); + EmuQueue.waitIdle(); + printf("total time %f\n", armTicksToNs(armGetSystemTick()-starttime)*0.000001f);*/ + + /*for (u32 i = 0; i < RenderNumPolygons; i++) + { + if (RenderPolygons[i].Variant >= numVariants) + { + printf("blarb %d %d %d\n", RenderPolygons[i].Variant, i, RenderNumPolygons); + } + //assert(RenderPolygons[i].Variant < numVariants); + }*/ + + /*for (int i = 0; i < binresult->SortWorkWorkCount[0]*32; i++) + { + printf("sorted %x %x\n", binresult->SortedWork[i*2+0], binresult->SortedWork[i*2+1]); + }*/ +/* if (polygonvisible != -1) + { + SpanSetupX* xspans = Gfx::DataHeap->CpuAddr(XSpanSetupMemory); + printf("span result\n"); + Polygon* poly = RenderPolygonRAM[polygonvisible]; + u32 xspanoffset = RenderPolygons[polygonvisible].FirstXSpan; + for (u32 i = 0; i < (poly->YBottom - poly->YTop); i++) + { + printf("%d: %d - %d | %d %d | %d %d\n", i + poly->YTop, xspans[xspanoffset + i].X0, xspans[xspanoffset + i].X1, xspans[xspanoffset + i].__pad0, xspans[xspanoffset + i].__pad1, RenderPolygons[polygonvisible].YTop, RenderPolygons[polygonvisible].YBot); + } + }*/ +/* + printf("xspans: %d\n", numSetupIndices); + SpanSetupX* xspans = Gfx::DataHeap->CpuAddr(XSpanSetupMemory[curSlice]); + for (int i = 0; i < numSetupIndices; i++) + { + printf("poly %d %d %d | line %d | %d to %d\n", YSpanIndices[i].PolyIdx, YSpanIndices[i].SpanIdxL, YSpanIndices[i].SpanIdxR, YSpanIndices[i].Y, xspans[i].X0, xspans[i].X1); + } + printf("bin result\n"); + BinResult* binresult = Gfx::DataHeap->CpuAddr(BinResultMemory); + for (u32 y = 0; y < 192/8; y++) + { + for (u32 x = 0; x < 256/8; x++) + { + printf("%08x ", binresult->BinnedMaskCoarse[(x + y * (256/8)) * 2]); + } + printf("\n"); + }*/ +} + +void ComputeRenderer::RestartFrame(GPU& gpu) +{ + +} + +u32* ComputeRenderer::GetLine(int line) +{ + int stride = 256; + + if (line == 0) + { + glBindBuffer(GL_PIXEL_PACK_BUFFER, PixelBuffer); + u8* data = (u8*)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); + if (data) memcpy(&FramebufferCPU[0], data, 4*stride*192); + glUnmapBuffer(GL_PIXEL_PACK_BUFFER); + } + + return &FramebufferCPU[stride * line]; +} + +void ComputeRenderer::SetupAccelFrame() +{ + glBindTexture(GL_TEXTURE_2D, Framebuffer); +} + +void ComputeRenderer::PrepareCaptureFrame() +{ + glBindBuffer(GL_PIXEL_PACK_BUFFER, PixelBuffer); + glBindTexture(GL_TEXTURE_2D, LowResFramebuffer); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, nullptr); +} + +void ComputeRenderer::BindOutputTexture(int buffer) +{ + CurGLCompositor.BindOutputTexture(buffer); +} + +void ComputeRenderer::Blit(const GPU &gpu) +{ + CurGLCompositor.RenderFrame(gpu, *this); +} + +void ComputeRenderer::Stop(const GPU &gpu) +{ + CurGLCompositor.Stop(gpu); +} + +} \ No newline at end of file diff --git a/src/GPU3D_Compute.h b/src/GPU3D_Compute.h new file mode 100644 index 00000000..751737b7 --- /dev/null +++ b/src/GPU3D_Compute.h @@ -0,0 +1,242 @@ +/* + Copyright 2016-2024 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 GPU3D_COMPUTE +#define GPU3D_COMPUTE + +#include + +#include "types.h" + +#include "GPU3D.h" + +#include "OpenGLSupport.h" +#include "GPU_OpenGL.h" + +#include "GPU3D_TexcacheOpenGL.h" + +#include "NonStupidBitfield.h" + +namespace melonDS +{ + +class ComputeRenderer : public Renderer3D +{ +public: + static std::unique_ptr New(); + ~ComputeRenderer() override; + + void Reset(GPU& gpu) override; + + void SetRenderSettings(int scale, bool highResolutionCoordinates); + + void VCount144(GPU& gpu) override; + + void RenderFrame(GPU& gpu) override; + void RestartFrame(GPU& gpu) override; + u32* GetLine(int line) override; + + void SetupAccelFrame() override; + void PrepareCaptureFrame() override; + + void BindOutputTexture(int buffer) override; + + void Blit(const GPU& gpu) override; + void Stop(const GPU& gpu) override; + + bool NeedsShaderCompile() override { return ShaderStepIdx != 33; } + void ShaderCompileStep(int& current, int& count) override; +private: + ComputeRenderer(GLCompositor&& compositor); + + GLuint ShaderInterpXSpans[2]; + GLuint ShaderBinCombined; + GLuint ShaderDepthBlend[2]; + GLuint ShaderRasteriseNoTexture[2]; + GLuint ShaderRasteriseNoTextureToon[2]; + GLuint ShaderRasteriseNoTextureHighlight[2]; + GLuint ShaderRasteriseUseTextureDecal[2]; + GLuint ShaderRasteriseUseTextureModulate[2]; + GLuint ShaderRasteriseUseTextureToon[2]; + GLuint ShaderRasteriseUseTextureHighlight[2]; + GLuint ShaderRasteriseShadowMask[2]; + GLuint ShaderClearCoarseBinMask; + GLuint ShaderClearIndirectWorkCount; + GLuint ShaderCalculateWorkListOffset; + GLuint ShaderSortWork; + GLuint ShaderFinalPass[8]; + + GLuint YSpanIndicesTextureMemory; + GLuint YSpanIndicesTexture; + GLuint YSpanSetupMemory; + GLuint XSpanSetupMemory; + GLuint BinResultMemory; + GLuint RenderPolygonMemory; + GLuint WorkDescMemory; + + enum + { + tilememoryLayer_Color, + tilememoryLayer_Depth, + tilememoryLayer_Attr, + tilememoryLayer_Num, + }; + + GLuint TileMemory[tilememoryLayer_Num]; + GLuint FinalTileMemory; + + u32 DummyLine[256] = {}; + + struct SpanSetupY + { + // Attributes + s32 Z0, Z1, W0, W1; + s32 ColorR0, ColorG0, ColorB0; + s32 ColorR1, ColorG1, ColorB1; + s32 TexcoordU0, TexcoordV0; + s32 TexcoordU1, TexcoordV1; + + // Interpolator + s32 I0, I1; + s32 Linear; + s32 IRecip; + s32 W0n, W0d, W1d; + + // Slope + s32 Increment; + + s32 X0, X1, Y0, Y1; + s32 XMin, XMax; + s32 DxInitial; + + s32 XCovIncr; + u32 IsDummy; + }; + struct SpanSetupX + { + s32 X0, X1; + + s32 EdgeLenL, EdgeLenR, EdgeCovL, EdgeCovR; + + s32 XRecip; + + u32 Flags; + + s32 Z0, Z1, W0, W1; + s32 ColorR0, ColorG0, ColorB0; + s32 ColorR1, ColorG1, ColorB1; + s32 TexcoordU0, TexcoordV0; + s32 TexcoordU1, TexcoordV1; + + s32 CovLInitial, CovRInitial; + }; + struct SetupIndices + { + u16 PolyIdx, SpanIdxL, SpanIdxR, Y; + }; + struct RenderPolygon + { + u32 FirstXSpan; + s32 YTop, YBot; + + s32 XMin, XMax; + s32 XMinY, XMaxY; + + u32 Variant; + u32 Attr; + + float TextureLayer; + }; + + static constexpr int TileSize = 8; + static constexpr int CoarseTileCountX = 8; + static constexpr int CoarseTileCountY = 4; + static constexpr int CoarseTileW = CoarseTileCountX * TileSize; + static constexpr int CoarseTileH = CoarseTileCountY * TileSize; + + static constexpr int BinStride = 2048/32; + static constexpr int CoarseBinStride = BinStride/32; + + static constexpr int MaxVariants = 256; + + static constexpr int UniformIdxCurVariant = 0; + static constexpr int UniformIdxTextureSize = 1; + + static constexpr int MaxFullscreenLayers = 16; + + struct BinResultHeader + { + u32 VariantWorkCount[MaxVariants*4]; + u32 SortedWorkOffset[MaxVariants]; + + u32 SortWorkWorkCount[4]; + }; + + static const int MaxYSpanSetups = 6144*2; + std::vector YSpanIndices; + SpanSetupY YSpanSetups[MaxYSpanSetups]; + RenderPolygon RenderPolygons[2048]; + + TexcacheOpenGL Texcache; + + struct MetaUniform + { + u32 NumPolygons; + u32 NumVariants; + + u32 AlphaRef; + u32 DispCnt; + + u32 ToonTable[4*34]; + + u32 ClearColor, ClearDepth, ClearAttr; + + u32 FogOffset, FogShift, FogColor; + }; + GLuint MetaUniformMemory; + + GLuint Samplers[9]; + + GLuint Framebuffer = 0; + GLuint LowResFramebuffer; + GLuint PixelBuffer; + + u32 FramebufferCPU[256*192]; + + int ScreenWidth, ScreenHeight; + int TilesPerLine, TileLines; + int ScaleFactor = -1; + int MaxWorkTiles; + bool HiresCoordinates; + + GLCompositor CurGLCompositor; + + int ShaderStepIdx = 0; + + void DeleteShaders(); + + void SetupAttrs(SpanSetupY* span, Polygon* poly, int from, int to); + void SetupYSpan(RenderPolygon* rp, SpanSetupY* span, Polygon* poly, int from, int to, int side, s32 positions[10][2]); + void SetupYSpanDummy(RenderPolygon* rp, SpanSetupY* span, Polygon* poly, int vertex, int side, s32 positions[10][2]); + + bool CompileShader(GLuint& shader, const std::string& source, const std::initializer_list& defines); +}; + +} + +#endif \ No newline at end of file diff --git a/src/GPU3D_Compute_shaders.h b/src/GPU3D_Compute_shaders.h new file mode 100644 index 00000000..26fb7bde --- /dev/null +++ b/src/GPU3D_Compute_shaders.h @@ -0,0 +1,1665 @@ +/* + Copyright 2016-2024 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 GPU3D_COMPUTE_SHADERS +#define GPU3D_COMPUTE_SHADERS + +#include + +namespace melonDS +{ + +namespace ComputeRendererShaders +{ + +// defines: +// InterpSpans +// BinCombined +// Rasterise +// DepthBlend +// ClearCoarseBinMask +// ClearIndirectWorkCount +// CalculateWorkOffsets +// SortWork +// FinalPass + +// AntiAliasing +// EdgeMarking +// Fog + +// ZBuffer +// WBuffer + +// for Rasterise +// NoTexture +// UseTexture +// Decal +// Modulate +// Toon +// Highlight +// ShadowMask + + +/* + Some notes on signed division: + + we want to avoid it, so we can avoid higher precision numbers + in a few places. + + Fortunately all divisions *should* assuming I'm not mistaken + have the same sign on the divisor and the dividend. + + Thus we apply: + + assuming n < 0 <=> d < 0 + n/d = abs(n)/abs(d) + +*/ + +const std::string XSpanSetupBuffer{R"( + +const uint XSpanSetup_Linear = 1U << 0; +const uint XSpanSetup_FillInside = 1U << 1; +const uint XSpanSetup_FillLeft = 1U << 2; +const uint XSpanSetup_FillRight = 1U << 3; + +struct XSpanSetup +{ + int X0, X1; + + int InsideStart, InsideEnd, EdgeCovL, EdgeCovR; + + int XRecip; + + uint Flags; + + int Z0, Z1, W0, W1; + int ColorR0, ColorG0, ColorB0; + int ColorR1, ColorG1, ColorB1; + int TexcoordU0, TexcoordV0; + int TexcoordU1, TexcoordV1; + + int CovLInitial, CovRInitial; +}; + +#if defined(Rasterise) +int CalcYFactorX(XSpanSetup span, int x) +{ + x -= span.X0; + + if (span.X0 != span.X1) + { + uint numLo = uint(x) * uint(span.W0); + uint numHi = 0U; + numHi |= numLo >> (32U-YFactorShift); + numLo <<= YFactorShift; + + uint den = uint(x) * uint(span.W0) + uint(span.X1 - span.X0 - x) * uint(span.W1); + + if (den == 0) + return 0; + else + return int(Div64_32_32(numHi, numLo, den)); + } + else + { + return 0; + } +} +#endif + +layout (std430, binding = 1) buffer XSpanSetupsBuffer +{ + XSpanSetup XSpanSetups[]; +}; + +)"}; + +const std::string YSpanSetupBuffer{R"( + +struct YSpanSetup +{ + // Attributes + int Z0, Z1, W0, W1; + int ColorR0, ColorG0, ColorB0; + int ColorR1, ColorG1, ColorB1; + int TexcoordU0, TexcoordV0; + int TexcoordU1, TexcoordV1; + + // Interpolator + int I0, I1; + bool Linear; + int IRecip; + int W0n, W0d, W1d; + + // Slope + int Increment; + + int X0, X1, Y0, Y1; + int XMin, XMax; + int DxInitial; + + int XCovIncr; + + bool IsDummy; +}; + +#if defined(InterpSpans) +int CalcYFactorY(YSpanSetup span, int i) +{ + /* + maybe it would be better to do use a 32x32=64 multiplication? + */ + uint numLo = uint(abs(i)) * uint(span.W0n); + uint numHi = 0U; + numHi |= numLo >> (32U-YFactorShift); + numLo <<= YFactorShift; + + uint den = uint(abs(i)) * uint(span.W0d) + uint(abs(span.I1 - span.I0 - i)) * span.W1d; + + if (den == 0) + { + return 0; + } + else + { + return int(Div64_32_32(numHi, numLo, den)); + } +} + +int CalculateDx(int y, YSpanSetup span) +{ + return span.DxInitial + (y - span.Y0) * span.Increment; +} + +int CalculateX(int dx, YSpanSetup span) +{ + int x = span.X0; + if (span.X1 < span.X0) + x -= dx >> 18; + else + x += dx >> 18; + return clamp(x, span.XMin, span.XMax); +} + +void EdgeParams_XMajor(bool side, int dx, YSpanSetup span, out int edgelen, out int edgecov) +{ + bool negative = span.X1 < span.X0; + int len; + if (side != negative) + len = (dx >> 18) - ((dx-span.Increment) >> 18); + else + len = ((dx+span.Increment) >> 18) - (dx >> 18); + edgelen = len; + + int xlen = span.XMax + 1 - span.XMin; + int startx = dx >> 18; + if (negative) startx = xlen - startx; + if (side) startx = startx - len + 1; + + uint r; + int startcov = int(Div(uint(((startx << 10) + 0x1FF) * (span.Y1 - span.Y0)), uint(xlen), r)); + edgecov = (1<<31) | ((startcov & 0x3FF) << 12) | (span.XCovIncr & 0x3FF); +} + +void EdgeParams_YMajor(bool side, int dx, YSpanSetup span, out int edgelen, out int edgecov) +{ + bool negative = span.X1 < span.X0; + edgelen = 1; + + if (span.Increment == 0) + { + edgecov = 31; + } + else + { + int cov = ((dx >> 9) + (span.Increment >> 10)) >> 4; + if ((cov >> 5) != (dx >> 18)) cov = 31; + cov &= 0x1F; + if (side == negative) cov = 0x1F - cov; + + edgecov = cov; + } +} +#endif + +layout (std430, binding = 2) buffer YSpanSetupsBuffer +{ + YSpanSetup YSpanSetups[]; +}; + +)"}; + +const std::string PolygonBuffer{R"( +struct Polygon +{ + int FirstXSpan; + int YTop, YBot; + + int XMin, XMax; + int XMinY, XMaxY; + + int Variant; + + uint Attr; + + float TextureLayer; +}; + +layout (std430, binding = 0) readonly buffer PolygonBuffer +{ + Polygon Polygons[]; +}; +)"}; + +const std::string BinningBuffer{R"( + +layout (std430, binding = 6) buffer BinResultBuffer +{ + uvec4 VariantWorkCount[MaxVariants]; + uint SortedWorkOffset[MaxVariants]; + + uvec4 SortWorkWorkCount; + + uint BinningMaskAndOffset[]; + //uint BinnedMaskCoarse[TilesPerLine*TileLines*CoarseBinStride]; + //uint BinnedMask[TilesPerLine*TileLines*BinStride]; + //uint WorkOffsets[TilesPerLine*TileLines*BinStride]; +}; + +const int BinningCoarseMaskStart = 0; +const int BinningMaskStart = BinningCoarseMaskStart+TilesPerLine*TileLines*CoarseBinStride; +const int BinningWorkOffsetsStart = BinningMaskStart+TilesPerLine*TileLines*BinStride; + +)"}; + +/* + structure of each WorkDesc item: + x: + bits 0-10: polygon idx + bits 11-31: tile idx (before sorting within variant after sorting within all tiles) + y: + bits 0-15: X position on screen + bits 15-31: Y position on screen +*/ +const std::string WorkDescBuffer{R"( +layout (std430, binding = 7) buffer WorkDescBuffer +{ + //uvec2 UnsortedWorkDescs[MaxWorkTiles]; + //uvec2 SortedWorkDescs[MaxWorkTiles]; + uvec2 WorkDescs[]; +}; + +const uint WorkDescsUnsortedStart = 0; +const uint WorkDescsSortedStart = WorkDescsUnsortedStart+MaxWorkTiles; + +)"}; + +const std::string Tilebuffers{R"( +layout (std430, binding = 2) buffer ColorTileBuffer +{ + uint ColorTiles[]; +}; +layout (std430, binding = 3) buffer DepthTileBuffer +{ + uint DepthTiles[]; +}; +layout (std430, binding = 4) buffer AttrTileBuffer +{ + uint AttrTiles[]; +}; + +)"}; + +const std::string ResultBuffer{R"( +layout (std430, binding = 5) buffer ResultBuffer +{ + uint ResultValue[]; +}; + +const uint ResultColorStart = 0; +const uint ResultDepthStart = ResultColorStart+ScreenWidth*ScreenHeight*2; +const uint ResultAttrStart = ResultDepthStart+ScreenWidth*ScreenHeight*2; +)"}; + +const char* Common = R"( + +#define TileSize 8 +const int CoarseTileCountX = 8; +const int CoarseTileCountY = 4; +const int CoarseTileW = (CoarseTileCountX * TileSize); +const int CoarseTileH = (CoarseTileCountY * TileSize); + +const int FramebufferStride = ScreenWidth*ScreenHeight; +const int TilesPerLine = ScreenWidth/TileSize; +const int TileLines = ScreenHeight/TileSize; + +const int BinStride = 2048/32; +const int CoarseBinStride = BinStride/32; + +const int MaxVariants = 256; + +layout (std140, binding = 0) uniform MetaUniform +{ + uint NumPolygons; + uint NumVariants; + + int AlphaRef; + + uint DispCnt; + + // r = Toon + // g = Fog Density + // b = Edge Color + uvec4 ToonTable[34]; + + uint ClearColor, ClearDepth, ClearAttr; + + uint FogOffset, FogShift, FogColor; +}; + +#ifdef InterpSpans +const int YFactorShift = 9; +#else +const int YFactorShift = 8; +#endif + +#if defined(InterpSpans) || defined(Rasterise) +uint Umulh(uint a, uint b) +{ + uint lo, hi; + umulExtended(a, b, hi, lo); + return hi; +} + +const uint startTable[256] = uint[256]( + 254, 252, 250, 248, 246, 244, 242, 240, 238, 236, 234, 233, 231, 229, 227, 225, 224, 222, 220, 218, 217, 215, 213, 212, 210, 208, 207, 205, 203, 202, 200, 199, 197, 195, 194, 192, 191, 189, 188, 186, 185, 183, 182, 180, 179, 178, 176, 175, 173, 172, 170, 169, 168, 166, 165, 164, 162, 161, 160, 158, +157, 156, 154, 153, 152, 151, 149, 148, 147, 146, 144, 143, 142, 141, 139, 138, 137, 136, 135, 134, 132, 131, 130, 129, 128, 127, 126, 125, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 88, 87, 86, 85, 84, 83, 82, 81, 80, 80, 79, 78, 77, 76, 75, 74, 74, 73, 72, 71, 70, 70, 69, 68, 67, 66, 66, 65, 64, 63, 62, 62, 61, 60, 59, 59, 58, 57, 56, 56, 55, 54, 53, 53, 52, 51, 50, 50, 49, 48, 48, 47, 46, 46, 45, 44, 43, 43, 42, 41, 41, 40, 39, 39, 38, 37, 37, 36, 35, 35, 34, 33, 33, 32, 32, 31, 30, 30, 29, 28, 28, 27, 27, 26, 25, 25, 24, 24, 23, 22, 22, 21, 21, 20, 19, 19, 18, 18, 17, 17, 16, 15, 15, 14, 14, 13, 13, 12, 12, 11, 10, 10, 9, 9, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0 +); + +uint Div(uint x, uint y, out uint r) +{ + // https://www.microsoft.com/en-us/research/publication/software-integer-division/ + uint k = 31 - findMSB(y); + uint ty = (y << k) >> (32 - 9); + uint t = startTable[ty - 256] + 256; + uint z = (t << (32 - 9)) >> (32 - k - 1); + uint my = 0 - y; + + z += Umulh(z, my * z); + z += Umulh(z, my * z); + + uint q = Umulh(x, z); + r = x - y * q; + if(r >= y) + { + r = r - y; + q = q + 1; + if(r >= y) + { + r = r - y; + q = q + 1; + } + } + + return q; +} + +uint Div64_32_32(uint numHi, uint numLo, uint den) +{ + // based on https://github.com/ridiculousfish/libdivide/blob/3bd34388573681ce563348cdf04fe15d24770d04/libdivide.h#L469 + // modified to work with half the size 64/32=32 instead of 128/64=64 + // for further details see https://ridiculousfish.com/blog/posts/labor-of-division-episode-iv.html + + // We work in base 2**16. + // A uint32 holds a single digit (in the lower 16 bit). A uint32 holds two digits. + // Our numerator is conceptually [num3, num2, num1, num0]. + // Our denominator is [den1, den0]. + const uint b = (1U << 16); + + // Determine the normalization factor. We multiply den by this, so that its leading digit is at + // least half b. In binary this means just shifting left by the number of leading zeros, so that + // there's a 1 in the MSB. + // We also shift numer by the same amount. This cannot overflow because numHi < den. + // The expression (-shift & 63) is the same as (64 - shift), except it avoids the UB of shifting + // by 64. (it's also UB in GLSL!!!!) + uint shift = 31 - findMSB(den); + den <<= shift; + numHi <<= shift; + numHi |= (numLo >> (-shift & 31U)) & uint(-int(shift) >> 31); + numLo <<= shift; + + // Extract the low digits of the numerator and both digits of the denominator. + uint num1 = (numLo >> 16); + uint num0 = (numLo & 0xFFFFU); + uint den1 = (den >> 16); + uint den0 = (den & 0xFFFFU); + + // We wish to compute q1 = [n3 n2 n1] / [d1 d0]. + // Estimate q1 as [n3 n2] / [d1], and then correct it. + // Note while qhat may be 2 digits, q1 is always 1 digit. + + uint rhat; + uint qhat = Div(numHi, den1, rhat); + uint c1 = qhat * den0; + uint c2 = rhat * b + num1; + if (c1 > c2) qhat -= (c1 - c2 > den) ? 2 : 1; + uint q1 = qhat & 0xFFFFU; + + // Compute the true (partial) remainder. + uint rem = numHi * b + num1 - q1 * den; + + // We wish to compute q0 = [rem1 rem0 n0] / [d1 d0]. + // Estimate q0 as [rem1 rem0] / [d1] and correct it. + qhat = Div(rem, den1, rhat); + c1 = qhat * den0; + c2 = rhat * b + num0; + if (c1 > c2) qhat -= (c1 - c2 > den) ? 2 : 1; + + return bitfieldInsert(qhat, q1, 16, 16); +} + +int InterpolateAttrPersp(int y0, int y1, int ifactor) +{ + if (y0 == y1) + return y0; + + if (y0 < y1) + return y0 + (((y1-y0) * ifactor) >> YFactorShift); + else + return y1 + (((y0-y1) * ((1<> YFactorShift); +} + +int InterpolateAttrLinear(int y0, int y1, int i, int irecip, int idiff) +{ + if (y0 == y1) + return y0; + +#ifndef Rasterise + irecip = abs(irecip); +#endif + + uint mulLo, mulHi, carry; + if (y0 < y1) + { +#ifndef Rasterise + uint offset = uint(abs(i)); +#else + uint offset = uint(i); +#endif + umulExtended(uint(y1-y0)*offset, uint(irecip), mulHi, mulLo); + mulLo = uaddCarry(mulLo, 3U<<24, carry); + mulHi += carry; + return y0 + int((mulLo >> 30) | (mulHi << (32 - 30))); + //return y0 + int(((int64_t(y1-y0) * int64_t(offset) * int64_t(irecip)) + int64_t(3<<24)) >> 30); + } + else + { +#ifndef Rasterise + uint offset = uint(abs(idiff-i)); +#else + uint offset = uint(idiff-i); +#endif + umulExtended(uint(y0-y1)*offset, uint(irecip), mulHi, mulLo); + mulLo = uaddCarry(mulLo, 3<<24, carry); + mulHi += carry; + return y1 + int((mulLo >> 30) | (mulHi << (32 - 30))); + //return y1 + int(((int64_t(y0-y1) * int64_t(offset) * int64_t(irecip)) + int64_t(3<<24)) >> 30); + } +} + +uint InterpolateZZBuffer(int z0, int z1, int i, int irecip, int idiff) +{ + if (z0 == z1) + return z0; + + uint base, disp, factor; + if (z0 < z1) + { + base = uint(z0); + disp = uint(z1 - z0); + factor = uint(abs(i)); + } + else + { + base = uint(z1); + disp = uint(z0 - z1), + factor = uint(abs(idiff - i)); + } + +#ifdef InterpSpans + int shiftl = 0; + const int shiftr = 22; + if (disp > 0x3FF) + { + shiftl = findMSB(disp) - 9; + disp >>= shiftl; + } +#else + disp >>= 9; + const int shiftl = 0; + const int shiftr = 13; +#endif + uint mulLo, mulHi; + + umulExtended(disp * factor, abs(irecip) >> 8, mulHi, mulLo); + + return base + (((mulLo >> shiftr) | (mulHi << (32 - shiftr))) << shiftl); +/* + int base, disp, factor; + if (z0 < z1) + { + base = z0; + disp = z1 - z0; + factor = i; + } + else + { + base = z1; + disp = z0 - z1, + factor = idiff - i; + } + +#ifdef InterpSpans + { + int shift = 0; + while (disp > 0x3FF) + { + disp >>= 1; + shift++; + } + + return base + int(((int64_t(disp) * int64_t(factor) * (int64_t(irecip) >> 8)) >> 22) << shift); + } +#else + { + disp >>= 9; + return base + int((int64_t(disp) * int64_t(factor) * (int64_t(irecip) >> 8)) >> 13); + } +#endif*/ +} + +uint InterpolateZWBuffer(int z0, int z1, int ifactor) +{ + if (z0 == z1) + return z0; + +#ifdef Rasterise + // since the precision along x spans is only 8 bit the result will always fit in 32-bit + if (z0 < z1) + { + return uint(z0) + (((z1-z0) * ifactor) >> YFactorShift); + } + else + { + return uint(z1) + (((z0-z1) * ((1<> YFactorShift); + } +#else + uint mulLo, mulHi; + if (z0 < z1) + { + umulExtended(z1-z0, ifactor, mulHi, mulLo); + // 64-bit shift + return uint(z0) + ((mulLo >> YFactorShift) | (mulHi << (32-YFactorShift))); + } + else + { + umulExtended(z0-z1, (1<> YFactorShift) | (mulHi << (32-YFactorShift))); + } +#endif + /*if (z0 < z1) + { + return uint(z0) + uint((int64_t(z1-z0) * int64_t(ifactor)) >> YFactorShift); + } + else + { + return uint(z1) + uint((int64_t(z0-z1) * int64_t((1<> YFactorShift); + }*/ +} +#endif + +)"; + +const std::string InterpSpans = + PolygonBuffer + + XSpanSetupBuffer + + YSpanSetupBuffer + R"( +layout (local_size_x = 32) in; + +layout (binding = 0, rgba16ui) uniform readonly uimageBuffer SetupIndices; + +void main() +{ + uvec4 setup = imageLoad(SetupIndices, int(gl_GlobalInvocationID.x)); + + YSpanSetup spanL = YSpanSetups[setup.y]; + YSpanSetup spanR = YSpanSetups[setup.z]; + + XSpanSetup xspan; + xspan.Flags = 0U; + + int y = int(setup.w); + + int dxl = CalculateDx(y, spanL); + int dxr = CalculateDx(y, spanR); + + int xl = CalculateX(dxl, spanL); + int xr = CalculateX(dxr, spanR); + + Polygon polygon = Polygons[setup.x]; + + int edgeLenL, edgeLenR; + + if (xl > xr) + { + YSpanSetup tmpSpan = spanL; + spanL = spanR; + spanR = tmpSpan; + + int tmp = xl; + xl = xr; + xr = tmp; + + EdgeParams_YMajor(false, dxr, spanL, edgeLenL, xspan.EdgeCovL); + EdgeParams_YMajor(true, dxl, spanR, edgeLenR, xspan.EdgeCovR); + } + else + { + // edges are the right way + if (spanL.Increment > 0x40000) + EdgeParams_XMajor(false, dxl, spanL, edgeLenL, xspan.EdgeCovL); + else + EdgeParams_YMajor(false, dxl, spanL, edgeLenL, xspan.EdgeCovL); + if (spanR.Increment > 0x40000) + EdgeParams_XMajor(true, dxr, spanR, edgeLenR, xspan.EdgeCovR); + else + EdgeParams_YMajor(true, dxr, spanR, edgeLenR, xspan.EdgeCovR); + } + + xspan.CovLInitial = (xspan.EdgeCovL >> 12) & 0x3FF; + if (xspan.CovLInitial == 0x3FF) + xspan.CovLInitial = 0; + xspan.CovRInitial = (xspan.EdgeCovR >> 12) & 0x3FF; + if (xspan.CovRInitial == 0x3FF) + xspan.CovRInitial = 0; + + xspan.X0 = xl; + xspan.X1 = xr + 1; + + uint polyalpha = ((polygon.Attr >> 16) & 0x1FU); + bool isWireframe = polyalpha == 0U; + + if (!isWireframe || (y == polygon.YTop || y == polygon.YBot - 1)) + xspan.Flags |= XSpanSetup_FillInside; + + xspan.InsideStart = xspan.X0 + edgeLenL; + if (xspan.InsideStart > xspan.X1) + xspan.InsideStart = xspan.X1; + xspan.InsideEnd = xspan.X1 - edgeLenR; + if (xspan.InsideEnd > xspan.X1) + xspan.InsideEnd = xspan.X1; + + bool isShadowMask = ((polygon.Attr & 0x3F000030U) == 0x00000030U); + bool fillAllEdges = polyalpha < 31 || (DispCnt & (3U<<4)) != 0U; + + if (fillAllEdges || spanL.X1 < spanL.X0 || spanL.Increment <= 0x40000) + xspan.Flags |= XSpanSetup_FillLeft; + if (fillAllEdges || (spanR.X1 >= spanR.X0 && spanR.Increment > 0x40000) || spanR.Increment == 0) + xspan.Flags |= XSpanSetup_FillRight; + + if (spanL.I0 == spanL.I1) + { + xspan.TexcoordU0 = spanL.TexcoordU0; + xspan.TexcoordV0 = spanL.TexcoordV0; + xspan.ColorR0 = spanL.ColorR0; + xspan.ColorG0 = spanL.ColorG0; + xspan.ColorB0 = spanL.ColorB0; + xspan.Z0 = spanL.Z0; + xspan.W0 = spanL.W0; + } + else + { + int i = (spanL.Increment > 0x40000 ? xl : y) - spanL.I0; + int ifactor = CalcYFactorY(spanL, i); + int idiff = spanL.I1 - spanL.I0; + +#ifdef ZBuffer + xspan.Z0 = int(InterpolateZZBuffer(spanL.Z0, spanL.Z1, i, spanL.IRecip, idiff)); +#endif +#ifdef WBuffer + xspan.Z0 = int(InterpolateZWBuffer(spanL.Z0, spanL.Z1, ifactor)); +#endif + + if (!spanL.Linear) + { + xspan.TexcoordU0 = InterpolateAttrPersp(spanL.TexcoordU0, spanL.TexcoordU1, ifactor); + xspan.TexcoordV0 = InterpolateAttrPersp(spanL.TexcoordV0, spanL.TexcoordV1, ifactor); + + xspan.ColorR0 = InterpolateAttrPersp(spanL.ColorR0, spanL.ColorR1, ifactor); + xspan.ColorG0 = InterpolateAttrPersp(spanL.ColorG0, spanL.ColorG1, ifactor); + xspan.ColorB0 = InterpolateAttrPersp(spanL.ColorB0, spanL.ColorB1, ifactor); + + xspan.W0 = InterpolateAttrPersp(spanL.W0, spanL.W1, ifactor); + } + else + { + xspan.TexcoordU0 = InterpolateAttrLinear(spanL.TexcoordU0, spanL.TexcoordU1, i, spanL.IRecip, idiff); + xspan.TexcoordV0 = InterpolateAttrLinear(spanL.TexcoordV0, spanL.TexcoordV1, i, spanL.IRecip, idiff); + + xspan.ColorR0 = InterpolateAttrLinear(spanL.ColorR0, spanL.ColorR1, i, spanL.IRecip, idiff); + xspan.ColorG0 = InterpolateAttrLinear(spanL.ColorG0, spanL.ColorG1, i, spanL.IRecip, idiff); + xspan.ColorB0 = InterpolateAttrLinear(spanL.ColorB0, spanL.ColorB1, i, spanL.IRecip, idiff); + + xspan.W0 = spanL.W0; // linear mode is only taken if W0 == W1 + } + } + + if (spanR.I0 == spanR.I1) + { + xspan.TexcoordU1 = spanR.TexcoordU0; + xspan.TexcoordV1 = spanR.TexcoordV0; + xspan.ColorR1 = spanR.ColorR0; + xspan.ColorG1 = spanR.ColorG0; + xspan.ColorB1 = spanR.ColorB0; + xspan.Z1 = spanR.Z0; + xspan.W1 = spanR.W0; + } + else + { + int i = (spanR.Increment > 0x40000 ? xr : y) - spanR.I0; + int ifactor = CalcYFactorY(spanR, i); + int idiff = spanR.I1 - spanR.I0; + + #ifdef ZBuffer + xspan.Z1 = int(InterpolateZZBuffer(spanR.Z0, spanR.Z1, i, spanR.IRecip, idiff)); + #endif + #ifdef WBuffer + xspan.Z1 = int(InterpolateZWBuffer(spanR.Z0, spanR.Z1, ifactor)); + #endif + + if (!spanR.Linear) + { + xspan.TexcoordU1 = InterpolateAttrPersp(spanR.TexcoordU0, spanR.TexcoordU1, ifactor); + xspan.TexcoordV1 = InterpolateAttrPersp(spanR.TexcoordV0, spanR.TexcoordV1, ifactor); + + xspan.ColorR1 = InterpolateAttrPersp(spanR.ColorR0, spanR.ColorR1, ifactor); + xspan.ColorG1 = InterpolateAttrPersp(spanR.ColorG0, spanR.ColorG1, ifactor); + xspan.ColorB1 = InterpolateAttrPersp(spanR.ColorB0, spanR.ColorB1, ifactor); + + xspan.W1 = int(InterpolateAttrPersp(spanR.W0, spanR.W1, ifactor)); + } + else + { + xspan.TexcoordU1 = InterpolateAttrLinear(spanR.TexcoordU0, spanR.TexcoordU1, i, spanR.IRecip, idiff); + xspan.TexcoordV1 = InterpolateAttrLinear(spanR.TexcoordV0, spanR.TexcoordV1, i, spanR.IRecip, idiff); + + xspan.ColorR1 = InterpolateAttrLinear(spanR.ColorR0, spanR.ColorR1, i, spanR.IRecip, idiff); + xspan.ColorG1 = InterpolateAttrLinear(spanR.ColorG0, spanR.ColorG1, i, spanR.IRecip, idiff); + xspan.ColorB1 = InterpolateAttrLinear(spanR.ColorB0, spanR.ColorB1, i, spanR.IRecip, idiff); + + xspan.W1 = spanR.W0; + } + } + + if (xspan.W0 == xspan.W1 && ((xspan.W0 | xspan.W1) & 0x7F) == 0) + { + xspan.Flags |= XSpanSetup_Linear; +// a bit hacky, but when wbuffering we only need to calculate xrecip for linear spans +#ifdef ZBuffer + } + { +#endif + uint r; + xspan.XRecip = int(Div(1U<<30, uint(xspan.X1 - xspan.X0), r)); + } + + XSpanSetups[gl_GlobalInvocationID.x] = xspan; +} + +)"; + +const std::string ClearIndirectWorkCount = + BinningBuffer + R"( + +layout (local_size_x = 32) in; + +void main() +{ + VariantWorkCount[gl_GlobalInvocationID.x] = uvec4(1, 1, 0, 0); +} + +)"; + +const std::string ClearCoarseBinMask = + BinningBuffer + R"( +layout (local_size_x = 32) in; + +void main() +{ + BinningMaskAndOffset[BinningCoarseMaskStart + gl_GlobalInvocationID.x*CoarseBinStride+0] = 0; + BinningMaskAndOffset[BinningCoarseMaskStart + gl_GlobalInvocationID.x*CoarseBinStride+1] = 0; +} + +)"; + +const std::string BinCombined = + PolygonBuffer + + BinningBuffer + + XSpanSetupBuffer + + WorkDescBuffer + R"( + +layout (local_size_x = 32) in; + +bool BinPolygon(Polygon polygon, ivec2 topLeft, ivec2 botRight) +{ + if (polygon.YTop > botRight.y || polygon.YBot <= topLeft.y) + return false; + + int polygonHeight = polygon.YBot - polygon.YTop; + + /* + All (good) polygons are convex. So the following holds true: + + Starting from the top most point where both edges originate + the X coordinate of the left edge will stay the same or falls until + the minimum X-axis coordinate is reached. Then it stays the same or + rises until the point it meets with the right edge. + + The same applies to the right edge, except that it first may rise or stay equal and + after the maximum point may only fall or stay the same. + + This means that for every tile which doesn't contain the point where the direction changes + we can just get the maximum point by sampling the top most and bottom most coordinate + within the tile. + + For a tile which is that the height of the direction change + + As a sidenote another consequence of this design decision is + that malformed polygons aren't binned properly. + + As a note bottom Y is exclusive! + */ + int polyInnerTopY = clamp(topLeft.y - polygon.YTop, 0, max(polygonHeight-1, 0)); + int polyInnerBotY = clamp(botRight.y - polygon.YTop, 0, max(polygonHeight-1, 0)); + + XSpanSetup xspanTop = XSpanSetups[polygon.FirstXSpan + polyInnerTopY]; + XSpanSetup xspanBot = XSpanSetups[polygon.FirstXSpan + polyInnerBotY]; + + int minXL; + if (polygon.XMinY >= topLeft.y && polygon.XMinY <= botRight.y) + minXL = polygon.XMin; + else + minXL = min(xspanTop.X0, xspanBot.X0); + + if (minXL > botRight.x) + return false; + + int maxXR; + if (polygon.XMaxY >= topLeft.y && polygon.XMaxY <= botRight.y) + maxXR = polygon.XMax; + else + maxXR = max(xspanTop.X1, xspanBot.X1) - 1; + + if (maxXR < topLeft.x) + return false; + + return true; +} + +shared uint mergedMaskShared; + +void main() +{ + int groupIdx = int(gl_WorkGroupID.x); + ivec2 coarseTile = ivec2(gl_WorkGroupID.yz); + +#if 0 + int localIdx = int(gl_SubGroupInvocationARB); +#else + int localIdx = int(gl_LocalInvocationIndex); + + if (localIdx == 0) + mergedMaskShared = 0U; + barrier(); +#endif + + int polygonIdx = groupIdx * 32 + localIdx; + + ivec2 coarseTopLeft = coarseTile * ivec2(CoarseTileW, CoarseTileH); + ivec2 coarseBotRight = coarseTopLeft + ivec2(CoarseTileW-1, CoarseTileH-1); + + bool binned = false; + if (polygonIdx < NumPolygons) + { + binned = BinPolygon(Polygons[polygonIdx], coarseTopLeft, coarseBotRight); + } + +#if 0 + uint mergedMask = unpackUint2x32(ballotARB(binned)).x; +#else + if (binned) + atomicOr(mergedMaskShared, 1U << localIdx); + barrier(); + uint mergedMask = mergedMaskShared; +#endif + + ivec2 fineTile = ivec2(localIdx & 0x7, localIdx >> 3); + + ivec2 fineTileTopLeft = coarseTopLeft + fineTile * ivec2(TileSize, TileSize); + ivec2 fineTileBotRight = fineTileTopLeft + ivec2(TileSize-1, TileSize-1); + + uint binnedMask = 0U; + while (mergedMask != 0U) + { + int bit = findLSB(mergedMask); + mergedMask &= ~(1U << bit); + + int polygonIdx = groupIdx * 32 + bit; + + if (BinPolygon(Polygons[polygonIdx], fineTileTopLeft, fineTileBotRight)) + binnedMask |= 1U << bit; + } + + int linearTile = fineTile.x + fineTile.y * TilesPerLine + coarseTile.x * CoarseTileCountX + coarseTile.y * TilesPerLine * CoarseTileCountY; + + BinningMaskAndOffset[BinningMaskStart + linearTile * BinStride + groupIdx] = binnedMask; + int coarseMaskIdx = linearTile * CoarseBinStride + (groupIdx >> 5); + if (binnedMask != 0U) + atomicOr(BinningMaskAndOffset[BinningCoarseMaskStart + coarseMaskIdx], 1U << (groupIdx & 0x1F)); + + if (binnedMask != 0U) + { + uint workOffset = atomicAdd(VariantWorkCount[0].w, uint(bitCount(binnedMask))); + BinningMaskAndOffset[BinningWorkOffsetsStart + linearTile * BinStride + groupIdx] = workOffset; + + uint tilePositionCombined = bitfieldInsert(fineTileTopLeft.x, fineTileTopLeft.y, 16, 16); + + int idx = 0; + while (binnedMask != 0U) + { + int bit = findLSB(binnedMask); + binnedMask &= ~(1U << bit); + + int polygonIdx = groupIdx * 32 + bit; + int variantIdx = Polygons[polygonIdx].Variant; + + int inVariantOffset = int(atomicAdd(VariantWorkCount[variantIdx].z, 1)); + WorkDescs[WorkDescsUnsortedStart + workOffset + idx] = uvec2(tilePositionCombined, bitfieldInsert(polygonIdx, inVariantOffset, 11, 21)); + + idx++; + } + } +} + +)"; + +const std::string CalcOffsets = + BinningBuffer + R"( + +layout (local_size_x = 32) in; + +void main() +{ + if (gl_GlobalInvocationID.x < NumVariants) + { + if (gl_GlobalInvocationID.x == 0) + { + // a bit of a cheat putting this here, but this shader won't run that often + SortWorkWorkCount = uvec4((VariantWorkCount[0].w + 31) / 32, 1, 1, 0); + } + SortedWorkOffset[gl_GlobalInvocationID.x] = atomicAdd(VariantWorkCount[1].w, VariantWorkCount[gl_GlobalInvocationID.x].z); + } +} + + +)"; + +const std::string SortWork = + PolygonBuffer + + BinningBuffer + + WorkDescBuffer + R"( + +layout (local_size_x = 32) in; + +void main() +{ + if (gl_GlobalInvocationID.x < VariantWorkCount[0].w) + { + uvec2 workDesc = WorkDescs[WorkDescsUnsortedStart + gl_GlobalInvocationID.x]; + int inVariantOffset = int(bitfieldExtract(workDesc.y, 11, 21)); + int polygonIdx = int(bitfieldExtract(workDesc.y, 0, 11)); + int variantIdx = Polygons[polygonIdx].Variant; + + int sortedIndex = int(SortedWorkOffset[variantIdx]) + inVariantOffset; + WorkDescs[WorkDescsSortedStart + sortedIndex] = uvec2(workDesc.x, bitfieldInsert(workDesc.y, gl_GlobalInvocationID.x, 11, 21)); + } +} + +)"; + +const std::string Rasterise = + PolygonBuffer + + WorkDescBuffer + + XSpanSetupBuffer + + BinningBuffer + + Tilebuffers + R"( + +layout (local_size_x = TileSize, local_size_y = TileSize) in; + +layout (binding = 0) uniform usampler2DArray CurrentTexture; + +layout (location = 0) uniform uint CurVariant; +layout (location = 1) uniform vec2 InvTextureSize; + +void main() +{ + uvec2 workDesc = WorkDescs[WorkDescsSortedStart + SortedWorkOffset[CurVariant] + gl_WorkGroupID.z]; + Polygon polygon = Polygons[bitfieldExtract(workDesc.y, 0, 11)]; + ivec2 position = ivec2(bitfieldExtract(workDesc.x, 0, 16), bitfieldExtract(workDesc.x, 16, 16)) + ivec2(gl_LocalInvocationID.xy); + int tileOffset = int(bitfieldExtract(workDesc.y, 11, 21)) * TileSize * TileSize + TileSize * int(gl_LocalInvocationID.y) + int(gl_LocalInvocationID.x); + + uint color = 0U; + if (position.y >= polygon.YTop && position.y < polygon.YBot) + { + XSpanSetup xspan = XSpanSetups[polygon.FirstXSpan + (position.y - polygon.YTop)]; + + bool insideLeftEdge = position.x < xspan.InsideStart; + bool insideRightEdge = position.x >= xspan.InsideEnd; + bool insidePolygonInside = !insideLeftEdge && !insideRightEdge; + + if (position.x >= xspan.X0 && position.x < xspan.X1 + && ((insideLeftEdge && (xspan.Flags & XSpanSetup_FillLeft) != 0U) + || (insideRightEdge && (xspan.Flags & XSpanSetup_FillRight) != 0U) + || (insidePolygonInside && (xspan.Flags & XSpanSetup_FillInside) != 0U))) + { + uint attr = 0; + if (position.y == polygon.YTop) + attr |= 0x4U; + else if (position.y == polygon.YBot - 1) + attr |= 0x8U; + + if (insideLeftEdge) + { + attr |= 0x1U; + + int cov = xspan.EdgeCovL; + if (cov < 0) + { + int xcov = xspan.CovLInitial + (xspan.EdgeCovL & 0x3FF) * (position.x - xspan.X0); + cov = min(xcov >> 5, 31); + } + + attr |= uint(cov) << 8; + } + else if (insideRightEdge) + { + attr |= 0x2U; + + int cov = xspan.EdgeCovR; + if (cov < 0) + { + int xcov = xspan.CovRInitial + (xspan.EdgeCovR & 0x3FF) * (position.x - xspan.InsideEnd); + cov = max(0x1F - (xcov >> 5), 0); + } + + attr |= uint(cov) << 8; + } + + uint z; + int u, v, vr, vg, vb; + + if (xspan.X0 == xspan.X1) + { + z = xspan.Z0; + u = xspan.TexcoordU0; + v = xspan.TexcoordV0; + vr = xspan.ColorR0; + vg = xspan.ColorG0; + vb = xspan.ColorB0; + } + else + { + int ifactor = CalcYFactorX(xspan, position.x); + int idiff = xspan.X1 - xspan.X0; + int i = position.x - xspan.X0; + +#ifdef ZBuffer + z = InterpolateZZBuffer(xspan.Z0, xspan.Z1, i, xspan.XRecip, idiff); +#endif +#ifdef WBuffer + z = InterpolateZWBuffer(xspan.Z0, xspan.Z1, ifactor); +#endif + if ((xspan.Flags & XSpanSetup_Linear) == 0U) + { + u = InterpolateAttrPersp(xspan.TexcoordU0, xspan.TexcoordU1, ifactor); + v = InterpolateAttrPersp(xspan.TexcoordV0, xspan.TexcoordV1, ifactor); + + vr = InterpolateAttrPersp(xspan.ColorR0, xspan.ColorR1, ifactor); + vg = InterpolateAttrPersp(xspan.ColorG0, xspan.ColorG1, ifactor); + vb = InterpolateAttrPersp(xspan.ColorB0, xspan.ColorB1, ifactor); + } + else + { + u = InterpolateAttrLinear(xspan.TexcoordU0, xspan.TexcoordU1, i, xspan.XRecip, idiff); + v = InterpolateAttrLinear(xspan.TexcoordV0, xspan.TexcoordV1, i, xspan.XRecip, idiff); + + vr = InterpolateAttrLinear(xspan.ColorR0, xspan.ColorR1, i, xspan.XRecip, idiff); + vg = InterpolateAttrLinear(xspan.ColorG0, xspan.ColorG1, i, xspan.XRecip, idiff); + vb = InterpolateAttrLinear(xspan.ColorB0, xspan.ColorB1, i, xspan.XRecip, idiff); + } + } + +#ifndef ShadowMask + vr >>= 3; + vg >>= 3; + vb >>= 3; + + uint r, g, b, a; + uint polyalpha = bitfieldExtract(polygon.Attr, 16, 5); + +#ifdef Toon + uint tooncolor = ToonTable[vr >> 1].r; + vr = int(bitfieldExtract(tooncolor, 0, 8)); + vg = int(bitfieldExtract(tooncolor, 8, 8)); + vb = int(bitfieldExtract(tooncolor, 16, 8)); +#endif +#ifdef Highlight + vg = vr; + vb = vr; +#endif + +#ifdef NoTexture + a = int(polyalpha); +#endif + r = vr; + g = vg; + b = vb; + +#ifdef UseTexture + vec2 uvf = vec2(ivec2(u, v)) * vec2(1.0 / 16.0) * InvTextureSize; + + uvec4 texcolor = texture(CurrentTexture, vec3(uvf, polygon.TextureLayer)); +#ifdef Decal + if (texcolor.a == 31) + { + r = int(texcolor.r); + g = int(texcolor.g); + b = int(texcolor.b); + } + else if (texcolor.a > 0) + { + r = int((texcolor.r * texcolor.a) + (vr * (31-texcolor.a))) >> 5; + g = int((texcolor.g * texcolor.a) + (vg * (31-texcolor.a))) >> 5; + b = int((texcolor.b * texcolor.a) + (vb * (31-texcolor.a))) >> 5; + } + a = int(polyalpha); +#endif +#if defined(Modulate) || defined(Toon) || defined(Highlight) + r = int((texcolor.r+1) * (vr+1) - 1) >> 6; + g = int((texcolor.g+1) * (vg+1) - 1) >> 6; + b = int((texcolor.b+1) * (vb+1) - 1) >> 6; + a = int((texcolor.a+1) * (polyalpha+1) - 1) >> 5; +#endif +#endif + +#ifdef Highlight + uint tooncolor = ToonTable[vr >> 1].r; + + r = min(r + int(bitfieldExtract(tooncolor, 0, 8)), 63); + g = min(g + int(bitfieldExtract(tooncolor, 8, 8)), 63); + b = min(b + int(bitfieldExtract(tooncolor, 16, 8)), 63); +#endif + + if (polyalpha == 0) + a = 31; + + if (a > AlphaRef) + { + color = r | (g << 8) | (b << 16) | (a << 24); + + DepthTiles[tileOffset] = z; + AttrTiles[tileOffset] = attr; + } +#else + color = 0xFFFFFFFF; // doesn't really matter as long as it's not 0 + DepthTiles[tileOffset] = z; +#endif + } + } + + ColorTiles[tileOffset] = color; +} + +)"; + +const std::string DepthBlend = + PolygonBuffer + + Tilebuffers + + ResultBuffer + + BinningBuffer + R"( + +layout (local_size_x = TileSize, local_size_y = TileSize) in; + +void PlotTranslucent(inout uint color, inout uint depth, inout uint attr, bool isShadow, uint tileColor, uint srcA, uint tileDepth, uint srcAttr, bool writeDepth) +{ + uint blendAttr = (srcAttr & 0xE0F0U) | ((srcAttr >> 8) & 0xFF0000U) | (1U<<22) | (attr & 0xFF001F0FU); + + if ((!isShadow || (attr & (1U<<22)) != 0U) + ? (attr & 0x007F0000U) != (blendAttr & 0x007F0000U) + : (attr & 0x3F000000U) != (srcAttr & 0x3F000000U)) + { + // le blend + if (writeDepth) + depth = tileDepth; + + if ((attr & (1U<<15)) == 0) + blendAttr &= ~(1U<<15); + attr = blendAttr; + + uint srcRB = tileColor & 0x3F003FU; + uint srcG = tileColor & 0x003F00U; + uint dstRB = color & 0x3F003FU; + uint dstG = color & 0x003F00U; + uint dstA = color & 0x1F000000U; + + uint alpha = (srcA >> 24) + 1; + if (dstA != 0) + { + srcRB = ((srcRB * alpha) + (dstRB * (32-alpha))) >> 5; + srcG = ((srcG * alpha) + (dstG * (32-alpha))) >> 5; + } + + color = (srcRB & 0x3F003FU) | (srcG & 0x003F00U) | max(dstA, srcA); + } +} + +void ProcessCoarseMask(int linearTile, uint coarseMask, uint coarseOffset, + inout uvec2 color, inout uvec2 depth, inout uvec2 attr, inout uint stencil, + inout bool prevIsShadowMask) +{ + int tileInnerOffset = int(gl_LocalInvocationID.x) + int(gl_LocalInvocationID.y) * TileSize; + + while (coarseMask != 0U) + { + uint coarseBit = findLSB(coarseMask); + coarseMask &= ~(1U << coarseBit); + + uint tileOffset = linearTile * BinStride + coarseBit + coarseOffset; + + uint fineMask = BinningMaskAndOffset[BinningMaskStart + tileOffset]; + uint workIdx = BinningMaskAndOffset[BinningWorkOffsetsStart + tileOffset]; + + while (fineMask != 0U) + { + uint fineIdx = findLSB(fineMask); + fineMask &= ~(1U << fineIdx); + + uint pixelindex = tileInnerOffset + workIdx * TileSize * TileSize; + uint tileColor = ColorTiles[pixelindex]; + workIdx++; + + uint polygonIdx = fineIdx + (coarseBit + coarseOffset) * 32; + + if (tileColor != 0U) + { + uint polygonAttr = Polygons[polygonIdx].Attr; + + bool isShadowMask = ((polygonAttr & 0x3F000030U) == 0x00000030U); + bool prevIsShadowMaskOld = prevIsShadowMask; + prevIsShadowMask = isShadowMask; + + bool equalDepthTest = (polygonAttr & (1U << 14)) != 0U; + + uint tileDepth = DepthTiles[pixelindex]; + uint tileAttr = AttrTiles[pixelindex]; + + uint dstattr = attr.x; + + if (!isShadowMask) + { + bool isShadow = (polygonAttr & 0x30U) == 0x30U; + + bool writeSecondLayer = false; + + if (isShadow) + { + if (stencil == 0U) + continue; + if ((stencil & 1U) == 0U) + writeSecondLayer = true; + if ((stencil & 2U) == 0U) + dstattr &= ~0x3U; + } + + uint dstDepth = writeSecondLayer ? depth.y : depth.x; + if (!(equalDepthTest +#ifdef WBuffer + ? dstDepth - tileDepth + 0xFFU <= 0x1FE +#endif +#ifdef ZBuffer + ? dstDepth - tileDepth + 0x200 <= 0x400 +#endif + : tileDepth < dstDepth)) + { + if ((dstattr & 0x3U) == 0U || writeSecondLayer) + continue; + + writeSecondLayer = true; + dstattr = attr.y; + if (!(equalDepthTest +#ifdef WBuffer + ? depth.y - tileDepth + 0xFFU <= 0x1FE +#endif +#ifdef ZBuffer + ? depth.y - tileDepth + 0x200 <= 0x400 +#endif + : tileDepth < depth.y)) + continue; + } + + uint srcAttr = (polygonAttr & 0x3F008000U); + + uint srcA = tileColor & 0x1F000000U; + if (srcA == 0x1F000000U) + { + srcAttr |= tileAttr; + + if (!writeSecondLayer) + { + if ((srcAttr & 0x3U) != 0U) + { + color.y = color.x; + depth.y = depth.x; + attr.y = attr.x; + } + + color.x = tileColor; + depth.x = tileDepth; + attr.x = srcAttr; + } + else + { + color.y = tileColor; + depth.y = tileDepth; + attr.y = srcAttr; + } + } + else + { + bool writeDepth = (polygonAttr & (1U<<11)) != 0; + + if (!writeSecondLayer) + { + // blend into both layers + PlotTranslucent(color.x, depth.x, attr.x, isShadow, tileColor, srcA, tileDepth, srcAttr, writeDepth); + } + if (writeSecondLayer || (dstattr & 0x3U) != 0U) + { + PlotTranslucent(color.y, depth.y, attr.y, isShadow, tileColor, srcA, tileDepth, srcAttr, writeDepth); + } + } + } + else + { + if (!prevIsShadowMaskOld) + stencil = 0; + + if (!(equalDepthTest +#ifdef WBuffer + ? depth.x - tileDepth + 0xFFU <= 0x1FE +#endif +#ifdef ZBuffer + ? depth.x - tileDepth + 0x200 <= 0x400 +#endif + : tileDepth < depth.x)) + stencil = 0x1U; + + if ((dstattr & 0x3U) != 0U) + { + if (!(equalDepthTest +#ifdef WBuffer + ? depth.y - tileDepth + 0xFFU <= 0x1FE +#endif +#ifdef ZBuffer + ? depth.y - tileDepth + 0x200 <= 0x400 +#endif + : tileDepth < depth.y)) + stencil |= 0x2U; + } + } + } + } + } +} + +void main() +{ + int linearTile = int(gl_WorkGroupID.x + (gl_WorkGroupID.y * TilesPerLine)); + + uint coarseMaskLo = BinningMaskAndOffset[BinningCoarseMaskStart + linearTile*CoarseBinStride + 0]; + uint coarseMaskHi = BinningMaskAndOffset[BinningCoarseMaskStart + linearTile*CoarseBinStride + 1]; + + uvec2 color = uvec2(ClearColor, 0U); + uvec2 depth = uvec2(ClearDepth, 0U); + uvec2 attr = uvec2(ClearAttr, 0U); + uint stencil = 0U; + bool prevIsShadowMask = false; + + ProcessCoarseMask(linearTile, coarseMaskLo, 0, color, depth, attr, stencil, prevIsShadowMask); + ProcessCoarseMask(linearTile, coarseMaskHi, BinStride/2, color, depth, attr, stencil, prevIsShadowMask); + + int resultOffset = int(gl_GlobalInvocationID.x) + int(gl_GlobalInvocationID.y) * ScreenWidth; + ResultValue[ResultColorStart+resultOffset] = color.x; + ResultValue[ResultColorStart+resultOffset+FramebufferStride] = color.y; + ResultValue[ResultDepthStart+resultOffset] = depth.x; + ResultValue[ResultDepthStart+resultOffset+FramebufferStride] = depth.y; + ResultValue[ResultAttrStart+resultOffset] = attr.x; + ResultValue[ResultAttrStart+resultOffset+FramebufferStride] = attr.y; +} + +)"; + +const std::string FinalPass = + ResultBuffer + R"( + +layout (local_size_x = 32) in; + +layout (binding = 0, rgba8) writeonly uniform image2D FinalFB; +layout (binding = 1, rgba8ui) writeonly uniform uimage2D LowResFB; + +uint BlendFog(uint color, uint depth) +{ + uint densityid = 0, densityfrac = 0; + + if (depth >= FogOffset) + { + depth -= FogOffset; + depth = (depth >> 2) << FogShift; + + densityid = depth >> 17; + if (densityid >= 32) + { + densityid = 32; + densityfrac = 0; + } + else + { + densityfrac = depth & 0x1FFFFU; + } + } + + uint density = + ((ToonTable[densityid].g * (0x20000U-densityfrac)) + + (ToonTable[densityid+1].g * densityfrac)) >> 17; + density = min(density, 128U); + + uint colorRB = color & 0x3F003FU; + uint colorGA = (color >> 8) & 0x3F003FU; + + uint fogRB = FogColor & 0x3F003FU; + uint fogGA = (FogColor >> 8) & 0x1F003FU; + + uint finalColorRB = ((fogRB * density) + (colorRB * (128-density))) >> 7; + uint finalColorGA = ((fogGA * density) + (colorGA * (128-density))) >> 7; + + finalColorRB &= 0x3F003FU; + finalColorGA &= 0x1F003FU; + + return (DispCnt & (1U<<6)) != 0 + ? (bitfieldInsert(color, finalColorGA >> 16, 24, 8)) + : (finalColorRB | (finalColorGA << 8)); +} + +void main() +{ + int srcX = int(gl_GlobalInvocationID.x); + int resultOffset = int(srcX) + int(gl_GlobalInvocationID.y) * ScreenWidth; + + uvec2 color = uvec2(ResultValue[resultOffset+ResultColorStart], ResultValue[resultOffset+FramebufferStride+ResultColorStart]); + uvec2 depth = uvec2(ResultValue[resultOffset+ResultDepthStart], ResultValue[resultOffset+FramebufferStride+ResultDepthStart]); + uvec2 attr = uvec2(ResultValue[resultOffset+ResultAttrStart], ResultValue[resultOffset+FramebufferStride+ResultAttrStart]); + +#ifdef EdgeMarking + if ((attr.x & 0xFU) != 0U) + { + uvec4 otherAttr = uvec4(ClearAttr); + uvec4 otherDepth = uvec4(ClearDepth); + + if (srcX > 0U) + { + otherAttr.x = ResultValue[resultOffset-1+ResultAttrStart]; + otherDepth.x = ResultValue[resultOffset-1+ResultDepthStart]; + } + if (srcX < ScreenWidth-1) + { + otherAttr.y = ResultValue[resultOffset+1+ResultAttrStart]; + otherDepth.y = ResultValue[resultOffset+1+ResultDepthStart]; + } + if (gl_GlobalInvocationID.y > 0U) + { + otherAttr.z = ResultValue[resultOffset-ScreenWidth+ResultAttrStart]; + otherDepth.z = ResultValue[resultOffset-ScreenWidth+ResultDepthStart]; + } + if (gl_GlobalInvocationID.y < ScreenHeight-1) + { + otherAttr.w = ResultValue[resultOffset+ScreenWidth+ResultAttrStart]; + otherDepth.w = ResultValue[resultOffset+ScreenWidth+ResultDepthStart]; + } + + uint polyId = bitfieldExtract(attr.x, 24, 6); + uvec4 otherPolyId = bitfieldExtract(otherAttr, 24, 6); + + bvec4 polyIdMismatch = notEqual(uvec4(polyId), otherPolyId); + bvec4 nearer = lessThan(uvec4(depth.x), otherDepth); + + if ((polyIdMismatch.x && nearer.x) + || (polyIdMismatch.y && nearer.y) + || (polyIdMismatch.z && nearer.z) + || (polyIdMismatch.w && nearer.w)) + { + color.x = ToonTable[polyId >> 3].b | (color.x & 0xFF000000U); + attr.x = (attr.x & 0xFFFFE0FFU) | 0x00001000U; + } + } +#endif + +#ifdef Fog + if ((attr.x & (1U<<15)) != 0U) + { + color.x = BlendFog(color.x, depth.x); + } + + if ((attr.x & 0xFU) != 0 && (attr.y & (1U<<15)) != 0U) + { + color.y = BlendFog(color.y, depth.y); + } +#endif + +#ifdef AntiAliasing + // resolve anti-aliasing + if ((attr.x & 0x3U) != 0) + { + uint coverage = (attr.x >> 8) & 0x1FU; + + if (coverage != 0) + { + uint topRB = color.x & 0x3F003FU; + uint topG = color.x & 0x003F00U; + uint topA = bitfieldExtract(color.x, 24, 5); + + uint botRB = color.y & 0x3F003FU; + uint botG = color.y & 0x003F00U; + uint botA = bitfieldExtract(color.y, 24, 5); + + coverage++; + + if (botA > 0) + { + topRB = ((topRB * coverage) + (botRB * (32-coverage))) >> 5; + topG = ((topG * coverage) + (botG * (32-coverage))) >> 5; + + topRB &= 0x3F003FU; + topG &= 0x003F00U; + } + + topA = ((topA * coverage) + (botA * (32-coverage))) >> 5; + + color.x = topRB | topG | (topA << 24); + } + else + { + color.x = color.y; + } + } +#endif + +// if (bitfieldExtract(color.x, 24, 8) != 0U) +// color.x |= 0x40000000U; +// else +// color.x = 0U; + + //if ((gl_GlobalInvocationID.y % 8) == 7 || (gl_GlobalInvocationID.y % 8) == 7) + // color.x = 0x1F00001FU | 0x40000000U; + + vec4 result = vec4(bitfieldExtract(color.x, 16, 8), bitfieldExtract(color.x, 8, 8), color.x & 0x3FU, bitfieldExtract(color.x, 24, 8)); + result /= vec4(63.0, 63.0, 63.0, 31.0); + imageStore(FinalFB, ivec2(gl_GlobalInvocationID.xy), result); + + // It's a division by constant, so using the builtin division is fine + const int scale = ScreenWidth/256; + ivec2 lowresCoordinate = ivec2(gl_GlobalInvocationID.xy) / scale; + ivec2 lowresCoordinateRest = ivec2(gl_GlobalInvocationID.xy) % scale; + if (lowresCoordinateRest == ivec2(0, 0)) + { + uvec4 color8; + color8.x = bitfieldExtract(color.x, 0, 8); + color8.y = bitfieldExtract(color.x, 8, 8); + color8.z = bitfieldExtract(color.x, 16, 8); + color8.w = bitfieldExtract(color.x, 24, 8); + imageStore(LowResFB, lowresCoordinate, color8); + } +} + +)"; + +} + +} + +#endif \ No newline at end of file diff --git a/src/GPU3D_OpenGL.cpp b/src/GPU3D_OpenGL.cpp index 3e9ce5b0..3f85db8c 100644 --- a/src/GPU3D_OpenGL.cpp +++ b/src/GPU3D_OpenGL.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -28,46 +28,32 @@ namespace melonDS { -bool GLRenderer::BuildRenderShader(u32 flags, const char* vs, const char* fs) +bool GLRenderer::BuildRenderShader(u32 flags, const std::string& vs, const std::string& fs) { char shadername[32]; snprintf(shadername, sizeof(shadername), "RenderShader%02X", flags); int headerlen = strlen(kShaderHeader); - int vslen = strlen(vs); - int vsclen = strlen(kRenderVSCommon); - char* vsbuf = new char[headerlen + vsclen + vslen + 1]; - strcpy(&vsbuf[0], kShaderHeader); - strcpy(&vsbuf[headerlen], kRenderVSCommon); - strcpy(&vsbuf[headerlen + vsclen], vs); + std::string vsbuf; + vsbuf += kShaderHeader; + vsbuf += kRenderVSCommon; + vsbuf += vs; - int fslen = strlen(fs); - int fsclen = strlen(kRenderFSCommon); - char* fsbuf = new char[headerlen + fsclen + fslen + 1]; - strcpy(&fsbuf[0], kShaderHeader); - strcpy(&fsbuf[headerlen], kRenderFSCommon); - strcpy(&fsbuf[headerlen + fsclen], fs); + std::string fsbuf; + fsbuf += kShaderHeader; + fsbuf += kRenderFSCommon; + fsbuf += fs; - bool ret = OpenGL::BuildShaderProgram(vsbuf, fsbuf, RenderShader[flags], shadername); - - delete[] vsbuf; - delete[] fsbuf; + GLuint prog; + bool ret = OpenGL::CompileVertexFragmentProgram(prog, + vsbuf, fsbuf, + shadername, + {{"vPosition", 0}, {"vColor", 1}, {"vTexcoord", 2}, {"vPolygonAttr", 3}}, + {{"oColor", 0}, {"oAttr", 1}}); if (!ret) return false; - GLuint prog = RenderShader[flags][2]; - - glBindAttribLocation(prog, 0, "vPosition"); - glBindAttribLocation(prog, 1, "vColor"); - glBindAttribLocation(prog, 2, "vTexcoord"); - glBindAttribLocation(prog, 3, "vPolygonAttr"); - glBindFragDataLocation(prog, 0, "oColor"); - glBindFragDataLocation(prog, 1, "oAttr"); - - if (!OpenGL::LinkShaderProgram(RenderShader[flags])) - return false; - GLint uni_id = glGetUniformBlockIndex(prog, "uConfig"); glUniformBlockBinding(prog, uni_id, 0); @@ -78,13 +64,15 @@ bool GLRenderer::BuildRenderShader(u32 flags, const char* vs, const char* fs) uni_id = glGetUniformLocation(prog, "TexPalMem"); glUniform1i(uni_id, 1); + RenderShader[flags] = prog; + return true; } void GLRenderer::UseRenderShader(u32 flags) { if (CurShaderID == flags) return; - glUseProgram(RenderShader[flags][2]); + glUseProgram(RenderShader[flags]); CurShaderID = flags; } @@ -125,21 +113,17 @@ std::unique_ptr GLRenderer::New() noexcept glDepthRange(0, 1); glClearDepth(1.0); - - if (!OpenGL::BuildShaderProgram(kClearVS, kClearFS, result->ClearShaderPlain, "ClearShader")) + if (!OpenGL::CompileVertexFragmentProgram(result->ClearShaderPlain, + kClearVS, kClearFS, + "ClearShader", + {{"vPosition", 0}}, + {{"oColor", 0}, {"oAttr", 1}})) return nullptr; - glBindAttribLocation(result->ClearShaderPlain[2], 0, "vPosition"); - glBindFragDataLocation(result->ClearShaderPlain[2], 0, "oColor"); - glBindFragDataLocation(result->ClearShaderPlain[2], 1, "oAttr"); - - if (!OpenGL::LinkShaderProgram(result->ClearShaderPlain)) - return nullptr; - - result->ClearUniformLoc[0] = glGetUniformLocation(result->ClearShaderPlain[2], "uColor"); - result->ClearUniformLoc[1] = glGetUniformLocation(result->ClearShaderPlain[2], "uDepth"); - result->ClearUniformLoc[2] = glGetUniformLocation(result->ClearShaderPlain[2], "uOpaquePolyID"); - result->ClearUniformLoc[3] = glGetUniformLocation(result->ClearShaderPlain[2], "uFogFlag"); + result->ClearUniformLoc[0] = glGetUniformLocation(result->ClearShaderPlain, "uColor"); + result->ClearUniformLoc[1] = glGetUniformLocation(result->ClearShaderPlain, "uDepth"); + result->ClearUniformLoc[2] = glGetUniformLocation(result->ClearShaderPlain, "uOpaquePolyID"); + result->ClearUniformLoc[3] = glGetUniformLocation(result->ClearShaderPlain, "uFogFlag"); memset(result->RenderShader, 0, sizeof(RenderShader)); @@ -167,42 +151,35 @@ std::unique_ptr GLRenderer::New() noexcept if (!result->BuildRenderShader(RenderFlag_ShadowMask | RenderFlag_WBuffer, kRenderVS_W, kRenderFS_WSM)) return nullptr; - if (!OpenGL::BuildShaderProgram(kFinalPassVS, kFinalPassEdgeFS, result->FinalPassEdgeShader, "FinalPassEdgeShader")) + if (!OpenGL::CompileVertexFragmentProgram(result->FinalPassEdgeShader, + kFinalPassVS, kFinalPassEdgeFS, + "FinalPassEdgeShader", + {{"vPosition", 0}}, + {{"oColor", 0}})) + return nullptr; + if (!OpenGL::CompileVertexFragmentProgram(result->FinalPassFogShader, + kFinalPassVS, kFinalPassFogFS, + "FinalPassFogShader", + {{"vPosition", 0}}, + {{"oColor", 0}})) return nullptr; - if (!OpenGL::BuildShaderProgram(kFinalPassVS, kFinalPassFogFS, result->FinalPassFogShader, "FinalPassFogShader")) - return nullptr; + GLuint uni_id = glGetUniformBlockIndex(result->FinalPassEdgeShader, "uConfig"); + glUniformBlockBinding(result->FinalPassEdgeShader, uni_id, 0); - glBindAttribLocation(result->FinalPassEdgeShader[2], 0, "vPosition"); - glBindFragDataLocation(result->FinalPassEdgeShader[2], 0, "oColor"); - - if (!OpenGL::LinkShaderProgram(result->FinalPassEdgeShader)) - return nullptr; - - GLint uni_id = glGetUniformBlockIndex(result->FinalPassEdgeShader[2], "uConfig"); - glUniformBlockBinding(result->FinalPassEdgeShader[2], uni_id, 0); - - glUseProgram(result->FinalPassEdgeShader[2]); - - uni_id = glGetUniformLocation(result->FinalPassEdgeShader[2], "DepthBuffer"); + glUseProgram(result->FinalPassEdgeShader); + uni_id = glGetUniformLocation(result->FinalPassEdgeShader, "DepthBuffer"); glUniform1i(uni_id, 0); - uni_id = glGetUniformLocation(result->FinalPassEdgeShader[2], "AttrBuffer"); + uni_id = glGetUniformLocation(result->FinalPassEdgeShader, "AttrBuffer"); glUniform1i(uni_id, 1); - glBindAttribLocation(result->FinalPassFogShader[2], 0, "vPosition"); - glBindFragDataLocation(result->FinalPassFogShader[2], 0, "oColor"); + uni_id = glGetUniformBlockIndex(result->FinalPassFogShader, "uConfig"); + glUniformBlockBinding(result->FinalPassFogShader, uni_id, 0); - if (!OpenGL::LinkShaderProgram(result->FinalPassFogShader)) - return nullptr; - - uni_id = glGetUniformBlockIndex(result->FinalPassFogShader[2], "uConfig"); - glUniformBlockBinding(result->FinalPassFogShader[2], uni_id, 0); - - glUseProgram(result->FinalPassFogShader[2]); - - uni_id = glGetUniformLocation(result->FinalPassFogShader[2], "DepthBuffer"); + glUseProgram(result->FinalPassFogShader); + uni_id = glGetUniformLocation(result->FinalPassFogShader, "DepthBuffer"); glUniform1i(uni_id, 0); - uni_id = glGetUniformLocation(result->FinalPassFogShader[2], "AttrBuffer"); + uni_id = glGetUniformLocation(result->FinalPassFogShader, "AttrBuffer"); glUniform1i(uni_id, 1); @@ -255,29 +232,27 @@ std::unique_ptr GLRenderer::New() noexcept glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, result->IndexBufferID); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(IndexBuffer), nullptr, GL_DYNAMIC_DRAW); - glGenFramebuffers(4, &result->FramebufferID[0]); - glBindFramebuffer(GL_FRAMEBUFFER, result->FramebufferID[0]); - - glGenTextures(8, &result->FramebufferTex[0]); - result->FrontBuffer = 0; + glGenFramebuffers(1, &result->MainFramebuffer); + glGenFramebuffers(1, &result->DownscaleFramebuffer); // color buffers - SetupDefaultTexParams(result->FramebufferTex[0]); - SetupDefaultTexParams(result->FramebufferTex[1]); + glGenTextures(1, &result->ColorBufferTex); + SetupDefaultTexParams(result->ColorBufferTex); // depth/stencil buffer - SetupDefaultTexParams(result->FramebufferTex[4]); - SetupDefaultTexParams(result->FramebufferTex[6]); + glGenTextures(1, &result->DepthBufferTex); + SetupDefaultTexParams(result->DepthBufferTex); // attribute buffer // R: opaque polyID (for edgemarking) // G: edge flag // B: fog flag - SetupDefaultTexParams(result->FramebufferTex[5]); - SetupDefaultTexParams(result->FramebufferTex[7]); + glGenTextures(1, &result->AttrBufferTex); + SetupDefaultTexParams(result->AttrBufferTex); // downscale framebuffer for display capture (always 256x192) - SetupDefaultTexParams(result->FramebufferTex[3]); + glGenTextures(1, &result->DownScaleBufferTex); + SetupDefaultTexParams(result->DownScaleBufferTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 192, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glEnable(GL_BLEND); @@ -315,8 +290,12 @@ GLRenderer::~GLRenderer() glDeleteTextures(1, &TexMemID); glDeleteTextures(1, &TexPalMemID); - glDeleteFramebuffers(4, &FramebufferID[0]); - glDeleteTextures(8, &FramebufferTex[0]); + glDeleteFramebuffers(1, &MainFramebuffer); + glDeleteFramebuffers(1, &DownscaleFramebuffer); + glDeleteTextures(1, &ColorBufferTex); + glDeleteTextures(1, &DepthBufferTex); + glDeleteTextures(1, &AttrBufferTex); + glDeleteTextures(1, &DownScaleBufferTex); glDeleteVertexArrays(1, &VertexArrayID); glDeleteBuffers(1, &VertexBufferID); @@ -327,8 +306,8 @@ GLRenderer::~GLRenderer() for (int i = 0; i < 16; i++) { - if (!RenderShader[i][2]) continue; - OpenGL::DeleteShaderProgram(RenderShader[i]); + if (!RenderShader[i]) continue; + glDeleteProgram(RenderShader[i]); } } @@ -361,40 +340,25 @@ void GLRenderer::SetRenderSettings(bool betterpolygons, int scale) noexcept ScreenW = 256 * scale; ScreenH = 192 * scale; - glBindTexture(GL_TEXTURE_2D, FramebufferTex[0]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ScreenW, ScreenH, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - glBindTexture(GL_TEXTURE_2D, FramebufferTex[1]); + glBindTexture(GL_TEXTURE_2D, ColorBufferTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ScreenW, ScreenH, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - glBindTexture(GL_TEXTURE_2D, FramebufferTex[4]); + glBindTexture(GL_TEXTURE_2D, DepthBufferTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, ScreenW, ScreenH, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL); - glBindTexture(GL_TEXTURE_2D, FramebufferTex[5]); + glBindTexture(GL_TEXTURE_2D, AttrBufferTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, ScreenW, ScreenH, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); - glBindTexture(GL_TEXTURE_2D, FramebufferTex[6]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, ScreenW, ScreenH, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL); - glBindTexture(GL_TEXTURE_2D, FramebufferTex[7]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, ScreenW, ScreenH, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); - - glBindFramebuffer(GL_FRAMEBUFFER, FramebufferID[3]); - glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, FramebufferTex[3], 0); + glBindFramebuffer(GL_FRAMEBUFFER, DownscaleFramebuffer); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, DownScaleBufferTex, 0); GLenum fbassign[2] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}; - glBindFramebuffer(GL_FRAMEBUFFER, FramebufferID[0]); - glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, FramebufferTex[0], 0); - glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, FramebufferTex[4], 0); - glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, FramebufferTex[5], 0); + glBindFramebuffer(GL_FRAMEBUFFER, MainFramebuffer); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, ColorBufferTex, 0); + glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, DepthBufferTex, 0); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, AttrBufferTex, 0); glDrawBuffers(2, fbassign); - glBindFramebuffer(GL_FRAMEBUFFER, FramebufferID[1]); - glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, FramebufferTex[1], 0); - glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, FramebufferTex[6], 0); - glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, FramebufferTex[7], 0); - glDrawBuffers(2, fbassign); - - glBindFramebuffer(GL_FRAMEBUFFER, FramebufferID[0]); - glBindBuffer(GL_PIXEL_PACK_BUFFER, PixelbufferID); glBufferData(GL_PIXEL_PACK_BUFFER, 256*192*4, NULL, GL_DYNAMIC_READ); @@ -1103,9 +1067,9 @@ void GLRenderer::RenderSceneChunk(const GPU3D& gpu3d, int y, int h) glStencilMask(0); glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, FramebufferTex[FrontBuffer ? 6 : 4]); + glBindTexture(GL_TEXTURE_2D, DepthBufferTex); glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, FramebufferTex[FrontBuffer ? 7 : 5]); + glBindTexture(GL_TEXTURE_2D, AttrBufferTex); glBindBuffer(GL_ARRAY_BUFFER, ClearVertexBufferID); glBindVertexArray(ClearVertexArrayID); @@ -1115,7 +1079,7 @@ void GLRenderer::RenderSceneChunk(const GPU3D& gpu3d, int y, int h) // edge marking // TODO: depth/polyid values at screen edges - glUseProgram(FinalPassEdgeShader[2]); + glUseProgram(FinalPassEdgeShader); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); @@ -1126,7 +1090,7 @@ void GLRenderer::RenderSceneChunk(const GPU3D& gpu3d, int y, int h) { // fog - glUseProgram(FinalPassFogShader[2]); + glUseProgram(FinalPassFogShader); if (gpu3d.RenderDispCnt & (1<<6)) glBlendFuncSeparate(GL_ZERO, GL_ONE, GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_ALPHA); @@ -1154,7 +1118,7 @@ void GLRenderer::RenderFrame(GPU& gpu) CurShaderID = -1; glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, FramebufferID[FrontBuffer]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, MainFramebuffer); ShaderConfig.uScreenSize[0] = ScreenW; ShaderConfig.uScreenSize[1] = ScreenH; @@ -1260,7 +1224,7 @@ void GLRenderer::RenderFrame(GPU& gpu) // TODO: check whether 'clear polygon ID' affects translucent polyID // (for example when alpha is 1..30) { - glUseProgram(ClearShaderPlain[2]); + glUseProgram(ClearShaderPlain); glDepthFunc(GL_ALWAYS); u32 r = gpu.GPU3D.RenderClearAttr1 & 0x1F; @@ -1320,8 +1284,6 @@ void GLRenderer::RenderFrame(GPU& gpu) RenderSceneChunk(gpu.GPU3D, 0, 192); } - - FrontBuffer = FrontBuffer ? 0 : 1; } void GLRenderer::Stop(const GPU& gpu) @@ -1331,16 +1293,14 @@ void GLRenderer::Stop(const GPU& gpu) void GLRenderer::PrepareCaptureFrame() { - // TODO: make sure this picks the right buffer when doing antialiasing - int original_fb = FrontBuffer^1; - - glBindFramebuffer(GL_READ_FRAMEBUFFER, FramebufferID[original_fb]); + glBindFramebuffer(GL_READ_FRAMEBUFFER, MainFramebuffer); glReadBuffer(GL_COLOR_ATTACHMENT0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, FramebufferID[3]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, DownscaleFramebuffer); glDrawBuffer(GL_COLOR_ATTACHMENT0); glBlitFramebuffer(0, 0, ScreenW, ScreenH, 0, 0, 256, 192, GL_COLOR_BUFFER_BIT, GL_NEAREST); - glBindFramebuffer(GL_READ_FRAMEBUFFER, FramebufferID[3]); + glBindBuffer(GL_PIXEL_PACK_BUFFER, PixelbufferID); + glBindFramebuffer(GL_READ_FRAMEBUFFER, DownscaleFramebuffer); glReadPixels(0, 0, 256, 192, GL_BGRA, GL_UNSIGNED_BYTE, NULL); } @@ -1349,12 +1309,18 @@ void GLRenderer::Blit(const GPU& gpu) CurGLCompositor.RenderFrame(gpu, *this); } +void GLRenderer::BindOutputTexture(int buffer) +{ + CurGLCompositor.BindOutputTexture(buffer); +} + u32* GLRenderer::GetLine(int line) { int stride = 256; if (line == 0) { + glBindBuffer(GL_PIXEL_PACK_BUFFER, PixelbufferID); u8* data = (u8*)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); if (data) memcpy(&Framebuffer[stride*0], data, 4*stride*192); glUnmapBuffer(GL_PIXEL_PACK_BUFFER); @@ -1374,7 +1340,7 @@ u32* GLRenderer::GetLine(int line) void GLRenderer::SetupAccelFrame() { - glBindTexture(GL_TEXTURE_2D, FramebufferTex[FrontBuffer]); + glBindTexture(GL_TEXTURE_2D, ColorBufferTex); } } diff --git a/src/GPU3D_OpenGL.h b/src/GPU3D_OpenGL.h index c30232ca..d69af324 100644 --- a/src/GPU3D_OpenGL.h +++ b/src/GPU3D_OpenGL.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -44,12 +44,11 @@ public: void Stop(const GPU& gpu) override; u32* GetLine(int line) override; - void SetupAccelFrame(); + void SetupAccelFrame() override; void PrepareCaptureFrame() override; void Blit(const GPU& gpu) override; - [[nodiscard]] const GLCompositor& GetCompositor() const noexcept { return CurGLCompositor; } - GLCompositor& GetCompositor() noexcept { return CurGLCompositor; } + void BindOutputTexture(int buffer) override; static std::unique_ptr New() noexcept; private: @@ -77,7 +76,7 @@ private: GLCompositor CurGLCompositor; RendererPolygon PolygonList[2048] {}; - bool BuildRenderShader(u32 flags, const char* vs, const char* fs); + bool BuildRenderShader(u32 flags, const std::string& vs, const std::string& fs); void UseRenderShader(u32 flags); void SetupPolygon(RendererPolygon* rp, Polygon* polygon) const; u32* SetupVertex(const Polygon* poly, int vid, const Vertex* vtx, u32 vtxattr, u32* vptr) const; @@ -96,13 +95,13 @@ private: }; - GLuint ClearShaderPlain[3] {}; + GLuint ClearShaderPlain {}; - GLuint RenderShader[16][3] {}; + GLuint RenderShader[16] {}; GLuint CurShaderID = -1; - GLuint FinalPassEdgeShader[3] {}; - GLuint FinalPassFogShader[3] {}; + GLuint FinalPassEdgeShader {}; + GLuint FinalPassFogShader {}; // std140 compliant structure struct @@ -155,12 +154,12 @@ private: bool BetterPolygons {}; int ScreenW {}, ScreenH {}; - GLuint FramebufferTex[8] {}; - int FrontBuffer {}; - GLuint FramebufferID[4] {}, PixelbufferID {}; + GLuint ColorBufferTex {}, DepthBufferTex {}, AttrBufferTex {}; + GLuint DownScaleBufferTex {}; + GLuint PixelbufferID {}; + + GLuint MainFramebuffer {}, DownscaleFramebuffer {}; u32 Framebuffer[256*192] {}; - - }; } #endif \ No newline at end of file diff --git a/src/GPU3D_OpenGL_shaders.h b/src/GPU3D_OpenGL_shaders.h index 13492b7f..03bd43f9 100644 --- a/src/GPU3D_OpenGL_shaders.h +++ b/src/GPU3D_OpenGL_shaders.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/GPU3D_Soft.cpp b/src/GPU3D_Soft.cpp index 74027d5b..a9d0bd64 100644 --- a/src/GPU3D_Soft.cpp +++ b/src/GPU3D_Soft.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -95,8 +95,8 @@ void SoftRenderer::EnableRenderThread() } } -SoftRenderer::SoftRenderer(bool threaded) noexcept - : Renderer3D(false), Threaded(threaded) +SoftRenderer::SoftRenderer() noexcept + : Renderer3D(false) { Sema_RenderStart = Platform::Semaphore_Create(); Sema_RenderDone = Platform::Semaphore_Create(); @@ -193,10 +193,10 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 1: // A3I5 { vramaddr += ((t * width) + s); - u8 pixel = ReadVRAM_Texture(vramaddr, gpu); + u8 pixel = gpu.ReadVRAMFlat_Texture(vramaddr); texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + ((pixel&0x1F)<<1), gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + ((pixel&0x1F)<<1)); *alpha = ((pixel >> 3) & 0x1C) + (pixel >> 6); } break; @@ -204,12 +204,12 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 2: // 4-color { vramaddr += (((t * width) + s) >> 2); - u8 pixel = ReadVRAM_Texture(vramaddr, gpu); + u8 pixel = gpu.ReadVRAMFlat_Texture(vramaddr); pixel >>= ((s & 0x3) << 1); pixel &= 0x3; texpal <<= 3; - *color = ReadVRAM_TexPal(texpal + (pixel<<1), gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + (pixel<<1)); *alpha = (pixel==0) ? alpha0 : 31; } break; @@ -217,12 +217,12 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 3: // 16-color { vramaddr += (((t * width) + s) >> 1); - u8 pixel = ReadVRAM_Texture(vramaddr, gpu); + u8 pixel = gpu.ReadVRAMFlat_Texture(vramaddr); if (s & 0x1) pixel >>= 4; else pixel &= 0xF; texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + (pixel<<1), gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + (pixel<<1)); *alpha = (pixel==0) ? alpha0 : 31; } break; @@ -230,10 +230,10 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 4: // 256-color { vramaddr += ((t * width) + s); - u8 pixel = ReadVRAM_Texture(vramaddr, gpu); + u8 pixel = gpu.ReadVRAMFlat_Texture(vramaddr); texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + (pixel<<1), gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + (pixel<<1)); *alpha = (pixel==0) ? alpha0 : 31; } break; @@ -242,35 +242,42 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s { vramaddr += ((t & 0x3FC) * (width>>2)) + (s & 0x3FC); vramaddr += (t & 0x3); + vramaddr &= 0x7FFFF; // address used for all calcs wraps around after slot 3 u32 slot1addr = 0x20000 + ((vramaddr & 0x1FFFC) >> 1); if (vramaddr >= 0x40000) slot1addr += 0x10000; - u8 val = ReadVRAM_Texture(vramaddr, gpu); - val >>= (2 * (s & 0x3)); + u8 val; + if (vramaddr >= 0x20000 && vramaddr < 0x40000) // reading slot 1 for texels should always read 0 + val = 0; + else + { + val = gpu.ReadVRAMFlat_Texture(vramaddr); + val >>= (2 * (s & 0x3)); + } - u16 palinfo = ReadVRAM_Texture(slot1addr, gpu); + u16 palinfo = gpu.ReadVRAMFlat_Texture(slot1addr); u32 paloffset = (palinfo & 0x3FFF) << 2; texpal <<= 4; switch (val & 0x3) { case 0: - *color = ReadVRAM_TexPal(texpal + paloffset, gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + paloffset); *alpha = 31; break; case 1: - *color = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 2); *alpha = 31; break; case 2: if ((palinfo >> 14) == 1) { - u16 color0 = ReadVRAM_TexPal(texpal + paloffset, gpu); - u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); + u16 color0 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset); + u16 color1 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 2); u32 r0 = color0 & 0x001F; u32 g0 = color0 & 0x03E0; @@ -287,8 +294,8 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s } else if ((palinfo >> 14) == 3) { - u16 color0 = ReadVRAM_TexPal(texpal + paloffset, gpu); - u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); + u16 color0 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset); + u16 color1 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 2); u32 r0 = color0 & 0x001F; u32 g0 = color0 & 0x03E0; @@ -304,20 +311,20 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s *color = r | g | b; } else - *color = ReadVRAM_TexPal(texpal + paloffset + 4, gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 4); *alpha = 31; break; case 3: if ((palinfo >> 14) == 2) { - *color = ReadVRAM_TexPal(texpal + paloffset + 6, gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 6); *alpha = 31; } else if ((palinfo >> 14) == 3) { - u16 color0 = ReadVRAM_TexPal(texpal + paloffset, gpu); - u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); + u16 color0 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset); + u16 color1 = gpu.ReadVRAMFlat_TexPal(texpal + paloffset + 2); u32 r0 = color0 & 0x001F; u32 g0 = color0 & 0x03E0; @@ -346,10 +353,10 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 6: // A5I3 { vramaddr += ((t * width) + s); - u8 pixel = ReadVRAM_Texture(vramaddr, gpu); + u8 pixel = gpu.ReadVRAMFlat_Texture(vramaddr); texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + ((pixel&0x7)<<1), gpu); + *color = gpu.ReadVRAMFlat_TexPal(texpal + ((pixel&0x7)<<1)); *alpha = (pixel >> 3); } break; @@ -357,7 +364,7 @@ void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s case 7: // direct color { vramaddr += (((t * width) + s) << 1); - *color = ReadVRAM_Texture(vramaddr, gpu); + *color = gpu.ReadVRAMFlat_Texture(vramaddr); *alpha = (*color & 0x8000) ? 31 : 0; } break; @@ -1652,8 +1659,8 @@ void SoftRenderer::ClearBuffers(const GPU& gpu) { for (int x = 0; x < 256; x++) { - u16 val2 = ReadVRAM_Texture(0x40000 + (yoff << 9) + (xoff << 1), gpu); - u16 val3 = ReadVRAM_Texture(0x60000 + (yoff << 9) + (xoff << 1), gpu); + u16 val2 = gpu.ReadVRAMFlat_Texture(0x40000 + (yoff << 9) + (xoff << 1)); + u16 val3 = gpu.ReadVRAMFlat_Texture(0x60000 + (yoff << 9) + (xoff << 1)); // TODO: confirm color conversion u32 r = (val2 << 1) & 0x3E; if (r) r++; diff --git a/src/GPU3D_Soft.h b/src/GPU3D_Soft.h index 9cfdf9ad..73d02e4f 100644 --- a/src/GPU3D_Soft.h +++ b/src/GPU3D_Soft.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -29,7 +29,7 @@ namespace melonDS class SoftRenderer : public Renderer3D { public: - SoftRenderer(bool threaded = false) noexcept; + SoftRenderer() noexcept; ~SoftRenderer() override; void Reset(GPU& gpu) override; @@ -430,16 +430,6 @@ private: s32 ycoverage, ycov_incr; }; - template - inline T ReadVRAM_Texture(u32 addr, const GPU& gpu) const - { - return *(T*)&gpu.VRAMFlat_Texture[addr & 0x7FFFF]; - } - template - inline T ReadVRAM_TexPal(u32 addr, const GPU& gpu) const - { - return *(T*)&gpu.VRAMFlat_TexPal[addr & 0x1FFFF]; - } u32 AlphaBlend(const GPU3D& gpu3d, u32 srccolor, u32 dstcolor, u32 alpha) const noexcept; struct RendererPolygon @@ -504,7 +494,7 @@ private: // threading - bool Threaded; + bool Threaded = false; Platform::Thread* RenderThread; std::atomic_bool RenderThreadRunning; std::atomic_bool RenderThreadRendering; diff --git a/src/GPU3D_Texcache.cpp b/src/GPU3D_Texcache.cpp new file mode 100644 index 00000000..a6a40a04 --- /dev/null +++ b/src/GPU3D_Texcache.cpp @@ -0,0 +1,270 @@ +#include "GPU3D_Texcache.h" + +namespace melonDS +{ + +inline u16 ColorAvg(u16 color0, u16 color1) +{ + u32 r0 = color0 & 0x001F; + u32 g0 = color0 & 0x03E0; + u32 b0 = color0 & 0x7C00; + u32 r1 = color1 & 0x001F; + u32 g1 = color1 & 0x03E0; + u32 b1 = color1 & 0x7C00; + + u32 r = (r0 + r1) >> 1; + u32 g = ((g0 + g1) >> 1) & 0x03E0; + u32 b = ((b0 + b1) >> 1) & 0x7C00; + + return r | g | b; +} + +inline u16 Color5of3(u16 color0, u16 color1) +{ + u32 r0 = color0 & 0x001F; + u32 g0 = color0 & 0x03E0; + u32 b0 = color0 & 0x7C00; + u32 r1 = color1 & 0x001F; + u32 g1 = color1 & 0x03E0; + u32 b1 = color1 & 0x7C00; + + u32 r = (r0*5 + r1*3) >> 3; + u32 g = ((g0*5 + g1*3) >> 3) & 0x03E0; + u32 b = ((b0*5 + b1*3) >> 3) & 0x7C00; + + return r | g | b; +} + +inline u16 Color3of5(u16 color0, u16 color1) +{ + u32 r0 = color0 & 0x001F; + u32 g0 = color0 & 0x03E0; + u32 b0 = color0 & 0x7C00; + u32 r1 = color1 & 0x001F; + u32 g1 = color1 & 0x03E0; + u32 b1 = color1 & 0x7C00; + + u32 r = (r0*3 + r1*5) >> 3; + u32 g = ((g0*3 + g1*5) >> 3) & 0x03E0; + u32 b = ((b0*3 + b1*5) >> 3) & 0x7C00; + + return r | g | b; +} + +inline u32 ConvertRGB5ToRGB8(u16 val) +{ + return (((u32)val & 0x1F) << 3) + | (((u32)val & 0x3E0) << 6) + | (((u32)val & 0x7C00) << 9); +} +inline u32 ConvertRGB5ToBGR8(u16 val) +{ + return (((u32)val & 0x1F) << 9) + | (((u32)val & 0x3E0) << 6) + | (((u32)val & 0x7C00) << 3); +} +inline u32 ConvertRGB5ToRGB6(u16 val) +{ + u8 r = (val & 0x1F) << 1; + u8 g = (val & 0x3E0) >> 4; + u8 b = (val & 0x7C00) >> 9; + if (r) r++; + if (g) g++; + if (b) b++; + return (u32)r | ((u32)g << 8) | ((u32)b << 16); +} + +template +void ConvertBitmapTexture(u32 width, u32 height, u32* output, u32 addr, GPU& gpu) +{ + for (u32 i = 0; i < width*height; i++) + { + u16 value = gpu.ReadVRAMFlat_Texture(addr + i * 2); + + switch (outputFmt) + { + case outputFmt_RGB6A5: + output[i] = ConvertRGB5ToRGB6(value) | (value & 0x8000 ? 0x1F000000 : 0); + break; + case outputFmt_RGBA8: + output[i] = ConvertRGB5ToRGB8(value) | (value & 0x8000 ? 0xFF000000 : 0); + break; + case outputFmt_BGRA8: + output[i] = ConvertRGB5ToBGR8(value) | (value & 0x8000 ? 0xFF000000 : 0); + break; + } + } +} + +template void ConvertBitmapTexture(u32 width, u32 height, u32* output, u32 addr, GPU& gpu); + +template +void ConvertCompressedTexture(u32 width, u32 height, u32* output, u32 addr, u32 addrAux, u32 palAddr, GPU& gpu) +{ + // we process a whole block at the time + for (int y = 0; y < height / 4; y++) + { + for (int x = 0; x < width / 4; x++) + { + u32 data = gpu.ReadVRAMFlat_Texture(addr + (x + y * (width / 4))*4); + u16 auxData = gpu.ReadVRAMFlat_Texture(addrAux + (x + y * (width / 4))*2); + + u32 paletteOffset = palAddr + (auxData & 0x3FFF) * 4; + u16 color0 = gpu.ReadVRAMFlat_TexPal(paletteOffset) | 0x8000; + u16 color1 = gpu.ReadVRAMFlat_TexPal(paletteOffset+2) | 0x8000; + u16 color2 = gpu.ReadVRAMFlat_TexPal(paletteOffset+4) | 0x8000; + u16 color3 = gpu.ReadVRAMFlat_TexPal(paletteOffset+6) | 0x8000; + + switch ((auxData >> 14) & 0x3) + { + case 0: + color3 = 0; + break; + case 1: + { + u32 r0 = color0 & 0x001F; + u32 g0 = color0 & 0x03E0; + u32 b0 = color0 & 0x7C00; + u32 r1 = color1 & 0x001F; + u32 g1 = color1 & 0x03E0; + u32 b1 = color1 & 0x7C00; + + u32 r = (r0 + r1) >> 1; + u32 g = ((g0 + g1) >> 1) & 0x03E0; + u32 b = ((b0 + b1) >> 1) & 0x7C00; + color2 = r | g | b | 0x8000; + } + color3 = 0; + break; + case 2: + break; + case 3: + { + u32 r0 = color0 & 0x001F; + u32 g0 = color0 & 0x03E0; + u32 b0 = color0 & 0x7C00; + u32 r1 = color1 & 0x001F; + u32 g1 = color1 & 0x03E0; + u32 b1 = color1 & 0x7C00; + + u32 r = (r0*5 + r1*3) >> 3; + u32 g = ((g0*5 + g1*3) >> 3) & 0x03E0; + u32 b = ((b0*5 + b1*3) >> 3) & 0x7C00; + + color2 = r | g | b | 0x8000; + } + { + u32 r0 = color0 & 0x001F; + u32 g0 = color0 & 0x03E0; + u32 b0 = color0 & 0x7C00; + u32 r1 = color1 & 0x001F; + u32 g1 = color1 & 0x03E0; + u32 b1 = color1 & 0x7C00; + + u32 r = (r0*3 + r1*5) >> 3; + u32 g = ((g0*3 + g1*5) >> 3) & 0x03E0; + u32 b = ((b0*3 + b1*5) >> 3) & 0x7C00; + + color3 = r | g | b | 0x8000; + } + break; + } + + // in 2020 our default data types are big enough to be used as lookup tables... + u64 packed = color0 | ((u64)color1 << 16) | ((u64)color2 << 32) | ((u64)color3 << 48); + + for (int j = 0; j < 4; j++) + { + for (int i = 0; i < 4; i++) + { + u32 colorIdx = 16 * ((data >> 2 * (i + j * 4)) & 0x3); + u16 color = (packed >> colorIdx) & 0xFFFF; + u32 res; + switch (outputFmt) + { + case outputFmt_RGB6A5: res = ConvertRGB5ToRGB6(color) + | ((color & 0x8000) ? 0x1F000000 : 0); break; + case outputFmt_RGBA8: res = ConvertRGB5ToRGB8(color) + | ((color & 0x8000) ? 0xFF000000 : 0); break; + case outputFmt_BGRA8: res = ConvertRGB5ToBGR8(color) + | ((color & 0x8000) ? 0xFF000000 : 0); break; + } + output[x * 4 + i + (y * 4 + j) * width] = res; + } + } + } + } +} + +template void ConvertCompressedTexture(u32, u32, u32*, u32, u32, u32, GPU&); + +template +void ConvertAXIYTexture(u32 width, u32 height, u32* output, u32 addr, u32 palAddr, GPU& gpu) +{ + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + u8 val = gpu.ReadVRAMFlat_Texture(addr + x + y * width); + + u32 idx = val & ((1 << Y) - 1); + + u16 color = gpu.ReadVRAMFlat_TexPal(palAddr + idx * 2); + u32 alpha = (val >> Y) & ((1 << X) - 1); + if (X != 5) + alpha = alpha * 4 + alpha / 2; + + u32 res; + switch (outputFmt) + { + case outputFmt_RGB6A5: res = ConvertRGB5ToRGB6(color) | alpha << 24; break; + // make sure full alpha == 255 + case outputFmt_RGBA8: res = ConvertRGB5ToRGB8(color) | (alpha << 27 | (alpha & 0x1C) << 22); break; + case outputFmt_BGRA8: res = ConvertRGB5ToBGR8(color) | (alpha << 27 | (alpha & 0x1C) << 22); break; + } + output[x + y * width] = res; + } + } +} + +template void ConvertAXIYTexture(u32, u32, u32*, u32, u32, GPU&); +template void ConvertAXIYTexture(u32, u32, u32*, u32, u32, GPU&); + +template +void ConvertNColorsTexture(u32 width, u32 height, u32* output, u32 addr, u32 palAddr, bool color0Transparent, GPU& gpu) +{ + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width / (16 / colorBits); x++) + { + // smallest possible row is 8 pixels with 2bpp => fits in u16 + u16 val = gpu.ReadVRAMFlat_Texture(addr + 2 * (x + y * (width / (16 / colorBits)))); + + for (int i = 0; i < 16 / colorBits; i++) + { + u32 index = val & ((1 << colorBits) - 1); + val >>= colorBits; + u16 color = gpu.ReadVRAMFlat_TexPal(palAddr + index * 2); + + bool transparent = color0Transparent && index == 0; + u32 res; + switch (outputFmt) + { + case outputFmt_RGB6A5: res = ConvertRGB5ToRGB6(color) + | (transparent ? 0 : 0x1F000000); break; + case outputFmt_RGBA8: res = ConvertRGB5ToRGB8(color) + | (transparent ? 0 : 0xFF000000); break; + case outputFmt_BGRA8: res = ConvertRGB5ToBGR8(color) + | (transparent ? 0 : 0xFF000000); break; + } + output[x * (16 / colorBits) + y * width + i] = res; + } + } + } +} + +template void ConvertNColorsTexture(u32, u32, u32*, u32, u32, bool, GPU&); +template void ConvertNColorsTexture(u32, u32, u32*, u32, u32, bool, GPU&); +template void ConvertNColorsTexture(u32, u32, u32*, u32, u32, bool, GPU&); + +} \ No newline at end of file diff --git a/src/GPU3D_Texcache.h b/src/GPU3D_Texcache.h new file mode 100644 index 00000000..f2cd6416 --- /dev/null +++ b/src/GPU3D_Texcache.h @@ -0,0 +1,330 @@ +#ifndef GPU3D_TEXCACHE +#define GPU3D_TEXCACHE + +#include "types.h" +#include "GPU.h" + +#include +#include +#include + +#define XXH_STATIC_LINKING_ONLY +#include "xxhash/xxhash.h" + +namespace melonDS +{ + +inline u32 TextureWidth(u32 texparam) +{ + return 8 << ((texparam >> 20) & 0x7); +} + +inline u32 TextureHeight(u32 texparam) +{ + return 8 << ((texparam >> 23) & 0x7); +} + +enum +{ + outputFmt_RGB6A5, + outputFmt_RGBA8, + outputFmt_BGRA8 +}; + +template +void ConvertBitmapTexture(u32 width, u32 height, u32* output, u32 addr, GPU& gpu); +template +void ConvertCompressedTexture(u32 width, u32 height, u32* output, u32 addr, u32 addrAux, u32 palAddr, GPU& gpu); +template +void ConvertAXIYTexture(u32 width, u32 height, u32* output, u32 addr, u32 palAddr, GPU& gpu); +template +void ConvertNColorsTexture(u32 width, u32 height, u32* output, u32 addr, u32 palAddr, bool color0Transparent, GPU& gpu); + +template +class Texcache +{ +public: + Texcache(const TexLoaderT& texloader) + : TexLoader(texloader) // probably better if this would be a move constructor??? + {} + + u64 MaskedHash(u8* vram, u32 vramSize, u32 addr, u32 size) + { + u64 hash = 0; + + while (size > 0) + { + u32 pieceSize; + if (addr + size > vramSize) + // wraps around, only do the part inside + pieceSize = vramSize - addr; + else + // fits completely inside + pieceSize = size; + + hash = XXH64(&vram[addr], pieceSize, hash); + + addr += pieceSize; + addr &= (vramSize - 1); + assert(size >= pieceSize); + size -= pieceSize; + } + + return hash; + } + + bool CheckInvalid(u32 start, u32 size, u64 oldHash, u64* dirty, u8* vram, u32 vramSize) + { + u32 startBit = start / VRAMDirtyGranularity; + u32 bitsCount = ((start + size + VRAMDirtyGranularity - 1) / VRAMDirtyGranularity) - startBit; + + u32 startEntry = startBit >> 6; + u64 entriesCount = ((startBit + bitsCount + 0x3F) >> 6) - startEntry; + for (u32 j = startEntry; j < startEntry + entriesCount; j++) + { + if (GetRangedBitMask(j, startBit, bitsCount) & dirty[j & ((vramSize / VRAMDirtyGranularity)-1)]) + { + if (MaskedHash(vram, vramSize, start, size) != oldHash) + return true; + } + } + + return false; + } + + bool Update(GPU& gpu) + { + auto textureDirty = gpu.VRAMDirty_Texture.DeriveState(gpu.VRAMMap_Texture, gpu); + auto texPalDirty = gpu.VRAMDirty_TexPal.DeriveState(gpu.VRAMMap_TexPal, gpu); + + bool textureChanged = gpu.MakeVRAMFlat_TextureCoherent(textureDirty); + bool texPalChanged = gpu.MakeVRAMFlat_TexPalCoherent(texPalDirty); + + if (textureChanged || texPalChanged) + { + //printf("check invalidation %d\n", TexCache.size()); + for (auto it = Cache.begin(); it != Cache.end();) + { + TexCacheEntry& entry = it->second; + if (textureChanged) + { + for (u32 i = 0; i < 2; i++) + { + if (CheckInvalid(entry.TextureRAMStart[i], entry.TextureRAMSize[i], + entry.TextureHash[i], + textureDirty.Data, + gpu.VRAMFlat_Texture, sizeof(gpu.VRAMFlat_Texture))) + goto invalidate; + } + } + + if (texPalChanged && entry.TexPalSize > 0) + { + if (CheckInvalid(entry.TexPalStart, entry.TexPalSize, + entry.TexPalHash, + texPalDirty.Data, + gpu.VRAMFlat_TexPal, sizeof(gpu.VRAMFlat_TexPal))) + goto invalidate; + } + + it++; + continue; + invalidate: + FreeTextures[entry.WidthLog2][entry.HeightLog2].push_back(entry.Texture); + + //printf("invalidating texture %d\n", entry.ImageDescriptor); + + it = Cache.erase(it); + } + + return true; + } + + return false; + } + + void GetTexture(GPU& gpu, u32 texParam, u32 palBase, TexHandleT& textureHandle, u32& layer, u32*& helper) + { + // remove sampling and texcoord gen params + texParam &= ~0xC00F0000; + + u32 fmt = (texParam >> 26) & 0x7; + u64 key = texParam; + if (fmt != 7) + { + key |= (u64)palBase << 32; + if (fmt == 5) + key &= ~((u64)1 << 29); + } + //printf("%" PRIx64 " %" PRIx32 " %" PRIx32 "\n", key, texParam, palBase); + + assert(fmt != 0 && "no texture is not a texture format!"); + + auto it = Cache.find(key); + + if (it != Cache.end()) + { + textureHandle = it->second.Texture.TextureID; + layer = it->second.Texture.Layer; + helper = &it->second.LastVariant; + return; + } + + u32 widthLog2 = (texParam >> 20) & 0x7; + u32 heightLog2 = (texParam >> 23) & 0x7; + u32 width = 8 << widthLog2; + u32 height = 8 << heightLog2; + + u32 addr = (texParam & 0xFFFF) * 8; + + TexCacheEntry entry = {0}; + + entry.TextureRAMStart[0] = addr; + entry.WidthLog2 = widthLog2; + entry.HeightLog2 = heightLog2; + + // apparently a new texture + if (fmt == 7) + { + entry.TextureRAMSize[0] = width*height*2; + + ConvertBitmapTexture(width, height, DecodingBuffer, addr, gpu); + } + else if (fmt == 5) + { + u32 slot1addr = 0x20000 + ((addr & 0x1FFFC) >> 1); + if (addr >= 0x40000) + slot1addr += 0x10000; + + entry.TextureRAMSize[0] = width*height/16*4; + entry.TextureRAMStart[1] = slot1addr; + entry.TextureRAMSize[1] = width*height/16*2; + entry.TexPalStart = palBase*16; + entry.TexPalSize = 0x10000; + + ConvertCompressedTexture(width, height, DecodingBuffer, addr, slot1addr, entry.TexPalStart, gpu); + } + else + { + u32 texSize, palAddr = palBase*16, numPalEntries; + switch (fmt) + { + case 1: texSize = width*height; numPalEntries = 32; break; + case 6: texSize = width*height; numPalEntries = 8; break; + case 2: texSize = width*height/4; numPalEntries = 4; palAddr >>= 1; break; + case 3: texSize = width*height/2; numPalEntries = 16; break; + case 4: texSize = width*height; numPalEntries = 256; break; + } + + palAddr &= 0x1FFFF; + + /*printf("creating texture | fmt: %d | %dx%d | %08x | %08x\n", fmt, width, height, addr, palAddr); + svcSleepThread(1000*1000);*/ + + entry.TextureRAMSize[0] = texSize; + entry.TexPalStart = palAddr; + entry.TexPalSize = numPalEntries*2; + + //assert(entry.TexPalStart+entry.TexPalSize <= 128*1024*1024); + + bool color0Transparent = texParam & (1 << 29); + + switch (fmt) + { + case 1: ConvertAXIYTexture(width, height, DecodingBuffer, addr, palAddr, gpu); break; + case 6: ConvertAXIYTexture(width, height, DecodingBuffer, addr, palAddr, gpu); break; + case 2: ConvertNColorsTexture(width, height, DecodingBuffer, addr, palAddr, color0Transparent, gpu); break; + case 3: ConvertNColorsTexture(width, height, DecodingBuffer, addr, palAddr, color0Transparent, gpu); break; + case 4: ConvertNColorsTexture(width, height, DecodingBuffer, addr, palAddr, color0Transparent, gpu); break; + } + } + + for (int i = 0; i < 2; i++) + { + if (entry.TextureRAMSize[i]) + entry.TextureHash[i] = MaskedHash(gpu.VRAMFlat_Texture, sizeof(gpu.VRAMFlat_Texture), + entry.TextureRAMStart[i], entry.TextureRAMSize[i]); + } + if (entry.TexPalSize) + entry.TexPalHash = MaskedHash(gpu.VRAMFlat_TexPal, sizeof(gpu.VRAMFlat_TexPal), + entry.TexPalStart, entry.TexPalSize); + + auto& texArrays = TexArrays[widthLog2][heightLog2]; + auto& freeTextures = FreeTextures[widthLog2][heightLog2]; + + if (freeTextures.size() == 0) + { + texArrays.resize(texArrays.size()+1); + TexHandleT& array = texArrays[texArrays.size()-1]; + + u32 layers = std::min((8*1024*1024) / (width*height*4), 64); + + // allocate new array texture + //printf("allocating new layer set for %d %d %d %d\n", width, height, texArrays.size()-1, array.ImageDescriptor); + array = TexLoader.GenerateTexture(width, height, layers); + + for (u32 i = 0; i < layers; i++) + { + freeTextures.push_back(TexArrayEntry{array, i}); + } + } + + TexArrayEntry storagePlace = freeTextures[freeTextures.size()-1]; + freeTextures.pop_back(); + + entry.Texture = storagePlace; + + TexLoader.UploadTexture(storagePlace.TextureID, width, height, storagePlace.Layer, DecodingBuffer); + //printf("using storage place %d %d | %d %d (%d)\n", width, height, storagePlace.TexArrayIdx, storagePlace.LayerIdx, array.ImageDescriptor); + + textureHandle = storagePlace.TextureID; + layer = storagePlace.Layer; + helper = &Cache.emplace(std::make_pair(key, entry)).first->second.LastVariant; + } + + void Reset() + { + for (u32 i = 0; i < 8; i++) + { + for (u32 j = 0; j < 8; j++) + { + for (u32 k = 0; k < TexArrays[i][j].size(); k++) + TexLoader.DeleteTexture(TexArrays[i][j][k]); + TexArrays[i][j].clear(); + FreeTextures[i][j].clear(); + } + } + Cache.clear(); + } +private: + struct TexArrayEntry + { + TexHandleT TextureID; + u32 Layer; + }; + + struct TexCacheEntry + { + u32 LastVariant; // very cheap way to make variant lookup faster + + u32 TextureRAMStart[2], TextureRAMSize[2]; + u32 TexPalStart, TexPalSize; + u8 WidthLog2, HeightLog2; + TexArrayEntry Texture; + + u64 TextureHash[2]; + u64 TexPalHash; + }; + std::unordered_map Cache; + + TexLoaderT TexLoader; + + std::vector FreeTextures[8][8]; + std::vector TexArrays[8][8]; + + u32 DecodingBuffer[1024*1024]; +}; + +} + +#endif \ No newline at end of file diff --git a/src/GPU3D_TexcacheOpenGL.cpp b/src/GPU3D_TexcacheOpenGL.cpp new file mode 100644 index 00000000..95ca8cdc --- /dev/null +++ b/src/GPU3D_TexcacheOpenGL.cpp @@ -0,0 +1,29 @@ +#include "GPU3D_TexcacheOpenGL.h" + +namespace melonDS +{ + +GLuint TexcacheOpenGLLoader::GenerateTexture(u32 width, u32 height, u32 layers) +{ + GLuint texarray; + glGenTextures(1, &texarray); + glBindTexture(GL_TEXTURE_2D_ARRAY, texarray); + glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8UI, width, height, layers); + return texarray; +} + +void TexcacheOpenGLLoader::UploadTexture(GLuint handle, u32 width, u32 height, u32 layer, void* data) +{ + glBindTexture(GL_TEXTURE_2D_ARRAY, handle); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, + 0, 0, 0, layer, + width, height, 1, + GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, data); +} + +void TexcacheOpenGLLoader::DeleteTexture(GLuint handle) +{ + glDeleteTextures(1, &handle); +} + +} \ No newline at end of file diff --git a/src/GPU3D_TexcacheOpenGL.h b/src/GPU3D_TexcacheOpenGL.h new file mode 100644 index 00000000..a8cfa576 --- /dev/null +++ b/src/GPU3D_TexcacheOpenGL.h @@ -0,0 +1,25 @@ +#ifndef GPU3D_TEXCACHEOPENGL +#define GPU3D_TEXCACHEOPENGL + +#include "GPU3D_Texcache.h" +#include "OpenGLSupport.h" + +namespace melonDS +{ + +template +class Texcache; + +class TexcacheOpenGLLoader +{ +public: + GLuint GenerateTexture(u32 width, u32 height, u32 layers); + void UploadTexture(GLuint handle, u32 width, u32 height, u32 layer, void* data); + void DeleteTexture(GLuint handle); +}; + +using TexcacheOpenGL = Texcache; + +} + +#endif \ No newline at end of file diff --git a/src/GPU_OpenGL.cpp b/src/GPU_OpenGL.cpp index 2e2857ce..a58dbedb 100644 --- a/src/GPU_OpenGL.cpp +++ b/src/GPU_OpenGL.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -36,32 +36,26 @@ using namespace OpenGL; std::optional GLCompositor::New() noexcept { assert(glBindAttribLocation != nullptr); + GLuint CompShader {}; - std::array CompShader {}; - if (!OpenGL::BuildShaderProgram(kCompositorVS, kCompositorFS_Nearest, &CompShader[0], "CompositorShader")) - return std::nullopt; - - glBindAttribLocation(CompShader[2], 0, "vPosition"); - glBindAttribLocation(CompShader[2], 1, "vTexcoord"); - glBindFragDataLocation(CompShader[2], 0, "oColor"); - - if (!OpenGL::LinkShaderProgram(CompShader.data())) - // OpenGL::LinkShaderProgram already deletes the shader program object - // if linking the shaders together failed. + if (!OpenGL::CompileVertexFragmentProgram(CompShader, + kCompositorVS, kCompositorFS_Nearest, + "CompositorShader", + {{"vPosition", 0}, {"vTexcoord", 1}}, + {{"oColor", 0}})) return std::nullopt; return { GLCompositor(CompShader) }; } -GLCompositor::GLCompositor(std::array compShader) noexcept : CompShader(compShader) +GLCompositor::GLCompositor(GLuint compShader) noexcept : CompShader(compShader) { - CompScaleLoc = glGetUniformLocation(CompShader[2], "u3DScale"); - Comp3DXPosLoc = glGetUniformLocation(CompShader[2], "u3DXPos"); + CompScaleLoc = glGetUniformLocation(CompShader, "u3DScale"); - glUseProgram(CompShader[2]); - GLuint screenTextureUniform = glGetUniformLocation(CompShader[2], "ScreenTex"); + glUseProgram(CompShader); + GLuint screenTextureUniform = glGetUniformLocation(CompShader, "ScreenTex"); glUniform1i(screenTextureUniform, 0); - GLuint _3dTextureUniform = glGetUniformLocation(CompShader[2], "_3DTex"); + GLuint _3dTextureUniform = glGetUniformLocation(CompShader, "_3DTex"); glUniform1i(_3dTextureUniform, 1); // all this mess is to prevent bleeding @@ -136,7 +130,7 @@ GLCompositor::~GLCompositor() glDeleteVertexArrays(1, &CompVertexArrayID); glDeleteBuffers(1, &CompVertexBufferID); - OpenGL::DeleteShaderProgram(CompShader.data()); + glDeleteProgram(CompShader); } @@ -145,7 +139,6 @@ GLCompositor::GLCompositor(GLCompositor&& other) noexcept : ScreenH(other.ScreenH), ScreenW(other.ScreenW), CompScaleLoc(other.CompScaleLoc), - Comp3DXPosLoc(other.Comp3DXPosLoc), CompVertices(other.CompVertices), CompShader(other.CompShader), CompVertexBufferID(other.CompVertexBufferID), @@ -170,11 +163,10 @@ GLCompositor& GLCompositor::operator=(GLCompositor&& other) noexcept ScreenH = other.ScreenH; ScreenW = other.ScreenW; CompScaleLoc = other.CompScaleLoc; - Comp3DXPosLoc = other.Comp3DXPosLoc; CompVertices = other.CompVertices; // Clean up these resources before overwriting them - OpenGL::DeleteShaderProgram(CompShader.data()); + glDeleteProgram(CompShader); CompShader = other.CompShader; glDeleteBuffers(1, &CompVertexBufferID); @@ -244,11 +236,11 @@ void GLCompositor::Stop(const GPU& gpu) noexcept glBindFramebuffer(GL_FRAMEBUFFER, 0); } -void GLCompositor::RenderFrame(const GPU& gpu, GLRenderer& renderer) noexcept +void GLCompositor::RenderFrame(const GPU& gpu, Renderer3D& renderer) noexcept { - int frontbuf = gpu.FrontBuffer; + int backbuf = gpu.FrontBuffer ^ 1; glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, CompScreenOutputFB[frontbuf]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, CompScreenOutputFB[backbuf]); glDisable(GL_DEPTH_TEST); glDisable(GL_STENCIL_TEST); @@ -260,21 +252,18 @@ void GLCompositor::RenderFrame(const GPU& gpu, GLRenderer& renderer) noexcept glClear(GL_COLOR_BUFFER_BIT); // TODO: select more shaders (filtering, etc) - OpenGL::UseShaderProgram(CompShader.data()); + glUseProgram(CompShader); glUniform1ui(CompScaleLoc, Scale); - // TODO: support setting this midframe, if ever needed - glUniform1i(Comp3DXPosLoc, ((int)gpu.GPU3D.GetRenderXPos() << 23) >> 23); - glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, CompScreenInputTex); - if (gpu.Framebuffer[frontbuf][0] && gpu.Framebuffer[frontbuf][1]) + if (gpu.Framebuffer[backbuf][0] && gpu.Framebuffer[backbuf][1]) { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256*3 + 1, 192, GL_RGBA_INTEGER, - GL_UNSIGNED_BYTE, gpu.Framebuffer[frontbuf][0].get()); + GL_UNSIGNED_BYTE, gpu.Framebuffer[backbuf][0].get()); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256*3 + 1, 192, GL_RGBA_INTEGER, - GL_UNSIGNED_BYTE, gpu.Framebuffer[frontbuf][1].get()); + GL_UNSIGNED_BYTE, gpu.Framebuffer[backbuf][1].get()); } glActiveTexture(GL_TEXTURE1); diff --git a/src/GPU_OpenGL.h b/src/GPU_OpenGL.h index 9c040966..2e482861 100644 --- a/src/GPU_OpenGL.h +++ b/src/GPU_OpenGL.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -28,6 +28,7 @@ namespace melonDS class GPU; struct RenderSettings; class GLRenderer; +class Renderer3D; class GLCompositor { public: @@ -42,16 +43,15 @@ public: [[nodiscard]] int GetScaleFactor() const noexcept { return Scale; } void Stop(const GPU& gpu) noexcept; - void RenderFrame(const GPU& gpu, GLRenderer& renderer) noexcept; + void RenderFrame(const GPU& gpu, Renderer3D& renderer) noexcept; void BindOutputTexture(int buf); private: - GLCompositor(std::array CompShader) noexcept; + GLCompositor(GLuint CompShader) noexcept; int Scale = 0; int ScreenH = 0, ScreenW = 0; - std::array CompShader {}; + GLuint CompShader {}; GLuint CompScaleLoc = 0; - GLuint Comp3DXPosLoc = 0; GLuint CompVertexBufferID = 0; GLuint CompVertexArrayID = 0; diff --git a/src/GPU_OpenGL_shaders.h b/src/GPU_OpenGL_shaders.h index a8c5b951..3c463ab8 100644 --- a/src/GPU_OpenGL_shaders.h +++ b/src/GPU_OpenGL_shaders.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -43,7 +43,6 @@ void main() const char* kCompositorFS_Nearest = R"(#version 140 uniform uint u3DScale; -uniform int u3DXPos; uniform usampler2D ScreenTex; uniform sampler2D _3DTex; @@ -56,11 +55,13 @@ void main() { ivec4 pixel = ivec4(texelFetch(ScreenTex, ivec2(fTexcoord), 0)); - float _3dxpos = float(u3DXPos); - ivec4 mbright = ivec4(texelFetch(ScreenTex, ivec2(256*3, int(fTexcoord.y)), 0)); int dispmode = mbright.b & 0x3; + // mbright.a == HOFS bit0..7 + // mbright.b bit7 == HOFS bit8 (sign) + float _3dxpos = float(mbright.a - ((mbright.b & 0x80) * 2)); + if (dispmode == 1) { ivec4 val1 = pixel; diff --git a/src/JitBlock.h b/src/JitBlock.h index 9b31d6d7..2dc516fd 100644 --- a/src/JitBlock.h +++ b/src/JitBlock.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/MemConstants.h b/src/MemConstants.h index e9aa6b2b..3e10cbce 100644 --- a/src/MemConstants.h +++ b/src/MemConstants.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/MemRegion.h b/src/MemRegion.h index 11b3d1da..0a8212c7 100644 --- a/src/MemRegion.h +++ b/src/MemRegion.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/NDS.cpp b/src/NDS.cpp index 62a07b4e..7e8711bf 100644 --- a/src/NDS.cpp +++ b/src/NDS.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -35,6 +35,7 @@ #include "Platform.h" #include "FreeBIOS.h" #include "Args.h" +#include "version.h" #include "DSi.h" #include "DSi_SPI_TSC.h" @@ -80,18 +81,19 @@ NDS::NDS() noexcept : NDSArgs { nullptr, nullptr, - bios_arm9_bin, - bios_arm7_bin, + std::make_unique(bios_arm9_bin), + std::make_unique(bios_arm7_bin), Firmware(0), } ) { } -NDS::NDS(NDSArgs&& args, int type) noexcept : +NDS::NDS(NDSArgs&& args, int type, void* userdata) noexcept : ConsoleType(type), - ARM7BIOS(args.ARM7BIOS), - ARM9BIOS(args.ARM9BIOS), + UserData(userdata), + ARM7BIOS(*args.ARM7BIOS), + ARM9BIOS(*args.ARM9BIOS), ARM7BIOSNative(CRC32(ARM7BIOS.data(), ARM7BIOS.size()) == ARM7BIOSCRC32), ARM9BIOSNative(CRC32(ARM9BIOS.data(), ARM9BIOS.size()) == ARM9BIOSCRC32), JIT(*this, args.JIT), @@ -101,10 +103,13 @@ NDS::NDS(NDSArgs&& args, int type) noexcept : RTC(*this), Wifi(*this), NDSCartSlot(*this, std::move(args.NDSROM)), - GBACartSlot(type == 1 ? nullptr : std::move(args.GBAROM)), + GBACartSlot(*this, type == 1 ? nullptr : std::move(args.GBAROM)), AREngine(*this), ARM9(*this, args.GDB, args.JIT.has_value()), ARM7(*this, args.GDB, args.JIT.has_value()), +#ifdef GDBSTUB_ENABLED + EnableGDBStub(args.GDB.has_value()), +#endif #ifdef JIT_ENABLED EnableJIT(args.JIT.has_value()), #endif @@ -573,7 +578,7 @@ void NDS::Stop(Platform::StopReason reason) Log(level, "Stopping emulated console (Reason: %s)\n", StopReasonName(reason)); Running = false; - Platform::SignalStop(reason); + Platform::SignalStop(reason, UserData); GPU.Stop(); SPU.Stop(); } @@ -754,7 +759,7 @@ void NDS::SetDebugPrint(bool enabled) noexcept void NDS::LoadGBAAddon(int type) { - GBACartSlot.LoadAddon(type); + GBACartSlot.LoadAddon(UserData, type); } void NDS::LoadBIOS() @@ -889,7 +894,7 @@ void NDS::RunSystemSleep(u64 timestamp) } } -template +template u32 NDS::RunFrame() { FrameStartTimestamp = SysTimestamp; @@ -930,8 +935,11 @@ u32 NDS::RunFrame() } else { - ARM9.CheckGdbIncoming(); - ARM7.CheckGdbIncoming(); + if (cpuMode == CPUExecuteMode::InterpreterGDB) + { + ARM9.CheckGdbIncoming(); + ARM7.CheckGdbIncoming(); + } if (!(CPUStop & CPUStop_Wakeup)) { @@ -966,12 +974,7 @@ u32 NDS::RunFrame() } else { -#ifdef JIT_ENABLED - if (EnableJIT) - ARM9.ExecuteJIT(); - else -#endif - ARM9.Execute(); + ARM9.Execute(); } RunTimers(0); @@ -998,12 +1001,7 @@ u32 NDS::RunFrame() } else { -#ifdef JIT_ENABLED - if (EnableJIT) - ARM7.ExecuteJIT(); - else -#endif - ARM7.Execute(); + ARM7.Execute(); } RunTimers(1); @@ -1048,10 +1046,18 @@ u32 NDS::RunFrame() { #ifdef JIT_ENABLED if (EnableJIT) - return RunFrame(); + return RunFrame(); else #endif - return RunFrame(); +#ifdef GDBSTUB_ENABLED + if (EnableGDBStub) + { + return RunFrame(); + } else +#endif + { + return RunFrame(); + } } void NDS::Reschedule(u64 target) @@ -1466,7 +1472,7 @@ u64 NDS::GetSysClockCycles(int num) return ret; } -void NDS::NocashPrint(u32 ncpu, u32 addr) +void NDS::NocashPrint(u32 ncpu, u32 addr, bool appendNewline) { // addr: debug string @@ -1544,7 +1550,7 @@ void NDS::NocashPrint(u32 ncpu, u32 addr) } output[ptr] = '\0'; - Log(LogLevel::Debug, "%s", output); + Log(LogLevel::Debug, appendNewline ? "%s\n" : "%s", output); } void NDS::MonitorARM9Jump(u32 addr) @@ -1850,7 +1856,7 @@ void NDS::debug(u32 param) //for (int i = 0; i < 9; i++) // printf("VRAM %c: %02X\n", 'A'+i, GPU->VRAMCNT[i]); - Platform::FileHandle* shit = Platform::OpenFile("debug/DSfirmware.bin", FileMode::Write); + Platform::FileHandle* shit = Platform::OpenFile("debug/pokeplat.bin", FileMode::Write); Platform::FileWrite(ARM9.ITCM, 0x8000, 1, shit); for (u32 i = 0x02000000; i < 0x02400000; i+=4) { @@ -2733,11 +2739,37 @@ u8 NDS::ARM9IORead8(u32 addr) case 0x04000132: return KeyCnt[0] & 0xFF; case 0x04000133: return KeyCnt[0] >> 8; + case 0x040001A0: + if (!(ExMemCnt[0] & (1<<11))) + return NDSCartSlot.GetSPICnt() & 0xFF; + return 0; + case 0x040001A1: + if (!(ExMemCnt[0] & (1<<11))) + return NDSCartSlot.GetSPICnt() >> 8; + return 0; + case 0x040001A2: if (!(ExMemCnt[0] & (1<<11))) return NDSCartSlot.ReadSPIData(); return 0; + case 0x040001A4: + if (!(ExMemCnt[0] & (1<<11))) + return NDSCartSlot.GetROMCnt() & 0xFF; + return 0; + case 0x040001A5: + if (!(ExMemCnt[0] & (1<<11))) + return (NDSCartSlot.GetROMCnt() >> 8) & 0xFF; + return 0; + case 0x040001A6: + if (!(ExMemCnt[0] & (1<<11))) + return (NDSCartSlot.GetROMCnt() >> 16) & 0xFF; + return 0; + case 0x040001A7: + if (!(ExMemCnt[0] & (1<<11))) + return NDSCartSlot.GetROMCnt() >> 24; + return 0; + case 0x040001A8: if (!(ExMemCnt[0] & (1<<11))) return NDSCartSlot.GetROMCommand(0); @@ -2818,7 +2850,7 @@ u8 NDS::ARM9IORead8(u32 addr) if(addr >= 0x04FFFA00 && addr < 0x04FFFA10) { // FIX: GBATek says this should be padded with spaces - static char const emuID[16] = "melonDS " MELONDS_VERSION; + static char const emuID[16] = "melonDS " MELONDS_VERSION_BASE; auto idx = addr - 0x04FFFA00; return (u8)(emuID[idx]); } @@ -2889,6 +2921,15 @@ u16 NDS::ARM9IORead16(u32 addr) return NDSCartSlot.ReadSPIData(); return 0; + case 0x040001A4: + if (!(ExMemCnt[0] & (1<<11))) + return NDSCartSlot.GetROMCnt() & 0xFFFF; + return 0; + case 0x040001A6: + if (!(ExMemCnt[0] & (1<<11))) + return NDSCartSlot.GetROMCnt() >> 16; + return 0; + case 0x040001A8: if (!(ExMemCnt[0] & (1<<11))) return NDSCartSlot.GetROMCommand(0) | @@ -2914,6 +2955,8 @@ u16 NDS::ARM9IORead16(u32 addr) case 0x04000208: return IME[0]; case 0x04000210: return IE[0] & 0xFFFF; case 0x04000212: return IE[0] >> 16; + case 0x04000214: return IF[0] & 0xFFFF; + case 0x04000216: return IF[0] >> 16; case 0x04000240: return GPU.VRAMCNT[0] | (GPU.VRAMCNT[1] << 8); case 0x04000242: return GPU.VRAMCNT[2] | (GPU.VRAMCNT[3] << 8); @@ -3152,6 +3195,23 @@ void NDS::ARM9IOWrite8(u32 addr, u8 val) NDSCartSlot.WriteSPIData(val); return; + case 0x040001A4: + if (!(ExMemCnt[0] & (1<<11))) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFFFFFF00) | val); + return; + case 0x040001A5: + if (!(ExMemCnt[0] & (1<<11))) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFFFF00FF) | (val << 8)); + return; + case 0x040001A6: + if (!(ExMemCnt[0] & (1<<11))) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFF00FFFF) | (val << 16)); + return; + case 0x040001A7: + if (!(ExMemCnt[0] & (1<<11))) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0x00FFFFFF) | (val << 24)); + return; + case 0x040001A8: if (!(ExMemCnt[0] & (1<<11))) NDSCartSlot.SetROMCommand(0, val); return; case 0x040001A9: if (!(ExMemCnt[0] & (1<<11))) NDSCartSlot.SetROMCommand(1, val); return; case 0x040001AA: if (!(ExMemCnt[0] & (1<<11))) NDSCartSlot.SetROMCommand(2, val); return; @@ -3208,6 +3268,9 @@ void NDS::ARM9IOWrite16(u32 addr, u16 val) case 0x04000060: GPU.GPU3D.Write16(addr, val); return; + case 0x04000064: + case 0x04000066: GPU.GPU2D_A.Write16(addr, val); return; + case 0x04000068: case 0x0400006A: GPU.GPU2D_A.Write16(addr, val); return; @@ -3281,6 +3344,15 @@ void NDS::ARM9IOWrite16(u32 addr, u16 val) NDSCartSlot.WriteSPIData(val & 0xFF); return; + case 0x040001A4: + if (!(ExMemCnt[0] & (1<<11))) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFFFF0000) | val); + return; + case 0x040001A6: + if (!(ExMemCnt[0] & (1<<11))) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0x0000FFFF) | (val << 16)); + return; + case 0x040001A8: if (!(ExMemCnt[0] & (1<<11))) { @@ -3327,6 +3399,8 @@ void NDS::ARM9IOWrite16(u32 addr, u16 val) case 0x04000210: IE[0] = (IE[0] & 0xFFFF0000) | val; UpdateIRQ(0); return; case 0x04000212: IE[0] = (IE[0] & 0x0000FFFF) | (val << 16); UpdateIRQ(0); return; // TODO: what happens when writing to IF this way?? + case 0x04000214: IF[0] &= ~val; GPU.GPU3D.CheckFIFOIRQ(); UpdateIRQ(0); return; + case 0x04000216: IF[0] &= ~(val<<16); GPU.GPU3D.CheckFIFOIRQ(); UpdateIRQ(0); return; case 0x04000240: GPU.MapVRAM_AB(0, val & 0xFF); @@ -3551,10 +3625,8 @@ void NDS::ARM9IOWrite32(u32 addr, u32 val) case 0x04FFFA14: case 0x04FFFA18: { - bool appendLF = 0x04FFFA18 == addr; - NocashPrint(0, val); - if(appendLF) - Log(LogLevel::Debug, "\n"); + NocashPrint(0, val, 0x04FFFA18 == addr); + return; } @@ -3597,11 +3669,37 @@ u8 NDS::ARM7IORead8(u32 addr) case 0x04000138: return RTC.Read() & 0xFF; + case 0x040001A0: + if (ExMemCnt[0] & (1<<11)) + return NDSCartSlot.GetSPICnt() & 0xFF; + return 0; + case 0x040001A1: + if (ExMemCnt[0] & (1<<11)) + return NDSCartSlot.GetSPICnt() >> 8; + return 0; + case 0x040001A2: if (ExMemCnt[0] & (1<<11)) return NDSCartSlot.ReadSPIData(); return 0; + case 0x040001A4: + if (ExMemCnt[0] & (1<<11)) + return NDSCartSlot.GetROMCnt() & 0xFF; + return 0; + case 0x040001A5: + if (ExMemCnt[0] & (1<<11)) + return (NDSCartSlot.GetROMCnt() >> 8) & 0xFF; + return 0; + case 0x040001A6: + if (ExMemCnt[0] & (1<<11)) + return (NDSCartSlot.GetROMCnt() >> 16) & 0xFF; + return 0; + case 0x040001A7: + if (ExMemCnt[0] & (1<<11)) + return NDSCartSlot.GetROMCnt() >> 24; + return 0; + case 0x040001A8: if (ExMemCnt[0] & (1<<11)) return NDSCartSlot.GetROMCommand(0); @@ -3702,6 +3800,15 @@ u16 NDS::ARM7IORead16(u32 addr) case 0x040001A0: if (ExMemCnt[0] & (1<<11)) return NDSCartSlot.GetSPICnt(); return 0; case 0x040001A2: if (ExMemCnt[0] & (1<<11)) return NDSCartSlot.ReadSPIData(); return 0; + case 0x040001A4: + if (ExMemCnt[0] & (1<<11)) + return NDSCartSlot.GetROMCnt() & 0xFFFF; + return 0; + case 0x040001A6: + if (ExMemCnt[0] & (1<<11)) + return NDSCartSlot.GetROMCnt() >> 16; + return 0; + case 0x040001A8: if (ExMemCnt[0] & (1<<11)) return NDSCartSlot.GetROMCommand(0) | @@ -3889,6 +3996,23 @@ void NDS::ARM7IOWrite8(u32 addr, u8 val) NDSCartSlot.WriteSPIData(val); return; + case 0x040001A4: + if (ExMemCnt[0] & (1<<11)) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFFFFFF00) | val); + return; + case 0x040001A5: + if (ExMemCnt[0] & (1<<11)) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFFFF00FF) | (val << 8)); + return; + case 0x040001A6: + if (ExMemCnt[0] & (1<<11)) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFF00FFFF) | (val << 16)); + return; + case 0x040001A7: + if (ExMemCnt[0] & (1<<11)) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0x00FFFFFF) | (val << 24)); + return; + case 0x040001A8: if (ExMemCnt[0] & (1<<11)) NDSCartSlot.SetROMCommand(0, val); return; case 0x040001A9: if (ExMemCnt[0] & (1<<11)) NDSCartSlot.SetROMCommand(1, val); return; case 0x040001AA: if (ExMemCnt[0] & (1<<11)) NDSCartSlot.SetROMCommand(2, val); return; @@ -3994,6 +4118,15 @@ void NDS::ARM7IOWrite16(u32 addr, u16 val) NDSCartSlot.WriteSPIData(val & 0xFF); return; + case 0x040001A4: + if (ExMemCnt[0] & (1<<11)) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFFFFFF00) | val); + return; + case 0x040001A6: + if (ExMemCnt[0] & (1<<11)) + NDSCartSlot.WriteROMCnt((NDSCartSlot.GetROMCnt() & 0xFF00FFFF) | (val << 16)); + return; + case 0x040001A8: if (ExMemCnt[0] & (1<<11)) { diff --git a/src/NDS.h b/src/NDS.h index ea07a442..985b5cb8 100644 --- a/src/NDS.h +++ b/src/NDS.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -210,6 +210,7 @@ enum enum { GBAAddon_RAMExpansion = 1, + GBAAddon_RumblePak = 2, }; class SPU; @@ -227,8 +228,13 @@ private: #ifdef JIT_ENABLED bool EnableJIT; #endif +#ifdef GDBSTUB_ENABLED + bool EnableGDBStub = false; +#endif public: // TODO: Encapsulate the rest of these members + void* UserData; + int ConsoleType; int CurCPU; @@ -424,7 +430,7 @@ public: // TODO: Encapsulate the rest of these members u32 GetPC(u32 cpu) const; u64 GetSysClockCycles(int num); - void NocashPrint(u32 cpu, u32 addr); + void NocashPrint(u32 cpu, u32 addr, bool appendNewline = true); void MonitorARM9Jump(u32 addr); @@ -523,10 +529,11 @@ private: void SetWifiWaitCnt(u16 val); void SetGBASlotTimings(); void EnterSleepMode(); - template + template u32 RunFrame(); + public: - NDS(NDSArgs&& args) noexcept : NDS(std::move(args), 0) {} + NDS(NDSArgs&& args, void* userdata = nullptr) noexcept : NDS(std::move(args), 0, userdata) {} NDS() noexcept; virtual ~NDS() noexcept; NDS(const NDS&) = delete; @@ -536,7 +543,7 @@ public: // The frontend should set and unset this manually after creating and destroying the NDS object. [[deprecated("Temporary workaround until JIT code generation is revised to accommodate multiple NDS objects.")]] static NDS* Current; protected: - explicit NDS(NDSArgs&& args, int type) noexcept; + explicit NDS(NDSArgs&& args, int type, void* userdata) noexcept; virtual void DoSavestateExtra(Savestate* file) {} }; diff --git a/src/NDSCart.cpp b/src/NDSCart.cpp index a64d8a27..b0eef56a 100644 --- a/src/NDSCart.cpp +++ b/src/NDSCart.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -173,17 +173,18 @@ void NDSCartSlot::Key2_Encrypt(const u8* data, u32 len) noexcept } -CartCommon::CartCommon(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, melonDS::NDSCart::CartType type) : - CartCommon(CopyToUnique(rom, len), len, chipid, badDSiDump, romparams, type) +CartCommon::CartCommon(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, melonDS::NDSCart::CartType type, void* userdata) : + CartCommon(CopyToUnique(rom, len), len, chipid, badDSiDump, romparams, type, userdata) { } -CartCommon::CartCommon(std::unique_ptr&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, melonDS::NDSCart::CartType type) : +CartCommon::CartCommon(std::unique_ptr&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, melonDS::NDSCart::CartType type, void* userdata) : ROM(std::move(rom)), ROMLength(len), ChipID(chipid), ROMParams(romparams), - CartType(type) + CartType(type), + UserData(userdata) { memcpy(&Header, ROM.get(), sizeof(Header)); IsDSi = Header.IsDSi() && !badDSiDump; @@ -375,13 +376,13 @@ const NDSBanner* CartCommon::Banner() const return nullptr; } -CartRetail::CartRetail(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, melonDS::NDSCart::CartType type) : - CartRetail(CopyToUnique(rom, len), len, chipid, badDSiDump, romparams, std::move(sram), sramlen, type) +CartRetail::CartRetail(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata, melonDS::NDSCart::CartType type) : + CartRetail(CopyToUnique(rom, len), len, chipid, badDSiDump, romparams, std::move(sram), sramlen, userdata, type) { } -CartRetail::CartRetail(std::unique_ptr&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, melonDS::NDSCart::CartType type) : - CartCommon(std::move(rom), len, chipid, badDSiDump, romparams, type) +CartRetail::CartRetail(std::unique_ptr&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata, melonDS::NDSCart::CartType type) : + CartCommon(std::move(rom), len, chipid, badDSiDump, romparams, type, userdata) { u32 savememtype = ROMParams.SaveMemType <= 10 ? ROMParams.SaveMemType : 0; constexpr int sramlengths[] = @@ -469,7 +470,7 @@ void CartRetail::DoSavestate(Savestate* file) file->Var8(&SRAMStatus); if ((!file->Saving) && SRAM) - Platform::WriteNDSSave(SRAM.get(), SRAMLength, 0, SRAMLength); + Platform::WriteNDSSave(SRAM.get(), SRAMLength, 0, SRAMLength, UserData); } void CartRetail::SetSaveMemory(const u8* savedata, u32 savelen) @@ -478,7 +479,7 @@ void CartRetail::SetSaveMemory(const u8* savedata, u32 savelen) u32 len = std::min(savelen, SRAMLength); memcpy(SRAM.get(), savedata, len); - Platform::WriteNDSSave(savedata, len, 0, len); + Platform::WriteNDSSave(savedata, len, 0, len, UserData); } int CartRetail::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) @@ -594,7 +595,8 @@ u8 CartRetail::SRAMWrite_EEPROMTiny(u8 val, u32 pos, bool last) { SRAMStatus &= ~(1<<1); Platform::WriteNDSSave(SRAM.get(), SRAMLength, - (SRAMFirstAddr + ((SRAMCmd==0x0A)?0x100:0)) & 0x1FF, SRAMAddr-SRAMFirstAddr); + (SRAMFirstAddr + ((SRAMCmd==0x0A)?0x100:0)) & 0x1FF, SRAMAddr-SRAMFirstAddr, + UserData); } return 0; @@ -658,7 +660,8 @@ u8 CartRetail::SRAMWrite_EEPROM(u8 val, u32 pos, bool last) { SRAMStatus &= ~(1<<1); Platform::WriteNDSSave(SRAM.get(), SRAMLength, - SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); + SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr, + UserData); } return 0; @@ -715,7 +718,8 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) { SRAMStatus &= ~(1<<1); Platform::WriteNDSSave(SRAM.get(), SRAMLength, - SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); + SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr, + UserData); } return 0; @@ -752,7 +756,8 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) { SRAMStatus &= ~(1<<1); Platform::WriteNDSSave(SRAM.get(), SRAMLength, - SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); + SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr, + UserData); } return 0; @@ -798,7 +803,8 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) { SRAMStatus &= ~(1<<1); Platform::WriteNDSSave(SRAM.get(), SRAMLength, - SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); + SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr, + UserData); } return 0; @@ -821,7 +827,8 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) { SRAMStatus &= ~(1<<1); Platform::WriteNDSSave(SRAM.get(), SRAMLength, - SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); + SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr, + UserData); } return 0; @@ -832,13 +839,13 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) } } -CartRetailNAND::CartRetailNAND(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : - CartRetailNAND(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen) +CartRetailNAND::CartRetailNAND(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata) : + CartRetailNAND(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen, userdata) { } -CartRetailNAND::CartRetailNAND(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : - CartRetail(std::move(rom), len, chipid, false, romparams, std::move(sram), sramlen, CartType::RetailNAND) +CartRetailNAND::CartRetailNAND(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata) : + CartRetail(std::move(rom), len, chipid, false, romparams, std::move(sram), sramlen, userdata, CartType::RetailNAND) { BuildSRAMID(); } @@ -908,7 +915,7 @@ int CartRetailNAND::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, co if (SRAMLength && SRAMAddr < (SRAMBase+SRAMLength-0x20000)) { memcpy(&SRAM[SRAMAddr - SRAMBase], SRAMWriteBuffer, 0x800); - Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMAddr - SRAMBase, 0x800); + Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMAddr - SRAMBase, 0x800, UserData); } SRAMAddr = 0; @@ -1064,8 +1071,8 @@ void CartRetailNAND::BuildSRAMID() } -CartRetailIR::CartRetailIR(const u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : - CartRetailIR(CopyToUnique(rom, len), len, chipid, irversion, badDSiDump, romparams, std::move(sram), sramlen) +CartRetailIR::CartRetailIR(const u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata) : + CartRetailIR(CopyToUnique(rom, len), len, chipid, irversion, badDSiDump, romparams, std::move(sram), sramlen, userdata) { } @@ -1077,9 +1084,10 @@ CartRetailIR::CartRetailIR( bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, - u32 sramlen + u32 sramlen, + void* userdata ) : - CartRetail(std::move(rom), len, chipid, badDSiDump, romparams, std::move(sram), sramlen, CartType::RetailIR), + CartRetail(std::move(rom), len, chipid, badDSiDump, romparams, std::move(sram), sramlen, userdata, CartType::RetailIR), IRVersion(irversion) { } @@ -1122,13 +1130,13 @@ u8 CartRetailIR::SPIWrite(u8 val, u32 pos, bool last) return 0; } -CartRetailBT::CartRetailBT(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : - CartRetailBT(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen) +CartRetailBT::CartRetailBT(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata) : + CartRetailBT(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen, userdata) { } -CartRetailBT::CartRetailBT(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : - CartRetail(std::move(rom), len, chipid, false, romparams, std::move(sram), sramlen, CartType::RetailBT) +CartRetailBT::CartRetailBT(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata) : + CartRetail(std::move(rom), len, chipid, false, romparams, std::move(sram), sramlen, userdata, CartType::RetailBT) { Log(LogLevel::Info,"POKETYPE CART\n"); } @@ -1150,12 +1158,12 @@ u8 CartRetailBT::SPIWrite(u8 val, u32 pos, bool last) } -CartSD::CartSD(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard) : - CartSD(CopyToUnique(rom, len), len, chipid, romparams, std::move(sdcard)) +CartSD::CartSD(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional&& sdcard) : + CartSD(CopyToUnique(rom, len), len, chipid, romparams, userdata, std::move(sdcard)) {} -CartSD::CartSD(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard) : - CartCommon(std::move(rom), len, chipid, false, romparams, CartType::Homebrew), +CartSD::CartSD(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional&& sdcard) : + CartCommon(std::move(rom), len, chipid, false, romparams, CartType::Homebrew, userdata), SD(std::move(sdcard)) { sdcard = std::nullopt; @@ -1306,12 +1314,12 @@ void CartSD::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) const memcpy(data+offset, ROM.get()+addr, len); } -CartHomebrew::CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard) : - CartSD(rom, len, chipid, romparams, std::move(sdcard)) +CartHomebrew::CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional&& sdcard) : + CartSD(rom, len, chipid, romparams, userdata, std::move(sdcard)) {} -CartHomebrew::CartHomebrew(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard) : - CartSD(std::move(rom), len, chipid, romparams, std::move(sdcard)) +CartHomebrew::CartHomebrew(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional&& sdcard) : + CartSD(std::move(rom), len, chipid, romparams, userdata, std::move(sdcard)) {} CartHomebrew::~CartHomebrew() = default; @@ -1565,12 +1573,12 @@ void NDSCartSlot::DecryptSecureArea(u8* out) noexcept } } -std::unique_ptr ParseROM(const u8* romdata, u32 romlen, std::optional&& args) +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, void* userdata, std::optional&& args) { - return ParseROM(CopyToUnique(romdata, romlen), romlen, std::move(args)); + return ParseROM(CopyToUnique(romdata, romlen), romlen, userdata, std::move(args)); } -std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::optional&& args) +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, void* userdata, std::optional&& args) { if (romdata == nullptr) { @@ -1659,21 +1667,21 @@ std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen if (homebrew) { std::optional sdcard = args && args->SDCard ? std::make_optional(std::move(*args->SDCard)) : std::nullopt; - cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sdcard)); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, userdata, std::move(sdcard)); } else if (gametitle[0] == 0 && !strncmp("SD/TF-NDS", gametitle + 1, 9) && gamecode == 0x414D5341) { std::optional sdcard = args && args->SDCard ? std::make_optional(std::move(*args->SDCard)) : std::nullopt; - cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, CartR4TypeR4, CartR4LanguageEnglish, std::move(sdcard)); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, CartR4TypeR4, CartR4LanguageEnglish, userdata, std::move(sdcard)); } else if (cartid & 0x08000000) - cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen, userdata); else if (irversion != 0) - cart = std::make_unique(std::move(cartrom), cartromsize, cartid, irversion, badDSiDump, romparams, std::move(sram), sramlen); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, irversion, badDSiDump, romparams, std::move(sram), sramlen, userdata); else if ((gamecode & 0xFFFFFF) == 0x505A55) // UZPx - cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen, userdata); else - cart = std::make_unique(std::move(cartrom), cartromsize, cartid, badDSiDump, romparams, std::move(sram), sramlen); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, badDSiDump, romparams, std::move(sram), sramlen, userdata); args = std::nullopt; return cart; diff --git a/src/NDSCart.h b/src/NDSCart.h index 2f6a3be5..3c4bb4f5 100644 --- a/src/NDSCart.h +++ b/src/NDSCart.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -76,8 +76,8 @@ struct NDSCartArgs class CartCommon { public: - CartCommon(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, CartType type); - CartCommon(std::unique_ptr&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, CartType type); + CartCommon(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, CartType type, void* userdata); + CartCommon(std::unique_ptr&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, CartType type, void* userdata); virtual ~CartCommon(); [[nodiscard]] u32 Type() const { return CartType; }; @@ -111,6 +111,8 @@ public: protected: void ReadROM(u32 addr, u32 len, u8* data, u32 offset) const; + void* UserData; + std::unique_ptr ROM = nullptr; u32 ROMLength = 0; u32 ChipID = 0; @@ -139,6 +141,7 @@ public: ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, + void* userdata, melonDS::NDSCart::CartType type = CartType::Retail ); CartRetail( @@ -148,6 +151,7 @@ public: ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, + void* userdata, melonDS::NDSCart::CartType type = CartType::Retail ); ~CartRetail() override; @@ -187,8 +191,8 @@ protected: class CartRetailNAND : public CartRetail { public: - CartRetailNAND(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); - CartRetailNAND(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); + CartRetailNAND(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata); + CartRetailNAND(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata); ~CartRetailNAND() override; void Reset() override; @@ -216,8 +220,8 @@ private: class CartRetailIR : public CartRetail { public: - CartRetailIR(const u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); - CartRetailIR(std::unique_ptr&& rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); + CartRetailIR(const u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata); + CartRetailIR(std::unique_ptr&& rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata); ~CartRetailIR() override; void Reset() override; @@ -235,8 +239,8 @@ private: class CartRetailBT : public CartRetail { public: - CartRetailBT(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); - CartRetailBT(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); + CartRetailBT(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata); + CartRetailBT(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata); ~CartRetailBT() override; u8 SPIWrite(u8 val, u32 pos, bool last) override; @@ -246,8 +250,8 @@ public: class CartSD : public CartCommon { public: - CartSD(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard = std::nullopt); - CartSD(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard = std::nullopt); + CartSD(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional&& sdcard = std::nullopt); + CartSD(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional&& sdcard = std::nullopt); ~CartSD() override; [[nodiscard]] const std::optional& GetSDCard() const noexcept { return SD; } @@ -288,8 +292,8 @@ protected: class CartHomebrew : public CartSD { public: - CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard = std::nullopt); - CartHomebrew(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard = std::nullopt); + CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional&& sdcard = std::nullopt); + CartHomebrew(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional&& sdcard = std::nullopt); ~CartHomebrew() override; void Reset() override; @@ -322,7 +326,7 @@ enum CartR4Language class CartR4 : public CartSD { public: - CartR4(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage, + CartR4(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage, void* userdata, std::optional&& sdcard = std::nullopt); ~CartR4() override; @@ -461,8 +465,8 @@ private: /// If not given, the cart will not have an SD card. /// @returns A \c NDSCart::CartCommon object representing the parsed ROM, /// or \c nullptr if the ROM data couldn't be parsed. -std::unique_ptr ParseROM(const u8* romdata, u32 romlen, std::optional&& args = std::nullopt); -std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::optional&& args = std::nullopt); +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, void* userdata = nullptr, std::optional&& args = std::nullopt); +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, void* userdata = nullptr, std::optional&& args = std::nullopt); } #endif diff --git a/src/NDSCartR4.cpp b/src/NDSCartR4.cpp index 8497f556..46fbeb6a 100644 --- a/src/NDSCartR4.cpp +++ b/src/NDSCartR4.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -67,9 +67,9 @@ static void DecryptR4Sector(u8* dest, u8* src, u16 key1) } } -CartR4::CartR4(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage, +CartR4::CartR4(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage, void* userdata, std::optional&& sdcard) - : CartSD(std::move(rom), len, chipid, romparams, std::move(sdcard)) + : CartSD(std::move(rom), len, chipid, romparams, userdata, std::move(sdcard)) { InitStatus = 0; R4CartType = ctype; diff --git a/src/NDS_Header.h b/src/NDS_Header.h index de75f7c7..80d5ced6 100644 --- a/src/NDS_Header.h +++ b/src/NDS_Header.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2022 melonDS team, WaluigiWare64 + Copyright 2016-2024 melonDS team, WaluigiWare64 This file is part of melonDS. @@ -39,6 +39,8 @@ enum RegionMask : u32 RegionFree = 0xFFFFFFFF, }; +constexpr u32 DSiWareTitleIDHigh = 0x00030004; + // Consult GBATEK for info on what these are struct NDSHeader { @@ -198,8 +200,9 @@ struct NDSHeader u8 HeaderSignature[128]; // RSA-SHA1 across 0x000..0xDFF - /// @return \c true if this header represents a DSi title - /// (either a physical cartridge or a DSiWare title). + /// @return \c true if this header represents a title + /// that is DSi-exclusive (including DSiWare) + /// or DSi-enhanced (including cartridges). [[nodiscard]] bool IsDSi() const { return (UnitCode & 0x02) != 0; } [[nodiscard]] u32 GameCodeAsU32() const { return (u32)GameCode[3] << 24 | @@ -213,7 +216,7 @@ struct NDSHeader } /// @return \c true if this header represents a DSiWare title. - [[nodiscard]] bool IsDSiWare() const { return IsDSi() && DSiRegionStart == 0; } + [[nodiscard]] bool IsDSiWare() const { return IsDSi() && DSiTitleIDHigh == DSiWareTitleIDHigh; } }; static_assert(sizeof(NDSHeader) == 4096, "NDSHeader is not 4096 bytes!"); diff --git a/src/NonStupidBitfield.h b/src/NonStupidBitfield.h index 4a5550f1..7332394b 100644 --- a/src/NonStupidBitfield.h +++ b/src/NonStupidBitfield.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2022 melonDS team, RSDuck + Copyright 2016-2024 melonDS team, RSDuck This file is part of melonDS. @@ -26,11 +26,38 @@ #include #include +namespace melonDS +{ + +inline u64 GetRangedBitMask(u32 idx, u32 startBit, u32 bitsCount) +{ + u32 startEntry = startBit >> 6; + u64 entriesCount = ((startBit + bitsCount + 0x3F) >> 6) - startEntry; + + if (entriesCount > 1) + { + if (idx == startEntry) + return 0xFFFFFFFFFFFFFFFF << (startBit & 0x3F); + if (((startBit + bitsCount) & 0x3F) && idx == startEntry + entriesCount - 1) + return ~(0xFFFFFFFFFFFFFFFF << ((startBit + bitsCount) & 0x3F)); + + return 0xFFFFFFFFFFFFFFFF; + } + else if (idx == startEntry) + { + return bitsCount == 64 + ? 0xFFFFFFFFFFFFFFFF + : ((1ULL << bitsCount) - 1) << (startBit & 0x3F); + } + else + { + return 0; + } +} + // like std::bitset but less stupid and optimised for // our use case (keeping track of memory invalidations) -namespace melonDS -{ template struct NonStupidBitField { @@ -166,6 +193,11 @@ struct NonStupidBitField return Ref{*this, idx}; } + bool operator[](u32 idx) const + { + return Data[idx >> 6] & (1ULL << (idx & 0x3F)); + } + void SetRange(u32 startBit, u32 bitsCount) { u32 startEntry = startBit >> 6; @@ -187,6 +219,26 @@ struct NonStupidBitField } } + int Min() const + { + for (int i = 0; i < DataLength; i++) + { + if (Data[i]) + return i * 64 + __builtin_ctzll(Data[i]); + } + return -1; + } + + int Max() const + { + for (int i = DataLength - 1; i >= 0; i--) + { + if (Data[i]) + return i * 64 + (63 - __builtin_clzll(Data[i])); + } + return -1; + } + NonStupidBitField& operator|=(const NonStupidBitField& other) { for (u32 i = 0; i < DataLength; i++) @@ -195,6 +247,7 @@ struct NonStupidBitField } return *this; } + NonStupidBitField& operator&=(const NonStupidBitField& other) { for (u32 i = 0; i < DataLength; i++) @@ -203,6 +256,20 @@ struct NonStupidBitField } return *this; } + + operator bool() const + { + for (int i = 0; i < DataLength - 1; i++) + { + if (Data[i]) + return true; + } + if (Data[DataLength-1] & ((Size&0x3F) ? ~(0xFFFFFFFFFFFFFFFF << (Size&0x3F)) : 0xFFFFFFFFFFFFFFFF)) + { + return true; + } + return false; + } }; } diff --git a/src/OpenGLSupport.cpp b/src/OpenGLSupport.cpp index 0eb05c53..2740e157 100644 --- a/src/OpenGLSupport.cpp +++ b/src/OpenGLSupport.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -18,6 +18,14 @@ #include "OpenGLSupport.h" +#include +#include + +#include + +#define XXH_STATIC_LINKING_ONLY +#include "xxhash/xxhash.h" + namespace melonDS { @@ -27,9 +35,158 @@ using Platform::LogLevel; namespace OpenGL { -bool BuildShaderProgram(const char* vs, const char* fs, GLuint* ids, const char* name) +struct ShaderCacheEntry +{ + u32 Length; + u8* Data; + u32 BinaryFormat; + + ShaderCacheEntry(u8* data, u32 length, u32 binaryFmt) + : Length(length), Data(data), BinaryFormat(binaryFmt) + { + assert(data != nullptr); + } + + ShaderCacheEntry(const ShaderCacheEntry&) = delete; + ShaderCacheEntry(ShaderCacheEntry&& other) + { + Data = other.Data; + Length = other.Length; + BinaryFormat = other.BinaryFormat; + + other.Data = nullptr; + other.Length = 0; + other.BinaryFormat = 0; + } + + ~ShaderCacheEntry() + { + if (Data) // check whether it was moved + delete[] Data; + } +}; + +std::unordered_map ShaderCache; +std::vector NewShaders; + +constexpr u32 ShaderCacheMagic = 0x11CAC4E1; +constexpr u32 ShaderCacheVersion = 1; + +void LoadShaderCache() +{ + // for now the shader cache only contains only compute shaders + // because they take the longest to compile + Platform::FileHandle* file = Platform::OpenLocalFile("shadercache", Platform::FileMode::Read); + if (file == nullptr) + { + Log(LogLevel::Error, "Could not find shader cache\n"); + return; + } + + u32 magic, version, numPrograms; + if (Platform::FileRead(&magic, 4, 1, file) != 1 || magic != ShaderCacheMagic) + { + Log(LogLevel::Error, "Shader cache file has invalid magic\n"); + goto fileInvalid; + } + + if (Platform::FileRead(&version, 4, 1, file) != 1 || version != ShaderCacheVersion) + { + Log(LogLevel::Error, "Shader cache file has bad version\n"); + goto fileInvalid; + } + + if (Platform::FileRead(&numPrograms, 4, 1, file) != 1) + { + Log(LogLevel::Error, "Shader cache file invalid program count\n"); + goto fileInvalid; + } + + // not the best approach, because once changes pile up + // we read and overwrite the old files + for (u32 i = 0; i < numPrograms; i++) + { + int error = 3; + + u32 length, binaryFormat; + u64 sourceHash; + error -= Platform::FileRead(&sourceHash, 8, 1, file); + error -= Platform::FileRead(&length, 4, 1, file); + error -= Platform::FileRead(&binaryFormat, 4, 1, file); + + if (error != 0) + { + Log(LogLevel::Error, "Invalid shader cache entry\n"); + goto fileInvalid; + } + + u8* data = new u8[length]; + if (Platform::FileRead(data, length, 1, file) != 1) + { + Log(LogLevel::Error, "Could not read shader cache entry data\n"); + delete[] data; + goto fileInvalid; + } + + ShaderCache.erase(sourceHash); + ShaderCache.emplace(sourceHash, ShaderCacheEntry(data, length, binaryFormat)); + } + +fileInvalid: + Platform::CloseFile(file); +} + +void SaveShaderCache() +{ + Platform::FileHandle* file = Platform::OpenLocalFile("shadercache", Platform::FileMode::ReadWrite); + + if (file == nullptr) + { + Log(LogLevel::Error, "Could not open or create shader cache file\n"); + return; + } + + int written = 3; + u32 magic = ShaderCacheMagic, version = ShaderCacheVersion, numPrograms = ShaderCache.size(); + written -= Platform::FileWrite(&magic, 4, 1, file); + written -= Platform::FileWrite(&version, 4, 1, file); + written -= Platform::FileWrite(&numPrograms, 4, 1, file); + + if (written != 0) + { + Log(LogLevel::Error, "Could not write shader cache header\n"); + goto writeError; + } + + Platform::FileSeek(file, 0, Platform::FileSeekOrigin::End); + + printf("new shaders %zu\n", NewShaders.size()); + + for (u64 newShader : NewShaders) + { + int error = 4; + auto it = ShaderCache.find(newShader); + + error -= Platform::FileWrite(&it->first, 8, 1, file); + error -= Platform::FileWrite(&it->second.Length, 4, 1, file); + error -= Platform::FileWrite(&it->second.BinaryFormat, 4, 1, file); + error -= Platform::FileWrite(it->second.Data, it->second.Length, 1, file); + + if (error != 0) + { + Log(LogLevel::Error, "Could not insert new shader cache entry\n"); + goto writeError; + } + } + +writeError: + Platform::CloseFile(file); + + NewShaders.clear(); +} + +bool CompilerShader(GLuint& id, const std::string& source, const std::string& name, const std::string& type) { - int len; int res; if (!glCreateShader) @@ -38,61 +195,32 @@ bool BuildShaderProgram(const char* vs, const char* fs, GLuint* ids, const char* return false; } - ids[0] = glCreateShader(GL_VERTEX_SHADER); - len = strlen(vs); - glShaderSource(ids[0], 1, &vs, &len); - glCompileShader(ids[0]); + const char* sourceC = source.c_str(); + int len = source.length(); + glShaderSource(id, 1, &sourceC, &len); - glGetShaderiv(ids[0], GL_COMPILE_STATUS, &res); + glCompileShader(id); + + glGetShaderiv(id, GL_COMPILE_STATUS, &res); if (res != GL_TRUE) { - glGetShaderiv(ids[0], GL_INFO_LOG_LENGTH, &res); + glGetShaderiv(id, GL_INFO_LOG_LENGTH, &res); if (res < 1) res = 1024; char* log = new char[res+1]; - glGetShaderInfoLog(ids[0], res+1, NULL, log); - Log(LogLevel::Error, "OpenGL: failed to compile vertex shader %s: %s\n", name, log); - Log(LogLevel::Debug, "shader source:\n--\n%s\n--\n", vs); + glGetShaderInfoLog(id, res+1, NULL, log); + Log(LogLevel::Error, "OpenGL: failed to compile %s shader %s: %s\n", type.c_str(), name.c_str(), log); + Log(LogLevel::Debug, "shader source:\n--\n%s\n--\n", source.c_str()); delete[] log; - glDeleteShader(ids[0]); + glDeleteShader(id); return false; } - ids[1] = glCreateShader(GL_FRAGMENT_SHADER); - len = strlen(fs); - glShaderSource(ids[1], 1, &fs, &len); - glCompileShader(ids[1]); - - glGetShaderiv(ids[1], GL_COMPILE_STATUS, &res); - if (res != GL_TRUE) - { - glGetShaderiv(ids[1], GL_INFO_LOG_LENGTH, &res); - if (res < 1) res = 1024; - char* log = new char[res+1]; - glGetShaderInfoLog(ids[1], res+1, NULL, log); - Log(LogLevel::Error, "OpenGL: failed to compile fragment shader %s: %s\n", name, log); - //printf("shader source:\n--\n%s\n--\n", fs); - delete[] log; - - Platform::FileHandle* logf = Platform::OpenFile("shaderfail.log", Platform::FileMode::WriteText); - Platform::FileWrite(fs, len+1, 1, logf); - Platform::CloseFile(logf); - - glDeleteShader(ids[0]); - glDeleteShader(ids[1]); - - return false; - } - - ids[2] = glCreateProgram(); - glAttachShader(ids[2], ids[0]); - glAttachShader(ids[2], ids[1]); - return true; } -bool LinkShaderProgram(GLuint* ids) +bool LinkProgram(GLuint& result, GLuint* ids, int numIds) { int res; @@ -102,46 +230,132 @@ bool LinkShaderProgram(GLuint* ids) return false; } - glLinkProgram(ids[2]); + for (int i = 0; i < numIds; i++) + { + glAttachShader(result, ids[i]); + } - glDetachShader(ids[2], ids[0]); - glDetachShader(ids[2], ids[1]); + glLinkProgram(result); - glDeleteShader(ids[0]); - glDeleteShader(ids[1]); + for (int i = 0; i < numIds; i++) + glDetachShader(result, ids[i]); - glGetProgramiv(ids[2], GL_LINK_STATUS, &res); + glGetProgramiv(result, GL_LINK_STATUS, &res); if (res != GL_TRUE) { - glGetProgramiv(ids[2], GL_INFO_LOG_LENGTH, &res); + glGetProgramiv(result, GL_INFO_LOG_LENGTH, &res); if (res < 1) res = 1024; char* log = new char[res+1]; - glGetProgramInfoLog(ids[2], res+1, NULL, log); + glGetProgramInfoLog(result, res+1, NULL, log); Log(LogLevel::Error, "OpenGL: failed to link shader program: %s\n", log); delete[] log; - glDeleteProgram(ids[2]); - return false; } return true; } -void DeleteShaderProgram(GLuint* ids) +bool CompileComputeProgram(GLuint& result, const std::string& source, const std::string& name) { - if (glDeleteProgram) - { // If OpenGL isn't loaded, then there's no shader program to delete - glDeleteProgram(ids[2]); + result = glCreateProgram(); + + /*u64 sourceHash = XXH64(source.data(), source.size(), 0); + auto it = ShaderCache.find(sourceHash); + if (it != ShaderCache.end()) + { + glProgramBinary(result, it->second.BinaryFormat, it->second.Data, it->second.Length); + + GLint linkStatus; + glGetProgramiv(result, GL_LINK_STATUS, &linkStatus); + if (linkStatus == GL_TRUE) + { + Log(LogLevel::Info, "Restored shader %s from cache\n", name.c_str()); + return true; + } + else + { + } + }*/ + Log(LogLevel::Error, "Shader %s from cache was rejected\n", name.c_str()); + + GLuint shader; + bool linkingSucess = false; + + if (!glCreateShader || !glDeleteShader) + goto error; + + shader = glCreateShader(GL_COMPUTE_SHADER); + + if (!CompilerShader(shader, source, name, "compute")) + goto error; + + linkingSucess = LinkProgram(result, &shader, 1); + +error: + glDeleteShader(shader); + + if (!linkingSucess) + { + glDeleteProgram(result); } + /*else + { + GLint length; + GLenum format; + glGetProgramiv(result, GL_PROGRAM_BINARY_LENGTH, &length); + + u8* buffer = new u8[length]; + glGetProgramBinary(result, length, nullptr, &format, buffer); + + ShaderCache.emplace(sourceHash, ShaderCacheEntry(buffer, length, format)); + NewShaders.push_back(sourceHash); + }*/ + + return linkingSucess; } -void UseShaderProgram(GLuint* ids) +bool CompileVertexFragmentProgram(GLuint& result, + const std::string& vs, const std::string& fs, + const std::string& name, + const std::initializer_list& vertexInAttrs, + const std::initializer_list& fragmentOutAttrs) { - if (glUseProgram) - { // If OpenGL isn't loaded, then there's no shader program to use - glUseProgram(ids[2]); + GLuint shaders[2] = + { + glCreateShader(GL_VERTEX_SHADER), + glCreateShader(GL_FRAGMENT_SHADER) + }; + result = glCreateProgram(); + + bool linkingSucess = false; + + if (!CompilerShader(shaders[0], vs, name, "vertex")) + goto error; + + if (!CompilerShader(shaders[1], fs, name, "fragment")) + goto error; + + + for (const AttributeTarget& target : vertexInAttrs) + { + glBindAttribLocation(result, target.Location, target.Name); } + for (const AttributeTarget& target : fragmentOutAttrs) + { + glBindFragDataLocation(result, target.Location, target.Name); + } + + linkingSucess = LinkProgram(result, shaders, 2); + +error: + glDeleteShader(shaders[1]); + glDeleteShader(shaders[0]); + + if (!linkingSucess) + glDeleteProgram(result); + + return linkingSucess; } } diff --git a/src/OpenGLSupport.h b/src/OpenGLSupport.h index ee5b5043..b426597d 100644 --- a/src/OpenGLSupport.h +++ b/src/OpenGLSupport.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -28,10 +28,23 @@ namespace melonDS::OpenGL { -bool BuildShaderProgram(const char* vs, const char* fs, GLuint* ids, const char* name); -bool LinkShaderProgram(GLuint* ids); -void DeleteShaderProgram(GLuint* ids); -void UseShaderProgram(GLuint* ids); +void LoadShaderCache(); +void SaveShaderCache(); + +struct AttributeTarget +{ + const char* Name; + u32 Location; +}; + + +bool CompileVertexFragmentProgram(GLuint& result, + const std::string& vs, const std::string& fs, + const std::string& name, + const std::initializer_list& vertexInAttrs, + const std::initializer_list& fragmentOutAttrs); + +bool CompileComputeProgram(GLuint& result, const std::string& source, const std::string& name); } diff --git a/src/Platform.h b/src/Platform.h index 21b3d465..bef66593 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -31,14 +31,6 @@ class Firmware; namespace Platform { -void Init(int argc, char** argv); - -/** - * Frees all resources that were allocated in \c Init - * or by any other \c Platform function. - */ -void DeInit(); - enum StopReason { /** * The emulator stopped for some unspecified reason. @@ -77,20 +69,8 @@ enum StopReason { * Frontends should not call this directly; * use \c NDS::Stop instead. */ -void SignalStop(StopReason reason); +void SignalStop(StopReason reason, void* userdata); -/** - * @returns The ID of the running melonDS instance if running in local multiplayer mode, - * or 0 if not. - */ -int InstanceID(); - -/** - * @returns A suffix that should be appended to all instance-specific paths - * if running in local multiplayer mode, - * or the empty string if not. - */ -std::string InstanceFileSuffix(); /** * Denotes how a file will be opened and accessed. @@ -136,6 +116,11 @@ enum FileMode : unsigned { */ Text = 0b01'00'00, + /** + * Opens a file in append mode. + */ + Append = 0b10'00'00, + /** * Opens a file for reading and writing. * Equivalent to Read | Write. @@ -183,6 +168,9 @@ enum class FileSeekOrigin */ struct FileHandle; +// retrieves the path to a local file, without opening the file +std::string GetLocalFilePath(const std::string& filename); + // Simple fopen() wrapper that supports UTF8. // Can be optionally restricted to only opening a file that already exists. FileHandle* OpenFile(const std::string& path, FileMode mode); @@ -201,6 +189,13 @@ FileHandle* OpenLocalFile(const std::string& path, FileMode mode); bool FileExists(const std::string& name); bool LocalFileExists(const std::string& name); +// Returns true if we have permission to write to the file. +// Warning: Also creates the file if not present! +bool CheckFileWritable(const std::string& filepath); + +// Same as above (CheckFileWritable()) but for local files. +bool CheckLocalFileWritable(const std::string& filepath); + /** Close a file opened with \c OpenFile. * @returns \c true if the file was closed successfully, false otherwise. * @post \c file is no longer valid and should not be used. @@ -261,6 +256,9 @@ Semaphore* Semaphore_Create(); void Semaphore_Free(Semaphore* sema); void Semaphore_Reset(Semaphore* sema); void Semaphore_Wait(Semaphore* sema); +/// Waits for the semaphore to be signaled, or until the timeout (in milliseconds) expires. +/// If the timeout is 0, then don't wait; return immediately if the semaphore is not signaled. +bool Semaphore_TryWait(Semaphore* sema, int timeout_ms = 0); void Semaphore_Post(Semaphore* sema, int count = 1); struct Mutex; @@ -272,45 +270,45 @@ bool Mutex_TryLock(Mutex* mutex); void Sleep(u64 usecs); +u64 GetMSCount(); +u64 GetUSCount(); + // functions called when the NDS or GBA save files need to be written back to storage // savedata and savelen are always the entire save memory buffer and its full length // writeoffset and writelen indicate which part of the memory was altered -void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen); -void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen); +void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen, void* userdata); +void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen, void* userdata); /// Called when the firmware needs to be written back to storage, /// after one of the supported write commands finishes execution. /// @param firmware The firmware that was just written. /// @param writeoffset The offset of the first byte that was written to firmware. /// @param writelen The number of bytes that were written to firmware. -void WriteFirmware(const Firmware& firmware, u32 writeoffset, u32 writelen); +void WriteFirmware(const Firmware& firmware, u32 writeoffset, u32 writelen, void* userdata); // called when the RTC date/time is changed and the frontend might need to take it into account -void WriteDateTime(int year, int month, int day, int hour, int minute, int second); +void WriteDateTime(int year, int month, int day, int hour, int minute, int second, void* userdata); // local multiplayer comm interface // packet type: DS-style TX header (12 bytes) + original 802.11 frame -bool MP_Init(); -void MP_DeInit(); -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); +void MP_Begin(void* userdata); +void MP_End(void* userdata); +int MP_SendPacket(u8* data, int len, u64 timestamp, void* userdata); +int MP_RecvPacket(u8* data, u64* timestamp, void* userdata); +int MP_SendCmd(u8* data, int len, u64 timestamp, void* userdata); +int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid, void* userdata); +int MP_SendAck(u8* data, int len, u64 timestamp, void* userdata); +int MP_RecvHostPacket(u8* data, u64* timestamp, void* userdata); +u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask, void* userdata); -// LAN comm interface +// network comm interface // packet type: Ethernet (802.3) -bool LAN_Init(); -void LAN_DeInit(); -int LAN_SendPacket(u8* data, int len); -int LAN_RecvPacket(u8* data); +int Net_SendPacket(u8* data, int len, void* userdata); +int Net_RecvPacket(u8* data, void* userdata); +using SendPacketCallback = std::function; // interface for camera emulation @@ -318,9 +316,20 @@ int LAN_RecvPacket(u8* data); // 0 = DSi outer camera // 1 = DSi inner camera // other values reserved for future camera addon emulation -void Camera_Start(int num); -void Camera_Stop(int num); -void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv); +void Camera_Start(int num, void* userdata); +void Camera_Stop(int num, void* userdata); +void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv, void* userdata); + +// interface for addon inputs + +// Called by the DS Rumble Pak emulation to start the necessary +// rumble effects on the connected game controller, if available. +// @param len The duration of the controller rumble effect in milliseconds. +void Addon_RumbleStart(u32 len, void* userdata); + +// Called by the DS Rumble Pak emulation to stop any necessary +// rumble effects on the connected game controller, if available. +void Addon_RumbleStop(void* userdata); struct DynamicLibrary; diff --git a/src/PlatformOGL.h b/src/PlatformOGL.h index bc047807..120e8690 100644 --- a/src/PlatformOGL.h +++ b/src/PlatformOGL.h @@ -1,9 +1,16 @@ #ifndef PLATFORMOGL_H #define PLATFORMOGL_H -// if you don't wanna use glad for your platform -// add your header here! +// If you don't wanna use glad for your platform, +// define MELONDS_GL_HEADER to the path of some other header +// that pulls in the necessary OpenGL declarations. +// Make sure to include quotes or angle brackets as needed, +// and that all targets get the same MELONDS_GL_HEADER definition. -#include "frontend/glad/glad.h" +#ifndef MELONDS_GL_HEADER +#define MELONDS_GL_HEADER "\"frontend/glad/glad.h\"" +#endif + +#include MELONDS_GL_HEADER #endif diff --git a/src/ROMList.cpp b/src/ROMList.cpp index 3ff771f8..a00b0b22 100644 --- a/src/ROMList.cpp +++ b/src/ROMList.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -1830,7 +1830,7 @@ const ROMListEntry ROMList[] = {0x45564E43, 0x10000000, 0x00000005}, {0x45564F59, 0x00800000, 0x00000001}, {0x45565041, 0x00800000, 0x00000002}, - {0x45565042, 0x00800000, 0x00000004}, + {0x45565042, 0x00800000, 0x00000002}, {0x45565043, 0x04000000, 0x00000002}, {0x45565056, 0x04000000, 0x00000002}, {0x45565059, 0x04000000, 0x00000001}, @@ -6804,4 +6804,4 @@ const ROMListEntry ROMList[] = const size_t ROMListEntryCount = sizeof(ROMList) / sizeof(ROMListEntry); -} \ No newline at end of file +} diff --git a/src/ROMList.h b/src/ROMList.h index 82ee0ccf..e27ee728 100644 --- a/src/ROMList.h +++ b/src/ROMList.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/RTC.cpp b/src/RTC.cpp index d8219df1..24e00de7 100644 --- a/src/RTC.cpp +++ b/src/RTC.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -40,7 +40,7 @@ RTC::RTC(melonDS::NDS& nds) : NDS(nds) // indicate the power was off // this will be changed if a previously saved RTC state is loaded - State.StatusReg1 = 0x80; + State.StatusReg1 = 0x80 | (1<<1); } RTC::~RTC() @@ -602,7 +602,7 @@ void RTC::SaveDateTime() { int y, m, d, h, i, s; GetDateTime(y, m, d, h, i, s); - Platform::WriteDateTime(y, m, d, h, i, s); + Platform::WriteDateTime(y, m, d, h, i, s, NDS.UserData); } void RTC::CmdRead() @@ -943,4 +943,4 @@ void RTC::Write(u16 val, bool byte) IO = (IO & 0x0001) | (val & 0xFFFE); } -} \ No newline at end of file +} diff --git a/src/RTC.h b/src/RTC.h index 1477e0eb..7c3671ce 100644 --- a/src/RTC.h +++ b/src/RTC.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/SPI.cpp b/src/SPI.cpp index 2aa915c6..6ab94c3a 100644 --- a/src/SPI.cpp +++ b/src/SPI.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -260,7 +260,7 @@ void FirmwareMem::Release() // Request that the start of the Wi-fi/userdata settings region // through the end of the firmware blob be flushed to disk - Platform::WriteFirmware(FirmwareData, wifioffset, FirmwareData.Length() - wifioffset); + Platform::WriteFirmware(FirmwareData, wifioffset, FirmwareData.Length() - wifioffset, NDS.UserData); } SPIDevice::Release(); diff --git a/src/SPI.h b/src/SPI.h index 7ed889a4..350708ef 100644 --- a/src/SPI.h +++ b/src/SPI.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/SPI_Firmware.cpp b/src/SPI_Firmware.cpp index 89c8fa61..472500af 100644 --- a/src/SPI_Firmware.cpp +++ b/src/SPI_Firmware.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -80,6 +80,7 @@ void Firmware::WifiAccessPoint::UpdateChecksum() Firmware::ExtendedWifiAccessPoint::ExtendedWifiAccessPoint() { + memset(Bytes, 0, sizeof(Bytes)); Data.Base = WifiAccessPoint(); UpdateChecksum(); @@ -93,6 +94,7 @@ void Firmware::ExtendedWifiAccessPoint::UpdateChecksum() Firmware::FirmwareHeader::FirmwareHeader(int consoletype) { + memset(Bytes, 0, sizeof(Bytes)); if (consoletype == 1) { ConsoleType = FirmwareConsoleType::DSi; @@ -156,7 +158,7 @@ void Firmware::FirmwareHeader::UpdateChecksum() Firmware::UserData::UserData() { - memset(Bytes, 0, 0x74); + memset(Bytes, 0, sizeof(Bytes)); Version = 5; BirthdayMonth = 1; BirthdayDay = 1; @@ -273,7 +275,8 @@ Firmware::Firmware(const u8* data, u32 length) : FirmwareBuffer(nullptr), Firmwa if (data) { FirmwareBuffer = new u8[FirmwareBufferLength]; - memcpy(FirmwareBuffer, data, FirmwareBufferLength); + memset(FirmwareBuffer, 0, FirmwareBufferLength); + memcpy(FirmwareBuffer, data, std::min(length, FirmwareBufferLength)); FirmwareMask = FirmwareBufferLength - 1; } } @@ -345,7 +348,7 @@ const Firmware::UserData& Firmware::GetEffectiveUserData() const { if (userdata0ChecksumOk && userdata1ChecksumOk) { - return userdata[0].UpdateCounter > userdata[1].UpdateCounter ? userdata[0] : userdata[1]; + return userdata[0].UpdateCounter >= userdata[1].UpdateCounter ? userdata[0] : userdata[1]; } else if (userdata0ChecksumOk) { @@ -368,7 +371,7 @@ Firmware::UserData& Firmware::GetEffectiveUserData() { if (userdata0ChecksumOk && userdata1ChecksumOk) { - return userdata[0].UpdateCounter > userdata[1].UpdateCounter ? userdata[0] : userdata[1]; + return userdata[0].UpdateCounter >= userdata[1].UpdateCounter ? userdata[0] : userdata[1]; } else if (userdata0ChecksumOk) { diff --git a/src/SPI_Firmware.h b/src/SPI_Firmware.h index c8ca25c3..a5d40eeb 100644 --- a/src/SPI_Firmware.h +++ b/src/SPI_Firmware.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/SPU.cpp b/src/SPU.cpp index 86307097..eb30c0a9 100644 --- a/src/SPU.cpp +++ b/src/SPU.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -140,6 +140,41 @@ constexpr array2d InterpCubic = []() constexpr { return interp; }(); +const std::array InterpSNESGauss = { + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, + 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x002, 0x002, 0x002, 0x002, 0x002, + 0x002, 0x002, 0x003, 0x003, 0x003, 0x003, 0x003, 0x004, 0x004, 0x004, 0x004, 0x004, 0x005, 0x005, 0x005, 0x005, + 0x006, 0x006, 0x006, 0x006, 0x007, 0x007, 0x007, 0x008, 0x008, 0x008, 0x009, 0x009, 0x009, 0x00A, 0x00A, 0x00A, + 0x00B, 0x00B, 0x00B, 0x00C, 0x00C, 0x00D, 0x00D, 0x00E, 0x00E, 0x00F, 0x00F, 0x00F, 0x010, 0x010, 0x011, 0x011, + 0x012, 0x013, 0x013, 0x014, 0x014, 0x015, 0x015, 0x016, 0x017, 0x017, 0x018, 0x018, 0x019, 0x01A, 0x01B, 0x01B, + 0x01C, 0x01D, 0x01D, 0x01E, 0x01F, 0x020, 0x020, 0x021, 0x022, 0x023, 0x024, 0x024, 0x025, 0x026, 0x027, 0x028, + 0x029, 0x02A, 0x02B, 0x02C, 0x02D, 0x02E, 0x02F, 0x030, 0x031, 0x032, 0x033, 0x034, 0x035, 0x036, 0x037, 0x038, + 0x03A, 0x03B, 0x03C, 0x03D, 0x03E, 0x040, 0x041, 0x042, 0x043, 0x045, 0x046, 0x047, 0x049, 0x04A, 0x04C, 0x04D, + 0x04E, 0x050, 0x051, 0x053, 0x054, 0x056, 0x057, 0x059, 0x05A, 0x05C, 0x05E, 0x05F, 0x061, 0x063, 0x064, 0x066, + 0x068, 0x06A, 0x06B, 0x06D, 0x06F, 0x071, 0x073, 0x075, 0x076, 0x078, 0x07A, 0x07C, 0x07E, 0x080, 0x082, 0x084, + 0x086, 0x089, 0x08B, 0x08D, 0x08F, 0x091, 0x093, 0x096, 0x098, 0x09A, 0x09C, 0x09F, 0x0A1, 0x0A3, 0x0A6, 0x0A8, + 0x0AB, 0x0AD, 0x0AF, 0x0B2, 0x0B4, 0x0B7, 0x0BA, 0x0BC, 0x0BF, 0x0C1, 0x0C4, 0x0C7, 0x0C9, 0x0CC, 0x0CF, 0x0D2, + 0x0D4, 0x0D7, 0x0DA, 0x0DD, 0x0E0, 0x0E3, 0x0E6, 0x0E9, 0x0EC, 0x0EF, 0x0F2, 0x0F5, 0x0F8, 0x0FB, 0x0FE, 0x101, + 0x104, 0x107, 0x10B, 0x10E, 0x111, 0x114, 0x118, 0x11B, 0x11E, 0x122, 0x125, 0x129, 0x12C, 0x130, 0x133, 0x137, + 0x13A, 0x13E, 0x141, 0x145, 0x148, 0x14C, 0x150, 0x153, 0x157, 0x15B, 0x15F, 0x162, 0x166, 0x16A, 0x16E, 0x172, + 0x176, 0x17A, 0x17D, 0x181, 0x185, 0x189, 0x18D, 0x191, 0x195, 0x19A, 0x19E, 0x1A2, 0x1A6, 0x1AA, 0x1AE, 0x1B2, + 0x1B7, 0x1BB, 0x1BF, 0x1C3, 0x1C8, 0x1CC, 0x1D0, 0x1D5, 0x1D9, 0x1DD, 0x1E2, 0x1E6, 0x1EB, 0x1EF, 0x1F3, 0x1F8, + 0x1FC, 0x201, 0x205, 0x20A, 0x20F, 0x213, 0x218, 0x21C, 0x221, 0x226, 0x22A, 0x22F, 0x233, 0x238, 0x23D, 0x241, + 0x246, 0x24B, 0x250, 0x254, 0x259, 0x25E, 0x263, 0x267, 0x26C, 0x271, 0x276, 0x27B, 0x280, 0x284, 0x289, 0x28E, + 0x293, 0x298, 0x29D, 0x2A2, 0x2A6, 0x2AB, 0x2B0, 0x2B5, 0x2BA, 0x2BF, 0x2C4, 0x2C9, 0x2CE, 0x2D3, 0x2D8, 0x2DC, + 0x2E1, 0x2E6, 0x2EB, 0x2F0, 0x2F5, 0x2FA, 0x2FF, 0x304, 0x309, 0x30E, 0x313, 0x318, 0x31D, 0x322, 0x326, 0x32B, + 0x330, 0x335, 0x33A, 0x33F, 0x344, 0x349, 0x34E, 0x353, 0x357, 0x35C, 0x361, 0x366, 0x36B, 0x370, 0x374, 0x379, + 0x37E, 0x383, 0x388, 0x38C, 0x391, 0x396, 0x39B, 0x39F, 0x3A4, 0x3A9, 0x3AD, 0x3B2, 0x3B7, 0x3BB, 0x3C0, 0x3C5, + 0x3C9, 0x3CE, 0x3D2, 0x3D7, 0x3DC, 0x3E0, 0x3E5, 0x3E9, 0x3ED, 0x3F2, 0x3F6, 0x3FB, 0x3FF, 0x403, 0x408, 0x40C, + 0x410, 0x415, 0x419, 0x41D, 0x421, 0x425, 0x42A, 0x42E, 0x432, 0x436, 0x43A, 0x43E, 0x442, 0x446, 0x44A, 0x44E, + 0x452, 0x455, 0x459, 0x45D, 0x461, 0x465, 0x468, 0x46C, 0x470, 0x473, 0x477, 0x47A, 0x47E, 0x481, 0x485, 0x488, + 0x48C, 0x48F, 0x492, 0x496, 0x499, 0x49C, 0x49F, 0x4A2, 0x4A6, 0x4A9, 0x4AC, 0x4AF, 0x4B2, 0x4B5, 0x4B7, 0x4BA, + 0x4BD, 0x4C0, 0x4C3, 0x4C5, 0x4C8, 0x4CB, 0x4CD, 0x4D0, 0x4D2, 0x4D5, 0x4D7, 0x4D9, 0x4DC, 0x4DE, 0x4E0, 0x4E3, + 0x4E5, 0x4E7, 0x4E9, 0x4EB, 0x4ED, 0x4EF, 0x4F1, 0x4F3, 0x4F5, 0x4F6, 0x4F8, 0x4FA, 0x4FB, 0x4FD, 0x4FF, 0x500, + 0x502, 0x503, 0x504, 0x506, 0x507, 0x508, 0x50A, 0x50B, 0x50C, 0x50D, 0x50E, 0x50F, 0x510, 0x511, 0x511, 0x512, + 0x513, 0x514, 0x514, 0x515, 0x516, 0x516, 0x517, 0x517, 0x517, 0x518, 0x518, 0x518, 0x518, 0x518, 0x519, 0x519 +}; + SPU::SPU(melonDS::NDS& nds, AudioBitDepth bitdepth, AudioInterpolation interpolation) : NDS(nds), Channels { @@ -621,6 +656,19 @@ s32 SPUChannel::Run() (PrevSample[0] * InterpCubic[samplepos][2]) + (val * InterpCubic[samplepos][3])) >> 14; break; + + case AudioInterpolation::SNESGaussian: { + // Avoid clipping (from fullsnes) +#define CLAMP(s) (std::clamp((s) >> 1, -0x3FFA, 0x3FF8)) + s32 out = (InterpSNESGauss[0x0FF - samplepos] * CLAMP(PrevSample[2]) >> 10); + out = out + ((InterpSNESGauss[0x1FF - samplepos] * CLAMP(PrevSample[1])) >> 10); + out = out + ((InterpSNESGauss[0x100 + samplepos] * CLAMP(PrevSample[0])) >> 10); + out = out + ((InterpSNESGauss[0x000 + samplepos] * CLAMP(val)) >> 10); + val = std::clamp(out, -0x8000, 0x7FFF); +#undef CLAMP + break; + } + default: break; } @@ -1257,4 +1305,4 @@ void SPU::Write32(u32 addr, u32 val) } } -} \ No newline at end of file +} diff --git a/src/SPU.h b/src/SPU.h index b2b05ac7..19253891 100644 --- a/src/SPU.h +++ b/src/SPU.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -40,6 +40,7 @@ enum class AudioInterpolation Linear, Cosine, Cubic, + SNESGaussian }; class SPUChannel diff --git a/src/Savestate.cpp b/src/Savestate.cpp index 6d6a9a47..c51459d9 100644 --- a/src/Savestate.cpp +++ b/src/Savestate.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/Savestate.h b/src/Savestate.h index 2e1400a0..dce62844 100644 --- a/src/Savestate.h +++ b/src/Savestate.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/TinyVector.h b/src/TinyVector.h index 5a30ff65..66da63ec 100644 --- a/src/TinyVector.h +++ b/src/TinyVector.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team, RSDuck + Copyright 2016-2024 melonDS team, RSDuck This file is part of melonDS. diff --git a/src/Utils.cpp b/src/Utils.cpp index 698cf9bd..bb5848c1 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/Utils.h b/src/Utils.h index 63be217b..eae9193a 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/Wifi.cpp b/src/Wifi.cpp index 4da253ef..3eaa9fd3 100644 --- a/src/Wifi.cpp +++ b/src/Wifi.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -93,25 +93,11 @@ Wifi::Wifi(melonDS::NDS& nds) : NDS(nds) { NDS.RegisterEventFunc(Event_Wifi, 0, MemberEventFunc(Wifi, USTimer)); - //MPInited = false; - //LANInited = false; - - Platform::MP_Init(); - MPInited = true; - - Platform::LAN_Init(); - LANInited = true; - - WifiAP = new class WifiAP(this); + WifiAP = new class WifiAP(this, NDS.UserData); } Wifi::~Wifi() { - if (MPInited) - Platform::MP_DeInit(); - if (LANInited) - Platform::LAN_DeInit(); - delete WifiAP; WifiAP = nullptr; NDS.UnregisterEventFunc(Event_Wifi, 0); @@ -159,11 +145,45 @@ void Wifi::Reset() #undef BBREG_FIXED const Firmware& fw = NDS.SPI.GetFirmware(); + const auto& fwheader = fw.GetHeader(); - RFVersion = fw.GetHeader().RFChipType; + RFVersion = fwheader.RFChipType; memset(RFRegs, 0, 4*0x40); - Firmware::FirmwareConsoleType console = fw.GetHeader().ConsoleType; + // load channel index/data from the firmware + // the current channel will be determined by RF settings + // so we compare the two 'most important' RF registers to these values to figure out which channel is selected + + if (RFVersion == 3) + { + RFChannelIndex[0] = fwheader.Type3Config.RFIndex1; + RFChannelIndex[1] = fwheader.Type3Config.RFIndex2; + + for (int i = 0; i < 14; i++) + { + RFChannelData[i][0] = fwheader.Type3Config.RFData1[i]; + RFChannelData[i][1] = fwheader.Type3Config.RFData2[i]; + } + } + else + { + RFChannelIndex[0] = fwheader.Type2Config.InitialRF56Values[2] >> 2; + RFChannelIndex[1] = fwheader.Type2Config.InitialRF56Values[5] >> 2; + + for (int i = 0; i < 14; i++) + { + RFChannelData[i][0] = fwheader.Type2Config.InitialRF56Values[i*6 + 0] | + (fwheader.Type2Config.InitialRF56Values[i*6 + 1] << 8) | + ((fwheader.Type2Config.InitialRF56Values[i*6 + 2] & 0x03) << 16); + RFChannelData[i][1] = fwheader.Type2Config.InitialRF56Values[i*6 + 3] | + (fwheader.Type2Config.InitialRF56Values[i*6 + 4] << 8) | + ((fwheader.Type2Config.InitialRF56Values[i*6 + 5] & 0x03) << 16); + } + } + + CurChannel = 0; + + Firmware::FirmwareConsoleType console = fwheader.ConsoleType; if (console == Firmware::FirmwareConsoleType::DS) IOPORT(0x000) = 0x1440; else if (console == Firmware::FirmwareConsoleType::DSLite) @@ -182,6 +202,8 @@ void Wifi::Reset() // TODO: find out what the initial values are IOPORT(W_PowerUS) = 0x0001; + //IOPORT(W_BeaconInterval) = 100; + USTimestamp = 0; USCounter = 0; @@ -209,7 +231,6 @@ void Wifi::Reset() CmdCounter = 0; USUntilPowerOn = 0; - ForcePowerOn = false; IsMP = false; IsMPClient = false; @@ -244,6 +265,8 @@ void Wifi::DoSavestate(Savestate* file) file->Var8(&RFVersion); file->VarArray(RFRegs, 4*0x40); + file->Var32((u32*)&CurChannel); + file->Var64(&USCounter); file->Var64(&USCompare); file->Bool32(&BlockBeaconIRQ14); @@ -285,7 +308,6 @@ void Wifi::DoSavestate(Savestate* file) file->Var16(&MPLastSeqno); file->Var32((u32*)&USUntilPowerOn); - file->Bool32(&ForcePowerOn); file->Bool32(&IsMP); file->Bool32(&IsMPClient); @@ -332,7 +354,7 @@ void Wifi::UpdatePowerOn() ScheduleTimer(true); - Platform::MP_Begin(); + Platform::MP_Begin(NDS.UserData); } else { @@ -340,7 +362,7 @@ void Wifi::UpdatePowerOn() NDS.CancelEvent(Event_Wifi); - Platform::MP_End(); + Platform::MP_End(NDS.UserData); } } @@ -351,33 +373,38 @@ void Wifi::SetPowerCnt(u32 val) } -void Wifi::SetIRQ(u32 irq) +void Wifi::CheckIRQ(u16 oldflags) { - u32 oldflags = IOPORT(W_IF) & IOPORT(W_IE); - - IOPORT(W_IF) |= (1<SendPacket(TXBuffer, 12+len); break; case 1: *(u16*)&TXBuffer[12 + 24+2] = MPClientMask; - Platform::MP_SendCmd(TXBuffer, 12+len, USTimestamp); + Platform::MP_SendCmd(TXBuffer, 12+len, USTimestamp, NDS.UserData); break; case 5: IncrementTXCount(slot); - Platform::MP_SendReply(TXBuffer, 12+len, USTimestamp, IOPORT(W_AIDLow)); + Platform::MP_SendReply(TXBuffer, 12+len, USTimestamp, IOPORT(W_AIDLow), NDS.UserData); break; case 4: *(u64*)&TXBuffer[0xC + 24] = USCounter; - Platform::MP_SendPacket(TXBuffer, 12+len, USTimestamp); + Platform::MP_SendPacket(TXBuffer, 12+len, USTimestamp, NDS.UserData); break; } } @@ -600,6 +724,9 @@ void Wifi::StartTX_Cmd() slot->CurPhase = 13; slot->CurPhaseTime = CmdCounter - 100; } + + // starting a CMD transfer wakes up the transceiver automatically + UpdatePowerStatus(1); } void Wifi::StartTX_Beacon() @@ -678,6 +805,9 @@ void Wifi::SendMPDefaultReply() // TODO reply[0x8] = 0x14; + if (CurChannel == 0) return; + reply[0x9] = CurChannel; + *(u16*)&reply[0xC + 0x00] = 0x0158; *(u16*)&reply[0xC + 0x02] = 0x00F0;//0; // TODO?? *(u16*)&reply[0xC + 0x04] = IOPORT(W_BSSID0); @@ -692,7 +822,7 @@ void Wifi::SendMPDefaultReply() *(u16*)&reply[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; *(u32*)&reply[0xC + 0x18] = 0; - int txlen = Platform::MP_SendReply(reply, 12+28, USTimestamp, IOPORT(W_AIDLow)); + int txlen = Platform::MP_SendReply(reply, 12+28, USTimestamp, IOPORT(W_AIDLow), NDS.UserData); WIFI_LOG("wifi: sent %d/40 bytes of MP default reply\n", txlen); } @@ -767,6 +897,9 @@ void Wifi::SendMPAck(u16 cmdcount, u16 clientfail) if (TXSlots[1].Rate == 2) ack[0x8] = 0x14; else ack[0x8] = 0xA; + if (CurChannel == 0) return; + ack[0x9] = CurChannel; + *(u16*)&ack[0xC + 0x00] = 0x0218; *(u16*)&ack[0xC + 0x02] = 0; *(u16*)&ack[0xC + 0x04] = 0x0903; @@ -799,7 +932,7 @@ void Wifi::SendMPAck(u16 cmdcount, u16 clientfail) *(u32*)&ack[0] = PreambleLen(TXSlots[1].Rate); } - int txlen = Platform::MP_SendAck(ack, 12+32, USTimestamp); + int txlen = Platform::MP_SendAck(ack, 12+32, USTimestamp, NDS.UserData); WIFI_LOG("wifi: sent %d/44 bytes of MP ack, %d %d\n", txlen, ComStatus, RXTime); } @@ -922,7 +1055,7 @@ bool Wifi::ProcessTX(TXSlot* slot, int num) u16 res = 0; if (MPClientMask) - res = Platform::MP_RecvReplies(MPClientReplies, USTimestamp, MPClientMask); + res = Platform::MP_RecvReplies(MPClientReplies, USTimestamp, MPClientMask, NDS.UserData); MPClientFail &= ~res; // TODO: 112 likely includes the ack preamble, which needs adjusted @@ -1110,8 +1243,11 @@ void Wifi::FinishRX() if (!ComStatus) { - if (IOPORT(W_PowerState) & 0x0300) + if (IOPORT(W_PowerState) & (1<<9)) + { + IOPORT(W_TRXPower) = 0; SetStatus(9); + } else SetStatus(1); } @@ -1358,7 +1494,7 @@ void Wifi::FinishRX() // 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); + Platform::MP_SendReply(nullptr, 0, USTimestamp, 0, NDS.UserData); } } else if ((rxflags & 0x800F) == 0x8001) @@ -1380,7 +1516,7 @@ void Wifi::FinishRX() void Wifi::MPClientReplyRX(int client) { - if (IOPORT(W_PowerState) & 0x0300) + if (IOPORT(W_PowerState) & (1<<9)) return; if (!(IOPORT(W_RXCnt) & 0x8000)) @@ -1421,7 +1557,7 @@ void Wifi::MPClientReplyRX(int client) bool Wifi::CheckRX(int type) // 0=regular 1=MP replies 2=MP host frames { - if (IOPORT(W_PowerState) & 0x0300) + if (IOPORT(W_PowerState) & (1<<9)) return false; if (!(IOPORT(W_RXCnt) & 0x8000)) @@ -1433,7 +1569,7 @@ bool Wifi::CheckRX(int type) // 0=regular 1=MP replies 2=MP host frames int rxlen; int framelen; u16 framectl; - u8 txrate; + u8 txrate, chan; u64 timestamp; for (;;) @@ -1442,13 +1578,13 @@ bool Wifi::CheckRX(int type) // 0=regular 1=MP replies 2=MP host frames if (type == 0) { - rxlen = Platform::MP_RecvPacket(RXBuffer, ×tamp); + rxlen = Platform::MP_RecvPacket(RXBuffer, ×tamp, NDS.UserData); if ((rxlen <= 0) && (!IsMP)) rxlen = WifiAP->RecvPacket(RXBuffer); } else { - rxlen = Platform::MP_RecvHostPacket(RXBuffer, ×tamp); + rxlen = Platform::MP_RecvHostPacket(RXBuffer, ×tamp, NDS.UserData); if (rxlen < 0) { // host is gone @@ -1468,6 +1604,24 @@ bool Wifi::CheckRX(int type) // 0=regular 1=MP replies 2=MP host frames continue; } + chan = RXBuffer[9]; + if (chan != CurChannel || CurChannel == 0) + { + Log(LogLevel::Debug, "received frame but bad channel %d (expected %d)\n", chan, CurChannel); + continue; + } + + // hack: ignore MP frames if not engaged in a MP comm + if (type == 0 && (!IsMP)) + { + if (MACEqual(&RXBuffer[12 + 16], MPReplyMAC) || + MACEqual(&RXBuffer[12 + 4], MPCmdMAC) || + MACEqual(&RXBuffer[12 + 4], MPReplyMAC)) + { + continue; + } + } + framectl = *(u16*)&RXBuffer[12+0]; txrate = RXBuffer[8]; @@ -1491,9 +1645,21 @@ bool Wifi::CheckRX(int type) // 0=regular 1=MP replies 2=MP host frames *(u16*)&RXBuffer[6] = txrate; *(u16*)&RXBuffer[8] = framelen; + u16 frametype = (framectl & 0x00FF); bool macgood = (RXBuffer[12 + 4] & 0x01) || MACEqual(&RXBuffer[12 + 4], (u8*)&IOPORT(W_MACAddr0)); - if (((framectl & 0x00FF) == 0x0010) && timestamp && macgood) + // HACK: when receiving auth/assoc frames, extend the post-beacon interval + // during MP comm, the host will periodically wake up to send a beacon, and stay awake during the + // post-beacon interval to see if any clients are trying to connect + // the auth/assoc procedure would normally fit during that window, but when we are emulating wifi + // and not yet synced, these frames may lag behind, preventing a successful connection + if ((frametype == 0x00B0 || frametype == 0x0010 || frametype == 0x0000) && timestamp && macgood) + { + if (IOPORT(W_BeaconCount2)) + IOPORT(W_BeaconCount2) += 10; + } + + if ((frametype == 0x0010) && timestamp && macgood) { // if receiving an association response: get the sync value from the host @@ -1512,7 +1678,7 @@ bool Wifi::CheckRX(int type) // 0=regular 1=MP replies 2=MP host frames RXTimestamp = 0; StartRX(); } - else if (((framectl & 0x00FF) == 0x00C0) && timestamp && macgood && IsMPClient) + else if ((frametype == 0x00C0) && timestamp && macgood && IsMPClient) { IsMP = false; IsMPClient = false; @@ -1528,7 +1694,6 @@ bool Wifi::CheckRX(int type) // 0=regular 1=MP replies 2=MP host frames // we also need to determine how far we can run after having received this frame RXTimestamp = timestamp; - //if (RXTimestamp < USTimestamp) printf("CRAP!! %04X %016llX %016llX\n", framectl, RXTimestamp, USTimestamp); if (RXTimestamp < USTimestamp) RXTimestamp = USTimestamp; NextSync = RXTimestamp + (framelen * (txrate==0x14 ? 4:8)); @@ -1570,11 +1735,13 @@ void Wifi::MSTimer() } } - IOPORT(W_BeaconCount1)--; - if (IOPORT(W_BeaconCount1) == 0) + if (IOPORT(W_BeaconCount1) != 0) { - SetIRQ14(1); + IOPORT(W_BeaconCount1)--; + if (IOPORT(W_BeaconCount1) == 0) SetIRQ14(1); } + if (IOPORT(W_BeaconCount1) == 0) + IOPORT(W_BeaconCount1) = IOPORT(W_BeaconInterval); if (IOPORT(W_BeaconCount2) != 0) { @@ -1605,19 +1772,19 @@ void Wifi::USTimer(u32 param) if (!(USTimestamp & 0x3FF & kTimeCheckMask)) WifiAP->MSTimer(); - bool switchOffPowerSaving = false; if (USUntilPowerOn < 0) { USUntilPowerOn += kTimerInterval; - switchOffPowerSaving = (USUntilPowerOn >= 0) && (IOPORT(W_PowerUnk) & 0x0001 || ForcePowerOn); - } - if ((USUntilPowerOn >= 0) && (IOPORT(W_PowerState) & 0x0002 || switchOffPowerSaving)) - { - IOPORT(W_PowerState) = 0; - IOPORT(W_RFPins) = 1; - IOPORT(W_RFPins) = 0x0084; - SetIRQ(11); + if (USUntilPowerOn >= 0) + { + USUntilPowerOn = 0; + + IOPORT(W_PowerState) = 0; + SetStatus(1); + + UpdatePowerStatus(0); + } } if (IOPORT(W_USCountCnt)) @@ -1660,7 +1827,7 @@ void Wifi::USTimer(u32 param) u16 txbusy = IOPORT(W_TXBusy); if (txbusy) { - if (IOPORT(W_PowerState) & 0x0300) + if (IOPORT(W_PowerState) & (1<<9)) { ComStatus = 0; TXCurSlot = -1; @@ -1695,9 +1862,10 @@ void Wifi::USTimer(u32 param) bool finished = ProcessTX(&TXSlots[TXCurSlot], TXCurSlot); if (finished) { - if (IOPORT(W_PowerState) & 0x0300) + if (IOPORT(W_PowerState) & (1<<9)) { IOPORT(W_TXBusy) = 0; + IOPORT(W_TRXPower) = 0; SetStatus(9); } @@ -1754,8 +1922,9 @@ void Wifi::USTimer(u32 param) RXCounter = 0; } // TODO: proper error management - if ((!ComStatus) && (IOPORT(W_PowerState) & 0x0300)) + if ((!ComStatus) && (IOPORT(W_PowerState) & (1<<9))) { + IOPORT(W_TRXPower) = 0; SetStatus(9); } } @@ -1766,6 +1935,28 @@ void Wifi::USTimer(u32 param) } +void Wifi::ChangeChannel() +{ + u32 val1 = RFRegs[RFChannelIndex[0]]; + u32 val2 = RFRegs[RFChannelIndex[1]]; + + CurChannel = 0; + + for (int i = 0; i < 14; i++) + { + if (val1 == RFChannelData[i][0] && val2 == RFChannelData[i][1]) + { + CurChannel = i+1; + break; + } + } + + if (CurChannel > 0) + Log(LogLevel::Debug, "wifi: switching to channel %d\n", CurChannel); + else + Log(LogLevel::Debug, "wifi: invalid channel values %05X:%05X\n", val1, val2); +} + void Wifi::RFTransfer_Type2() { u32 id = (IOPORT(W_RFData2) >> 2) & 0x1F; @@ -1780,6 +1971,9 @@ void Wifi::RFTransfer_Type2() { u32 data = IOPORT(W_RFData1) | ((IOPORT(W_RFData2) & 0x0003) << 16); RFRegs[id] = data; + + if (id == RFChannelIndex[0] || id == RFChannelIndex[1]) + ChangeChannel(); } } @@ -1796,6 +1990,9 @@ void Wifi::RFTransfer_Type3() { u32 data = IOPORT(W_RFData1) & 0xFF; RFRegs[id] = data; + + if (id == RFChannelIndex[0] || id == RFChannelIndex[1]) + ChangeChannel(); } } @@ -1819,6 +2016,7 @@ u16 Wifi::Read(u32 addr) switch (addr) { case W_Random: // random generator. not accurate + // TODO: rotate the sequence based on the ARM7 cycle counter (if this is important) Random = (Random & 0x1) ^ (((Random & 0x3FF) << 1) | (Random >> 10)); return Random; @@ -1899,7 +2097,6 @@ u16 Wifi::Read(u32 addr) } } - //printf("WIFI: read %08X\n", addr); return IOPORT(addr&0xFFF); } @@ -1923,28 +2120,20 @@ void Wifi::Write(u32 addr, u16 val) case W_ModeReset: { u16 oldval = IOPORT(W_ModeReset); + IOPORT(W_ModeReset) = val & 0x0001; if (!(oldval & 0x0001) && (val & 0x0001)) { - if (!(USUntilPowerOn < 0 && ForcePowerOn)) - { - //printf("mode reset power on %08x\n", NDS::ARM7->R[15]); - IOPORT(0x034) = 0x0002; - IOPORT(0x27C) = 0x0005; - // TODO: 02A2?? + IOPORT(0x27C) = 0x0005; + // TODO: 02A2?? - if (IOPORT(W_PowerUnk) & 0x0002) - { - USUntilPowerOn = -2048; - IOPORT(W_PowerState) |= 0x100; - } - } + UpdatePowerStatus(0); } else if ((oldval & 0x0001) && !(val & 0x0001)) { - //printf("mode reset shutdown %08x\n", NDS::ARM7->R[15]); IOPORT(0x27C) = 0x000A; - PowerDown(); + + UpdatePowerStatus(0); } if (val & 0x2000) @@ -1986,23 +2175,43 @@ void Wifi::Write(u32 addr, u16 val) IOPORT(0x230) = 0x0047; } } - break; + return; case W_ModeWEP: val &= 0x007F; - //printf("writing mode web %x\n", val); - if ((val & 0x7) == 1) - IOPORT(W_PowerUnk) |= 0x0002; - if ((val & 0x7) == 2) - IOPORT(W_PowerUnk) = 0x0003; - break; + IOPORT(W_ModeWEP) = val; + if (IOPORT(W_PowerTX) & (1<<1)) + { + if ((val & 0x7) == 1) + IOPORT(W_PowerDownCtrl) |= (1<<1); + else if ((val & 0x7) == 2) + IOPORT(W_PowerDownCtrl) = 3; + + if ((val & 0x7) != 3) + IOPORT(W_PowerState) &= 0x0300; + + UpdatePowerStatus(0); + } + return; + + case W_IE: + { + u16 oldflags = IOPORT(W_IF) & IOPORT(W_IE); + IOPORT(W_IE) = val; + CheckIRQ(oldflags); + } + return; case W_IF: IOPORT(W_IF) &= ~val; return; case W_IFSet: - IOPORT(W_IF) |= (val & 0xFBFF); - Log(LogLevel::Debug, "wifi: force-setting IF %04X\n", val); + { + u16 oldflags = IOPORT(W_IF) & IOPORT(W_IE); + IOPORT(W_IF) |= (val & 0xFBFF); + CheckIRQ(oldflags); + Log(LogLevel::Debug, "wifi: force-setting IF %04X\n", val); + } return; case W_AIDLow: @@ -2012,67 +2221,63 @@ void Wifi::Write(u32 addr, u16 val) 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; - - if (IOPORT(W_ModeReset) & 0x0001 && IOPORT(W_PowerState) & 0x0002) - { - /*if (IOPORT(W_PowerState) & 0x100) - { - AlwaysPowerOn = true; - USUntilPowerOn = -1; - } - else */ - if (IOPORT(W_PowerForce) == 1) - { - //printf("power on\n"); - IOPORT(W_PowerState) |= 0x100; - USUntilPowerOn = -2048; - ForcePowerOn = false; - } - } - 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) - { - //printf("force power off\n"); - IOPORT(0x034) = 0x0002; - IOPORT(W_PowerState) = 0x0200; - IOPORT(W_TXReqRead) = 0; - PowerDown(); - } - if (val == 1 && IOPORT(W_PowerState) & 0x0002) - { - //printf("power on\n"); - IOPORT(W_PowerState) |= 0x100; - USUntilPowerOn = -2048; - ForcePowerOn = false; - } - if (val == 0x8000) - { - //printf("force power on\n"); - IOPORT(W_PowerState) |= 0x100; - USUntilPowerOn = -2048; - ForcePowerOn = true; - } - break; case W_PowerUS: IOPORT(W_PowerUS) = val & 0x0003; UpdatePowerOn(); return; - case W_PowerUnk: - val &= 0x0003; - //printf("writing power unk %x\n", val); - if ((IOPORT(W_ModeWEP) & 0x7) == 1) - val |= 2; - else if ((IOPORT(W_ModeWEP) & 0x7) == 2) - val = 3; - break; + + case W_PowerTX: + IOPORT(W_PowerTX) = val & 0x0003; + if (val & (1<<1)) + { + if ((IOPORT(W_ModeWEP) & 0x7) == 1) + IOPORT(W_PowerDownCtrl) |= (1<<1); + else if ((IOPORT(W_ModeWEP) & 0x7) == 2) + IOPORT(W_PowerDownCtrl) = 3; + + UpdatePowerStatus(0); + } + return; + + case W_PowerState: + if ((IOPORT(W_ModeWEP) & 0x7) != 3) + return; + + val = (IOPORT(W_PowerState) & 0x0300) | (val & 0x0003); + if ((val & 0x0300) == 0x0200) + val &= ~(1<<0); + else + val &= ~(1<<1); + + if (!(val & (1<<9))) + val &= ~(1<<8); + + IOPORT(W_PowerState) = val; + UpdatePowerStatus(0); + return; + + case W_PowerForce: + val &= 0x8001; + IOPORT(W_PowerForce) = val; + UpdatePowerStatus(0); + return; + + case W_PowerDownCtrl: + IOPORT(W_PowerDownCtrl) = val & 0x0003; + + if (IOPORT(W_PowerTX) & (1<<1)) + { + if ((IOPORT(W_ModeWEP) & 0x7) == 1) + IOPORT(W_PowerDownCtrl) |= (1<<1); + else if ((IOPORT(W_ModeWEP) & 0x7) == 2) + IOPORT(W_PowerDownCtrl) = 3; + } + + if (val != 0 && val != 3) + Log(LogLevel::Warn, "wifi: unusual W_PowerDownCtrl value %04X\n", val); + + UpdatePowerStatus(0); + return; case W_USCountCnt: val &= 0x0001; break; case W_USCompareCnt: @@ -2231,6 +2436,7 @@ void Wifi::Write(u32 addr, u16 val) // read-only ports case 0x000: + case 0x034: case 0x044: case 0x054: case 0x098: diff --git a/src/Wifi.h b/src/Wifi.h index 5553a6f5..14eddd65 100644 --- a/src/Wifi.h +++ b/src/Wifi.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -52,11 +52,12 @@ public: W_RXCnt = 0x030, W_WEPCnt = 0x032, + W_TRXPower = 0x034, W_PowerUS = 0x036, W_PowerTX = 0x038, W_PowerState = 0x03C, W_PowerForce = 0x040, - W_PowerUnk = 0x48, + W_PowerDownCtrl = 0x48, W_Random = 0x044, @@ -206,6 +207,10 @@ private: u8 RFVersion; u32 RFRegs[0x40]; + u32 RFChannelIndex[2]; + u32 RFChannelData[14][2]; + int CurChannel; + struct TXSlot { bool Valid; @@ -236,11 +241,7 @@ private: u16 MPLastSeqno; - bool MPInited; - bool LANInited; - int USUntilPowerOn; - bool ForcePowerOn; // MULTIPLAYER SYNC APPARATUS bool IsMP; @@ -253,13 +254,15 @@ private: void ScheduleTimer(bool first); void UpdatePowerOn(); + void CheckIRQ(u16 oldflags); void SetIRQ(u32 irq); void SetIRQ13(); void SetIRQ14(int source); void SetIRQ15(); void SetStatus(u32 status); - void PowerDown(); + + void UpdatePowerStatus(int power); int PreambleLen(int rate) const; u32 NumClients(u16 bitmask) const; @@ -284,6 +287,8 @@ private: void MSTimer(); + void ChangeChannel(); + void RFTransfer_Type2(); void RFTransfer_Type3(); }; diff --git a/src/WifiAP.cpp b/src/WifiAP.cpp index 4c645203..0a239f7d 100644 --- a/src/WifiAP.cpp +++ b/src/WifiAP.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -35,6 +35,7 @@ using Platform::LogLevel; const char* WifiAP::APName = "melonAP"; const u8 WifiAP::APMac[6] = {0x00, 0xF0, 0x77, 0x77, 0x77, 0x77}; +const u8 WifiAP::APChannel = 6; #define PWRITE_8(p, v) *p++ = v; #define PWRITE_16(p, v) *(u16*)p = v; p += 2; @@ -55,7 +56,7 @@ const u8 WifiAP::APMac[6] = {0x00, 0xF0, 0x77, 0x77, 0x77, 0x77}; PWRITE_16(p, 0); \ PWRITE_16(p, 0); \ PWRITE_8(p, rate); \ - PWRITE_8(p, 0); \ + PWRITE_8(p, APChannel); \ PWRITE_16(p, len); //#define PALIGN_4(p, base) p += ((4 - ((ptrdiff_t)(p-base) & 0x3)) & 0x3); @@ -70,7 +71,7 @@ bool MACEqual(const u8* a, const u8* b); bool MACIsBroadcast(const u8* a); -WifiAP::WifiAP(Wifi* client) : Client(client) +WifiAP::WifiAP(Wifi* client, void* userdata) : Client(client), UserData(userdata) { } @@ -174,7 +175,7 @@ int WifiAP::HandleManagementFrame(const u8* data, int len) PWRITE_16(p, 128); // beacon interval PWRITE_16(p, 0x0021); // capability PWRITE_8(p, 0x01); PWRITE_8(p, 0x02); PWRITE_8(p, 0x82); PWRITE_8(p, 0x84); // rates - PWRITE_8(p, 0x03); PWRITE_8(p, 0x01); PWRITE_8(p, 0x06); // current channel + PWRITE_8(p, 0x03); PWRITE_8(p, 0x01); PWRITE_8(p, APChannel); // current channel PWRITE_8(p, 0x00); PWRITE_8(p, strlen(APName)); memcpy(p, APName, strlen(APName)); p += strlen(APName); @@ -260,6 +261,9 @@ int WifiAP::HandleManagementFrame(const u8* data, int len) int WifiAP::SendPacket(const u8* data, int len) { + if (data[9] != APChannel) + return 0; + data += 12; u16 framectl = *(u16*)&data[0]; @@ -297,7 +301,7 @@ int WifiAP::SendPacket(const u8* data, int len) *(u16*)&LANBuffer[12] = *(u16*)&data[30]; // type memcpy(&LANBuffer[14], &data[32], lan_len - 14); - Platform::LAN_SendPacket(LANBuffer, lan_len); + Platform::Net_SendPacket(LANBuffer, lan_len, UserData); } } return len; @@ -327,7 +331,7 @@ int WifiAP::RecvPacket(u8* data) PWRITE_16(p, 128); // beacon interval PWRITE_16(p, 0x0021); // capability PWRITE_8(p, 0x01); PWRITE_8(p, 0x02); PWRITE_8(p, 0x82); PWRITE_8(p, 0x84); // rates - PWRITE_8(p, 0x03); PWRITE_8(p, 0x01); PWRITE_8(p, 0x06); // current channel + PWRITE_8(p, 0x03); PWRITE_8(p, 0x01); PWRITE_8(p, APChannel); // current channel PWRITE_8(p, 0x05); PWRITE_8(p, 0x04); PWRITE_8(p, 0); PWRITE_8(p, 0); PWRITE_8(p, 0); PWRITE_8(p, 0); // TIM PWRITE_8(p, 0x00); PWRITE_8(p, strlen(APName)); memcpy(p, APName, strlen(APName)); p += strlen(APName); @@ -364,14 +368,23 @@ int WifiAP::RecvPacket(u8* data) if (ClientStatus < 2) return 0; - int rxlen = Platform::LAN_RecvPacket(LANBuffer); - if (rxlen > 0) + int rxlen = Platform::Net_RecvPacket(LANBuffer, UserData); + while (rxlen > 0) { // check destination MAC if (!MACIsBroadcast(&LANBuffer[0])) { if (!MACEqual(&LANBuffer[0], Client->GetMAC())) - return 0; + { + rxlen = Platform::Net_RecvPacket(LANBuffer, UserData); + continue; + } + } + + if (MACEqual(&LANBuffer[6], Client->GetMAC())) + { + rxlen = Platform::Net_RecvPacket(LANBuffer, UserData); + continue; } // packet is good diff --git a/src/WifiAP.h b/src/WifiAP.h index 8f3ed111..8f0ef608 100644 --- a/src/WifiAP.h +++ b/src/WifiAP.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -28,12 +28,13 @@ class Wifi; class WifiAP { public: - WifiAP(Wifi* client); + WifiAP(Wifi* client, void* userdata); ~WifiAP(); void Reset(); static const char* APName; static const u8 APMac[6]; + static const u8 APChannel; void MSTimer(); @@ -43,6 +44,7 @@ public: private: Wifi* Client; + void* UserData; u64 USCounter; diff --git a/src/debug/GdbCmds.cpp b/src/debug/GdbCmds.cpp index c4706138..c2b1f9c4 100644 --- a/src/debug/GdbCmds.cpp +++ b/src/debug/GdbCmds.cpp @@ -1,12 +1,13 @@ #include #include +#include #include "../CRC32.h" #include "../Platform.h" #include "hexutil.h" -#include "GdbProto.h" +#include "GdbStub.h" using namespace melonDS; using Platform::Log; @@ -878,6 +879,7 @@ ExecResult GdbStub::Handle_v_Stopped(GdbStub* stub, const u8* cmd, ssize_t len) ExecResult GdbStub::Handle_v_MustReplyEmpty(GdbStub* stub, const u8* cmd, ssize_t len) { + printf("must reply empty\n"); stub->Resp(NULL, 0); return ExecResult::Ok; } @@ -886,6 +888,7 @@ ExecResult GdbStub::Handle_v_Cont(GdbStub* stub, const u8* cmd, ssize_t len) { if (len < 1) { + printf("insufficient length"); stub->RespStr("E01"); return ExecResult::Ok; } @@ -902,6 +905,7 @@ ExecResult GdbStub::Handle_v_Cont(GdbStub* stub, const u8* cmd, ssize_t len) stub->RespStr("OK"); return ExecResult::MustBreak; default: + printf("invalid continue %c %s\n", cmd[0], cmd); stub->RespStr("E01"); return ExecResult::Ok; } diff --git a/src/debug/GdbProto.cpp b/src/debug/GdbProto.cpp index ebdf3be5..a69538db 100644 --- a/src/debug/GdbProto.cpp +++ b/src/debug/GdbProto.cpp @@ -1,16 +1,18 @@ #ifdef _WIN32 -#include +#include #include #include #endif +#include #include #include #include #include #include #include +#include #ifndef _WIN32 #include @@ -21,8 +23,7 @@ #include "../Platform.h" #include "hexutil.h" -#include "GdbProto.h" - +#include "GdbStub.h" using namespace melonDS; using Platform::Log; @@ -42,87 +43,128 @@ namespace Gdb * vKill;pid * qRcmd? qSupported? */ -u8 Cmdbuf[GDBPROTO_BUFFER_CAPACITY]; -ssize_t Cmdlen; -namespace Proto + +Gdb::ReadResult GdbStub::TryParsePacket(size_t start, size_t& packetStart, size_t& packetSize, size_t& packetContentSize) { - -u8 PacketBuf[GDBPROTO_BUFFER_CAPACITY]; -u8 RespBuf[GDBPROTO_BUFFER_CAPACITY+5]; - -ReadResult MsgRecv(int connfd, u8 cmd_dest[/*static GDBPROTO_BUFFER_CAPACITY*/]) -{ - static ssize_t dataoff = 0; - - ssize_t recv_total = dataoff; - ssize_t cksumoff = -1; - u8 sum = 0; - - bool first = true; - - //printf("--- dataoff=%zd\n", dataoff); - if (dataoff != 0) { - printf("--- got preexisting: %s\n", PacketBuf); - - ssize_t datastart = 0; - while (true) + RecvBuffer[RecvBufferFilled] = '\0'; + //Log(LogLevel::Debug, "[GDB] Trying to parse packet %s %d %d\n", &RecvBuffer[0], start, RecvBufferFilled); + size_t i = start; + while (i < RecvBufferFilled) + { + char curChar = RecvBuffer[i++]; + if (curChar == '\x04') return ReadResult::Eof; + else if (curChar == '\x03') { - if (PacketBuf[datastart] == '\x04') return ReadResult::Eof; - else if (PacketBuf[datastart] == '+' || PacketBuf[datastart] == '-') + packetStart = i - 1; + packetSize = packetContentSize = 1; + return ReadResult::Break; + } + else if (curChar == '+' || curChar == '-') continue; + else if (curChar == '$') + { + packetStart = i; + uint8_t checksumGot = 0; + while (i < RecvBufferFilled) { - /*if (PacketBuf[datastart] == '+') SendAck(connfd); - else SendNak(connfd);*/ - ++datastart; - continue; - } - else if (PacketBuf[datastart] == '$') - { - ++datastart; - break; - } - else - { - __builtin_trap(); - return ReadResult::Wut; + curChar = RecvBuffer[i]; + if (curChar == '#' && i + 2 < RecvBufferFilled) + { + u8 checksumShould = (hex2nyb(RecvBuffer[i+1]) << 4) + | hex2nyb(RecvBuffer[i+2]); + + Log(LogLevel::Debug, "[GDB] found pkt, checksumGot: %02x vs %02x\n", checksumShould, checksumGot); + + if (checksumShould != checksumGot) + { + return ReadResult::CksumErr; + } + + packetContentSize = i - packetStart; + packetSize = packetContentSize + 3; + return ReadResult::CmdRecvd; + } + else + { + checksumGot += curChar; + } + + i++; } } - printf("--- datastart=%zd\n", datastart); - - for (ssize_t i = datastart; i < dataoff; ++i) + else { - if (PacketBuf[i] == '#') - { - cksumoff = i + 1; - printf("--- cksumoff=%zd\n", cksumoff); - break; - } - - sum += PacketBuf[i]; - } - - if (cksumoff >= 0) - { - recv_total = dataoff - datastart + 1; - dataoff = cksumoff + 2 - datastart + 1; - cksumoff -= datastart - 1; - - memmove(&PacketBuf[1], &PacketBuf[datastart], recv_total); - PacketBuf[0] = '$'; - PacketBuf[recv_total] = 0; - - printf("=== cksumoff=%zi recv_total=%zi datastart=%zi dataoff=%zi\n==> %s\n", - cksumoff, recv_total, datastart, dataoff, PacketBuf); - //break; + Log(LogLevel::Error, "[GDB] Received unknown character %c (%d)\n", curChar, curChar); + return ReadResult::Wut; } } - while (cksumoff < 0) - { - u8* pkt = &PacketBuf[dataoff]; - ssize_t n, blehoff = 0; + return ReadResult::NoPacket; +} - memset(pkt, 0, sizeof(PacketBuf) - dataoff); +Gdb::ReadResult GdbStub::ParseAndSetupPacket() +{ + // This complicated logic seems to be unfortunately necessary + // to handle the case of packet resends when we answered too slowly. + // GDB only expects a single response (as it assumes the previous packet was dropped) + size_t i = 0; + size_t prevPacketStart = SIZE_MAX, prevPacketSize, prevPacketContentSize; + size_t packetStart, packetSize, packetContentSize; + ReadResult result, prevResult; + while (true) + { + ReadResult result = TryParsePacket(i, packetStart, packetSize, packetContentSize); + if (result == ReadResult::NoPacket) + break; + if (result != ReadResult::CmdRecvd && result != ReadResult::Break) + return result; + + // looks like there is a different packet coming up + // so we quit here + if (prevPacketStart != SIZE_MAX && + (packetContentSize != prevPacketContentSize || + memcmp(&RecvBuffer[packetStart], &RecvBuffer[prevPacketStart], prevPacketContentSize) != 0)) + { + Log(LogLevel::Debug, "[GDB] found differing packet further back %zu %zu\n", packetContentSize, prevPacketContentSize); + break; + } + + i = packetStart + packetSize; + prevPacketStart = packetStart; + prevPacketSize = packetSize; + prevPacketContentSize = packetContentSize; + prevResult = result; + } + + if (prevPacketStart != SIZE_MAX) + { + memcpy(&Cmdbuf[0], &RecvBuffer[prevPacketStart], prevPacketContentSize); + Cmdbuf[prevPacketContentSize] = '\0'; + Cmdlen = static_cast(prevPacketContentSize); + + RecvBufferFilled -= prevPacketStart + prevPacketSize; + if (RecvBufferFilled > 0) + memmove(&RecvBuffer[0], &RecvBuffer[prevPacketStart + prevPacketSize], RecvBufferFilled); + + assert(prevResult == ReadResult::CmdRecvd || prevResult == ReadResult::Break); + return prevResult; + } + + assert(result == ReadResult::NoPacket); + return ReadResult::NoPacket; +} + +ReadResult GdbStub::MsgRecv() +{ + { + ReadResult result = ParseAndSetupPacket(); + if (result != ReadResult::NoPacket) + return result; + } + + bool first = true; + while (true) + { int flag = 0; #if MOCKTEST static bool FIRST = false; @@ -142,113 +184,35 @@ ReadResult MsgRecv(int connfd, u8 cmd_dest[/*static GDBPROTO_BUFFER_CAPACITY*/]) #else #ifndef _WIN32 if (first) flag |= MSG_DONTWAIT; - n = recv(connfd, pkt, sizeof(PacketBuf) - dataoff, flag); + ssize_t receivedNum = recv(ConnFd, &RecvBuffer[RecvBufferFilled], sizeof(RecvBuffer) - RecvBufferFilled, flag); + Log(LogLevel::Debug, "[GDB] receiving from stream %d\n", receivedNum); #else // fuck windows - n = recv(connfd, (char*)pkt, sizeof(PacketBuf) - dataoff, flag); + ssize_t receivedNum = recv(ConnFd, (char*)&RecvBuffer[RecvBufferFilled], sizeof(RecvBuffer) - RecvBufferFilled, flag); #endif #endif - if (n <= 0) + if (receivedNum <= 0) { if (first) return ReadResult::NoPacket; else { - Log(LogLevel::Debug, "[GDB] recv() error %zi, errno=%d (%s)\n", n, errno, strerror(errno)); + Log(LogLevel::Debug, "[GDB] recv() error %zi, errno=%d (%s)\n", receivedNum, errno, strerror(errno)); return ReadResult::Eof; } } + RecvBufferFilled += static_cast(receivedNum); - Log(LogLevel::Debug, "[GDB] recv() %zd bytes: '%s' (%02x)\n", n, pkt, pkt[0]); - first = false; - - do - { - if (dataoff == 0) - { - if (pkt[blehoff] == '\x04') return ReadResult::Eof; - else if (pkt[blehoff] == '\x03') return ReadResult::Break; - else if (pkt[blehoff] != '$') - { - ++blehoff; - --n; - } - else break; - - if (n == 0) goto next_outer; - } - } - while (true); - - if (blehoff > 0) - { - memmove(pkt, &pkt[blehoff], n - blehoff + 1); - n -= blehoff - 1; // ??? - } - - recv_total += n; - - Log(LogLevel::Debug, "[GDB] recv() after skipping: n=%zd, recv_total=%zd\n", n, recv_total); - - for (ssize_t i = (dataoff == 0) ? 1 : 0; i < n; ++i) - { - u8 v = pkt[i]; - if (v == '#') - { - cksumoff = dataoff + i + 1; - break; - } - - sum += pkt[i]; - } - - if (cksumoff < 0) - { - // oops, need more data - dataoff += n; - } - - next_outer:; + ReadResult result = ParseAndSetupPacket(); + if (result != ReadResult::NoPacket) + return result; } - - u8 ck = (hex2nyb(PacketBuf[cksumoff+0]) << 4) - | hex2nyb(PacketBuf[cksumoff+1]); - - Log(LogLevel::Debug, "[GDB] got pkt, checksum: %02x vs %02x\n", ck, sum); - - if (ck != sum) - { - //__builtin_trap(); - return ReadResult::CksumErr; - } - - if (cksumoff + 2 > recv_total) - { - Log(LogLevel::Error, "[GDB] BIG MISTAKE: %zi > %zi which shouldn't happen!\n", cksumoff + 2, recv_total); - //__builtin_trap(); - return ReadResult::Wut; - } - else - { - Cmdlen = cksumoff - 2; - memcpy(Cmdbuf, &PacketBuf[1], Cmdlen); - Cmdbuf[Cmdlen] = 0; - - if (cksumoff + 2 < recv_total) { - // huh, we have the start of the next packet - dataoff = recv_total - (cksumoff + 2); - memmove(PacketBuf, &PacketBuf[cksumoff + 2], (size_t)dataoff); - PacketBuf[dataoff] = 0; - Log(LogLevel::Debug, "[GDB] got more: cksumoff=%zd, recvtotal=%zd, remain=%zd\n==> %s\n", cksumoff, recv_total, dataoff, PacketBuf); - } - else dataoff = 0; - } - - return ReadResult::CmdRecvd; } -int SendAck(int connfd) +int GdbStub::SendAck() { + if (NoAck) return 1; + Log(LogLevel::Debug, "[GDB] send ack\n"); u8 v = '+'; #if MOCKTEST @@ -257,14 +221,16 @@ int SendAck(int connfd) #ifdef _WIN32 // fuck windows - return send(connfd, (const char*)&v, 1, 0); + return send(ConnFd, (const char*)&v, 1, 0); #else - return send(connfd, &v, 1, 0); + return send(ConnFd, &v, 1, 0); #endif } -int SendNak(int connfd) +int GdbStub::SendNak() { + if (NoAck) return 1; + Log(LogLevel::Debug, "[GDB] send nak\n"); u8 v = '-'; #if MOCKTEST @@ -273,13 +239,13 @@ int SendNak(int connfd) #ifdef _WIN32 // fuck windows - return send(connfd, (const char*)&v, 1, 0); + return send(ConnFd, (const char*)&v, 1, 0); #else - return send(connfd, &v, 1, 0); + return send(ConnFd, &v, 1, 0); #endif } -int WaitAckBlocking(int connfd, u8* ackp, int to_ms) +int GdbStub::WaitAckBlocking(u8* ackp, int to_ms) { #if MOCKTEST *ackp = '+'; @@ -289,18 +255,18 @@ int WaitAckBlocking(int connfd, u8* ackp, int to_ms) #ifdef _WIN32 fd_set infd, outfd, errfd; FD_ZERO(&infd); FD_ZERO(&outfd); FD_ZERO(&errfd); - FD_SET(connfd, &infd); + FD_SET(ConnFd, &infd); struct timeval to; to.tv_sec = to_ms / 1000; to.tv_usec = (to_ms % 1000) * 1000; - int r = select(connfd+1, &infd, &outfd, &errfd, &to); + int r = select(ConnFd+1, &infd, &outfd, &errfd, &to); - if (FD_ISSET(connfd, &errfd)) return -1; - else if (FD_ISSET(connfd, &infd)) + if (FD_ISSET(ConnFd, &errfd)) return -1; + else if (FD_ISSET(ConnFd, &infd)) { - r = recv(connfd, (char*)ackp, 1, 0); + r = recv(ConnFd, (char*)ackp, 1, 0); if (r < 0) return r; return 0; } @@ -309,7 +275,7 @@ int WaitAckBlocking(int connfd, u8* ackp, int to_ms) #else struct pollfd pfd; - pfd.fd = connfd; + pfd.fd = ConnFd; pfd.events = POLLIN; pfd.revents = 0; @@ -319,14 +285,14 @@ int WaitAckBlocking(int connfd, u8* ackp, int to_ms) if (pfd.revents & (POLLHUP|POLLERR)) return -69; - r = recv(connfd, ackp, 1, 0); + r = recv(ConnFd, ackp, 1, 0); if (r < 0) return r; return (r == 1) ? 0 : -1; #endif } -int Resp(int connfd, const u8* data1, size_t len1, const u8* data2, size_t len2, bool noack) +int GdbStub::Resp(const u8* data1, size_t len1, const u8* data2, size_t len2, bool noack) { u8 cksum = 0; int tries = 0; @@ -359,22 +325,22 @@ int Resp(int connfd, const u8* data1, size_t len1, const u8* data2, size_t len2, ssize_t r; u8 ack; - Log(LogLevel::Debug, "[GDB] send resp: '%s'\n", RespBuf); + Log(LogLevel::Debug, "[GDB] send resp: '%s'\n", &RespBuf[0]); #if MOCKTEST r = totallen+4; #else #ifdef _WIN32 - r = send(connfd, (const char*)RespBuf, totallen+4, 0); + r = send(ConnFd, (const char*)&RespBuf[0], totallen+4, 0); #else - r = send(connfd, RespBuf, totallen+4, 0); + r = send(ConnFd, &RespBuf[0], totallen+4, 0); #endif #endif if (r < 0) return r; if (noack) break; - r = WaitAckBlocking(connfd, &ack, 2000); - //Log(LogLevel::Debug, "[GDB] got ack: '%c'\n", ack); + r = WaitAckBlocking(&ack, 2000); + Log(LogLevel::Debug, "[GDB] got ack: '%c'\n", ack); if (r == 0 && ack == '+') break; ++tries; @@ -386,5 +352,4 @@ int Resp(int connfd, const u8* data1, size_t len1, const u8* data2, size_t len2, } -} diff --git a/src/debug/GdbProto.h b/src/debug/GdbProto.h deleted file mode 100644 index 68122f06..00000000 --- a/src/debug/GdbProto.h +++ /dev/null @@ -1,41 +0,0 @@ - -#ifndef GDBPROTO_H_ -#define GDBPROTO_H_ - -#include -#include - -#include "GdbStub.h" /* class GdbStub */ - - -#define MOCKTEST 0 - - -namespace Gdb { - -using namespace melonDS; -constexpr int GDBPROTO_BUFFER_CAPACITY = 1024+128; - -extern u8 Cmdbuf[GDBPROTO_BUFFER_CAPACITY]; -extern ssize_t Cmdlen; - -namespace Proto { - -extern u8 PacketBuf[GDBPROTO_BUFFER_CAPACITY]; -extern u8 RespBuf[GDBPROTO_BUFFER_CAPACITY+5]; - -Gdb::ReadResult MsgRecv(int connfd, u8 cmd_dest[/*static GDBPROTO_BUFFER_CAPACITY*/]); - -int SendAck(int connfd); -int SendNak(int connfd); - -int Resp(int connfd, const u8* data1, size_t len1, const u8* data2, size_t len2, bool noack); - -int WaitAckBlocking(int connfd, u8* ackp, int to_ms); - -} - -} - -#endif - diff --git a/src/debug/GdbStub.cpp b/src/debug/GdbStub.cpp index 099f27f1..b055794a 100644 --- a/src/debug/GdbStub.cpp +++ b/src/debug/GdbStub.cpp @@ -1,6 +1,6 @@ #ifdef _WIN32 -#include +#include #include #include #endif @@ -23,7 +23,7 @@ #include "../Platform.h" -#include "GdbProto.h" +#include "GdbStub.h" using namespace melonDS; using Platform::Log; @@ -101,6 +101,15 @@ bool GdbStub::Init() Log(LogLevel::Error, "[GDB] err: can't create a socket fd\n"); goto err; } + { + // Make sure the port can be reused immediately after melonDS stops and/or restarts + int enable = 1; +#ifdef _WIN32 + setsockopt(SockFd, SOL_SOCKET, SO_REUSEADDR, (const char*)&enable, sizeof(enable)); +#else + setsockopt(SockFd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); +#endif + } #ifndef __linux__ SocketSetBlocking(SockFd, false); #endif @@ -304,7 +313,7 @@ StubState GdbStub::Poll(bool wait) if (ConnFd < 0) return StubState::NoConn; u8 a; - if (Proto::WaitAckBlocking(ConnFd, &a, 1000) < 0) + if (WaitAckBlocking(&a, 1000) < 0) { Log(LogLevel::Error, "[GDB] inital handshake: didn't receive inital ack!\n"); close(ConnFd); @@ -380,7 +389,7 @@ StubState GdbStub::Poll(bool wait) #endif #endif - ReadResult res = Proto::MsgRecv(ConnFd, Cmdbuf); + ReadResult res = MsgRecv(); switch (res) { @@ -422,11 +431,12 @@ ExecResult GdbStub::SubcmdExec(const u8* cmd, ssize_t len, const SubcmdHandler* // check if prefix matches if (!strncmp((const char*)cmd, handlers[i].SubStr, strlen(handlers[i].SubStr))) { - if (SendAck() < 0) + // ack should have already been sent by CmdExec + /*if (SendAck() < 0) { Log(LogLevel::Error, "[GDB] send packet ack failed!\n"); return ExecResult::NetErr; - } + }*/ return handlers[i].Handler(this, &cmd[strlen(handlers[i].SubStr)], len-strlen(handlers[i].SubStr)); } } @@ -444,7 +454,7 @@ ExecResult GdbStub::SubcmdExec(const u8* cmd, ssize_t len, const SubcmdHandler* ExecResult GdbStub::CmdExec(const CmdHandler* handlers) { - Log(LogLevel::Debug, "[GDB] command in: '%s'\n", Cmdbuf); + Log(LogLevel::Debug, "[GDB] command in: '%s'\n", &Cmdbuf[0]); for (size_t i = 0; handlers[i].Handler != NULL; ++i) { @@ -644,24 +654,13 @@ StubState GdbStub::CheckWatchpt(u32 addr, int kind, bool enter, bool stay) return StubState::CheckNoHit; } -int GdbStub::SendAck() -{ - if (NoAck) return 1; - return Proto::SendAck(ConnFd); -} -int GdbStub::SendNak() -{ - if (NoAck) return 1; - return Proto::SendNak(ConnFd); -} - int GdbStub::Resp(const u8* data1, size_t len1, const u8* data2, size_t len2) { - return Proto::Resp(ConnFd, data1, len1, data2, len2, NoAck); + return Resp(data1, len1, data2, len2, NoAck); } int GdbStub::RespC(const char* data1, size_t len1, const u8* data2, size_t len2) { - return Proto::Resp(ConnFd, (const u8*)data1, len1, data2, len2, NoAck); + return Resp((const u8*)data1, len1, data2, len2, NoAck); } #if defined(__GCC__) || defined(__clang__) __attribute__((__format__(printf, 2/*includes implicit this*/, 3))) @@ -670,19 +669,19 @@ int GdbStub::RespFmt(const char* fmt, ...) { va_list args; va_start(args, fmt); - int r = vsnprintf((char*)&Proto::RespBuf[1], sizeof(Proto::RespBuf)-5, fmt, args); + int r = vsnprintf((char*)&RespBuf[1], sizeof(RespBuf)-5, fmt, args); va_end(args); if (r < 0) return r; - if ((size_t)r >= sizeof(Proto::RespBuf)-5) + if ((size_t)r >= sizeof(RespBuf)-5) { Log(LogLevel::Error, "[GDB] truncated response in send_fmt()! (lost %zd bytes)\n", - (ssize_t)r - (ssize_t)(sizeof(Proto::RespBuf)-5)); - r = sizeof(Proto::RespBuf)-5; + (ssize_t)r - (ssize_t)(sizeof(RespBuf)-5)); + r = sizeof(RespBuf)-5; } - return Resp(&Proto::RespBuf[1], r); + return Resp(&RespBuf[1], r); } int GdbStub::RespStr(const char* str) diff --git a/src/debug/GdbStub.h b/src/debug/GdbStub.h index ace07bf6..3e4a0efe 100644 --- a/src/debug/GdbStub.h +++ b/src/debug/GdbStub.h @@ -3,6 +3,7 @@ #define GDBSTUB_H_ #include +#include #include #include @@ -85,6 +86,8 @@ enum class ExecResult Continue }; +constexpr int GDBPROTO_BUFFER_CAPACITY = 1024+128; + class GdbStub; typedef ExecResult (*GdbProtoCmd)(GdbStub* stub, const u8* cmd, ssize_t len); @@ -140,9 +143,6 @@ public: Gdb::ExecResult CmdExec(const CmdHandler* handlers); public: - int SendAck(); - int SendNak(); - int Resp(const u8* data1, size_t len1, const u8* data2 = NULL, size_t len2 = 0); int RespC(const char* data1, size_t len1, const u8* data2 = NULL, size_t len2 = 0); #if defined(__GCC__) || defined(__clang__) @@ -151,12 +151,26 @@ public: int RespFmt(const char* fmt, ...); int RespStr(const char* str); + inline bool IsConnected() { return ConnFd > 0; } private: void Disconnect(); StubState HandlePacket(); -private: + Gdb::ReadResult MsgRecv(); + + Gdb::ReadResult TryParsePacket(size_t start, size_t& packetStart, size_t& packetSize, size_t& packetContentSize); + Gdb::ReadResult ParseAndSetupPacket(); + + void SetupCommand(size_t packetStart, size_t packetSize); + + int SendAck(); + int SendNak(); + + int Resp(const u8* data1, size_t len1, const u8* data2, size_t len2, bool noack); + + int WaitAckBlocking(u8* ackp, int to_ms); + StubCallbacks* Cb; //struct sockaddr_in server, client; @@ -170,6 +184,13 @@ private: bool StatFlag; bool NoAck; + std::array RecvBuffer; + u32 RecvBufferFilled = 0; + std::array RespBuf; + + std::array Cmdbuf; + ssize_t Cmdlen; + std::map BpList; std::vector WpList; diff --git a/src/fatfs/ff.h b/src/fatfs/ff.h index 1662d836..88ff1f71 100644 --- a/src/fatfs/ff.h +++ b/src/fatfs/ff.h @@ -40,8 +40,8 @@ extern "C" { #include typedef unsigned __int64 QWORD; #include -#define isnan(v) _isnan(v) -#define isinf(v) (!_finite(v)) +//#define isnan(v) _isnan(v) +//#define isinf(v) (!_finite(v)) #elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */ #define FF_INTDEF 2 diff --git a/src/frontend/FrontendUtil.h b/src/frontend/FrontendUtil.h deleted file mode 100644 index 6f09d4b7..00000000 --- a/src/frontend/FrontendUtil.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - Copyright 2016-2023 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 FRONTENDUTIL_H -#define FRONTENDUTIL_H - -#include "types.h" - -#include -#include - -namespace melonDS -{ -class NDS; -} -namespace Frontend -{ -using namespace melonDS; - -enum ScreenLayout -{ - screenLayout_Natural, // top screen above bottom screen always - screenLayout_Horizontal, - screenLayout_Vertical, - screenLayout_Hybrid, - screenLayout_MAX, -}; - -enum ScreenRotation -{ - screenRot_0Deg, - screenRot_90Deg, - screenRot_180Deg, - screenRot_270Deg, - screenRot_MAX, -}; - -enum ScreenSizing -{ - screenSizing_Even, // both screens get same size - screenSizing_EmphTop, // make top screen as big as possible, fit bottom screen in remaining space - screenSizing_EmphBot, - screenSizing_Auto, // not applied in SetupScreenLayout - screenSizing_TopOnly, - screenSizing_BotOnly, - screenSizing_MAX, -}; - -// setup the display layout based on the provided display size and parameters -// * screenWidth/screenHeight: size of the host display -// * screenLayout: how the DS screens are laid out -// * rotation: angle at which the DS screens are presented -// * sizing: how the display size is shared between the two screens -// * screenGap: size of the gap between the two screens in pixels -// * integerScale: force screens to be scaled up at integer scaling factors -// * screenSwap: whether to swap the position of both screens -// * topAspect/botAspect: ratio by which to scale the top and bottom screen respectively -void SetupScreenLayout(int screenWidth, int screenHeight, - ScreenLayout screenLayout, - ScreenRotation rotation, - ScreenSizing sizing, - int screenGap, - bool integerScale, - bool swapScreens, - float topAspect, float botAspect); - -const int MaxScreenTransforms = 3; - -// get a 2x3 transform matrix for each screen and whether it's a top or bottom screen -// note: the transform assumes an origin point at the top left of the display, -// X going right and Y going down -// for each screen the source coordinates should be (0,0) and (256,192) -// 'out' should point to an array of 6*MaxScreenTransforms floats -// 'kind' should point to an array of MaxScreenTransforms ints -// (0 = indicates top screen, 1 = bottom screen) -// returns the amount of screens -int GetScreenTransforms(float* out, int* kind); - -// de-transform the provided host display coordinates to get coordinates -// on the bottom screen -bool GetTouchCoords(int& x, int& y, bool clamp); - - -// initialize the audio utility -void Init_Audio(int outputfreq); - -// get how many samples to read from the core audio output -// based on how many are needed by the frontend (outlen in samples) -int AudioOut_GetNumSamples(int outlen); - -// resample audio from the core audio output to match the frontend's -// output frequency, and apply specified volume -// note: this assumes the output buffer is interleaved stereo -void AudioOut_Resample(s16* inbuf, int inlen, s16* outbuf, int outlen, int volume); - -// feed silence to the microphone input -void Mic_FeedSilence(NDS& nds); - -// feed random noise to the microphone input -void Mic_FeedNoise(NDS& nds); - -// feed an external buffer to the microphone input -// buffer should be mono -void Mic_FeedExternalBuffer(NDS& nds); -void Mic_SetExternalBuffer(s16* buffer, u32 len); - -} - -#endif // FRONTENDUTIL_H diff --git a/src/frontend/Util_Video.cpp b/src/frontend/ScreenLayout.cpp similarity index 93% rename from src/frontend/Util_Video.cpp rename to src/frontend/ScreenLayout.cpp index e772be9a..bb9a60cb 100644 --- a/src/frontend/Util_Video.cpp +++ b/src/frontend/ScreenLayout.cpp @@ -1,553 +1,548 @@ -/* - Copyright 2016-2023 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 -#include -#include - -#include "FrontendUtil.h" - - -namespace Frontend -{ - -float TopScreenMtx[6]; -float BotScreenMtx[6]; -float HybScreenMtx[6]; -float TouchMtx[6]; -float HybTouchMtx[6]; -bool TopEnable; -bool BotEnable; -bool HybEnable; -int HybScreen; -int HybPrevTouchScreen; // 0:unknown, 1:buttom screen, 2:hybrid screen - -void M23_Identity(float* m) -{ - m[0] = 1; m[1] = 0; - m[2] = 0; m[3] = 1; - m[4] = 0; m[5] = 0; -} - -void M23_Scale(float* m, float s) -{ - m[0] *= s; m[1] *= s; - m[2] *= s; m[3] *= s; - m[4] *= s; m[5] *= s; -} - -void M23_Scale(float* m, float x, float y) -{ - m[0] *= x; m[1] *= y; - m[2] *= x; m[3] *= y; - m[4] *= x; m[5] *= y; -} - -void M23_RotateFast(float* m, int angle) -{ - if (angle == 0) return; - - float temp[4]; memcpy(temp, m, sizeof(float)*4); - - switch (angle) - { - case 1: // 90 - m[0] = temp[2]; - m[1] = temp[3]; - m[2] = -temp[0]; - m[3] = -temp[1]; - break; - - case 2: // 180 - m[0] = -temp[0]; - m[1] = -temp[1]; - m[2] = -temp[2]; - m[3] = -temp[3]; - break; - - case 3: // 270 - m[0] = -temp[2]; - m[1] = -temp[3]; - m[2] = temp[0]; - m[3] = temp[1]; - break; - } -} - -void M23_Translate(float* m, float tx, float ty) -{ - m[4] += tx; - m[5] += ty; -} - -void M23_Multiply(float* m, float* _a, float* _b) -{ - float a[6]; memcpy(a, _a, 6*sizeof(float)); - float b[6]; memcpy(b, _b, 6*sizeof(float)); - - m[0] = (a[0] * b[0]) + (a[2] * b[1]); - m[1] = (a[1] * b[0]) + (a[3] * b[1]); - - m[2] = (a[0] * b[2]) + (a[2] * b[3]); - m[3] = (a[1] * b[2]) + (a[3] * b[3]); - - m[4] = (a[0] * b[4]) + (a[2] * b[5]) + a[4]; - m[5] = (a[1] * b[4]) + (a[3] * b[5]) + a[5]; -} - -void M23_Transform(float* m, float& x, float& y) -{ - float vx = x; - float vy = y; - - x = (vx * m[0]) + (vy * m[2]) + m[4]; - y = (vx * m[1]) + (vy * m[3]) + m[5]; -} - - -void SetupScreenLayout(int screenWidth, int screenHeight, - ScreenLayout screenLayout, - ScreenRotation rotation, - ScreenSizing sizing, - int screenGap, - bool integerScale, - bool swapScreens, - float topAspect, float botAspect) -{ - HybEnable = screenLayout == 3; - if (HybEnable) - { - screenLayout = screenLayout_Natural; - sizing = screenSizing_Even; - HybScreen = swapScreens ? 1 : 0; - swapScreens = false; - topAspect = botAspect = 1; - HybPrevTouchScreen = 0; - } - - float refpoints[6][2] = - { - {0, 0}, {256, 192}, - {0, 0}, {256, 192}, - {0, 0}, {256, 192} - }; - - int layout = screenLayout == screenLayout_Natural - ? rotation % 2 - : screenLayout - 1; - - float botScale = 1; - float hybScale = 1; - float botTrans[4] = {0}; - float hybTrans[2] = {0}; - - M23_Identity(TopScreenMtx); - M23_Identity(BotScreenMtx); - M23_Identity(HybScreenMtx); - - M23_Translate(TopScreenMtx, -256/2, -192/2); - M23_Translate(BotScreenMtx, -256/2, -192/2); - - M23_Scale(TopScreenMtx, topAspect, 1); - M23_Scale(BotScreenMtx, botAspect, 1); - - // rotation - { - float rotmtx[6]; - M23_Identity(rotmtx); - - M23_RotateFast(rotmtx, rotation); - M23_Multiply(TopScreenMtx, rotmtx, TopScreenMtx); - M23_Multiply(BotScreenMtx, rotmtx, BotScreenMtx); - M23_Multiply(HybScreenMtx, rotmtx, HybScreenMtx); - - M23_Transform(TopScreenMtx, refpoints[0][0], refpoints[0][1]); - M23_Transform(TopScreenMtx, refpoints[1][0], refpoints[1][1]); - M23_Transform(BotScreenMtx, refpoints[2][0], refpoints[2][1]); - M23_Transform(BotScreenMtx, refpoints[3][0], refpoints[3][1]); - } - - int posRefPointOffset = 0; - int posRefPointCount = HybEnable ? 6 : 4; - - if (sizing == screenSizing_TopOnly || sizing == screenSizing_BotOnly) - { - float* mtx = sizing == screenSizing_TopOnly ? TopScreenMtx : BotScreenMtx; - int primOffset = sizing == screenSizing_TopOnly ? 0 : 2; - int secOffset = sizing == screenSizing_BotOnly ? 2 : 0; - - float hSize = fabsf(refpoints[primOffset][0] - refpoints[primOffset+1][0]); - float vSize = fabsf(refpoints[primOffset][1] - refpoints[primOffset+1][1]); - - float scale = std::min(screenWidth / hSize, screenHeight / vSize); - if (integerScale) - scale = floorf(scale); - - TopEnable = sizing == screenSizing_TopOnly; - BotEnable = sizing == screenSizing_BotOnly; - botScale = scale; - - M23_Scale(mtx, scale); - refpoints[primOffset][0] *= scale; - refpoints[primOffset][1] *= scale; - refpoints[primOffset+1][0] *= scale; - refpoints[primOffset+1][1] *= scale; - - posRefPointOffset = primOffset; - posRefPointCount = 2; - } - else - { - TopEnable = BotEnable = true; - - // move screens apart - { - int idx = layout == 0 ? 1 : 0; - - bool moveV = rotation % 2 == layout; - - float offsetBot = (moveV ? 192.0 : 256.0 * botAspect) / 2.0 + screenGap / 2.0; - float offsetTop = -((moveV ? 192.0 : 256.0 * topAspect) / 2.0 + screenGap / 2.0); - - if ((rotation == 1 || rotation == 2) ^ swapScreens) - { - offsetTop *= -1; - offsetBot *= -1; - } - - M23_Translate(TopScreenMtx, (idx==0)?offsetTop:0, (idx==1)?offsetTop:0); - M23_Translate(BotScreenMtx, (idx==0)?offsetBot:0, (idx==1)?offsetBot:0); - - refpoints[0][idx] += offsetTop; - refpoints[1][idx] += offsetTop; - refpoints[2][idx] += offsetBot; - refpoints[3][idx] += offsetBot; - - botTrans[idx] = offsetBot; - } - - // scale - { - if (sizing == screenSizing_Even) - { - float minX = refpoints[0][0], maxX = minX; - float minY = refpoints[0][1], maxY = minY; - - for (int i = 1; i < 4; i++) - { - minX = std::min(minX, refpoints[i][0]); - minY = std::min(minY, refpoints[i][1]); - maxX = std::max(maxX, refpoints[i][0]); - maxY = std::max(maxY, refpoints[i][1]); - } - - float hSize = maxX - minX; - float vSize = maxY - minY; - - if (HybEnable) - { - hybScale = layout == 0 - ? (4 * vSize) / (3 * hSize) - : (4 * hSize) / (3 * vSize); - if (layout == 0) - hSize += (vSize * 4) / 3; - else - vSize += (hSize * 4) / 3; - } - - // scale evenly - float scale = std::min(screenWidth / hSize, screenHeight / vSize); - - if (integerScale) - scale = floor(scale); - - hybScale *= scale; - - M23_Scale(TopScreenMtx, scale); - M23_Scale(BotScreenMtx, scale); - M23_Scale(HybScreenMtx, hybScale); - - for (int i = 0; i < 4; i++) - { - refpoints[i][0] *= scale; - refpoints[i][1] *= scale; - } - - botScale = scale; - - // move screens aside - if (HybEnable) - { - float hybWidth = layout == 0 - ? (scale * vSize * 4) / 3 - : (scale * hSize * 4) / 3; - - if (rotation > screenRot_90Deg) - hybWidth *= -1; - - M23_Translate(TopScreenMtx, (layout==0)?hybWidth:0, (layout==1)?hybWidth:0); - M23_Translate(BotScreenMtx, (layout==0)?hybWidth:0, (layout==1)?hybWidth:0); - refpoints[0][layout] += hybWidth; - refpoints[1][layout] += hybWidth; - refpoints[2][layout] += hybWidth; - refpoints[3][layout] += hybWidth; - - botTrans[2+layout] += hybWidth; - - hybTrans[0] = scale * (rotation == screenRot_0Deg || rotation == screenRot_270Deg ? minX : maxX); - hybTrans[1] = scale * (rotation == screenRot_0Deg || rotation == screenRot_90Deg ? minY : maxY); - M23_Translate(HybScreenMtx, hybTrans[0], hybTrans[1]); - - M23_Transform(HybScreenMtx, refpoints[4][0], refpoints[4][1]); - M23_Transform(HybScreenMtx, refpoints[5][0], refpoints[5][1]); - } - } - else - { - int primOffset = (sizing == screenSizing_EmphTop) ? 0 : 2; - int secOffset = (sizing == screenSizing_EmphTop) ? 2 : 0; - float* primMtx = (sizing == screenSizing_EmphTop) ? TopScreenMtx : BotScreenMtx; - float* secMtx = (sizing == screenSizing_EmphTop) ? BotScreenMtx : TopScreenMtx; - - float primMinX = refpoints[primOffset][0], primMaxX = primMinX; - float primMinY = refpoints[primOffset][1], primMaxY = primMinY; - float secMinX = refpoints[secOffset][0], secMaxX = secMinX; - float secMinY = refpoints[secOffset][1], secMaxY = secMinY; - - primMinX = std::min(primMinX, refpoints[primOffset+1][0]); - primMinY = std::min(primMinY, refpoints[primOffset+1][1]); - primMaxX = std::max(primMaxX, refpoints[primOffset+1][0]); - primMaxY = std::max(primMaxY, refpoints[primOffset+1][1]); - - secMinX = std::min(secMinX, refpoints[secOffset+1][0]); - secMinY = std::min(secMinY, refpoints[secOffset+1][1]); - secMaxX = std::max(secMaxX, refpoints[secOffset+1][0]); - secMaxY = std::max(secMaxY, refpoints[secOffset+1][1]); - - float primHSize = layout == 1 ? std::max(primMaxX, -primMinX) : primMaxX - primMinX; - float primVSize = layout == 0 ? std::max(primMaxY, -primMinY) : primMaxY - primMinY; - - float secHSize = layout == 1 ? std::max(secMaxX, -secMinX) : secMaxX - secMinX; - float secVSize = layout == 0 ? std::max(secMaxY, -secMinY) : secMaxY - secMinY; - - float primScale = std::min(screenWidth / primHSize, screenHeight / primVSize); - float secScale = 1.f; - - if (integerScale) - primScale = floorf(primScale); - - if (layout == 0) - { - if (screenHeight - primVSize * primScale < secVSize) - primScale = std::min(screenWidth / primHSize, (screenHeight - secVSize) / primVSize); - else - secScale = std::min((screenHeight - primVSize * primScale) / secVSize, screenWidth / secHSize); - } - else - { - if (screenWidth - primHSize * primScale < secHSize) - primScale = std::min((screenWidth - secHSize) / primHSize, screenHeight / primVSize); - else - secScale = std::min((screenWidth - primHSize * primScale) / secHSize, screenHeight / secVSize); - } - - if (integerScale) - { - primScale = floorf(primScale); - secScale = floorf(secScale); - } - - M23_Scale(primMtx, primScale); - M23_Scale(secMtx, secScale); - - refpoints[primOffset+0][0] *= primScale; - refpoints[primOffset+0][1] *= primScale; - refpoints[primOffset+1][0] *= primScale; - refpoints[primOffset+1][1] *= primScale; - refpoints[secOffset+0][0] *= secScale; - refpoints[secOffset+0][1] *= secScale; - refpoints[secOffset+1][0] *= secScale; - refpoints[secOffset+1][1] *= secScale; - - botScale = (sizing == screenSizing_EmphTop) ? secScale : primScale; - } - } - } - - // position - { - float minX = refpoints[posRefPointOffset][0], maxX = minX; - float minY = refpoints[posRefPointOffset][1], maxY = minY; - - for (int i = posRefPointOffset + 1; i < posRefPointOffset + posRefPointCount; i++) - { - minX = std::min(minX, refpoints[i][0]); - minY = std::min(minY, refpoints[i][1]); - maxX = std::max(maxX, refpoints[i][0]); - maxY = std::max(maxY, refpoints[i][1]); - } - - float width = maxX - minX; - float height = maxY - minY; - - float tx = (screenWidth/2) - (width/2) - minX; - float ty = (screenHeight/2) - (height/2) - minY; - - M23_Translate(TopScreenMtx, tx, ty); - M23_Translate(BotScreenMtx, tx, ty); - M23_Translate(HybScreenMtx, tx, ty); - - botTrans[2] += tx; botTrans[3] += ty; - hybTrans[0] += tx; hybTrans[1] += ty; - } - - // prepare a 'reverse' matrix for the touchscreen - // this matrix undoes the transforms applied to the bottom screen - // and can be used to calculate touchscreen coords from host screen coords - if (BotEnable) - { - M23_Identity(TouchMtx); - - M23_Translate(TouchMtx, -botTrans[2], -botTrans[3]); - M23_Scale(TouchMtx, 1.f / botScale); - M23_Translate(TouchMtx, -botTrans[0], -botTrans[1]); - - float rotmtx[6]; - M23_Identity(rotmtx); - M23_RotateFast(rotmtx, (4-rotation) & 3); - M23_Multiply(TouchMtx, rotmtx, TouchMtx); - - M23_Scale(TouchMtx, 1.f/botAspect, 1); - M23_Translate(TouchMtx, 256/2, 192/2); - - if (HybEnable && HybScreen == 1) - { - M23_Identity(HybTouchMtx); - - M23_Translate(HybTouchMtx, -hybTrans[0], -hybTrans[1]); - M23_Scale(HybTouchMtx, 1.f/hybScale); - M23_Multiply(HybTouchMtx, rotmtx, HybTouchMtx); - } - } -} - -int GetScreenTransforms(float* out, int* kind) -{ - int num = 0; - if (TopEnable) - { - memcpy(out + 6*num, TopScreenMtx, sizeof(TopScreenMtx)); - kind[num++] = 0; - } - if (BotEnable) - { - memcpy(out + 6*num, BotScreenMtx, sizeof(BotScreenMtx)); - kind[num++] = 1; - } - if (HybEnable) - { - memcpy(out + 6*num, HybScreenMtx, sizeof(HybScreenMtx)); - kind[num++] = HybScreen; - } - return num; -} - -bool GetTouchCoords(int& x, int& y, bool clamp) -{ - if (HybEnable && HybScreen == 1) - { - float vx = x; - float vy = y; - float hvx = x; - float hvy = y; - - M23_Transform(TouchMtx, vx, vy); - M23_Transform(HybTouchMtx, hvx, hvy); - - if (clamp) - { - if (HybPrevTouchScreen == 1) - { - x = std::clamp((int)vx, 0, 255); - y = std::clamp((int)vy, 0, 191); - - return true; - } - if (HybPrevTouchScreen == 2) - { - x = std::clamp((int)hvx, 0, 255); - y = std::clamp((int)hvy, 0, 191); - - return true; - } - } - else - { - if (vx >= 0 && vx < 256 && vy >= 0 && vy < 192) - { - HybPrevTouchScreen = 1; - - x = (int)vx; - y = (int)vy; - - return true; - } - if (hvx >= 0 && hvx < 256 && hvy >= 0 && hvy < 192) - { - HybPrevTouchScreen = 2; - - x = (int)hvx; - y = (int)hvy; - - return true; - } - } - } - else if (BotEnable) - { - float vx = x; - float vy = y; - - M23_Transform(TouchMtx, vx, vy); - - if (clamp) - { - x = std::clamp((int)vx, 0, 255); - y = std::clamp((int)vy, 0, 191); - - return true; - } - else - { - if (vx >= 0 && vx < 256 && vy >= 0 && vy < 192) - { - x = (int)vx; - y = (int)vy; - - return true; - } - } - } - - return false; -} - -} - +/* + Copyright 2016-2024 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 + +#include "ScreenLayout.h" + + +void M23_Identity(float* m) +{ + m[0] = 1; m[1] = 0; + m[2] = 0; m[3] = 1; + m[4] = 0; m[5] = 0; +} + +void M23_Scale(float* m, float s) +{ + m[0] *= s; m[1] *= s; + m[2] *= s; m[3] *= s; + m[4] *= s; m[5] *= s; +} + +void M23_Scale(float* m, float x, float y) +{ + m[0] *= x; m[1] *= y; + m[2] *= x; m[3] *= y; + m[4] *= x; m[5] *= y; +} + +void M23_RotateFast(float* m, int angle) +{ + if (angle == 0) return; + + float temp[4]; memcpy(temp, m, sizeof(float)*4); + + switch (angle) + { + case 1: // 90 + m[0] = temp[2]; + m[1] = temp[3]; + m[2] = -temp[0]; + m[3] = -temp[1]; + break; + + case 2: // 180 + m[0] = -temp[0]; + m[1] = -temp[1]; + m[2] = -temp[2]; + m[3] = -temp[3]; + break; + + case 3: // 270 + m[0] = -temp[2]; + m[1] = -temp[3]; + m[2] = temp[0]; + m[3] = temp[1]; + break; + } +} + +void M23_Translate(float* m, float tx, float ty) +{ + m[4] += tx; + m[5] += ty; +} + +void M23_Multiply(float* m, float* _a, float* _b) +{ + float a[6]; memcpy(a, _a, 6*sizeof(float)); + float b[6]; memcpy(b, _b, 6*sizeof(float)); + + m[0] = (a[0] * b[0]) + (a[2] * b[1]); + m[1] = (a[1] * b[0]) + (a[3] * b[1]); + + m[2] = (a[0] * b[2]) + (a[2] * b[3]); + m[3] = (a[1] * b[2]) + (a[3] * b[3]); + + m[4] = (a[0] * b[4]) + (a[2] * b[5]) + a[4]; + m[5] = (a[1] * b[4]) + (a[3] * b[5]) + a[5]; +} + +void M23_Transform(float* m, float& x, float& y) +{ + float vx = x; + float vy = y; + + x = (vx * m[0]) + (vy * m[2]) + m[4]; + y = (vx * m[1]) + (vy * m[3]) + m[5]; +} + + +ScreenLayout::ScreenLayout() +{ + M23_Identity(TopScreenMtx); + M23_Identity(BotScreenMtx); + M23_Identity(HybScreenMtx); + M23_Identity(TouchMtx); + M23_Identity(HybTouchMtx); + TopEnable = true; + BotEnable = true; + HybEnable = false; + HybScreen = 0; + HybPrevTouchScreen = 0; +} + +void ScreenLayout::Setup(int screenWidth, int screenHeight, + ScreenLayoutType screenLayout, + ScreenRotation rotation, + ScreenSizing sizing, + int screenGap, + bool integerScale, + bool swapScreens, + float topAspect, float botAspect) +{ + HybEnable = screenLayout == 3; + if (HybEnable) + { + screenLayout = screenLayout_Natural; + sizing = screenSizing_Even; + HybScreen = swapScreens ? 1 : 0; + swapScreens = false; + topAspect = botAspect = 1; + HybPrevTouchScreen = 0; + } + + float refpoints[6][2] = + { + {0, 0}, {256, 192}, + {0, 0}, {256, 192}, + {0, 0}, {256, 192} + }; + + int layout = screenLayout == screenLayout_Natural + ? rotation % 2 + : screenLayout - 1; + + float botScale = 1; + float hybScale = 1; + float botTrans[4] = {0}; + float hybTrans[2] = {0}; + + M23_Identity(TopScreenMtx); + M23_Identity(BotScreenMtx); + M23_Identity(HybScreenMtx); + + M23_Translate(TopScreenMtx, -256/2, -192/2); + M23_Translate(BotScreenMtx, -256/2, -192/2); + + M23_Scale(TopScreenMtx, topAspect, 1); + M23_Scale(BotScreenMtx, botAspect, 1); + + // rotation + { + float rotmtx[6]; + M23_Identity(rotmtx); + + M23_RotateFast(rotmtx, rotation); + M23_Multiply(TopScreenMtx, rotmtx, TopScreenMtx); + M23_Multiply(BotScreenMtx, rotmtx, BotScreenMtx); + M23_Multiply(HybScreenMtx, rotmtx, HybScreenMtx); + + M23_Transform(TopScreenMtx, refpoints[0][0], refpoints[0][1]); + M23_Transform(TopScreenMtx, refpoints[1][0], refpoints[1][1]); + M23_Transform(BotScreenMtx, refpoints[2][0], refpoints[2][1]); + M23_Transform(BotScreenMtx, refpoints[3][0], refpoints[3][1]); + } + + int posRefPointOffset = 0; + int posRefPointCount = HybEnable ? 6 : 4; + + if (sizing == screenSizing_TopOnly || sizing == screenSizing_BotOnly) + { + float* mtx = sizing == screenSizing_TopOnly ? TopScreenMtx : BotScreenMtx; + int primOffset = sizing == screenSizing_TopOnly ? 0 : 2; + int secOffset = sizing == screenSizing_BotOnly ? 2 : 0; + + float hSize = fabsf(refpoints[primOffset][0] - refpoints[primOffset+1][0]); + float vSize = fabsf(refpoints[primOffset][1] - refpoints[primOffset+1][1]); + + float scale = std::min(screenWidth / hSize, screenHeight / vSize); + if (integerScale) + scale = floorf(scale); + + TopEnable = sizing == screenSizing_TopOnly; + BotEnable = sizing == screenSizing_BotOnly; + botScale = scale; + + M23_Scale(mtx, scale); + refpoints[primOffset][0] *= scale; + refpoints[primOffset][1] *= scale; + refpoints[primOffset+1][0] *= scale; + refpoints[primOffset+1][1] *= scale; + + posRefPointOffset = primOffset; + posRefPointCount = 2; + } + else + { + TopEnable = BotEnable = true; + + // move screens apart + { + int idx = layout == 0 ? 1 : 0; + + bool moveV = rotation % 2 == layout; + + float offsetBot = (moveV ? 192.0 : 256.0 * botAspect) / 2.0 + screenGap / 2.0; + float offsetTop = -((moveV ? 192.0 : 256.0 * topAspect) / 2.0 + screenGap / 2.0); + + if ((rotation == 1 || rotation == 2) ^ swapScreens) + { + offsetTop *= -1; + offsetBot *= -1; + } + + M23_Translate(TopScreenMtx, (idx==0)?offsetTop:0, (idx==1)?offsetTop:0); + M23_Translate(BotScreenMtx, (idx==0)?offsetBot:0, (idx==1)?offsetBot:0); + + refpoints[0][idx] += offsetTop; + refpoints[1][idx] += offsetTop; + refpoints[2][idx] += offsetBot; + refpoints[3][idx] += offsetBot; + + botTrans[idx] = offsetBot; + } + + // scale + { + if (sizing == screenSizing_Even) + { + float minX = refpoints[0][0], maxX = minX; + float minY = refpoints[0][1], maxY = minY; + + for (int i = 1; i < 4; i++) + { + minX = std::min(minX, refpoints[i][0]); + minY = std::min(minY, refpoints[i][1]); + maxX = std::max(maxX, refpoints[i][0]); + maxY = std::max(maxY, refpoints[i][1]); + } + + float hSize = maxX - minX; + float vSize = maxY - minY; + + if (HybEnable) + { + hybScale = layout == 0 + ? (4 * vSize) / (3 * hSize) + : (4 * hSize) / (3 * vSize); + if (layout == 0) + hSize += (vSize * 4) / 3; + else + vSize += (hSize * 4) / 3; + } + + // scale evenly + float scale = std::min(screenWidth / hSize, screenHeight / vSize); + + if (integerScale) + scale = floor(scale); + + hybScale *= scale; + + M23_Scale(TopScreenMtx, scale); + M23_Scale(BotScreenMtx, scale); + M23_Scale(HybScreenMtx, hybScale); + + for (int i = 0; i < 4; i++) + { + refpoints[i][0] *= scale; + refpoints[i][1] *= scale; + } + + botScale = scale; + + // move screens aside + if (HybEnable) + { + float hybWidth = layout == 0 + ? (scale * vSize * 4) / 3 + : (scale * hSize * 4) / 3; + + if (rotation > screenRot_90Deg) + hybWidth *= -1; + + M23_Translate(TopScreenMtx, (layout==0)?hybWidth:0, (layout==1)?hybWidth:0); + M23_Translate(BotScreenMtx, (layout==0)?hybWidth:0, (layout==1)?hybWidth:0); + refpoints[0][layout] += hybWidth; + refpoints[1][layout] += hybWidth; + refpoints[2][layout] += hybWidth; + refpoints[3][layout] += hybWidth; + + botTrans[2+layout] += hybWidth; + + hybTrans[0] = scale * (rotation == screenRot_0Deg || rotation == screenRot_270Deg ? minX : maxX); + hybTrans[1] = scale * (rotation == screenRot_0Deg || rotation == screenRot_90Deg ? minY : maxY); + M23_Translate(HybScreenMtx, hybTrans[0], hybTrans[1]); + + M23_Transform(HybScreenMtx, refpoints[4][0], refpoints[4][1]); + M23_Transform(HybScreenMtx, refpoints[5][0], refpoints[5][1]); + } + } + else + { + int primOffset = (sizing == screenSizing_EmphTop) ? 0 : 2; + int secOffset = (sizing == screenSizing_EmphTop) ? 2 : 0; + float* primMtx = (sizing == screenSizing_EmphTop) ? TopScreenMtx : BotScreenMtx; + float* secMtx = (sizing == screenSizing_EmphTop) ? BotScreenMtx : TopScreenMtx; + + float primMinX = refpoints[primOffset][0], primMaxX = primMinX; + float primMinY = refpoints[primOffset][1], primMaxY = primMinY; + float secMinX = refpoints[secOffset][0], secMaxX = secMinX; + float secMinY = refpoints[secOffset][1], secMaxY = secMinY; + + primMinX = std::min(primMinX, refpoints[primOffset+1][0]); + primMinY = std::min(primMinY, refpoints[primOffset+1][1]); + primMaxX = std::max(primMaxX, refpoints[primOffset+1][0]); + primMaxY = std::max(primMaxY, refpoints[primOffset+1][1]); + + secMinX = std::min(secMinX, refpoints[secOffset+1][0]); + secMinY = std::min(secMinY, refpoints[secOffset+1][1]); + secMaxX = std::max(secMaxX, refpoints[secOffset+1][0]); + secMaxY = std::max(secMaxY, refpoints[secOffset+1][1]); + + float primHSize = layout == 1 ? std::max(primMaxX, -primMinX) : primMaxX - primMinX; + float primVSize = layout == 0 ? std::max(primMaxY, -primMinY) : primMaxY - primMinY; + + float secHSize = layout == 1 ? std::max(secMaxX, -secMinX) : secMaxX - secMinX; + float secVSize = layout == 0 ? std::max(secMaxY, -secMinY) : secMaxY - secMinY; + + float primScale = std::min(screenWidth / primHSize, screenHeight / primVSize); + float secScale = 1.f; + + if (integerScale) + primScale = floorf(primScale); + + if (layout == 0) + { + if (screenHeight - primVSize * primScale < secVSize) + primScale = std::min(screenWidth / primHSize, (screenHeight - secVSize) / primVSize); + else + secScale = std::min((screenHeight - primVSize * primScale) / secVSize, screenWidth / secHSize); + } + else + { + if (screenWidth - primHSize * primScale < secHSize) + primScale = std::min((screenWidth - secHSize) / primHSize, screenHeight / primVSize); + else + secScale = std::min((screenWidth - primHSize * primScale) / secHSize, screenHeight / secVSize); + } + + if (integerScale) + { + primScale = floorf(primScale); + secScale = floorf(secScale); + } + + M23_Scale(primMtx, primScale); + M23_Scale(secMtx, secScale); + + refpoints[primOffset+0][0] *= primScale; + refpoints[primOffset+0][1] *= primScale; + refpoints[primOffset+1][0] *= primScale; + refpoints[primOffset+1][1] *= primScale; + refpoints[secOffset+0][0] *= secScale; + refpoints[secOffset+0][1] *= secScale; + refpoints[secOffset+1][0] *= secScale; + refpoints[secOffset+1][1] *= secScale; + + botScale = (sizing == screenSizing_EmphTop) ? secScale : primScale; + } + } + } + + // position + { + float minX = refpoints[posRefPointOffset][0], maxX = minX; + float minY = refpoints[posRefPointOffset][1], maxY = minY; + + for (int i = posRefPointOffset + 1; i < posRefPointOffset + posRefPointCount; i++) + { + minX = std::min(minX, refpoints[i][0]); + minY = std::min(minY, refpoints[i][1]); + maxX = std::max(maxX, refpoints[i][0]); + maxY = std::max(maxY, refpoints[i][1]); + } + + float width = maxX - minX; + float height = maxY - minY; + + float tx = (screenWidth/2) - (width/2) - minX; + float ty = (screenHeight/2) - (height/2) - minY; + + M23_Translate(TopScreenMtx, tx, ty); + M23_Translate(BotScreenMtx, tx, ty); + M23_Translate(HybScreenMtx, tx, ty); + + botTrans[2] += tx; botTrans[3] += ty; + hybTrans[0] += tx; hybTrans[1] += ty; + } + + // prepare a 'reverse' matrix for the touchscreen + // this matrix undoes the transforms applied to the bottom screen + // and can be used to calculate touchscreen coords from host screen coords + if (BotEnable) + { + M23_Identity(TouchMtx); + + M23_Translate(TouchMtx, -botTrans[2], -botTrans[3]); + M23_Scale(TouchMtx, 1.f / botScale); + M23_Translate(TouchMtx, -botTrans[0], -botTrans[1]); + + float rotmtx[6]; + M23_Identity(rotmtx); + M23_RotateFast(rotmtx, (4-rotation) & 3); + M23_Multiply(TouchMtx, rotmtx, TouchMtx); + + M23_Scale(TouchMtx, 1.f/botAspect, 1); + M23_Translate(TouchMtx, 256/2, 192/2); + + if (HybEnable && HybScreen == 1) + { + M23_Identity(HybTouchMtx); + + M23_Translate(HybTouchMtx, -hybTrans[0], -hybTrans[1]); + M23_Scale(HybTouchMtx, 1.f/hybScale); + M23_Multiply(HybTouchMtx, rotmtx, HybTouchMtx); + } + } +} + +int ScreenLayout::GetScreenTransforms(float* out, int* kind) +{ + int num = 0; + if (TopEnable) + { + memcpy(out + 6*num, TopScreenMtx, sizeof(TopScreenMtx)); + kind[num++] = 0; + } + if (BotEnable) + { + memcpy(out + 6*num, BotScreenMtx, sizeof(BotScreenMtx)); + kind[num++] = 1; + } + if (HybEnable) + { + memcpy(out + 6*num, HybScreenMtx, sizeof(HybScreenMtx)); + kind[num++] = HybScreen; + } + return num; +} + +bool ScreenLayout::GetTouchCoords(int& x, int& y, bool clamp) +{ + if (HybEnable && HybScreen == 1) + { + float vx = x; + float vy = y; + float hvx = x; + float hvy = y; + + M23_Transform(TouchMtx, vx, vy); + M23_Transform(HybTouchMtx, hvx, hvy); + + if (clamp) + { + if (HybPrevTouchScreen == 1) + { + x = std::clamp((int)vx, 0, 255); + y = std::clamp((int)vy, 0, 191); + + return true; + } + if (HybPrevTouchScreen == 2) + { + x = std::clamp((int)hvx, 0, 255); + y = std::clamp((int)hvy, 0, 191); + + return true; + } + } + else + { + if (vx >= 0 && vx < 256 && vy >= 0 && vy < 192) + { + HybPrevTouchScreen = 1; + + x = (int)vx; + y = (int)vy; + + return true; + } + if (hvx >= 0 && hvx < 256 && hvy >= 0 && hvy < 192) + { + HybPrevTouchScreen = 2; + + x = (int)hvx; + y = (int)hvy; + + return true; + } + } + } + else if (BotEnable) + { + float vx = x; + float vy = y; + + M23_Transform(TouchMtx, vx, vy); + + if (clamp) + { + x = std::clamp((int)vx, 0, 255); + y = std::clamp((int)vy, 0, 191); + + return true; + } + else + { + if (vx >= 0 && vx < 256 && vy >= 0 && vy < 192) + { + x = (int)vx; + y = (int)vy; + + return true; + } + } + } + + return false; +} diff --git a/src/frontend/ScreenLayout.h b/src/frontend/ScreenLayout.h new file mode 100644 index 00000000..7b69bd40 --- /dev/null +++ b/src/frontend/ScreenLayout.h @@ -0,0 +1,104 @@ +/* + Copyright 2016-2024 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 SCREENLAYOUT_H +#define SCREENLAYOUT_H + +enum ScreenLayoutType +{ + screenLayout_Natural, // top screen above bottom screen always + screenLayout_Horizontal, + screenLayout_Vertical, + screenLayout_Hybrid, + screenLayout_MAX, +}; + +enum ScreenRotation +{ + screenRot_0Deg, + screenRot_90Deg, + screenRot_180Deg, + screenRot_270Deg, + screenRot_MAX, +}; + +enum ScreenSizing +{ + screenSizing_Even, // both screens get same size + screenSizing_EmphTop, // make top screen as big as possible, fit bottom screen in remaining space + screenSizing_EmphBot, + screenSizing_Auto, // not applied in SetupScreenLayout + screenSizing_TopOnly, + screenSizing_BotOnly, + screenSizing_MAX, +}; + +const int kMaxScreenTransforms = 3; + +class ScreenLayout +{ +public: + ScreenLayout(); + ~ScreenLayout() {} + + // setup the display layout based on the provided display size and parameters + // * screenWidth/screenHeight: size of the host display + // * screenLayout: how the DS screens are laid out + // * rotation: angle at which the DS screens are presented + // * sizing: how the display size is shared between the two screens + // * screenGap: size of the gap between the two screens in pixels + // * integerScale: force screens to be scaled up at integer scaling factors + // * screenSwap: whether to swap the position of both screens + // * topAspect/botAspect: ratio by which to scale the top and bottom screen respectively + void Setup(int screenWidth, int screenHeight, + ScreenLayoutType screenLayout, + ScreenRotation rotation, + ScreenSizing sizing, + int screenGap, + bool integerScale, + bool swapScreens, + float topAspect, float botAspect); + + // get a 2x3 transform matrix for each screen and whether it's a top or bottom screen + // note: the transform assumes an origin point at the top left of the display, + // X going right and Y going down + // for each screen the source coordinates should be (0,0) and (256,192) + // 'out' should point to an array of 6*MaxScreenTransforms floats + // 'kind' should point to an array of MaxScreenTransforms ints + // (0 = indicates top screen, 1 = bottom screen) + // returns the amount of screens + int GetScreenTransforms(float* out, int* kind); + + // de-transform the provided host display coordinates to get coordinates + // on the bottom screen + bool GetTouchCoords(int& x, int& y, bool clamp); + +private: + float TopScreenMtx[6]; + float BotScreenMtx[6]; + float HybScreenMtx[6]; + float TouchMtx[6]; + float HybTouchMtx[6]; + bool TopEnable; + bool BotEnable; + bool HybEnable; + int HybScreen; + int HybPrevTouchScreen; // 0:unknown, 1:buttom screen, 2:hybrid screen +}; + +#endif // SCREENLAYOUT_H diff --git a/src/frontend/Util_Audio.cpp b/src/frontend/Util_Audio.cpp deleted file mode 100644 index 02b3026e..00000000 --- a/src/frontend/Util_Audio.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/* - Copyright 2016-2023 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 -#include - -#include "FrontendUtil.h" - -#include "NDS.h" - -#include "mic_blow.h" - -using namespace melonDS; - -namespace Frontend -{ - -int AudioOut_Freq; -float AudioOut_SampleFrac; - -s16* MicBuffer; -u32 MicBufferLength; -u32 MicBufferReadPos; - - -void Init_Audio(int outputfreq) -{ - AudioOut_Freq = outputfreq; - AudioOut_SampleFrac = 0; - - MicBuffer = nullptr; - MicBufferLength = 0; - MicBufferReadPos = 0; -} - - -int AudioOut_GetNumSamples(int outlen) -{ - float f_len_in = (outlen * 32823.6328125) / (float)AudioOut_Freq; - f_len_in += AudioOut_SampleFrac; - int len_in = (int)floor(f_len_in); - AudioOut_SampleFrac = f_len_in - len_in; - - return len_in; -} - -void AudioOut_Resample(s16* inbuf, int inlen, s16* outbuf, int outlen, int volume) -{ - float res_incr = inlen / (float)outlen; - float res_timer = 0; - int res_pos = 0; - - for (int i = 0; i < outlen; i++) - { - outbuf[i*2 ] = (inbuf[res_pos*2 ] * volume) >> 8; - outbuf[i*2+1] = (inbuf[res_pos*2+1] * volume) >> 8; - - res_timer += res_incr; - while (res_timer >= 1.0) - { - res_timer -= 1.0; - res_pos++; - } - } -} - - -void Mic_FeedSilence(NDS& nds) -{ - MicBufferReadPos = 0; - nds.MicInputFrame(NULL, 0); -} - -void Mic_FeedNoise(NDS& nds) -{ - int sample_len = sizeof(mic_blow) / sizeof(u16); - static int sample_pos = 0; - - s16 tmp[735]; - - for (int i = 0; i < 735; i++) - { - tmp[i] = mic_blow[sample_pos]; - sample_pos++; - if (sample_pos >= sample_len) sample_pos = 0; - } - - nds.MicInputFrame(tmp, 735); -} - -void Mic_FeedExternalBuffer(NDS& nds) -{ - if (!MicBuffer) return Mic_FeedSilence(nds); - - if ((MicBufferReadPos + 735) > MicBufferLength) - { - s16 tmp[735]; - u32 len1 = MicBufferLength - MicBufferReadPos; - memcpy(&tmp[0], &MicBuffer[MicBufferReadPos], len1*sizeof(s16)); - memcpy(&tmp[len1], &MicBuffer[0], (735 - len1)*sizeof(s16)); - - nds.MicInputFrame(tmp, 735); - MicBufferReadPos = 735 - len1; - } - else - { - nds.MicInputFrame(&MicBuffer[MicBufferReadPos], 735); - MicBufferReadPos += 735; - } -} - -void Mic_SetExternalBuffer(s16* buffer, u32 len) -{ - MicBuffer = buffer; - MicBufferLength = len; - MicBufferReadPos = 0; -} - -} diff --git a/src/frontend/duckstation/gl/context.cpp b/src/frontend/duckstation/gl/context.cpp index a0a4183b..308b3c40 100644 --- a/src/frontend/duckstation/gl/context.cpp +++ b/src/frontend/duckstation/gl/context.cpp @@ -3,11 +3,7 @@ #include "loader.h" #include #include -#ifdef __APPLE__ #include -#else -#include -#endif Log_SetChannel(GL::Context); #if defined(_WIN32) diff --git a/src/frontend/mic_blow.h b/src/frontend/mic_blow.h index f8dbc107..f90d8c8e 100644 --- a/src/frontend/mic_blow.h +++ b/src/frontend/mic_blow.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/AboutDialog.cpp b/src/frontend/qt_sdl/AboutDialog.cpp new file mode 100644 index 00000000..22022de9 --- /dev/null +++ b/src/frontend/qt_sdl/AboutDialog.cpp @@ -0,0 +1,59 @@ +/* + Copyright 2016-2023 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 "AboutDialog.h" + +#include +#include + +#include "ui_AboutDialog.h" + +#include "version.h" + +AboutDialog::AboutDialog(QWidget *parent) : + QDialog(parent), ui(new Ui::AboutDialog) +{ + ui->setupUi(this); + + ui->lblVersionInfo->setText("Version " MELONDS_VERSION); +#ifdef MELONDS_EMBED_BUILD_INFO + ui->lblBuildInfo->setText( + "Branch: " MELONDS_GIT_BRANCH "\n" + "Commit: " MELONDS_GIT_HASH "\n" + "Built by: " MELONDS_BUILD_PROVIDER + ); +#else + ui->lblBuildInfo->hide(); +#endif + adjustSize(); +} + +AboutDialog::~AboutDialog() +{ + delete ui; +} + +void AboutDialog::openWebsite() +{ + QDesktopServices::openUrl(QUrl(MELONDS_URL)); +} + +void AboutDialog::openGitHub() +{ + QDesktopServices::openUrl(QUrl("https://github.com/melonDS-emu/melonDS"));; +} diff --git a/src/frontend/qt_sdl/AudioInOut.h b/src/frontend/qt_sdl/AboutDialog.h similarity index 62% rename from src/frontend/qt_sdl/AudioInOut.h rename to src/frontend/qt_sdl/AboutDialog.h index 0bf36540..eb328f5b 100644 --- a/src/frontend/qt_sdl/AudioInOut.h +++ b/src/frontend/qt_sdl/AboutDialog.h @@ -16,34 +16,35 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#ifndef AUDIO_INOUT_H -#define AUDIO_INOUT_H +#ifndef MELONDS_ABOUTDIALOG_H +#define MELONDS_ABOUTDIALOG_H -#include "types.h" +#include -#include -class EmuThread; -namespace melonDS +QT_BEGIN_NAMESPACE +namespace Ui { -class NDS; + class AboutDialog; } -namespace AudioInOut +QT_END_NAMESPACE + +class AboutDialog : public QDialog { +Q_OBJECT -void Init(EmuThread* thread); -void DeInit(); +public: + explicit AboutDialog(QWidget *parent = nullptr); -void MicProcess(melonDS::NDS& nds); -void AudioMute(QMainWindow* mainWindow); + ~AboutDialog() override; -void AudioSync(melonDS::NDS& nds); +private slots: + static void openWebsite(); + static void openGitHub(); -void UpdateSettings(melonDS::NDS& nds); +private: + Ui::AboutDialog *ui; +}; -void Enable(); -void Disable(); -} - -#endif +#endif //MELONDS_ABOUTDIALOG_H diff --git a/src/frontend/qt_sdl/AboutDialog.ui b/src/frontend/qt_sdl/AboutDialog.ui new file mode 100644 index 00000000..89ca7a36 --- /dev/null +++ b/src/frontend/qt_sdl/AboutDialog.ui @@ -0,0 +1,300 @@ + + + AboutDialog + + + + 0 + 0 + 600 + 304 + + + + + 0 + 0 + + + + + 600 + 0 + + + + About melonDS + + + false + + + + 0 + + + QLayout::SizeConstraint::SetFixedSize + + + 12 + + + + + 24 + + + QLayout::SizeConstraint::SetDefaultConstraint + + + + + true + + + + 0 + 0 + + + + + 128 + 128 + + + + + 128 + 128 + + + + + + + Qt::TextFormat::PlainText + + + :/melon-icon + + + true + + + Qt::AlignmentFlag::AlignCenter + + + 0 + + + 0 + + + + + + + 12 + + + + + true + + + + 32 + + + + melonDS + + + + + + + true + + + [VERSION INFO PLACEHOLDER] + + + Qt::TextFormat::MarkdownText + + + + + + + true + + + By Arisotura, the melonDS team <a href="https://github.com/melonDS-emu/melonDS/graphs/contributors">and contributors</a>. + + + + + + + true + + + [EMBEDDED BUILD INFO PLACEHOLDER] + + + Qt::TextInteractionFlag::LinksAccessibleByMouse|Qt::TextInteractionFlag::TextSelectableByMouse + + + + + + + false + + + + 10 + + + + <html><head/><body><p>Licensed under the GNU General Public License v3 or any newer version.</p><p>melonDS is intended only for use with software that you own.</p><p>The Nintendo DS and Nintendo DSi systems are the property of Nintendo.<br />melonDS and the melonDS team are not affiliated with or endorsed by Nintendo.</p></body></html> + + + Qt::TextFormat::RichText + + + false + + + + + + + + + + + 12 + + + 12 + + + + + true + + + Qt::Orientation::Horizontal + + + + 40 + 0 + + + + + + + + true + + + Visit the &website + + + true + + + + + + + true + + + View on &GitHub + + + true + + + + + + + true + + + &Close + + + true + + + + + + + + + + + + + btnClose + clicked() + AboutDialog + accept() + + + 586 + 261 + + + 294 + 154 + + + + + btnOpenGitHub + clicked() + AboutDialog + openGitHub() + + + 449 + 243 + + + 299 + 139 + + + + + btnOpenWebsite + clicked() + AboutDialog + openWebsite() + + + 345 + 245 + + + 96 + 275 + + + + + + openWebsite() + openGitHub() + + diff --git a/src/frontend/qt_sdl/ArchiveUtil.cpp b/src/frontend/qt_sdl/ArchiveUtil.cpp index aa508e8d..f4155682 100644 --- a/src/frontend/qt_sdl/ArchiveUtil.cpp +++ b/src/frontend/qt_sdl/ArchiveUtil.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/ArchiveUtil.h b/src/frontend/qt_sdl/ArchiveUtil.h index 246670e7..c422a84b 100644 --- a/src/frontend/qt_sdl/ArchiveUtil.h +++ b/src/frontend/qt_sdl/ArchiveUtil.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/AudioInOut.cpp b/src/frontend/qt_sdl/AudioInOut.cpp deleted file mode 100644 index 1f1ee1c5..00000000 --- a/src/frontend/qt_sdl/AudioInOut.cpp +++ /dev/null @@ -1,390 +0,0 @@ -/* - Copyright 2016-2023 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 "AudioInOut.h" - -#include - -#include "FrontendUtil.h" -#include "Config.h" -#include "NDS.h" -#include "SPU.h" -#include "Platform.h" -#include "Input.h" -#include "main.h" - -using namespace melonDS; -namespace AudioInOut -{ - -SDL_AudioDeviceID audioDevice; -int audioFreq; -bool audioMuted; -SDL_cond* audioSync; -SDL_mutex* audioSyncLock; - -SDL_AudioDeviceID micDevice; -s16 micExtBuffer[2048]; -u32 micExtBufferWritePos; - -u32 micWavLength; -s16* micWavBuffer; - -void AudioCallback(void* data, Uint8* stream, int len) -{ - len /= (sizeof(s16) * 2); - - // resample incoming audio to match the output sample rate - - int len_in = Frontend::AudioOut_GetNumSamples(len); - s16 buf_in[1024*2]; - int num_in; - - EmuThread* emuThread = (EmuThread*)data; - SDL_LockMutex(audioSyncLock); - num_in = emuThread->NDS->SPU.ReadOutput(buf_in, len_in); - SDL_CondSignal(audioSync); - SDL_UnlockMutex(audioSyncLock); - - if ((num_in < 1) || audioMuted) - { - memset(stream, 0, len*sizeof(s16)*2); - return; - } - - int margin = 6; - if (num_in < len_in-margin) - { - int last = num_in-1; - - for (int i = num_in; i < len_in-margin; i++) - ((u32*)buf_in)[i] = ((u32*)buf_in)[last]; - - num_in = len_in-margin; - } - - Frontend::AudioOut_Resample(buf_in, num_in, (s16*)stream, len, Config::AudioVolume); -} - -void MicCallback(void* data, Uint8* stream, int len) -{ - s16* input = (s16*)stream; - len /= sizeof(s16); - - int maxlen = sizeof(micExtBuffer) / sizeof(s16); - - if ((micExtBufferWritePos + len) > maxlen) - { - u32 len1 = maxlen - micExtBufferWritePos; - memcpy(&micExtBuffer[micExtBufferWritePos], &input[0], len1*sizeof(s16)); - memcpy(&micExtBuffer[0], &input[len1], (len - len1)*sizeof(s16)); - micExtBufferWritePos = len - len1; - } - else - { - memcpy(&micExtBuffer[micExtBufferWritePos], input, len*sizeof(s16)); - micExtBufferWritePos += len; - } -} - -void AudioMute(QMainWindow* mainWindow) -{ - 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 != nullptr) - audioMuted = !mainWindow->isActiveWindow(); - break; - } -} - - -void MicOpen() -{ - if (Config::MicInputType != micInputType_External) - { - micDevice = 0; - return; - } - - int numMics = SDL_GetNumAudioDevices(1); - if (numMics == 0) - return; - - SDL_AudioSpec whatIwant, whatIget; - memset(&whatIwant, 0, sizeof(SDL_AudioSpec)); - whatIwant.freq = 44100; - whatIwant.format = AUDIO_S16LSB; - whatIwant.channels = 1; - whatIwant.samples = 1024; - whatIwant.callback = MicCallback; - const char* mic = NULL; - if (Config::MicDevice != "") - { - mic = Config::MicDevice.c_str(); - } - micDevice = SDL_OpenAudioDevice(mic, 1, &whatIwant, &whatIget, 0); - if (!micDevice) - { - Platform::Log(Platform::LogLevel::Error, "Mic init failed: %s\n", SDL_GetError()); - } - else - { - SDL_PauseAudioDevice(micDevice, 0); - } -} - -void MicClose() -{ - if (micDevice) - SDL_CloseAudioDevice(micDevice); - - micDevice = 0; -} - -void MicLoadWav(const std::string& name) -{ - SDL_AudioSpec format; - memset(&format, 0, sizeof(SDL_AudioSpec)); - - if (micWavBuffer) delete[] micWavBuffer; - micWavBuffer = nullptr; - micWavLength = 0; - - u8* buf; - u32 len; - if (!SDL_LoadWAV(name.c_str(), &format, &buf, &len)) - return; - - const u64 dstfreq = 44100; - - int srcinc = format.channels; - len /= ((SDL_AUDIO_BITSIZE(format.format) / 8) * srcinc); - - micWavLength = (len * dstfreq) / format.freq; - if (micWavLength < 735) micWavLength = 735; - micWavBuffer = new s16[micWavLength]; - - float res_incr = len / (float)micWavLength; - float res_timer = 0; - int res_pos = 0; - - for (int i = 0; i < micWavLength; i++) - { - u16 val = 0; - - switch (SDL_AUDIO_BITSIZE(format.format)) - { - case 8: - val = buf[res_pos] << 8; - break; - - case 16: - if (SDL_AUDIO_ISBIGENDIAN(format.format)) - val = (buf[res_pos*2] << 8) | buf[res_pos*2 + 1]; - else - val = (buf[res_pos*2 + 1] << 8) | buf[res_pos*2]; - break; - - case 32: - if (SDL_AUDIO_ISFLOAT(format.format)) - { - u32 rawval; - if (SDL_AUDIO_ISBIGENDIAN(format.format)) - rawval = (buf[res_pos*4] << 24) | (buf[res_pos*4 + 1] << 16) | (buf[res_pos*4 + 2] << 8) | buf[res_pos*4 + 3]; - else - rawval = (buf[res_pos*4 + 3] << 24) | (buf[res_pos*4 + 2] << 16) | (buf[res_pos*4 + 1] << 8) | buf[res_pos*4]; - - float fval = *(float*)&rawval; - s32 ival = (s32)(fval * 0x8000); - ival = std::clamp(ival, -0x8000, 0x7FFF); - val = (s16)ival; - } - else if (SDL_AUDIO_ISBIGENDIAN(format.format)) - val = (buf[res_pos*4] << 8) | buf[res_pos*4 + 1]; - else - val = (buf[res_pos*4 + 3] << 8) | buf[res_pos*4 + 2]; - break; - } - - if (SDL_AUDIO_ISUNSIGNED(format.format)) - val ^= 0x8000; - - micWavBuffer[i] = val; - - res_timer += res_incr; - while (res_timer >= 1.0) - { - res_timer -= 1.0; - res_pos += srcinc; - } - } - - SDL_FreeWAV(buf); -} - -void MicProcess(melonDS::NDS& nds) -{ - int type = Config::MicInputType; - bool cmd = Input::HotkeyDown(HK_Mic); - - if (type != micInputType_External && !cmd) - { - type = micInputType_Silence; - } - - switch (type) - { - case micInputType_Silence: // no mic - Frontend::Mic_FeedSilence(nds); - break; - - case micInputType_External: // host mic - case micInputType_Wav: // WAV - Frontend::Mic_FeedExternalBuffer(nds); - break; - - case micInputType_Noise: // blowing noise - Frontend::Mic_FeedNoise(nds); - break; - } -} - -void SetupMicInputData() -{ - if (micWavBuffer != nullptr) - { - delete[] micWavBuffer; - micWavBuffer = nullptr; - micWavLength = 0; - } - - switch (Config::MicInputType) - { - case micInputType_Silence: - case micInputType_Noise: - Frontend::Mic_SetExternalBuffer(NULL, 0); - break; - case micInputType_External: - Frontend::Mic_SetExternalBuffer(micExtBuffer, sizeof(micExtBuffer)/sizeof(s16)); - break; - case micInputType_Wav: - MicLoadWav(Config::MicWavPath); - Frontend::Mic_SetExternalBuffer(micWavBuffer, micWavLength); - break; - } -} - -void Init(EmuThread* thread) -{ - audioMuted = false; - audioSync = SDL_CreateCond(); - audioSyncLock = SDL_CreateMutex(); - - audioFreq = 48000; // TODO: make configurable? - SDL_AudioSpec whatIwant, whatIget; - memset(&whatIwant, 0, sizeof(SDL_AudioSpec)); - whatIwant.freq = audioFreq; - whatIwant.format = AUDIO_S16LSB; - whatIwant.channels = 2; - whatIwant.samples = 1024; - whatIwant.callback = AudioCallback; - whatIwant.userdata = thread; - audioDevice = SDL_OpenAudioDevice(NULL, 0, &whatIwant, &whatIget, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); - if (!audioDevice) - { - Platform::Log(Platform::LogLevel::Error, "Audio init failed: %s\n", SDL_GetError()); - } - else - { - audioFreq = whatIget.freq; - Platform::Log(Platform::LogLevel::Info, "Audio output frequency: %d Hz\n", audioFreq); - SDL_PauseAudioDevice(audioDevice, 1); - } - - micDevice = 0; - - memset(micExtBuffer, 0, sizeof(micExtBuffer)); - micExtBufferWritePos = 0; - micWavBuffer = nullptr; - - Frontend::Init_Audio(audioFreq); - - SetupMicInputData(); -} - -void DeInit() -{ - if (audioDevice) SDL_CloseAudioDevice(audioDevice); - audioDevice = 0; - MicClose(); - - if (audioSync) SDL_DestroyCond(audioSync); - audioSync = nullptr; - - if (audioSyncLock) SDL_DestroyMutex(audioSyncLock); - audioSyncLock = nullptr; - - if (micWavBuffer) delete[] micWavBuffer; - micWavBuffer = nullptr; -} - -void AudioSync(NDS& nds) -{ - if (audioDevice) - { - SDL_LockMutex(audioSyncLock); - while (nds.SPU.GetOutputSize() > 1024) - { - int ret = SDL_CondWaitTimeout(audioSync, audioSyncLock, 500); - if (ret == SDL_MUTEX_TIMEDOUT) break; - } - SDL_UnlockMutex(audioSyncLock); - } -} - -void UpdateSettings(NDS& nds) -{ - MicClose(); - - nds.SPU.SetInterpolation(static_cast(Config::AudioInterp)); - SetupMicInputData(); - - MicOpen(); -} - -void Enable() -{ - if (audioDevice) SDL_PauseAudioDevice(audioDevice, 0); - MicOpen(); -} - -void Disable() -{ - if (audioDevice) SDL_PauseAudioDevice(audioDevice, 1); - MicClose(); -} - -} diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.cpp b/src/frontend/qt_sdl/AudioSettingsDialog.cpp index 5e8812e9..5f2aef19 100644 --- a/src/frontend/qt_sdl/AudioSettingsDialog.cpp +++ b/src/frontend/qt_sdl/AudioSettingsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -16,7 +16,6 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#include #include #include @@ -25,7 +24,6 @@ #include "Config.h" #include "NDS.h" #include "DSi.h" -#include "DSi_I2C.h" #include "AudioSettingsDialog.h" #include "ui_AudioSettingsDialog.h" @@ -34,46 +32,56 @@ using namespace melonDS; AudioSettingsDialog* AudioSettingsDialog::currentDlg = nullptr; -extern std::string EmuDirectory; - -AudioSettingsDialog::AudioSettingsDialog(QWidget* parent, bool emuActive, EmuThread* emuThread) : QDialog(parent), ui(new Ui::AudioSettingsDialog), emuThread(emuThread) +AudioSettingsDialog::AudioSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AudioSettingsDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - oldInterp = Config::AudioInterp; - oldBitDepth = Config::AudioBitDepth; - oldVolume = Config::AudioVolume; - oldDSiSync = Config::DSiVolumeSync; + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + auto& cfg = emuInstance->getGlobalConfig(); + auto& instcfg = emuInstance->getLocalConfig(); + bool emuActive = emuInstance->emuIsActive(); + + oldInterp = cfg.GetInt("Audio.Interpolation"); + oldBitDepth = cfg.GetInt("Audio.BitDepth"); + oldVolume = instcfg.GetInt("Audio.Volume"); + oldDSiSync = instcfg.GetBool("Audio.DSiVolumeSync"); + + volume = oldVolume; + dsiSync = oldDSiSync; ui->cbInterpolation->addItem("None"); ui->cbInterpolation->addItem("Linear"); ui->cbInterpolation->addItem("Cosine"); ui->cbInterpolation->addItem("Cubic"); - ui->cbInterpolation->setCurrentIndex(Config::AudioInterp); + ui->cbInterpolation->addItem("Gaussian (SNES)"); + ui->cbInterpolation->setCurrentIndex(oldInterp); ui->cbBitDepth->addItem("Automatic"); ui->cbBitDepth->addItem("10-bit"); ui->cbBitDepth->addItem("16-bit"); - ui->cbBitDepth->setCurrentIndex(Config::AudioBitDepth); + ui->cbBitDepth->setCurrentIndex(oldBitDepth); bool state = ui->slVolume->blockSignals(true); - ui->slVolume->setValue(Config::AudioVolume); + ui->slVolume->setValue(oldVolume); ui->slVolume->blockSignals(state); - ui->chkSyncDSiVolume->setChecked(Config::DSiVolumeSync); + ui->chkSyncDSiVolume->setChecked(oldDSiSync); // Setup volume slider accordingly - if (emuActive && emuThread->NDS->ConsoleType == 1) + if (emuActive && emuInstance->getNDS()->ConsoleType == 1) { - on_chkSyncDSiVolume_clicked(Config::DSiVolumeSync); + on_chkSyncDSiVolume_clicked(oldDSiSync); } else { ui->chkSyncDSiVolume->setEnabled(false); } - bool isext = (Config::MicInputType == 1); + + int mictype = cfg.GetInt("Mic.InputType"); + + bool isext = (mictype == micInputType_External); ui->cbMic->setEnabled(isext); const int count = SDL_GetNumAudioDevices(true); @@ -81,28 +89,34 @@ AudioSettingsDialog::AudioSettingsDialog(QWidget* parent, bool emuActive, EmuThr { ui->cbMic->addItem(SDL_GetAudioDeviceName(i, true)); } - if (Config::MicDevice == "" && count > 0) + + QString micdev = cfg.GetQString("Mic.Device"); + if (micdev == "" && count > 0) { - Config::MicDevice = SDL_GetAudioDeviceName(0, true); + micdev = SDL_GetAudioDeviceName(0, true); } - ui->cbMic->setCurrentText(QString::fromStdString(Config::MicDevice)); + ui->cbMic->setCurrentText(micdev); grpMicMode = new QButtonGroup(this); grpMicMode->addButton(ui->rbMicNone, micInputType_Silence); grpMicMode->addButton(ui->rbMicExternal, micInputType_External); grpMicMode->addButton(ui->rbMicNoise, micInputType_Noise); grpMicMode->addButton(ui->rbMicWav, micInputType_Wav); +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) connect(grpMicMode, SIGNAL(buttonClicked(int)), this, SLOT(onChangeMicMode(int))); - grpMicMode->button(Config::MicInputType)->setChecked(true); +#else + connect(grpMicMode, SIGNAL(idClicked(int)), this, SLOT(onChangeMicMode(int))); +#endif + grpMicMode->button(mictype)->setChecked(true); - ui->txtMicWavPath->setText(QString::fromStdString(Config::MicWavPath)); + ui->txtMicWavPath->setText(cfg.GetQString("Mic.WavPath")); - bool iswav = (Config::MicInputType == micInputType_Wav); + bool iswav = (mictype == micInputType_Wav); ui->txtMicWavPath->setEnabled(iswav); ui->btnMicWavBrowse->setEnabled(iswav); - int inst = Platform::InstanceID(); + int inst = emuInstance->getInstanceID(); if (inst > 0) { ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1)); @@ -125,26 +139,30 @@ AudioSettingsDialog::~AudioSettingsDialog() void AudioSettingsDialog::onSyncVolumeLevel() { - if (Config::DSiVolumeSync && emuThread->NDS->ConsoleType == 1) + if (dsiSync && emuInstance->getNDS()->ConsoleType == 1) { - auto& dsi = static_cast(*emuThread->NDS); + auto dsi = static_cast(emuInstance->getNDS()); + volume = dsi->I2C.GetBPTWL()->GetVolumeLevel(); + bool state = ui->slVolume->blockSignals(true); - ui->slVolume->setValue(dsi.I2C.GetBPTWL()->GetVolumeLevel()); + ui->slVolume->setValue(volume); ui->slVolume->blockSignals(state); } } void AudioSettingsDialog::onConsoleReset() { - on_chkSyncDSiVolume_clicked(Config::DSiVolumeSync); - ui->chkSyncDSiVolume->setEnabled(emuThread->NDS->ConsoleType == 1); + on_chkSyncDSiVolume_clicked(dsiSync); + ui->chkSyncDSiVolume->setEnabled(emuInstance->getNDS()->ConsoleType == 1); } void AudioSettingsDialog::on_AudioSettingsDialog_accepted() { - Config::MicDevice = ui->cbMic->currentText().toStdString(); - Config::MicInputType = grpMicMode->checkedId(); - Config::MicWavPath = ui->txtMicWavPath->text().toStdString(); + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetQString("Mic.Device", ui->cbMic->currentText()); + cfg.SetInt("Mic.InputType", grpMicMode->checkedId()); + cfg.SetQString("Mic.WavPath", ui->txtMicWavPath->text()); + Config::Save(); closeDlg(); @@ -152,10 +170,21 @@ void AudioSettingsDialog::on_AudioSettingsDialog_accepted() void AudioSettingsDialog::on_AudioSettingsDialog_rejected() { - Config::AudioInterp = oldInterp; - Config::AudioBitDepth = oldBitDepth; - Config::AudioVolume = oldVolume; - Config::DSiVolumeSync = oldDSiSync; + if (!((MainWindow*)parent())->getEmuInstance()) + { + closeDlg(); + return; + } + + auto& cfg = emuInstance->getGlobalConfig(); + auto& instcfg = emuInstance->getLocalConfig(); + cfg.SetInt("Audio.Interpolation", oldInterp); + cfg.SetInt("Audio.BitDepth", oldBitDepth); + instcfg.SetInt("Audio.Volume", oldVolume); + instcfg.SetBool("Audio.DSiVolumeSync", oldDSiSync); + + emit updateAudioVolume(oldVolume, oldDSiSync); + emit updateAudioSettings(); closeDlg(); } @@ -165,7 +194,8 @@ void AudioSettingsDialog::on_cbBitDepth_currentIndexChanged(int idx) // prevent a spurious change if (ui->cbBitDepth->count() < 3) return; - Config::AudioBitDepth = ui->cbBitDepth->currentIndex(); + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetInt("Audio.BitDepth", ui->cbBitDepth->currentIndex()); emit updateAudioSettings(); } @@ -173,47 +203,58 @@ void AudioSettingsDialog::on_cbBitDepth_currentIndexChanged(int idx) void AudioSettingsDialog::on_cbInterpolation_currentIndexChanged(int idx) { // prevent a spurious change - if (ui->cbInterpolation->count() < 4) return; + if (ui->cbInterpolation->count() < 5) return; - Config::AudioInterp = ui->cbInterpolation->currentIndex(); + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetInt("Audio.Interpolation", ui->cbInterpolation->currentIndex()); emit updateAudioSettings(); } void AudioSettingsDialog::on_slVolume_valueChanged(int val) { - if (Config::DSiVolumeSync && emuThread->NDS->ConsoleType == 1) + auto& cfg = emuInstance->getLocalConfig(); + + if (dsiSync && emuInstance->getNDS()->ConsoleType == 1) { - auto& dsi = static_cast(*emuThread->NDS); - dsi.I2C.GetBPTWL()->SetVolumeLevel(val); + auto dsi = static_cast(emuInstance->getNDS()); + dsi->I2C.GetBPTWL()->SetVolumeLevel(val); return; } - Config::AudioVolume = val; + volume = val; + cfg.SetInt("Audio.Volume", val); + emit updateAudioVolume(val, dsiSync); } void AudioSettingsDialog::on_chkSyncDSiVolume_clicked(bool checked) { - Config::DSiVolumeSync = checked; + dsiSync = checked; + + auto& cfg = emuInstance->getLocalConfig(); + cfg.SetBool("Audio.DSiVolumeSync", dsiSync); bool state = ui->slVolume->blockSignals(true); - if (Config::DSiVolumeSync && emuThread->NDS->ConsoleType == 1) + if (dsiSync && emuInstance->getNDS()->ConsoleType == 1) { - auto& dsi = static_cast(*emuThread->NDS); + auto dsi = static_cast(emuInstance->getNDS()); ui->slVolume->setMaximum(31); - ui->slVolume->setValue(dsi.I2C.GetBPTWL()->GetVolumeLevel()); + ui->slVolume->setValue(dsi->I2C.GetBPTWL()->GetVolumeLevel()); ui->slVolume->setPageStep(4); ui->slVolume->setTickPosition(QSlider::TicksBelow); } else { - Config::AudioVolume = oldVolume; + volume = oldVolume; + cfg.SetInt("Audio.Volume", oldVolume); ui->slVolume->setMaximum(256); - ui->slVolume->setValue(Config::AudioVolume); + ui->slVolume->setValue(oldVolume); ui->slVolume->setPageStep(16); ui->slVolume->setTickPosition(QSlider::NoTicks); } ui->slVolume->blockSignals(state); + + emit updateAudioVolume(volume, dsiSync); } void AudioSettingsDialog::onChangeMicMode(int mode) @@ -229,7 +270,7 @@ void AudioSettingsDialog::on_btnMicWavBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select WAV file...", - QString::fromStdString(EmuDirectory), + emuDirectory, "WAV files (*.wav);;Any file (*.*)"); if (file.isEmpty()) return; diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.h b/src/frontend/qt_sdl/AudioSettingsDialog.h index ced9bae9..f05cd8dd 100644 --- a/src/frontend/qt_sdl/AudioSettingsDialog.h +++ b/src/frontend/qt_sdl/AudioSettingsDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -24,18 +24,19 @@ namespace Ui { class AudioSettingsDialog; } class AudioSettingsDialog; -class EmuThread; + +class EmuInstance; class AudioSettingsDialog : public QDialog { Q_OBJECT public: - explicit AudioSettingsDialog(QWidget* parent, bool emuActive, EmuThread* emuThread); + explicit AudioSettingsDialog(QWidget* parent); ~AudioSettingsDialog(); static AudioSettingsDialog* currentDlg; - static AudioSettingsDialog* openDlg(QWidget* parent, bool emuActive, EmuThread* emuThread) + static AudioSettingsDialog* openDlg(QWidget* parent) { if (currentDlg) { @@ -43,7 +44,7 @@ public: return currentDlg; } - currentDlg = new AudioSettingsDialog(parent, emuActive, emuThread); + currentDlg = new AudioSettingsDialog(parent); currentDlg->show(); return currentDlg; } @@ -56,6 +57,7 @@ public: void onConsoleReset(); signals: + void updateAudioVolume(int vol, bool dsisync); void updateAudioSettings(); private slots: @@ -70,14 +72,18 @@ private slots: void on_btnMicWavBrowse_clicked(); private: - EmuThread* emuThread; Ui::AudioSettingsDialog* ui; + EmuInstance* emuInstance; + int oldInterp; int oldBitDepth; int oldVolume; bool oldDSiSync; QButtonGroup* grpMicMode; + + int volume; + bool dsiSync; }; #endif // AUDIOSETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/CLI.cpp b/src/frontend/qt_sdl/CLI.cpp index 299ce65b..5e352cae 100644 --- a/src/frontend/qt_sdl/CLI.cpp +++ b/src/frontend/qt_sdl/CLI.cpp @@ -96,7 +96,7 @@ CommandLineOptions* ManageArgs(QApplication& melon) } else { - options->errorsToDisplay += "Option -a/--archive-file given, but no archive specified!"; + Log(LogLevel::Error, "Option -a/--archive-file given, but no archive specified!"); } } @@ -108,7 +108,7 @@ CommandLineOptions* ManageArgs(QApplication& melon) } else { - options->errorsToDisplay += "Option -A/--archive-file-gba given, but no archive specified!"; + Log(LogLevel::Error, "Option -A/--archive-file-gba given, but no archive specified!"); } } #endif diff --git a/src/frontend/qt_sdl/CLI.h b/src/frontend/qt_sdl/CLI.h index 4997e6a7..beb120bf 100644 --- a/src/frontend/qt_sdl/CLI.h +++ b/src/frontend/qt_sdl/CLI.h @@ -28,8 +28,6 @@ namespace CLI { struct CommandLineOptions { - QStringList errorsToDisplay = {}; - std::optional dsRomPath; std::optional dsRomArchivePath; std::optional gbaRomPath; diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 8eeb44a4..7dc4a00c 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -7,6 +7,9 @@ set(SOURCES_QT_SDL main_shaders.h Screen.cpp Window.cpp + EmuInstance.cpp + EmuInstanceAudio.cpp + EmuInstanceInput.cpp EmuThread.cpp CheatsDialog.cpp Config.cpp @@ -28,25 +31,20 @@ set(SOURCES_QT_SDL ROMInfoDialog.cpp RAMInfoDialog.cpp TitleManagerDialog.cpp - Input.cpp - LAN_PCap.cpp - LAN_Socket.cpp - LocalMP.cpp OSD_shaders.h font.h Platform.cpp QPathInput.h - ROMManager.cpp SaveManager.cpp CameraManager.cpp - AudioInOut.cpp + AboutDialog.cpp + AboutDialog.h + AboutDialog.ui ArchiveUtil.h ArchiveUtil.cpp - ../Util_Video.cpp - ../Util_Audio.cpp - ../FrontendUtil.h + ../ScreenLayout.cpp ../mic_blow.h ../glad/glad.c @@ -56,6 +54,9 @@ set(SOURCES_QT_SDL CLI.h CLI.cpp + + LANDialog.cpp + NetplayDialog.cpp ) if (APPLE) @@ -65,10 +66,10 @@ else() endif() if (USE_QT6) - find_package(Qt6 COMPONENTS Core Gui Widgets Network Multimedia OpenGL OpenGLWidgets REQUIRED) + find_package(Qt6 COMPONENTS Core Gui Widgets Network Multimedia OpenGL OpenGLWidgets Svg REQUIRED) set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Multimedia Qt6::OpenGL Qt6::OpenGLWidgets) else() - find_package(Qt5 COMPONENTS Core Gui Widgets Network Multimedia REQUIRED) + find_package(Qt5 COMPONENTS Core Gui Widgets Network Multimedia Svg REQUIRED) set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network Qt5::Multimedia) endif() @@ -84,7 +85,6 @@ if (BUILD_STATIC) endif() pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2) -pkg_check_modules(Slirp REQUIRED slirp) pkg_check_modules(LibArchive REQUIRED IMPORTED_TARGET libarchive) pkg_check_modules(Zstd REQUIRED IMPORTED_TARGET libzstd) @@ -94,6 +94,12 @@ add_compile_definitions(ARCHIVE_SUPPORT_ENABLED) add_executable(melonDS ${SOURCES_QT_SDL}) +add_subdirectory("../../net" + "${CMAKE_BINARY_DIR}/net" +) + +target_link_libraries(melonDS PRIVATE net-utils) + if (WIN32) target_link_libraries(melonDS PUBLIC opengl32) @@ -102,14 +108,24 @@ if (WIN32) ../glad/glad_wgl.c ) + + if (MINGW AND CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") + set_property(TARGET melonDS PROPERTY AUTORCC_OPTIONS "--no-zstd") + endif() elseif (APPLE) if (NOT USE_QT6) find_library(COCOA_LIB Cocoa) target_link_libraries(melonDS PRIVATE ${COCOA_LIB}) endif() + find_library(OPENGL_LIB OpenGL) + target_link_libraries(melonDS PRIVATE ${OPENGL_LIB}) target_sources(melonDS PRIVATE ../duckstation/gl/context_agl.mm ) + set_source_files_properties( + ../duckstation/gl/context_agl.mm + PROPERTIES COMPILE_OPTIONS "-Wno-deprecated-declarations" + ) else() find_package(X11 REQUIRED) find_package(EGL REQUIRED) @@ -148,12 +164,21 @@ endif() if (BUILD_STATIC) qt_import_plugins(melonDS INCLUDE Qt::QSvgPlugin) - target_link_options(melonDS PRIVATE -static) + if (WIN32 AND USE_QT6) + qt_import_plugins(melonDS INCLUDE Qt::QModernWindowsStylePlugin) + endif() + if (USE_VCPKG AND UNIX AND NOT APPLE) + pkg_check_modules(ALSA REQUIRED IMPORTED_TARGET alsa) + target_link_libraries(melonDS PRIVATE PkgConfig::ALSA) + else() + target_link_options(melonDS PRIVATE -static) + endif() 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_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../../net") if (USE_QT6) target_include_directories(melonDS PUBLIC ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) else() @@ -163,14 +188,13 @@ target_link_libraries(melonDS PRIVATE core) target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::LibArchive PkgConfig::Zstd) target_link_libraries(melonDS PRIVATE ${QT_LINK_LIBS} ${CMAKE_DL_LIBS}) -target_include_directories(melonDS PRIVATE "${Slirp_INCLUDE_DIRS}") -target_link_libraries(melonDS PRIVATE "${Slirp_LINK_LIBRARIES}") - -if (UNIX) - option(PORTABLE "Make a portable build that looks for its configuration in the current directory" OFF) -elseif (WIN32) +if (WIN32) option(PORTABLE "Make a portable build that looks for its configuration in the current directory" ON) + if (PORTABLE) + target_compile_definitions(melonDS PRIVATE WIN32_PORTABLE) + endif() + configure_file("${CMAKE_SOURCE_DIR}/res/melon.rc.in" "${CMAKE_BINARY_DIR}/res/melon.rc") target_sources(melonDS PUBLIC "${CMAKE_BINARY_DIR}/res/melon.rc") target_include_directories(melonDS PRIVATE "${CMAKE_BINARY_DIR}/res") @@ -189,10 +213,6 @@ elseif (WIN32) set_target_properties(melonDS PROPERTIES LINK_FLAGS_DEBUG "-mconsole") endif() -if (PORTABLE) - target_compile_definitions(melonDS PRIVATE PORTABLE) -endif() - if (APPLE) target_sources(melonDS PRIVATE sem_timedwait.cpp) @@ -235,4 +255,14 @@ if (UNIX AND NOT APPLE) INTERPROCEDURAL_OPTIMIZATION OFF INTERPROCEDURAL_OPTIMIZATION_RELEASE OFF) endif() +elseif(APPLE) + install(TARGETS melonDS BUNDLE DESTINATION "${CMAKE_INSTALL_PREFIX}/Applications") +endif() + +if (ENABLE_OGLRENDERER) + set(MELONDS_GL_HEADER \"frontend/glad/glad.h\" CACHE STRING "Path to a header that contains OpenGL function and type declarations.") + + target_compile_definitions(melonDS PUBLIC OGLRENDERER_ENABLED) + target_compile_definitions(melonDS PUBLIC MELONDS_GL_HEADER=${MELONDS_GL_HEADER}) + target_compile_definitions(core PUBLIC MELONDS_GL_HEADER=${MELONDS_GL_HEADER}) endif() diff --git a/src/frontend/qt_sdl/CameraManager.cpp b/src/frontend/qt_sdl/CameraManager.cpp index cc575d2c..2306da84 100644 --- a/src/frontend/qt_sdl/CameraManager.cpp +++ b/src/frontend/qt_sdl/CameraManager.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -23,6 +23,8 @@ using namespace melonDS; +const char* kCamConfigPath[] = {"DSi.Camera0", "DSi.Camera1"}; + #if QT_VERSION >= 0x060000 CameraFrameDumper::CameraFrameDumper(QObject* parent) : QVideoSink(parent) @@ -116,10 +118,11 @@ QList CameraFrameDumper::supportedPixelFormats(QAbstra #endif -CameraManager::CameraManager(int num, int width, int height, bool yuv) : QObject() +CameraManager::CameraManager(int num, int width, int height, bool yuv) + : QObject(), + num(num), + config(Config::GetGlobalTable().GetTable(kCamConfigPath[num])) { - this->num = num; - startNum = 0; // QCamera needs to be controlled from the UI thread, hence this @@ -136,7 +139,7 @@ CameraManager::CameraManager(int num, int width, int height, bool yuv) : QObject tempFrameBuffer = new u32[fbsize]; inputType = -1; - xFlip = false; + xFlip = config.GetBool("XFlip"); init(); } @@ -157,9 +160,9 @@ void CameraManager::init() startNum = 0; - inputType = Config::Camera[num].InputType; - imagePath = QString::fromStdString(Config::Camera[num].ImagePath); - camDeviceName = QString::fromStdString(Config::Camera[num].CamDeviceName); + inputType = config.GetInt("InputType"); + imagePath = config.GetQString("ImagePath"); + camDeviceName = config.GetQString("DeviceName"); camDevice = nullptr; diff --git a/src/frontend/qt_sdl/CameraManager.h b/src/frontend/qt_sdl/CameraManager.h index 882b051a..806a8ba7 100644 --- a/src/frontend/qt_sdl/CameraManager.h +++ b/src/frontend/qt_sdl/CameraManager.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -33,6 +33,7 @@ #include #include "types.h" +#include "Config.h" class CameraManager; @@ -80,6 +81,8 @@ public: CameraManager(int num, int width, int height, bool yuv); ~CameraManager(); + Config::Table& getConfig() { return config; } + void init(); void deInit(); @@ -106,6 +109,8 @@ private slots: private: int num; + Config::Table config; + int startNum; int inputType; diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.cpp b/src/frontend/qt_sdl/CameraSettingsDialog.cpp index cf404173..63b7a76e 100644 --- a/src/frontend/qt_sdl/CameraSettingsDialog.cpp +++ b/src/frontend/qt_sdl/CameraSettingsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -22,6 +22,7 @@ #include #include "types.h" +#include "main.h" #include "CameraSettingsDialog.h" #include "ui_CameraSettingsDialog.h" @@ -30,8 +31,6 @@ using namespace melonDS; CameraSettingsDialog* CameraSettingsDialog::currentDlg = nullptr; -extern std::string EmuDirectory; - extern CameraManager* camManager[2]; @@ -71,9 +70,16 @@ CameraSettingsDialog::CameraSettingsDialog(QWidget* parent) : QDialog(parent), u ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + for (int i = 0; i < 2; i++) { - oldCamSettings[i] = Config::Camera[i]; + auto& cfg = camManager[i]->getConfig(); + + oldCamSettings[i].InputType = cfg.GetInt("InputType"); + oldCamSettings[i].ImagePath = cfg.GetString("ImagePath"); + oldCamSettings[i].CamDeviceName = cfg.GetString("DeviceName"); + oldCamSettings[i].XFlip = cfg.GetBool("XFlip"); } ui->cbCameraSel->addItem("DSi outer camera"); @@ -157,11 +163,23 @@ void CameraSettingsDialog::on_CameraSettingsDialog_accepted() void CameraSettingsDialog::on_CameraSettingsDialog_rejected() { + if (!((MainWindow*)parent())->getEmuInstance()) + { + closeDlg(); + return; + } + for (int i = 0; i < 2; i++) { camManager[i]->stop(); camManager[i]->deInit(); - Config::Camera[i] = oldCamSettings[i]; + + auto& cfg = camManager[i]->getConfig(); + cfg.SetInt("InputType", oldCamSettings[i].InputType); + cfg.SetString("ImagePath", oldCamSettings[i].ImagePath); + cfg.SetString("DeviceName", oldCamSettings[i].CamDeviceName); + cfg.SetBool("XFlip", oldCamSettings[i].XFlip); + camManager[i]->init(); } @@ -178,7 +196,7 @@ void CameraSettingsDialog::on_cbCameraSel_currentIndexChanged(int id) } currentId = id; - currentCfg = &Config::Camera[id]; + currentCfg = &camManager[id]->getConfig(); //currentCam = camManager[id]; currentCam = nullptr; populateCamControls(id); @@ -198,16 +216,16 @@ void CameraSettingsDialog::onChangeInputType(int type) currentCam->deInit(); } - currentCfg->InputType = type; + currentCfg->SetInt("InputType", type); ui->txtSrcImagePath->setEnabled(type == 1); ui->btnSrcImageBrowse->setEnabled(type == 1); ui->cbPhysicalCamera->setEnabled((type == 2) && (ui->cbPhysicalCamera->count()>0)); - currentCfg->ImagePath = ui->txtSrcImagePath->text().toStdString(); + currentCfg->SetQString("ImagePath", ui->txtSrcImagePath->text()); if (ui->cbPhysicalCamera->count() > 0) - currentCfg->CamDeviceName = ui->cbPhysicalCamera->currentData().toString().toStdString(); + currentCfg->SetQString("DeviceName", ui->cbPhysicalCamera->currentData().toString()); if (currentCam) { @@ -226,7 +244,7 @@ void CameraSettingsDialog::on_txtSrcImagePath_textChanged() currentCam->deInit(); } - currentCfg->ImagePath = ui->txtSrcImagePath->text().toStdString(); + currentCfg->SetQString("ImagePath", ui->txtSrcImagePath->text()); if (currentCam) { @@ -239,7 +257,7 @@ void CameraSettingsDialog::on_btnSrcImageBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select image file...", - QString::fromStdString(EmuDirectory), + emuDirectory, "Image files (*.png *.jpg *.jpeg *.bmp);;Any file (*.*)"); if (file.isEmpty()) return; @@ -257,7 +275,7 @@ void CameraSettingsDialog::on_cbPhysicalCamera_currentIndexChanged(int id) currentCam->deInit(); } - currentCfg->CamDeviceName = ui->cbPhysicalCamera->itemData(id).toString().toStdString(); + currentCfg->SetQString("DeviceName", ui->cbPhysicalCamera->itemData(id).toString()); if (currentCam) { @@ -268,16 +286,16 @@ void CameraSettingsDialog::on_cbPhysicalCamera_currentIndexChanged(int id) void CameraSettingsDialog::populateCamControls(int id) { - Config::CameraConfig& cfg = Config::Camera[id]; + Config::Table& cfg = camManager[id]->getConfig(); - int type = cfg.InputType; + int type = cfg.GetInt("InputType"); if (type < 0 || type >= grpInputType->buttons().count()) type = 0; grpInputType->button(type)->setChecked(true); - ui->txtSrcImagePath->setText(QString::fromStdString(cfg.ImagePath)); + ui->txtSrcImagePath->setText(cfg.GetQString("ImagePath")); bool deviceset = false; - QString device = QString::fromStdString(cfg.CamDeviceName); + QString device = cfg.GetQString("DeviceName"); for (int i = 0; i < ui->cbPhysicalCamera->count(); i++) { QString itemdev = ui->cbPhysicalCamera->itemData(i).toString(); @@ -293,13 +311,14 @@ void CameraSettingsDialog::populateCamControls(int id) onChangeInputType(type); - ui->chkFlipPicture->setChecked(cfg.XFlip); + ui->chkFlipPicture->setChecked(cfg.GetBool("XFlip")); } void CameraSettingsDialog::on_chkFlipPicture_clicked() { if (!currentCfg) return; - currentCfg->XFlip = ui->chkFlipPicture->isChecked(); - if (currentCam) currentCam->setXFlip(currentCfg->XFlip); + bool xflip = ui->chkFlipPicture->isChecked(); + currentCfg->SetBool("XFlip", xflip); + if (currentCam) currentCam->setXFlip(xflip); } diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.h b/src/frontend/qt_sdl/CameraSettingsDialog.h index a740193a..41caaf63 100644 --- a/src/frontend/qt_sdl/CameraSettingsDialog.h +++ b/src/frontend/qt_sdl/CameraSettingsDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -28,6 +28,8 @@ namespace Ui { class CameraSettingsDialog; } class CameraSettingsDialog; +class EmuInstance; + class CameraPreviewPanel : public QWidget { Q_OBJECT @@ -92,15 +94,22 @@ private slots: private: Ui::CameraSettingsDialog* ui; + EmuInstance* emuInstance; QButtonGroup* grpInputType; CameraPreviewPanel* previewPanel; int currentId; - Config::CameraConfig* currentCfg; + Config::Table* currentCfg; CameraManager* currentCam; - Config::CameraConfig oldCamSettings[2]; + struct + { + int InputType; // 0=blank 1=image 2=camera + std::string ImagePath; + std::string CamDeviceName; + bool XFlip; + } oldCamSettings[2]; void populateCamControls(int id); }; diff --git a/src/frontend/qt_sdl/CheatsDialog.cpp b/src/frontend/qt_sdl/CheatsDialog.cpp index df687230..19065a8d 100644 --- a/src/frontend/qt_sdl/CheatsDialog.cpp +++ b/src/frontend/qt_sdl/CheatsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -24,7 +24,7 @@ #include "types.h" #include "Platform.h" #include "Config.h" -#include "ROMManager.h" +#include "EmuInstance.h" #include "CheatsDialog.h" #include "ui_CheatsDialog.h" @@ -35,15 +35,15 @@ using Platform::LogLevel; CheatsDialog* CheatsDialog::currentDlg = nullptr; -extern std::string EmuDirectory; - CheatsDialog::CheatsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::CheatsDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - codeFile = ROMManager::GetCheatFile(); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + codeFile = emuInstance->getCheatFile(); QStandardItemModel* model = new QStandardItemModel(); ui->tvCodeList->setModel(model); diff --git a/src/frontend/qt_sdl/CheatsDialog.h b/src/frontend/qt_sdl/CheatsDialog.h index ab2ac309..cafb9684 100644 --- a/src/frontend/qt_sdl/CheatsDialog.h +++ b/src/frontend/qt_sdl/CheatsDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -33,6 +33,8 @@ Q_DECLARE_METATYPE(melonDS::ARCodeCatList::iterator) namespace Ui { class CheatsDialog; } class CheatsDialog; +class EmuInstance; + class ARCodeChecker : public QSyntaxHighlighter { Q_OBJECT @@ -87,6 +89,8 @@ private slots: private: Ui::CheatsDialog* ui; + EmuInstance* emuInstance; + melonDS::ARCodeFile* codeFile; ARCodeChecker* codeChecker; }; diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index 68e61911..3f417328 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -20,375 +20,678 @@ #include #include #include +#include +#include +#include +#include +#include "toml/toml.hpp" + #include "Platform.h" #include "Config.h" +#include "ScreenLayout.h" +#include "main.h" + +using namespace std::string_literals; namespace Config { using namespace melonDS; -int KeyMapping[12]; -int JoyMapping[12]; -int HKKeyMapping[HK_MAX]; -int HKJoyMapping[HK_MAX]; +const char* kConfigFile = "melonDS.toml"; -int JoystickID; +const char* kLegacyConfigFile = "melonDS.ini"; +const char* kLegacyUniqueConfigFile = "melonDS.%d.ini"; -int WindowWidth; -int WindowHeight; -bool WindowMaximized; +toml::value RootTable; -int ScreenRotation; -int ScreenGap; -int ScreenLayout; -bool ScreenSwap; -int ScreenSizing; -bool IntegerScaling; -int ScreenAspectTop; -int ScreenAspectBot; -bool ScreenFilter; - -bool ScreenUseGL; -bool ScreenVSync; -int ScreenVSyncInterval; - -int _3DRenderer; -bool Threaded3D; - -int GL_ScaleFactor; -bool GL_BetterPolygons; - -bool LimitFPS; -bool AudioSync; -bool ShowOSD; - -int ConsoleType; -bool DirectBoot; - -#ifdef JIT_ENABLED -bool JIT_Enable = false; -int JIT_MaxBlockSize = 32; -bool JIT_BranchOptimisations = true; -bool JIT_LiteralOptimisations = true; -bool JIT_FastMemory = true; -#endif - -bool ExternalBIOSEnable; - -std::string BIOS9Path; -std::string BIOS7Path; -std::string FirmwarePath; - -std::string DSiBIOS9Path; -std::string DSiBIOS7Path; -std::string DSiFirmwarePath; -std::string DSiNANDPath; - -bool DLDIEnable; -std::string DLDISDPath; -int DLDISize; -bool DLDIReadOnly; -bool DLDIFolderSync; -std::string DLDIFolderPath; - -bool DSiSDEnable; -std::string DSiSDPath; -int DSiSDSize; -bool DSiSDReadOnly; -bool DSiSDFolderSync; -std::string DSiSDFolderPath; - -bool FirmwareOverrideSettings; -std::string FirmwareUsername; -int FirmwareLanguage; -int FirmwareBirthdayMonth; -int FirmwareBirthdayDay; -int FirmwareFavouriteColour; -std::string FirmwareMessage; -std::string FirmwareMAC; -std::string WifiSettingsPath = "wfcsettings.bin"; // Should this be configurable? - -int MPAudioMode; -int MPRecvTimeout; - -std::string LANDevice; -bool DirectLAN; - -bool SavestateRelocSRAM; - -int AudioInterp; -int AudioBitDepth; -int AudioVolume; -bool DSiVolumeSync; -int MicInputType; -std::string MicDevice; -std::string MicWavPath; - -std::string LastROMFolder; -std::string LastBIOSFolder; - -std::string RecentROMList[10]; - -std::string SaveFilePath; -std::string SavestatePath; -std::string CheatFilePath; - -bool EnableCheats; - -bool MouseHide; -int MouseHideSeconds; - -bool PauseLostFocus; - -int64_t RTCOffset; - -bool DSBatteryLevelOkay; -int DSiBatteryLevel; -bool DSiBatteryCharging; - -bool DSiFullBIOSBoot; - -bool DebugPrintEnabled; - -#ifdef GDBSTUB_ENABLED -bool GdbEnabled; -int GdbPortARM7; -int GdbPortARM9; -bool GdbARM7BreakOnStartup; -bool GdbARM9BreakOnStartup; -#endif - -CameraConfig Camera[2]; - - -const char* kConfigFile = "melonDS.ini"; -const char* kUniqueConfigFile = "melonDS.%d.ini"; - -ConfigEntry ConfigFile[] = +DefaultList DefaultInts = { - {"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}, + {"Instance*.Keyboard", -1}, + {"Instance*.Joystick", -1}, + {"Instance*.Window*.Width", 256}, + {"Instance*.Window*.Height", 384}, + {"Screen.VSyncInterval", 1}, + {"3D.Renderer", renderer3D_Software}, + {"3D.GL.ScaleFactor", 1}, +#ifdef JIT_ENABLED + {"JIT.MaxBlockSize", 32}, +#endif + {"Instance*.Firmware.Language", 1}, + {"Instance*.Firmware.BirthdayMonth", 1}, + {"Instance*.Firmware.BirthdayDay", 1}, + {"MP.AudioMode", 1}, + {"MP.RecvTimeout", 25}, + {"Instance*.Audio.Volume", 256}, + {"Mic.InputType", 1}, + {"Mouse.HideSeconds", 5}, + {"Instance*.DSi.Battery.Level", 0xF}, +#ifdef GDBSTUB_ENABLED + {"Instance*.Gdb.ARM7.Port", 3334}, + {"Instance*.Gdb.ARM9.Port", 3333}, +#endif + {"LAN.HostNumPlayers", 16}, +}; - {"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}, +RangeList IntRanges = +{ + {"Emu.ConsoleType", {0, 1}}, + {"3D.Renderer", {0, renderer3D_Max-1}}, + {"Screen.VSyncInterval", {1, 20}}, + {"3D.GL.ScaleFactor", {1, 16}}, + {"Audio.Interpolation", {0, 4}}, + {"Instance*.Audio.Volume", {0, 256}}, + {"Mic.InputType", {0, micInputType_MAX-1}}, + {"Instance*.Window*.ScreenRotation", {0, screenRot_MAX-1}}, + {"Instance*.Window*.ScreenGap", {0, 500}}, + {"Instance*.Window*.ScreenLayout", {0, screenLayout_MAX-1}}, + {"Instance*.Window*.ScreenSizing", {0, screenSizing_MAX-1}}, + {"Instance*.Window*.ScreenAspectTop", {0, AspectRatiosNum-1}}, + {"Instance*.Window*.ScreenAspectBot", {0, AspectRatiosNum-1}}, + {"MP.AudioMode", {0, 2}}, + {"LAN.HostNumPlayers", {2, 16}}, +}; - {"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_SwapScreenEmphasis", 0, &HKKeyMapping[HK_SwapScreenEmphasis], -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}, - {"HKKey_PowerButton", 0, &HKKeyMapping[HK_PowerButton], -1, true}, - {"HKKey_VolumeUp", 0, &HKKeyMapping[HK_VolumeUp], -1, true}, - {"HKKey_VolumeDown", 0, &HKKeyMapping[HK_VolumeDown], -1, true}, +DefaultList DefaultBools = +{ + {"Screen.Filter", true}, + {"3D.Soft.Threaded", true}, + {"3D.GL.HiresCoordinates", true}, + {"LimitFPS", true}, + {"Instance*.Window*.ShowOSD", true}, + {"Emu.DirectBoot", true}, + {"Instance*.DS.Battery.LevelOkay", true}, + {"Instance*.DSi.Battery.Charging", true}, +#ifdef JIT_ENABLED + {"JIT.BranchOptimisations", true}, + {"JIT.LiteralOptimisations", true}, +#ifndef __APPLE__ + {"JIT.FastMemory", true}, +#endif +#endif +}; - {"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_SwapScreenEmphasis", 0, &HKJoyMapping[HK_SwapScreenEmphasis], -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}, - {"HKJoy_PowerButton", 0, &HKJoyMapping[HK_PowerButton], -1, true}, - {"HKJoy_VolumeUp", 0, &HKJoyMapping[HK_VolumeUp], -1, true}, - {"HKJoy_VolumeDown", 0, &HKJoyMapping[HK_VolumeDown], -1, true}, +DefaultList DefaultStrings = +{ + {"DLDI.ImagePath", "dldi.bin"}, + {"DSi.SD.ImagePath", "dsisd.bin"}, + {"Instance*.Firmware.Username", "melonDS"} +}; - {"JoystickID", 0, &JoystickID, 0, true}, +DefaultList DefaultDoubles = +{ + {"TargetFPS", 60.0}, + {"FastForwardFPS", 1000.0}, + {"SlowmoFPS", 30.0}, +}; - {"WindowWidth", 0, &WindowWidth, 256, true}, - {"WindowHeight", 0, &WindowHeight, 384, true}, - {"WindowMax", 1, &WindowMaximized, false, true}, +LegacyEntry LegacyFile[] = +{ + {"Key_A", 0, "Keyboard.A", true}, + {"Key_B", 0, "Keyboard.B", true}, + {"Key_Select", 0, "Keyboard.Select", true}, + {"Key_Start", 0, "Keyboard.Start", true}, + {"Key_Right", 0, "Keyboard.Right", true}, + {"Key_Left", 0, "Keyboard.Left", true}, + {"Key_Up", 0, "Keyboard.Up", true}, + {"Key_Down", 0, "Keyboard.Down", true}, + {"Key_R", 0, "Keyboard.R", true}, + {"Key_L", 0, "Keyboard.L", true}, + {"Key_X", 0, "Keyboard.X", true}, + {"Key_Y", 0, "Keyboard.Y", 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}, + {"Joy_A", 0, "Joystick.A", true}, + {"Joy_B", 0, "Joystick.B", true}, + {"Joy_Select", 0, "Joystick.Select", true}, + {"Joy_Start", 0, "Joystick.Start", true}, + {"Joy_Right", 0, "Joystick.Right", true}, + {"Joy_Left", 0, "Joystick.Left", true}, + {"Joy_Up", 0, "Joystick.Up", true}, + {"Joy_Down", 0, "Joystick.Down", true}, + {"Joy_R", 0, "Joystick.R", true}, + {"Joy_L", 0, "Joystick.L", true}, + {"Joy_X", 0, "Joystick.X", true}, + {"Joy_Y", 0, "Joystick.Y", true}, - {"ScreenUseGL", 1, &ScreenUseGL, false, false}, - {"ScreenVSync", 1, &ScreenVSync, false, false}, - {"ScreenVSyncInterval", 0, &ScreenVSyncInterval, 1, false}, + {"HKKey_Lid", 0, "Keyboard.HK_Lid", true}, + {"HKKey_Mic", 0, "Keyboard.HK_Mic", true}, + {"HKKey_Pause", 0, "Keyboard.HK_Pause", true}, + {"HKKey_Reset", 0, "Keyboard.HK_Reset", true}, + {"HKKey_FastForward", 0, "Keyboard.HK_FastForward", true}, + {"HKKey_FastForwardToggle", 0, "Keyboard.HK_FrameLimitToggle", true}, + {"HKKey_FullscreenToggle", 0, "Keyboard.HK_FullscreenToggle", true}, + {"HKKey_SwapScreens", 0, "Keyboard.HK_SwapScreens", true}, + {"HKKey_SwapScreenEmphasis", 0, "Keyboard.HK_SwapScreenEmphasis", true}, + {"HKKey_SolarSensorDecrease", 0, "Keyboard.HK_SolarSensorDecrease", true}, + {"HKKey_SolarSensorIncrease", 0, "Keyboard.HK_SolarSensorIncrease", true}, + {"HKKey_FrameStep", 0, "Keyboard.HK_FrameStep", true}, + {"HKKey_PowerButton", 0, "Keyboard.HK_PowerButton", true}, + {"HKKey_VolumeUp", 0, "Keyboard.HK_VolumeUp", true}, + {"HKKey_VolumeDown", 0, "Keyboard.HK_VolumeDown", true}, - {"3DRenderer", 0, &_3DRenderer, 0, false}, - {"Threaded3D", 1, &Threaded3D, true, false}, + {"HKJoy_Lid", 0, "Joystick.HK_Lid", true}, + {"HKJoy_Mic", 0, "Joystick.HK_Mic", true}, + {"HKJoy_Pause", 0, "Joystick.HK_Pause", true}, + {"HKJoy_Reset", 0, "Joystick.HK_Reset", true}, + {"HKJoy_FastForward", 0, "Joystick.HK_FastForward", true}, + {"HKJoy_FastForwardToggle", 0, "Joystick.HK_FrameLimitToggle", true}, + {"HKJoy_FullscreenToggle", 0, "Joystick.HK_FullscreenToggle", true}, + {"HKJoy_SwapScreens", 0, "Joystick.HK_SwapScreens", true}, + {"HKJoy_SwapScreenEmphasis", 0, "Joystick.HK_SwapScreenEmphasis", true}, + {"HKJoy_SolarSensorDecrease", 0, "Joystick.HK_SolarSensorDecrease", true}, + {"HKJoy_SolarSensorIncrease", 0, "Joystick.HK_SolarSensorIncrease", true}, + {"HKJoy_FrameStep", 0, "Joystick.HK_FrameStep", true}, + {"HKJoy_PowerButton", 0, "Joystick.HK_PowerButton", true}, + {"HKJoy_VolumeUp", 0, "Joystick.HK_VolumeUp", true}, + {"HKJoy_VolumeDown", 0, "Joystick.HK_VolumeDown", true}, - {"GL_ScaleFactor", 0, &GL_ScaleFactor, 1, false}, - {"GL_BetterPolygons", 1, &GL_BetterPolygons, false, false}, + {"JoystickID", 0, "JoystickID", true}, - {"LimitFPS", 1, &LimitFPS, true, false}, - {"AudioSync", 1, &AudioSync, false}, - {"ShowOSD", 1, &ShowOSD, true, false}, + {"ScreenRotation", 0, "Window0.ScreenRotation", true}, + {"ScreenGap", 0, "Window0.ScreenGap", true}, + {"ScreenLayout", 0, "Window0.ScreenLayout", true}, + {"ScreenSwap", 1, "Window0.ScreenSwap", true}, + {"ScreenSizing", 0, "Window0.ScreenSizing", true}, + {"IntegerScaling", 1, "Window0.IntegerScaling", true}, + {"ScreenAspectTop",0, "Window0.ScreenAspectTop", true}, + {"ScreenAspectBot",0, "Window0.ScreenAspectBot", true}, - {"ConsoleType", 0, &ConsoleType, 0, false}, - {"DirectBoot", 1, &DirectBoot, true, false}, + {"ScreenFilter", 1, "Screen.Filter", false}, + {"ScreenUseGL", 1, "Screen.UseGL", false}, + {"ScreenVSync", 1, "Screen.VSync", false}, + {"ScreenVSyncInterval", 0, "Screen.VSyncInterval", false}, + + {"3DRenderer", 0, "3D.Renderer", false}, + {"Threaded3D", 1, "3D.Soft.Threaded", false}, + + {"GL_ScaleFactor", 0, "3D.GL.ScaleFactor", false}, + {"GL_BetterPolygons", 1, "3D.GL.BetterPolygons", false}, + {"GL_HiresCoordinates", 1, "3D.GL.HiresCoordinates", false}, + + {"LimitFPS", 1, "LimitFPS", false}, + {"MaxFPS", 0, "MaxFPS", false}, + {"AudioSync", 1, "AudioSync", false}, + {"ShowOSD", 1, "Window0.ShowOSD", true}, + + {"ConsoleType", 0, "Emu.ConsoleType", false}, + {"DirectBoot", 1, "Emu.DirectBoot", false}, #ifdef JIT_ENABLED - {"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, false}, - #else - {"JIT_FastMemory", 1, &JIT_FastMemory, true, false}, - #endif + {"JIT_Enable", 1, "JIT.Enable", false}, + {"JIT_MaxBlockSize", 0, "JIT.MaxBlockSize", false}, + {"JIT_BranchOptimisations", 1, "JIT.BranchOptimisations", false}, + {"JIT_LiteralOptimisations", 1, "JIT.LiteralOptimisations", false}, + {"JIT_FastMemory", 1, "JIT.FastMemory", false}, #endif - {"ExternalBIOSEnable", 1, &ExternalBIOSEnable, false, false}, + {"ExternalBIOSEnable", 1, "Emu.ExternalBIOSEnable", false}, - {"BIOS9Path", 2, &BIOS9Path, (std::string)"", false}, - {"BIOS7Path", 2, &BIOS7Path, (std::string)"", false}, - {"FirmwarePath", 2, &FirmwarePath, (std::string)"", false}, + {"BIOS9Path", 2, "DS.BIOS9Path", false}, + {"BIOS7Path", 2, "DS.BIOS7Path", false}, + {"FirmwarePath", 2, "DS.FirmwarePath", false}, - {"DSiBIOS9Path", 2, &DSiBIOS9Path, (std::string)"", false}, - {"DSiBIOS7Path", 2, &DSiBIOS7Path, (std::string)"", false}, - {"DSiFirmwarePath", 2, &DSiFirmwarePath, (std::string)"", false}, - {"DSiNANDPath", 2, &DSiNANDPath, (std::string)"", false}, + {"DSiBIOS9Path", 2, "DSi.BIOS9Path", false}, + {"DSiBIOS7Path", 2, "DSi.BIOS7Path", false}, + {"DSiFirmwarePath", 2, "DSi.FirmwarePath", false}, + {"DSiNANDPath", 2, "DSi.NANDPath", false}, - {"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}, + {"DLDIEnable", 1, "DLDI.Enable", false}, + {"DLDISDPath", 2, "DLDI.ImagePath", false}, + {"DLDISize", 0, "DLDI.ImageSize", false}, + {"DLDIReadOnly", 1, "DLDI.ReadOnly", false}, + {"DLDIFolderSync", 1, "DLDI.FolderSync", false}, + {"DLDIFolderPath", 2, "DLDI.FolderPath", false}, - {"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}, + {"DSiSDEnable", 1, "DSi.SD.Enable", false}, + {"DSiSDPath", 2, "DSi.SD.ImagePath", false}, + {"DSiSDSize", 0, "DSi.SD.ImageSize", false}, + {"DSiSDReadOnly", 1, "DSi.SD.ReadOnly", false}, + {"DSiSDFolderSync", 1, "DSi.SD.FolderSync", false}, + {"DSiSDFolderPath", 2, "DSi.SD.FolderPath", 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}, + {"FirmwareOverrideSettings", 1, "Firmware.OverrideSettings", true}, + {"FirmwareUsername", 2, "Firmware.Username", true}, + {"FirmwareLanguage", 0, "Firmware.Language", true}, + {"FirmwareBirthdayMonth", 0, "Firmware.BirthdayMonth", true}, + {"FirmwareBirthdayDay", 0, "Firmware.BirthdayDay", true}, + {"FirmwareFavouriteColour", 0, "Firmware.FavouriteColour", true}, + {"FirmwareMessage", 2, "Firmware.Message", true}, + {"FirmwareMAC", 2, "Firmware.MAC", true}, - {"MPAudioMode", 0, &MPAudioMode, 1, false}, - {"MPRecvTimeout", 0, &MPRecvTimeout, 25, false}, + {"MPAudioMode", 0, "MP.AudioMode", false}, + {"MPRecvTimeout", 0, "MP.RecvTimeout", false}, - {"LANDevice", 2, &LANDevice, (std::string)"", false}, - {"DirectLAN", 1, &DirectLAN, false, false}, + {"LANDevice", 2, "LAN.Device", false}, + {"DirectLAN", 1, "LAN.DirectMode", false}, - {"SavStaRelocSRAM", 1, &SavestateRelocSRAM, false, false}, + {"SavStaRelocSRAM", 1, "Savestate.RelocSRAM", false}, - {"AudioInterp", 0, &AudioInterp, 0, false}, - {"AudioBitDepth", 0, &AudioBitDepth, 0, false}, - {"AudioVolume", 0, &AudioVolume, 256, true}, - {"DSiVolumeSync", 1, &DSiVolumeSync, false, true}, - {"MicInputType", 0, &MicInputType, 1, false}, - {"MicDevice", 2, &MicDevice, (std::string)"", false}, - {"MicWavPath", 2, &MicWavPath, (std::string)"", false}, + {"AudioInterp", 0, "Audio.Interpolation", false}, + {"AudioBitDepth", 0, "Audio.BitDepth", false}, + {"AudioVolume", 0, "Audio.Volume", true}, + {"DSiVolumeSync", 1, "Audio.DSiVolumeSync", true}, + {"MicInputType", 0, "Mic.InputType", false}, + {"MicDevice", 2, "Mic.Device", false}, + {"MicWavPath", 2, "Mic.WavPath", false}, - {"LastROMFolder", 2, &LastROMFolder, (std::string)"", true}, - {"LastBIOSFolder", 2, &LastBIOSFolder, (std::string)"", true}, + {"LastROMFolder", 2, "LastROMFolder", false}, + {"LastBIOSFolder", 2, "LastBIOSFolder", false}, - {"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}, + {"RecentROM_0", 4, "RecentROM[0]", false}, + {"RecentROM_1", 4, "RecentROM[1]", false}, + {"RecentROM_2", 4, "RecentROM[2]", false}, + {"RecentROM_3", 4, "RecentROM[3]", false}, + {"RecentROM_4", 4, "RecentROM[4]", false}, + {"RecentROM_5", 4, "RecentROM[5]", false}, + {"RecentROM_6", 4, "RecentROM[6]", false}, + {"RecentROM_7", 4, "RecentROM[7]", false}, + {"RecentROM_8", 4, "RecentROM[8]", false}, + {"RecentROM_9", 4, "RecentROM[9]", false}, - {"SaveFilePath", 2, &SaveFilePath, (std::string)"", true}, - {"SavestatePath", 2, &SavestatePath, (std::string)"", true}, - {"CheatFilePath", 2, &CheatFilePath, (std::string)"", true}, + {"SaveFilePath", 2, "SaveFilePath", true}, + {"SavestatePath", 2, "SavestatePath", true}, + {"CheatFilePath", 2, "CheatFilePath", true}, - {"EnableCheats", 1, &EnableCheats, false, true}, + {"EnableCheats", 1, "EnableCheats", true}, - {"MouseHide", 1, &MouseHide, false, false}, - {"MouseHideSeconds", 0, &MouseHideSeconds, 5, false}, - {"PauseLostFocus", 1, &PauseLostFocus, false, false}, + {"MouseHide", 1, "Mouse.Hide", false}, + {"MouseHideSeconds", 0, "Mouse.HideSeconds", false}, + {"PauseLostFocus", 1, "PauseLostFocus", false}, + {"UITheme", 2, "UITheme", false}, - {"RTCOffset", 3, &RTCOffset, (int64_t)0, true}, + {"RTCOffset", 3, "RTC.Offset", true}, - {"DSBatteryLevelOkay", 1, &DSBatteryLevelOkay, true, true}, - {"DSiBatteryLevel", 0, &DSiBatteryLevel, 0xF, true}, - {"DSiBatteryCharging", 1, &DSiBatteryCharging, true, true}, + {"DSBatteryLevelOkay", 1, "DS.Battery.LevelOkay", true}, + {"DSiBatteryLevel", 0, "DSi.Battery.Level", true}, + {"DSiBatteryCharging", 1, "DSi.Battery.Charging", true}, - {"DSiFullBIOSBoot", 1, &DSiFullBIOSBoot, false, true}, + {"DSiFullBIOSBoot", 1, "DSi.FullBIOSBoot", true}, + + {"DebugPrintEnabled", 1, "DS.DebugPrintEnabled", true}, #ifdef GDBSTUB_ENABLED - {"GdbEnabled", 1, &GdbEnabled, false, false}, - {"GdbPortARM7", 0, &GdbPortARM7, 3334, true}, - {"GdbPortARM9", 0, &GdbPortARM9, 3333, true}, - {"GdbARM7BreakOnStartup", 1, &GdbARM7BreakOnStartup, false, true}, - {"GdbARM9BreakOnStartup", 1, &GdbARM9BreakOnStartup, false, true}, + {"GdbEnabled", 1, "Gdb.Enabled", false}, + {"GdbPortARM7", 0, "Gdb.ARM7.Port", true}, + {"GdbPortARM9", 0, "Gdb.ARM9.Port", true}, + {"GdbARM7BreakOnStartup", 1, "Gdb.ARM7.BreakOnStartup", true}, + {"GdbARM9BreakOnStartup", 1, "Gdb.ARM9.BreakOnStartup", true}, #endif - // TODO!! - // we need a more elegant way to deal with this - {"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}, + {"Camera0_InputType", 0, "DSi.Camera0.InputType", false}, + {"Camera0_ImagePath", 2, "DSi.Camera0.ImagePath", false}, + {"Camera0_CamDeviceName", 2, "DSi.Camera0.DeviceName", false}, + {"Camera0_XFlip", 1, "DSi.Camera0.XFlip", false}, + {"Camera1_InputType", 0, "DSi.Camera1.InputType", false}, + {"Camera1_ImagePath", 2, "DSi.Camera1.ImagePath", false}, + {"Camera1_CamDeviceName", 2, "DSi.Camera1.DeviceName", false}, + {"Camera1_XFlip", 1, "DSi.Camera1.XFlip", false}, - {"", -1, nullptr, 0, false} + {"", -1, "", false} }; -void LoadFile(int inst) +static std::string GetDefaultKey(std::string path) +{ + std::string tables[] = {"Instance", "Window", "Camera"}; + + std::string ret = ""; + int plen = path.length(); + for (int i = 0; i < plen;) + { + bool found = false; + + for (auto& tbl : tables) + { + int tlen = tbl.length(); + if ((plen-i) <= tlen) continue; + if (path.substr(i, tlen) != tbl) continue; + if (path[i+tlen] < '0' || path[i+tlen] > '9') continue; + + ret += tbl + "*"; + i = path.find('.', i+tlen); + if (i == std::string::npos) return ret; + + found = true; + break; + } + + if (!found) + { + ret += path[i]; + i++; + } + } + + return ret; +} + + +Array::Array(toml::value& data) : Data(data) +{ +} + +size_t Array::Size() +{ + return Data.size(); +} + +void Array::Clear() +{ + toml::array newarray; + Data = newarray; +} + +Array Array::GetArray(const int id) +{ + while (Data.size() < id+1) + Data.push_back(toml::array()); + + toml::value& arr = Data[id]; + if (!arr.is_array()) + arr = toml::array(); + + return Array(arr); +} + +int Array::GetInt(const int id) +{ + while (Data.size() < id+1) + Data.push_back(0); + + toml::value& tval = Data[id]; + if (!tval.is_integer()) + tval = 0; + + return (int)tval.as_integer(); +} + +int64_t Array::GetInt64(const int id) +{ + while (Data.size() < id+1) + Data.push_back(0); + + toml::value& tval = Data[id]; + if (!tval.is_integer()) + tval = 0; + + return tval.as_integer(); +} + +bool Array::GetBool(const int id) +{ + while (Data.size() < id+1) + Data.push_back(false); + + toml::value& tval = Data[id]; + if (!tval.is_boolean()) + tval = false; + + return tval.as_boolean(); +} + +std::string Array::GetString(const int id) +{ + while (Data.size() < id+1) + Data.push_back(""); + + toml::value& tval = Data[id]; + if (!tval.is_string()) + tval = ""; + + return tval.as_string(); +} + +double Array::GetDouble(const int id) +{ + while (Data.size() < id+1) + Data.push_back(0.0); + + toml::value& tval = Data[id]; + if (!tval.is_floating()) + tval = 0.0; + + return tval.as_floating(); +} + +void Array::SetInt(const int id, int val) +{ + while (Data.size() < id+1) + Data.push_back(0); + + toml::value& tval = Data[id]; + tval = val; +} + +void Array::SetInt64(const int id, int64_t val) +{ + while (Data.size() < id+1) + Data.push_back(0); + + toml::value& tval = Data[id]; + tval = val; +} + +void Array::SetBool(const int id, bool val) +{ + while (Data.size() < id+1) + Data.push_back(false); + + toml::value& tval = Data[id]; + tval = val; +} + +void Array::SetString(const int id, const std::string& val) +{ + while (Data.size() < id+1) + Data.push_back(""); + + toml::value& tval = Data[id]; + tval = val; +} + +void Array::SetDouble(const int id, double val) +{ + while (Data.size() < id+1) + Data.push_back(0.0); + + toml::value& tval = Data[id]; + tval = val; +} + + +/*Table::Table()// : Data(toml::value()) +{ + Data = toml::value(); + PathPrefix = ""; +}*/ + +Table::Table(toml::value& data, const std::string& path) : Data(data) +{ + if (path.empty()) + PathPrefix = ""; + else + PathPrefix = path + "."; +} + +Table& Table::operator=(const Table& b) +{ + Data = b.Data; + PathPrefix = b.PathPrefix; + + return *this; +} + +Array Table::GetArray(const std::string& path) +{ + toml::value& arr = ResolvePath(path); + if (!arr.is_array()) + arr = toml::array(); + + return Array(arr); +} + +Table Table::GetTable(const std::string& path, const std::string& defpath) +{ + toml::value& tbl = ResolvePath(path); + if (!tbl.is_table()) + { + toml::value defval = toml::table(); + if (!defpath.empty()) + defval = ResolvePath(defpath); + + tbl = defval; + } + + return Table(tbl, PathPrefix + path); +} + +int Table::GetInt(const std::string& path) +{ + toml::value& tval = ResolvePath(path); + if (!tval.is_integer()) + tval = FindDefault(path, 0, DefaultInts); + + int ret = (int)tval.as_integer(); + + std::string rngkey = GetDefaultKey(PathPrefix+path); + if (IntRanges.count(rngkey) != 0) + { + auto& range = IntRanges[rngkey]; + ret = std::clamp(ret, std::get<0>(range), std::get<1>(range)); + } + + return ret; +} + +int64_t Table::GetInt64(const std::string& path) +{ + toml::value& tval = ResolvePath(path); + if (!tval.is_integer()) + tval = 0; + + return tval.as_integer(); +} + +bool Table::GetBool(const std::string& path) +{ + toml::value& tval = ResolvePath(path); + if (!tval.is_boolean()) + tval = FindDefault(path, false, DefaultBools); + + return tval.as_boolean(); +} + +std::string Table::GetString(const std::string& path) +{ + toml::value& tval = ResolvePath(path); + if (!tval.is_string()) + tval = FindDefault(path, ""s, DefaultStrings); + + return tval.as_string(); +} + +double Table::GetDouble(const std::string& path) +{ + toml::value& tval = ResolvePath(path); + if (!tval.is_floating()) + tval = FindDefault(path, 0.0, DefaultDoubles); + + return tval.as_floating(); +} + +void Table::SetInt(const std::string& path, int val) +{ + std::string rngkey = GetDefaultKey(PathPrefix+path); + if (IntRanges.count(rngkey) != 0) + { + auto& range = IntRanges[rngkey]; + val = std::clamp(val, std::get<0>(range), std::get<1>(range)); + } + + toml::value& tval = ResolvePath(path); + tval = val; +} + +void Table::SetInt64(const std::string& path, int64_t val) +{ + toml::value& tval = ResolvePath(path); + tval = val; +} + +void Table::SetBool(const std::string& path, bool val) +{ + toml::value& tval = ResolvePath(path); + tval = val; +} + +void Table::SetString(const std::string& path, const std::string& val) +{ + toml::value& tval = ResolvePath(path); + tval = val; +} + +void Table::SetDouble(const std::string& path, double val) +{ + toml::value& tval = ResolvePath(path); + tval = val; +} + +toml::value& Table::ResolvePath(const std::string& path) +{ + toml::value* ret = &Data; + std::string tmp = path; + + size_t sep; + while ((sep = tmp.find('.')) != std::string::npos) + { + ret = &(*ret)[tmp.substr(0, sep)]; + tmp = tmp.substr(sep+1); + } + + return (*ret)[tmp]; +} + +template T Table::FindDefault(const std::string& path, T def, DefaultList list) +{ + std::string defkey = GetDefaultKey(PathPrefix+path); + + T ret = def; + while (list.count(defkey) == 0) + { + if (defkey.empty()) break; + size_t sep = defkey.rfind('.'); + if (sep == std::string::npos) break; + defkey = defkey.substr(0, sep); + } + if (list.count(defkey) != 0) + ret = list[defkey]; + + return ret; +} + + +bool LoadLegacyFile(int inst) { Platform::FileHandle* f; if (inst > 0) { char name[100] = {0}; - snprintf(name, 99, kUniqueConfigFile, inst+1); + snprintf(name, 99, kLegacyUniqueConfigFile, inst+1); f = Platform::OpenLocalFile(name, Platform::FileMode::ReadText); } else - f = Platform::OpenLocalFile(kConfigFile, Platform::FileMode::ReadText); + { + f = Platform::OpenLocalFile(kLegacyConfigFile, Platform::FileMode::ReadText); + } - if (!f) return; + if (!f) return true; + + toml::value* root;// = GetLocalTable(inst); + if (inst == -1) + root = &RootTable; + else + root = &RootTable["Instance" + std::to_string(inst)]; char linebuf[1024]; char entryname[32]; @@ -402,19 +705,56 @@ void LoadFile(int inst) entryname[31] = '\0'; if (ret < 2) continue; - for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) + for (LegacyEntry* entry = &LegacyFile[0]; entry->Type != -1; entry++) { if (!strncmp(entry->Name, entryname, 32)) { - if ((inst > 0) && (!entry->InstanceUnique)) + if (!(entry->InstanceUnique ^ (inst == -1))) break; + std::string path = entry->TOMLPath; + toml::value* table = root; + size_t sep; + while ((sep = path.find('.')) != std::string::npos) + { + table = &(*table)[path.substr(0, sep)]; + path = path.substr(sep+1); + } + + int arrayid = -1; + if (path[path.size()-1] == ']') + { + size_t tmp = path.rfind('['); + arrayid = std::stoi(path.substr(tmp+1, path.size()-tmp-2)); + path = path.substr(0, tmp); + } + + toml::value& val = (*table)[path]; + switch (entry->Type) { - case 0: *(int*)entry->Value = strtol(entryval, NULL, 10); break; - case 1: *(bool*)entry->Value = strtol(entryval, NULL, 10) ? true:false; break; - case 2: *(std::string*)entry->Value = entryval; break; - case 3: *(int64_t*)entry->Value = strtoll(entryval, NULL, 10); break; + case 0: + val = strtol(entryval, nullptr, 10); + break; + + case 1: + val = !!strtol(entryval, nullptr, 10); + break; + + case 2: + val = entryval; + break; + + case 3: + val = strtoll(entryval, nullptr, 10); + break; + + case 4: + if (!val.is_array()) val = toml::array(); + while (val.size() < arrayid+1) + val.push_back(""); + val[arrayid] = entryval; + break; } break; @@ -423,60 +763,65 @@ void LoadFile(int inst) } CloseFile(f); + return true; } -void Load() +bool LoadLegacy() { + for (int i = -1; i < 16; i++) + LoadLegacyFile(i); - for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) + return true; +} + +bool Load() +{ + auto cfgpath = Platform::GetLocalFilePath(kConfigFile); + + if (!Platform::CheckFileWritable(cfgpath)) + return false; + + RootTable = toml::value(); + + if (!Platform::FileExists(cfgpath)) + return LoadLegacy(); + + try { - 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; - case 3: *(int64_t*)entry->Value = std::get(entry->Default); break; - } + RootTable = toml::parse(std::filesystem::u8path(cfgpath)); + } + catch (toml::syntax_error& err) + { + //RootTable = toml::table(); } - LoadFile(0); - - int inst = Platform::InstanceID(); - if (inst > 0) - LoadFile(inst); + return true; } void Save() { - int inst = Platform::InstanceID(); + auto cfgpath = Platform::GetLocalFilePath(kConfigFile); + if (!Platform::CheckFileWritable(cfgpath)) + return; - Platform::FileHandle* f; - if (inst > 0) - { - char name[100] = {0}; - snprintf(name, 99, kUniqueConfigFile, inst+1); - f = Platform::OpenLocalFile(name, Platform::FileMode::WriteText); - } - else - f = Platform::OpenLocalFile(kConfigFile, Platform::FileMode::WriteText); + std::ofstream file; + file.open(std::filesystem::u8path(cfgpath), std::ofstream::out | std::ofstream::trunc); + file << RootTable; + file.close(); +} - if (!f) return; - for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) - { - if ((inst > 0) && (!entry->InstanceUnique)) - continue; +Table GetLocalTable(int instance) +{ + if (instance == -1) + return Table(RootTable, ""); - switch (entry->Type) - { - case 0: Platform::FileWriteFormatted(f, "%s=%d\n", entry->Name, *(int*)entry->Value); break; - case 1: Platform::FileWriteFormatted(f, "%s=%d\n", entry->Name, *(bool*)entry->Value ? 1:0); break; - case 2: Platform::FileWriteFormatted(f, "%s=%s\n", entry->Name, (*(std::string*)entry->Value).c_str()); break; - case 3: Platform::FileWriteFormatted(f, "%s=%" PRId64 "\n", entry->Name, *(int64_t*)entry->Value); break; - } - } + std::string key = "Instance" + std::to_string(instance); + toml::value& tbl = RootTable[key]; + if (tbl.is_uninitialized()) + RootTable[key] = RootTable["Instance0"]; - CloseFile(f); + return Table(tbl, key); } } diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h index ed14f1e4..9e6d3ea4 100644 --- a/src/frontend/qt_sdl/Config.h +++ b/src/frontend/qt_sdl/Config.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -16,197 +16,126 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#ifndef PLATFORMCONFIG_H -#define PLATFORMCONFIG_H +#ifndef CONFIG_H +#define CONFIG_H #include #include +#include +#include +#include -enum -{ - HK_Lid = 0, - HK_Mic, - HK_Pause, - HK_Reset, - HK_FastForward, - HK_FastForwardToggle, - HK_FullscreenToggle, - HK_SwapScreens, - HK_SwapScreenEmphasis, - HK_SolarSensorDecrease, - HK_SolarSensorIncrease, - HK_FrameStep, - HK_PowerButton, - HK_VolumeUp, - HK_VolumeDown, - HK_MAX -}; - -enum -{ - micInputType_Silence, - micInputType_External, - micInputType_Noise, - micInputType_Wav, - micInputType_MAX, -}; +#include "toml/toml/value.hpp" namespace Config { -struct ConfigEntry +struct LegacyEntry { char Name[32]; int Type; // 0=int 1=bool 2=string 3=64bit int - void* Value; // pointer to the value variable - std::variant Default; + char TOMLPath[64]; bool InstanceUnique; // whether the setting can exist individually for each instance in multiplayer }; -struct CameraConfig +template +using DefaultList = std::unordered_map; + +using RangeList = std::unordered_map>; + +class Table; + +class Array { - int InputType; // 0=blank 1=image 2=camera - std::string ImagePath; - std::string CamDeviceName; - bool XFlip; +public: + Array(toml::value& data); + ~Array() {} + + size_t Size(); + + void Clear(); + + Array GetArray(const int id); + + int GetInt(const int id); + int64_t GetInt64(const int id); + bool GetBool(const int id); + std::string GetString(const int id); + double GetDouble(const int id); + + void SetInt(const int id, int val); + void SetInt64(const int id, int64_t val); + void SetBool(const int id, bool val); + void SetString(const int id, const std::string& val); + void SetDouble(const int id, double val); + + // convenience + + QString GetQString(const int id) + { + return QString::fromStdString(GetString(id)); + } + + void SetQString(const int id, const QString& val) + { + return SetString(id, val.toStdString()); + } + +private: + toml::value& Data; +}; + +class Table +{ +public: + //Table(); + Table(toml::value& data, const std::string& path); + ~Table() {} + + Table& operator=(const Table& b); + + Array GetArray(const std::string& path); + Table GetTable(const std::string& path, const std::string& defpath = ""); + + int GetInt(const std::string& path); + int64_t GetInt64(const std::string& path); + bool GetBool(const std::string& path); + std::string GetString(const std::string& path); + double GetDouble(const std::string& path); + + void SetInt(const std::string& path, int val); + void SetInt64(const std::string& path, int64_t val); + void SetBool(const std::string& path, bool val); + void SetString(const std::string& path, const std::string& val); + void SetDouble(const std::string& path, double val); + + // convenience + + QString GetQString(const std::string& path) + { + return QString::fromStdString(GetString(path)); + } + + void SetQString(const std::string& path, const QString& val) + { + return SetString(path, val.toStdString()); + } + +private: + toml::value& Data; + std::string PathPrefix; + + toml::value& ResolvePath(const std::string& path); + template T FindDefault(const std::string& path, T def, DefaultList list); }; -extern int KeyMapping[12]; -extern int JoyMapping[12]; - -extern int HKKeyMapping[HK_MAX]; -extern int HKJoyMapping[HK_MAX]; - -extern int JoystickID; - -extern int WindowWidth; -extern int WindowHeight; -extern bool WindowMaximized; - -extern int ScreenRotation; -extern int ScreenGap; -extern int ScreenLayout; -extern bool ScreenSwap; -extern int ScreenSizing; -extern int ScreenAspectTop; -extern int ScreenAspectBot; -extern bool IntegerScaling; -extern bool ScreenFilter; - -extern bool ScreenUseGL; -extern bool ScreenVSync; -extern int ScreenVSyncInterval; - -extern int _3DRenderer; -extern bool Threaded3D; - -extern int GL_ScaleFactor; -extern bool GL_BetterPolygons; - -extern bool LimitFPS; -extern bool AudioSync; -extern bool ShowOSD; - -extern int ConsoleType; -extern bool DirectBoot; - -#ifdef JIT_ENABLED -extern bool JIT_Enable; -extern int JIT_MaxBlockSize; -extern bool JIT_BranchOptimisations; -extern bool JIT_LiteralOptimisations; -extern bool JIT_FastMemory; -#endif - -extern bool ExternalBIOSEnable; - -extern std::string BIOS9Path; -extern std::string BIOS7Path; -extern std::string FirmwarePath; - -extern std::string DSiBIOS9Path; -extern std::string DSiBIOS7Path; -extern std::string DSiFirmwarePath; -extern std::string DSiNANDPath; - -extern bool DLDIEnable; -extern std::string DLDISDPath; -extern int DLDISize; -extern bool DLDIReadOnly; -extern bool DLDIFolderSync; -extern std::string DLDIFolderPath; - -extern bool DSiSDEnable; -extern std::string DSiSDPath; -extern int DSiSDSize; -extern bool DSiSDReadOnly; -extern bool DSiSDFolderSync; -extern std::string DSiSDFolderPath; - -extern bool FirmwareOverrideSettings; -extern std::string FirmwareUsername; -extern int FirmwareLanguage; -extern int FirmwareBirthdayMonth; -extern int FirmwareBirthdayDay; -extern int FirmwareFavouriteColour; -extern std::string FirmwareMessage; -extern std::string FirmwareMAC; -extern std::string WifiSettingsPath; - -extern int MPAudioMode; -extern int MPRecvTimeout; - -extern std::string LANDevice; -extern bool DirectLAN; - -extern bool SavestateRelocSRAM; - -extern int AudioInterp; -extern int AudioBitDepth; -extern int AudioVolume; -extern bool DSiVolumeSync; -extern int MicInputType; -extern std::string MicDevice; -extern std::string MicWavPath; - -extern std::string LastROMFolder; -extern std::string LastBIOSFolder; - -extern std::string RecentROMList[10]; - -extern std::string SaveFilePath; -extern std::string SavestatePath; -extern std::string CheatFilePath; - -extern bool EnableCheats; - -extern bool MouseHide; -extern int MouseHideSeconds; -extern bool PauseLostFocus; - -extern int64_t RTCOffset; - -extern bool DSBatteryLevelOkay; -extern int DSiBatteryLevel; -extern bool DSiBatteryCharging; - -extern bool DSiFullBIOSBoot; - -extern bool DebugPrintEnabled; - -extern CameraConfig Camera[2]; - -extern bool GdbEnabled; -extern int GdbPortARM7; -extern int GdbPortARM9; -extern bool GdbARM7BreakOnStartup; -extern bool GdbARM9BreakOnStartup; - - -void Load(); +bool Load(); void Save(); +Table GetLocalTable(int instance); +inline Table GetGlobalTable() { return GetLocalTable(-1); } + } -#endif // PLATFORMCONFIG_H +#endif // CONFIG_H diff --git a/src/frontend/qt_sdl/DateTimeDialog.cpp b/src/frontend/qt_sdl/DateTimeDialog.cpp index 88ae6942..ac98300e 100644 --- a/src/frontend/qt_sdl/DateTimeDialog.cpp +++ b/src/frontend/qt_sdl/DateTimeDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -20,6 +20,7 @@ #include "types.h" #include "Config.h" +#include "main.h" #include "DateTimeDialog.h" #include "ui_DateTimeDialog.h" @@ -32,8 +33,11 @@ DateTimeDialog::DateTimeDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Da ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + auto& cfg = emuInstance->getLocalConfig(); QDateTime now = QDateTime::currentDateTime(); - customTime = now.addSecs(Config::RTCOffset); + customTime = now.addSecs(cfg.GetInt64("RTC.Offset")); ui->chkChangeTime->setChecked(false); ui->chkResetTime->setChecked(false); @@ -59,13 +63,15 @@ void DateTimeDialog::done(int r) { if (r == QDialog::Accepted) { + auto& cfg = emuInstance->getLocalConfig(); + if (ui->chkChangeTime->isChecked()) { QDateTime now = QDateTime::currentDateTime(); - Config::RTCOffset = now.secsTo(ui->txtNewCustomTime->dateTime()); + cfg.SetInt64("RTC.Offset", now.secsTo(ui->txtNewCustomTime->dateTime())); } else if (ui->chkResetTime->isChecked()) - Config::RTCOffset = 0; + cfg.SetInt64("RTC.Offset", 0); Config::Save(); } diff --git a/src/frontend/qt_sdl/DateTimeDialog.h b/src/frontend/qt_sdl/DateTimeDialog.h index 22dabcd0..9763f306 100644 --- a/src/frontend/qt_sdl/DateTimeDialog.h +++ b/src/frontend/qt_sdl/DateTimeDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -26,6 +26,8 @@ namespace Ui {class DateTimeDialog; } class DateTimeDialog; +class EmuInstance; + class DateTimeDialog : public QDialog { Q_OBJECT @@ -63,6 +65,7 @@ private slots: private: Ui::DateTimeDialog* ui; + EmuInstance* emuInstance; QDateTime customTime; }; diff --git a/src/frontend/qt_sdl/EmuInstance.cpp b/src/frontend/qt_sdl/EmuInstance.cpp new file mode 100644 index 00000000..a44aaaef --- /dev/null +++ b/src/frontend/qt_sdl/EmuInstance.cpp @@ -0,0 +1,2190 @@ +/* + Copyright 2016-2024 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 + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#ifdef ARCHIVE_SUPPORT_ENABLED +#include "ArchiveUtil.h" +#endif +#include "EmuInstance.h" +#include "Config.h" +#include "Platform.h" +#include "Net.h" +#include "MPInterface.h" + +#include "NDS.h" +#include "DSi.h" +#include "SPI.h" +#include "RTC.h" +#include "DSi_I2C.h" +#include "FreeBIOS.h" +#include "main.h" + +using std::make_unique; +using std::pair; +using std::string; +using std::tie; +using std::unique_ptr; +using std::wstring_convert; +using namespace melonDS; +using namespace melonDS::Platform; + + +MainWindow* topWindow = nullptr; + +const string kWifiSettingsPath = "wfcsettings.bin"; +extern Net net; + + +EmuInstance::EmuInstance(int inst) : deleting(false), + instanceID(inst), + globalCfg(Config::GetGlobalTable()), + localCfg(Config::GetLocalTable(inst)) +{ + consoleType = globalCfg.GetInt("Emu.ConsoleType"); + + ndsSave = nullptr; + cartType = -1; + baseROMDir = ""; + baseROMName = ""; + baseAssetName = ""; + + gbaSave = nullptr; + gbaCartType = -1; + baseGBAROMDir = ""; + baseGBAROMName = ""; + baseGBAAssetName = ""; + + cheatFile = nullptr; + cheatsOn = localCfg.GetBool("EnableCheats"); + + doLimitFPS = globalCfg.GetBool("LimitFPS"); + + double val = globalCfg.GetDouble("TargetFPS"); + if (val == 0.0) + { + Platform::Log(Platform::LogLevel::Error, "Target FPS in config invalid\n"); + targetFPS = 60.0; + } + else targetFPS = val; + + val = globalCfg.GetDouble("FastForwardFPS"); + if (val == 0.0) + { + Platform::Log(Platform::LogLevel::Error, "Fast-Forward FPS in config invalid\n"); + fastForwardFPS = 60.0; + } + else fastForwardFPS = val; + + val = globalCfg.GetDouble("SlowmoFPS"); + if (val == 0.0) + { + Platform::Log(Platform::LogLevel::Error, "Slow-Mo FPS in config invalid\n"); + slowmoFPS = 60.0; + } + else slowmoFPS = val; + + doAudioSync = globalCfg.GetBool("AudioSync"); + + mpAudioMode = globalCfg.GetInt("MP.AudioMode"); + + nds = nullptr; + //updateConsole(nullptr, nullptr); + + audioInit(); + inputInit(); + + net.RegisterInstance(instanceID); + + emuThread = new EmuThread(this); + + numWindows = 0; + mainWindow = nullptr; + for (int i = 0; i < kMaxWindows; i++) + windowList[i] = nullptr; + + if (inst == 0) topWindow = nullptr; + createWindow(); + + emuThread->start(); + + // if any extra windows were saved as enabled, open them + for (int i = 1; i < kMaxWindows; i++) + { + std::string key = "Window" + std::to_string(i) + ".Enabled"; + bool enable = localCfg.GetBool(key); + if (enable) + createWindow(i); + } +} + +EmuInstance::~EmuInstance() +{ + deleting = true; + deleteAllWindows(); + + emuThread->emuExit(); + emuThread->wait(); + delete emuThread; + + net.UnregisterInstance(instanceID); + + audioDeInit(); + inputDeInit(); + + NDS::Current = nullptr; + if (nds) + { + saveRTCData(); + delete nds; + } +} + + +std::string EmuInstance::instanceFileSuffix() +{ + if (instanceID == 0) return ""; + + char suffix[16] = {0}; + snprintf(suffix, 15, ".%d", instanceID+1); + return suffix; +} + +void EmuInstance::createWindow(int id) +{ + if (numWindows >= kMaxWindows) + { + // TODO + return; + } + + if (id == -1) + { + for (int i = 0; i < kMaxWindows; i++) + { + if (windowList[i]) continue; + id = i; + break; + } + } + + if (id == -1) + return; + if (windowList[id]) + return; + + MainWindow* win = new MainWindow(id, this, topWindow); + if (!topWindow) topWindow = win; + if (!mainWindow) mainWindow = win; + windowList[id] = win; + numWindows++; + + emuThread->attachWindow(win); + + // if creating a secondary window, we may need to initialize its OpenGL context here + if (win->hasOpenGL() && (id != 0)) + emuThread->initContext(id); + + bool enable = (numWindows < kMaxWindows); + doOnAllWindows([=](MainWindow* win) + { + win->actNewWindow->setEnabled(enable); + }); +} + +void EmuInstance::deleteWindow(int id, bool close) +{ + if (id >= kMaxWindows) return; + + MainWindow* win = windowList[id]; + if (!win) return; + + if (win->hasOpenGL()) + emuThread->deinitContext(id); + + emuThread->detachWindow(win); + + windowList[id] = nullptr; + numWindows--; + + if (topWindow == win) topWindow = nullptr; + if (mainWindow == win) mainWindow = nullptr; + + if (close) + win->close(); + + if (numWindows == 0) + { + // if we closed the last window, delete the instance + // if the main window is closed, Qt will take care of closing any secondary windows + deleteEmuInstance(instanceID); + } + else + { + bool enable = (numWindows < kMaxWindows); + doOnAllWindows([=](MainWindow* win) + { + win->actNewWindow->setEnabled(enable); + }); + } +} + +void EmuInstance::deleteAllWindows() +{ + for (int i = kMaxWindows-1; i >= 0; i--) + deleteWindow(i, true); +} + +void EmuInstance::doOnAllWindows(std::function func, int exclude) +{ + for (int i = 0; i < kMaxWindows; i++) + { + if (i == exclude) continue; + if (!windowList[i]) continue; + + func(windowList[i]); + } +} + +void EmuInstance::saveEnabledWindows() +{ + doOnAllWindows([=](MainWindow* win) + { + win->saveEnabled(true); + }); +} + + +void EmuInstance::broadcastCommand(int cmd, QVariant param) +{ + broadcastInstanceCommand(cmd, param, instanceID); +} + +void EmuInstance::handleCommand(int cmd, QVariant& param) +{ + switch (cmd) + { + case InstCmd_Pause: + emuThread->emuPause(false); + break; + + case InstCmd_Unpause: + emuThread->emuUnpause(false); + break; + + case InstCmd_UpdateRecentFiles: + for (int i = 0; i < kMaxWindows; i++) + { + if (windowList[i]) + windowList[i]->loadRecentFilesMenu(true); + } + break; + + /*case InstCmd_UpdateVideoSettings: + mainWindow->updateVideoSettings(param.value()); + break;*/ + } +} + + +void EmuInstance::osdAddMessage(unsigned int color, const char* fmt, ...) +{ + if (fmt == nullptr) + return; + + char msg[256]; + va_list args; + va_start(args, fmt); + vsnprintf(msg, 256, fmt, args); + va_end(args); + + for (int i = 0; i < kMaxWindows; i++) + { + if (windowList[i]) + windowList[i]->osdAddMessage(color, msg); + } +} + + +bool EmuInstance::emuIsActive() +{ + return emuThread->emuIsActive(); +} + +void EmuInstance::emuStop(StopReason reason) +{ + if (reason != StopReason::External) + emuThread->emuStop(false); + + switch (reason) + { + case StopReason::GBAModeNotSupported: + osdAddMessage(0xFFA0A0, "GBA mode not supported"); + break; + case StopReason::BadExceptionRegion: + osdAddMessage(0xFFA0A0, "Internal error"); + break; + case StopReason::PowerOff: + case StopReason::External: + osdAddMessage(0xFFC040, "Shutdown"); + default: + break; + } +} + + +bool EmuInstance::usesOpenGL() +{ + return globalCfg.GetBool("Screen.UseGL") || + (globalCfg.GetInt("3D.Renderer") != renderer3D_Software); +} + +void EmuInstance::initOpenGL(int win) +{ + if (windowList[win]) + windowList[win]->initOpenGL(); + + setVSyncGL(true); +} + +void EmuInstance::deinitOpenGL(int win) +{ + if (windowList[win]) + windowList[win]->deinitOpenGL(); +} + +void EmuInstance::setVSyncGL(bool vsync) +{ + int intv; + + vsync = vsync && globalCfg.GetBool("Screen.VSync"); + if (vsync) + intv = globalCfg.GetInt("Screen.VSyncInterval"); + else + intv = 0; + + for (int i = 0; i < kMaxWindows; i++) + { + if (windowList[i]) + windowList[i]->setGLSwapInterval(intv); + } +} + +void EmuInstance::makeCurrentGL() +{ + mainWindow->makeCurrentGL(); +} + +void EmuInstance::drawScreenGL() +{ + for (int i = 0; i < kMaxWindows; i++) + { + if (windowList[i]) + windowList[i]->drawScreenGL(); + } +} + + +int EmuInstance::lastSep(const std::string& path) +{ + int i = path.length() - 1; + while (i >= 0) + { + if (path[i] == '/' || path[i] == '\\') + return i; + + i--; + } + + return -1; +} + +string EmuInstance::getAssetPath(bool gba, const string& configpath, const string& ext, const string& file = "") +{ + string result; + + if (configpath.empty()) + result = gba ? baseGBAROMDir : baseROMDir; + else + result = configpath; + + // cut off trailing slashes + for (;;) + { + int i = result.length() - 1; + if (i < 0) break; + if (result[i] == '/' || result[i] == '\\') + result.resize(i); + else + break; + } + + if (!result.empty()) + result += '/'; + + if (file.empty()) + { + std::string& baseName = gba ? baseGBAAssetName : baseAssetName; + if (baseName.empty()) + result += "firmware"; + else + result += baseName; + } + else + { + result += file; + } + + result += ext; + + return result; +} + + +QString EmuInstance::verifyDSBIOS() +{ + FileHandle* f; + long len; + + f = Platform::OpenLocalFile(globalCfg.GetString("DS.BIOS9Path"), FileMode::Read); + if (!f) return "DS ARM9 BIOS was not found or could not be accessed. Check your emu settings."; + + len = FileLength(f); + if (len != 0x1000) + { + CloseFile(f); + return "DS ARM9 BIOS is not a valid BIOS dump."; + } + + CloseFile(f); + + f = Platform::OpenLocalFile(globalCfg.GetString("DS.BIOS7Path"), FileMode::Read); + if (!f) return "DS ARM7 BIOS was not found or could not be accessed. Check your emu settings."; + + len = FileLength(f); + if (len != 0x4000) + { + CloseFile(f); + return "DS ARM7 BIOS is not a valid BIOS dump."; + } + + CloseFile(f); + + return ""; +} + +QString EmuInstance::verifyDSiBIOS() +{ + FileHandle* f; + long len; + + // TODO: check the first 32 bytes + + f = Platform::OpenLocalFile(globalCfg.GetString("DSi.BIOS9Path"), FileMode::Read); + if (!f) return "DSi ARM9 BIOS was not found or could not be accessed. Check your emu settings."; + + len = FileLength(f); + if (len != 0x10000) + { + CloseFile(f); + return "DSi ARM9 BIOS is not a valid BIOS dump."; + } + + CloseFile(f); + + f = Platform::OpenLocalFile(globalCfg.GetString("DSi.BIOS7Path"), FileMode::Read); + if (!f) return "DSi ARM7 BIOS was not found or could not be accessed. Check your emu settings."; + + len = FileLength(f); + if (len != 0x10000) + { + CloseFile(f); + return "DSi ARM7 BIOS is not a valid BIOS dump."; + } + + CloseFile(f); + + return ""; +} + +QString EmuInstance::verifyDSFirmware() +{ + FileHandle* f; + long len; + + std::string fwpath = globalCfg.GetString("DS.FirmwarePath"); + + f = Platform::OpenLocalFile(fwpath, FileMode::Read); + if (!f) return "DS firmware was not found or could not be accessed. Check your emu settings."; + + if (!Platform::CheckFileWritable(fwpath)) + return "DS firmware is unable to be written to.\nPlease check file/folder write permissions."; + + len = FileLength(f); + if (len == 0x20000) + { + // 128KB firmware, not bootable + CloseFile(f); + // TODO report it somehow? detect in core? + return ""; + } + else if (len != 0x40000 && len != 0x80000) + { + CloseFile(f); + return "DS firmware is not a valid firmware dump."; + } + + CloseFile(f); + + return ""; +} + +QString EmuInstance::verifyDSiFirmware() +{ + FileHandle* f; + long len; + + std::string fwpath = globalCfg.GetString("DSi.FirmwarePath"); + + f = Platform::OpenLocalFile(fwpath, FileMode::Read); + if (!f) return "DSi firmware was not found or could not be accessed. Check your emu settings."; + + if (!Platform::CheckFileWritable(fwpath)) + return "DSi firmware is unable to be written to.\nPlease check file/folder write permissions."; + + len = FileLength(f); + if (len != 0x20000) + { + // not 128KB + // TODO: check whether those work + CloseFile(f); + return "DSi firmware is not a valid firmware dump."; + } + + CloseFile(f); + + return ""; +} + +QString EmuInstance::verifyDSiNAND() +{ + FileHandle* f; + long len; + + std::string nandpath = globalCfg.GetString("DSi.NANDPath"); + + f = Platform::OpenLocalFile(nandpath, FileMode::ReadWriteExisting); + if (!f) return "DSi NAND was not found or could not be accessed. Check your emu settings."; + + if (!Platform::CheckFileWritable(nandpath)) + return "DSi NAND is unable to be written to.\nPlease check file/folder write permissions."; + + // TODO: some basic checks + // check that it has the nocash footer, and all + + CloseFile(f); + + return ""; +} + +QString EmuInstance::verifySetup() +{ + QString res; + + bool extbios = globalCfg.GetBool("Emu.ExternalBIOSEnable"); + int console = globalCfg.GetInt("Emu.ConsoleType"); + + if (extbios) + { + res = verifyDSBIOS(); + if (!res.isEmpty()) return res; + } + + if (console == 1) + { + res = verifyDSiBIOS(); + if (!res.isEmpty()) return res; + + if (extbios) + { + res = verifyDSiFirmware(); + if (!res.isEmpty()) return res; + } + + res = verifyDSiNAND(); + if (!res.isEmpty()) return res; + } + else + { + if (extbios) + { + res = verifyDSFirmware(); + if (!res.isEmpty()) return res; + } + } + + return ""; +} + + +std::string EmuInstance::getEffectiveFirmwareSavePath() +{ + if (!globalCfg.GetBool("Emu.ExternalBIOSEnable")) + { + return GetLocalFilePath(kWifiSettingsPath); + } + if (consoleType == 1) + { + return globalCfg.GetString("DSi.FirmwarePath"); + } + else + { + return globalCfg.GetString("DS.FirmwarePath"); + } +} + +// Initializes the firmware save manager with the selected firmware image's path +// OR the path to the wi-fi settings. +void EmuInstance::initFirmwareSaveManager() noexcept +{ + firmwareSave = std::make_unique(getEffectiveFirmwareSavePath() + instanceFileSuffix()); +} + +std::string EmuInstance::getSavestateName(int slot) +{ + std::string ext = ".ml"; + ext += (char)('0'+slot); + return getAssetPath(false, globalCfg.GetString("SavestatePath"), ext); +} + +bool EmuInstance::savestateExists(int slot) +{ + std::string ssfile = getSavestateName(slot); + return Platform::FileExists(ssfile); +} + +bool EmuInstance::loadState(const std::string& filename) +{ + Platform::FileHandle* file = Platform::OpenFile(filename, Platform::FileMode::Read); + if (file == nullptr) + { // If we couldn't open the state file... + Platform::Log(Platform::LogLevel::Error, "Failed to open state file \"%s\"\n", filename.c_str()); + return false; + } + + std::unique_ptr backup = std::make_unique(Savestate::DEFAULT_SIZE); + if (backup->Error) + { // If we couldn't allocate memory for the backup... + Platform::Log(Platform::LogLevel::Error, "Failed to allocate memory for state backup\n"); + Platform::CloseFile(file); + return false; + } + + if (!nds->DoSavestate(backup.get()) || backup->Error) + { // Back up the emulator's state. If that failed... + Platform::Log(Platform::LogLevel::Error, "Failed to back up state, aborting load (from \"%s\")\n", filename.c_str()); + Platform::CloseFile(file); + return false; + } + // We'll store the backup once we're sure that the state was loaded. + // Now that we know the file and backup are both good, let's load the new state. + + // Get the size of the file that we opened + size_t size = Platform::FileLength(file); + + // Allocate exactly as much memory as we need for the savestate + std::vector buffer(size); + if (Platform::FileRead(buffer.data(), size, 1, file) == 0) + { // Read the state file into the buffer. If that failed... + Platform::Log(Platform::LogLevel::Error, "Failed to read %u-byte state file \"%s\"\n", size, filename.c_str()); + Platform::CloseFile(file); + return false; + } + Platform::CloseFile(file); // done with the file now + + // Get ready to load the state from the buffer into the emulator + std::unique_ptr state = std::make_unique(buffer.data(), size, false); + + if (!nds->DoSavestate(state.get()) || state->Error) + { // If we couldn't load the savestate from the buffer... + Platform::Log(Platform::LogLevel::Error, "Failed to load state file \"%s\" into emulator\n", filename.c_str()); + return false; + } + + // The backup was made and the state was loaded, so we can store the backup now. + backupState = std::move(backup); // This will clean up any existing backup + assert(backup == nullptr); + + if (globalCfg.GetBool("Savestate.RelocSRAM") && ndsSave) + { + previousSaveFile = ndsSave->GetPath(); + + std::string savefile = filename.substr(lastSep(filename)+1); + savefile = getAssetPath(false, globalCfg.GetString("SaveFilePath"), ".sav", savefile); + savefile += instanceFileSuffix(); + ndsSave->SetPath(savefile, true); + } + + savestateLoaded = true; + + return true; +} + +bool EmuInstance::saveState(const std::string& filename) +{ + Platform::FileHandle* file = Platform::OpenFile(filename, Platform::FileMode::Write); + + if (file == nullptr) + { // If the file couldn't be opened... + return false; + } + + Savestate state; + if (state.Error) + { // If there was an error creating the state (and allocating its memory)... + Platform::CloseFile(file); + return false; + } + + // Write the savestate to the in-memory buffer + nds->DoSavestate(&state); + + if (state.Error) + { + Platform::CloseFile(file); + return false; + } + + if (Platform::FileWrite(state.Buffer(), state.Length(), 1, file) == 0) + { // Write the Savestate buffer to the file. If that fails... + Platform::Log(Platform::Error, + "Failed to write %d-byte savestate to %s\n", + state.Length(), + filename.c_str() + ); + Platform::CloseFile(file); + return false; + } + + Platform::CloseFile(file); + + if (globalCfg.GetBool("Savestate.RelocSRAM") && ndsSave) + { + std::string savefile = filename.substr(lastSep(filename)+1); + savefile = getAssetPath(false, globalCfg.GetString("SaveFilePath"), ".sav", savefile); + savefile += instanceFileSuffix(); + ndsSave->SetPath(savefile, false); + } + + return true; +} + +void EmuInstance::undoStateLoad() +{ + if (!savestateLoaded || !backupState) return; + + // Rewind the backup state and put it in load mode + backupState->Rewind(false); + // pray that this works + // what do we do if it doesn't??? + // but it should work. + nds->DoSavestate(backupState.get()); + + if (ndsSave && (!previousSaveFile.empty())) + { + ndsSave->SetPath(previousSaveFile, true); + } +} + + +void EmuInstance::unloadCheats() +{ + cheatFile = nullptr; // cleaned up by unique_ptr + nds->AREngine.Cheats.clear(); +} + +void EmuInstance::loadCheats() +{ + unloadCheats(); + + std::string filename = getAssetPath(false, globalCfg.GetString("CheatFilePath"), ".mch"); + + // TODO: check for error (malformed cheat file, ...) + cheatFile = std::make_unique(filename); + + if (cheatsOn) + { + nds->AREngine.Cheats = cheatFile->GetCodes(); + } + else + { + nds->AREngine.Cheats.clear(); + } +} + +std::unique_ptr EmuInstance::loadARM9BIOS() noexcept +{ + if (!globalCfg.GetBool("Emu.ExternalBIOSEnable")) + { + return globalCfg.GetInt("Emu.ConsoleType") == 0 ? std::make_unique(bios_arm9_bin) : nullptr; + } + + string path = globalCfg.GetString("DS.BIOS9Path"); + + if (FileHandle* f = OpenLocalFile(path, Read)) + { + std::unique_ptr bios = std::make_unique(); + FileRewind(f); + FileRead(bios->data(), bios->size(), 1, f); + CloseFile(f); + Log(Info, "ARM9 BIOS loaded from %s\n", path.c_str()); + return bios; + } + + Log(Warn, "ARM9 BIOS not found\n"); + return nullptr; +} + +std::unique_ptr EmuInstance::loadARM7BIOS() noexcept +{ + if (!globalCfg.GetBool("Emu.ExternalBIOSEnable")) + { + return globalCfg.GetInt("Emu.ConsoleType") == 0 ? std::make_unique(bios_arm7_bin) : nullptr; + } + + string path = globalCfg.GetString("DS.BIOS7Path"); + + if (FileHandle* f = OpenLocalFile(path, Read)) + { + std::unique_ptr bios = std::make_unique(); + FileRead(bios->data(), bios->size(), 1, f); + CloseFile(f); + Log(Info, "ARM7 BIOS loaded from %s\n", path.c_str()); + return bios; + } + + Log(Warn, "ARM7 BIOS not found\n"); + return nullptr; +} + +std::unique_ptr EmuInstance::loadDSiARM9BIOS() noexcept +{ + string path = globalCfg.GetString("DSi.BIOS9Path"); + + if (FileHandle* f = OpenLocalFile(path, Read)) + { + std::unique_ptr bios = std::make_unique(); + FileRead(bios->data(), bios->size(), 1, f); + CloseFile(f); + + if (!globalCfg.GetBool("DSi.FullBIOSBoot")) + { + // herp + *(u32*)bios->data() = 0xEAFFFFFE; // overwrites the reset vector + + // TODO!!!! + // hax the upper 32K out of the goddamn DSi + // done that :) -pcy + } + Log(Info, "ARM9i BIOS loaded from %s\n", path.c_str()); + return bios; + } + + Log(Warn, "ARM9i BIOS not found\n"); + return nullptr; +} + +std::unique_ptr EmuInstance::loadDSiARM7BIOS() noexcept +{ + string path = globalCfg.GetString("DSi.BIOS7Path"); + + if (FileHandle* f = OpenLocalFile(path, Read)) + { + std::unique_ptr bios = std::make_unique(); + FileRead(bios->data(), bios->size(), 1, f); + CloseFile(f); + + if (!globalCfg.GetBool("DSi.FullBIOSBoot")) + { + // herp + *(u32*)bios->data() = 0xEAFFFFFE; // overwrites the reset vector + + // TODO!!!! + // hax the upper 32K out of the goddamn DSi + // done that :) -pcy + } + Log(Info, "ARM7i BIOS loaded from %s\n", path.c_str()); + return bios; + } + + Log(Warn, "ARM7i BIOS not found\n"); + return nullptr; +} + +Firmware EmuInstance::generateFirmware(int type) noexcept +{ + // Construct the default firmware... + string settingspath; + Firmware firmware = Firmware(type); + assert(firmware.Buffer() != nullptr); + + // If using generated firmware, we keep the wi-fi settings on the host disk separately. + // Wi-fi access point data includes Nintendo WFC settings, + // and if we didn't keep them then the player would have to reset them in each session. + // We don't need to save the whole firmware, just the part that may actually change. + if (FileHandle* f = OpenLocalFile(kWifiSettingsPath, Read)) + {// If we have Wi-fi settings to load... + constexpr unsigned TOTAL_WFC_SETTINGS_SIZE = 3 * (sizeof(Firmware::WifiAccessPoint) + sizeof(Firmware::ExtendedWifiAccessPoint)); + + if (!FileRead(firmware.GetExtendedAccessPointPosition(), TOTAL_WFC_SETTINGS_SIZE, 1, f)) + { // If we couldn't read the Wi-fi settings from this file... + Log(Warn, "Failed to read Wi-fi settings from \"%s\"; using defaults instead\n", kWifiSettingsPath.c_str()); + + // The access point and extended access point segments might + // be in different locations depending on the firmware revision, + // but our generated firmware always keeps them next to each other. + // (Extended access points first, then regular ones.) + firmware.GetAccessPoints() = { + Firmware::WifiAccessPoint(type), + Firmware::WifiAccessPoint(), + Firmware::WifiAccessPoint(), + }; + + firmware.GetExtendedAccessPoints() = { + Firmware::ExtendedWifiAccessPoint(), + Firmware::ExtendedWifiAccessPoint(), + Firmware::ExtendedWifiAccessPoint(), + }; + firmware.UpdateChecksums(); + CloseFile(f); + } + } + + customizeFirmware(firmware, true); + + // If we don't have Wi-fi settings to load, + // then the defaults will have already been populated by the constructor. + return firmware; +} + +std::optional EmuInstance::loadFirmware(int type) noexcept +{ + if (!globalCfg.GetBool("Emu.ExternalBIOSEnable")) + { // If we're using built-in firmware... + if (type == 1) + { + Log(Error, "DSi firmware: cannot use built-in firmware in DSi mode!\n"); + return std::nullopt; + } + + return generateFirmware(type); + } + //const string& firmwarepath = type == 1 ? Config::DSiFirmwarePath : Config::FirmwarePath; + string firmwarepath; + if (type == 1) + firmwarepath = globalCfg.GetString("DSi.FirmwarePath"); + else + firmwarepath = globalCfg.GetString("DS.FirmwarePath"); + + Log(Debug, "SPI firmware: loading from file %s\n", firmwarepath.c_str()); + + FileHandle* file = OpenLocalFile(firmwarepath, Read); + + if (!file) + { + Log(Error, "SPI firmware: couldn't open firmware file!\n"); + return std::nullopt; + } + Firmware firmware(file); + CloseFile(file); + + if (!firmware.Buffer()) + { + Log(Error, "SPI firmware: couldn't read firmware file!\n"); + return std::nullopt; + } + + customizeFirmware(firmware, localCfg.GetBool("Firmware.OverrideSettings")); + + return firmware; +} + + +std::optional EmuInstance::loadNAND(const std::array& arm7ibios) noexcept +{ + string path = globalCfg.GetString("DSi.NANDPath"); + + FileHandle* nandfile = OpenLocalFile(path, ReadWriteExisting); + if (!nandfile) + return std::nullopt; + + DSi_NAND::NANDImage nandImage(nandfile, &arm7ibios[0x8308]); + if (!nandImage) + { + Log(Error, "Failed to parse DSi NAND\n"); + return std::nullopt; + // the NANDImage takes ownership of the FileHandle, no need to clean it up here + } + + // scoped so that mount isn't alive when we move the NAND image to DSi::NANDImage + { + auto mount = DSi_NAND::NANDMount(nandImage); + if (!mount) + { + Log(Error, "Failed to mount DSi NAND\n"); + return std::nullopt; + } + + DSi_NAND::DSiFirmwareSystemSettings settings {}; + if (!mount.ReadUserData(settings)) + { + Log(Error, "Failed to read DSi NAND user data\n"); + return std::nullopt; + } + + // override user settings, if needed + if (localCfg.GetBool("Firmware.OverrideSettings")) + { + auto firmcfg = localCfg.GetTable("Firmware"); + + // we store relevant strings as UTF-8, so we need to convert them to UTF-16 + auto converter = wstring_convert, char16_t>{}; + + // setting up username + std::u16string username = converter.from_bytes(firmcfg.GetString("Username")); + size_t usernameLength = std::min(username.length(), (size_t) 10); + memset(&settings.Nickname, 0, sizeof(settings.Nickname)); + memcpy(&settings.Nickname, username.data(), usernameLength * sizeof(char16_t)); + + // setting language + settings.Language = static_cast(firmcfg.GetInt("Language")); + + // setting up color + settings.FavoriteColor = firmcfg.GetInt("FavouriteColour"); + + // setting up birthday + settings.BirthdayMonth = firmcfg.GetInt("BirthdayMonth"); + settings.BirthdayDay = firmcfg.GetInt("BirthdayDay"); + + // setup message + std::u16string message = converter.from_bytes(firmcfg.GetString("Message")); + size_t messageLength = std::min(message.length(), (size_t) 26); + memset(&settings.Message, 0, sizeof(settings.Message)); + memcpy(&settings.Message, message.data(), messageLength * sizeof(char16_t)); + + // TODO: make other items configurable? + } + + // fix touchscreen coords + settings.TouchCalibrationADC1 = {0, 0}; + settings.TouchCalibrationPixel1 = {0, 0}; + settings.TouchCalibrationADC2 = {255 << 4, 191 << 4}; + settings.TouchCalibrationPixel2 = {255, 191}; + + settings.UpdateHash(); + + if (!mount.ApplyUserData(settings)) + { + Log(LogLevel::Error, "Failed to write patched DSi NAND user data\n"); + return std::nullopt; + } + } + + return nandImage; +} + +constexpr u64 MB(u64 i) +{ + return i * 1024 * 1024; +} + +constexpr u64 imgsizes[] = {0, MB(256), MB(512), MB(1024), MB(2048), MB(4096)}; + +std::optional EmuInstance::getSDCardArgs(const string& key) noexcept +{ + // key = DSi.SD or DLDI + Config::Table sdopt = globalCfg.GetTable(key); + + if (!sdopt.GetBool("Enable")) + return std::nullopt; + + return FATStorageArgs { + sdopt.GetString("ImagePath"), + imgsizes[sdopt.GetInt("ImageSize")], + sdopt.GetBool("ReadOnly"), + sdopt.GetBool("FolderSync") ? std::make_optional(sdopt.GetString("FolderPath")) : std::nullopt + }; +} + +std::optional EmuInstance::loadSDCard(const string& key) noexcept +{ + auto args = getSDCardArgs(key); + if (!args.has_value()) + return std::nullopt; + + return FATStorage(args.value()); +} + +void EmuInstance::enableCheats(bool enable) +{ + cheatsOn = enable; + if (cheatsOn && cheatFile) + nds->AREngine.Cheats = cheatFile->GetCodes(); + else + nds->AREngine.Cheats.clear(); +} + +ARCodeFile* EmuInstance::getCheatFile() +{ + return cheatFile.get(); +} + +void EmuInstance::setBatteryLevels() +{ + if (consoleType == 1) + { + auto dsi = static_cast(nds); + dsi->I2C.GetBPTWL()->SetBatteryLevel(localCfg.GetInt("DSi.Battery.Level")); + dsi->I2C.GetBPTWL()->SetBatteryCharging(localCfg.GetBool("DSi.Battery.Charging")); + } + else + { + nds->SPI.GetPowerMan()->SetBatteryLevelOkay(localCfg.GetBool("DS.Battery.LevelOkay")); + } +} + +void EmuInstance::loadRTCData() +{ + auto file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Read); + if (file) + { + RTC::StateData state; + Platform::FileRead(&state, sizeof(state), 1, file); + Platform::CloseFile(file); + nds->RTC.SetState(state); + } +} + +void EmuInstance::saveRTCData() +{ + auto file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write); + if (file) + { + RTC::StateData state; + nds->RTC.GetState(state); + Platform::FileWrite(&state, sizeof(state), 1, file); + Platform::CloseFile(file); + } +} + +void EmuInstance::setDateTime() +{ + QDateTime hosttime = QDateTime::currentDateTime(); + QDateTime time = hosttime.addSecs(localCfg.GetInt64("RTC.Offset")); + + nds->RTC.SetDateTime(time.date().year(), time.date().month(), time.date().day(), + time.time().hour(), time.time().minute(), time.time().second()); +} + +bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGBAArgs&& _gbaargs) noexcept +{ + // update the console type + consoleType = globalCfg.GetInt("Emu.ConsoleType"); + + // Let's get the cart we want to use; + // if we wnat to keep the cart, we'll eject it from the existing console first. + std::unique_ptr nextndscart; + if (std::holds_alternative(_ndsargs)) + { // If we want to keep the existing cart (if any)... + nextndscart = nds ? nds->EjectCart() : nullptr; + _ndsargs = {}; + } + else if (const auto ptr = std::get_if>(&_ndsargs)) + { + nextndscart = std::move(*ptr); + _ndsargs = {}; + } + + if (auto* cartsd = dynamic_cast(nextndscart.get())) + { + // LoadDLDISDCard will return nullopt if the SD card is disabled; + // SetSDCard will accept nullopt, which means no SD card + cartsd->SetSDCard(getSDCardArgs("DLDI")); + } + + std::unique_ptr nextgbacart; + if (std::holds_alternative(_gbaargs)) + { + nextgbacart = nds ? nds->EjectGBACart() : nullptr; + } + else if (const auto ptr = std::get_if>(&_gbaargs)) + { + nextgbacart = std::move(*ptr); + _gbaargs = {}; + } + + + auto arm9bios = loadARM9BIOS(); + if (!arm9bios) + return false; + + auto arm7bios = loadARM7BIOS(); + if (!arm7bios) + return false; + + auto firmware = loadFirmware(consoleType); + if (!firmware) + return false; + +#ifdef JIT_ENABLED + Config::Table jitopt = globalCfg.GetTable("JIT"); + JITArgs _jitargs { + static_cast(jitopt.GetInt("MaxBlockSize")), + jitopt.GetBool("LiteralOptimisations"), + jitopt.GetBool("BranchOptimisations"), + jitopt.GetBool("FastMemory"), + }; + auto jitargs = jitopt.GetBool("Enable") ? std::make_optional(_jitargs) : std::nullopt; +#else + std::optional jitargs = std::nullopt; +#endif + +#ifdef GDBSTUB_ENABLED + Config::Table gdbopt = localCfg.GetTable("Gdb"); + GDBArgs _gdbargs { + static_cast(gdbopt.GetInt("ARM7.Port")), + static_cast(gdbopt.GetInt("ARM9.Port")), + gdbopt.GetBool("ARM7.BreakOnStartup"), + gdbopt.GetBool("ARM9.BreakOnStartup"), + }; + auto gdbargs = gdbopt.GetBool("Enabled") ? std::make_optional(_gdbargs) : std::nullopt; +#else + optional gdbargs = std::nullopt; +#endif + + NDSArgs ndsargs { + std::move(nextndscart), + std::move(nextgbacart), + std::move(arm9bios), + std::move(arm7bios), + std::move(*firmware), + jitargs, + static_cast(globalCfg.GetInt("Audio.BitDepth")), + static_cast(globalCfg.GetInt("Audio.Interpolation")), + gdbargs, + }; + NDSArgs* args = &ndsargs; + + std::optional dsiargs = std::nullopt; + if (consoleType == 1) + { + ndsargs.GBAROM = nullptr; + + auto arm7ibios = loadDSiARM7BIOS(); + if (!arm7ibios) + return false; + + auto arm9ibios = loadDSiARM9BIOS(); + if (!arm9ibios) + return false; + + auto nand = loadNAND(*arm7ibios); + if (!nand) + return false; + + auto sdcard = loadSDCard("DSi.SD"); + + DSiArgs _dsiargs { + std::move(ndsargs), + std::move(arm9ibios), + std::move(arm7ibios), + std::move(*nand), + std::move(sdcard), + globalCfg.GetBool("DSi.FullBIOSBoot"), + }; + + dsiargs = std::move(_dsiargs); + args = &(*dsiargs); + } + + renderLock.lock(); + if ((!nds) || (consoleType != nds->ConsoleType)) + { + NDS::Current = nullptr; + if (nds) + { + saveRTCData(); + delete nds; + } + + if (consoleType == 1) + nds = new DSi(std::move(dsiargs.value()), this); + else + nds = new NDS(std::move(ndsargs), this); + + NDS::Current = nds; + nds->Reset(); + loadRTCData(); + //emuThread->updateVideoRenderer(); // not actually needed? + } + else + { + nds->SetARM7BIOS(*args->ARM7BIOS); + nds->SetARM9BIOS(*args->ARM9BIOS); + nds->SetFirmware(std::move(args->Firmware)); + nds->SetNDSCart(std::move(args->NDSROM)); + nds->SetGBACart(std::move(args->GBAROM)); + nds->SetJITArgs(args->JIT); + // TODO GDB stub shit + nds->SPU.SetInterpolation(args->Interpolation); + nds->SPU.SetDegrade10Bit(args->BitDepth); + + if (consoleType == 1) + { + DSi* dsi = (DSi*)nds; + DSiArgs& _dsiargs = *dsiargs; + + dsi->SetFullBIOSBoot(_dsiargs.FullBIOSBoot); + dsi->ARM7iBIOS = *_dsiargs.ARM7iBIOS; + dsi->ARM9iBIOS = *_dsiargs.ARM9iBIOS; + dsi->SetNAND(std::move(_dsiargs.NANDImage)); + dsi->SetSDCard(std::move(_dsiargs.DSiSDCard)); + // We're moving the optional, not the card + // (inserting std::nullopt here is okay, it means no card) + + dsi->EjectGBACart(); + } + } + nds->SetDebugPrint(globalCfg.GetBool("DS.DebugPrintEnabled")); + renderLock.unlock(); + + return true; +} + +void EmuInstance::reset() +{ + updateConsole(Keep {}, Keep {}); + + if (consoleType == 1) ejectGBACart(); + + nds->Reset(); + setBatteryLevels(); + setDateTime(); + + if ((cartType != -1) && ndsSave) + { + std::string oldsave = ndsSave->GetPath(); + std::string newsave = getAssetPath(false, globalCfg.GetString("SaveFilePath"), ".sav"); + newsave += instanceFileSuffix(); + if (oldsave != newsave) + ndsSave->SetPath(newsave, false); + } + + if ((gbaCartType != -1) && gbaSave) + { + std::string oldsave = gbaSave->GetPath(); + std::string newsave = getAssetPath(true, globalCfg.GetString("SaveFilePath"), ".sav"); + newsave += instanceFileSuffix(); + if (oldsave != newsave) + gbaSave->SetPath(newsave, false); + } + + initFirmwareSaveManager(); + if (firmwareSave) + { + std::string oldsave = firmwareSave->GetPath(); + string newsave; + if (globalCfg.GetBool("Emu.ExternalBIOSEnable")) + { + if (consoleType == 1) + newsave = globalCfg.GetString("DSi.FirmwarePath") + instanceFileSuffix(); + else + newsave = globalCfg.GetString("DS.FirmwarePath") + instanceFileSuffix(); + } + else + { + newsave = GetLocalFilePath(kWifiSettingsPath + instanceFileSuffix()); + } + + if (oldsave != newsave) + { // If the player toggled the ConsoleType or ExternalBIOSEnable... + firmwareSave->SetPath(newsave, true); + } + } + + if (!baseROMName.empty()) + { + if (globalCfg.GetBool("Emu.DirectBoot") || nds->NeedsDirectBoot()) + { + nds->SetupDirectBoot(baseROMName); + } + } + + nds->Start(); +} + + +bool EmuInstance::bootToMenu() +{ + // Keep whatever cart is in the console, if any. + if (!updateConsole(Keep {}, Keep {})) + // Try to update the console, but keep the existing cart. If that fails... + return false; + + // BIOS and firmware files are loaded, patched, and installed in UpdateConsole + if (nds->NeedsDirectBoot()) + return false; + + initFirmwareSaveManager(); + nds->Reset(); + setBatteryLevels(); + setDateTime(); + return true; +} + +u32 EmuInstance::decompressROM(const u8* inContent, const u32 inSize, unique_ptr& outContent) +{ + u64 realSize = ZSTD_getFrameContentSize(inContent, inSize); + const u32 maxSize = 0x40000000; + + if (realSize == ZSTD_CONTENTSIZE_ERROR || (realSize > maxSize && realSize != ZSTD_CONTENTSIZE_UNKNOWN)) + { + return 0; + } + + if (realSize != ZSTD_CONTENTSIZE_UNKNOWN) + { + auto newOutContent = make_unique(realSize); + u64 decompressed = ZSTD_decompress(newOutContent.get(), realSize, inContent, inSize); + + if (ZSTD_isError(decompressed)) + { + outContent = nullptr; + return 0; + } + + outContent = std::move(newOutContent); + return realSize; + } + else + { + ZSTD_DStream* dStream = ZSTD_createDStream(); + ZSTD_initDStream(dStream); + + ZSTD_inBuffer inBuf = { + .src = inContent, + .size = inSize, + .pos = 0 + }; + + const u32 startSize = 1024 * 1024 * 16; + u8* partialOutContent = (u8*) malloc(startSize); + + ZSTD_outBuffer outBuf = { + .dst = partialOutContent, + .size = startSize, + .pos = 0 + }; + + size_t result; + + do + { + result = ZSTD_decompressStream(dStream, &outBuf, &inBuf); + + if (ZSTD_isError(result)) + { + ZSTD_freeDStream(dStream); + free(outBuf.dst); + return 0; + } + + // if result == 0 and not inBuf.pos < inBuf.size, go again to let zstd flush everything. + if (result == 0) + continue; + + if (outBuf.pos == outBuf.size) + { + outBuf.size *= 2; + + if (outBuf.size > maxSize) + { + ZSTD_freeDStream(dStream); + free(outBuf.dst); + return 0; + } + + outBuf.dst = realloc(outBuf.dst, outBuf.size); + } + } while (inBuf.pos < inBuf.size); + + outContent = make_unique(outBuf.pos); + memcpy(outContent.get(), outBuf.dst, outBuf.pos); + + ZSTD_freeDStream(dStream); + free(outBuf.dst); + + return outBuf.size; + } +} + +void EmuInstance::clearBackupState() +{ + if (backupState != nullptr) + { + backupState = nullptr; + } +} + +pair, string> EmuInstance::generateDefaultFirmware() +{ + // Construct the default firmware... + string settingspath; + std::unique_ptr firmware = std::make_unique(consoleType); + assert(firmware->Buffer() != nullptr); + + // Try to open the instanced Wi-fi settings, falling back to the regular Wi-fi settings if they don't exist. + // We don't need to save the whole firmware, just the part that may actually change. + std::string wfcsettingspath = kWifiSettingsPath; + settingspath = wfcsettingspath + instanceFileSuffix(); + FileHandle* f = Platform::OpenLocalFile(settingspath, FileMode::Read); + if (!f) + { + settingspath = wfcsettingspath; + f = Platform::OpenLocalFile(settingspath, FileMode::Read); + } + + // If using generated firmware, we keep the wi-fi settings on the host disk separately. + // Wi-fi access point data includes Nintendo WFC settings, + // and if we didn't keep them then the player would have to reset them in each session. + if (f) + { // If we have Wi-fi settings to load... + constexpr unsigned TOTAL_WFC_SETTINGS_SIZE = 3 * (sizeof(Firmware::WifiAccessPoint) + sizeof(Firmware::ExtendedWifiAccessPoint)); + + // The access point and extended access point segments might + // be in different locations depending on the firmware revision, + // but our generated firmware always keeps them next to each other. + // (Extended access points first, then regular ones.) + + if (!FileRead(firmware->GetExtendedAccessPointPosition(), TOTAL_WFC_SETTINGS_SIZE, 1, f)) + { // If we couldn't read the Wi-fi settings from this file... + Platform::Log(Platform::LogLevel::Warn, "Failed to read Wi-fi settings from \"%s\"; using defaults instead\n", wfcsettingspath.c_str()); + + firmware->GetAccessPoints() = { + Firmware::WifiAccessPoint(consoleType), + Firmware::WifiAccessPoint(), + Firmware::WifiAccessPoint(), + }; + + firmware->GetExtendedAccessPoints() = { + Firmware::ExtendedWifiAccessPoint(), + Firmware::ExtendedWifiAccessPoint(), + Firmware::ExtendedWifiAccessPoint(), + }; + } + + firmware->UpdateChecksums(); + + CloseFile(f); + } + + // If we don't have Wi-fi settings to load, + // then the defaults will have already been populated by the constructor. + return std::make_pair(std::move(firmware), std::move(wfcsettingspath)); +} + +bool EmuInstance::parseMacAddress(void* data) +{ + const std::string mac_in = localCfg.GetString("Firmware.MAC"); + u8* mac_out = (u8*)data; + + int o = 0; + u8 tmp = 0; + for (int i = 0; i < 18; i++) + { + char c = mac_in[i]; + if (c == '\0') break; + + int n; + if (c >= '0' && c <= '9') n = c - '0'; + else if (c >= 'a' && c <= 'f') n = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') n = c - 'A' + 10; + else continue; + + if (!(o & 1)) + tmp = n; + else + mac_out[o >> 1] = n | (tmp << 4); + + o++; + if (o >= 12) return true; + } + + return false; +} + +void EmuInstance::customizeFirmware(Firmware& firmware, bool overridesettings) noexcept +{ + if (overridesettings) + { + auto ¤tData = firmware.GetEffectiveUserData(); + + auto firmcfg = localCfg.GetTable("Firmware"); + + // setting up username + std::string orig_username = firmcfg.GetString("Username"); + if (!orig_username.empty()) + { // If the frontend defines a username, take it. If not, leave the existing one. + std::u16string username = std::wstring_convert, char16_t>{}.from_bytes( + orig_username); + size_t usernameLength = std::min(username.length(), (size_t) 10); + currentData.NameLength = usernameLength; + memcpy(currentData.Nickname, username.data(), usernameLength * sizeof(char16_t)); + } + + auto language = static_cast(firmcfg.GetInt("Language")); + if (language != Firmware::Language::Reserved) + { // If the frontend specifies a language (rather than using the existing value)... + currentData.Settings &= ~Firmware::Language::Reserved; // ..clear the existing language... + currentData.Settings |= language; // ...and set the new one. + } + + // setting up color + u8 favoritecolor = firmcfg.GetInt("FavouriteColour"); + if (favoritecolor != 0xFF) + { + currentData.FavoriteColor = favoritecolor; + } + + u8 birthmonth = firmcfg.GetInt("BirthdayMonth"); + if (birthmonth != 0) + { // If the frontend specifies a birth month (rather than using the existing value)... + currentData.BirthdayMonth = birthmonth; + } + + u8 birthday = firmcfg.GetInt("BirthdayDay"); + if (birthday != 0) + { // If the frontend specifies a birthday (rather than using the existing value)... + currentData.BirthdayDay = birthday; + } + + // setup message + std::string orig_message = firmcfg.GetString("Message"); + if (!orig_message.empty()) + { + std::u16string message = std::wstring_convert, char16_t>{}.from_bytes( + orig_message); + size_t messageLength = std::min(message.length(), (size_t) 26); + currentData.MessageLength = messageLength; + memcpy(currentData.Message, message.data(), messageLength * sizeof(char16_t)); + } + } + + MacAddress mac; + bool rep = false; + auto& header = firmware.GetHeader(); + + memcpy(&mac, header.MacAddr.data(), sizeof(MacAddress)); + + if (overridesettings) + { + MacAddress configuredMac; + rep = parseMacAddress(&configuredMac); + rep &= (configuredMac != MacAddress()); + + if (rep) + { + mac = configuredMac; + } + } + + if (instanceID > 0) + { + rep = true; + mac[3] += instanceID; + mac[4] += instanceID*0x44; + mac[5] += instanceID*0x10; + } + + if (rep) + { + mac[0] &= 0xFC; // ensure the MAC isn't a broadcast MAC + header.MacAddr = mac; + header.UpdateChecksum(); + } + + firmware.UpdateChecksums(); +} + +// Loads ROM data without parsing it. Works for GBA and NDS ROMs. +bool EmuInstance::loadROMData(const QStringList& filepath, std::unique_ptr& filedata, u32& filelen, string& basepath, string& romname) noexcept +{ + if (filepath.empty()) return false; + + if (int num = filepath.count(); num == 1) + { + // regular file + + std::string filename = filepath.at(0).toStdString(); + Platform::FileHandle* f = Platform::OpenFile(filename, FileMode::Read); + if (!f) return false; + + long len = Platform::FileLength(f); + if (len > 0x40000000) + { + Platform::CloseFile(f); + return false; + } + + Platform::FileRewind(f); + filedata = make_unique(len); + size_t nread = Platform::FileRead(filedata.get(), (size_t)len, 1, f); + Platform::CloseFile(f); + if (nread != 1) + { + filedata = nullptr; + return false; + } + + filelen = (u32)len; + + if (filename.length() > 4 && filename.substr(filename.length() - 4) == ".zst") + { + filelen = decompressROM(filedata.get(), len, filedata); + + if (filelen > 0) + { + filename = filename.substr(0, filename.length() - 4); + } + else + { + filedata = nullptr; + filelen = 0; + basepath = ""; + romname = ""; + return false; + } + } + + int pos = lastSep(filename); + if(pos != -1) + basepath = filename.substr(0, pos); + + romname = filename.substr(pos+1); + return true; + } +#ifdef ARCHIVE_SUPPORT_ENABLED + else if (num == 2) + { + // file inside archive + + s32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), filedata, &filelen); + if (lenread < 0) return false; + if (!filedata) return false; + if (lenread != filelen) + { + filedata = nullptr; + return false; + } + + std::string std_archivepath = filepath.at(0).toStdString(); + basepath = std_archivepath.substr(0, lastSep(std_archivepath)); + + std::string std_romname = filepath.at(1).toStdString(); + romname = std_romname.substr(lastSep(std_romname)+1); + return true; + } +#endif + else + return false; +} + +QString EmuInstance::getSavErrorString(std::string& filepath, bool gba) +{ + std::string console = gba ? "GBA" : "DS"; + std::string err1 = "Unable to write to "; + std::string err2 = " save.\nPlease check file/folder write permissions.\n\nAttempted to Access:\n"; + + err1 += console + err2 + filepath; + + return QString::fromStdString(err1); +} + +bool EmuInstance::loadROM(QStringList filepath, bool reset) +{ + unique_ptr filedata = nullptr; + u32 filelen; + std::string basepath; + std::string romname; + + if (!loadROMData(filepath, filedata, filelen, basepath, romname)) + { + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM."); + return false; + } + + ndsSave = nullptr; + + baseROMDir = basepath; + baseROMName = romname; + baseAssetName = romname.substr(0, romname.rfind('.')); + + u32 savelen = 0; + std::unique_ptr savedata = nullptr; + + std::string savname = getAssetPath(false, globalCfg.GetString("SaveFilePath"), ".sav"); + std::string origsav = savname; + savname += instanceFileSuffix(); + + FileHandle* sav = Platform::OpenFile(savname, FileMode::Read); + if (!sav) + { + if (!Platform::CheckFileWritable(origsav)) + { + QMessageBox::critical(mainWindow, "melonDS", getSavErrorString(origsav, false)); + return false; + } + + sav = Platform::OpenFile(origsav, FileMode::Read); + } + else if (!Platform::CheckFileWritable(savname)) + { + QMessageBox::critical(mainWindow, "melonDS", getSavErrorString(savname, false)); + return false; + } + + if (sav) + { + savelen = (u32)Platform::FileLength(sav); + + FileRewind(sav); + savedata = std::make_unique(savelen); + FileRead(savedata.get(), savelen, 1, sav); + CloseFile(sav); + } + + NDSCart::NDSCartArgs cartargs { + // Don't load the SD card itself yet, because we don't know if + // the ROM is homebrew or not. + // So this is the card we *would* load if the ROM were homebrew. + .SDCard = getSDCardArgs("DLDI"), + .SRAM = std::move(savedata), + .SRAMLength = savelen, + }; + + auto cart = NDSCart::ParseROM(std::move(filedata), filelen, this, std::move(cartargs)); + if (!cart) + { + // If we couldn't parse the ROM... + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM."); + return false; + } + + if (reset) + { + if (!updateConsole(std::move(cart), Keep {})) + { + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM."); + return false; + } + + initFirmwareSaveManager(); + nds->Reset(); + + if (globalCfg.GetBool("Emu.DirectBoot") || nds->NeedsDirectBoot()) + { // If direct boot is enabled or forced... + nds->SetupDirectBoot(romname); + } + + setBatteryLevels(); + setDateTime(); + } + else + { + assert(nds != nullptr); + nds->SetNDSCart(std::move(cart)); + } + + cartType = 0; + ndsSave = std::make_unique(savname); + loadCheats(); + + return true; // success +} + +void EmuInstance::ejectCart() +{ + ndsSave = nullptr; + + unloadCheats(); + + nds->EjectCart(); + + cartType = -1; + baseROMDir = ""; + baseROMName = ""; + baseAssetName = ""; +} + +bool EmuInstance::cartInserted() +{ + return cartType != -1; +} + +QString EmuInstance::cartLabel() +{ + if (cartType == -1) + return "(none)"; + + QString ret = QString::fromStdString(baseROMName); + + int maxlen = 32; + if (ret.length() > maxlen) + ret = ret.left(maxlen-6) + "..." + ret.right(3); + + return ret; +} + + +bool EmuInstance::loadGBAROM(QStringList filepath) +{ + if (consoleType == 1) + { + QMessageBox::critical(mainWindow, "melonDS", "The DSi doesn't have a GBA slot."); + return false; + } + + unique_ptr filedata = nullptr; + u32 filelen; + std::string basepath; + std::string romname; + + if (!loadROMData(filepath, filedata, filelen, basepath, romname)) + { + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the GBA ROM."); + return false; + } + + gbaSave = nullptr; + + baseGBAROMDir = basepath; + baseGBAROMName = romname; + baseGBAAssetName = romname.substr(0, romname.rfind('.')); + + u32 savelen = 0; + std::unique_ptr savedata = nullptr; + + std::string savname = getAssetPath(true, globalCfg.GetString("SaveFilePath"), ".sav"); + std::string origsav = savname; + savname += instanceFileSuffix(); + + FileHandle* sav = Platform::OpenFile(savname, FileMode::Read); + if (!sav) + { + if (!Platform::CheckFileWritable(origsav)) + { + QMessageBox::critical(mainWindow, "melonDS", getSavErrorString(origsav, true)); + return false; + } + + sav = Platform::OpenFile(origsav, FileMode::Read); + } + else if (!Platform::CheckFileWritable(savname)) + { + QMessageBox::critical(mainWindow, "melonDS", getSavErrorString(savname, true)); + return false; + } + + if (sav) + { + savelen = (u32)FileLength(sav); + + if (savelen > 0) + { + FileRewind(sav); + savedata = std::make_unique(savelen); + FileRead(savedata.get(), savelen, 1, sav); + } + CloseFile(sav); + } + + auto cart = GBACart::ParseROM(std::move(filedata), filelen, std::move(savedata), savelen, this); + if (!cart) + { + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the GBA ROM."); + return false; + } + + nds->SetGBACart(std::move(cart)); + gbaCartType = 0; + gbaSave = std::make_unique(savname); + return true; +} + +void EmuInstance::loadGBAAddon(int type) +{ + if (consoleType == 1) return; + + gbaSave = nullptr; + + nds->LoadGBAAddon(type); + + gbaCartType = type; + baseGBAROMDir = ""; + baseGBAROMName = ""; + baseGBAAssetName = ""; +} + +void EmuInstance::ejectGBACart() +{ + gbaSave = nullptr; + + nds->EjectGBACart(); + + gbaCartType = -1; + baseGBAROMDir = ""; + baseGBAROMName = ""; + baseGBAAssetName = ""; +} + +bool EmuInstance::gbaCartInserted() +{ + return gbaCartType != -1; +} + +QString EmuInstance::gbaAddonName(int addon) +{ + switch (addon) + { + case GBAAddon_RumblePak: + return "Rumble Pak"; + case GBAAddon_RAMExpansion: + return "Memory expansion"; + } + + return "???"; +} + +QString EmuInstance::gbaCartLabel() +{ + if (consoleType == 1) return "none (DSi)"; + + if (gbaCartType == 0) + { + QString ret = QString::fromStdString(baseGBAROMName); + + int maxlen = 32; + if (ret.length() > maxlen) + ret = ret.left(maxlen-6) + "..." + ret.right(3); + + return ret; + } + else if (gbaCartType != -1) + { + return gbaAddonName(gbaCartType); + } + + return "(none)"; +} + + +void EmuInstance::romIcon(const u8 (&data)[512], const u16 (&palette)[16], u32 (&iconRef)[32*32]) +{ + u32 paletteRGBA[16]; + for (int i = 0; i < 16; i++) + { + u8 r = ((palette[i] >> 0) & 0x1F) * 255 / 31; + u8 g = ((palette[i] >> 5) & 0x1F) * 255 / 31; + u8 b = ((palette[i] >> 10) & 0x1F) * 255 / 31; + u8 a = i ? 255 : 0; + paletteRGBA[i] = r | (g << 8) | (b << 16) | (a << 24); + } + + int count = 0; + for (int ytile = 0; ytile < 4; ytile++) + { + for (int xtile = 0; xtile < 4; xtile++) + { + for (int ypixel = 0; ypixel < 8; ypixel++) + { + for (int xpixel = 0; xpixel < 8; xpixel++) + { + u8 pal_index = count % 2 ? data[count/2] >> 4 : data[count/2] & 0x0F; + iconRef[ytile*256 + ypixel*32 + xtile*8 + xpixel] = paletteRGBA[pal_index]; + count++; + } + } + } + } +} + +#define SEQ_FLIPV(i) ((i & 0b1000000000000000) >> 15) +#define SEQ_FLIPH(i) ((i & 0b0100000000000000) >> 14) +#define SEQ_PAL(i) ((i & 0b0011100000000000) >> 11) +#define SEQ_BMP(i) ((i & 0b0000011100000000) >> 8) +#define SEQ_DUR(i) ((i & 0b0000000011111111) >> 0) + +void EmuInstance::animatedROMIcon(const u8 (&data)[8][512], const u16 (&palette)[8][16], const u16 (&sequence)[64], u32 (&animatedIconRef)[64][32*32], std::vector &animatedSequenceRef) +{ + for (int i = 0; i < 64; i++) + { + if (!sequence[i]) + break; + + romIcon(data[SEQ_BMP(sequence[i])], palette[SEQ_PAL(sequence[i])], animatedIconRef[i]); + u32* frame = animatedIconRef[i]; + + if (SEQ_FLIPH(sequence[i])) + { + for (int x = 0; x < 32; x++) + { + for (int y = 0; y < 32/2; y++) + { + std::swap(frame[x * 32 + y], frame[x * 32 + (32 - 1 - y)]); + } + } + } + if (SEQ_FLIPV(sequence[i])) + { + for (int x = 0; x < 32/2; x++) + { + for (int y = 0; y < 32; y++) + { + std::swap(frame[x * 32 + y], frame[(32 - 1 - x) * 32 + y]); + } + } + } + + for (int j = 0; j < SEQ_DUR(sequence[i]); j++) + animatedSequenceRef.push_back(i); + } +} diff --git a/src/frontend/qt_sdl/EmuInstance.h b/src/frontend/qt_sdl/EmuInstance.h new file mode 100644 index 00000000..a135a52c --- /dev/null +++ b/src/frontend/qt_sdl/EmuInstance.h @@ -0,0 +1,346 @@ +/* + Copyright 2016-2024 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 EMUINSTANCE_H +#define EMUINSTANCE_H + +#include + +#include "main.h" +#include "NDS.h" +#include "EmuThread.h" +#include "Window.h" +#include "Config.h" +#include "SaveManager.h" + +const int kMaxWindows = 4; + +enum +{ + HK_Lid = 0, + HK_Mic, + HK_Pause, + HK_Reset, + HK_FastForward, + HK_FrameLimitToggle, + HK_FullscreenToggle, + HK_SwapScreens, + HK_SwapScreenEmphasis, + HK_SolarSensorDecrease, + HK_SolarSensorIncrease, + HK_FrameStep, + HK_PowerButton, + HK_VolumeUp, + HK_VolumeDown, + HK_SlowMo, + HK_FastForwardToggle, + HK_SlowMoToggle, + HK_MAX +}; + +enum +{ + micInputType_Silence, + micInputType_External, + micInputType_Noise, + micInputType_Wav, + micInputType_MAX, +}; + +enum +{ + renderer3D_Software = 0, +#ifdef OGLRENDERER_ENABLED + renderer3D_OpenGL, + renderer3D_OpenGLCompute, +#endif + renderer3D_Max, +}; + +bool isRightModKey(QKeyEvent* event); +int getEventKeyVal(QKeyEvent* event); + +class EmuInstance +{ +public: + EmuInstance(int inst); + ~EmuInstance(); + + int getInstanceID() { return instanceID; } + int getConsoleType() { return consoleType; } + EmuThread* getEmuThread() { return emuThread; } + melonDS::NDS* getNDS() { return nds; } + + MainWindow* getMainWindow() { return mainWindow; } + int getNumWindows() { return numWindows; } + MainWindow* getWindow(int id) { return windowList[id]; } + + void doOnAllWindows(std::function func, int exclude = -1); + void saveEnabledWindows(); + + Config::Table& getGlobalConfig() { return globalCfg; } + Config::Table& getLocalConfig() { return localCfg; } + + void broadcastCommand(int cmd, QVariant param = QVariant()); + void handleCommand(int cmd, QVariant& param); + + std::string instanceFileSuffix(); + + void createWindow(int id = -1); + void deleteWindow(int id, bool close); + void deleteAllWindows(); + + void osdAddMessage(unsigned int color, const char* fmt, ...); + + bool emuIsActive(); + void emuStop(melonDS::Platform::StopReason reason); + + bool usesOpenGL(); + void initOpenGL(int win); + void deinitOpenGL(int win); + void setVSyncGL(bool vsync); + void makeCurrentGL(); + void drawScreenGL(); + + // return: empty string = setup OK, non-empty = error message + QString verifySetup(); + + bool updateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept; + + void enableCheats(bool enable); + melonDS::ARCodeFile* getCheatFile(); + + void romIcon(const melonDS::u8 (&data)[512], + const melonDS::u16 (&palette)[16], + melonDS::u32 (&iconRef)[32*32]); + void animatedROMIcon(const melonDS::u8 (&data)[8][512], + const melonDS::u16 (&palette)[8][16], + const melonDS::u16 (&sequence)[64], + melonDS::u32 (&animatedIconRef)[64][32*32], + std::vector &animatedSequenceRef); + + static const char* buttonNames[12]; + static const char* hotkeyNames[HK_MAX]; + + void inputInit(); + void inputDeInit(); + void inputLoadConfig(); + void inputRumbleStart(melonDS::u32 len_ms); + void inputRumbleStop(); + + void setJoystick(int id); + int getJoystickID() { return joystickID; } + SDL_Joystick* getJoystick() { return joystick; } + + void touchScreen(int x, int y); + void releaseScreen(); + + QMutex renderLock; + +private: + static int lastSep(const std::string& path); + std::string getAssetPath(bool gba, const std::string& configpath, const std::string& ext, const std::string& file); + + QString verifyDSBIOS(); + QString verifyDSiBIOS(); + QString verifyDSFirmware(); + QString verifyDSiFirmware(); + QString verifyDSiNAND(); + + std::string getEffectiveFirmwareSavePath(); + void initFirmwareSaveManager() noexcept; + std::string getSavestateName(int slot); + bool savestateExists(int slot); + bool loadState(const std::string& filename); + bool saveState(const std::string& filename); + void undoStateLoad(); + void unloadCheats(); + void loadCheats(); + std::unique_ptr loadARM9BIOS() noexcept; + std::unique_ptr loadARM7BIOS() noexcept; + std::unique_ptr loadDSiARM9BIOS() noexcept; + std::unique_ptr loadDSiARM7BIOS() noexcept; + melonDS::Firmware generateFirmware(int type) noexcept; + std::optional loadFirmware(int type) noexcept; + std::optional loadNAND(const std::array& arm7ibios) noexcept; + std::optional getSDCardArgs(const std::string& key) noexcept; + std::optional loadSDCard(const std::string& key) noexcept; + void setBatteryLevels(); + void reset(); + bool bootToMenu(); + melonDS::u32 decompressROM(const melonDS::u8* inContent, const melonDS::u32 inSize, std::unique_ptr& outContent); + void clearBackupState(); + std::pair, std::string> generateDefaultFirmware(); + bool parseMacAddress(void* data); + void customizeFirmware(melonDS::Firmware& firmware, bool overridesettings) noexcept; + bool loadROMData(const QStringList& filepath, std::unique_ptr& filedata, melonDS::u32& filelen, std::string& basepath, std::string& romname) noexcept; + QString getSavErrorString(std::string& filepath, bool gba); + bool loadROM(QStringList filepath, bool reset); + void ejectCart(); + bool cartInserted(); + QString cartLabel(); + bool loadGBAROM(QStringList filepath); + void loadGBAAddon(int type); + void ejectGBACart(); + bool gbaCartInserted(); + QString gbaAddonName(int addon); + QString gbaCartLabel(); + + void audioInit(); + void audioDeInit(); + void audioEnable(); + void audioDisable(); + void audioMute(); + void audioSync(); + void audioUpdateSettings(); + + void micOpen(); + void micClose(); + void micLoadWav(const std::string& name); + void micProcess(); + void setupMicInputData(); + + int audioGetNumSamplesOut(int outlen); + void audioResample(melonDS::s16* inbuf, int inlen, melonDS::s16* outbuf, int outlen, int volume); + + static void audioCallback(void* data, Uint8* stream, int len); + static void micCallback(void* data, Uint8* stream, int len); + + void onKeyPress(QKeyEvent* event); + void onKeyRelease(QKeyEvent* event); + void keyReleaseAll(); + + void openJoystick(); + void closeJoystick(); + bool joystickButtonDown(int val); + + void inputProcess(); + + bool hotkeyDown(int id) { return hotkeyMask & (1< ndsSave; + std::unique_ptr gbaSave; + std::unique_ptr firmwareSave; + + bool doLimitFPS; + double curFPS; + double targetFPS; + double fastForwardFPS; + double slowmoFPS; + bool fastForwardToggled; + bool slowmoToggled; + bool doAudioSync; +private: + + std::unique_ptr backupState; + bool savestateLoaded; + std::string previousSaveFile; + + std::unique_ptr cheatFile; + bool cheatsOn; + + SDL_AudioDeviceID audioDevice; + int audioFreq; + float audioSampleFrac; + bool audioMuted; + SDL_cond* audioSyncCond; + SDL_mutex* audioSyncLock; + + int mpAudioMode; + + SDL_AudioDeviceID micDevice; + melonDS::s16 micExtBuffer[4096]; + melonDS::u32 micExtBufferWritePos; + melonDS::u32 micExtBufferCount; + + melonDS::u32 micWavLength; + melonDS::s16* micWavBuffer; + + melonDS::s16* micBuffer; + melonDS::u32 micBufferLength; + melonDS::u32 micBufferReadPos; + + SDL_mutex* micLock; + + //int audioInterp; + int audioVolume; + bool audioDSiVolumeSync; + int micInputType; + std::string micDeviceName; + std::string micWavPath; + + int keyMapping[12]; + int joyMapping[12]; + int hkKeyMapping[HK_MAX]; + int hkJoyMapping[HK_MAX]; + + int joystickID; + SDL_Joystick* joystick; + SDL_GameController* controller; + bool hasRumble = false; + bool isRumbling = false; + + melonDS::u32 keyInputMask, joyInputMask; + melonDS::u32 keyHotkeyMask, joyHotkeyMask; + melonDS::u32 hotkeyMask, lastHotkeyMask; + melonDS::u32 hotkeyPress, hotkeyRelease; + + melonDS::u32 inputMask; + + bool isTouching; + melonDS::u16 touchX, touchY; + + friend class EmuThread; + friend class MainWindow; +}; + +#endif //EMUINSTANCE_H diff --git a/src/frontend/qt_sdl/EmuInstanceAudio.cpp b/src/frontend/qt_sdl/EmuInstanceAudio.cpp new file mode 100644 index 00000000..3b222ba0 --- /dev/null +++ b/src/frontend/qt_sdl/EmuInstanceAudio.cpp @@ -0,0 +1,505 @@ +/* + Copyright 2016-2024 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 "Config.h" +#include "NDS.h" +#include "SPU.h" +#include "Platform.h" +#include "main.h" + +#include "mic_blow.h" + +using namespace melonDS; + + +int EmuInstance::audioGetNumSamplesOut(int outlen) +{ + float f_len_in = (outlen * 32823.6328125) / (float)audioFreq; + f_len_in += audioSampleFrac; + int len_in = (int)floor(f_len_in); + audioSampleFrac = f_len_in - len_in; + + return len_in; +} + +void EmuInstance::audioResample(s16* inbuf, int inlen, s16* outbuf, int outlen, int volume) +{ + float res_incr = inlen / (float)outlen; + float res_timer = -0.5; + int res_pos = 0; + + for (int i = 0; i < outlen; i++) + { + s16 l1 = inbuf[res_pos * 2]; + s16 l2 = inbuf[res_pos * 2 + 2]; + s16 r1 = inbuf[res_pos * 2 + 1]; + s16 r2 = inbuf[res_pos * 2 + 3]; + + float l = (float) l1 + ((l2 - l1) * res_timer); + float r = (float) r1 + ((r2 - r1) * res_timer); + + outbuf[i*2 ] = (s16) (((s32) round(l) * volume) >> 8); + outbuf[i*2+1] = (s16) (((s32) round(r) * volume) >> 8); + + res_timer += res_incr; + while (res_timer >= 1.0) + { + res_timer -= 1.0; + res_pos++; + } + } +} + +void EmuInstance::audioCallback(void* data, Uint8* stream, int len) +{ + EmuInstance* inst = (EmuInstance*)data; + len /= (sizeof(s16) * 2); + + // resample incoming audio to match the output sample rate + + int len_in = inst->audioGetNumSamplesOut(len); + s16 buf_in[1024*2]; + int num_in; + + SDL_LockMutex(inst->audioSyncLock); + num_in = inst->nds->SPU.ReadOutput(buf_in, len_in); + SDL_CondSignal(inst->audioSyncCond); + SDL_UnlockMutex(inst->audioSyncLock); + + if ((num_in < 1) || inst->audioMuted) + { + memset(stream, 0, len*sizeof(s16)*2); + return; + } + + int margin = 6; + if (num_in < len_in-margin) + { + int last = num_in-1; + + for (int i = num_in; i < len_in-margin; i++) + ((u32*)buf_in)[i] = ((u32*)buf_in)[last]; + + num_in = len_in-margin; + } + + inst->audioResample(buf_in, num_in, (s16*)stream, len, inst->audioVolume); +} + +void EmuInstance::micCallback(void* data, Uint8* stream, int len) +{ + EmuInstance* inst = (EmuInstance*)data; + s16* input = (s16*)stream; + len /= sizeof(s16); + + SDL_LockMutex(inst->micLock); + int maxlen = sizeof(micExtBuffer) / sizeof(s16); + + if ((inst->micExtBufferCount + len) > maxlen) + len = maxlen - inst->micExtBufferCount; + + if ((inst->micExtBufferWritePos + len) > maxlen) + { + u32 len1 = maxlen - inst->micExtBufferWritePos; + memcpy(&inst->micExtBuffer[inst->micExtBufferWritePos], &input[0], len1*sizeof(s16)); + memcpy(&inst->micExtBuffer[0], &input[len1], (len - len1)*sizeof(s16)); + inst->micExtBufferWritePos = len - len1; + } + else + { + memcpy(&inst->micExtBuffer[inst->micExtBufferWritePos], input, len*sizeof(s16)); + inst->micExtBufferWritePos += len; + } + + inst->micExtBufferCount += len; + SDL_UnlockMutex(inst->micLock); +} + +void EmuInstance::audioMute() +{ + audioMuted = false; + if (numEmuInstances() < 2) return; + + switch (mpAudioMode) + { + case 1: // only instance 1 + if (instanceID > 0) audioMuted = true; + break; + + case 2: // only currently focused instance + audioMuted = true; + for (int i = 0; i < kMaxWindows; i++) + { + if (!windowList[i]) continue; + if (windowList[i]->isFocused()) + { + audioMuted = false; + break; + } + } + break; + } +} + + +void EmuInstance::micOpen() +{ + if (micInputType != micInputType_External) + { + micDevice = 0; + return; + } + + int numMics = SDL_GetNumAudioDevices(1); + if (numMics == 0) + return; + + SDL_AudioSpec whatIwant, whatIget; + memset(&whatIwant, 0, sizeof(SDL_AudioSpec)); + whatIwant.freq = 44100; + whatIwant.format = AUDIO_S16LSB; + whatIwant.channels = 1; + whatIwant.samples = 1024; + whatIwant.callback = micCallback; + whatIwant.userdata = this; + const char* mic = NULL; + if (micDeviceName != "") + { + mic = micDeviceName.c_str(); + } + micDevice = SDL_OpenAudioDevice(mic, 1, &whatIwant, &whatIget, 0); + if (!micDevice) + { + Platform::Log(Platform::LogLevel::Error, "Mic init failed: %s\n", SDL_GetError()); + } + else + { + SDL_PauseAudioDevice(micDevice, 0); + } +} + +void EmuInstance::micClose() +{ + if (micDevice) + SDL_CloseAudioDevice(micDevice); + + micDevice = 0; +} + +void EmuInstance::micLoadWav(const std::string& name) +{ + SDL_AudioSpec format; + memset(&format, 0, sizeof(SDL_AudioSpec)); + + if (micWavBuffer) delete[] micWavBuffer; + micWavBuffer = nullptr; + micWavLength = 0; + + u8* buf; + u32 len; + if (!SDL_LoadWAV(name.c_str(), &format, &buf, &len)) + return; + + const u64 dstfreq = 44100; + + int srcinc = format.channels; + len /= ((SDL_AUDIO_BITSIZE(format.format) / 8) * srcinc); + + micWavLength = (len * dstfreq) / format.freq; + if (micWavLength < 735) micWavLength = 735; + micWavBuffer = new s16[micWavLength]; + + float res_incr = len / (float)micWavLength; + float res_timer = 0; + int res_pos = 0; + + for (int i = 0; i < micWavLength; i++) + { + u16 val = 0; + + switch (SDL_AUDIO_BITSIZE(format.format)) + { + case 8: + val = buf[res_pos] << 8; + break; + + case 16: + if (SDL_AUDIO_ISBIGENDIAN(format.format)) + val = (buf[res_pos*2] << 8) | buf[res_pos*2 + 1]; + else + val = (buf[res_pos*2 + 1] << 8) | buf[res_pos*2]; + break; + + case 32: + if (SDL_AUDIO_ISFLOAT(format.format)) + { + u32 rawval; + if (SDL_AUDIO_ISBIGENDIAN(format.format)) + rawval = (buf[res_pos*4] << 24) | (buf[res_pos*4 + 1] << 16) | (buf[res_pos*4 + 2] << 8) | buf[res_pos*4 + 3]; + else + rawval = (buf[res_pos*4 + 3] << 24) | (buf[res_pos*4 + 2] << 16) | (buf[res_pos*4 + 1] << 8) | buf[res_pos*4]; + + float fval = *(float*)&rawval; + s32 ival = (s32)(fval * 0x8000); + ival = std::clamp(ival, -0x8000, 0x7FFF); + val = (s16)ival; + } + else if (SDL_AUDIO_ISBIGENDIAN(format.format)) + val = (buf[res_pos*4] << 8) | buf[res_pos*4 + 1]; + else + val = (buf[res_pos*4 + 3] << 8) | buf[res_pos*4 + 2]; + break; + } + + if (SDL_AUDIO_ISUNSIGNED(format.format)) + val ^= 0x8000; + + micWavBuffer[i] = val; + + res_timer += res_incr; + while (res_timer >= 1.0) + { + res_timer -= 1.0; + res_pos += srcinc; + } + } + + SDL_FreeWAV(buf); +} + +void EmuInstance::micProcess() +{ + SDL_LockMutex(micLock); + + int type = micInputType; + bool cmd = hotkeyDown(HK_Mic); + + if (type != micInputType_External && !cmd) + { + type = micInputType_Silence; + } + + const int kFrameLen = 735; + + switch (type) + { + case micInputType_Silence: // no mic + micBufferReadPos = 0; + nds->MicInputFrame(nullptr, 0); + break; + + case micInputType_External: // host mic + case micInputType_Wav: // WAV + if (micBuffer) + { + int len = kFrameLen; + if (micExtBufferCount < len) + len = micExtBufferCount; + + s16 tmp[kFrameLen]; + + if ((micBufferReadPos + len) > micBufferLength) + { + u32 part1 = micBufferLength - micBufferReadPos; + memcpy(&tmp[0], &micBuffer[micBufferReadPos], part1*sizeof(s16)); + memcpy(&tmp[part1], &micBuffer[0], (len - part1)*sizeof(s16)); + + micBufferReadPos = len - part1; + } + else + { + memcpy(&tmp[0], &micBuffer[micBufferReadPos], len*sizeof(s16)); + + micBufferReadPos += len; + } + + if (len < kFrameLen) + { + for (int i = len; i < kFrameLen; i++) + tmp[i] = tmp[len-1]; + } + nds->MicInputFrame(tmp, 735); + + micExtBufferCount -= len; + } + else + { + micBufferReadPos = 0; + nds->MicInputFrame(nullptr, 0); + } + break; + + case micInputType_Noise: // blowing noise + { + int sample_len = sizeof(mic_blow) / sizeof(u16); + static int sample_pos = 0; + + s16 tmp[kFrameLen]; + + for (int i = 0; i < kFrameLen; i++) + { + tmp[i] = mic_blow[sample_pos] ^ 0x8000; + sample_pos++; + if (sample_pos >= sample_len) sample_pos = 0; + } + + nds->MicInputFrame(tmp, kFrameLen); + } + break; + } + + SDL_UnlockMutex(micLock); +} + +void EmuInstance::setupMicInputData() +{ + if (micWavBuffer != nullptr) + { + delete[] micWavBuffer; + micWavBuffer = nullptr; + micWavLength = 0; + } + + micInputType = globalCfg.GetInt("Mic.InputType"); + micDeviceName = globalCfg.GetString("Mic.Device"); + micWavPath = globalCfg.GetString("Mic.WavPath"); + + switch (micInputType) + { + case micInputType_Silence: + case micInputType_Noise: + micBuffer = nullptr; + micBufferLength = 0; + break; + case micInputType_External: + micBuffer = micExtBuffer; + micBufferLength = sizeof(micExtBuffer)/sizeof(s16); + break; + case micInputType_Wav: + micLoadWav(micWavPath); + micBuffer = micWavBuffer; + micBufferLength = micWavLength; + break; + } + + micBufferReadPos = 0; +} + +void EmuInstance::audioInit() +{ + audioVolume = localCfg.GetInt("Audio.Volume"); + audioDSiVolumeSync = localCfg.GetBool("Audio.DSiVolumeSync"); + + audioMuted = false; + audioSyncCond = SDL_CreateCond(); + audioSyncLock = SDL_CreateMutex(); + + audioFreq = 48000; // TODO: make configurable? + SDL_AudioSpec whatIwant, whatIget; + memset(&whatIwant, 0, sizeof(SDL_AudioSpec)); + whatIwant.freq = audioFreq; + whatIwant.format = AUDIO_S16LSB; + whatIwant.channels = 2; + whatIwant.samples = 1024; + whatIwant.callback = audioCallback; + whatIwant.userdata = this; + audioDevice = SDL_OpenAudioDevice(NULL, 0, &whatIwant, &whatIget, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); + if (!audioDevice) + { + Platform::Log(Platform::LogLevel::Error, "Audio init failed: %s\n", SDL_GetError()); + } + else + { + audioFreq = whatIget.freq; + Platform::Log(Platform::LogLevel::Info, "Audio output frequency: %d Hz\n", audioFreq); + SDL_PauseAudioDevice(audioDevice, 1); + } + + audioSampleFrac = 0; + + micDevice = 0; + + memset(micExtBuffer, 0, sizeof(micExtBuffer)); + micExtBufferWritePos = 0; + micExtBufferCount = 0; + micWavBuffer = nullptr; + + micBuffer = nullptr; + micBufferLength = 0; + micBufferReadPos = 0; + + micLock = SDL_CreateMutex(); + + setupMicInputData(); +} + +void EmuInstance::audioDeInit() +{ + if (audioDevice) SDL_CloseAudioDevice(audioDevice); + audioDevice = 0; + micClose(); + + if (audioSyncCond) SDL_DestroyCond(audioSyncCond); + audioSyncCond = nullptr; + + if (audioSyncLock) SDL_DestroyMutex(audioSyncLock); + audioSyncLock = nullptr; + + if (micWavBuffer) delete[] micWavBuffer; + micWavBuffer = nullptr; + + if (micLock) SDL_DestroyMutex(micLock); + micLock = nullptr; +} + +void EmuInstance::audioSync() +{ + if (audioDevice) + { + SDL_LockMutex(audioSyncLock); + while (nds->SPU.GetOutputSize() > 1024) + { + int ret = SDL_CondWaitTimeout(audioSyncCond, audioSyncLock, 500); + if (ret == SDL_MUTEX_TIMEDOUT) break; + } + SDL_UnlockMutex(audioSyncLock); + } +} + +void EmuInstance::audioUpdateSettings() +{ + micClose(); + + int audiointerp = globalCfg.GetInt("Audio.Interpolation"); + nds->SPU.SetInterpolation(static_cast(audiointerp)); + setupMicInputData(); + + micOpen(); +} + +void EmuInstance::audioEnable() +{ + if (audioDevice) SDL_PauseAudioDevice(audioDevice, 0); + micOpen(); +} + +void EmuInstance::audioDisable() +{ + if (audioDevice) SDL_PauseAudioDevice(audioDevice, 1); + micClose(); +} diff --git a/src/frontend/qt_sdl/EmuInstanceInput.cpp b/src/frontend/qt_sdl/EmuInstanceInput.cpp new file mode 100644 index 00000000..aa1c529f --- /dev/null +++ b/src/frontend/qt_sdl/EmuInstanceInput.cpp @@ -0,0 +1,371 @@ +/* + Copyright 2016-2024 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 "main.h" +#include "Config.h" + +using namespace melonDS; + +const char* EmuInstance::buttonNames[12] = +{ + "A", + "B", + "Select", + "Start", + "Right", + "Left", + "Up", + "Down", + "R", + "L", + "X", + "Y" +}; + +const char* EmuInstance::hotkeyNames[HK_MAX] = +{ + "HK_Lid", + "HK_Mic", + "HK_Pause", + "HK_Reset", + "HK_FastForward", + "HK_FrameLimitToggle", + "HK_FullscreenToggle", + "HK_SwapScreens", + "HK_SwapScreenEmphasis", + "HK_SolarSensorDecrease", + "HK_SolarSensorIncrease", + "HK_FrameStep", + "HK_PowerButton", + "HK_VolumeUp", + "HK_VolumeDown", + "HK_SlowMo", + "HK_FastForwardToggle", + "HK_SlowMoToggle" +}; + + +void EmuInstance::inputInit() +{ + keyInputMask = 0xFFF; + joyInputMask = 0xFFF; + inputMask = 0xFFF; + + keyHotkeyMask = 0; + joyHotkeyMask = 0; + hotkeyMask = 0; + lastHotkeyMask = 0; + + isTouching = false; + touchX = 0; + touchY = 0; + + joystick = nullptr; + controller = nullptr; + hasRumble = false; + isRumbling = false; + inputLoadConfig(); +} + +void EmuInstance::inputDeInit() +{ + closeJoystick(); +} + +void EmuInstance::inputLoadConfig() +{ + Config::Table keycfg = localCfg.GetTable("Keyboard"); + Config::Table joycfg = localCfg.GetTable("Joystick"); + + for (int i = 0; i < 12; i++) + { + keyMapping[i] = keycfg.GetInt(buttonNames[i]); + joyMapping[i] = joycfg.GetInt(buttonNames[i]); + } + + for (int i = 0; i < HK_MAX; i++) + { + hkKeyMapping[i] = keycfg.GetInt(hotkeyNames[i]); + hkJoyMapping[i] = joycfg.GetInt(hotkeyNames[i]); + } + + setJoystick(localCfg.GetInt("JoystickID")); +} + +void EmuInstance::inputRumbleStart(melonDS::u32 len_ms) +{ + if (controller && hasRumble && !isRumbling) + { + SDL_GameControllerRumble(controller, 0xFFFF, 0xFFFF, len_ms); + isRumbling = true; + } +} + +void EmuInstance::inputRumbleStop() +{ + if (controller && hasRumble && isRumbling) + { + SDL_GameControllerRumble(controller, 0, 0, 0); + isRumbling = false; + } +} + + +void EmuInstance::setJoystick(int id) +{ + joystickID = id; + openJoystick(); +} + +void EmuInstance::openJoystick() +{ + if (controller) SDL_GameControllerClose(controller); + + if (joystick) SDL_JoystickClose(joystick); + + int num = SDL_NumJoysticks(); + if (num < 1) + { + controller = nullptr; + joystick = nullptr; + hasRumble = false; + return; + } + + if (joystickID >= num) + joystickID = 0; + + joystick = SDL_JoystickOpen(joystickID); + + if (SDL_IsGameController(joystickID)) + { + controller = SDL_GameControllerOpen(joystickID); + } + + if (controller) + { + if (SDL_GameControllerHasRumble(controller)) + { + hasRumble = true; + } + } +} + +void EmuInstance::closeJoystick() +{ + if (controller) + { + SDL_GameControllerClose(controller); + controller = nullptr; + hasRumble = false; + } + + if (joystick) + { + SDL_JoystickClose(joystick); + joystick = nullptr; + } +} + + +// distinguish between left and right modifier keys (Ctrl, Alt, Shift) +// Qt provides no real cross-platform way to do this, so here we go +// for Windows and Linux we can distinguish via scancodes (but both +// provide different scancodes) +bool isRightModKey(QKeyEvent* event) +{ +#ifdef __WIN32__ + quint32 scan = event->nativeScanCode(); + return (scan == 0x11D || scan == 0x138 || scan == 0x36); +#elif __APPLE__ + quint32 scan = event->nativeVirtualKey(); + return (scan == 0x36 || scan == 0x3C || scan == 0x3D || scan == 0x3E); +#else + quint32 scan = event->nativeScanCode(); + return (scan == 0x69 || scan == 0x6C || scan == 0x3E); +#endif +} + +int getEventKeyVal(QKeyEvent* event) +{ + int key = event->key(); + int mod = event->modifiers(); + bool ismod = (key == Qt::Key_Control || + key == Qt::Key_Alt || + key == Qt::Key_AltGr || + key == Qt::Key_Shift || + key == Qt::Key_Meta); + + if (!ismod) + key |= mod; + else if (isRightModKey(event)) + key |= (1<<31); + + return key; +} + + +void EmuInstance::onKeyPress(QKeyEvent* event) +{ + int keyHK = getEventKeyVal(event); + int keyKP = keyHK; + if (event->modifiers() != Qt::KeypadModifier) + keyKP &= ~event->modifiers(); + + for (int i = 0; i < 12; i++) + if (keyKP == keyMapping[i]) + keyInputMask &= ~(1<modifiers() != Qt::KeypadModifier) + keyKP &= ~event->modifiers(); + + for (int i = 0; i < 12; i++) + if (keyKP == keyMapping[i]) + keyInputMask |= (1<> 4) & 0xF; + int hatdir = val & 0xF; + Uint8 hatval = SDL_JoystickGetHat(joystick, hatnum); + + bool pressed = false; + if (hatdir == 0x1) pressed = (hatval & SDL_HAT_UP); + else if (hatdir == 0x4) pressed = (hatval & SDL_HAT_DOWN); + else if (hatdir == 0x2) pressed = (hatval & SDL_HAT_RIGHT); + else if (hatdir == 0x8) pressed = (hatval & SDL_HAT_LEFT); + + if (pressed) return true; + } + else + { + int btnnum = val & 0xFFFF; + Uint8 btnval = SDL_JoystickGetButton(joystick, btnnum); + + if (btnval) return true; + } + } + + if (val & 0x10000) + { + int axisnum = (val >> 24) & 0xF; + int axisdir = (val >> 20) & 0xF; + Sint16 axisval = SDL_JoystickGetAxis(joystick, axisnum); + + switch (axisdir) + { + case 0: // positive + if (axisval > 16384) return true; + break; + + case 1: // negative + if (axisval < -16384) return true; + break; + + case 2: // trigger + if (axisval > 0) return true; + break; + } + } + + return false; +} + +void EmuInstance::inputProcess() +{ + SDL_JoystickUpdate(); + + if (joystick) + { + if (!SDL_JoystickGetAttached(joystick)) + { + SDL_JoystickClose(joystick); + joystick = nullptr; + } + } + if (!joystick && (SDL_NumJoysticks() > 0)) + { + openJoystick(); + } + + joyInputMask = 0xFFF; + if (joystick) + { + for (int i = 0; i < 12; i++) + if (joystickButtonDown(joyMapping[i])) + joyInputMask &= ~(1 << i); + } + + inputMask = keyInputMask & joyInputMask; + + joyHotkeyMask = 0; + if (joystick) + { + for (int i = 0; i < HK_MAX; i++) + if (joystickButtonDown(hkJoyMapping[i])) + joyHotkeyMask |= (1 << i); + } + + hotkeyMask = keyHotkeyMask | joyHotkeyMask; + hotkeyPress = hotkeyMask & ~lastHotkeyMask; + hotkeyRelease = lastHotkeyMask & ~hotkeyMask; + lastHotkeyMask = hotkeyMask; +} + +void EmuInstance::touchScreen(int x, int y) +{ + touchX = x; + touchY = y; + isTouching = true; +} + +void EmuInstance::releaseScreen() +{ + isTouching = false; +} diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.cpp b/src/frontend/qt_sdl/EmuSettingsDialog.cpp index b4e605a5..1c99eb07 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.cpp +++ b/src/frontend/qt_sdl/EmuSettingsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -16,11 +16,8 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#include #include #include -#include -#include #include "types.h" #include "Platform.h" @@ -28,17 +25,16 @@ #include "EmuSettingsDialog.h" #include "ui_EmuSettingsDialog.h" +#include "main.h" using namespace melonDS::Platform; using namespace melonDS; EmuSettingsDialog* EmuSettingsDialog::currentDlg = nullptr; -extern bool RunningSomething; - bool EmuSettingsDialog::needsReset = false; -inline void updateLastBIOSFolder(QString& filename) +inline void EmuSettingsDialog::updateLastBIOSFolder(QString& filename) { int pos = filename.lastIndexOf("/"); if (pos == -1) @@ -47,9 +43,11 @@ inline void updateLastBIOSFolder(QString& filename) } QString path_dir = filename.left(pos); - QString path_file = filename.mid(pos+1); + //QString path_file = filename.mid(pos+1); - Config::LastBIOSFolder = path_dir.toStdString(); + Config::Table cfg = Config::GetGlobalTable(); + cfg.SetQString("LastBIOSFolder", path_dir); + lastBIOSFolder = path_dir; } EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::EmuSettingsDialog) @@ -57,31 +55,39 @@ EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - ui->chkExternalBIOS->setChecked(Config::ExternalBIOSEnable); - ui->txtBIOS9Path->setText(QString::fromStdString(Config::BIOS9Path)); - ui->txtBIOS7Path->setText(QString::fromStdString(Config::BIOS7Path)); - ui->txtFirmwarePath->setText(QString::fromStdString(Config::FirmwarePath)); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + auto& cfg = emuInstance->getGlobalConfig(); + auto& instcfg = emuInstance->getLocalConfig(); - ui->txtDSiBIOS9Path->setText(QString::fromStdString(Config::DSiBIOS9Path)); - ui->txtDSiBIOS7Path->setText(QString::fromStdString(Config::DSiBIOS7Path)); - ui->txtDSiFirmwarePath->setText(QString::fromStdString(Config::DSiFirmwarePath)); - ui->txtDSiNANDPath->setText(QString::fromStdString(Config::DSiNANDPath)); + lastBIOSFolder = cfg.GetQString("LastBIOSFolder"); + + ui->cbDebugPrintEnabled->setChecked(cfg.GetBool("DS.DebugPrintEnabled")); + + ui->chkExternalBIOS->setChecked(cfg.GetBool("Emu.ExternalBIOSEnable")); + ui->txtBIOS9Path->setText(cfg.GetQString("DS.BIOS9Path")); + ui->txtBIOS7Path->setText(cfg.GetQString("DS.BIOS7Path")); + ui->txtFirmwarePath->setText(cfg.GetQString("DS.FirmwarePath")); + + ui->txtDSiBIOS9Path->setText(cfg.GetQString("DSi.BIOS9Path")); + ui->txtDSiBIOS7Path->setText(cfg.GetQString("DSi.BIOS7Path")); + ui->txtDSiFirmwarePath->setText(cfg.GetQString("DSi.FirmwarePath")); + ui->txtDSiNANDPath->setText(cfg.GetQString("DSi.NANDPath")); ui->cbxConsoleType->addItem("DS"); ui->cbxConsoleType->addItem("DSi (experimental)"); - ui->cbxConsoleType->setCurrentIndex(Config::ConsoleType); + ui->cbxConsoleType->setCurrentIndex(cfg.GetInt("Emu.ConsoleType")); - ui->chkDirectBoot->setChecked(Config::DirectBoot); + ui->chkDirectBoot->setChecked(cfg.GetBool("Emu.DirectBoot")); #ifdef JIT_ENABLED - ui->chkEnableJIT->setChecked(Config::JIT_Enable); - ui->chkJITBranchOptimisations->setChecked(Config::JIT_BranchOptimisations); - ui->chkJITLiteralOptimisations->setChecked(Config::JIT_LiteralOptimisations); - ui->chkJITFastMemory->setChecked(Config::JIT_FastMemory); + ui->chkEnableJIT->setChecked(cfg.GetBool("JIT.Enable")); + ui->chkJITBranchOptimisations->setChecked(cfg.GetBool("JIT.BranchOptimisations")); + ui->chkJITLiteralOptimisations->setChecked(cfg.GetBool("JIT.LiteralOptimisations")); + ui->chkJITFastMemory->setChecked(cfg.GetBool("JIT.FastMemory")); #ifdef __APPLE__ ui->chkJITFastMemory->setDisabled(true); #endif - ui->spnJITMaximumBlockSize->setValue(Config::JIT_MaxBlockSize); + ui->spnJITMaximumBlockSize->setValue(cfg.GetInt("JIT.MaxBlockSize")); #else ui->chkEnableJIT->setDisabled(true); ui->chkJITBranchOptimisations->setDisabled(true); @@ -91,11 +97,11 @@ EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new #endif #ifdef GDBSTUB_ENABLED - ui->cbGdbEnabled->setChecked(Config::GdbEnabled); - ui->intGdbPortA7->setValue(Config::GdbPortARM7); - ui->intGdbPortA9->setValue(Config::GdbPortARM9); - ui->cbGdbBOSA7->setChecked(Config::GdbARM7BreakOnStartup); - ui->cbGdbBOSA9->setChecked(Config::GdbARM9BreakOnStartup); + ui->cbGdbEnabled->setChecked(instcfg.GetBool("Gdb.Enabled")); + ui->intGdbPortA7->setValue(instcfg.GetInt("Gdb.ARM7.Port")); + ui->intGdbPortA9->setValue(instcfg.GetInt("Gdb.ARM9.Port")); + ui->cbGdbBOSA7->setChecked(instcfg.GetBool("Gdb.ARM7.BreakOnStartup")); + ui->cbGdbBOSA9->setChecked(instcfg.GetBool("Gdb.ARM9.BreakOnStartup")); #else ui->cbGdbEnabled->setDisabled(true); ui->intGdbPortA7->setDisabled(true); @@ -104,7 +110,7 @@ EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new ui->cbGdbBOSA9->setDisabled(true); #endif - ui->cbDebugPrintEnabled->setChecked(Config::DebugPrintEnabled); + ui->cbDebugPrintEnabled->setChecked(cfg.GetBool("DS.DebugPrintEnabled")); on_chkEnableJIT_toggled(); on_cbGdbEnabled_toggled(); @@ -133,23 +139,34 @@ EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new ui->cbxDSiSDSize->addItem(sizelbl); } - ui->cbDLDIEnable->setChecked(Config::DLDIEnable); - ui->txtDLDISDPath->setText(QString::fromStdString(Config::DLDISDPath)); - ui->cbxDLDISize->setCurrentIndex(Config::DLDISize); - ui->cbDLDIReadOnly->setChecked(Config::DLDIReadOnly); - ui->cbDLDIFolder->setChecked(Config::DLDIFolderSync); - ui->txtDLDIFolder->setText(QString::fromStdString(Config::DLDIFolderPath)); + ui->cbDLDIEnable->setChecked(cfg.GetBool("DLDI.Enable")); + ui->txtDLDISDPath->setText(cfg.GetQString("DLDI.ImagePath")); + ui->cbxDLDISize->setCurrentIndex(cfg.GetInt("DLDI.ImageSize")); + ui->cbDLDIReadOnly->setChecked(cfg.GetBool("DLDI.ReadOnly")); + ui->cbDLDIFolder->setChecked(cfg.GetBool("DLDI.FolderSync")); + ui->txtDLDIFolder->setText(cfg.GetQString("DLDI.FolderPath")); on_cbDLDIEnable_toggled(); - ui->cbDSiFullBIOSBoot->setChecked(Config::DSiFullBIOSBoot); + ui->cbDSiFullBIOSBoot->setChecked(cfg.GetBool("DSi.FullBIOSBoot")); - ui->cbDSiSDEnable->setChecked(Config::DSiSDEnable); - ui->txtDSiSDPath->setText(QString::fromStdString(Config::DSiSDPath)); - ui->cbxDSiSDSize->setCurrentIndex(Config::DSiSDSize); - ui->cbDSiSDReadOnly->setChecked(Config::DSiSDReadOnly); - ui->cbDSiSDFolder->setChecked(Config::DSiSDFolderSync); - ui->txtDSiSDFolder->setText(QString::fromStdString(Config::DSiSDFolderPath)); + ui->cbDSiSDEnable->setChecked(cfg.GetBool("DSi.SD.Enable")); + ui->txtDSiSDPath->setText(cfg.GetQString("DSi.SD.ImagePath")); + ui->cbxDSiSDSize->setCurrentIndex(cfg.GetInt("DSi.SD.ImageSize")); + ui->cbDSiSDReadOnly->setChecked(cfg.GetBool("DSi.SD.ReadOnly")); + ui->cbDSiSDFolder->setChecked(cfg.GetBool("DSi.SD.FolderSync")); + ui->txtDSiSDFolder->setText(cfg.GetQString("DSi.SD.FolderPath")); on_cbDSiSDEnable_toggled(); + +#define SET_ORIGVAL(type, val) \ + for (type* w : findChildren(nullptr)) \ + w->setProperty("user_originalValue", w->val()); + + SET_ORIGVAL(QLineEdit, text); + SET_ORIGVAL(QSpinBox, value); + SET_ORIGVAL(QComboBox, currentIndex); + SET_ORIGVAL(QCheckBox, isChecked); + +#undef SET_ORIGVAL } EmuSettingsDialog::~EmuSettingsDialog() @@ -157,6 +174,7 @@ EmuSettingsDialog::~EmuSettingsDialog() delete ui; } + void EmuSettingsDialog::verifyFirmware() { // verify the firmware @@ -201,143 +219,95 @@ void EmuSettingsDialog::verifyFirmware() void EmuSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + needsReset = false; if (r == QDialog::Accepted) { - verifyFirmware(); + bool modified = false; - int consoleType = ui->cbxConsoleType->currentIndex(); - bool directBoot = ui->chkDirectBoot->isChecked(); +#define CHECK_ORIGVAL(type, val) \ + if (!modified) for (type* w : findChildren(nullptr)) \ + { \ + QVariant v = w->val(); \ + if (v != w->property("user_originalValue")) \ + { \ + modified = true; \ + break; \ + }\ + } - bool jitEnable = ui->chkEnableJIT->isChecked(); - int jitMaxBlockSize = ui->spnJITMaximumBlockSize->value(); - bool jitBranchOptimisations = ui->chkJITBranchOptimisations->isChecked(); - bool jitLiteralOptimisations = ui->chkJITLiteralOptimisations->isChecked(); - bool jitFastMemory = ui->chkJITFastMemory->isChecked(); + CHECK_ORIGVAL(QLineEdit, text); + CHECK_ORIGVAL(QSpinBox, value); + CHECK_ORIGVAL(QComboBox, currentIndex); + CHECK_ORIGVAL(QCheckBox, isChecked); - bool externalBiosEnable = ui->chkExternalBIOS->isChecked(); - std::string bios9Path = ui->txtBIOS9Path->text().toStdString(); - std::string bios7Path = ui->txtBIOS7Path->text().toStdString(); - std::string firmwarePath = ui->txtFirmwarePath->text().toStdString(); +#undef CHECK_ORIGVAL - bool dldiEnable = ui->cbDLDIEnable->isChecked(); - std::string dldiSDPath = ui->txtDLDISDPath->text().toStdString(); - int dldiSize = ui->cbxDLDISize->currentIndex(); - bool dldiReadOnly = ui->cbDLDIReadOnly->isChecked(); - bool dldiFolderSync = ui->cbDLDIFolder->isChecked(); - std::string dldiFolderPath = ui->txtDLDIFolder->text().toStdString(); + if (QVariant(ui->txtFirmwarePath->text()) != ui->txtFirmwarePath->property("user_originalValue")) + verifyFirmware(); - std::string dsiBios9Path = ui->txtDSiBIOS9Path->text().toStdString(); - std::string dsiBios7Path = ui->txtDSiBIOS7Path->text().toStdString(); - std::string dsiFirmwarePath = ui->txtDSiFirmwarePath->text().toStdString(); - std::string dsiNANDPath = ui->txtDSiNANDPath->text().toStdString(); - bool dsiFullBiosBoot = ui->cbDSiFullBIOSBoot->isChecked(); - - bool debugPrintEnabled = ui->cbDebugPrintEnabled->isChecked(); - - bool dsiSDEnable = ui->cbDSiSDEnable->isChecked(); - std::string dsiSDPath = ui->txtDSiSDPath->text().toStdString(); - int dsiSDSize = ui->cbxDSiSDSize->currentIndex(); - bool dsiSDReadOnly = ui->cbDSiSDReadOnly->isChecked(); - bool dsiSDFolderSync = ui->cbDSiSDFolder->isChecked(); - std::string dsiSDFolderPath = ui->txtDSiSDFolder->text().toStdString(); - - bool gdbEnabled = ui->cbGdbEnabled->isChecked(); - int gdbPortA7 = ui->intGdbPortA7->value(); - int gdbPortA9 = ui->intGdbPortA9->value(); - bool gdbBOSA7 = ui->cbGdbBOSA7->isChecked(); - bool gdbBOSA9 = ui->cbGdbBOSA9->isChecked(); - - if (consoleType != Config::ConsoleType - || directBoot != Config::DirectBoot -#ifdef JIT_ENABLED - || jitEnable != Config::JIT_Enable - || jitMaxBlockSize != Config::JIT_MaxBlockSize - || jitBranchOptimisations != Config::JIT_BranchOptimisations - || jitLiteralOptimisations != Config::JIT_LiteralOptimisations - || jitFastMemory != Config::JIT_FastMemory -#endif -#ifdef GDBSTUB_ENABLED - || gdbEnabled != Config::GdbEnabled - || gdbPortA7 != Config::GdbPortARM7 - || gdbPortA9 != Config::GdbPortARM9 - || gdbBOSA7 != Config::GdbARM7BreakOnStartup - || gdbBOSA9 != Config::GdbARM9BreakOnStartup -#endif - || externalBiosEnable != Config::ExternalBIOSEnable - || bios9Path != Config::BIOS9Path - || bios7Path != Config::BIOS7Path - || firmwarePath != Config::FirmwarePath - || dldiEnable != Config::DLDIEnable - || dldiSDPath != Config::DLDISDPath - || dldiSize != Config::DLDISize - || dldiReadOnly != Config::DLDIReadOnly - || dldiFolderSync != Config::DLDIFolderSync - || dldiFolderPath != Config::DLDIFolderPath - || dsiBios9Path != Config::DSiBIOS9Path - || dsiBios7Path != Config::DSiBIOS7Path - || dsiFirmwarePath != Config::DSiFirmwarePath - || dsiNANDPath != Config::DSiNANDPath - || dsiFullBiosBoot != Config::DSiFullBIOSBoot - || debugPrintEnabled != Config::DebugPrintEnabled - || dsiSDEnable != Config::DSiSDEnable - || dsiSDPath != Config::DSiSDPath - || dsiSDSize != Config::DSiSDSize - || dsiSDReadOnly != Config::DSiSDReadOnly - || dsiSDFolderSync != Config::DSiSDFolderSync - || dsiSDFolderPath != Config::DSiSDFolderPath) + if (modified) { - if (RunningSomething + if (emuInstance->emuIsActive() && QMessageBox::warning(this, "Reset necessary to apply changes", "The emulation will be reset for the changes to take place.", QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) return; - Config::ExternalBIOSEnable = externalBiosEnable; - Config::BIOS9Path = bios9Path; - Config::BIOS7Path = bios7Path; - Config::FirmwarePath = firmwarePath; + auto& cfg = emuInstance->getGlobalConfig(); + auto& instcfg = emuInstance->getLocalConfig(); - Config::DLDIEnable = dldiEnable; - Config::DLDISDPath = dldiSDPath; - Config::DLDISize = dldiSize; - Config::DLDIReadOnly = dldiReadOnly; - Config::DLDIFolderSync = dldiFolderSync; - Config::DLDIFolderPath = dldiFolderPath; + cfg.SetBool("Emu.ExternalBIOSEnable", ui->chkExternalBIOS->isChecked()); + cfg.SetQString("DS.BIOS9Path", ui->txtBIOS9Path->text()); + cfg.SetQString("DS.BIOS7Path", ui->txtBIOS7Path->text()); + cfg.SetQString("DS.FirmwarePath", ui->txtFirmwarePath->text()); - Config::DSiBIOS9Path = dsiBios9Path; - Config::DSiBIOS7Path = dsiBios7Path; - Config::DSiFirmwarePath = dsiFirmwarePath; - Config::DSiNANDPath = dsiNANDPath; - Config::DSiFullBIOSBoot = dsiFullBiosBoot; + cfg.SetBool("DLDI.Enable", ui->cbDLDIEnable->isChecked()); + cfg.SetQString("DLDI.ImagePath", ui->txtDLDISDPath->text()); + cfg.SetInt("DLDI.ImageSize", ui->cbxDLDISize->currentIndex()); + cfg.SetBool("DLDI.ReadOnly", ui->cbDLDIReadOnly->isChecked()); + cfg.SetBool("DLDI.FolderSync", ui->cbDLDIFolder->isChecked()); + cfg.SetQString("DLDI.FolderPath", ui->txtDLDIFolder->text()); - Config::DebugPrintEnabled = debugPrintEnabled; + cfg.SetBool("DS.DebugPrintEnabled", ui->cbDebugPrintEnabled->isChecked()); - Config::DSiSDEnable = dsiSDEnable; - Config::DSiSDPath = dsiSDPath; - Config::DSiSDSize = dsiSDSize; - Config::DSiSDReadOnly = dsiSDReadOnly; - Config::DSiSDFolderSync = dsiSDFolderSync; - Config::DSiSDFolderPath = dsiSDFolderPath; + cfg.SetQString("DSi.BIOS9Path", ui->txtDSiBIOS9Path->text()); + cfg.SetQString("DSi.BIOS7Path", ui->txtDSiBIOS7Path->text()); + cfg.SetQString("DSi.FirmwarePath", ui->txtDSiFirmwarePath->text()); + cfg.SetQString("DSi.NANDPath", ui->txtDSiNANDPath->text()); + cfg.SetBool("DSi.FullBIOSBoot", ui->cbDSiFullBIOSBoot->isChecked()); + + cfg.SetBool("DSi.SD.Enable", ui->cbDSiSDEnable->isChecked()); + cfg.SetQString("DSi.SD.ImagePath", ui->txtDSiSDPath->text()); + cfg.SetInt("DSi.SD.ImageSize", ui->cbxDSiSDSize->currentIndex()); + cfg.SetBool("DSi.SD.ReadOnly", ui->cbDSiSDReadOnly->isChecked()); + cfg.SetBool("DSi.SD.FolderSync", ui->cbDSiSDFolder->isChecked()); + cfg.SetQString("DSi.SD.FolderPath", ui->txtDSiSDFolder->text()); #ifdef JIT_ENABLED - Config::JIT_Enable = jitEnable; - Config::JIT_MaxBlockSize = jitMaxBlockSize; - Config::JIT_BranchOptimisations = jitBranchOptimisations; - Config::JIT_LiteralOptimisations = jitLiteralOptimisations; - Config::JIT_FastMemory = jitFastMemory; + cfg.SetBool("JIT.Enable", ui->chkEnableJIT->isChecked()); + cfg.SetInt("JIT.MaxBlockSize", ui->spnJITMaximumBlockSize->value()); + cfg.SetBool("JIT.BranchOptimisations", ui->chkJITBranchOptimisations->isChecked()); + cfg.SetBool("JIT.LiteralOptimisations", ui->chkJITLiteralOptimisations->isChecked()); + cfg.SetBool("JIT.FastMemory", ui->chkJITFastMemory->isChecked()); #endif #ifdef GDBSTUB_ENABLED - Config::GdbEnabled = gdbEnabled; - Config::GdbPortARM7 = gdbPortA7; - Config::GdbPortARM9 = gdbPortA9; - Config::GdbARM7BreakOnStartup = gdbBOSA7; - Config::GdbARM9BreakOnStartup = gdbBOSA9; + instcfg.SetBool("Gdb.Enabled", ui->cbGdbEnabled->isChecked()); + instcfg.SetInt("Gdb.ARM7.Port", ui->intGdbPortA7->value()); + instcfg.SetInt("Gdb.ARM9.Port", ui->intGdbPortA9->value()); + instcfg.SetBool("Gdb.ARM7.BreakOnStartup", ui->cbGdbBOSA7->isChecked()); + instcfg.SetBool("Gdb.ARM9.BreakOnStartup", ui->cbGdbBOSA9->isChecked()); #endif - Config::ConsoleType = consoleType; - Config::DirectBoot = directBoot; + cfg.SetInt("Emu.ConsoleType", ui->cbxConsoleType->currentIndex()); + cfg.SetBool("Emu.DirectBoot", ui->chkDirectBoot->isChecked()); Config::Save(); @@ -354,7 +324,7 @@ void EmuSettingsDialog::on_btnBIOS9Browse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DS-mode ARM9 BIOS...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "BIOS files (*.bin *.rom);;Any file (*.*)"); if (file.isEmpty()) return; @@ -368,7 +338,7 @@ void EmuSettingsDialog::on_btnBIOS7Browse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DS-mode ARM7 BIOS...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "BIOS files (*.bin *.rom);;Any file (*.*)"); if (file.isEmpty()) return; @@ -382,11 +352,17 @@ void EmuSettingsDialog::on_btnFirmwareBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DS-mode firmware...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "Firmware files (*.bin *.rom);;Any file (*.*)"); if (file.isEmpty()) return; + if (!Platform::CheckFileWritable(file.toStdString())) + { + QMessageBox::critical(this, "melonDS", "Unable to write to firmware file.\nPlease check file/folder write permissions."); + return; + } + updateLastBIOSFolder(file); ui->txtFirmwarePath->setText(file); @@ -396,7 +372,7 @@ void EmuSettingsDialog::on_btnDSiBIOS9Browse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DSi-mode ARM9 BIOS...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "BIOS files (*.bin *.rom);;Any file (*.*)"); if (file.isEmpty()) return; @@ -410,7 +386,7 @@ void EmuSettingsDialog::on_btnDSiBIOS7Browse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DSi-mode ARM7 BIOS...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "BIOS files (*.bin *.rom);;Any file (*.*)"); if (file.isEmpty()) return; @@ -438,11 +414,17 @@ void EmuSettingsDialog::on_btnDLDISDBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DLDI SD image...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "Image files (*.bin *.rom *.img *.dmg);;Any file (*.*)"); if (file.isEmpty()) return; + if (!Platform::CheckFileWritable(file.toStdString())) + { + QMessageBox::critical(this, "melonDS", "Unable to write to DLDI SD image.\nPlease check file/folder write permissions."); + return; + } + updateLastBIOSFolder(file); ui->txtDLDISDPath->setText(file); @@ -459,7 +441,7 @@ void EmuSettingsDialog::on_btnDLDIFolderBrowse_clicked() { QString dir = QFileDialog::getExistingDirectory(this, "Select DLDI SD folder...", - QString::fromStdString(Config::LastBIOSFolder)); + lastBIOSFolder); if (dir.isEmpty()) return; @@ -470,11 +452,18 @@ void EmuSettingsDialog::on_btnDSiFirmwareBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DSi DS-mode firmware...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "Firmware files (*.bin *.rom);;Any file (*.*)"); if (file.isEmpty()) return; + if (!Platform::CheckFileWritable(file.toStdString())) + { + QMessageBox::critical(this, "melonDS", "Unable to write to DSi firmware file.\nPlease check file/folder write permissions."); + return; + } + + updateLastBIOSFolder(file); ui->txtDSiFirmwarePath->setText(file); @@ -484,11 +473,18 @@ void EmuSettingsDialog::on_btnDSiNANDBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DSi NAND...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "NAND files (*.bin *.mmc *.rom);;Any file (*.*)"); if (file.isEmpty()) return; + if (!Platform::CheckFileWritable(file.toStdString())) + { + QMessageBox::critical(this, "melonDS", "Unable to write to DSi NAND image.\nPlease check file/folder write permissions."); + return; + } + + updateLastBIOSFolder(file); ui->txtDSiNANDPath->setText(file); @@ -512,11 +508,17 @@ void EmuSettingsDialog::on_btnDSiSDBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DSi SD image...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "Image files (*.bin *.rom *.img *.sd *.dmg);;Any file (*.*)"); if (file.isEmpty()) return; + if (!Platform::CheckFileWritable(file.toStdString())) + { + QMessageBox::critical(this, "melonDS", "Unable to write to DSi SD image.\nPlease check file/folder write permissions."); + return; + } + updateLastBIOSFolder(file); ui->txtDSiSDPath->setText(file); @@ -533,7 +535,7 @@ void EmuSettingsDialog::on_btnDSiSDFolderBrowse_clicked() { QString dir = QFileDialog::getExistingDirectory(this, "Select DSi SD folder...", - QString::fromStdString(Config::LastBIOSFolder)); + lastBIOSFolder); if (dir.isEmpty()) return; diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.h b/src/frontend/qt_sdl/EmuSettingsDialog.h index b53d090b..58a89b3a 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.h +++ b/src/frontend/qt_sdl/EmuSettingsDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -24,6 +24,8 @@ namespace Ui { class EmuSettingsDialog; } class EmuSettingsDialog; +class EmuInstance; + class EmuSettingsDialog : public QDialog { Q_OBJECT @@ -81,8 +83,11 @@ private slots: private: void verifyFirmware(); + void updateLastBIOSFolder(QString& filename); Ui::EmuSettingsDialog* ui; + EmuInstance* emuInstance; + QString lastBIOSFolder; }; #endif // EMUSETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/EmuThread.cpp b/src/frontend/qt_sdl/EmuThread.cpp index 6f4bab25..f767c6db 100644 --- a/src/frontend/qt_sdl/EmuThread.cpp +++ b/src/frontend/qt_sdl/EmuThread.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -29,13 +29,11 @@ #include #include "main.h" -#include "Input.h" -#include "AudioInOut.h" #include "types.h" #include "version.h" -#include "FrontendUtil.h" +#include "ScreenLayout.h" #include "Args.h" #include "NDS.h" @@ -52,261 +50,66 @@ #include "DSi_I2C.h" #include "GPU3D_Soft.h" #include "GPU3D_OpenGL.h" +#include "GPU3D_Compute.h" #include "Savestate.h" -#include "ROMManager.h" -//#include "ArchiveUtil.h" -//#include "CameraManager.h" +#include "EmuInstance.h" -//#include "CLI.h" - -// TODO: uniform variable spelling using namespace melonDS; -// TEMP -extern bool RunningSomething; -extern MainWindow* mainWindow; -extern int autoScreenSizing; -extern int videoRenderer; -extern bool videoSettingsDirty; - -EmuThread::EmuThread(QObject* parent) : QThread(parent) +EmuThread::EmuThread(EmuInstance* inst, QObject* parent) : QThread(parent) { - EmuStatus = emuStatus_Exit; - EmuRunning = emuStatus_Paused; - EmuPauseStack = EmuPauseStackRunning; - RunningSomething = false; + emuInstance = inst; - connect(this, SIGNAL(windowUpdate()), mainWindow->panel, 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())); - connect(this, SIGNAL(windowEmuPause()), mainWindow->actPause, SLOT(trigger())); - 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(windowFullscreenToggle()), mainWindow, SLOT(onFullscreenToggled())); - connect(this, SIGNAL(swapScreensToggle()), mainWindow->actScreenSwap, SLOT(trigger())); - connect(this, SIGNAL(screenEmphasisToggle()), mainWindow, SLOT(onScreenEmphasisToggled())); + emuStatus = emuStatus_Paused; + emuPauseStack = emuPauseStackRunning; + emuActive = false; } -std::unique_ptr EmuThread::CreateConsole( - std::unique_ptr&& ndscart, - std::unique_ptr&& gbacart -) noexcept +void EmuThread::attachWindow(MainWindow* window) { - auto arm7bios = ROMManager::LoadARM7BIOS(); - if (!arm7bios) - return nullptr; + connect(this, SIGNAL(windowTitleChange(QString)), window, SLOT(onTitleUpdate(QString))); + connect(this, SIGNAL(windowEmuStart()), window, SLOT(onEmuStart())); + connect(this, SIGNAL(windowEmuStop()), window, SLOT(onEmuStop())); + connect(this, SIGNAL(windowEmuPause(bool)), window, SLOT(onEmuPause(bool))); + connect(this, SIGNAL(windowEmuReset()), window, SLOT(onEmuReset())); + connect(this, SIGNAL(autoScreenSizingChange(int)), window->panel, SLOT(onAutoScreenSizingChanged(int))); + connect(this, SIGNAL(windowFullscreenToggle()), window, SLOT(onFullscreenToggled())); + connect(this, SIGNAL(screenEmphasisToggle()), window, SLOT(onScreenEmphasisToggled())); - auto arm9bios = ROMManager::LoadARM9BIOS(); - if (!arm9bios) - return nullptr; - - auto firmware = ROMManager::LoadFirmware(Config::ConsoleType); - if (!firmware) - return nullptr; - -#ifdef JIT_ENABLED - JITArgs jitargs { - static_cast(Config::JIT_MaxBlockSize), - Config::JIT_LiteralOptimisations, - Config::JIT_BranchOptimisations, - Config::JIT_FastMemory, - }; -#endif - -#ifdef GDBSTUB_ENABLED - GDBArgs gdbargs { - static_cast(Config::GdbPortARM7), - static_cast(Config::GdbPortARM9), - Config::GdbARM7BreakOnStartup, - Config::GdbARM9BreakOnStartup, - }; -#endif - - NDSArgs ndsargs { - std::move(ndscart), - std::move(gbacart), - *arm9bios, - *arm7bios, - std::move(*firmware), -#ifdef JIT_ENABLED - Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt, -#else - std::nullopt, -#endif - static_cast(Config::AudioBitDepth), - static_cast(Config::AudioInterp), -#ifdef GDBSTUB_ENABLED - Config::GdbEnabled ? std::make_optional(gdbargs) : std::nullopt, -#else - std::nullopt, -#endif - }; - - if (Config::ConsoleType == 1) + if (window->winHasMenu()) { - auto arm7ibios = ROMManager::LoadDSiARM7BIOS(); - if (!arm7ibios) - return nullptr; - - auto arm9ibios = ROMManager::LoadDSiARM9BIOS(); - if (!arm9ibios) - return nullptr; - - auto nand = ROMManager::LoadNAND(*arm7ibios); - if (!nand) - return nullptr; - - auto sdcard = ROMManager::LoadDSiSDCard(); - DSiArgs args { - std::move(ndsargs), - *arm9ibios, - *arm7ibios, - std::move(*nand), - std::move(sdcard), - Config::DSiFullBIOSBoot, - }; - - args.GBAROM = nullptr; - - return std::make_unique(std::move(args)); + connect(this, SIGNAL(windowLimitFPSChange()), window->actLimitFramerate, SLOT(trigger())); + connect(this, SIGNAL(swapScreensToggle()), window->actScreenSwap, SLOT(trigger())); } - - return std::make_unique(std::move(ndsargs)); } -bool EmuThread::UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept +void EmuThread::detachWindow(MainWindow* window) { - // Let's get the cart we want to use; - // if we wnat to keep the cart, we'll eject it from the existing console first. - std::unique_ptr nextndscart; - if (std::holds_alternative(ndsargs)) - { // If we want to keep the existing cart (if any)... - nextndscart = NDS ? NDS->EjectCart() : nullptr; - ndsargs = {}; - } - else if (const auto ptr = std::get_if>(&ndsargs)) + disconnect(this, SIGNAL(windowTitleChange(QString)), window, SLOT(onTitleUpdate(QString))); + disconnect(this, SIGNAL(windowEmuStart()), window, SLOT(onEmuStart())); + disconnect(this, SIGNAL(windowEmuStop()), window, SLOT(onEmuStop())); + disconnect(this, SIGNAL(windowEmuPause(bool)), window, SLOT(onEmuPause(bool))); + disconnect(this, SIGNAL(windowEmuReset()), window, SLOT(onEmuReset())); + disconnect(this, SIGNAL(autoScreenSizingChange(int)), window->panel, SLOT(onAutoScreenSizingChanged(int))); + disconnect(this, SIGNAL(windowFullscreenToggle()), window, SLOT(onFullscreenToggled())); + disconnect(this, SIGNAL(screenEmphasisToggle()), window, SLOT(onScreenEmphasisToggled())); + + if (window->winHasMenu()) { - nextndscart = std::move(*ptr); - ndsargs = {}; + disconnect(this, SIGNAL(windowLimitFPSChange()), window->actLimitFramerate, SLOT(trigger())); + disconnect(this, SIGNAL(swapScreensToggle()), window->actScreenSwap, SLOT(trigger())); } - - if (auto* cartsd = dynamic_cast(nextndscart.get())) - { - // LoadDLDISDCard will return nullopt if the SD card is disabled; - // SetSDCard will accept nullopt, which means no SD card - cartsd->SetSDCard(ROMManager::GetDLDISDCardArgs()); - } - - std::unique_ptr nextgbacart; - if (std::holds_alternative(gbaargs)) - { - nextgbacart = NDS ? NDS->EjectGBACart() : nullptr; - } - else if (const auto ptr = std::get_if>(&gbaargs)) - { - nextgbacart = std::move(*ptr); - gbaargs = {}; - } - - if (!NDS || NDS->ConsoleType != Config::ConsoleType) - { // If we're switching between DS and DSi mode, or there's no console... - // To ensure the destructor is called before a new one is created, - // as the presence of global signal handlers still complicates things a bit - NDS = nullptr; - NDS::Current = nullptr; - - NDS = CreateConsole(std::move(nextndscart), std::move(nextgbacart)); - - if (NDS == nullptr) - return false; - - NDS->Reset(); - NDS::Current = NDS.get(); - - return true; - } - - auto arm9bios = ROMManager::LoadARM9BIOS(); - if (!arm9bios) - return false; - - auto arm7bios = ROMManager::LoadARM7BIOS(); - if (!arm7bios) - return false; - - auto firmware = ROMManager::LoadFirmware(NDS->ConsoleType); - if (!firmware) - return false; - - if (NDS->ConsoleType == 1) - { // If the console we're updating is a DSi... - DSi& dsi = static_cast(*NDS); - - auto arm9ibios = ROMManager::LoadDSiARM9BIOS(); - if (!arm9ibios) - return false; - - auto arm7ibios = ROMManager::LoadDSiARM7BIOS(); - if (!arm7ibios) - return false; - - auto nandimage = ROMManager::LoadNAND(*arm7ibios); - if (!nandimage) - return false; - - auto dsisdcard = ROMManager::LoadDSiSDCard(); - - dsi.SetFullBIOSBoot(Config::DSiFullBIOSBoot); - dsi.ARM7iBIOS = *arm7ibios; - dsi.ARM9iBIOS = *arm9ibios; - dsi.SetNAND(std::move(*nandimage)); - dsi.SetSDCard(std::move(dsisdcard)); - // We're moving the optional, not the card - // (inserting std::nullopt here is okay, it means no card) - - dsi.EjectGBACart(); - } - - if (NDS->ConsoleType == 0) - { - NDS->SetGBACart(std::move(nextgbacart)); - } - -#ifdef JIT_ENABLED - JITArgs jitargs { - static_cast(Config::JIT_MaxBlockSize), - Config::JIT_LiteralOptimisations, - Config::JIT_BranchOptimisations, - Config::JIT_FastMemory, - }; - NDS->SetJITArgs(Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt); -#endif - NDS->SetARM7BIOS(*arm7bios); - NDS->SetARM9BIOS(*arm9bios); - NDS->SetFirmware(std::move(*firmware)); - NDS->SetNDSCart(std::move(nextndscart)); - NDS->SPU.SetInterpolation(static_cast(Config::AudioInterp)); - NDS->SPU.SetDegrade10Bit(static_cast(Config::AudioBitDepth)); - - NDS->SetDebugPrint(Config::DebugPrintEnabled); - - NDS::Current = NDS.get(); - - return true; } void EmuThread::run() { + Config::Table& globalCfg = emuInstance->getGlobalConfig(); u32 mainScreenPos[3]; - Platform::FileHandle* file; - UpdateConsole(nullptr, nullptr); + //emuInstance->updateConsole(nullptr, nullptr); // No carts are inserted when melonDS first boots mainScreenPos[0] = 0; @@ -314,32 +117,23 @@ void EmuThread::run() mainScreenPos[2] = 0; autoScreenSizing = 0; - videoSettingsDirty = false; + //videoSettingsDirty = false; - if (mainWindow->hasOGL) + if (emuInstance->usesOpenGL()) { - screenGL = static_cast(mainWindow->panel); - screenGL->initOpenGL(); - videoRenderer = Config::_3DRenderer; + emuInstance->initOpenGL(0); + + useOpenGL = true; + videoRenderer = globalCfg.GetInt("3D.Renderer"); } else { - screenGL = nullptr; + useOpenGL = false; videoRenderer = 0; } - if (videoRenderer == 0) - { // If we're using the software renderer... - NDS->GPU.SetRenderer3D(std::make_unique(Config::Threaded3D != 0)); - } - else - { - auto glrenderer = melonDS::GLRenderer::New(); - glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor); - NDS->GPU.SetRenderer3D(std::move(glrenderer)); - } - - Input::Init(); + //updateRenderer(); + videoSettingsDirty = true; u32 nframes = 0; double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency(); @@ -350,101 +144,98 @@ void EmuThread::run() u32 winUpdateCount = 0, winUpdateFreq = 1; u8 dsiVolumeLevel = 0x1F; - file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Read); - if (file) - { - RTC::StateData state; - Platform::FileRead(&state, sizeof(state), 1, file); - Platform::CloseFile(file); - NDS->RTC.SetState(state); - } - char melontitle[100]; - while (EmuRunning != emuStatus_Exit) + bool fastforward = false; + bool slowmo = false; + emuInstance->fastForwardToggled = false; + emuInstance->slowmoToggled = false; + + while (emuStatus != emuStatus_Exit) { - Input::Process(); + MPInterface::Get().Process(); + emuInstance->inputProcess(); - if (Input::HotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange(); + if (emuInstance->hotkeyPressed(HK_FrameLimitToggle)) emit windowLimitFPSChange(); - if (Input::HotkeyPressed(HK_Pause)) emit windowEmuPause(); - if (Input::HotkeyPressed(HK_Reset)) emit windowEmuReset(); - if (Input::HotkeyPressed(HK_FrameStep)) emit windowEmuFrameStep(); + if (emuInstance->hotkeyPressed(HK_Pause)) emuTogglePause(); + if (emuInstance->hotkeyPressed(HK_Reset)) emuReset(); + if (emuInstance->hotkeyPressed(HK_FrameStep)) emuFrameStep(); - if (Input::HotkeyPressed(HK_FullscreenToggle)) emit windowFullscreenToggle(); + if (emuInstance->hotkeyPressed(HK_FullscreenToggle)) emit windowFullscreenToggle(); - if (Input::HotkeyPressed(HK_SwapScreens)) emit swapScreensToggle(); - if (Input::HotkeyPressed(HK_SwapScreenEmphasis)) emit screenEmphasisToggle(); + if (emuInstance->hotkeyPressed(HK_SwapScreens)) emit swapScreensToggle(); + if (emuInstance->hotkeyPressed(HK_SwapScreenEmphasis)) emit screenEmphasisToggle(); - if (EmuRunning == emuStatus_Running || EmuRunning == emuStatus_FrameStep) + if (emuStatus == emuStatus_Running || emuStatus == emuStatus_FrameStep) { - EmuStatus = emuStatus_Running; - if (EmuRunning == emuStatus_FrameStep) EmuRunning = emuStatus_Paused; + if (emuStatus == emuStatus_FrameStep) emuStatus = emuStatus_Paused; - if (Input::HotkeyPressed(HK_SolarSensorDecrease)) + if (emuInstance->hotkeyPressed(HK_SolarSensorDecrease)) { - int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorDown, true); + int level = emuInstance->nds->GBACartSlot.SetInput(GBACart::Input_SolarSensorDown, true); if (level != -1) { - mainWindow->osdAddMessage(0, "Solar sensor level: %d", level); + emuInstance->osdAddMessage(0, "Solar sensor level: %d", level); } } - if (Input::HotkeyPressed(HK_SolarSensorIncrease)) + if (emuInstance->hotkeyPressed(HK_SolarSensorIncrease)) { - int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorUp, true); + int level = emuInstance->nds->GBACartSlot.SetInput(GBACart::Input_SolarSensorUp, true); if (level != -1) { - mainWindow->osdAddMessage(0, "Solar sensor level: %d", level); + emuInstance->osdAddMessage(0, "Solar sensor level: %d", level); } } - if (NDS->ConsoleType == 1) + if (emuInstance->nds->ConsoleType == 1) { - DSi& dsi = static_cast(*NDS); + DSi* dsi = static_cast(emuInstance->nds); double currentTime = SDL_GetPerformanceCounter() * perfCountsSec; // Handle power button - if (Input::HotkeyDown(HK_PowerButton)) + if (emuInstance->hotkeyDown(HK_PowerButton)) { - dsi.I2C.GetBPTWL()->SetPowerButtonHeld(currentTime); + dsi->I2C.GetBPTWL()->SetPowerButtonHeld(currentTime); } - else if (Input::HotkeyReleased(HK_PowerButton)) + else if (emuInstance->hotkeyReleased(HK_PowerButton)) { - dsi.I2C.GetBPTWL()->SetPowerButtonReleased(currentTime); + dsi->I2C.GetBPTWL()->SetPowerButtonReleased(currentTime); } // Handle volume buttons - if (Input::HotkeyDown(HK_VolumeUp)) + if (emuInstance->hotkeyDown(HK_VolumeUp)) { - dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Up); + dsi->I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Up); } - else if (Input::HotkeyReleased(HK_VolumeUp)) + else if (emuInstance->hotkeyReleased(HK_VolumeUp)) { - dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Up); + dsi->I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Up); } - if (Input::HotkeyDown(HK_VolumeDown)) + if (emuInstance->hotkeyDown(HK_VolumeDown)) { - dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Down); + dsi->I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Down); } - else if (Input::HotkeyReleased(HK_VolumeDown)) + else if (emuInstance->hotkeyReleased(HK_VolumeDown)) { - dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Down); + dsi->I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Down); } - dsi.I2C.GetBPTWL()->ProcessVolumeSwitchInput(currentTime); + dsi->I2C.GetBPTWL()->ProcessVolumeSwitchInput(currentTime); } + if (useOpenGL) + emuInstance->makeCurrentGL(); + // update render settings if needed - // HACK: - // once the fast forward hotkey is released, we need to update vsync - // to the old setting again - if (videoSettingsDirty || Input::HotkeyReleased(HK_FastForward)) + if (videoSettingsDirty) { - if (screenGL) + emuInstance->renderLock.lock(); + if (useOpenGL) { - screenGL->setSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); - videoRenderer = Config::_3DRenderer; + emuInstance->setVSyncGL(true); + videoRenderer = globalCfg.GetInt("3D.Renderer"); } #ifdef OGLRENDERER_ENABLED else @@ -453,41 +244,35 @@ void EmuThread::run() videoRenderer = 0; } - videoRenderer = screenGL ? Config::_3DRenderer : 0; + updateRenderer(); videoSettingsDirty = false; - - if (videoRenderer == 0) - { // If we're using the software renderer... - NDS->GPU.SetRenderer3D(std::make_unique(Config::Threaded3D != 0)); - } - else - { - auto glrenderer = melonDS::GLRenderer::New(); - glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor); - NDS->GPU.SetRenderer3D(std::move(glrenderer)); - } + emuInstance->renderLock.unlock(); } // process input and hotkeys - NDS->SetKeyMask(Input::InputMask); + emuInstance->nds->SetKeyMask(emuInstance->inputMask); - if (Input::HotkeyPressed(HK_Lid)) + if (emuInstance->isTouching) + emuInstance->nds->TouchScreen(emuInstance->touchX, emuInstance->touchY); + else + emuInstance->nds->ReleaseScreen(); + + if (emuInstance->hotkeyPressed(HK_Lid)) { - bool lid = !NDS->IsLidClosed(); - NDS->SetLidClosed(lid); - mainWindow->osdAddMessage(0, lid ? "Lid closed" : "Lid opened"); + bool lid = !emuInstance->nds->IsLidClosed(); + emuInstance->nds->SetLidClosed(lid); + emuInstance->osdAddMessage(0, lid ? "Lid closed" : "Lid opened"); } // microphone input - AudioInOut::MicProcess(*NDS); + emuInstance->micProcess(); // auto screen layout - if (Config::ScreenSizing == Frontend::screenSizing_Auto) { mainScreenPos[2] = mainScreenPos[1]; mainScreenPos[1] = mainScreenPos[0]; - mainScreenPos[0] = NDS->PowerControl9 >> 15; + mainScreenPos[0] = emuInstance->nds->PowerControl9 >> 15; int guess; if (mainScreenPos[0] == mainScreenPos[2] && @@ -495,98 +280,123 @@ void EmuThread::run() { // constant flickering, likely displaying 3D on both screens // TODO: when both screens are used for 2D only...??? - guess = Frontend::screenSizing_Even; + guess = screenSizing_Even; } else { if (mainScreenPos[0] == 1) - guess = Frontend::screenSizing_EmphTop; + guess = screenSizing_EmphTop; else - guess = Frontend::screenSizing_EmphBot; + guess = screenSizing_EmphBot; } if (guess != autoScreenSizing) { autoScreenSizing = guess; - emit screenLayoutChange(); + emit autoScreenSizingChange(autoScreenSizing); } } // emulate - u32 nlines = NDS->RunFrame(); - - if (ROMManager::NDSSave) - ROMManager::NDSSave->CheckFlush(); - - if (ROMManager::GBASave) - ROMManager::GBASave->CheckFlush(); - - if (ROMManager::FirmwareSave) - ROMManager::FirmwareSave->CheckFlush(); - - if (!screenGL) + u32 nlines; + if (emuInstance->nds->GPU.GetRenderer3D().NeedsShaderCompile()) { - FrontBufferLock.lock(); - FrontBuffer = NDS->GPU.FrontBuffer; - FrontBufferLock.unlock(); + compileShaders(); + nlines = 1; } else { - FrontBuffer = NDS->GPU.FrontBuffer; - screenGL->drawScreenGL(); + nlines = emuInstance->nds->RunFrame(); + } + + if (emuInstance->ndsSave) + emuInstance->ndsSave->CheckFlush(); + + if (emuInstance->gbaSave) + emuInstance->gbaSave->CheckFlush(); + + if (emuInstance->firmwareSave) + emuInstance->firmwareSave->CheckFlush(); + + if (!useOpenGL) + { + frontBufferLock.lock(); + frontBuffer = emuInstance->nds->GPU.FrontBuffer; + frontBufferLock.unlock(); + } + else + { + frontBuffer = emuInstance->nds->GPU.FrontBuffer; + emuInstance->drawScreenGL(); } #ifdef MELONCAP MelonCap::Update(); #endif // MELONCAP - if (EmuRunning == emuStatus_Exit) break; - winUpdateCount++; - if (winUpdateCount >= winUpdateFreq && !screenGL) + if (winUpdateCount >= winUpdateFreq && !useOpenGL) { emit windowUpdate(); winUpdateCount = 0; } + + if (emuInstance->hotkeyPressed(HK_FastForwardToggle)) emuInstance->fastForwardToggled = !emuInstance->fastForwardToggled; + if (emuInstance->hotkeyPressed(HK_SlowMoToggle)) emuInstance->slowmoToggled = !emuInstance->slowmoToggled; - bool fastforward = Input::HotkeyDown(HK_FastForward); + bool enablefastforward = emuInstance->hotkeyDown(HK_FastForward) | emuInstance->fastForwardToggled; + bool enableslowmo = emuInstance->hotkeyDown(HK_SlowMo) | emuInstance->slowmoToggled; - if (fastforward && screenGL && Config::ScreenVSync) + if (useOpenGL) { - screenGL->setSwapInterval(0); + // when using OpenGL: when toggling fast-forward or slowmo, change the vsync interval + if ((enablefastforward || enableslowmo) && !(fastforward || slowmo)) + { + emuInstance->setVSyncGL(false); + } + else if (!(enablefastforward || enableslowmo) && (fastforward || slowmo)) + { + emuInstance->setVSyncGL(true); + } } - if (Config::DSiVolumeSync && NDS->ConsoleType == 1) + fastforward = enablefastforward; + slowmo = enableslowmo; + + if (slowmo) emuInstance->curFPS = emuInstance->slowmoFPS; + else if (fastforward) emuInstance->curFPS = emuInstance->fastForwardFPS; + else if (!emuInstance->doLimitFPS) emuInstance->curFPS = 1000.0; + else emuInstance->curFPS = emuInstance->targetFPS; + + if (emuInstance->audioDSiVolumeSync && emuInstance->nds->ConsoleType == 1) { - DSi& dsi = static_cast(*NDS); - u8 volumeLevel = dsi.I2C.GetBPTWL()->GetVolumeLevel(); + DSi* dsi = static_cast(emuInstance->nds); + u8 volumeLevel = dsi->I2C.GetBPTWL()->GetVolumeLevel(); if (volumeLevel != dsiVolumeLevel) { dsiVolumeLevel = volumeLevel; emit syncVolumeLevel(); } - Config::AudioVolume = volumeLevel * (256.0 / 31.0); + emuInstance->audioVolume = volumeLevel * (256.0 / 31.0); } - if (Config::AudioSync && !fastforward) - AudioInOut::AudioSync(*this->NDS); + if (emuInstance->doAudioSync && !(fastforward || slowmo)) + emuInstance->audioSync(); - double frametimeStep = nlines / (60.0 * 263.0); + double frametimeStep = nlines / (emuInstance->curFPS * 263.0); + + if (frametimeStep < 0.001) frametimeStep = 0.001; { - bool limitfps = Config::LimitFPS && !fastforward; - - double practicalFramelimit = limitfps ? frametimeStep : 1.0 / 1000.0; - double curtime = SDL_GetPerformanceCounter() * perfCountsSec; - frameLimitError += practicalFramelimit - (curtime - lastTime); - if (frameLimitError < -practicalFramelimit) - frameLimitError = -practicalFramelimit; - if (frameLimitError > practicalFramelimit) - frameLimitError = practicalFramelimit; + frameLimitError += frametimeStep - (curtime - lastTime); + if (frameLimitError < -frametimeStep) + frameLimitError = -frametimeStep; + if (frameLimitError > frametimeStep) + frameLimitError = frametimeStep; if (round(frameLimitError * 1000.0) > 0.0) { @@ -614,10 +424,11 @@ void EmuThread::run() winUpdateFreq = fps / (u32)round(fpstarget); if (winUpdateFreq < 1) winUpdateFreq = 1; - - int inst = Platform::InstanceID(); + + double actualfps = (59.8261 * 263.0) / nlines; + int inst = emuInstance->instanceID; if (inst == 0) - sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); + sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, actualfps); else sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1); changeWindowTitle(melontitle); @@ -632,9 +443,7 @@ void EmuThread::run() emit windowUpdate(); - EmuStatus = EmuRunning; - - int inst = Platform::InstanceID(); + int inst = emuInstance->instanceID; if (inst == 0) sprintf(melontitle, "melonDS " MELONDS_VERSION); else @@ -643,38 +452,220 @@ void EmuThread::run() SDL_Delay(75); - if (screenGL) - screenGL->drawScreenGL(); - - ContextRequestKind contextRequest = ContextRequest; - if (contextRequest == contextRequest_InitGL) + if (useOpenGL) { - screenGL = static_cast(mainWindow->panel); - screenGL->initOpenGL(); - ContextRequest = contextRequest_None; - } - else if (contextRequest == contextRequest_DeInitGL) - { - screenGL->deinitOpenGL(); - screenGL = nullptr; - ContextRequest = contextRequest_None; + emuInstance->drawScreenGL(); } } - } - file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write); - if (file) + handleMessages(); + } +} + +void EmuThread::sendMessage(Message msg) +{ + msgMutex.lock(); + msgQueue.enqueue(msg); + msgMutex.unlock(); +} + +void EmuThread::waitMessage(int num) +{ + if (QThread::currentThread() == this) return; + msgSemaphore.acquire(num); +} + +void EmuThread::waitAllMessages() +{ + if (QThread::currentThread() == this) return; + while (!msgQueue.empty()) + msgSemaphore.acquire(); +} + +void EmuThread::handleMessages() +{ + msgMutex.lock(); + while (!msgQueue.empty()) { - RTC::StateData state; - NDS->RTC.GetState(state); - Platform::FileWrite(&state, sizeof(state), 1, file); - Platform::CloseFile(file); + Message msg = msgQueue.dequeue(); + switch (msg.type) + { + case msg_Exit: + emuStatus = emuStatus_Exit; + emuPauseStack = emuPauseStackRunning; + + emuInstance->audioDisable(); + MPInterface::Get().End(emuInstance->instanceID); + break; + + case msg_EmuRun: + emuStatus = emuStatus_Running; + emuPauseStack = emuPauseStackRunning; + emuActive = true; + + emuInstance->audioEnable(); + emit windowEmuStart(); + break; + + case msg_EmuPause: + emuPauseStack++; + if (emuPauseStack > emuPauseStackPauseThreshold) break; + + prevEmuStatus = emuStatus; + emuStatus = emuStatus_Paused; + + if (prevEmuStatus != emuStatus_Paused) + { + emuInstance->audioDisable(); + emit windowEmuPause(true); + emuInstance->osdAddMessage(0, "Paused"); + } + break; + + case msg_EmuUnpause: + if (emuPauseStack < emuPauseStackPauseThreshold) break; + + emuPauseStack--; + if (emuPauseStack >= emuPauseStackPauseThreshold) break; + + emuStatus = prevEmuStatus; + + if (emuStatus != emuStatus_Paused) + { + emuInstance->audioEnable(); + emit windowEmuPause(false); + emuInstance->osdAddMessage(0, "Resumed"); + } + break; + + case msg_EmuStop: + if (msg.param.value()) + emuInstance->nds->Stop(); + emuStatus = emuStatus_Paused; + emuActive = false; + + emuInstance->audioDisable(); + emit windowEmuStop(); + break; + + case msg_EmuFrameStep: + emuStatus = emuStatus_FrameStep; + break; + + case msg_EmuReset: + emuInstance->reset(); + + emuStatus = emuStatus_Running; + emuPauseStack = emuPauseStackRunning; + emuActive = true; + + emuInstance->audioEnable(); + emit windowEmuReset(); + emuInstance->osdAddMessage(0, "Reset"); + break; + + case msg_InitGL: + emuInstance->initOpenGL(msg.param.value()); + useOpenGL = true; + break; + + case msg_DeInitGL: + emuInstance->deinitOpenGL(msg.param.value()); + if (msg.param.value() == 0) + useOpenGL = false; + break; + + case msg_BootROM: + msgResult = 0; + if (!emuInstance->loadROM(msg.param.value(), true)) + break; + + assert(emuInstance->nds != nullptr); + emuInstance->nds->Start(); + msgResult = 1; + break; + + case msg_BootFirmware: + msgResult = 0; + if (!emuInstance->bootToMenu()) + break; + + assert(emuInstance->nds != nullptr); + emuInstance->nds->Start(); + msgResult = 1; + break; + + case msg_InsertCart: + msgResult = 0; + if (!emuInstance->loadROM(msg.param.value(), false)) + break; + + msgResult = 1; + break; + + case msg_EjectCart: + emuInstance->ejectCart(); + break; + + case msg_InsertGBACart: + msgResult = 0; + if (!emuInstance->loadGBAROM(msg.param.value())) + break; + + msgResult = 1; + break; + + case msg_InsertGBAAddon: + msgResult = 0; + emuInstance->loadGBAAddon(msg.param.value()); + msgResult = 1; + break; + + case msg_EjectGBACart: + emuInstance->ejectGBACart(); + break; + + case msg_SaveState: + msgResult = emuInstance->saveState(msg.param.value().toStdString()); + break; + + case msg_LoadState: + msgResult = emuInstance->loadState(msg.param.value().toStdString()); + break; + + case msg_UndoStateLoad: + emuInstance->undoStateLoad(); + msgResult = 1; + break; + + case msg_ImportSavefile: + { + msgResult = 0; + auto f = Platform::OpenFile(msg.param.value().toStdString(), Platform::FileMode::Read); + if (!f) break; + + u32 len = FileLength(f); + + std::unique_ptr data = std::make_unique(len); + Platform::FileRewind(f); + Platform::FileRead(data.get(), len, 1, f); + + assert(emuInstance->nds != nullptr); + emuInstance->nds->SetNDSSave(data.get(), len); + + CloseFile(f); + msgResult = 1; + } + break; + + case msg_EnableCheats: + emuInstance->enableCheats(msg.param.value()); + break; + } + + msgSemaphore.release(); } - - EmuStatus = emuStatus_Exit; - - NDS::Current = nullptr; - // nds is out of scope, so unique_ptr cleans it up for us + msgMutex.unlock(); } void EmuThread::changeWindowTitle(char* title) @@ -682,73 +673,219 @@ void EmuThread::changeWindowTitle(char* title) emit windowTitleChange(QString(title)); } +void EmuThread::initContext(int win) +{ + sendMessage({.type = msg_InitGL, .param = win}); + waitMessage(); +} + +void EmuThread::deinitContext(int win) +{ + sendMessage({.type = msg_DeInitGL, .param = win}); + waitMessage(); +} + void EmuThread::emuRun() { - EmuRunning = emuStatus_Running; - EmuPauseStack = EmuPauseStackRunning; - RunningSomething = true; - - // checkme - emit windowEmuStart(); - AudioInOut::Enable(); + sendMessage(msg_EmuRun); + waitMessage(); } -void EmuThread::initContext() +void EmuThread::emuPause(bool broadcast) { - ContextRequest = contextRequest_InitGL; - while (ContextRequest != contextRequest_None); + sendMessage(msg_EmuPause); + waitMessage(); + + if (broadcast) + emuInstance->broadcastCommand(InstCmd_Pause); } -void EmuThread::deinitContext() +void EmuThread::emuUnpause(bool broadcast) { - ContextRequest = contextRequest_DeInitGL; - while (ContextRequest != contextRequest_None); + sendMessage(msg_EmuUnpause); + waitMessage(); + + if (broadcast) + emuInstance->broadcastCommand(InstCmd_Unpause); } -void EmuThread::emuPause() +void EmuThread::emuTogglePause(bool broadcast) { - EmuPauseStack++; - if (EmuPauseStack > EmuPauseStackPauseThreshold) return; - - PrevEmuStatus = EmuRunning; - EmuRunning = emuStatus_Paused; - while (EmuStatus != emuStatus_Paused); - - AudioInOut::Disable(); + if (emuStatus == emuStatus_Paused) + emuUnpause(broadcast); + else + emuPause(broadcast); } -void EmuThread::emuUnpause() +void EmuThread::emuStop(bool external) { - if (EmuPauseStack < EmuPauseStackPauseThreshold) return; - - EmuPauseStack--; - if (EmuPauseStack >= EmuPauseStackPauseThreshold) return; - - EmuRunning = PrevEmuStatus; - - AudioInOut::Enable(); + sendMessage({.type = msg_EmuStop, .param = external}); + waitMessage(); } -void EmuThread::emuStop() +void EmuThread::emuExit() { - EmuRunning = emuStatus_Exit; - EmuPauseStack = EmuPauseStackRunning; - - AudioInOut::Disable(); + sendMessage(msg_Exit); + waitAllMessages(); } void EmuThread::emuFrameStep() { - if (EmuPauseStack < EmuPauseStackPauseThreshold) emit windowEmuPause(); - EmuRunning = emuStatus_FrameStep; + if (emuPauseStack < emuPauseStackPauseThreshold) + sendMessage(msg_EmuPause); + sendMessage(msg_EmuFrameStep); + waitAllMessages(); +} + +void EmuThread::emuReset() +{ + sendMessage(msg_EmuReset); + waitMessage(); } bool EmuThread::emuIsRunning() { - return EmuRunning == emuStatus_Running; + return emuStatus == emuStatus_Running; } bool EmuThread::emuIsActive() { - return (RunningSomething == 1); + return emuActive; +} + +int EmuThread::bootROM(const QStringList& filename) +{ + sendMessage({.type = msg_BootROM, .param = filename}); + waitMessage(); + if (!msgResult) + return msgResult; + + sendMessage(msg_EmuRun); + waitMessage(); + return msgResult; +} + +int EmuThread::bootFirmware() +{ + sendMessage(msg_BootFirmware); + waitMessage(); + if (!msgResult) + return msgResult; + + sendMessage(msg_EmuRun); + waitMessage(); + return msgResult; +} + +int EmuThread::insertCart(const QStringList& filename, bool gba) +{ + MessageType msgtype = gba ? msg_InsertGBACart : msg_InsertCart; + + sendMessage({.type = msgtype, .param = filename}); + waitMessage(); + return msgResult; +} + +void EmuThread::ejectCart(bool gba) +{ + sendMessage(gba ? msg_EjectGBACart : msg_EjectCart); + waitMessage(); +} + +int EmuThread::insertGBAAddon(int type) +{ + sendMessage({.type = msg_InsertGBAAddon, .param = type}); + waitMessage(); + return msgResult; +} + +int EmuThread::saveState(const QString& filename) +{ + sendMessage({.type = msg_SaveState, .param = filename}); + waitMessage(); + return msgResult; +} + +int EmuThread::loadState(const QString& filename) +{ + sendMessage({.type = msg_LoadState, .param = filename}); + waitMessage(); + return msgResult; +} + +int EmuThread::undoStateLoad() +{ + sendMessage(msg_UndoStateLoad); + waitMessage(); + return msgResult; +} + +int EmuThread::importSavefile(const QString& filename) +{ + sendMessage(msg_EmuReset); + sendMessage({.type = msg_ImportSavefile, .param = filename}); + waitMessage(2); + return msgResult; +} + +void EmuThread::enableCheats(bool enable) +{ + sendMessage({.type = msg_EnableCheats, .param = enable}); + waitMessage(); +} + +void EmuThread::updateRenderer() +{ + if (videoRenderer != lastVideoRenderer) + { + switch (videoRenderer) + { + case renderer3D_Software: + emuInstance->nds->GPU.SetRenderer3D(std::make_unique()); + break; + case renderer3D_OpenGL: + emuInstance->nds->GPU.SetRenderer3D(GLRenderer::New()); + break; + case renderer3D_OpenGLCompute: + emuInstance->nds->GPU.SetRenderer3D(ComputeRenderer::New()); + break; + default: __builtin_unreachable(); + } + } + lastVideoRenderer = videoRenderer; + + auto& cfg = emuInstance->getGlobalConfig(); + switch (videoRenderer) + { + case renderer3D_Software: + static_cast(emuInstance->nds->GPU.GetRenderer3D()).SetThreaded( + cfg.GetBool("3D.Soft.Threaded"), + emuInstance->nds->GPU); + break; + case renderer3D_OpenGL: + static_cast(emuInstance->nds->GPU.GetRenderer3D()).SetRenderSettings( + cfg.GetBool("3D.GL.BetterPolygons"), + cfg.GetInt("3D.GL.ScaleFactor")); + break; + case renderer3D_OpenGLCompute: + static_cast(emuInstance->nds->GPU.GetRenderer3D()).SetRenderSettings( + cfg.GetInt("3D.GL.ScaleFactor"), + cfg.GetBool("3D.GL.HiresCoordinates")); + break; + default: __builtin_unreachable(); + } +} + +void EmuThread::compileShaders() +{ + int currentShader, shadersCount; + u64 startTime = SDL_GetPerformanceCounter(); + // kind of hacky to look at the wallclock, though it is easier than + // than disabling vsync + do + { + emuInstance->nds->GPU.GetRenderer3D().ShaderCompileStep(currentShader, shadersCount); + } while (emuInstance->nds->GPU.GetRenderer3D().NeedsShaderCompile() && + (SDL_GetPerformanceCounter() - startTime) * perfCountsSec < 1.0 / 6.0); + emuInstance->osdAddMessage(0, "Compiling shader %d/%d", currentShader+1, shadersCount); } diff --git a/src/frontend/qt_sdl/EmuThread.h b/src/frontend/qt_sdl/EmuThread.h index 4950ebbf..f28c0604 100644 --- a/src/frontend/qt_sdl/EmuThread.h +++ b/src/frontend/qt_sdl/EmuThread.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -21,10 +21,14 @@ #include #include +#include +#include +#include #include #include #include +#include #include "NDSCart.h" #include "GBACart.h" @@ -37,6 +41,8 @@ namespace melonDS class NDS; } +class EmuInstance; +class MainWindow; class ScreenPanelGL; class EmuThread : public QThread @@ -45,46 +51,106 @@ class EmuThread : public QThread void run() override; public: - explicit EmuThread(QObject* parent = nullptr); + explicit EmuThread(EmuInstance* inst, QObject* parent = nullptr); + + void attachWindow(MainWindow* window); + void detachWindow(MainWindow* window); + + enum MessageType + { + msg_Exit, + + msg_EmuRun, + msg_EmuPause, + msg_EmuUnpause, + msg_EmuStop, + msg_EmuFrameStep, + msg_EmuReset, + + msg_InitGL, + msg_DeInitGL, + + msg_BootROM, + msg_BootFirmware, + msg_InsertCart, + msg_EjectCart, + msg_InsertGBACart, + msg_InsertGBAAddon, + msg_EjectGBACart, + + msg_LoadState, + msg_SaveState, + msg_UndoStateLoad, + + msg_ImportSavefile, + + msg_EnableCheats, + }; + + struct Message + { + MessageType type; + QVariant param; + }; + + void sendMessage(Message msg); + void waitMessage(int num = 1); + void waitAllMessages(); + + void sendMessage(MessageType type) + { + return sendMessage({.type = type}); + } void changeWindowTitle(char* title); // to be called from the UI thread void emuRun(); - void emuPause(); - void emuUnpause(); - void emuStop(); + void emuPause(bool broadcast = true); + void emuUnpause(bool broadcast = true); + void emuTogglePause(bool broadcast = true); + void emuStop(bool external); + void emuExit(); void emuFrameStep(); + void emuReset(); + + int bootROM(const QStringList& filename); + int bootFirmware(); + int insertCart(const QStringList& filename, bool gba); + void ejectCart(bool gba); + int insertGBAAddon(int type); + + int saveState(const QString& filename); + int loadState(const QString& filename); + int undoStateLoad(); + + int importSavefile(const QString& filename); + + void enableCheats(bool enable); bool emuIsRunning(); bool emuIsActive(); - void initContext(); - void deinitContext(); + void initContext(int win); + void deinitContext(int win); + void updateVideoSettings() { videoSettingsDirty = true; } + void updateVideoRenderer() { videoSettingsDirty = true; lastVideoRenderer = -1; } - int FrontBuffer = 0; - QMutex FrontBufferLock; + int frontBuffer = 0; + QMutex frontBufferLock; - /// Applies the config in args. - /// Creates a new NDS console if needed, - /// modifies the existing one if possible. - /// @return \c true if the console was updated. - /// If this returns \c false, then the existing NDS console is not modified. - bool UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept; - std::unique_ptr NDS; // TODO: Proper encapsulation and synchronization signals: void windowUpdate(); void windowTitleChange(QString title); void windowEmuStart(); void windowEmuStop(); - void windowEmuPause(); + void windowEmuPause(bool pause); void windowEmuReset(); - void windowEmuFrameStep(); void windowLimitFPSChange(); - void screenLayoutChange(); + void autoScreenSizingChange(int sizing); void windowFullscreenToggle(); @@ -94,10 +160,10 @@ signals: void syncVolumeLevel(); private: - std::unique_ptr CreateConsole( - std::unique_ptr&& ndscart, - std::unique_ptr&& gbacart - ) noexcept; + void handleMessages(); + + void updateRenderer(); + void compileShaders(); enum EmuStatusKind { @@ -106,27 +172,30 @@ private: emuStatus_Paused, emuStatus_FrameStep, }; - std::atomic EmuStatus; - EmuStatusKind PrevEmuStatus; - EmuStatusKind EmuRunning; + EmuStatusKind prevEmuStatus; + EmuStatusKind emuStatus; + bool emuActive; - constexpr static int EmuPauseStackRunning = 0; - constexpr static int EmuPauseStackPauseThreshold = 1; - int EmuPauseStack; + constexpr static int emuPauseStackRunning = 0; + constexpr static int emuPauseStackPauseThreshold = 1; + int emuPauseStack; - enum ContextRequestKind - { - contextRequest_None = 0, - contextRequest_InitGL, - contextRequest_DeInitGL - }; - std::atomic ContextRequest = contextRequest_None; + int msgResult = 0; - ScreenPanelGL* screenGL; + QMutex msgMutex; + QSemaphore msgSemaphore; + QQueue msgQueue; + + EmuInstance* emuInstance; int autoScreenSizing; + int lastVideoRenderer = -1; + + double perfCountsSec; + + bool useOpenGL; int videoRenderer; bool videoSettingsDirty; }; diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp index 1ec2e8c4..1f71b5b9 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -20,6 +20,7 @@ #include "Platform.h" #include "Config.h" +#include "main.h" #include "FirmwareSettingsDialog.h" #include "ui_FirmwareSettingsDialog.h" @@ -29,8 +30,6 @@ namespace Platform = melonDS::Platform; FirmwareSettingsDialog* FirmwareSettingsDialog::currentDlg = nullptr; -extern bool RunningSomething; - bool FirmwareSettingsDialog::needsReset = false; FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::FirmwareSettingsDialog) @@ -38,10 +37,15 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - ui->usernameEdit->setText(QString::fromStdString(Config::FirmwareUsername)); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + auto& cfg = emuInstance->getLocalConfig(); + auto firmcfg = cfg.GetTable("Firmware"); + + ui->usernameEdit->setText(firmcfg.GetQString("Username")); ui->languageBox->addItems(languages); - ui->languageBox->setCurrentIndex(Config::FirmwareLanguage); + ui->languageBox->setCurrentIndex(firmcfg.GetInt("Language")); for (int i = 1; i <= 31; i++) { @@ -49,9 +53,9 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent } ui->cbxBirthdayMonth->addItems(months); - ui->cbxBirthdayMonth->setCurrentIndex(Config::FirmwareBirthdayMonth - 1); + ui->cbxBirthdayMonth->setCurrentIndex(firmcfg.GetInt("BirthdayMonth") - 1); - ui->cbxBirthdayDay->setCurrentIndex(Config::FirmwareBirthdayDay - 1); + ui->cbxBirthdayDay->setCurrentIndex(firmcfg.GetInt("BirthdayDay") - 1); for (int i = 0; i < 16; i++) { @@ -60,21 +64,29 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent QIcon icon(QPixmap::fromImage(image.copy())); ui->colorsEdit->addItem(icon, colornames[i]); } - ui->colorsEdit->setCurrentIndex(Config::FirmwareFavouriteColour); + ui->colorsEdit->setCurrentIndex(firmcfg.GetInt("FavouriteColour")); - ui->messageEdit->setText(QString::fromStdString(Config::FirmwareMessage)); + ui->messageEdit->setText(firmcfg.GetQString("Message")); - ui->overrideFirmwareBox->setChecked(Config::FirmwareOverrideSettings); + ui->overrideFirmwareBox->setChecked(firmcfg.GetBool("OverrideSettings")); - ui->txtMAC->setText(QString::fromStdString(Config::FirmwareMAC)); + ui->txtMAC->setText(firmcfg.GetQString("MAC")); - on_overrideFirmwareBox_toggled(); - - int inst = Platform::InstanceID(); + int inst = emuInstance->getInstanceID(); if (inst > 0) ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1)); else ui->lblInstanceNum->hide(); + +#define SET_ORIGVAL(type, val) \ + for (type* w : findChildren(nullptr)) \ + w->setProperty("user_originalValue", w->val()); + + SET_ORIGVAL(QLineEdit, text); + SET_ORIGVAL(QComboBox, currentIndex); + SET_ORIGVAL(QCheckBox, isChecked); + +#undef SET_ORIGVAL } FirmwareSettingsDialog::~FirmwareSettingsDialog() @@ -120,6 +132,13 @@ bool FirmwareSettingsDialog::verifyMAC() void FirmwareSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + needsReset = false; if (r == QDialog::Accepted) @@ -132,42 +151,46 @@ void FirmwareSettingsDialog::done(int r) return; } - bool newOverride = ui->overrideFirmwareBox->isChecked(); + bool modified = false; - std::string newName = ui->usernameEdit->text().toStdString(); - int newLanguage = ui->languageBox->currentIndex(); - int newFavColor = ui->colorsEdit->currentIndex(); - int newBirthdayDay = ui->cbxBirthdayDay->currentIndex() + 1; - int newBirthdayMonth = ui->cbxBirthdayMonth->currentIndex() + 1; - std::string newMessage = ui->messageEdit->text().toStdString(); +#define CHECK_ORIGVAL(type, val) \ + if (!modified) for (type* w : findChildren(nullptr)) \ + { \ + QVariant v = w->val(); \ + if (v != w->property("user_originalValue")) \ + { \ + modified = true; \ + break; \ + }\ + } - std::string newMAC = ui->txtMAC->text().toStdString(); + CHECK_ORIGVAL(QLineEdit, text); + CHECK_ORIGVAL(QComboBox, currentIndex); + CHECK_ORIGVAL(QCheckBox, isChecked); - if ( newOverride != Config::FirmwareOverrideSettings - || newName != Config::FirmwareUsername - || newLanguage != Config::FirmwareLanguage - || newFavColor != Config::FirmwareFavouriteColour - || newBirthdayDay != Config::FirmwareBirthdayDay - || newBirthdayMonth != Config::FirmwareBirthdayMonth - || newMessage != Config::FirmwareMessage - || newMAC != Config::FirmwareMAC) +#undef CHECK_ORIGVAL + + if (modified) { - if (RunningSomething + if (emuInstance->emuIsActive() && QMessageBox::warning(this, "Reset necessary to apply changes", "The emulation will be reset for the changes to take place.", QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) return; - Config::FirmwareOverrideSettings = newOverride; + auto& cfg = emuInstance->getLocalConfig(); + auto firmcfg = cfg.GetTable("Firmware"); - Config::FirmwareUsername = newName; - Config::FirmwareLanguage = newLanguage; - Config::FirmwareFavouriteColour = newFavColor; - Config::FirmwareBirthdayDay = newBirthdayDay; - Config::FirmwareBirthdayMonth = newBirthdayMonth; - Config::FirmwareMessage = newMessage; + firmcfg.SetBool("OverrideSettings", ui->overrideFirmwareBox->isChecked()); - Config::FirmwareMAC = newMAC; + firmcfg.SetQString("Username", ui->usernameEdit->text()); + firmcfg.SetInt("Language", ui->languageBox->currentIndex()); + firmcfg.SetInt("FavouriteColour", ui->colorsEdit->currentIndex()); + firmcfg.SetInt("BirthdayDay", ui->cbxBirthdayDay->currentIndex() + 1); + firmcfg.SetInt("BirthdayMonth", ui->cbxBirthdayMonth->currentIndex() + 1); + firmcfg.SetQString("Message", ui->messageEdit->text()); + + firmcfg.SetQString("MAC", ui->txtMAC->text()); Config::Save(); @@ -207,10 +230,3 @@ void FirmwareSettingsDialog::on_cbxBirthdayMonth_currentIndexChanged(int idx) } } } - -void FirmwareSettingsDialog::on_overrideFirmwareBox_toggled() -{ - bool disable = !ui->overrideFirmwareBox->isChecked(); - ui->grpUserSettings->setDisabled(disable); - ui->grpWifiSettings->setDisabled(disable); -} diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.h b/src/frontend/qt_sdl/FirmwareSettingsDialog.h index d22ce3a2..2053ab99 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.h +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -25,6 +25,8 @@ namespace Ui { class FirmwareSettingsDialog; } class FirmwareSettingsDialog; +class EmuInstance; + class FirmwareSettingsDialog : public QDialog { Q_OBJECT @@ -123,12 +125,12 @@ private slots: void done(int r); void on_cbxBirthdayMonth_currentIndexChanged(int idx); - void on_overrideFirmwareBox_toggled(); private: bool verifyMAC(); Ui::FirmwareSettingsDialog* ui; + EmuInstance* emuInstance; }; #endif // FIRMWARESETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/Input.cpp b/src/frontend/qt_sdl/Input.cpp deleted file mode 100644 index c429cd36..00000000 --- a/src/frontend/qt_sdl/Input.cpp +++ /dev/null @@ -1,254 +0,0 @@ -/* - Copyright 2016-2023 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 "Input.h" -#include "Config.h" - -using namespace melonDS; - -namespace Input -{ - -int JoystickID; -SDL_Joystick* Joystick = nullptr; - -u32 KeyInputMask, JoyInputMask; -u32 KeyHotkeyMask, JoyHotkeyMask; -u32 HotkeyMask, LastHotkeyMask; -u32 HotkeyPress, HotkeyRelease; - -u32 InputMask; - - -void Init() -{ - KeyInputMask = 0xFFF; - JoyInputMask = 0xFFF; - InputMask = 0xFFF; - - KeyHotkeyMask = 0; - JoyHotkeyMask = 0; - HotkeyMask = 0; - LastHotkeyMask = 0; -} - - -void OpenJoystick() -{ - if (Joystick) SDL_JoystickClose(Joystick); - - int num = SDL_NumJoysticks(); - if (num < 1) - { - Joystick = nullptr; - return; - } - - if (JoystickID >= num) - JoystickID = 0; - - Joystick = SDL_JoystickOpen(JoystickID); -} - -void CloseJoystick() -{ - if (Joystick) - { - SDL_JoystickClose(Joystick); - Joystick = nullptr; - } -} - - -int GetEventKeyVal(QKeyEvent* event) -{ - int key = event->key(); - int mod = event->modifiers(); - bool ismod = (key == Qt::Key_Control || - key == Qt::Key_Alt || - key == Qt::Key_AltGr || - key == Qt::Key_Shift || - key == Qt::Key_Meta); - - if (!ismod) - key |= mod; - else if (Input::IsRightModKey(event)) - key |= (1<<31); - - return key; -} - -void KeyPress(QKeyEvent* event) -{ - int keyHK = GetEventKeyVal(event); - int keyKP = keyHK; - if (event->modifiers() != Qt::KeypadModifier) - keyKP &= ~event->modifiers(); - - for (int i = 0; i < 12; i++) - if (keyKP == Config::KeyMapping[i]) - KeyInputMask &= ~(1<modifiers() != Qt::KeypadModifier) - keyKP &= ~event->modifiers(); - - for (int i = 0; i < 12; i++) - if (keyKP == Config::KeyMapping[i]) - KeyInputMask |= (1<> 4) & 0xF; - int hatdir = val & 0xF; - Uint8 hatval = SDL_JoystickGetHat(Joystick, hatnum); - - bool pressed = false; - if (hatdir == 0x1) pressed = (hatval & SDL_HAT_UP); - else if (hatdir == 0x4) pressed = (hatval & SDL_HAT_DOWN); - else if (hatdir == 0x2) pressed = (hatval & SDL_HAT_RIGHT); - else if (hatdir == 0x8) pressed = (hatval & SDL_HAT_LEFT); - - if (pressed) return true; - } - else - { - int btnnum = val & 0xFFFF; - Uint8 btnval = SDL_JoystickGetButton(Joystick, btnnum); - - if (btnval) return true; - } - } - - if (val & 0x10000) - { - int axisnum = (val >> 24) & 0xF; - int axisdir = (val >> 20) & 0xF; - Sint16 axisval = SDL_JoystickGetAxis(Joystick, axisnum); - - switch (axisdir) - { - case 0: // positive - if (axisval > 16384) return true; - break; - - case 1: // negative - if (axisval < -16384) return true; - break; - - case 2: // trigger - if (axisval > 0) return true; - break; - } - } - - return false; -} - -void Process() -{ - SDL_JoystickUpdate(); - - if (Joystick) - { - if (!SDL_JoystickGetAttached(Joystick)) - { - SDL_JoystickClose(Joystick); - Joystick = NULL; - } - } - if (!Joystick && (SDL_NumJoysticks() > 0)) - { - JoystickID = Config::JoystickID; - OpenJoystick(); - } - - JoyInputMask = 0xFFF; - for (int i = 0; i < 12; i++) - if (JoystickButtonDown(Config::JoyMapping[i])) - JoyInputMask &= ~(1<nativeScanCode(); - return (scan == 0x11D || scan == 0x138 || scan == 0x36); -} -#elif __APPLE__ -bool IsRightModKey(QKeyEvent* event) -{ - quint32 scan = event->nativeVirtualKey(); - return (scan == 0x36 || scan == 0x3C || scan == 0x3D || scan == 0x3E); -} -#else -bool IsRightModKey(QKeyEvent* event) -{ - quint32 scan = event->nativeScanCode(); - return (scan == 0x69 || scan == 0x6C || scan == 0x3E); -} -#endif - -} diff --git a/src/frontend/qt_sdl/Input.h b/src/frontend/qt_sdl/Input.h deleted file mode 100644 index 2dfa4a77..00000000 --- a/src/frontend/qt_sdl/Input.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - Copyright 2016-2023 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 INPUT_H -#define INPUT_H - -#include - -#include "types.h" - -namespace Input -{ - -using namespace melonDS; -extern int JoystickID; -extern SDL_Joystick* Joystick; - -extern u32 InputMask; - -void Init(); - -// set joystickID before calling openJoystick() -void OpenJoystick(); -void CloseJoystick(); - -void KeyPress(QKeyEvent* event); -void KeyRelease(QKeyEvent* event); - -void Process(); - -bool HotkeyDown(int id); -bool HotkeyPressed(int id); -bool HotkeyReleased(int id); - -bool IsRightModKey(QKeyEvent* event); - -} - -#endif // INPUT_H diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp index 02a76bb7..4428a515 100644 --- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp +++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -26,10 +26,9 @@ #include "types.h" #include "Platform.h" -#include "MapButton.h" -#include "Input.h" #include "InputConfigDialog.h" #include "ui_InputConfigDialog.h" +#include "MapButton.h" using namespace melonDS; @@ -43,31 +42,42 @@ InputConfigDialog::InputConfigDialog(QWidget* parent) : QDialog(parent), ui(new ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + Config::Table& instcfg = emuInstance->getLocalConfig(); + Config::Table keycfg = instcfg.GetTable("Keyboard"); + Config::Table joycfg = instcfg.GetTable("Joystick"); + for (int i = 0; i < keypad_num; i++) { - keypadKeyMap[i] = Config::KeyMapping[dskeyorder[i]]; - keypadJoyMap[i] = Config::JoyMapping[dskeyorder[i]]; + const char* btn = EmuInstance::buttonNames[dskeyorder[i]]; + keypadKeyMap[i] = keycfg.GetInt(btn); + keypadJoyMap[i] = joycfg.GetInt(btn); } int i = 0; for (int hotkey : hk_addons) { - addonsKeyMap[i] = Config::HKKeyMapping[hotkey]; - addonsJoyMap[i] = Config::HKJoyMapping[hotkey]; + const char* btn = EmuInstance::hotkeyNames[hotkey]; + addonsKeyMap[i] = keycfg.GetInt(btn); + addonsJoyMap[i] = joycfg.GetInt(btn); i++; } i = 0; for (int hotkey : hk_general) { - hkGeneralKeyMap[i] = Config::HKKeyMapping[hotkey]; - hkGeneralJoyMap[i] = Config::HKJoyMapping[hotkey]; + const char* btn = EmuInstance::hotkeyNames[hotkey]; + hkGeneralKeyMap[i] = keycfg.GetInt(btn); + hkGeneralJoyMap[i] = joycfg.GetInt(btn); i++; } populatePage(ui->tabAddons, hk_addons_labels, addonsKeyMap, addonsJoyMap); populatePage(ui->tabHotkeysGeneral, hk_general_labels, hkGeneralKeyMap, hkGeneralJoyMap); + joystickID = instcfg.GetInt("JoystickID"); + int njoy = SDL_NumJoysticks(); if (njoy > 0) { @@ -76,7 +86,7 @@ InputConfigDialog::InputConfigDialog(QWidget* parent) : QDialog(parent), ui(new const char* name = SDL_JoystickNameForIndex(i); ui->cbxJoystick->addItem(QString(name)); } - ui->cbxJoystick->setCurrentIndex(Input::JoystickID); + ui->cbxJoystick->setCurrentIndex(joystickID); } else { @@ -86,7 +96,7 @@ InputConfigDialog::InputConfigDialog(QWidget* parent) : QDialog(parent), ui(new setupKeypadPage(); - int inst = Platform::InstanceID(); + int inst = emuInstance->getInstanceID(); if (inst > 0) ui->lblInstanceNum->setText(QString("Configuring mappings for instance %1").arg(inst+1)); else @@ -174,38 +184,47 @@ void InputConfigDialog::populatePage(QWidget* page, void InputConfigDialog::on_InputConfigDialog_accepted() { + Config::Table& instcfg = emuInstance->getLocalConfig(); + Config::Table keycfg = instcfg.GetTable("Keyboard"); + Config::Table joycfg = instcfg.GetTable("Joystick"); + for (int i = 0; i < keypad_num; i++) { - Config::KeyMapping[dskeyorder[i]] = keypadKeyMap[i]; - Config::JoyMapping[dskeyorder[i]] = keypadJoyMap[i]; + const char* btn = EmuInstance::buttonNames[dskeyorder[i]]; + keycfg.SetInt(btn, keypadKeyMap[i]); + joycfg.SetInt(btn, keypadJoyMap[i]); } int i = 0; for (int hotkey : hk_addons) { - Config::HKKeyMapping[hotkey] = addonsKeyMap[i]; - Config::HKJoyMapping[hotkey] = addonsJoyMap[i]; + const char* btn = EmuInstance::hotkeyNames[hotkey]; + keycfg.SetInt(btn, addonsKeyMap[i]); + joycfg.SetInt(btn, addonsJoyMap[i]); i++; } i = 0; for (int hotkey : hk_general) { - Config::HKKeyMapping[hotkey] = hkGeneralKeyMap[i]; - Config::HKJoyMapping[hotkey] = hkGeneralJoyMap[i]; + const char* btn = EmuInstance::hotkeyNames[hotkey]; + keycfg.SetInt(btn, hkGeneralKeyMap[i]); + joycfg.SetInt(btn, hkGeneralJoyMap[i]); i++; } - Config::JoystickID = Input::JoystickID; + instcfg.SetInt("JoystickID", joystickID); Config::Save(); + emuInstance->inputLoadConfig(); + closeDlg(); } void InputConfigDialog::on_InputConfigDialog_rejected() { - Input::JoystickID = Config::JoystickID; - Input::OpenJoystick(); + Config::Table& instcfg = emuInstance->getLocalConfig(); + emuInstance->setJoystick(instcfg.GetInt("JoystickID")); closeDlg(); } @@ -225,6 +244,11 @@ void InputConfigDialog::on_cbxJoystick_currentIndexChanged(int id) // prevent a spurious change if (ui->cbxJoystick->count() < 2) return; - Input::JoystickID = id; - Input::OpenJoystick(); + joystickID = id; + emuInstance->setJoystick(id); +} + +SDL_Joystick* InputConfigDialog::getJoystick() +{ + return emuInstance->getJoystick(); } diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h index 8d4f882a..3337228f 100644 --- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h +++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -24,6 +24,7 @@ #include #include "Config.h" +#include "EmuInstance.h" static constexpr int keypad_num = 12; @@ -48,6 +49,9 @@ static constexpr std::initializer_list hk_general = HK_FrameStep, HK_FastForward, HK_FastForwardToggle, + HK_SlowMo, + HK_SlowMoToggle, + HK_FrameLimitToggle, HK_FullscreenToggle, HK_Lid, HK_Mic, @@ -64,6 +68,9 @@ static constexpr std::initializer_list hk_general_labels = "Reset", "Frame step", "Fast forward", + "Toggle fast forward", + "Slow mo", + "Toggle slow mo", "Toggle FPS limit", "Toggle fullscreen", "Close/open lid", @@ -89,6 +96,8 @@ public: explicit InputConfigDialog(QWidget* parent); ~InputConfigDialog(); + SDL_Joystick* getJoystick(); + static InputConfigDialog* currentDlg; static InputConfigDialog* openDlg(QWidget* parent) { @@ -123,9 +132,12 @@ private: Ui::InputConfigDialog* ui; + EmuInstance* emuInstance; + int keypadKeyMap[12], keypadJoyMap[12]; int addonsKeyMap[hk_addons.size()], addonsJoyMap[hk_addons.size()]; int hkGeneralKeyMap[hk_general.size()], hkGeneralJoyMap[hk_general.size()]; + int joystickID; }; diff --git a/src/frontend/qt_sdl/InputConfig/MapButton.h b/src/frontend/qt_sdl/InputConfig/MapButton.h index 5d4fb3eb..14bc3962 100644 --- a/src/frontend/qt_sdl/InputConfig/MapButton.h +++ b/src/frontend/qt_sdl/InputConfig/MapButton.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -23,8 +23,10 @@ #include -#include "Input.h" #include "Platform.h" +#include "EmuInstance.h" + +class InputConfigDialog; class KeyMapButton : public QPushButton { @@ -76,7 +78,7 @@ protected: if (!ismod) key |= mod; - else if (Input::IsRightModKey(event)) + else if (isRightModKey(event)) key |= (1<<31); *mapping = key; @@ -162,6 +164,9 @@ public: this->mapping = mapping; this->isHotkey = hotkey; + // the parent will be set later when this button is added to a layout + parentDialog = nullptr; + setCheckable(true); setText(mappingText()); setFocusPolicy(Qt::StrongFocus); //Fixes binding keys in macOS @@ -176,6 +181,20 @@ public: } protected: + void showEvent(QShowEvent* event) override + { + if (event->spontaneous()) return; + + QWidget* w = parentWidget(); + for (;;) + { + parentDialog = qobject_cast(w); + if (parentDialog) break; + w = w->parentWidget(); + if (!w) break; + } + } + void keyPressEvent(QKeyEvent* event) override { if (!isChecked()) return QPushButton::keyPressEvent(event); @@ -203,7 +222,7 @@ protected: void timerEvent(QTimerEvent* event) override { - SDL_Joystick* joy = Input::Joystick; + SDL_Joystick* joy = parentDialog->getJoystick(); if (!joy) { click(); return; } if (!SDL_JoystickGetAttached(joy)) { click(); return; } @@ -279,13 +298,15 @@ private slots: timerID = startTimer(50); memset(axesRest, 0, sizeof(axesRest)); - if (Input::Joystick && SDL_JoystickGetAttached(Input::Joystick)) + + SDL_Joystick* joy = parentDialog->getJoystick(); + if (joy && SDL_JoystickGetAttached(joy)) { - int naxes = SDL_JoystickNumAxes(Input::Joystick); + int naxes = SDL_JoystickNumAxes(joy); if (naxes > 16) naxes = 16; for (int a = 0; a < naxes; a++) { - axesRest[a] = SDL_JoystickGetAxis(Input::Joystick, a); + axesRest[a] = SDL_JoystickGetAxis(joy, a); } } } @@ -349,6 +370,8 @@ private: return str; } + InputConfigDialog* parentDialog; + int* mapping; bool isHotkey; diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp index 2f7417f6..bd02405e 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -16,24 +16,45 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ +#include #include "InterfaceSettingsDialog.h" #include "ui_InterfaceSettingsDialog.h" #include "types.h" #include "Platform.h" #include "Config.h" +#include "main.h" InterfaceSettingsDialog* InterfaceSettingsDialog::currentDlg = nullptr; - InterfaceSettingsDialog::InterfaceSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::InterfaceSettingsDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - ui->cbMouseHide->setChecked(Config::MouseHide != 0); - ui->spinMouseHideSeconds->setEnabled(Config::MouseHide != 0); - ui->spinMouseHideSeconds->setValue(Config::MouseHideSeconds); - ui->cbPauseLostFocus->setChecked(Config::PauseLostFocus != 0); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + auto& cfg = emuInstance->getGlobalConfig(); + + ui->cbMouseHide->setChecked(cfg.GetBool("MouseHide")); + ui->spinMouseHideSeconds->setEnabled(ui->cbMouseHide->isChecked()); + ui->spinMouseHideSeconds->setValue(cfg.GetInt("MouseHideSeconds")); + ui->cbPauseLostFocus->setChecked(cfg.GetBool("PauseLostFocus")); + ui->spinTargetFPS->setValue(cfg.GetDouble("TargetFPS")); + ui->spinFFW->setValue(cfg.GetDouble("FastForwardFPS")); + ui->spinSlow->setValue(cfg.GetDouble("SlowmoFPS")); + + const QList themeKeys = QStyleFactory::keys(); + const QString currentTheme = qApp->style()->objectName(); + QString cfgTheme = cfg.GetQString("UITheme"); + + ui->cbxUITheme->addItem("System default", ""); + + for (int i = 0; i < themeKeys.length(); i++) + { + ui->cbxUITheme->addItem(themeKeys[i], themeKeys[i]); + if (!cfgTheme.isEmpty() && themeKeys[i].compare(currentTheme, Qt::CaseInsensitive) == 0) + ui->cbxUITheme->setCurrentIndex(i + 1); + } } InterfaceSettingsDialog::~InterfaceSettingsDialog() @@ -43,27 +64,84 @@ InterfaceSettingsDialog::~InterfaceSettingsDialog() void InterfaceSettingsDialog::on_cbMouseHide_clicked() { - if (ui->spinMouseHideSeconds->isEnabled()) - { - ui->spinMouseHideSeconds->setEnabled(false); - } - else - { - ui->spinMouseHideSeconds->setEnabled(true); - } + ui->spinMouseHideSeconds->setEnabled(ui->cbMouseHide->isChecked()); +} + +void InterfaceSettingsDialog::on_pbClean_clicked() +{ + ui->spinTargetFPS->setValue(60.0000); +} + +void InterfaceSettingsDialog::on_pbAccurate_clicked() +{ + ui->spinTargetFPS->setValue(59.8261); +} + +void InterfaceSettingsDialog::on_pb2x_clicked() +{ + ui->spinFFW->setValue(ui->spinTargetFPS->value() * 2.0); +} + +void InterfaceSettingsDialog::on_pb3x_clicked() +{ + ui->spinFFW->setValue(ui->spinTargetFPS->value() * 3.0); +} + +void InterfaceSettingsDialog::on_pbMAX_clicked() +{ + ui->spinFFW->setValue(1000.0); +} + +void InterfaceSettingsDialog::on_pbHalf_clicked() +{ + ui->spinSlow->setValue(ui->spinTargetFPS->value() / 2.0); +} + +void InterfaceSettingsDialog::on_pbQuarter_clicked() +{ + ui->spinSlow->setValue(ui->spinTargetFPS->value() / 4.0); } void InterfaceSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + if (r == QDialog::Accepted) { - Config::MouseHide = ui->cbMouseHide->isChecked() ? 1:0; - Config::MouseHideSeconds = ui->spinMouseHideSeconds->value(); - Config::PauseLostFocus = ui->cbPauseLostFocus->isChecked() ? 1:0; + auto& cfg = emuInstance->getGlobalConfig(); + + cfg.SetBool("MouseHide", ui->cbMouseHide->isChecked()); + cfg.SetInt("MouseHideSeconds", ui->spinMouseHideSeconds->value()); + cfg.SetBool("PauseLostFocus", ui->cbPauseLostFocus->isChecked()); + + double val = ui->spinTargetFPS->value(); + if (val == 0.0) cfg.SetDouble("TargetFPS", 0.0001); + else cfg.SetDouble("TargetFPS", val); + + val = ui->spinFFW->value(); + if (val == 0.0) cfg.SetDouble("FastForwardFPS", 0.0001); + else cfg.SetDouble("FastForwardFPS", val); + + val = ui->spinSlow->value(); + if (val == 0.0) cfg.SetDouble("SlowmoFPS", 0.0001); + else cfg.SetDouble("SlowmoFPS", val); + + QString themeName = ui->cbxUITheme->currentData().toString(); + cfg.SetQString("UITheme", themeName); Config::Save(); - emit updateMouseTimer(); + if (!themeName.isEmpty()) + qApp->setStyle(themeName); + else + qApp->setStyle(*systemThemeName); + + emit updateInterfaceSettings(); } QDialog::done(r); diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.h b/src/frontend/qt_sdl/InterfaceSettingsDialog.h index 5a23b6ea..c960e560 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.h +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -24,6 +24,8 @@ namespace Ui { class InterfaceSettingsDialog; } class InterfaceSettingsDialog; +class EmuInstance; + class InterfaceSettingsDialog : public QDialog { Q_OBJECT @@ -51,15 +53,27 @@ public: } signals: - void updateMouseTimer(); + void updateInterfaceSettings(); private slots: void done(int r); void on_cbMouseHide_clicked(); + void on_pbClean_clicked(); + void on_pbAccurate_clicked(); + + void on_pb2x_clicked(); + void on_pb3x_clicked(); + void on_pbMAX_clicked(); + + void on_pbHalf_clicked(); + void on_pbQuarter_clicked(); + private: Ui::InterfaceSettingsDialog* ui; + + EmuInstance* emuInstance; }; #endif // INTERFACESETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.ui b/src/frontend/qt_sdl/InterfaceSettingsDialog.ui index 8ee9feda..460a6e99 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.ui +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.ui @@ -6,12 +6,12 @@ 0 0 - 262 - 113 + 389 + 356 - + 0 0 @@ -19,55 +19,304 @@ Interface settings - melonDS - - - - - Hide after + + + QLayout::SizeConstraint::SetFixedSize + + + + + User interface + + + + + + + Theme + + + cbxUITheme + + + + + + + + + + + + Hide mouse after inactivity + + + + + + + 18 + + + + + After + + + spinMouseHideSeconds + + + + + + + + + + seconds + + + spinMouseHideSeconds + + + + + + + + + Pause emulation when window is not in focus + + + + - - - - Pause emulation when window is not in focus + + + + Framerate + + + + + 6 + + + 2 + + + + + Target FPS + + + + + + + Fast-Forward + + + spinFFW + + + + + + + FPS + + + 4 + + + 0.000100000000000 + + + 1000.000000000000000 + + + 59.826099999999997 + + + + + + + 2 + + + + + + 63 + 16777215 + + + + 1/4 + + + + + + + + 62 + 16777215 + + + + 1/2 + + + + + + + + + FPS + + + 4 + + + 0.000100000000000 + + + 1000.000000000000000 + + + 29.913000000000000 + + + + + + + FPS + + + 4 + + + 0.000100000000000 + + + 1000.000000000000000 + + + 1000.000000000000000 + + + + + + + Slow-Mo + + + + + + + 2 + + + + + + 63 + 16777215 + + + + Accurate + + + + + + + + 62 + 16777215 + + + + Clean + + + + + + + + + 2 + + + + + + 41 + 16777215 + + + + 2x + + + + + + + + 41 + 16777215 + + + + 3x + + + + + + + + 41 + 16777215 + + + + MAX + + + + + + + + - - - - Hide mouse after inactivity - - - - - - - + - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - seconds of inactivity + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok - - cbMouseHide - spinMouseHideSeconds - cbPauseLostFocus - diff --git a/src/frontend/qt_sdl/LANDialog.cpp b/src/frontend/qt_sdl/LANDialog.cpp new file mode 100644 index 00000000..bec9c48f --- /dev/null +++ b/src/frontend/qt_sdl/LANDialog.cpp @@ -0,0 +1,436 @@ +/* + Copyright 2016-2024 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 +#include + +#include +#include +#include +#include + +#include "LANDialog.h" +#include "Config.h" +#include "main.h" +#include "LAN.h" + +#include "ui_LANStartHostDialog.h" +#include "ui_LANStartClientDialog.h" +#include "ui_LANDialog.h" + +using namespace melonDS; + + +LANStartClientDialog* lanClientDlg = nullptr; +LANDialog* lanDlg = nullptr; + +#define lan() ((LAN&)MPInterface::Get()) + + +LANStartHostDialog::LANStartHostDialog(QWidget* parent) : QDialog(parent), ui(new Ui::LANStartHostDialog) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + setMPInterface(MPInterface_LAN); + + auto cfg = Config::GetGlobalTable(); + ui->txtPlayerName->setText(cfg.GetQString("LAN.PlayerName")); + + ui->sbNumPlayers->setRange(2, 16); + ui->sbNumPlayers->setValue(cfg.GetInt("LAN.HostNumPlayers")); +} + +LANStartHostDialog::~LANStartHostDialog() +{ + delete ui; +} + +void LANStartHostDialog::done(int r) +{ + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + return; + } + + if (r == QDialog::Accepted) + { + if (ui->txtPlayerName->text().trimmed().isEmpty()) + { + QMessageBox::warning(this, "melonDS", "Please enter a player name."); + return; + } + + std::string player = ui->txtPlayerName->text().toStdString(); + int numplayers = ui->sbNumPlayers->value(); + + if (!lan().StartHost(player.c_str(), numplayers)) + { + QMessageBox::warning(this, "melonDS", "Failed to start LAN game."); + return; + } + + lanDlg = LANDialog::openDlg(parentWidget()); + + auto cfg = Config::GetGlobalTable(); + cfg.SetString("LAN.PlayerName", player); + cfg.SetInt("LAN.HostNumPlayers", numplayers); + Config::Save(); + } + else + { + setMPInterface(MPInterface_Local); + } + + QDialog::done(r); +} + + +LANStartClientDialog::LANStartClientDialog(QWidget* parent) : QDialog(parent), ui(new Ui::LANStartClientDialog) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + setMPInterface(MPInterface_LAN); + + auto cfg = Config::GetGlobalTable(); + ui->txtPlayerName->setText(cfg.GetQString("LAN.PlayerName")); + + QStandardItemModel* model = new QStandardItemModel(); + ui->tvAvailableGames->setModel(model); + const QStringList listheader = {"Name", "Players", "Status", "Host IP"}; + model->setHorizontalHeaderLabels(listheader); + + connect(ui->tvAvailableGames->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + this, SLOT(onGameSelectionChanged(const QItemSelection&, const QItemSelection&))); + + ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Connect"); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + + QPushButton* btn = ui->buttonBox->addButton("Direct connect...", QDialogButtonBox::ActionRole); + connect(btn, SIGNAL(clicked()), this, SLOT(onDirectConnect())); + + lanClientDlg = this; + lan().StartDiscovery(); + + timerID = startTimer(1000); +} + +LANStartClientDialog::~LANStartClientDialog() +{ + killTimer(timerID); + + lanClientDlg = nullptr; + delete ui; +} + +void LANStartClientDialog::onGameSelectionChanged(const QItemSelection& cur, const QItemSelection& prev) +{ + QModelIndexList indlist = cur.indexes(); + if (indlist.count() == 0) + { + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + } + else + { + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + } +} + +void LANStartClientDialog::on_tvAvailableGames_doubleClicked(QModelIndex index) +{ + done(QDialog::Accepted); +} + +void LANStartClientDialog::onDirectConnect() +{ + if (ui->txtPlayerName->text().trimmed().isEmpty()) + { + QMessageBox::warning(this, "melonDS", "Please enter a player name before connecting."); + return; + } + + QString host = QInputDialog::getText(this, "Direct connect", "Host address:"); + if (host.isEmpty()) return; + + std::string hostname = host.toStdString(); + std::string player = ui->txtPlayerName->text().toStdString(); + + setEnabled(false); + lan().EndDiscovery(); + if (!lan().StartClient(player.c_str(), hostname.c_str())) + { + QString msg = QString("Failed to connect to the host %0.").arg(QString::fromStdString(hostname)); + QMessageBox::warning(this, "melonDS", msg); + setEnabled(true); + lan().StartDiscovery(); + return; + } + + setEnabled(true); + lanDlg = LANDialog::openDlg(parentWidget()); + QDialog::done(QDialog::Accepted); +} + +void LANStartClientDialog::done(int r) +{ + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + return; + } + + if (r == QDialog::Accepted) + { + if (ui->txtPlayerName->text().trimmed().isEmpty()) + { + QMessageBox::warning(this, "melonDS", "Please enter a player name before connecting."); + return; + } + + QModelIndexList indlist = ui->tvAvailableGames->selectionModel()->selectedRows(); + if (indlist.count() == 0) return; + + QStandardItemModel* model = (QStandardItemModel*)ui->tvAvailableGames->model(); + QStandardItem* item = model->item(indlist[0].row()); + u32 addr = item->data().toUInt(); + char hostname[16]; + snprintf(hostname, 16, "%d.%d.%d.%d", (addr>>24), ((addr>>16)&0xFF), ((addr>>8)&0xFF), (addr&0xFF)); + + std::string player = ui->txtPlayerName->text().toStdString(); + + setEnabled(false); + lan().EndDiscovery(); + if (!lan().StartClient(player.c_str(), hostname)) + { + QString msg = QString("Failed to connect to the host %0.").arg(QString(hostname)); + QMessageBox::warning(this, "melonDS", msg); + setEnabled(true); + lan().StartDiscovery(); + return; + } + + setEnabled(true); + lanDlg = LANDialog::openDlg(parentWidget()); + + auto cfg = Config::GetGlobalTable(); + cfg.SetString("LAN.PlayerName", player); + Config::Save(); + } + else + { + lan().EndDiscovery(); + setMPInterface(MPInterface_Local); + } + + QDialog::done(r); +} + +void LANStartClientDialog::timerEvent(QTimerEvent *event) +{ + doUpdateDiscoveryList(); +} + +void LANStartClientDialog::doUpdateDiscoveryList() +{ + auto disclist = lan().GetDiscoveryList(); + + QStandardItemModel* model = (QStandardItemModel*)ui->tvAvailableGames->model(); + int curcount = model->rowCount(); + int newcount = disclist.size(); + if (curcount > newcount) + { + model->removeRows(newcount, curcount-newcount); + } + else if (curcount < newcount) + { + for (int i = curcount; i < newcount; i++) + { + QList row; + row.append(new QStandardItem()); + row.append(new QStandardItem()); + row.append(new QStandardItem()); + row.append(new QStandardItem()); + model->appendRow(row); + } + } + + int i = 0; + for (const auto& [key, data] : disclist) + { + model->item(i, 0)->setText(data.SessionName); + model->item(i, 0)->setData(QVariant(key)); + + QString plcount = QString("%0/%1").arg(data.NumPlayers).arg(data.MaxPlayers); + model->item(i, 1)->setText(plcount); + + QString status; + switch (data.Status) + { + case 0: status = "Idle"; break; + case 1: status = "Playing"; break; + } + model->item(i, 2)->setText(status); + + QString ip = QString("%0.%1.%2.%3").arg(key>>24).arg((key>>16)&0xFF).arg((key>>8)&0xFF).arg(key&0xFF); + model->item(i, 3)->setText(ip); + + i++; + } +} + + +LANDialog::LANDialog(QWidget* parent) : QDialog(parent), ui(new Ui::LANDialog) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + QStandardItemModel* model = new QStandardItemModel(); + ui->tvPlayerList->setModel(model); + const QStringList header = {"#", "Player", "Status", "Ping", "IP"}; + model->setHorizontalHeaderLabels(header); + + timerID = startTimer(1000); +} + +LANDialog::~LANDialog() +{ + killTimer(timerID); + + delete ui; +} + +void LANDialog::on_btnLeaveGame_clicked() +{ + done(QDialog::Accepted); +} + +void LANDialog::done(int r) +{ + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + return; + } + + bool showwarning = true; + if (lan().GetNumPlayers() < 2) + showwarning = false; + + if (showwarning) + { + if (QMessageBox::warning(this, "melonDS", "Really leave this LAN game?", + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) + return; + } + + lan().EndSession(); + setMPInterface(MPInterface_Local); + + QDialog::done(r); +} + +void LANDialog::timerEvent(QTimerEvent *event) +{ + doUpdatePlayerList(); +} + +void LANDialog::doUpdatePlayerList() +{ + auto playerlist = lan().GetPlayerList(); + auto maxplayers = lan().GetMaxPlayers(); + + QStandardItemModel* model = (QStandardItemModel*)ui->tvPlayerList->model(); + int curcount = model->rowCount(); + int newcount = playerlist.size(); + if (curcount > newcount) + { + model->removeRows(newcount, curcount-newcount); + } + else if (curcount < newcount) + { + for (int i = curcount; i < newcount; i++) + { + QList row; + row.append(new QStandardItem()); + row.append(new QStandardItem()); + row.append(new QStandardItem()); + row.append(new QStandardItem()); + row.append(new QStandardItem()); + model->appendRow(row); + } + } + + int i = 0; + for (const auto& player : playerlist) + { + QString id = QString("%0/%1").arg(player.ID+1).arg(maxplayers); + model->item(i, 0)->setText(id); + + QString name = player.Name; + model->item(i, 1)->setText(name); + + QString status = "???"; + switch (player.Status) + { + case LAN::Player_Client: + status = "Connected"; + break; + case LAN::Player_Host: + status = "Game host"; + break; + case LAN::Player_Connecting: + status = "Connecting"; + break; + case LAN::Player_Disconnected: + status = "Connection lost"; + break; + } + model->item(i, 2)->setText(status); + + if (player.IsLocalPlayer) + { + model->item(i, 3)->setText("-"); + model->item(i, 4)->setText("(local)"); + } + else + { + if (player.Status == LAN::Player_Client || + player.Status == LAN::Player_Host) + { + QString ping = QString("%0 ms").arg(player.Ping); + model->item(i, 3)->setText(ping); + } + else + { + model->item(i, 3)->setText("-"); + } + + + u32 ip = player.Address; + + QString ips = QString("%0.%1.%2.%3").arg(ip&0xFF).arg((ip>>8)&0xFF).arg((ip>>16)&0xFF).arg(ip>>24); + model->item(i, 4)->setText(ips); + } + + i++; + } +} diff --git a/src/frontend/qt_sdl/LANDialog.h b/src/frontend/qt_sdl/LANDialog.h new file mode 100644 index 00000000..03857d79 --- /dev/null +++ b/src/frontend/qt_sdl/LANDialog.h @@ -0,0 +1,117 @@ +/* + Copyright 2016-2024 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 LANDIALOG_H +#define LANDIALOG_H + +#include +#include +#include + +#include "types.h" + +namespace Ui +{ + class LANStartHostDialog; + class LANStartClientDialog; + class LANDialog; +} + +class LANStartHostDialog : public QDialog +{ +Q_OBJECT + +public: + explicit LANStartHostDialog(QWidget* parent); + ~LANStartHostDialog(); + + static LANStartHostDialog* openDlg(QWidget* parent) + { + LANStartHostDialog* dlg = new LANStartHostDialog(parent); + dlg->open(); + return dlg; + } + +private slots: + void done(int r); + +private: + Ui::LANStartHostDialog* ui; +}; + +class LANStartClientDialog : public QDialog +{ +Q_OBJECT + +public: + explicit LANStartClientDialog(QWidget* parent); + ~LANStartClientDialog(); + + static LANStartClientDialog* openDlg(QWidget* parent) + { + LANStartClientDialog* dlg = new LANStartClientDialog(parent); + dlg->open(); + return dlg; + } + +protected: + void timerEvent(QTimerEvent* event) override; + +private slots: + void onGameSelectionChanged(const QItemSelection& cur, const QItemSelection& prev); + void on_tvAvailableGames_doubleClicked(QModelIndex index); + void onDirectConnect(); + void done(int r); + + void doUpdateDiscoveryList(); + +private: + Ui::LANStartClientDialog* ui; + int timerID; +}; + +class LANDialog : public QDialog +{ +Q_OBJECT + +public: + explicit LANDialog(QWidget* parent); + ~LANDialog(); + + static LANDialog* openDlg(QWidget* parent) + { + LANDialog* dlg = new LANDialog(parent); + dlg->show(); + return dlg; + } + +protected: + void timerEvent(QTimerEvent* event) override; + +private slots: + void on_btnLeaveGame_clicked(); + void done(int r); + + void doUpdatePlayerList(); + +private: + Ui::LANDialog* ui; + int timerID; +}; + +#endif // LANDIALOG_H diff --git a/src/frontend/qt_sdl/LANDialog.ui b/src/frontend/qt_sdl/LANDialog.ui new file mode 100644 index 00000000..88e9718f --- /dev/null +++ b/src/frontend/qt_sdl/LANDialog.ui @@ -0,0 +1,51 @@ + + + LANDialog + + + + 0 + 0 + 522 + 391 + + + + LAN game - melonDS + + + + + + 0 + + + + + Leave game + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + diff --git a/src/frontend/qt_sdl/LANStartClientDialog.ui b/src/frontend/qt_sdl/LANStartClientDialog.ui new file mode 100644 index 00000000..eeea25e9 --- /dev/null +++ b/src/frontend/qt_sdl/LANStartClientDialog.ui @@ -0,0 +1,117 @@ + + + LANStartClientDialog + + + + 0 + 0 + 547 + 409 + + + + + 0 + 0 + + + + Join LAN game - melonDS + + + + + + + + Player name: + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QAbstractItemView::NoEditTriggers + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + LANStartClientDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + LANStartClientDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/frontend/qt_sdl/LANStartHostDialog.ui b/src/frontend/qt_sdl/LANStartHostDialog.ui new file mode 100644 index 00000000..0d6cd50c --- /dev/null +++ b/src/frontend/qt_sdl/LANStartHostDialog.ui @@ -0,0 +1,97 @@ + + + LANStartHostDialog + + + + 0 + 0 + 389 + 228 + + + + + 0 + 0 + + + + Host LAN game - melonDS + + + + QLayout::SetFixedSize + + + + + + + Player name: + + + + + + + + + + Number of players: + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + LANStartHostDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + LANStartHostDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/frontend/qt_sdl/LAN_PCap.cpp b/src/frontend/qt_sdl/LAN_PCap.cpp deleted file mode 100644 index 4a1ebc35..00000000 --- a/src/frontend/qt_sdl/LAN_PCap.cpp +++ /dev/null @@ -1,408 +0,0 @@ -/* - Copyright 2016-2023 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/. -*/ - -// direct LAN interface. Currently powered by libpcap, may change. - -#include -#include -#include -#include -#include "Wifi.h" -#include "LAN_PCap.h" -#include "Config.h" -#include "Platform.h" - -#ifdef __WIN32__ - #include -#else - #include - #include - #include - #ifdef __linux__ - #include - #else - #include - #include - #endif -#endif - -using namespace melonDS; -using Platform::Log; -using Platform::LogLevel; - -// welp -#ifndef PCAP_OPENFLAG_PROMISCUOUS -#define PCAP_OPENFLAG_PROMISCUOUS 1 -#endif - - -#define DECL_PCAP_FUNC(ret, name, args, args2) \ - typedef ret (*type_##name) args; \ - type_##name ptr_##name = NULL; \ - ret name args { return ptr_##name args2; } - -DECL_PCAP_FUNC(int, pcap_findalldevs, (pcap_if_t** alldevs, char* errbuf), (alldevs,errbuf)) -DECL_PCAP_FUNC(void, pcap_freealldevs, (pcap_if_t* alldevs), (alldevs)) -DECL_PCAP_FUNC(pcap_t*, pcap_open_live, (const char* src, int snaplen, int flags, int readtimeout, char* errbuf), (src,snaplen,flags,readtimeout,errbuf)) -DECL_PCAP_FUNC(void, pcap_close, (pcap_t* dev), (dev)) -DECL_PCAP_FUNC(int, pcap_setnonblock, (pcap_t* dev, int nonblock, char* errbuf), (dev,nonblock,errbuf)) -DECL_PCAP_FUNC(int, pcap_sendpacket, (pcap_t* dev, const u_char* data, int len), (dev,data,len)) -DECL_PCAP_FUNC(int, pcap_dispatch, (pcap_t* dev, int num, pcap_handler callback, u_char* data), (dev,num,callback,data)) -DECL_PCAP_FUNC(const u_char*, pcap_next, (pcap_t* dev, struct pcap_pkthdr* hdr), (dev,hdr)) - - -namespace LAN_PCap -{ - -const char* PCapLibNames[] = -{ -#ifdef __WIN32__ - // TODO: name for npcap in non-WinPCap mode - "wpcap.dll", -#elif defined(__APPLE__) - "libpcap.A.dylib", - "libpcap.dylib", -#else - // Linux lib names - "libpcap.so.1", - "libpcap.so", -#endif - NULL -}; - -AdapterData* Adapters = NULL; -int NumAdapters = 0; - -Platform::DynamicLibrary* PCapLib = NULL; -pcap_t* PCapAdapter = NULL; -AdapterData* PCapAdapterData; - -u8 PacketBuffer[2048]; -int PacketLen; -volatile int RXNum; - - -#define LOAD_PCAP_FUNC(sym) \ - ptr_##sym = (type_##sym)DynamicLibrary_LoadFunction(lib, #sym); \ - if (!ptr_##sym) return false; - -bool TryLoadPCap(Platform::DynamicLibrary *lib) -{ - LOAD_PCAP_FUNC(pcap_findalldevs) - LOAD_PCAP_FUNC(pcap_freealldevs) - LOAD_PCAP_FUNC(pcap_open_live) - LOAD_PCAP_FUNC(pcap_close) - LOAD_PCAP_FUNC(pcap_setnonblock) - LOAD_PCAP_FUNC(pcap_sendpacket) - LOAD_PCAP_FUNC(pcap_dispatch) - LOAD_PCAP_FUNC(pcap_next) - - return true; -} - -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) - { - PCapLib = NULL; - - for (int i = 0; PCapLibNames[i]; i++) - { - Platform::DynamicLibrary* lib = Platform::DynamicLibrary_Load(PCapLibNames[i]); - if (!lib) continue; - - if (!TryLoadPCap(lib)) - { - Platform::DynamicLibrary_Unload(lib); - continue; - } - - Log(LogLevel::Info, "PCap: lib %s, init successful\n", PCapLibNames[i]); - PCapLib = lib; - break; - } - - if (PCapLib == NULL) - { - Log(LogLevel::Error, "PCap: init failed\n"); - return false; - } - } - - char errbuf[PCAP_ERRBUF_SIZE]; - int ret; - - pcap_if_t* alldevs; - ret = pcap_findalldevs(&alldevs, errbuf); - if (ret < 0 || alldevs == NULL) - { - Log(LogLevel::Warn, "PCap: no devices available\n"); - return false; - } - - pcap_if_t* dev = alldevs; - while (dev) { NumAdapters++; dev = dev->next; } - - Adapters = new AdapterData[NumAdapters]; - memset(Adapters, 0, sizeof(AdapterData)*NumAdapters); - - AdapterData* adata = &Adapters[0]; - dev = alldevs; - while (dev) - { - adata->Internal = dev; - -#ifdef __WIN32__ - // hax - int len = strlen(dev->name); - len -= 12; if (len > 127) len = 127; - strncpy(adata->DeviceName, &dev->name[12], len); - adata->DeviceName[len] = '\0'; -#else - strncpy(adata->DeviceName, dev->name, 127); - adata->DeviceName[127] = '\0'; - - strncpy(adata->FriendlyName, adata->DeviceName, 127); - adata->FriendlyName[127] = '\0'; -#endif // __WIN32__ - - dev = dev->next; - adata++; - } - -#ifdef __WIN32__ - - ULONG bufsize = 16384; - IP_ADAPTER_ADDRESSES* buf = (IP_ADAPTER_ADDRESSES*)HeapAlloc(GetProcessHeap(), 0, bufsize); - ULONG uret = GetAdaptersAddresses(AF_INET, 0, NULL, buf, &bufsize); - if (uret == ERROR_BUFFER_OVERFLOW) - { - HeapFree(GetProcessHeap(), 0, buf); - buf = (IP_ADAPTER_ADDRESSES*)HeapAlloc(GetProcessHeap(), 0, bufsize); - uret = GetAdaptersAddresses(AF_INET, 0, NULL, buf, &bufsize); - } - if (uret != ERROR_SUCCESS) - { - Log(LogLevel::Error, "GetAdaptersAddresses() shat itself: %08X\n", uret); - return false; - } - - for (int i = 0; i < NumAdapters; i++) - { - adata = &Adapters[i]; - IP_ADAPTER_ADDRESSES* addr = buf; - while (addr) - { - if (strcmp(addr->AdapterName, adata->DeviceName)) - { - addr = addr->Next; - continue; - } - - WideCharToMultiByte(CP_UTF8, 0, addr->FriendlyName, 127, adata->FriendlyName, 127, NULL, NULL); - adata->FriendlyName[127] = '\0'; - - WideCharToMultiByte(CP_UTF8, 0, addr->Description, 127, adata->Description, 127, NULL, NULL); - adata->Description[127] = '\0'; - - if (addr->PhysicalAddressLength != 6) - { - Log(LogLevel::Warn, "weird MAC addr length %d for %s\n", addr->PhysicalAddressLength, addr->AdapterName); - } - else - memcpy(adata->MAC, addr->PhysicalAddress, 6); - - IP_ADAPTER_UNICAST_ADDRESS* ipaddr = addr->FirstUnicastAddress; - while (ipaddr) - { - SOCKADDR* sa = ipaddr->Address.lpSockaddr; - if (sa->sa_family == AF_INET) - { - struct in_addr sa4 = ((sockaddr_in*)sa)->sin_addr; - memcpy(adata->IP_v4, &sa4, 4); - } - - ipaddr = ipaddr->Next; - } - - break; - } - } - - HeapFree(GetProcessHeap(), 0, buf); - -#else - - struct ifaddrs* addrs; - if (getifaddrs(&addrs) != 0) - { - Log(LogLevel::Error, "getifaddrs() shat itself :(\n"); - return false; - } - - for (int i = 0; i < NumAdapters; i++) - { - adata = &Adapters[i]; - struct ifaddrs* curaddr = addrs; - while (curaddr) - { - if (strcmp(curaddr->ifa_name, adata->DeviceName)) - { - curaddr = curaddr->ifa_next; - continue; - } - - if (!curaddr->ifa_addr) - { - Log(LogLevel::Error, "Device (%s) does not have an address :/\n", curaddr->ifa_name); - curaddr = curaddr->ifa_next; - continue; - } - - u16 af = curaddr->ifa_addr->sa_family; - if (af == AF_INET) - { - struct sockaddr_in* sa = (sockaddr_in*)curaddr->ifa_addr; - memcpy(adata->IP_v4, &sa->sin_addr, 4); - } - #ifdef __linux__ - else if (af == AF_PACKET) - { - struct sockaddr_ll* sa = (sockaddr_ll*)curaddr->ifa_addr; - if (sa->sll_halen != 6) - Log(LogLevel::Warn, "weird MAC length %d for %s\n", sa->sll_halen, curaddr->ifa_name); - else - memcpy(adata->MAC, sa->sll_addr, 6); - } - #else - else if (af == AF_LINK) - { - struct sockaddr_dl* sa = (sockaddr_dl*)curaddr->ifa_addr; - if (sa->sdl_alen != 6) - Log(LogLevel::Warn, "weird MAC length %d for %s\n", sa->sdl_alen, curaddr->ifa_name); - else - memcpy(adata->MAC, LLADDR(sa), 6); - } - #endif - curaddr = curaddr->ifa_next; - } - } - - freeifaddrs(addrs); - -#endif // __WIN32__ - - if (!open_adapter) return true; - if (PCapAdapter) pcap_close(PCapAdapter); - - // open pcap device - PCapAdapterData = &Adapters[0]; - for (int i = 0; i < NumAdapters; i++) - { - if (!strncmp(Adapters[i].DeviceName, Config::LANDevice.c_str(), 128)) - PCapAdapterData = &Adapters[i]; - } - - dev = (pcap_if_t*)PCapAdapterData->Internal; - PCapAdapter = pcap_open_live(dev->name, 2048, PCAP_OPENFLAG_PROMISCUOUS, 1, errbuf); - if (!PCapAdapter) - { - Log(LogLevel::Error, "PCap: failed to open adapter %s\n", errbuf); - return false; - } - - pcap_freealldevs(alldevs); - - if (pcap_setnonblock(PCapAdapter, 1, errbuf) < 0) - { - Log(LogLevel::Error, "PCap: failed to set nonblocking mode\n"); - pcap_close(PCapAdapter); PCapAdapter = NULL; - return false; - } - - return true; -} - -void DeInit() -{ - if (PCapLib) - { - if (PCapAdapter) - { - pcap_close(PCapAdapter); - PCapAdapter = NULL; - } - - Platform::DynamicLibrary_Unload(PCapLib); - PCapLib = NULL; - } -} - - -void RXCallback(u_char* blarg, const struct pcap_pkthdr* header, const u_char* data) -{ - while (RXNum > 0); - - if (header->len > 2048-64) return; - - PacketLen = header->len; - memcpy(PacketBuffer, data, PacketLen); - RXNum = 1; -} - -int SendPacket(u8* data, int len) -{ - if (PCapAdapter == NULL) - return 0; - - if (len > 2048) - { - Log(LogLevel::Error, "LAN_SendPacket: error: packet too long (%d)\n", len); - return 0; - } - - pcap_sendpacket(PCapAdapter, data, len); - // TODO: check success - return len; -} - -int RecvPacket(u8* data) -{ - if (PCapAdapter == NULL) - return 0; - - int ret = 0; - if (RXNum > 0) - { - memcpy(data, PacketBuffer, PacketLen); - ret = PacketLen; - RXNum = 0; - } - - pcap_dispatch(PCapAdapter, 1, RXCallback, NULL); - return ret; -} - -} diff --git a/src/frontend/qt_sdl/LAN_PCap.h b/src/frontend/qt_sdl/LAN_PCap.h deleted file mode 100644 index 2e03d66a..00000000 --- a/src/frontend/qt_sdl/LAN_PCap.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright 2016-2023 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 LAN_PCAP_H -#define LAN_PCAP_H - -#include "types.h" - -namespace LAN_PCap -{ - -using namespace melonDS; -struct AdapterData -{ - char DeviceName[128]; - char FriendlyName[128]; - char Description[128]; - - u8 MAC[6]; - u8 IP_v4[4]; - - void* Internal; -}; - - -extern AdapterData* Adapters; -extern int NumAdapters; - - -bool Init(bool open_adapter); -void DeInit(); - -int SendPacket(u8* data, int len); -int RecvPacket(u8* data); - -} - -#endif // LAN_PCAP_H diff --git a/src/frontend/qt_sdl/LocalMP.cpp b/src/frontend/qt_sdl/LocalMP.cpp deleted file mode 100644 index 7ea98686..00000000 --- a/src/frontend/qt_sdl/LocalMP.cpp +++ /dev/null @@ -1,658 +0,0 @@ -/* - Copyright 2016-2023 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" -#include "Platform.h" - -using namespace melonDS; -using namespace melonDS::Platform; - -using Platform::Log; -using Platform::LogLevel; - -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 = CreateSemaphoreA(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()) - { - Log(LogLevel::Info, "MP sharedmem doesn't exist. creating\n"); - if (!MPQueue->create(kQueueSize)) - { - Log(LogLevel::Error, "MP sharedmem create failed :( (%d)\n", MPQueue->error()); - delete MPQueue; - MPQueue = nullptr; - 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; - - Log(LogLevel::Info, "MP comm init OK, instance ID %d\n", InstanceID); - - RecvTimeout = 25; - - return true; -} - -void DeInit() -{ - if (MPQueue) - { - MPQueue->lock(); - if (MPQueue->data() != nullptr) - { - MPQueueHeader *header = (MPQueueHeader *) MPQueue->data(); - header->ConnectedBitmask &= ~(1 << InstanceID); - header->InstanceBitmask &= ~(1 << InstanceID); - header->NumInstances--; - } - MPQueue->unlock(); - - SemPoolDeinit(); - - MPQueue->detach(); - } - - delete MPQueue; - MPQueue = nullptr; -} - -void SetRecvTimeout(int timeout) -{ - RecvTimeout = timeout; -} - -void Begin() -{ - if (!MPQueue) return; - 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() -{ - if (!MPQueue) return; - 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) -{ - if (!MPQueue) return 0; - 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) - { - Log(LogLevel::Warn, "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 (!MPQueue) return -1; - - 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) -{ - if (!MPQueue) return 0; - - 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) - { - Log(LogLevel::Warn, "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/MPSettingsDialog.cpp b/src/frontend/qt_sdl/MPSettingsDialog.cpp index bd64dfa2..54c35d15 100644 --- a/src/frontend/qt_sdl/MPSettingsDialog.cpp +++ b/src/frontend/qt_sdl/MPSettingsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -22,9 +22,10 @@ #include "types.h" #include "Platform.h" #include "Config.h" +#include "main.h" -#include "LAN_Socket.h" -#include "LAN_PCap.h" +#include "Net_Slirp.h" +#include "Net_PCap.h" #include "Wifi.h" #include "MPSettingsDialog.h" @@ -33,21 +34,22 @@ MPSettingsDialog* MPSettingsDialog::currentDlg = nullptr; -extern bool RunningSomething; - MPSettingsDialog::MPSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MPSettingsDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + auto& cfg = emuInstance->getGlobalConfig(); 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); + grpAudioMode->button(cfg.GetInt("MP.AudioMode"))->setChecked(true); - ui->sbReceiveTimeout->setValue(Config::MPRecvTimeout); + ui->sbReceiveTimeout->setValue(cfg.GetInt("MP.RecvTimeout")); } MPSettingsDialog::~MPSettingsDialog() @@ -57,10 +59,18 @@ MPSettingsDialog::~MPSettingsDialog() void MPSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + if (r == QDialog::Accepted) { - Config::MPAudioMode = grpAudioMode->checkedId(); - Config::MPRecvTimeout = ui->sbReceiveTimeout->value(); + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetInt("MP.AudioMode", grpAudioMode->checkedId()); + cfg.SetInt("MP.RecvTimeout", ui->sbReceiveTimeout->value()); Config::Save(); } @@ -69,5 +79,3 @@ void MPSettingsDialog::done(int r) closeDlg(); } - -// diff --git a/src/frontend/qt_sdl/MPSettingsDialog.h b/src/frontend/qt_sdl/MPSettingsDialog.h index 837ac8db..0fccb1c9 100644 --- a/src/frontend/qt_sdl/MPSettingsDialog.h +++ b/src/frontend/qt_sdl/MPSettingsDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -25,6 +25,8 @@ namespace Ui { class MPSettingsDialog; } class MPSettingsDialog; +class EmuInstance; + class MPSettingsDialog : public QDialog { Q_OBJECT @@ -58,6 +60,7 @@ private slots: private: Ui::MPSettingsDialog* ui; + EmuInstance* emuInstance; QButtonGroup* grpAudioMode; }; diff --git a/src/frontend/qt_sdl/NetplayDialog.cpp b/src/frontend/qt_sdl/NetplayDialog.cpp new file mode 100644 index 00000000..d7b7cf81 --- /dev/null +++ b/src/frontend/qt_sdl/NetplayDialog.cpp @@ -0,0 +1,193 @@ +/* + Copyright 2016-2024 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 +#include + +#include + +#include +#include + +#include "NDS.h" +#include "NDSCart.h" +#include "main.h" +//#include "IPC.h" +#include "NetplayDialog.h" +//#include "Input.h" +//#include "ROMManager.h" +#include "Config.h" +#include "Savestate.h" +#include "Platform.h" + +#include "ui_NetplayStartHostDialog.h" +#include "ui_NetplayStartClientDialog.h" +#include "ui_NetplayDialog.h" + +using namespace melonDS; + + +extern EmuThread* emuThread; +NetplayDialog* netplayDlg; + + +NetplayStartHostDialog::NetplayStartHostDialog(QWidget* parent) : QDialog(parent), ui(new Ui::NetplayStartHostDialog) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + ui->txtPort->setText("8064"); +} + +NetplayStartHostDialog::~NetplayStartHostDialog() +{ + delete ui; +} + +void NetplayStartHostDialog::done(int r) +{ + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + return; + } + + if (r == QDialog::Accepted) + { + std::string player = ui->txtPlayerName->text().toStdString(); + int port = ui->txtPort->text().toInt(); + + // TODO validate input!! + + netplayDlg = NetplayDialog::openDlg(parentWidget()); + + Netplay::StartHost(player.c_str(), port); + } + + QDialog::done(r); +} + + +NetplayStartClientDialog::NetplayStartClientDialog(QWidget* parent) : QDialog(parent), ui(new Ui::NetplayStartClientDialog) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + ui->txtPort->setText("8064"); +} + +NetplayStartClientDialog::~NetplayStartClientDialog() +{ + delete ui; +} + +void NetplayStartClientDialog::done(int r) +{ + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + return; + } + + if (r == QDialog::Accepted) + { + std::string player = ui->txtPlayerName->text().toStdString(); + std::string host = ui->txtIPAddress->text().toStdString(); + int port = ui->txtPort->text().toInt(); + + // TODO validate input!! + + netplayDlg = NetplayDialog::openDlg(parentWidget()); + + Netplay::StartClient(player.c_str(), host.c_str(), port); + } + + QDialog::done(r); +} + + +NetplayDialog::NetplayDialog(QWidget* parent) : QDialog(parent), ui(new Ui::NetplayDialog) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + QStandardItemModel* model = new QStandardItemModel(); + ui->tvPlayerList->setModel(model); + + connect(this, &NetplayDialog::sgUpdatePlayerList, this, &NetplayDialog::doUpdatePlayerList); +} + +NetplayDialog::~NetplayDialog() +{ + delete ui; +} + +void NetplayDialog::done(int r) +{ + // ??? + + QDialog::done(r); +} + +void NetplayDialog::updatePlayerList(Netplay::Player* players, int num) +{ + emit sgUpdatePlayerList(players, num); +} + +void NetplayDialog::doUpdatePlayerList(Netplay::Player* players, int num) +{ + QStandardItemModel* model = (QStandardItemModel*)ui->tvPlayerList->model(); + + model->clear(); + model->setRowCount(num); + + // TODO: remove IP column in final product + + const QStringList header = {"#", "Player", "Status", "Ping", "IP"}; + model->setHorizontalHeaderLabels(header); + + for (int i = 0; i < num; i++) + { + Netplay::Player* player = &players[i]; + + QString id = QString("%0").arg(player->ID+1); + model->setItem(i, 0, new QStandardItem(id)); + + QString name = player->Name; + model->setItem(i, 1, new QStandardItem(name)); + + QString status; + switch (player->Status) + { + case 1: status = ""; break; + case 2: status = "Host"; break; + default: status = "ded"; break; + } + model->setItem(i, 2, new QStandardItem(status)); + + // TODO: ping + model->setItem(i, 3, new QStandardItem("x")); + + char ip[32]; + u32 addr = player->Address; + sprintf(ip, "%d.%d.%d.%d", addr&0xFF, (addr>>8)&0xFF, (addr>>16)&0xFF, addr>>24); + model->setItem(i, 4, new QStandardItem(ip)); + } +} diff --git a/src/frontend/qt_sdl/NetplayDialog.h b/src/frontend/qt_sdl/NetplayDialog.h new file mode 100644 index 00000000..1fa0dcf2 --- /dev/null +++ b/src/frontend/qt_sdl/NetplayDialog.h @@ -0,0 +1,111 @@ +/* + Copyright 2016-2024 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 NETPLAYDIALOG_H +#define NETPLAYDIALOG_H + +#include + +#include "types.h" +#include "Netplay.h" + +namespace Ui +{ + class NetplayStartHostDialog; + class NetplayStartClientDialog; + class NetplayDialog; +} + +class NetplayStartHostDialog; +class NetplayStartClientDialog; +class NetplayDialog; + +class NetplayStartHostDialog : public QDialog +{ +Q_OBJECT + +public: + explicit NetplayStartHostDialog(QWidget* parent); + ~NetplayStartHostDialog(); + + static NetplayStartHostDialog* openDlg(QWidget* parent) + { + NetplayStartHostDialog* dlg = new NetplayStartHostDialog(parent); + dlg->open(); + return dlg; + } + +private slots: + void done(int r); + +private: + Ui::NetplayStartHostDialog* ui; +}; + +class NetplayStartClientDialog : public QDialog +{ +Q_OBJECT + +public: + explicit NetplayStartClientDialog(QWidget* parent); + ~NetplayStartClientDialog(); + + static NetplayStartClientDialog* openDlg(QWidget* parent) + { + NetplayStartClientDialog* dlg = new NetplayStartClientDialog(parent); + dlg->open(); + return dlg; + } + +private slots: + void done(int r); + +private: + Ui::NetplayStartClientDialog* ui; +}; + +class NetplayDialog : public QDialog +{ +Q_OBJECT + +public: + explicit NetplayDialog(QWidget* parent); + ~NetplayDialog(); + + static NetplayDialog* openDlg(QWidget* parent) + { + NetplayDialog* dlg = new NetplayDialog(parent); + dlg->show(); + return dlg; + } + + void updatePlayerList(Netplay::Player* players, int num); + +signals: + void sgUpdatePlayerList(Netplay::Player* players, int num); + +private slots: + void done(int r); + + void doUpdatePlayerList(Netplay::Player* players, int num); + +private: + Ui::NetplayDialog* ui; +}; + +#endif // NETPLAYDIALOG_H diff --git a/src/frontend/qt_sdl/NetplayDialog.ui b/src/frontend/qt_sdl/NetplayDialog.ui new file mode 100644 index 00000000..86b51324 --- /dev/null +++ b/src/frontend/qt_sdl/NetplayDialog.ui @@ -0,0 +1,31 @@ + + + NetplayDialog + + + + 0 + 0 + 522 + 391 + + + + NETPLAY SHITO + + + + + + STATUS PLACEHOLDER + + + + + + + + + + + diff --git a/src/frontend/qt_sdl/NetplayStartClientDialog.ui b/src/frontend/qt_sdl/NetplayStartClientDialog.ui new file mode 100644 index 00000000..df5b4ea7 --- /dev/null +++ b/src/frontend/qt_sdl/NetplayStartClientDialog.ui @@ -0,0 +1,107 @@ + + + NetplayStartClientDialog + + + + 0 + 0 + 400 + 229 + + + + + 0 + 0 + + + + NETPLAY CLIENT + + + + QLayout::SetFixedSize + + + + + + + Player name: + + + + + + + Host port: + + + + + + + + + + + + + Host address: + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + NetplayStartClientDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + NetplayStartClientDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/frontend/qt_sdl/NetplayStartHostDialog.ui b/src/frontend/qt_sdl/NetplayStartHostDialog.ui new file mode 100644 index 00000000..f704e743 --- /dev/null +++ b/src/frontend/qt_sdl/NetplayStartHostDialog.ui @@ -0,0 +1,97 @@ + + + NetplayStartHostDialog + + + + 0 + 0 + 400 + 229 + + + + + 0 + 0 + + + + NETPLAY HOST + + + + QLayout::SetFixedSize + + + + + + + Player name: + + + + + + + Port: + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + NetplayStartHostDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + NetplayStartHostDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/frontend/qt_sdl/OSD_shaders.h b/src/frontend/qt_sdl/OSD_shaders.h index 1324fd9d..253fcdc5 100644 --- a/src/frontend/qt_sdl/OSD_shaders.h +++ b/src/frontend/qt_sdl/OSD_shaders.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -26,6 +26,7 @@ uniform vec2 uScreenSize; uniform ivec2 uOSDPos; uniform ivec2 uOSDSize; uniform float uScaleFactor; +uniform float uTexScale; in vec2 vPosition; @@ -35,8 +36,8 @@ void main() { vec4 fpos; - vec2 osdpos = (vPosition * vec2(uOSDSize * uScaleFactor)); - fTexcoord = osdpos; + vec2 osdpos = (vPosition * vec2(uOSDSize)); + fTexcoord = osdpos * uTexScale; osdpos += uOSDPos; fpos.xy = ((osdpos * 2.0) / uScreenSize * uScaleFactor) - 1.0; diff --git a/src/frontend/qt_sdl/PathSettingsDialog.cpp b/src/frontend/qt_sdl/PathSettingsDialog.cpp index 1d698537..f3a453d1 100644 --- a/src/frontend/qt_sdl/PathSettingsDialog.cpp +++ b/src/frontend/qt_sdl/PathSettingsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -19,10 +19,12 @@ #include #include #include +#include #include "types.h" #include "Config.h" #include "Platform.h" +#include "main.h" #include "PathSettingsDialog.h" #include "ui_PathSettingsDialog.h" @@ -32,26 +34,35 @@ namespace Platform = melonDS::Platform; PathSettingsDialog* PathSettingsDialog::currentDlg = nullptr; -extern std::string EmuDirectory; -extern bool RunningSomething; - bool PathSettingsDialog::needsReset = false; +constexpr char errordialog[] = "melonDS cannot write to that directory."; PathSettingsDialog::PathSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::PathSettingsDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - ui->txtSaveFilePath->setText(QString::fromStdString(Config::SaveFilePath)); - ui->txtSavestatePath->setText(QString::fromStdString(Config::SavestatePath)); - ui->txtCheatFilePath->setText(QString::fromStdString(Config::CheatFilePath)); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); - int inst = Platform::InstanceID(); + auto& cfg = emuInstance->getGlobalConfig(); + ui->txtSaveFilePath->setText(cfg.GetQString("SaveFilePath")); + ui->txtSavestatePath->setText(cfg.GetQString("SavestatePath")); + ui->txtCheatFilePath->setText(cfg.GetQString("CheatFilePath")); + + int inst = emuInstance->getInstanceID(); if (inst > 0) ui->lblInstanceNum->setText(QString("Configuring paths for instance %1").arg(inst+1)); else ui->lblInstanceNum->hide(); + +#define SET_ORIGVAL(type, val) \ + for (type* w : findChildren(nullptr)) \ + w->setProperty("user_originalValue", w->val()); + + SET_ORIGVAL(QLineEdit, text); + +#undef SET_ORIGVAL } PathSettingsDialog::~PathSettingsDialog() @@ -61,27 +72,46 @@ PathSettingsDialog::~PathSettingsDialog() void PathSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + needsReset = false; if (r == QDialog::Accepted) { - std::string saveFilePath = ui->txtSaveFilePath->text().toStdString(); - std::string savestatePath = ui->txtSavestatePath->text().toStdString(); - std::string cheatFilePath = ui->txtCheatFilePath->text().toStdString(); + bool modified = false; - if ( saveFilePath != Config::SaveFilePath - || savestatePath != Config::SavestatePath - || cheatFilePath != Config::CheatFilePath) +#define CHECK_ORIGVAL(type, val) \ + if (!modified) for (type* w : findChildren(nullptr)) \ + { \ + QVariant v = w->val(); \ + if (v != w->property("user_originalValue")) \ + { \ + modified = true; \ + break; \ + }\ + } + + CHECK_ORIGVAL(QLineEdit, text); + +#undef CHECK_ORIGVAL + + if (modified) { - if (RunningSomething + if (emuInstance->emuIsActive() && QMessageBox::warning(this, "Reset necessary to apply changes", "The emulation will be reset for the changes to take place.", QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) return; - Config::SaveFilePath = saveFilePath; - Config::SavestatePath = savestatePath; - Config::CheatFilePath = cheatFilePath; + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetQString("SaveFilePath", ui->txtSaveFilePath->text()); + cfg.SetQString("SavestatePath", ui->txtSavestatePath->text()); + cfg.SetQString("CheatFilePath", ui->txtCheatFilePath->text()); Config::Save(); @@ -98,9 +128,15 @@ void PathSettingsDialog::on_btnSaveFileBrowse_clicked() { QString dir = QFileDialog::getExistingDirectory(this, "Select save files path...", - QString::fromStdString(EmuDirectory)); + emuDirectory); if (dir.isEmpty()) return; + + if (!QTemporaryFile(dir).open()) + { + QMessageBox::critical(this, "melonDS", errordialog); + return; + } ui->txtSaveFilePath->setText(dir); } @@ -109,9 +145,15 @@ void PathSettingsDialog::on_btnSavestateBrowse_clicked() { QString dir = QFileDialog::getExistingDirectory(this, "Select savestates path...", - QString::fromStdString(EmuDirectory)); + emuDirectory); if (dir.isEmpty()) return; + + if (!QTemporaryFile(dir).open()) + { + QMessageBox::critical(this, "melonDS", errordialog); + return; + } ui->txtSavestatePath->setText(dir); } @@ -120,9 +162,15 @@ void PathSettingsDialog::on_btnCheatFileBrowse_clicked() { QString dir = QFileDialog::getExistingDirectory(this, "Select cheat files path...", - QString::fromStdString(EmuDirectory)); + emuDirectory); if (dir.isEmpty()) return; + + if (!QTemporaryFile(dir).open()) + { + QMessageBox::critical(this, "melonDS", errordialog); + return; + } ui->txtCheatFilePath->setText(dir); } diff --git a/src/frontend/qt_sdl/PathSettingsDialog.h b/src/frontend/qt_sdl/PathSettingsDialog.h index dd64d46c..d2dc4970 100644 --- a/src/frontend/qt_sdl/PathSettingsDialog.h +++ b/src/frontend/qt_sdl/PathSettingsDialog.h @@ -1,6 +1,6 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -25,6 +25,8 @@ namespace Ui { class PathSettingsDialog; } class PathSettingsDialog; +class EmuInstance; + class PathSettingsDialog : public QDialog { Q_OBJECT @@ -62,6 +64,7 @@ private slots: private: Ui::PathSettingsDialog* ui; + EmuInstance* emuInstance; }; #endif // PATHSETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index efd33400..541b51f2 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -28,17 +29,16 @@ #include #include #include -#include #include +#include #include #include "Platform.h" #include "Config.h" -#include "ROMManager.h" +#include "main.h" #include "CameraManager.h" -#include "LAN_Socket.h" -#include "LAN_PCap.h" -#include "LocalMP.h" +#include "Net.h" +#include "MPInterface.h" #include "SPI_Firmware.h" #ifdef __WIN32__ @@ -46,171 +46,47 @@ #define ftell _ftelli64 #endif // __WIN32__ -std::string EmuDirectory; - extern CameraManager* camManager[2]; -void emuStop(); - -// TEMP -//#include "main.h" -//extern MainWindow* mainWindow; - +extern melonDS::Net net; namespace melonDS::Platform { -QSharedMemory* IPCBuffer = nullptr; -int IPCInstanceID; - -void IPCInit() +void SignalStop(StopReason reason, void* userdata) { - IPCInstanceID = 0; - - IPCBuffer = new QSharedMemory("melonIPC"); - -#if !defined(Q_OS_WINDOWS) - // QSharedMemory instances can be left over from crashed processes on UNIX platforms. - // To prevent melonDS thinking there's another instance, we attach and then immediately detach from the - // shared memory. If no other process was actually using it, it'll be destroyed and we'll have a clean - // shared memory buffer after creating it again below. - if (IPCBuffer->attach()) - { - IPCBuffer->detach(); - delete IPCBuffer; - IPCBuffer = new QSharedMemory("melonIPC"); - } -#endif - - if (!IPCBuffer->attach()) - { - Log(LogLevel::Info, "IPC sharedmem doesn't exist. creating\n"); - if (!IPCBuffer->create(1024)) - { - Log(LogLevel::Error, "IPC sharedmem create failed: %s\n", IPCBuffer->errorString().toStdString().c_str()); - 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(); - - Log(LogLevel::Info, "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; + EmuInstance* inst = (EmuInstance*)userdata; + inst->emuStop(reason); } -void Init(int argc, char** argv) +static QIODevice::OpenMode GetQMode(FileMode mode) { -#if defined(__WIN32__) || defined(PORTABLE) - if (argc > 0 && strlen(argv[0]) > 0) - { - int len = strlen(argv[0]); - while (len > 0) - { - if (argv[0][len] == '/') break; - if (argv[0][len] == '\\') break; - len--; - } - if (len > 0) - { - std::string emudir = argv[0]; - EmuDirectory = emudir.substr(0, len); - } - else - { - EmuDirectory = "."; - } - } - else - { - EmuDirectory = "."; - } -#else - QString confdir; - QDir config(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)); - config.mkdir("melonDS"); - confdir = config.absolutePath() + "/melonDS/"; - EmuDirectory = confdir.toStdString(); -#endif + QIODevice::OpenMode qmode = QIODevice::OpenModeFlag::NotOpen; + if (mode & FileMode::Read) + qmode |= QIODevice::OpenModeFlag::ReadOnly; + if (mode & FileMode::Write) + qmode |= QIODevice::OpenModeFlag::WriteOnly; + if (mode & FileMode::Append) + qmode |= QIODevice::OpenModeFlag::Append; - IPCInit(); -} + if ((mode & FileMode::Write) && !(mode & FileMode::Preserve)) + qmode |= QIODevice::OpenModeFlag::Truncate; -void DeInit() -{ - IPCDeInit(); -} + if (mode & FileMode::NoCreate) + qmode |= QIODevice::OpenModeFlag::ExistingOnly; -void SignalStop(StopReason reason) -{ - emuStop(); - switch (reason) - { - case StopReason::GBAModeNotSupported: - Log(LogLevel::Error, "!! GBA MODE NOT SUPPORTED\n"); - //mainWindow->osdAddMessage(0xFFA0A0, "GBA mode not supported."); - break; - case StopReason::BadExceptionRegion: - //mainWindow->osdAddMessage(0xFFA0A0, "Internal error."); - break; - case StopReason::PowerOff: - case StopReason::External: - //mainWindow->osdAddMessage(0xFFC040, "Shutdown"); - default: - break; - } -} + if (mode & FileMode::Text) + qmode |= QIODevice::OpenModeFlag::Text; - -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; + return qmode; } constexpr char AccessMode(FileMode mode, bool file_exists) { + if (mode & FileMode::Append) + return 'a'; + if (!(mode & FileMode::Write)) // If we're only opening the file for reading... return 'r'; @@ -249,16 +125,21 @@ static std::string GetModeString(FileMode mode, bool file_exists) FileHandle* OpenFile(const std::string& path, FileMode mode) { - if ((mode & FileMode::ReadWrite) == FileMode::None) + if ((mode & (FileMode::ReadWrite | FileMode::Append)) == FileMode::None) { // If we aren't reading or writing, then we can't open the file Log(LogLevel::Error, "Attempted to open \"%s\" in neither read nor write mode (FileMode 0x%x)\n", path.c_str(), mode); return nullptr; } - bool file_exists = QFile::exists(QString::fromStdString(path)); - std::string modeString = GetModeString(mode, file_exists); + QString qpath{QString::fromStdString(path)}; + + std::string modeString = GetModeString(mode, QFile::exists(qpath)); + QIODevice::OpenMode qmode = GetQMode(mode); + QFile qfile{qpath}; + qfile.open(qmode); + FILE* file = fdopen(dup(qfile.handle()), modeString.c_str()); + qfile.close(); - FILE* file = fopen(path.c_str(), modeString.c_str()); if (file) { Log(LogLevel::Debug, "Opened \"%s\" with FileMode 0x%x (effective mode \"%s\")\n", path.c_str(), mode, modeString.c_str()); @@ -271,9 +152,9 @@ FileHandle* OpenFile(const std::string& path, FileMode mode) } } -FileHandle* OpenLocalFile(const std::string& path, FileMode mode) +std::string GetLocalFilePath(const std::string& filename) { - QString qpath = QString::fromStdString(path); + QString qpath = QString::fromStdString(filename); QDir dir(qpath); QString fullpath; @@ -284,18 +165,15 @@ FileHandle* OpenLocalFile(const std::string& path, FileMode mode) } else { -#ifdef PORTABLE - fullpath = QString::fromStdString(EmuDirectory) + QDir::separator() + qpath; -#else - // Check user configuration directory - QDir config(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)); - config.mkdir("melonDS"); - fullpath = config.absolutePath() + "/melonDS/"; - fullpath.append(qpath); -#endif + fullpath = emuDirectory + QDir::separator() + qpath; } - return OpenFile(fullpath.toStdString(), mode); + return fullpath.toStdString(); +} + +FileHandle* OpenLocalFile(const std::string& path, FileMode mode) +{ + return OpenFile(GetLocalFilePath(path), mode); } bool CloseFile(FileHandle* file) @@ -329,6 +207,44 @@ bool LocalFileExists(const std::string& name) return true; } +bool CheckFileWritable(const std::string& filepath) +{ + FileHandle* file = Platform::OpenFile(filepath.c_str(), FileMode::Read); + + if (file) + { + // if the file exists, check if it can be opened for writing. + Platform::CloseFile(file); + file = Platform::OpenFile(filepath.c_str(), FileMode::Append); + if (file) + { + Platform::CloseFile(file); + return true; + } + else return false; + } + else + { + // if the file does not exist, create a temporary file to check, to avoid creating an empty file. + if (QTemporaryFile(filepath.c_str()).open()) + { + return true; + } + else return false; + } +} + +bool CheckLocalFileWritable(const std::string& name) +{ + FileHandle* file = Platform::OpenLocalFile(name.c_str(), FileMode::Append); + if (file) + { + Platform::CloseFile(file); + return true; + } + else return false; +} + bool FileSeek(FileHandle* file, s64 offset, FileSeekOrigin origin) { int stdorigin; @@ -436,6 +352,14 @@ void Semaphore_Wait(Semaphore* sema) ((QSemaphore*) sema)->acquire(); } +bool Semaphore_TryWait(Semaphore* sema, int timeout_ms) +{ + if (!timeout_ms) + return ((QSemaphore*)sema)->tryAcquire(1); + + return ((QSemaphore*)sema)->tryAcquire(1, timeout_ms); +} + void Semaphore_Post(Semaphore* sema, int count) { ((QSemaphore*) sema)->release(count); @@ -471,28 +395,42 @@ void Sleep(u64 usecs) QThread::usleep(usecs); } - -void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen) +u64 GetMSCount() { - if (ROMManager::NDSSave) - ROMManager::NDSSave->RequestFlush(savedata, savelen, writeoffset, writelen); + return sysTimer.elapsed(); } -void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen) +u64 GetUSCount() { - if (ROMManager::GBASave) - ROMManager::GBASave->RequestFlush(savedata, savelen, writeoffset, writelen); + return sysTimer.nsecsElapsed() / 1000; } -void WriteFirmware(const Firmware& firmware, u32 writeoffset, u32 writelen) + +void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen, void* userdata) { - if (!ROMManager::FirmwareSave) + EmuInstance* inst = (EmuInstance*)userdata; + if (inst->ndsSave) + inst->ndsSave->RequestFlush(savedata, savelen, writeoffset, writelen); +} + +void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen, void* userdata) +{ + EmuInstance* inst = (EmuInstance*)userdata; + if (inst->gbaSave) + inst->gbaSave->RequestFlush(savedata, savelen, writeoffset, writelen); +} + +void WriteFirmware(const Firmware& firmware, u32 writeoffset, u32 writelen, void* userdata) +{ + EmuInstance* inst = (EmuInstance*)userdata; + printf("saving firmware for instance %d\n", inst->getInstanceID()); + if (!inst->firmwareSave) return; if (firmware.GetHeader().Identifier != GENERATED_FIRMWARE_IDENTIFIER) { // If this is not the default built-in firmware... // ...then write the whole thing back. - ROMManager::FirmwareSave->RequestFlush(firmware.Buffer(), firmware.Length(), writeoffset, writelen); + inst->firmwareSave->RequestFlush(firmware.Buffer(), firmware.Length(), writeoffset, writelen); } else { @@ -509,135 +447,118 @@ void WriteFirmware(const Firmware& firmware, u32 writeoffset, u32 writelen) { // If we're writing to the access points... const u8* buffer = firmware.GetExtendedAccessPointPosition(); u32 length = sizeof(firmware.GetExtendedAccessPoints()) + sizeof(firmware.GetAccessPoints()); - ROMManager::FirmwareSave->RequestFlush(buffer, length, writeoffset - eapstart, writelen); + inst->firmwareSave->RequestFlush(buffer, length, writeoffset - eapstart, writelen); } } } -void WriteDateTime(int year, int month, int day, int hour, int minute, int second) +void WriteDateTime(int year, int month, int day, int hour, int minute, int second, void* userdata) { + EmuInstance* inst = (EmuInstance*)userdata; QDateTime hosttime = QDateTime::currentDateTime(); QDateTime time = QDateTime(QDate(year, month, day), QTime(hour, minute, second)); + auto& cfg = inst->getLocalConfig(); - Config::RTCOffset = hosttime.secsTo(time); + cfg.SetInt64("RTC.Offset", hosttime.secsTo(time)); Config::Save(); } -bool MP_Init() + +void MP_Begin(void* userdata) { - return LocalMP::Init(); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + MPInterface::Get().Begin(inst); } -void MP_DeInit() +void MP_End(void* userdata) { - return LocalMP::DeInit(); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + MPInterface::Get().End(inst); } -void MP_Begin() +int MP_SendPacket(u8* data, int len, u64 timestamp, void* userdata) { - return LocalMP::Begin(); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + return MPInterface::Get().SendPacket(inst, data, len, timestamp); } -void MP_End() +int MP_RecvPacket(u8* data, u64* timestamp, void* userdata) { - return LocalMP::End(); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + return MPInterface::Get().RecvPacket(inst, data, timestamp); } -int MP_SendPacket(u8* data, int len, u64 timestamp) +int MP_SendCmd(u8* data, int len, u64 timestamp, void* userdata) { - return LocalMP::SendPacket(data, len, timestamp); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + return MPInterface::Get().SendCmd(inst, data, len, timestamp); } -int MP_RecvPacket(u8* data, u64* timestamp) +int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid, void* userdata) { - return LocalMP::RecvPacket(data, timestamp); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + return MPInterface::Get().SendReply(inst, data, len, timestamp, aid); } -int MP_SendCmd(u8* data, int len, u64 timestamp) +int MP_SendAck(u8* data, int len, u64 timestamp, void* userdata) { - return LocalMP::SendCmd(data, len, timestamp); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + return MPInterface::Get().SendAck(inst, data, len, timestamp); } -int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid) +int MP_RecvHostPacket(u8* data, u64* timestamp, void* userdata) { - return LocalMP::SendReply(data, len, timestamp, aid); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + return MPInterface::Get().RecvHostPacket(inst, data, timestamp); } -int MP_SendAck(u8* data, int len, u64 timestamp) +u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask, void* userdata) { - return LocalMP::SendAck(data, len, timestamp); -} - -int MP_RecvHostPacket(u8* data, u64* timestamp) -{ - return LocalMP::RecvHostPacket(data, timestamp); -} - -u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask) -{ - return LocalMP::RecvReplies(data, timestamp, aidmask); -} - -bool LAN_Init() -{ - if (Config::DirectLAN) - { - if (!LAN_PCap::Init(true)) - return false; - } - else - { - if (!LAN_Socket::Init()) - return false; - } - - return true; -} - -void LAN_DeInit() -{ - // checkme. blarg - //if (Config::DirectLAN) - // LAN_PCap::DeInit(); - //else - // LAN_Socket::DeInit(); - LAN_PCap::DeInit(); - LAN_Socket::DeInit(); -} - -int LAN_SendPacket(u8* data, int len) -{ - if (Config::DirectLAN) - return LAN_PCap::SendPacket(data, len); - else - return LAN_Socket::SendPacket(data, len); -} - -int LAN_RecvPacket(u8* data) -{ - if (Config::DirectLAN) - return LAN_PCap::RecvPacket(data); - else - return LAN_Socket::RecvPacket(data); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + return MPInterface::Get().RecvReplies(inst, data, timestamp, aidmask); } -void Camera_Start(int num) +int Net_SendPacket(u8* data, int len, void* userdata) +{ + int inst = ((EmuInstance*)userdata)->getInstanceID(); + net.SendPacket(data, len, inst); + return 0; +} + +int Net_RecvPacket(u8* data, void* userdata) +{ + int inst = ((EmuInstance*)userdata)->getInstanceID(); + return net.RecvPacket(data, inst); +} + + +void Camera_Start(int num, void* userdata) { return camManager[num]->start(); } -void Camera_Stop(int num) +void Camera_Stop(int num, void* userdata) { return camManager[num]->stop(); } -void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv) +void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv, void* userdata) { return camManager[num]->captureFrame(frame, width, height, yuv); } +void Addon_RumbleStart(u32 len, void* userdata) +{ + ((EmuInstance*)userdata)->inputRumbleStart(len); +} + +void Addon_RumbleStop(void* userdata) +{ + ((EmuInstance*)userdata)->inputRumbleStop(); +} + DynamicLibrary* DynamicLibrary_Load(const char* lib) { return (DynamicLibrary*) SDL_LoadObject(lib); diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp index 3d47c45a..ddff9314 100644 --- a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp +++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -30,43 +30,47 @@ #include #include "main.h" +#include "EmuInstance.h" using namespace melonDS; PowerManagementDialog* PowerManagementDialog::currentDlg = nullptr; -PowerManagementDialog::PowerManagementDialog(QWidget* parent, EmuThread* emuThread) : QDialog(parent), emuThread(emuThread), ui(new Ui::PowerManagementDialog) +PowerManagementDialog::PowerManagementDialog(QWidget* parent) : QDialog(parent), ui(new Ui::PowerManagementDialog) { inited = false; ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - if (emuThread->NDS->ConsoleType == 1) + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + auto nds = emuInstance->getNDS(); + + if (nds->ConsoleType == 1) { ui->grpDSBattery->setEnabled(false); - auto& dsi = static_cast(*emuThread->NDS); - oldDSiBatteryLevel = dsi.I2C.GetBPTWL()->GetBatteryLevel(); - oldDSiBatteryCharging = dsi.I2C.GetBPTWL()->GetBatteryCharging(); + auto dsi = static_cast(nds); + oldDSiBatteryLevel = dsi->I2C.GetBPTWL()->GetBatteryLevel(); + oldDSiBatteryCharging = dsi->I2C.GetBPTWL()->GetBatteryCharging(); } else { ui->grpDSiBattery->setEnabled(false); - oldDSBatteryLevel = emuThread->NDS->SPI.GetPowerMan()->GetBatteryLevelOkay(); + oldDSBatteryLevel = nds->SPI.GetPowerMan()->GetBatteryLevelOkay(); } updateDSBatteryLevelControls(); - bool defaultDSiBatteryCharging = (emuThread->NDS->ConsoleType == 1) ? Config::DSiBatteryCharging : false; + //bool defaultDSiBatteryCharging = (nds->ConsoleType == 1) ? Config::DSiBatteryCharging : false; - if (emuThread->NDS->ConsoleType == 1) + if (nds->ConsoleType == 1) { - auto& dsi = static_cast(*emuThread->NDS); - ui->cbDSiBatteryCharging->setChecked(dsi.I2C.GetBPTWL()->GetBatteryCharging()); + auto dsi = static_cast(nds); + ui->cbDSiBatteryCharging->setChecked(dsi->I2C.GetBPTWL()->GetBatteryCharging()); int dsiBatterySliderPos = 4; - switch (dsi.I2C.GetBPTWL()->GetBatteryLevel()) + switch (dsi->I2C.GetBPTWL()->GetBatteryLevel()) { case DSi_BPTWL::batteryLevel_AlmostEmpty: dsiBatterySliderPos = 0; break; case DSi_BPTWL::batteryLevel_Low: dsiBatterySliderPos = 1; break; @@ -78,12 +82,14 @@ PowerManagementDialog::PowerManagementDialog(QWidget* parent, EmuThread* emuThre } else { - ui->cbDSiBatteryCharging->setChecked(Config::DSiBatteryCharging); - ui->sliderDSiBatteryLevel->setValue(Config::DSiBatteryLevel); + auto& cfg = emuInstance->getLocalConfig(); + + ui->cbDSiBatteryCharging->setChecked(cfg.GetBool("DSi.Battery.Charging")); + ui->sliderDSiBatteryLevel->setValue(cfg.GetInt("DSi.Battery.Level")); } - int inst = Platform::InstanceID(); + int inst = emuInstance->getInstanceID(); if (inst > 0) ui->lblInstanceNum->setText(QString("Setting battery levels for instance %1").arg(inst+1)); else @@ -99,30 +105,34 @@ PowerManagementDialog::~PowerManagementDialog() void PowerManagementDialog::done(int r) { + auto nds = emuInstance->getNDS(); + if (r == QDialog::Accepted) { - if (emuThread->NDS->ConsoleType == 1) + auto& cfg = emuInstance->getLocalConfig(); + + if (nds->ConsoleType == 1) { - auto& dsi = static_cast(*emuThread->NDS); - Config::DSiBatteryLevel = dsi.I2C.GetBPTWL()->GetBatteryLevel(); - Config::DSiBatteryCharging = dsi.I2C.GetBPTWL()->GetBatteryCharging(); + auto dsi = static_cast(nds); + cfg.SetInt("DSi.Battery.Level", dsi->I2C.GetBPTWL()->GetBatteryLevel()); + cfg.SetBool("DSi.Battery.Charging", dsi->I2C.GetBPTWL()->GetBatteryCharging()); } else { - Config::DSBatteryLevelOkay = emuThread->NDS->SPI.GetPowerMan()->GetBatteryLevelOkay(); + cfg.SetBool("DS.Battery.LevelOkay", nds->SPI.GetPowerMan()->GetBatteryLevelOkay()); } } else { - if (emuThread->NDS->ConsoleType == 1) + if (nds->ConsoleType == 1) { - auto& dsi = static_cast(*emuThread->NDS); - dsi.I2C.GetBPTWL()->SetBatteryLevel(oldDSiBatteryLevel); - dsi.I2C.GetBPTWL()->SetBatteryCharging(oldDSiBatteryCharging); + auto dsi = static_cast(nds); + dsi->I2C.GetBPTWL()->SetBatteryLevel(oldDSiBatteryLevel); + dsi->I2C.GetBPTWL()->SetBatteryCharging(oldDSiBatteryCharging); } else { - emuThread->NDS->SPI.GetPowerMan()->SetBatteryLevelOkay(oldDSBatteryLevel); + nds->SPI.GetPowerMan()->SetBatteryLevelOkay(oldDSBatteryLevel); } } @@ -133,17 +143,17 @@ void PowerManagementDialog::done(int r) void PowerManagementDialog::on_rbDSBatteryLow_clicked() { - emuThread->NDS->SPI.GetPowerMan()->SetBatteryLevelOkay(false); + emuInstance->getNDS()->SPI.GetPowerMan()->SetBatteryLevelOkay(false); } void PowerManagementDialog::on_rbDSBatteryOkay_clicked() { - emuThread->NDS->SPI.GetPowerMan()->SetBatteryLevelOkay(true); + emuInstance->getNDS()->SPI.GetPowerMan()->SetBatteryLevelOkay(true); } void PowerManagementDialog::updateDSBatteryLevelControls() { - if (emuThread->NDS->SPI.GetPowerMan()->GetBatteryLevelOkay()) + if (emuInstance->getNDS()->SPI.GetPowerMan()->GetBatteryLevelOkay()) ui->rbDSBatteryOkay->setChecked(true); else ui->rbDSBatteryLow->setChecked(true); @@ -151,10 +161,12 @@ void PowerManagementDialog::updateDSBatteryLevelControls() void PowerManagementDialog::on_cbDSiBatteryCharging_toggled() { - if (emuThread->NDS->ConsoleType == 1) + auto nds = emuInstance->getNDS(); + + if (nds->ConsoleType == 1) { - auto& dsi = static_cast(*emuThread->NDS); - dsi.I2C.GetBPTWL()->SetBatteryCharging(ui->cbDSiBatteryCharging->isChecked()); + auto dsi = static_cast(nds); + dsi->I2C.GetBPTWL()->SetBatteryCharging(ui->cbDSiBatteryCharging->isChecked()); } } @@ -162,9 +174,11 @@ void PowerManagementDialog::on_sliderDSiBatteryLevel_valueChanged(int value) { if (!inited) return; - if (emuThread->NDS->ConsoleType == 1) + auto nds = emuInstance->getNDS(); + + if (nds->ConsoleType == 1) { - auto& dsi = static_cast(*emuThread->NDS); + auto dsi = static_cast(nds); u8 newBatteryLevel = DSi_BPTWL::batteryLevel_Full; switch (value) { @@ -174,7 +188,7 @@ void PowerManagementDialog::on_sliderDSiBatteryLevel_valueChanged(int value) case 3: newBatteryLevel = DSi_BPTWL::batteryLevel_ThreeQuarters; break; case 4: newBatteryLevel = DSi_BPTWL::batteryLevel_Full; break; } - dsi.I2C.GetBPTWL()->SetBatteryLevel(newBatteryLevel); + dsi->I2C.GetBPTWL()->SetBatteryLevel(newBatteryLevel); } updateDSBatteryLevelControls(); diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.h b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.h index bc2abc3d..6853aeec 100644 --- a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.h +++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -25,19 +25,19 @@ #include "types.h" namespace Ui { class PowerManagementDialog; } -class EmuThread; class PowerManagementDialog; +class EmuInstance; class PowerManagementDialog : public QDialog { Q_OBJECT public: - explicit PowerManagementDialog(QWidget* parent, EmuThread* emu_thread); + explicit PowerManagementDialog(QWidget* parent); ~PowerManagementDialog(); static PowerManagementDialog* currentDlg; - static PowerManagementDialog* openDlg(QWidget* parent, EmuThread* emu_thread) + static PowerManagementDialog* openDlg(QWidget* parent) { if (currentDlg) { @@ -45,7 +45,7 @@ public: return currentDlg; } - currentDlg = new PowerManagementDialog(parent, emu_thread); + currentDlg = new PowerManagementDialog(parent); currentDlg->open(); return currentDlg; } @@ -65,7 +65,7 @@ private slots: private: Ui::PowerManagementDialog* ui; - EmuThread* emuThread; + EmuInstance* emuInstance; bool inited; bool oldDSBatteryLevel; diff --git a/src/frontend/qt_sdl/QPathInput.h b/src/frontend/qt_sdl/QPathInput.h index fbead36a..a26bf95f 100644 --- a/src/frontend/qt_sdl/QPathInput.h +++ b/src/frontend/qt_sdl/QPathInput.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/RAMInfoDialog.cpp b/src/frontend/qt_sdl/RAMInfoDialog.cpp index 5bff99ad..cbc954b1 100644 --- a/src/frontend/qt_sdl/RAMInfoDialog.cpp +++ b/src/frontend/qt_sdl/RAMInfoDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -22,7 +22,6 @@ #include "main.h" using namespace melonDS; -extern EmuThread* emuThread; s32 GetMainRAMValue(NDS& nds, const u32& addr, const ramInfo_ByteType& byteType) { @@ -41,11 +40,13 @@ s32 GetMainRAMValue(NDS& nds, const u32& addr, const ramInfo_ByteType& byteType) RAMInfoDialog* RAMInfoDialog::currentDlg = nullptr; -RAMInfoDialog::RAMInfoDialog(QWidget* parent, EmuThread* emuThread) : QDialog(parent), emuThread(emuThread), ui(new Ui::RAMInfoDialog) +RAMInfoDialog::RAMInfoDialog(QWidget* parent) : QDialog(parent), ui(new Ui::RAMInfoDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + qRegisterMetaType>("QVector"); qRegisterMetaType("u32"); qRegisterMetaType("s32"); @@ -91,7 +92,7 @@ void RAMInfoDialog::ShowRowsInTable() for (u32 row = scrollValue; row < std::min(scrollValue+25, RowDataVector->size()); row++) { ramInfo_RowData& rowData = RowDataVector->at(row); - rowData.Update(*emuThread->NDS, SearchThread->GetSearchByteType()); + rowData.Update(*emuInstance->getNDS(), SearchThread->GetSearchByteType()); if (ui->ramTable->item(row, ramInfo_Address) == nullptr) { @@ -186,7 +187,7 @@ void RAMInfoDialog::on_ramTable_itemChanged(QTableWidgetItem *item) s32 itemValue = item->text().toInt(); if (rowData.Value != itemValue) - rowData.SetValue(*emuThread->NDS, itemValue); + rowData.SetValue(*emuInstance->getNDS(), itemValue); } /** @@ -235,7 +236,7 @@ void RAMSearchThread::run() u32 progress = 0; // Pause game running - emuThread->emuPause(); + Dialog->emuInstance->getEmuThread()->emuPause(); // For following search modes below, RowDataVector must be filled. if (SearchMode == ramInfoSTh_SearchAll || RowDataVector->size() == 0) @@ -243,7 +244,7 @@ void RAMSearchThread::run() // First search mode for (u32 addr = 0x02000000; SearchRunning && addr < 0x02000000+MainRAMMaxSize; addr += SearchByteType) { - const s32& value = GetMainRAMValue(*emuThread->NDS, addr, SearchByteType); + const s32& value = GetMainRAMValue(*Dialog->emuInstance->getNDS(), addr, SearchByteType); RowDataVector->push_back({ addr, value, value }); @@ -264,7 +265,7 @@ void RAMSearchThread::run() for (u32 row = 0; SearchRunning && row < RowDataVector->size(); row++) { const u32& addr = RowDataVector->at(row).Address; - const s32& value = GetMainRAMValue(*emuThread->NDS, addr, SearchByteType); + const s32& value = GetMainRAMValue(*Dialog->emuInstance->getNDS(), addr, SearchByteType); if (SearchValue == value) newRowDataVector->push_back({ addr, value, value }); @@ -282,7 +283,7 @@ void RAMSearchThread::run() } // Unpause game running - emuThread->emuUnpause(); + Dialog->emuInstance->getEmuThread()->emuUnpause(); SearchRunning = false; } diff --git a/src/frontend/qt_sdl/RAMInfoDialog.h b/src/frontend/qt_sdl/RAMInfoDialog.h index 2a5b1620..5166e237 100644 --- a/src/frontend/qt_sdl/RAMInfoDialog.h +++ b/src/frontend/qt_sdl/RAMInfoDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2021 Arisotura + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -32,7 +32,7 @@ namespace Ui { class RAMInfoDialog; } class RAMInfoDialog; class RAMSearchThread; class RAMUpdateThread; -class EmuThread; +class EmuInstance; enum ramInfo_ByteType { @@ -79,11 +79,11 @@ class RAMInfoDialog : public QDialog Q_OBJECT public: - explicit RAMInfoDialog(QWidget* parent, EmuThread* emuThread); + explicit RAMInfoDialog(QWidget* parent); ~RAMInfoDialog(); static RAMInfoDialog* currentDlg; - static RAMInfoDialog* openDlg(QWidget* parent, EmuThread* emuThread) + static RAMInfoDialog* openDlg(QWidget* parent) { if (currentDlg) { @@ -91,7 +91,7 @@ public: return currentDlg; } - currentDlg = new RAMInfoDialog(parent, emuThread); + currentDlg = new RAMInfoDialog(parent); currentDlg->show(); return currentDlg; } @@ -119,11 +119,13 @@ private slots: void SetProgressbarValue(const melonDS::u32& value); private: - EmuThread* emuThread; Ui::RAMInfoDialog* ui; + EmuInstance* emuInstance; RAMSearchThread* SearchThread; QTimer* TableUpdater; + + friend class RAMSearchThread; }; class RAMSearchThread : public QThread diff --git a/src/frontend/qt_sdl/ROMInfoDialog.cpp b/src/frontend/qt_sdl/ROMInfoDialog.cpp index 0f10b2f5..75255638 100644 --- a/src/frontend/qt_sdl/ROMInfoDialog.cpp +++ b/src/frontend/qt_sdl/ROMInfoDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -27,6 +27,7 @@ #include "NDSCart.h" #include "Platform.h" #include "Config.h" +#include "main.h" using namespace melonDS; @@ -42,15 +43,18 @@ QString QStringBytes(u64 num) ROMInfoDialog* ROMInfoDialog::currentDlg = nullptr; -ROMInfoDialog::ROMInfoDialog(QWidget* parent, const melonDS::NDSCart::CartCommon& rom) : QDialog(parent), ui(new Ui::ROMInfoDialog) +ROMInfoDialog::ROMInfoDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ROMInfoDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - const NDSBanner* banner = rom.Banner(); - const NDSHeader& header = rom.GetHeader(); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + auto rom = emuInstance->getNDS()->NDSCartSlot.GetCart(); + const NDSBanner* banner = rom->Banner(); + const NDSHeader& header = rom->GetHeader(); u32 iconData[32 * 32]; - ROMManager::ROMIcon(banner->Icon, banner->Palette, iconData); + emuInstance->romIcon(banner->Icon, banner->Palette, iconData); iconImage = QImage(reinterpret_cast(iconData), 32, 32, QImage::Format_RGBA8888).copy(); ui->iconImage->setPixmap(QPixmap::fromImage(iconImage)); @@ -58,7 +62,7 @@ ROMInfoDialog::ROMInfoDialog(QWidget* parent, const melonDS::NDSCart::CartCommon { ui->saveAnimatedIconButton->setEnabled(true); - ROMManager::AnimatedROMIcon(banner->DSiIcon, banner->DSiPalette, banner->DSiSequence, animatedIconData, animatedSequence); + emuInstance->animatedROMIcon(banner->DSiIcon, banner->DSiPalette, banner->DSiSequence, animatedIconData, animatedSequence); for (u32* image: animatedIconData) { @@ -135,7 +139,7 @@ void ROMInfoDialog::on_saveIconButton_clicked() { QString filename = QFileDialog::getSaveFileName(this, "Save Icon", - QString::fromStdString(Config::LastROMFolder), + emuInstance->getGlobalConfig().GetQString("LastROMFolder"), "PNG Images (*.png)"); if (filename.isEmpty()) return; @@ -147,7 +151,7 @@ void ROMInfoDialog::on_saveAnimatedIconButton_clicked() { QString filename = QFileDialog::getSaveFileName(this, "Save Animated Icon", - QString::fromStdString(Config::LastROMFolder), + emuInstance->getGlobalConfig().GetQString("LastROMFolder"), "GIF Images (*.gif)"); if (filename.isEmpty()) return; diff --git a/src/frontend/qt_sdl/ROMInfoDialog.h b/src/frontend/qt_sdl/ROMInfoDialog.h index f7e3b5f5..7c13ad4d 100644 --- a/src/frontend/qt_sdl/ROMInfoDialog.h +++ b/src/frontend/qt_sdl/ROMInfoDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -25,21 +25,22 @@ #include #include "types.h" -#include "ROMManager.h" namespace Ui { class ROMInfoDialog; } class ROMInfoDialog; +class EmuInstance; namespace melonDS::NDSCart { class CartCommon; } + class ROMInfoDialog : public QDialog { Q_OBJECT public: - explicit ROMInfoDialog(QWidget* parent, const melonDS::NDSCart::CartCommon& rom); + explicit ROMInfoDialog(QWidget* parent); ~ROMInfoDialog(); static ROMInfoDialog* currentDlg; - static ROMInfoDialog* openDlg(QWidget* parent, const melonDS::NDSCart::CartCommon& rom) + static ROMInfoDialog* openDlg(QWidget* parent) { if (currentDlg) { @@ -47,7 +48,7 @@ public: return currentDlg; } - currentDlg = new ROMInfoDialog(parent, rom); + currentDlg = new ROMInfoDialog(parent); currentDlg->open(); return currentDlg; } @@ -66,6 +67,7 @@ private slots: private: Ui::ROMInfoDialog* ui; + EmuInstance* emuInstance; QImage iconImage; QTimeLine* iconTimeline; diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/ROMManager.cpp deleted file mode 100644 index 15a9ebf5..00000000 --- a/src/frontend/qt_sdl/ROMManager.cpp +++ /dev/null @@ -1,1564 +0,0 @@ -/* - Copyright 2016-2023 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 - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#ifdef ARCHIVE_SUPPORT_ENABLED -#include "ArchiveUtil.h" -#endif -#include "ROMManager.h" -#include "Config.h" -#include "Platform.h" - -#include "NDS.h" -#include "DSi.h" -#include "SPI.h" -#include "RTC.h" -#include "DSi_I2C.h" -#include "FreeBIOS.h" -#include "main.h" - -using std::make_unique; -using std::pair; -using std::string; -using std::tie; -using std::unique_ptr; -using std::wstring_convert; -using namespace melonDS; -using namespace melonDS::Platform; - -namespace ROMManager -{ - -int CartType = -1; -std::string BaseROMDir = ""; -std::string BaseROMName = ""; -std::string BaseAssetName = ""; - -int GBACartType = -1; -std::string BaseGBAROMDir = ""; -std::string BaseGBAROMName = ""; -std::string BaseGBAAssetName = ""; - -std::unique_ptr NDSSave = nullptr; -std::unique_ptr GBASave = nullptr; -std::unique_ptr FirmwareSave = nullptr; - -std::unique_ptr BackupState = nullptr; -bool SavestateLoaded = false; -std::string PreviousSaveFile = ""; - -ARCodeFile* CheatFile = nullptr; -bool CheatsOn = false; - - -int LastSep(const std::string& path) -{ - int i = path.length() - 1; - while (i >= 0) - { - if (path[i] == '/' || path[i] == '\\') - return i; - - i--; - } - - return -1; -} - -std::string GetAssetPath(bool gba, const std::string& configpath, const std::string& ext, const std::string& file = "") -{ - std::string result; - - if (configpath.empty()) - result = gba ? BaseGBAROMDir : BaseROMDir; - else - result = configpath; - - // cut off trailing slashes - for (;;) - { - int i = result.length() - 1; - if (i < 0) break; - if (result[i] == '/' || result[i] == '\\') - result.resize(i); - else - break; - } - - if (!result.empty()) - result += '/'; - - if (file.empty()) - { - std::string& baseName = gba ? BaseGBAAssetName : BaseAssetName; - if (baseName.empty()) - result += "firmware"; - else - result += baseName; - } - else - { - result += file; - } - - result += ext; - - return result; -} - - -QString VerifyDSBIOS() -{ - FileHandle* f; - long len; - - f = Platform::OpenLocalFile(Config::BIOS9Path, FileMode::Read); - if (!f) return "DS ARM9 BIOS was not found or could not be accessed. Check your emu settings."; - - len = FileLength(f); - if (len != 0x1000) - { - CloseFile(f); - return "DS ARM9 BIOS is not a valid BIOS dump."; - } - - CloseFile(f); - - f = Platform::OpenLocalFile(Config::BIOS7Path, FileMode::Read); - if (!f) return "DS ARM7 BIOS was not found or could not be accessed. Check your emu settings."; - - len = FileLength(f); - if (len != 0x4000) - { - CloseFile(f); - return "DS ARM7 BIOS is not a valid BIOS dump."; - } - - CloseFile(f); - - return ""; -} - -QString VerifyDSiBIOS() -{ - FileHandle* f; - long len; - - // TODO: check the first 32 bytes - - f = Platform::OpenLocalFile(Config::DSiBIOS9Path, FileMode::Read); - if (!f) return "DSi ARM9 BIOS was not found or could not be accessed. Check your emu settings."; - - len = FileLength(f); - if (len != 0x10000) - { - CloseFile(f); - return "DSi ARM9 BIOS is not a valid BIOS dump."; - } - - CloseFile(f); - - f = Platform::OpenLocalFile(Config::DSiBIOS7Path, FileMode::Read); - if (!f) return "DSi ARM7 BIOS was not found or could not be accessed. Check your emu settings."; - - len = FileLength(f); - if (len != 0x10000) - { - CloseFile(f); - return "DSi ARM7 BIOS is not a valid BIOS dump."; - } - - CloseFile(f); - - return ""; -} - -QString VerifyDSFirmware() -{ - FileHandle* f; - long len; - - f = Platform::OpenLocalFile(Config::FirmwarePath, FileMode::Read); - if (!f) return "DS firmware was not found or could not be accessed. Check your emu settings."; - - len = FileLength(f); - if (len == 0x20000) - { - // 128KB firmware, not bootable - CloseFile(f); - // TODO report it somehow? detect in core? - return ""; - } - else if (len != 0x40000 && len != 0x80000) - { - CloseFile(f); - return "DS firmware is not a valid firmware dump."; - } - - CloseFile(f); - - return ""; -} - -QString VerifyDSiFirmware() -{ - FileHandle* f; - long len; - - f = Platform::OpenLocalFile(Config::DSiFirmwarePath, FileMode::Read); - if (!f) return "DSi firmware was not found or could not be accessed. Check your emu settings."; - - len = FileLength(f); - if (len != 0x20000) - { - // not 128KB - // TODO: check whether those work - CloseFile(f); - return "DSi firmware is not a valid firmware dump."; - } - - CloseFile(f); - - return ""; -} - -QString VerifyDSiNAND() -{ - FileHandle* f; - long len; - - f = Platform::OpenLocalFile(Config::DSiNANDPath, FileMode::ReadWriteExisting); - if (!f) return "DSi NAND was not found or could not be accessed. Check your emu settings."; - - // TODO: some basic checks - // check that it has the nocash footer, and all - - CloseFile(f); - - return ""; -} - -QString VerifySetup() -{ - QString res; - - if (Config::ExternalBIOSEnable) - { - res = VerifyDSBIOS(); - if (!res.isEmpty()) return res; - } - - if (Config::ConsoleType == 1) - { - res = VerifyDSiBIOS(); - if (!res.isEmpty()) return res; - - if (Config::ExternalBIOSEnable) - { - res = VerifyDSiFirmware(); - if (!res.isEmpty()) return res; - } - - res = VerifyDSiNAND(); - if (!res.isEmpty()) return res; - } - else - { - if (Config::ExternalBIOSEnable) - { - res = VerifyDSFirmware(); - if (!res.isEmpty()) return res; - } - } - - return ""; -} - -std::string GetEffectiveFirmwareSavePath(EmuThread* thread) -{ - if (!Config::ExternalBIOSEnable) - { - return Config::WifiSettingsPath; - } - if (thread->NDS->ConsoleType == 1) - { - return Config::DSiFirmwarePath; - } - else - { - return Config::FirmwarePath; - } -} - -// Initializes the firmware save manager with the selected firmware image's path -// OR the path to the wi-fi settings. -void InitFirmwareSaveManager(EmuThread* thread) noexcept -{ - FirmwareSave = std::make_unique(GetEffectiveFirmwareSavePath(thread)); -} - -std::string GetSavestateName(int slot) -{ - std::string ext = ".ml"; - ext += (char)('0'+slot); - return GetAssetPath(false, Config::SavestatePath, ext); -} - -bool SavestateExists(int slot) -{ - std::string ssfile = GetSavestateName(slot); - return Platform::FileExists(ssfile); -} - -bool LoadState(NDS& nds, const std::string& filename) -{ - FILE* file = fopen(filename.c_str(), "rb"); - if (file == nullptr) - { // If we couldn't open the state file... - Platform::Log(Platform::LogLevel::Error, "Failed to open state file \"%s\"\n", filename.c_str()); - return false; - } - - std::unique_ptr backup = std::make_unique(Savestate::DEFAULT_SIZE); - if (backup->Error) - { // If we couldn't allocate memory for the backup... - Platform::Log(Platform::LogLevel::Error, "Failed to allocate memory for state backup\n"); - fclose(file); - return false; - } - - if (!nds.DoSavestate(backup.get()) || backup->Error) - { // Back up the emulator's state. If that failed... - Platform::Log(Platform::LogLevel::Error, "Failed to back up state, aborting load (from \"%s\")\n", filename.c_str()); - fclose(file); - return false; - } - // We'll store the backup once we're sure that the state was loaded. - // Now that we know the file and backup are both good, let's load the new state. - - // Get the size of the file that we opened - if (fseek(file, 0, SEEK_END) != 0) - { - Platform::Log(Platform::LogLevel::Error, "Failed to seek to end of state file \"%s\"\n", filename.c_str()); - fclose(file); - return false; - } - size_t size = ftell(file); - rewind(file); // reset the filebuf's position - - // Allocate exactly as much memory as we need for the savestate - std::vector buffer(size); - if (fread(buffer.data(), size, 1, file) == 0) - { // Read the state file into the buffer. If that failed... - Platform::Log(Platform::LogLevel::Error, "Failed to read %u-byte state file \"%s\"\n", size, filename.c_str()); - fclose(file); - return false; - } - fclose(file); // done with the file now - - // Get ready to load the state from the buffer into the emulator - std::unique_ptr state = std::make_unique(buffer.data(), size, false); - - if (!nds.DoSavestate(state.get()) || state->Error) - { // If we couldn't load the savestate from the buffer... - Platform::Log(Platform::LogLevel::Error, "Failed to load state file \"%s\" into emulator\n", filename.c_str()); - return false; - } - - // The backup was made and the state was loaded, so we can store the backup now. - BackupState = std::move(backup); // This will clean up any existing backup - assert(backup == nullptr); - - if (Config::SavestateRelocSRAM && NDSSave) - { - PreviousSaveFile = NDSSave->GetPath(); - - std::string savefile = filename.substr(LastSep(filename)+1); - savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile); - savefile += Platform::InstanceFileSuffix(); - NDSSave->SetPath(savefile, true); - } - - SavestateLoaded = true; - - return true; -} - -bool SaveState(NDS& nds, const std::string& filename) -{ - FILE* file = fopen(filename.c_str(), "wb"); - - if (file == nullptr) - { // If the file couldn't be opened... - return false; - } - - Savestate state; - if (state.Error) - { // If there was an error creating the state (and allocating its memory)... - fclose(file); - return false; - } - - // Write the savestate to the in-memory buffer - nds.DoSavestate(&state); - - if (state.Error) - { - fclose(file); - return false; - } - - if (fwrite(state.Buffer(), state.Length(), 1, file) == 0) - { // Write the Savestate buffer to the file. If that fails... - Platform::Log(Platform::Error, - "Failed to write %d-byte savestate to %s\n", - state.Length(), - filename.c_str() - ); - fclose(file); - return false; - } - - fclose(file); - - if (Config::SavestateRelocSRAM && NDSSave) - { - std::string savefile = filename.substr(LastSep(filename)+1); - savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile); - savefile += Platform::InstanceFileSuffix(); - NDSSave->SetPath(savefile, false); - } - - return true; -} - -void UndoStateLoad(NDS& nds) -{ - if (!SavestateLoaded || !BackupState) return; - - // Rewind the backup state and put it in load mode - BackupState->Rewind(false); - // pray that this works - // what do we do if it doesn't??? - // but it should work. - nds.DoSavestate(BackupState.get()); - - if (NDSSave && (!PreviousSaveFile.empty())) - { - NDSSave->SetPath(PreviousSaveFile, true); - } -} - - -void UnloadCheats(NDS& nds) -{ - if (CheatFile) - { - delete CheatFile; - CheatFile = nullptr; - nds.AREngine.SetCodeFile(nullptr); - } -} - -void LoadCheats(NDS& nds) -{ - UnloadCheats(nds); - - std::string filename = GetAssetPath(false, Config::CheatFilePath, ".mch"); - - // TODO: check for error (malformed cheat file, ...) - CheatFile = new ARCodeFile(filename); - - nds.AREngine.SetCodeFile(CheatsOn ? CheatFile : nullptr); -} - -std::optional> LoadARM9BIOS() noexcept -{ - if (!Config::ExternalBIOSEnable) - { - return Config::ConsoleType == 0 ? std::make_optional(bios_arm9_bin) : std::nullopt; - } - - if (FileHandle* f = OpenLocalFile(Config::BIOS9Path, Read)) - { - std::array bios {}; - FileRewind(f); - FileRead(bios.data(), sizeof(bios), 1, f); - CloseFile(f); - Log(Info, "ARM9 BIOS loaded from %s\n", Config::BIOS9Path.c_str()); - return bios; - } - - Log(Warn, "ARM9 BIOS not found\n"); - return std::nullopt; -} - -std::optional> LoadARM7BIOS() noexcept -{ - if (!Config::ExternalBIOSEnable) - { - return Config::ConsoleType == 0 ? std::make_optional(bios_arm7_bin) : std::nullopt; - } - - if (FileHandle* f = OpenLocalFile(Config::BIOS7Path, Read)) - { - std::array bios {}; - FileRead(bios.data(), sizeof(bios), 1, f); - CloseFile(f); - Log(Info, "ARM7 BIOS loaded from %s\n", Config::BIOS7Path.c_str()); - return bios; - } - - Log(Warn, "ARM7 BIOS not found\n"); - return std::nullopt; -} - -std::optional> LoadDSiARM9BIOS() noexcept -{ - if (FileHandle* f = OpenLocalFile(Config::DSiBIOS9Path, Read)) - { - std::array bios {}; - FileRead(bios.data(), sizeof(bios), 1, f); - CloseFile(f); - - if (!Config::DSiFullBIOSBoot) - { - // herp - *(u32*)&bios[0] = 0xEAFFFFFE; // overwrites the reset vector - - // TODO!!!! - // hax the upper 32K out of the goddamn DSi - // done that :) -pcy - } - Log(Info, "ARM9i BIOS loaded from %s\n", Config::DSiBIOS9Path.c_str()); - return bios; - } - - Log(Warn, "ARM9i BIOS not found\n"); - return std::nullopt; -} - -std::optional> LoadDSiARM7BIOS() noexcept -{ - if (FileHandle* f = OpenLocalFile(Config::DSiBIOS7Path, Read)) - { - std::array bios {}; - FileRead(bios.data(), sizeof(bios), 1, f); - CloseFile(f); - - if (!Config::DSiFullBIOSBoot) - { - // herp - *(u32*)&bios[0] = 0xEAFFFFFE; // overwrites the reset vector - - // TODO!!!! - // hax the upper 32K out of the goddamn DSi - // done that :) -pcy - } - Log(Info, "ARM7i BIOS loaded from %s\n", Config::DSiBIOS7Path.c_str()); - return bios; - } - - Log(Warn, "ARM7i BIOS not found\n"); - return std::nullopt; -} - -Firmware GenerateFirmware(int type) noexcept -{ - // Construct the default firmware... - string settingspath; - Firmware firmware = Firmware(type); - assert(firmware.Buffer() != nullptr); - - // If using generated firmware, we keep the wi-fi settings on the host disk separately. - // Wi-fi access point data includes Nintendo WFC settings, - // and if we didn't keep them then the player would have to reset them in each session. - // We don't need to save the whole firmware, just the part that may actually change. - if (FileHandle* f = OpenLocalFile(Config::WifiSettingsPath, Read)) - {// If we have Wi-fi settings to load... - constexpr unsigned TOTAL_WFC_SETTINGS_SIZE = 3 * (sizeof(Firmware::WifiAccessPoint) + sizeof(Firmware::ExtendedWifiAccessPoint)); - - if (!FileRead(firmware.GetExtendedAccessPointPosition(), TOTAL_WFC_SETTINGS_SIZE, 1, f)) - { // If we couldn't read the Wi-fi settings from this file... - Log(Warn, "Failed to read Wi-fi settings from \"%s\"; using defaults instead\n", Config::WifiSettingsPath.c_str()); - - // The access point and extended access point segments might - // be in different locations depending on the firmware revision, - // but our generated firmware always keeps them next to each other. - // (Extended access points first, then regular ones.) - firmware.GetAccessPoints() = { - Firmware::WifiAccessPoint(type), - Firmware::WifiAccessPoint(), - Firmware::WifiAccessPoint(), - }; - - firmware.GetExtendedAccessPoints() = { - Firmware::ExtendedWifiAccessPoint(), - Firmware::ExtendedWifiAccessPoint(), - Firmware::ExtendedWifiAccessPoint(), - }; - firmware.UpdateChecksums(); - CloseFile(f); - } - } - - CustomizeFirmware(firmware); - - // If we don't have Wi-fi settings to load, - // then the defaults will have already been populated by the constructor. - return firmware; -} - -std::optional LoadFirmware(int type) noexcept -{ - if (!Config::ExternalBIOSEnable) - { // If we're using built-in firmware... - if (type == 1) - { - Log(Error, "DSi firmware: cannot use built-in firmware in DSi mode!\n"); - return std::nullopt; - } - - return GenerateFirmware(type); - } - const string& firmwarepath = type == 1 ? Config::DSiFirmwarePath : Config::FirmwarePath; - - Log(Debug, "SPI firmware: loading from file %s\n", firmwarepath.c_str()); - - FileHandle* file = OpenLocalFile(firmwarepath, Read); - - if (!file) - { - Log(Error, "SPI firmware: couldn't open firmware file!\n"); - return std::nullopt; - } - Firmware firmware(file); - CloseFile(file); - - if (!firmware.Buffer()) - { - Log(Error, "SPI firmware: couldn't read firmware file!\n"); - return std::nullopt; - } - - if (Config::FirmwareOverrideSettings) - { - CustomizeFirmware(firmware); - } - - return firmware; -} - - -std::optional LoadNAND(const std::array& arm7ibios) noexcept -{ - FileHandle* nandfile = OpenLocalFile(Config::DSiNANDPath, ReadWriteExisting); - if (!nandfile) - return std::nullopt; - - DSi_NAND::NANDImage nandImage(nandfile, &arm7ibios[0x8308]); - if (!nandImage) - { - Log(Error, "Failed to parse DSi NAND\n"); - return std::nullopt; - // the NANDImage takes ownership of the FileHandle, no need to clean it up here - } - - // scoped so that mount isn't alive when we move the NAND image to DSi::NANDImage - { - auto mount = DSi_NAND::NANDMount(nandImage); - if (!mount) - { - Log(Error, "Failed to mount DSi NAND\n"); - return std::nullopt; - } - - DSi_NAND::DSiFirmwareSystemSettings settings {}; - if (!mount.ReadUserData(settings)) - { - Log(Error, "Failed to read DSi NAND user data\n"); - return std::nullopt; - } - - // override user settings, if needed - if (Config::FirmwareOverrideSettings) - { - // we store relevant strings as UTF-8, so we need to convert them to UTF-16 - auto converter = wstring_convert, char16_t>{}; - - // setting up username - std::u16string username = converter.from_bytes(Config::FirmwareUsername); - size_t usernameLength = std::min(username.length(), (size_t) 10); - memset(&settings.Nickname, 0, sizeof(settings.Nickname)); - memcpy(&settings.Nickname, username.data(), usernameLength * sizeof(char16_t)); - - // setting language - settings.Language = static_cast(Config::FirmwareLanguage); - - // setting up color - settings.FavoriteColor = Config::FirmwareFavouriteColour; - - // setting up birthday - settings.BirthdayMonth = Config::FirmwareBirthdayMonth; - settings.BirthdayDay = Config::FirmwareBirthdayDay; - - // setup message - std::u16string message = converter.from_bytes(Config::FirmwareMessage); - size_t messageLength = std::min(message.length(), (size_t) 26); - memset(&settings.Message, 0, sizeof(settings.Message)); - memcpy(&settings.Message, message.data(), messageLength * sizeof(char16_t)); - - // TODO: make other items configurable? - } - - // fix touchscreen coords - settings.TouchCalibrationADC1 = {0, 0}; - settings.TouchCalibrationPixel1 = {0, 0}; - settings.TouchCalibrationADC2 = {255 << 4, 191 << 4}; - settings.TouchCalibrationPixel2 = {255, 191}; - - settings.UpdateHash(); - - if (!mount.ApplyUserData(settings)) - { - Log(LogLevel::Error, "Failed to write patched DSi NAND user data\n"); - return std::nullopt; - } - } - - return nandImage; -} - -constexpr u64 MB(u64 i) -{ - return i * 1024 * 1024; -} - -constexpr u64 imgsizes[] = {0, MB(256), MB(512), MB(1024), MB(2048), MB(4096)}; -std::optional GetDSiSDCardArgs() noexcept -{ - if (!Config::DSiSDEnable) - return std::nullopt; - - return FATStorageArgs { - Config::DSiSDPath, - imgsizes[Config::DSiSDSize], - Config::DSiSDReadOnly, - Config::DSiSDFolderSync ? std::make_optional(Config::DSiSDFolderPath) : std::nullopt - }; -} - -std::optional LoadDSiSDCard() noexcept -{ - if (!Config::DSiSDEnable) - return std::nullopt; - - return FATStorage( - Config::DSiSDPath, - imgsizes[Config::DSiSDSize], - Config::DSiSDReadOnly, - Config::DSiSDFolderSync ? std::make_optional(Config::DSiSDFolderPath) : std::nullopt - ); -} - -std::optional GetDLDISDCardArgs() noexcept -{ - if (!Config::DLDIEnable) - return std::nullopt; - - return FATStorageArgs{ - Config::DLDISDPath, - imgsizes[Config::DLDISize], - Config::DLDIReadOnly, - Config::DLDIFolderSync ? std::make_optional(Config::DLDIFolderPath) : std::nullopt - }; -} - -std::optional LoadDLDISDCard() noexcept -{ - if (!Config::DLDIEnable) - return std::nullopt; - - return FATStorage(*GetDLDISDCardArgs()); -} - -void EnableCheats(NDS& nds, bool enable) -{ - CheatsOn = enable; - if (CheatFile) - nds.AREngine.SetCodeFile(CheatsOn ? CheatFile : nullptr); -} - -ARCodeFile* GetCheatFile() -{ - return CheatFile; -} - - -void SetBatteryLevels(NDS& nds) -{ - if (nds.ConsoleType == 1) - { - auto& dsi = static_cast(nds); - dsi.I2C.GetBPTWL()->SetBatteryLevel(Config::DSiBatteryLevel); - dsi.I2C.GetBPTWL()->SetBatteryCharging(Config::DSiBatteryCharging); - } - else - { - nds.SPI.GetPowerMan()->SetBatteryLevelOkay(Config::DSBatteryLevelOkay); - } -} - -void SetDateTime(NDS& nds) -{ - QDateTime hosttime = QDateTime::currentDateTime(); - QDateTime time = hosttime.addSecs(Config::RTCOffset); - - nds.RTC.SetDateTime(time.date().year(), time.date().month(), time.date().day(), - time.time().hour(), time.time().minute(), time.time().second()); -} - -void Reset(EmuThread* thread) -{ - thread->UpdateConsole(Keep {}, Keep {}); - - if (Config::ConsoleType == 1) EjectGBACart(*thread->NDS); - - thread->NDS->Reset(); - SetBatteryLevels(*thread->NDS); - SetDateTime(*thread->NDS); - - if ((CartType != -1) && NDSSave) - { - std::string oldsave = NDSSave->GetPath(); - std::string newsave = GetAssetPath(false, Config::SaveFilePath, ".sav"); - newsave += Platform::InstanceFileSuffix(); - if (oldsave != newsave) - NDSSave->SetPath(newsave, false); - } - - if ((GBACartType != -1) && GBASave) - { - std::string oldsave = GBASave->GetPath(); - std::string newsave = GetAssetPath(true, Config::SaveFilePath, ".sav"); - newsave += Platform::InstanceFileSuffix(); - if (oldsave != newsave) - GBASave->SetPath(newsave, false); - } - - InitFirmwareSaveManager(thread); - if (FirmwareSave) - { - std::string oldsave = FirmwareSave->GetPath(); - string newsave; - if (Config::ExternalBIOSEnable) - { - if (Config::ConsoleType == 1) - newsave = Config::DSiFirmwarePath + Platform::InstanceFileSuffix(); - else - newsave = Config::FirmwarePath + Platform::InstanceFileSuffix(); - } - else - { - newsave = Config::WifiSettingsPath + Platform::InstanceFileSuffix(); - } - - if (oldsave != newsave) - { // If the player toggled the ConsoleType or ExternalBIOSEnable... - FirmwareSave->SetPath(newsave, true); - } - } - - if (!BaseROMName.empty()) - { - if (Config::DirectBoot || thread->NDS->NeedsDirectBoot()) - { - thread->NDS->SetupDirectBoot(BaseROMName); - } - } -} - - -bool BootToMenu(EmuThread* thread) -{ - // Keep whatever cart is in the console, if any. - if (!thread->UpdateConsole(Keep {}, Keep {})) - // Try to update the console, but keep the existing cart. If that fails... - return false; - - // BIOS and firmware files are loaded, patched, and installed in UpdateConsole - if (thread->NDS->NeedsDirectBoot()) - return false; - - InitFirmwareSaveManager(thread); - thread->NDS->Reset(); - SetBatteryLevels(*thread->NDS); - SetDateTime(*thread->NDS); - return true; -} - -u32 DecompressROM(const u8* inContent, const u32 inSize, unique_ptr& outContent) -{ - u64 realSize = ZSTD_getFrameContentSize(inContent, inSize); - const u32 maxSize = 0x40000000; - - if (realSize == ZSTD_CONTENTSIZE_ERROR || (realSize > maxSize && realSize != ZSTD_CONTENTSIZE_UNKNOWN)) - { - return 0; - } - - if (realSize != ZSTD_CONTENTSIZE_UNKNOWN) - { - outContent = make_unique(realSize); - u64 decompressed = ZSTD_decompress(outContent.get(), realSize, inContent, inSize); - - if (ZSTD_isError(decompressed)) - { - outContent = nullptr; - return 0; - } - - return realSize; - } - else - { - ZSTD_DStream* dStream = ZSTD_createDStream(); - ZSTD_initDStream(dStream); - - ZSTD_inBuffer inBuf = { - .src = inContent, - .size = inSize, - .pos = 0 - }; - - const u32 startSize = 1024 * 1024 * 16; - u8* partialOutContent = (u8*) malloc(startSize); - - ZSTD_outBuffer outBuf = { - .dst = partialOutContent, - .size = startSize, - .pos = 0 - }; - - size_t result; - - do - { - result = ZSTD_decompressStream(dStream, &outBuf, &inBuf); - - if (ZSTD_isError(result)) - { - ZSTD_freeDStream(dStream); - free(outBuf.dst); - return 0; - } - - // if result == 0 and not inBuf.pos < inBuf.size, go again to let zstd flush everything. - if (result == 0) - continue; - - if (outBuf.pos == outBuf.size) - { - outBuf.size *= 2; - - if (outBuf.size > maxSize) - { - ZSTD_freeDStream(dStream); - free(outBuf.dst); - return 0; - } - - outBuf.dst = realloc(outBuf.dst, outBuf.size); - } - } while (inBuf.pos < inBuf.size); - - ZSTD_freeDStream(dStream); - outContent = make_unique(outBuf.pos); - memcpy(outContent.get(), outBuf.dst, outBuf.pos); - - ZSTD_freeDStream(dStream); - free(outBuf.dst); - - return outBuf.size; - } -} - -void ClearBackupState() -{ - if (BackupState != nullptr) - { - BackupState = nullptr; - } -} - -pair, string> GenerateDefaultFirmware() -{ - // Construct the default firmware... - string settingspath; - std::unique_ptr firmware = std::make_unique(Config::ConsoleType); - assert(firmware->Buffer() != nullptr); - - // Try to open the instanced Wi-fi settings, falling back to the regular Wi-fi settings if they don't exist. - // We don't need to save the whole firmware, just the part that may actually change. - std::string wfcsettingspath = Config::WifiSettingsPath; - settingspath = wfcsettingspath + Platform::InstanceFileSuffix(); - FileHandle* f = Platform::OpenLocalFile(settingspath, FileMode::Read); - if (!f) - { - settingspath = wfcsettingspath; - f = Platform::OpenLocalFile(settingspath, FileMode::Read); - } - - // If using generated firmware, we keep the wi-fi settings on the host disk separately. - // Wi-fi access point data includes Nintendo WFC settings, - // and if we didn't keep them then the player would have to reset them in each session. - if (f) - { // If we have Wi-fi settings to load... - constexpr unsigned TOTAL_WFC_SETTINGS_SIZE = 3 * (sizeof(Firmware::WifiAccessPoint) + sizeof(Firmware::ExtendedWifiAccessPoint)); - - // The access point and extended access point segments might - // be in different locations depending on the firmware revision, - // but our generated firmware always keeps them next to each other. - // (Extended access points first, then regular ones.) - - if (!FileRead(firmware->GetExtendedAccessPointPosition(), TOTAL_WFC_SETTINGS_SIZE, 1, f)) - { // If we couldn't read the Wi-fi settings from this file... - Platform::Log(Platform::LogLevel::Warn, "Failed to read Wi-fi settings from \"%s\"; using defaults instead\n", wfcsettingspath.c_str()); - - firmware->GetAccessPoints() = { - Firmware::WifiAccessPoint(Config::ConsoleType), - Firmware::WifiAccessPoint(), - Firmware::WifiAccessPoint(), - }; - - firmware->GetExtendedAccessPoints() = { - Firmware::ExtendedWifiAccessPoint(), - Firmware::ExtendedWifiAccessPoint(), - Firmware::ExtendedWifiAccessPoint(), - }; - } - - firmware->UpdateChecksums(); - - CloseFile(f); - } - - // If we don't have Wi-fi settings to load, - // then the defaults will have already been populated by the constructor. - return std::make_pair(std::move(firmware), std::move(wfcsettingspath)); -} - -bool ParseMacAddress(void* data) -{ - const std::string& mac_in = Config::FirmwareMAC; - u8* mac_out = (u8*)data; - - int o = 0; - u8 tmp = 0; - for (int i = 0; i < 18; i++) - { - char c = mac_in[i]; - if (c == '\0') break; - - int n; - if (c >= '0' && c <= '9') n = c - '0'; - else if (c >= 'a' && c <= 'f') n = c - 'a' + 10; - else if (c >= 'A' && c <= 'F') n = c - 'A' + 10; - else continue; - - if (!(o & 1)) - tmp = n; - else - mac_out[o >> 1] = n | (tmp << 4); - - o++; - if (o >= 12) return true; - } - - return false; -} - -void CustomizeFirmware(Firmware& firmware) noexcept -{ - auto& currentData = firmware.GetEffectiveUserData(); - - // setting up username - std::string orig_username = Config::FirmwareUsername; - if (!orig_username.empty()) - { // If the frontend defines a username, take it. If not, leave the existing one. - std::u16string username = std::wstring_convert, char16_t>{}.from_bytes(orig_username); - size_t usernameLength = std::min(username.length(), (size_t) 10); - currentData.NameLength = usernameLength; - memcpy(currentData.Nickname, username.data(), usernameLength * sizeof(char16_t)); - } - - auto language = static_cast(Config::FirmwareLanguage); - if (language != Firmware::Language::Reserved) - { // If the frontend specifies a language (rather than using the existing value)... - currentData.Settings &= ~Firmware::Language::Reserved; // ..clear the existing language... - currentData.Settings |= language; // ...and set the new one. - } - - // setting up color - u8 favoritecolor = Config::FirmwareFavouriteColour; - if (favoritecolor != 0xFF) - { - currentData.FavoriteColor = favoritecolor; - } - - u8 birthmonth = Config::FirmwareBirthdayMonth; - if (birthmonth != 0) - { // If the frontend specifies a birth month (rather than using the existing value)... - currentData.BirthdayMonth = birthmonth; - } - - u8 birthday = Config::FirmwareBirthdayDay; - if (birthday != 0) - { // If the frontend specifies a birthday (rather than using the existing value)... - currentData.BirthdayDay = birthday; - } - - // setup message - std::string orig_message = Config::FirmwareMessage; - if (!orig_message.empty()) - { - std::u16string message = std::wstring_convert, char16_t>{}.from_bytes(orig_message); - size_t messageLength = std::min(message.length(), (size_t) 26); - currentData.MessageLength = messageLength; - memcpy(currentData.Message, message.data(), messageLength * sizeof(char16_t)); - } - - MacAddress mac; - bool rep = false; - auto& header = firmware.GetHeader(); - - memcpy(&mac, header.MacAddr.data(), sizeof(MacAddress)); - - - MacAddress configuredMac; - rep = ParseMacAddress(&configuredMac); - rep &= (configuredMac != MacAddress()); - - if (rep) - { - mac = configuredMac; - } - - 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 - header.MacAddr = mac; - header.UpdateChecksum(); - } - - firmware.UpdateChecksums(); -} - -// Loads ROM data without parsing it. Works for GBA and NDS ROMs. -bool LoadROMData(const QStringList& filepath, std::unique_ptr& filedata, u32& filelen, string& basepath, string& romname) noexcept -{ - if (filepath.empty()) return false; - - if (int num = filepath.count(); num == 1) - { - // regular file - - std::string filename = filepath.at(0).toStdString(); - Platform::FileHandle* f = Platform::OpenFile(filename, FileMode::Read); - if (!f) return false; - - long len = Platform::FileLength(f); - if (len > 0x40000000) - { - Platform::CloseFile(f); - return false; - } - - Platform::FileRewind(f); - filedata = make_unique(len); - size_t nread = Platform::FileRead(filedata.get(), (size_t)len, 1, f); - Platform::CloseFile(f); - if (nread != 1) - { - filedata = nullptr; - return false; - } - - filelen = (u32)len; - - if (filename.length() > 4 && filename.substr(filename.length() - 4) == ".zst") - { - filelen = DecompressROM(filedata.get(), len, filedata); - - if (filelen > 0) - { - filename = filename.substr(0, filename.length() - 4); - } - else - { - filedata = nullptr; - filelen = 0; - basepath = ""; - romname = ""; - return false; - } - } - - int pos = LastSep(filename); - if(pos != -1) - basepath = filename.substr(0, pos); - - romname = filename.substr(pos+1); - return true; - } -#ifdef ARCHIVE_SUPPORT_ENABLED - else if (num == 2) - { - // file inside archive - - s32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), filedata, &filelen); - if (lenread < 0) return false; - if (!filedata) return false; - if (lenread != filelen) - { - filedata = nullptr; - return false; - } - - std::string std_archivepath = filepath.at(0).toStdString(); - basepath = std_archivepath.substr(0, LastSep(std_archivepath)); - - std::string std_romname = filepath.at(1).toStdString(); - romname = std_romname.substr(LastSep(std_romname)+1); - return true; - } -#endif - else - return false; -} - -bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) -{ - unique_ptr filedata = nullptr; - u32 filelen; - std::string basepath; - std::string romname; - - if (!LoadROMData(filepath, filedata, filelen, basepath, romname)) - return false; - - NDSSave = nullptr; - - BaseROMDir = basepath; - BaseROMName = romname; - BaseAssetName = romname.substr(0, romname.rfind('.')); - - u32 savelen = 0; - std::unique_ptr savedata = nullptr; - - std::string savname = GetAssetPath(false, Config::SaveFilePath, ".sav"); - std::string origsav = savname; - savname += Platform::InstanceFileSuffix(); - - FileHandle* sav = Platform::OpenFile(savname, FileMode::Read); - if (!sav) sav = Platform::OpenFile(origsav, FileMode::Read); - if (sav) - { - savelen = (u32)Platform::FileLength(sav); - - FileRewind(sav); - savedata = std::make_unique(savelen); - FileRead(savedata.get(), savelen, 1, sav); - CloseFile(sav); - } - - NDSCart::NDSCartArgs cartargs { - // Don't load the SD card itself yet, because we don't know if - // the ROM is homebrew or not. - // So this is the card we *would* load if the ROM were homebrew. - .SDCard = GetDLDISDCardArgs(), - .SRAM = std::move(savedata), - .SRAMLength = savelen, - }; - - auto cart = NDSCart::ParseROM(std::move(filedata), filelen, std::move(cartargs)); - if (!cart) - // If we couldn't parse the ROM... - return false; - - if (reset) - { - if (!emuthread->UpdateConsole(std::move(cart), Keep {})) - return false; - - InitFirmwareSaveManager(emuthread); - emuthread->NDS->Reset(); - - if (Config::DirectBoot || emuthread->NDS->NeedsDirectBoot()) - { // If direct boot is enabled or forced... - emuthread->NDS->SetupDirectBoot(romname); - } - - SetBatteryLevels(*emuthread->NDS); - SetDateTime(*emuthread->NDS); - } - else - { - assert(emuthread->NDS != nullptr); - emuthread->NDS->SetNDSCart(std::move(cart)); - } - - CartType = 0; - NDSSave = std::make_unique(savname); - LoadCheats(*emuthread->NDS); - - return true; -} - -void EjectCart(NDS& nds) -{ - NDSSave = nullptr; - - UnloadCheats(nds); - - nds.EjectCart(); - - CartType = -1; - BaseROMDir = ""; - BaseROMName = ""; - BaseAssetName = ""; -} - -bool CartInserted() -{ - return CartType != -1; -} - -QString CartLabel() -{ - if (CartType == -1) - return "(none)"; - - QString ret = QString::fromStdString(BaseROMName); - - int maxlen = 32; - if (ret.length() > maxlen) - ret = ret.left(maxlen-6) + "..." + ret.right(3); - - return ret; -} - - -bool LoadGBAROM(NDS& nds, QStringList filepath) -{ - if (nds.ConsoleType == 1) return false; // DSi doesn't have a GBA slot - - unique_ptr filedata = nullptr; - u32 filelen; - std::string basepath; - std::string romname; - - if (!LoadROMData(filepath, filedata, filelen, basepath, romname)) - return false; - - GBASave = nullptr; - - BaseGBAROMDir = basepath; - BaseGBAROMName = romname; - BaseGBAAssetName = romname.substr(0, romname.rfind('.')); - - u32 savelen = 0; - std::unique_ptr savedata = nullptr; - - std::string savname = GetAssetPath(true, Config::SaveFilePath, ".sav"); - std::string origsav = savname; - savname += Platform::InstanceFileSuffix(); - - FileHandle* sav = Platform::OpenFile(savname, FileMode::Read); - if (!sav) sav = Platform::OpenFile(origsav, FileMode::Read); - if (sav) - { - savelen = (u32)FileLength(sav); - - if (savelen > 0) - { - FileRewind(sav); - savedata = std::make_unique(savelen); - FileRead(savedata.get(), savelen, 1, sav); - } - CloseFile(sav); - } - - auto cart = GBACart::ParseROM(std::move(filedata), filelen, std::move(savedata), savelen); - if (!cart) - return false; - - nds.SetGBACart(std::move(cart)); - GBACartType = 0; - GBASave = std::make_unique(savname); - return true; -} - -void LoadGBAAddon(NDS& nds, int type) -{ - if (Config::ConsoleType == 1) return; - - GBASave = nullptr; - - nds.LoadGBAAddon(type); - - GBACartType = type; - BaseGBAROMDir = ""; - BaseGBAROMName = ""; - BaseGBAAssetName = ""; -} - -void EjectGBACart(NDS& nds) -{ - GBASave = nullptr; - - nds.EjectGBACart(); - - GBACartType = -1; - BaseGBAROMDir = ""; - BaseGBAROMName = ""; - BaseGBAAssetName = ""; -} - -bool GBACartInserted() -{ - return GBACartType != -1; -} - -QString GBACartLabel() -{ - if (Config::ConsoleType == 1) return "none (DSi)"; - - switch (GBACartType) - { - case 0: - { - QString ret = QString::fromStdString(BaseGBAROMName); - - int maxlen = 32; - if (ret.length() > maxlen) - ret = ret.left(maxlen-6) + "..." + ret.right(3); - - return ret; - } - - case GBAAddon_RAMExpansion: - return "Memory expansion"; - } - - return "(none)"; -} - - -void ROMIcon(const u8 (&data)[512], const u16 (&palette)[16], u32 (&iconRef)[32*32]) -{ - int index = 0; - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 4; j++) - { - for (int k = 0; k < 8; k++) - { - for (int l = 0; l < 8; l++) - { - u8 pal_index = index % 2 ? data[index/2] >> 4 : data[index/2] & 0x0F; - u8 r = ((palette[pal_index] >> 0) & 0x1F) * 255 / 31; - u8 g = ((palette[pal_index] >> 5) & 0x1F) * 255 / 31; - u8 b = ((palette[pal_index] >> 10) & 0x1F) * 255 / 31; - u8 a = pal_index ? 255: 0; - u32* row = &iconRef[256 * i + 32 * k + 8 * j]; - row[l] = r | (g << 8) | (b << 16) | (a << 24); - index++; - } - } - } - } -} - -#define SEQ_FLIPV(i) ((i & 0b1000000000000000) >> 15) -#define SEQ_FLIPH(i) ((i & 0b0100000000000000) >> 14) -#define SEQ_PAL(i) ((i & 0b0011100000000000) >> 11) -#define SEQ_BMP(i) ((i & 0b0000011100000000) >> 8) -#define SEQ_DUR(i) ((i & 0b0000000011111111) >> 0) - -void AnimatedROMIcon(const u8 (&data)[8][512], const u16 (&palette)[8][16], const u16 (&sequence)[64], u32 (&animatedIconRef)[64][32*32], std::vector &animatedSequenceRef) -{ - for (int i = 0; i < 64; i++) - { - if (!sequence[i]) - break; - - ROMIcon(data[SEQ_BMP(sequence[i])], palette[SEQ_PAL(sequence[i])], animatedIconRef[i]); - u32* frame = animatedIconRef[i]; - - if (SEQ_FLIPH(sequence[i])) - { - for (int x = 0; x < 32; x++) - { - for (int y = 0; y < 32/2; y++) - { - std::swap(frame[x * 32 + y], frame[x * 32 + (32 - 1 - y)]); - } - } - } - if (SEQ_FLIPV(sequence[i])) - { - for (int x = 0; x < 32/2; x++) - { - for (int y = 0; y < 32; y++) - { - std::swap(frame[x * 32 + y], frame[(32 - 1 - x) * 32 + y]); - } - } - } - - for (int j = 0; j < SEQ_DUR(sequence[i]); j++) - animatedSequenceRef.push_back(i); - } -} - -} diff --git a/src/frontend/qt_sdl/ROMManager.h b/src/frontend/qt_sdl/ROMManager.h deleted file mode 100644 index 0b640c84..00000000 --- a/src/frontend/qt_sdl/ROMManager.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - Copyright 2016-2023 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 ROMMANAGER_H -#define ROMMANAGER_H - -#include "types.h" -#include "SaveManager.h" -#include "AREngine.h" -#include "DSi_NAND.h" - -#include "MemConstants.h" -#include -#include -#include -#include - -namespace melonDS -{ -class NDS; -class DSi; -class FATStorage; -class FATStorageArgs; -} -class EmuThread; -namespace ROMManager -{ - -using namespace melonDS; -extern std::unique_ptr NDSSave; -extern std::unique_ptr GBASave; -extern std::unique_ptr FirmwareSave; - -QString VerifySetup(); -void Reset(EmuThread* thread); - -/// Boots the emulated console into its system menu without starting a game. -bool BootToMenu(EmuThread* thread); -void ClearBackupState(); - -/// Returns the configured ARM9 BIOS loaded from disk, -/// the FreeBIOS if external BIOS is disabled and we're in NDS mode, -/// or nullopt if loading failed. -std::optional> LoadARM9BIOS() noexcept; -std::optional> LoadARM7BIOS() noexcept; -std::optional> LoadDSiARM9BIOS() noexcept; -std::optional> LoadDSiARM7BIOS() noexcept; -std::optional GetDSiSDCardArgs() noexcept; -std::optional LoadDSiSDCard() noexcept; -std::optional GetDLDISDCardArgs() noexcept; -std::optional LoadDLDISDCard() noexcept; -void CustomizeFirmware(Firmware& firmware) noexcept; -Firmware GenerateFirmware(int type) noexcept; -/// Loads and customizes a firmware image based on the values in Config -std::optional LoadFirmware(int type) noexcept; -/// Loads and customizes a NAND image based on the values in Config -std::optional LoadNAND(const std::array& arm7ibios) noexcept; - -/// Inserts a ROM into the emulated console. -bool LoadROM(EmuThread*, QStringList filepath, bool reset); -void EjectCart(NDS& nds); -bool CartInserted(); -QString CartLabel(); - -bool LoadGBAROM(NDS& nds, QStringList filepath); -void LoadGBAAddon(NDS& nds, int type); -void EjectGBACart(NDS& nds); -bool GBACartInserted(); -QString GBACartLabel(); - -std::string GetSavestateName(int slot); -bool SavestateExists(int slot); -bool LoadState(NDS& nds, const std::string& filename); -bool SaveState(NDS& nds, const std::string& filename); -void UndoStateLoad(NDS& nds); - -void EnableCheats(NDS& nds, bool enable); -ARCodeFile* GetCheatFile(); - -void ROMIcon(const u8 (&data)[512], const u16 (&palette)[16], u32 (&iconRef)[32*32]); -void AnimatedROMIcon(const u8 (&data)[8][512], const u16 (&palette)[8][16], - const u16 (&sequence)[64], u32 (&animatedIconRef)[64][32*32], - std::vector &animatedSequenceRef); -} - -#endif // ROMMANAGER_H diff --git a/src/frontend/qt_sdl/SaveManager.cpp b/src/frontend/qt_sdl/SaveManager.cpp index 55279dca..ec8e08d3 100644 --- a/src/frontend/qt_sdl/SaveManager.cpp +++ b/src/frontend/qt_sdl/SaveManager.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/SaveManager.h b/src/frontend/qt_sdl/SaveManager.h index d7132e69..1b0f226a 100644 --- a/src/frontend/qt_sdl/SaveManager.h +++ b/src/frontend/qt_sdl/SaveManager.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/Screen.cpp b/src/frontend/qt_sdl/Screen.cpp index cfcbeed9..10abe1ce 100644 --- a/src/frontend/qt_sdl/Screen.cpp +++ b/src/frontend/qt_sdl/Screen.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -16,20 +16,13 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#include -#include -#include #include #include -#include -#include -#include #include #include #include -#include #ifndef _WIN32 #ifndef APPLE #include @@ -41,6 +34,7 @@ #include "duckstation/gl/context.h" #include "main.h" +#include "EmuInstance.h" #include "NDS.h" #include "GPU.h" @@ -52,31 +46,65 @@ #include "main_shaders.h" #include "OSD_shaders.h" #include "font.h" +#include "version.h" using namespace melonDS; -// TEMP -extern MainWindow* mainWindow; -extern EmuThread* emuThread; -extern bool RunningSomething; -extern int autoScreenSizing; - -extern int videoRenderer; -extern bool videoSettingsDirty; - const u32 kOSDMargin = 6; +const int kLogoWidth = 192; ScreenPanel::ScreenPanel(QWidget* parent) : QWidget(parent) { setMouseTracking(true); setAttribute(Qt::WA_AcceptTouchEvents); + + QWidget* w = parent; + for (;;) + { + mainWindow = qobject_cast(w); + if (mainWindow) break; + w = w->parentWidget(); + if (!w) break; + } + + emuInstance = mainWindow->getEmuInstance(); + + mouseHide = false; + mouseHideDelay = 0; + QTimer* mouseTimer = setupMouseTimer(); - connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) setCursor(Qt::BlankCursor);}); + connect(mouseTimer, &QTimer::timeout, [=] { if (mouseHide) setCursor(Qt::BlankCursor);}); osdEnabled = false; osdID = 1; + + loadConfig(); + setFilter(mainWindow->getWindowConfig().GetBool("ScreenFilter")); + + splashLogo = QPixmap(":/melon-logo"); + + strncpy(splashText[0].text, "File->Open ROM...", 256); + splashText[0].id = 0x80000000; + splashText[0].color = 0; + splashText[0].rendered = false; + splashText[0].rainbowstart = -1; + + strncpy(splashText[1].text, "to get started", 256); + splashText[1].id = 0x80000001; + splashText[1].color = 0; + splashText[1].rendered = false; + splashText[1].rainbowstart = -1; + + std::string url = MELONDS_URL; + int urlpos = url.find("://"); + urlpos = (urlpos == std::string::npos) ? 0 : urlpos+3; + strncpy(splashText[2].text, url.c_str() + urlpos, 256); + splashText[2].id = 0x80000002; + splashText[2].color = 0; + splashText[2].rendered = false; + splashText[2].rainbowstart = -1; } ScreenPanel::~ScreenPanel() @@ -85,21 +113,48 @@ ScreenPanel::~ScreenPanel() delete mouseTimer; } +void ScreenPanel::loadConfig() +{ + auto& cfg = mainWindow->getWindowConfig(); + + screenRotation = cfg.GetInt("ScreenRotation"); + screenGap = cfg.GetInt("ScreenGap"); + screenLayout = cfg.GetInt("ScreenLayout"); + screenSwap = cfg.GetBool("ScreenSwap"); + screenSizing = cfg.GetInt("ScreenSizing"); + integerScaling = cfg.GetBool("IntegerScaling"); + screenAspectTop = cfg.GetInt("ScreenAspectTop"); + screenAspectBot = cfg.GetInt("ScreenAspectBot"); +} + +void ScreenPanel::setFilter(bool filter) +{ + this->filter = filter; +} + +void ScreenPanel::setMouseHide(bool enable, int delay) +{ + mouseHide = enable; + mouseHideDelay = delay; + + mouseTimer->setInterval(mouseHideDelay); +} + void ScreenPanel::setupScreenLayout() { int w = width(); int h = height(); - int sizing = Config::ScreenSizing; - if (sizing == 3) sizing = autoScreenSizing; + int sizing = screenSizing; + if (sizing == screenSizing_Auto) sizing = autoScreenSizing; float aspectTop, aspectBot; for (auto ratio : aspectRatios) { - if (ratio.id == Config::ScreenAspectTop) + if (ratio.id == screenAspectTop) aspectTop = ratio.ratio; - if (ratio.id == Config::ScreenAspectBot) + if (ratio.id == screenAspectBot) aspectBot = ratio.ratio; } @@ -109,49 +164,51 @@ void ScreenPanel::setupScreenLayout() if (aspectBot == 0) aspectBot = ((float) w / h) / (4.f / 3.f); - Frontend::SetupScreenLayout(w, h, - static_cast(Config::ScreenLayout), - static_cast(Config::ScreenRotation), - static_cast(sizing), - Config::ScreenGap, - Config::IntegerScaling != 0, - Config::ScreenSwap != 0, - aspectTop, - aspectBot); + layout.Setup(w, h, + static_cast(screenLayout), + static_cast(screenRotation), + static_cast(sizing), + screenGap, + integerScaling != 0, + screenSwap != 0, + aspectTop, + aspectBot); - numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind); + numScreens = layout.GetScreenTransforms(screenMatrix[0], screenKind); + + calcSplashLayout(); } QSize ScreenPanel::screenGetMinSize(int factor = 1) { - bool isHori = (Config::ScreenRotation == Frontend::screenRot_90Deg - || Config::ScreenRotation == Frontend::screenRot_270Deg); - int gap = Config::ScreenGap * factor; + bool isHori = (screenRotation == screenRot_90Deg + || screenRotation == screenRot_270Deg); + int gap = screenGap * factor; int w = 256 * factor; int h = 192 * factor; - if (Config::ScreenSizing == Frontend::screenSizing_TopOnly - || Config::ScreenSizing == Frontend::screenSizing_BotOnly) + if (screenSizing == screenSizing_TopOnly + || screenSizing == screenSizing_BotOnly) { return QSize(w, h); } - if (Config::ScreenLayout == Frontend::screenLayout_Natural) + if (screenLayout == screenLayout_Natural) { if (isHori) return QSize(h+gap+h, w); else return QSize(w, h+gap+h); } - else if (Config::ScreenLayout == Frontend::screenLayout_Vertical) + else if (screenLayout == screenLayout_Vertical) { if (isHori) return QSize(h, w+gap+w); else return QSize(w, h+gap+h); } - else if (Config::ScreenLayout == Frontend::screenLayout_Horizontal) + else if (screenLayout == screenLayout_Horizontal) { if (isHori) return QSize(h+gap+h, w); @@ -169,10 +226,20 @@ QSize ScreenPanel::screenGetMinSize(int factor = 1) void ScreenPanel::onScreenLayoutChanged() { + loadConfig(); + setMinimumSize(screenGetMinSize()); setupScreenLayout(); } +void ScreenPanel::onAutoScreenSizingChanged(int sizing) +{ + autoScreenSizing = sizing; + if (screenSizing != screenSizing_Auto) return; + + setupScreenLayout(); +} + void ScreenPanel::resizeEvent(QResizeEvent* event) { setupScreenLayout(); @@ -182,29 +249,29 @@ void ScreenPanel::resizeEvent(QResizeEvent* event) void ScreenPanel::mousePressEvent(QMouseEvent* event) { event->accept(); + if (!emuInstance->emuIsActive()) { touching = false; return; } if (event->button() != Qt::LeftButton) return; int x = event->pos().x(); int y = event->pos().y(); - if (Frontend::GetTouchCoords(x, y, false)) + if (layout.GetTouchCoords(x, y, false)) { touching = true; - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); + emuInstance->touchScreen(x, y); } } void ScreenPanel::mouseReleaseEvent(QMouseEvent* event) { event->accept(); + if (!emuInstance->emuIsActive()) { touching = false; return; } if (event->button() != Qt::LeftButton) return; if (touching) { touching = false; - assert(emuThread->NDS != nullptr); - emuThread->NDS->ReleaseScreen(); + emuInstance->releaseScreen(); } } @@ -214,44 +281,48 @@ void ScreenPanel::mouseMoveEvent(QMouseEvent* event) showCursor(); - if (!(event->buttons() & Qt::LeftButton)) return; + if (!emuInstance->emuIsActive()) return; + //if (!(event->buttons() & Qt::LeftButton)) return; if (!touching) return; int x = event->pos().x(); int y = event->pos().y(); - if (Frontend::GetTouchCoords(x, y, true)) + if (layout.GetTouchCoords(x, y, true)) { - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); + emuInstance->touchScreen(x, y); } } void ScreenPanel::tabletEvent(QTabletEvent* event) { event->accept(); + if (!emuInstance->emuIsActive()) { touching = false; return; } switch(event->type()) { case QEvent::TabletPress: case QEvent::TabletMove: { +#if QT_VERSION_MAJOR == 6 + int x = event->position().x(); + int y = event->position().y(); +#else int x = event->x(); int y = event->y(); +#endif - if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TabletMove)) + if (layout.GetTouchCoords(x, y, event->type()==QEvent::TabletMove)) { touching = true; - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); + emuInstance->touchScreen(x, y); } } break; case QEvent::TabletRelease: if (touching) { - assert(emuThread->NDS != nullptr); - emuThread->NDS->ReleaseScreen(); + emuInstance->releaseScreen(); touching = false; } break; @@ -262,31 +333,41 @@ void ScreenPanel::tabletEvent(QTabletEvent* event) void ScreenPanel::touchEvent(QTouchEvent* event) { +#if QT_VERSION_MAJOR == 6 + if (event->device()->type() == QInputDevice::DeviceType::TouchPad) + return; +#endif + event->accept(); + if (!emuInstance->emuIsActive()) { touching = false; return; } switch(event->type()) { case QEvent::TouchBegin: case QEvent::TouchUpdate: +#if QT_VERSION_MAJOR == 6 + if (event->points().length() > 0) + { + QPointF lastPosition = event->points().first().lastPosition(); +#else if (event->touchPoints().length() > 0) { QPointF lastPosition = event->touchPoints().first().lastPos(); +#endif int x = (int)lastPosition.x(); int y = (int)lastPosition.y(); - if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TouchUpdate)) + if (layout.GetTouchCoords(x, y, event->type()==QEvent::TouchUpdate)) { touching = true; - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); + emuInstance->touchScreen(x, y); } } break; case QEvent::TouchEnd: if (touching) { - assert(emuThread->NDS != nullptr); - emuThread->NDS->ReleaseScreen(); + emuInstance->releaseScreen(); touching = false; } break; @@ -304,6 +385,10 @@ bool ScreenPanel::event(QEvent* event) touchEvent((QTouchEvent*)event); return true; } + else if (event->type() == QEvent::FocusIn) + mainWindow->onFocusIn(); + else if (event->type() == QEvent::FocusOut) + mainWindow->onFocusOut(); return QWidget::event(event); } @@ -318,7 +403,7 @@ QTimer* ScreenPanel::setupMouseTimer() { mouseTimer = new QTimer(); mouseTimer->setSingleShot(true); - mouseTimer->setInterval(Config::MouseHideSeconds*1000); + mouseTimer->setInterval(mouseHideDelay); mouseTimer->start(); return mouseTimer; @@ -422,8 +507,14 @@ void ScreenPanel::osdRenderItem(OSDItem* item) u32 color = item->color; bool rainbow = (color == 0); - u32 ticks = (u32)QDateTime::currentMSecsSinceEpoch(); - u32 rainbowinc = ((text[0] * 17) + (ticks * 13)) % 600; + u32 rainbowinc; + if (item->rainbowstart == -1) + { + u32 ticks = (u32) QDateTime::currentMSecsSinceEpoch(); + rainbowinc = ((text[0] * 17) + (ticks * 13)) % 600; + } + else + rainbowinc = (u32)item->rainbowstart; color |= 0xFF000000; const u32 shadow = 0xE0000000; @@ -521,6 +612,8 @@ void ScreenPanel::osdRenderItem(OSDItem* item) bitmap[(y * w) + x] = shadow; } } + + item->rainbowend = (int)rainbowinc; } void ScreenPanel::osdDeleteItem(OSDItem* item) @@ -542,11 +635,12 @@ void ScreenPanel::osdAddMessage(unsigned int color, const char* text) OSDItem item; - item.id = osdID++; + item.id = (osdID++) & 0x7FFFFFFF; item.timestamp = QDateTime::currentMSecsSinceEpoch(); strncpy(item.text, text, 255); item.text[255] = '\0'; item.color = color; item.rendered = false; + item.rainbowstart = -1; osdItems.push_back(item); @@ -580,6 +674,73 @@ void ScreenPanel::osdUpdate() it++; } + // render splashscreen text items if needed + + int rainbowinc = -1; + bool needrecalc = false; + + for (int i = 0; i < 3; i++) + { + if (!splashText[i].rendered) + { + splashText[i].rainbowstart = rainbowinc; + osdRenderItem(&splashText[i]); + splashText[i].rendered = true; + rainbowinc = splashText[i].rainbowend; + needrecalc = true; + } + } + + osdMutex.unlock(); + + if (needrecalc) + calcSplashLayout(); +} + +void ScreenPanel::calcSplashLayout() +{ + if (!splashText[0].rendered) + return; + + osdMutex.lock(); + + int w = width(); + int h = height(); + + int xlogo = (w - kLogoWidth) / 2; + int ylogo = (h - kLogoWidth) / 2; + + // top text + int totalwidth = splashText[0].bitmap.width() + 6 + splashText[1].bitmap.width(); + if (totalwidth >= w) + { + // stacked vertically + splashPos[0].setX((width() - splashText[0].bitmap.width()) / 2); + splashPos[1].setX((width() - splashText[1].bitmap.width()) / 2); + + int basey = ylogo / 2; + splashPos[0].setY(basey - splashText[0].bitmap.height() - 1); + splashPos[1].setY(basey + 1); + } + else + { + // horizontal + splashPos[0].setX((w - totalwidth) / 2); + splashPos[1].setX(splashPos[0].x() + splashText[0].bitmap.width() + 6); + + int basey = (ylogo - splashText[0].bitmap.height()) / 2; + splashPos[0].setY(basey); + splashPos[1].setY(basey); + } + + // bottom text + splashPos[2].setX((w - splashText[2].bitmap.width()) / 2); + splashPos[2].setY(ylogo + kLogoWidth + ((ylogo - splashText[2].bitmap.height()) / 2)); + + // logo + splashPos[3].setX(xlogo); + splashPos[3].setY(ylogo); + osdMutex.unlock(); } @@ -618,20 +779,25 @@ void ScreenPanelNative::paintEvent(QPaintEvent* event) // fill background painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0)); + auto emuThread = emuInstance->getEmuThread(); + if (emuThread->emuIsActive()) { - assert(emuThread->NDS != nullptr); - emuThread->FrontBufferLock.lock(); - int frontbuf = emuThread->FrontBuffer; - if (!emuThread->NDS->GPU.Framebuffer[frontbuf][0] || !emuThread->NDS->GPU.Framebuffer[frontbuf][1]) + emuInstance->renderLock.lock(); + auto nds = emuInstance->getNDS(); + + assert(nds != nullptr); + emuThread->frontBufferLock.lock(); + int frontbuf = emuThread->frontBuffer; + if (!nds->GPU.Framebuffer[frontbuf][0] || !nds->GPU.Framebuffer[frontbuf][1]) { - emuThread->FrontBufferLock.unlock(); + emuThread->frontBufferLock.unlock(); return; } - memcpy(screen[0].scanLine(0), emuThread->NDS->GPU.Framebuffer[frontbuf][0].get(), 256 * 192 * 4); - memcpy(screen[1].scanLine(0), emuThread->NDS->GPU.Framebuffer[frontbuf][1].get(), 256 * 192 * 4); - emuThread->FrontBufferLock.unlock(); + memcpy(screen[0].scanLine(0), nds->GPU.Framebuffer[frontbuf][0].get(), 256 * 192 * 4); + memcpy(screen[1].scanLine(0), nds->GPU.Framebuffer[frontbuf][1].get(), 256 * 192 * 4); + emuThread->frontBufferLock.unlock(); QRect screenrc(0, 0, 256, 192); @@ -640,9 +806,24 @@ void ScreenPanelNative::paintEvent(QPaintEvent* event) painter.setTransform(screenTrans[i]); painter.drawImage(screenrc, screen[screenKind[i]]); } + emuInstance->renderLock.unlock(); } osdUpdate(); + + if (!emuThread->emuIsActive()) + { + // splashscreen + osdMutex.lock(); + + painter.drawPixmap(QRect(splashPos[3], QSize(kLogoWidth, kLogoWidth)), splashLogo); + + for (int i = 0; i < 3; i++) + painter.drawImage(splashPos[i], splashText[i].bitmap); + + osdMutex.unlock(); + } + if (osdEnabled) { osdMutex.lock(); @@ -676,6 +857,8 @@ ScreenPanelGL::ScreenPanelGL(QWidget* parent) : ScreenPanel(parent) setAttribute(Qt::WA_KeyCompression, false); setFocusPolicy(Qt::StrongFocus); setMinimumSize(screenGetMinSize()); + + glInited = false; } ScreenPanelGL::~ScreenPanelGL() @@ -683,14 +866,27 @@ ScreenPanelGL::~ScreenPanelGL() bool ScreenPanelGL::createContext() { - std::optional windowInfo = getWindowInfo(); - std::array versionsToTry = { - GL::Context::Version{GL::Context::Profile::Core, 4, 3}, - GL::Context::Version{GL::Context::Profile::Core, 3, 2}}; - if (windowInfo.has_value()) + std::optional windowinfo = getWindowInfo(); + + // if our parent window is parented to another window, we will + // share our OpenGL context with that window + MainWindow* ourwin = (MainWindow*)parentWidget(); + MainWindow* parentwin = (MainWindow*)parentWidget()->parentWidget(); + //if (parentwin) + if (ourwin->getWindowID() != 0) { - glContext = GL::Context::Create(*getWindowInfo(), versionsToTry); - glContext->DoneCurrent(); + if (windowinfo.has_value()) + if (glContext = parentwin->getOGLContext()->CreateSharedContext(*windowinfo)) + glContext->DoneCurrent(); + } + else + { + std::array versionsToTry = { + GL::Context::Version{GL::Context::Profile::Core, 4, 3}, + GL::Context::Version{GL::Context::Profile::Core, 3, 2}}; + if (windowinfo.has_value()) + if (glContext = GL::Context::Create(*windowinfo, versionsToTry)) + glContext->DoneCurrent(); } return glContext != nullptr; @@ -706,22 +902,21 @@ void ScreenPanelGL::setSwapInterval(int intv) void ScreenPanelGL::initOpenGL() { if (!glContext) return; + if (glInited) return; glContext->MakeCurrent(); - OpenGL::BuildShaderProgram(kScreenVS, kScreenFS, screenShaderProgram, "ScreenShader"); - GLuint pid = screenShaderProgram[2]; - glBindAttribLocation(pid, 0, "vPosition"); - glBindAttribLocation(pid, 1, "vTexcoord"); - glBindFragDataLocation(pid, 0, "oColor"); + OpenGL::CompileVertexFragmentProgram(screenShaderProgram, + kScreenVS, kScreenFS, + "ScreenShader", + {{"vPosition", 0}, {"vTexcoord", 1}}, + {{"oColor", 0}}); - OpenGL::LinkShaderProgram(screenShaderProgram); + glUseProgram(screenShaderProgram); + glUniform1i(glGetUniformLocation(screenShaderProgram, "ScreenTex"), 0); - glUseProgram(pid); - glUniform1i(glGetUniformLocation(pid, "ScreenTex"), 0); - - screenShaderScreenSizeULoc = glGetUniformLocation(pid, "uScreenSize"); - screenShaderTransformULoc = glGetUniformLocation(pid, "uTransform"); + screenShaderScreenSizeULoc = glGetUniformLocation(screenShaderProgram, "uScreenSize"); + screenShaderTransformULoc = glGetUniformLocation(screenShaderProgram, "uTransform"); // to prevent bleeding between both parts of the screen // with bilinear filtering enabled @@ -770,20 +965,20 @@ void ScreenPanelGL::initOpenGL() glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256, 2, GL_RGBA, GL_UNSIGNED_BYTE, zeroData); - OpenGL::BuildShaderProgram(kScreenVS_OSD, kScreenFS_OSD, osdShader, "OSDShader"); + OpenGL::CompileVertexFragmentProgram(osdShader, + kScreenVS_OSD, kScreenFS_OSD, + "OSDShader", + {{"vPosition", 0}}, + {{"oColor", 0}}); - pid = osdShader[2]; - glBindAttribLocation(pid, 0, "vPosition"); - glBindFragDataLocation(pid, 0, "oColor"); + glUseProgram(osdShader); + glUniform1i(glGetUniformLocation(osdShader, "OSDTex"), 0); - OpenGL::LinkShaderProgram(osdShader); - glUseProgram(pid); - glUniform1i(glGetUniformLocation(pid, "OSDTex"), 0); - - osdScreenSizeULoc = glGetUniformLocation(pid, "uScreenSize"); - osdPosULoc = glGetUniformLocation(pid, "uOSDPos"); - osdSizeULoc = glGetUniformLocation(pid, "uOSDSize"); - osdScaleFactorULoc = glGetUniformLocation(pid, "uScaleFactor"); + osdScreenSizeULoc = glGetUniformLocation(osdShader, "uScreenSize"); + osdPosULoc = glGetUniformLocation(osdShader, "uOSDPos"); + osdSizeULoc = glGetUniformLocation(osdShader, "uOSDSize"); + osdScaleFactorULoc = glGetUniformLocation(osdShader, "uScaleFactor"); + osdTexScaleULoc = glGetUniformLocation(osdShader, "uTexScale"); const float osdvertices[6*2] = { @@ -804,21 +999,33 @@ void ScreenPanelGL::initOpenGL() glEnableVertexAttribArray(0); // position glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)(0)); + // splash logo texture + QImage logo = splashLogo.scaled(kLogoWidth*2, kLogoWidth*2).toImage(); + GLuint tex; + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, logo.width(), logo.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, logo.bits()); + logoTexture = tex; - glContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); transferLayout(); + glInited = true; } void ScreenPanelGL::deinitOpenGL() { if (!glContext) return; + if (!glInited) return; glDeleteTextures(1, &screenTexture); glDeleteVertexArrays(1, &screenVertexArray); glDeleteBuffers(1, &screenVertexBuffer); - OpenGL::DeleteShaderProgram(screenShaderProgram); + glDeleteProgram(screenShaderProgram); for (const auto& [key, tex] : osdTextures) @@ -830,12 +1037,22 @@ void ScreenPanelGL::deinitOpenGL() glDeleteVertexArrays(1, &osdVertexArray); glDeleteBuffers(1, &osdVertexBuffer); - OpenGL::DeleteShaderProgram(osdShader); + glDeleteTextures(1, &logoTexture); + + glDeleteProgram(osdShader); glContext->DoneCurrent(); lastScreenWidth = lastScreenHeight = -1; + glInited = false; +} + +void ScreenPanelGL::makeCurrentGL() +{ + if (!glContext) return; + + glContext->MakeCurrent(); } void ScreenPanelGL::osdRenderItem(OSDItem* item) @@ -869,7 +1086,10 @@ void ScreenPanelGL::osdDeleteItem(OSDItem* item) void ScreenPanelGL::drawScreenGL() { if (!glContext) return; - if (!emuThread->NDS) return; + + auto emuThread = emuInstance->getEmuThread(); + + glContext->MakeCurrent(); int w = windowInfo.surface_width; int h = windowInfo.surface_height; @@ -885,61 +1105,112 @@ void ScreenPanelGL::drawScreenGL() glViewport(0, 0, w, h); - glUseProgram(screenShaderProgram[2]); - glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor); + if (emuThread->emuIsActive()) + { + auto nds = emuInstance->getNDS(); - int frontbuf = emuThread->FrontBuffer; - glActiveTexture(GL_TEXTURE0); + glUseProgram(screenShaderProgram); + glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor); + + int frontbuf = emuThread->frontBuffer; + glActiveTexture(GL_TEXTURE0); #ifdef OGLRENDERER_ENABLED - if (emuThread->NDS->GPU.GetRenderer3D().Accelerated) - { - // hardware-accelerated render - static_cast(emuThread->NDS->GPU.GetRenderer3D()).GetCompositor().BindOutputTexture(frontbuf); - } - else -#endif - { - // regular render - glBindTexture(GL_TEXTURE_2D, screenTexture); - - if (emuThread->NDS->GPU.Framebuffer[frontbuf][0] && emuThread->NDS->GPU.Framebuffer[frontbuf][1]) + if (nds->GPU.GetRenderer3D().Accelerated) { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA, - GL_UNSIGNED_BYTE, emuThread->NDS->GPU.Framebuffer[frontbuf][0].get()); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192+2, 256, 192, GL_RGBA, - GL_UNSIGNED_BYTE, emuThread->NDS->GPU.Framebuffer[frontbuf][1].get()); + // hardware-accelerated render + nds->GPU.GetRenderer3D().BindOutputTexture(frontbuf); + } else +#endif + { + // regular render + glBindTexture(GL_TEXTURE_2D, screenTexture); + + if (nds->GPU.Framebuffer[frontbuf][0] && nds->GPU.Framebuffer[frontbuf][1]) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA, + GL_UNSIGNED_BYTE, nds->GPU.Framebuffer[frontbuf][0].get()); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192 + 2, 256, 192, GL_RGBA, + GL_UNSIGNED_BYTE, nds->GPU.Framebuffer[frontbuf][1].get()); + } } + + screenSettingsLock.lock(); + + GLint filter = this->filter ? GL_LINEAR : GL_NEAREST; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + + glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer); + glBindVertexArray(screenVertexArray); + + for (int i = 0; i < numScreens; i++) + { + glUniformMatrix2x3fv(screenShaderTransformULoc, 1, GL_TRUE, screenMatrix[i]); + glDrawArrays(GL_TRIANGLES, screenKind[i] == 0 ? 0 : 2 * 3, 2 * 3); + } + + screenSettingsLock.unlock(); } - screenSettingsLock.lock(); - - GLint filter = this->filter ? GL_LINEAR : GL_NEAREST; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); - - glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer); - glBindVertexArray(screenVertexArray); - - for (int i = 0; i < numScreens; i++) - { - glUniformMatrix2x3fv(screenShaderTransformULoc, 1, GL_TRUE, screenMatrix[i]); - glDrawArrays(GL_TRIANGLES, screenKind[i] == 0 ? 0 : 2*3, 2*3); - } - - screenSettingsLock.unlock(); - osdUpdate(); + + if (!emuThread->emuIsActive()) + { + // splashscreen + osdMutex.lock(); + + glUseProgram(osdShader); + + glUniform2f(osdScreenSizeULoc, w, h); + glUniform1f(osdScaleFactorULoc, factor); + glUniform1f(osdTexScaleULoc, 2.0); + + glBindBuffer(GL_ARRAY_BUFFER, osdVertexBuffer); + glBindVertexArray(osdVertexArray); + + glActiveTexture(GL_TEXTURE0); + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + glBindTexture(GL_TEXTURE_2D, logoTexture); + glUniform2i(osdPosULoc, splashPos[3].x(), splashPos[3].y()); + glUniform2i(osdSizeULoc, kLogoWidth, kLogoWidth); + glDrawArrays(GL_TRIANGLES, 0, 2*3); + + glUniform1f(osdTexScaleULoc, 1.0); + + for (int i = 0; i < 3; i++) + { + OSDItem& item = splashText[i]; + + if (!osdTextures.count(item.id)) + continue; + + glBindTexture(GL_TEXTURE_2D, osdTextures[item.id]); + glUniform2i(osdPosULoc, splashPos[i].x(), splashPos[i].y()); + glUniform2i(osdSizeULoc, item.bitmap.width(), item.bitmap.height()); + glDrawArrays(GL_TRIANGLES, 0, 2*3); + } + + glDisable(GL_BLEND); + glUseProgram(0); + + osdMutex.unlock(); + } + if (osdEnabled) { osdMutex.lock(); u32 y = kOSDMargin; - glUseProgram(osdShader[2]); + glUseProgram(osdShader); glUniform2f(osdScreenSizeULoc, w, h); glUniform1f(osdScaleFactorULoc, factor); + glUniform1f(osdTexScaleULoc, 1.0); glBindBuffer(GL_ARRAY_BUFFER, osdVertexBuffer); glBindVertexArray(osdVertexArray); @@ -1025,7 +1296,8 @@ std::optional ScreenPanelGL::getWindowInfo() } else { - qCritical() << "Unknown PNI platform " << platform_name; + //qCritical() << "Unknown PNI platform " << platform_name; + Platform::Log(Platform::LogLevel::Error, "Unknown PNI platform %s\n", platform_name.toStdString().c_str()); return std::nullopt; } #endif @@ -1064,7 +1336,6 @@ void ScreenPanelGL::transferLayout() lastScreenHeight = windowInfo->surface_height; } - this->filter = Config::ScreenFilter; this->windowInfo = *windowInfo; screenSettingsLock.unlock(); diff --git a/src/frontend/qt_sdl/Screen.h b/src/frontend/qt_sdl/Screen.h index c2f7fda1..a988815e 100644 --- a/src/frontend/qt_sdl/Screen.h +++ b/src/frontend/qt_sdl/Screen.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -31,11 +31,12 @@ #include #include "glad/glad.h" -#include "FrontendUtil.h" +#include "ScreenLayout.h" #include "duckstation/gl/context.h" -class EmuThread; +class MainWindow; +class EmuInstance; const struct { int id; float ratio; const char* label; } aspectRatios[] = @@ -57,6 +58,10 @@ public: explicit ScreenPanel(QWidget* parent); virtual ~ScreenPanel(); + void setFilter(bool filter); + + void setMouseHide(bool enable, int delay); + QTimer* setupMouseTimer(); void updateMouseTimer(); QTimer* mouseTimer; @@ -67,8 +72,34 @@ public: private slots: void onScreenLayoutChanged(); + void onAutoScreenSizingChanged(int sizing); protected: + MainWindow* mainWindow; + EmuInstance* emuInstance; + + bool filter; + + int screenRotation; + int screenGap; + int screenLayout; + bool screenSwap; + int screenSizing; + bool integerScaling; + int screenAspectTop, screenAspectBot; + + int autoScreenSizing; + + ScreenLayout layout; + float screenMatrix[kMaxScreenTransforms][6]; + int screenKind[kMaxScreenTransforms]; + int numScreens; + + bool touching = false; + + bool mouseHide; + int mouseHideDelay; + struct OSDItem { unsigned int id; @@ -79,6 +110,9 @@ protected: bool rendered; QImage bitmap; + + int rainbowstart; + int rainbowend; }; QMutex osdMutex; @@ -86,6 +120,12 @@ protected: unsigned int osdID; std::deque osdItems; + QPixmap splashLogo; + OSDItem splashText[3]; + QPoint splashPos[4]; + + void loadConfig(); + virtual void setupScreenLayout(); void resizeEvent(QResizeEvent* event) override; @@ -98,12 +138,6 @@ protected: void touchEvent(QTouchEvent* event); bool event(QEvent* event) override; - float screenMatrix[Frontend::MaxScreenTransforms][6]; - int screenKind[Frontend::MaxScreenTransforms]; - int numScreens; - - bool touching = false; - void showCursor(); int osdFindBreakPoint(const char* text, int i); @@ -114,6 +148,8 @@ protected: virtual void osdDeleteItem(OSDItem* item); void osdUpdate(); + + void calcSplashLayout(); }; @@ -132,7 +168,7 @@ private: void setupScreenLayout() override; QImage screen[2]; - QTransform screenTrans[Frontend::MaxScreenTransforms]; + QTransform screenTrans[kMaxScreenTransforms]; }; @@ -152,6 +188,7 @@ public: void initOpenGL(); void deinitOpenGL(); + void makeCurrentGL(); void drawScreenGL(); GL::Context* getContext() { return glContext.get(); } @@ -169,25 +206,28 @@ private: void setupScreenLayout() override; std::unique_ptr glContext; + bool glInited; GLuint screenVertexBuffer, screenVertexArray; GLuint screenTexture; - GLuint screenShaderProgram[3]; - GLuint screenShaderTransformULoc, screenShaderScreenSizeULoc; + GLuint screenShaderProgram; + GLint screenShaderTransformULoc, screenShaderScreenSizeULoc; QMutex screenSettingsLock; WindowInfo windowInfo; - bool filter; int lastScreenWidth = -1, lastScreenHeight = -1; - GLuint osdShader[3]; + GLuint osdShader; GLint osdScreenSizeULoc, osdPosULoc, osdSizeULoc; - GLfloat osdScaleFactorULoc; + GLint osdScaleFactorULoc; + GLint osdTexScaleULoc; GLuint osdVertexArray; GLuint osdVertexBuffer; std::map osdTextures; + GLuint logoTexture; + void osdRenderItem(OSDItem* item) override; void osdDeleteItem(OSDItem* item) override; }; diff --git a/src/frontend/qt_sdl/TitleManagerDialog.cpp b/src/frontend/qt_sdl/TitleManagerDialog.cpp index 21ca844e..30f29d24 100644 --- a/src/frontend/qt_sdl/TitleManagerDialog.cpp +++ b/src/frontend/qt_sdl/TitleManagerDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -23,7 +23,7 @@ #include "types.h" #include "Platform.h" #include "Config.h" -#include "ROMManager.h" +#include "main.h" #include "DSi_NAND.h" #include "TitleManagerDialog.h" @@ -36,14 +36,14 @@ using namespace melonDS::Platform; std::unique_ptr TitleManagerDialog::nand = nullptr; TitleManagerDialog* TitleManagerDialog::currentDlg = nullptr; -extern std::string EmuDirectory; - TitleManagerDialog::TitleManagerDialog(QWidget* parent, DSi_NAND::NANDImage& image) : QDialog(parent), ui(new Ui::TitleManagerDialog), nandmount(image) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + ui->lstTitleList->setIconSize(QSize(32, 32)); const u32 category = 0x00030004; @@ -113,7 +113,7 @@ void TitleManagerDialog::createTitleItem(u32 category, u32 titleid) nandmount.GetTitleInfo(category, titleid, version, &header, &banner); u32 icondata[32*32]; - ROMManager::ROMIcon(banner.Icon, banner.Palette, icondata); + emuInstance->romIcon(banner.Icon, banner.Palette, icondata); QImage iconimg((const uchar*)icondata, 32, 32, QImage::Format_RGBA8888); QIcon icon(QPixmap::fromImage(iconimg.copy())); @@ -140,7 +140,9 @@ bool TitleManagerDialog::openNAND() { nand = nullptr; - FileHandle* bios7i = Platform::OpenLocalFile(Config::DSiBIOS7Path, FileMode::Read); + Config::Table cfg = Config::GetGlobalTable(); + + FileHandle* bios7i = Platform::OpenLocalFile(cfg.GetString("DSi.BIOS7Path"), FileMode::Read); if (!bios7i) return false; @@ -149,7 +151,7 @@ bool TitleManagerDialog::openNAND() FileRead(es_keyY, 16, 1, bios7i); CloseFile(bios7i); - FileHandle* nandfile = Platform::OpenLocalFile(Config::DSiNANDPath, FileMode::ReadWriteExisting); + FileHandle* nandfile = Platform::OpenLocalFile(cfg.GetString("DSi.NANDPath"), FileMode::ReadWriteExisting); if (!nandfile) return false; @@ -296,12 +298,12 @@ void TitleManagerDialog::onImportTitleData() QString file = QFileDialog::getOpenFileName(this, "Select file to import...", - QString::fromStdString(EmuDirectory), + emuDirectory, "Title data files (" + extensions + ");;Any file (*.*)"); if (file.isEmpty()) return; - FILE* f = fopen(file.toStdString().c_str(), "rb"); + Platform::FileHandle* f = Platform::OpenFile(file.toStdString(), Platform::Read); if (!f) { QMessageBox::critical(this, @@ -310,9 +312,8 @@ void TitleManagerDialog::onImportTitleData() return; } - fseek(f, 0, SEEK_END); - u64 len = ftell(f); - fclose(f); + u64 len = Platform::FileLength(f); + Platform::CloseFile(f); if (len != wantedsize) { @@ -370,7 +371,7 @@ void TitleManagerDialog::onExportTitleData() QString file = QFileDialog::getSaveFileName(this, "Select path to export to...", - QString::fromStdString(EmuDirectory) + exportname, + emuDirectory + exportname, "Title data files (" + extensions + ");;Any file (*.*)"); if (file.isEmpty()) return; @@ -395,7 +396,11 @@ TitleImportDialog::TitleImportDialog(QWidget* parent, QString& apppath, const DS grpTmdSource = new QButtonGroup(this); grpTmdSource->addButton(ui->rbTmdFromFile, 0); grpTmdSource->addButton(ui->rbTmdFromNUS, 1); +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) connect(grpTmdSource, SIGNAL(buttonClicked(int)), this, SLOT(onChangeTmdSource(int))); +#else + connect(grpTmdSource, SIGNAL(idClicked(int)), this, SLOT(onChangeTmdSource(int))); +#endif grpTmdSource->button(0)->setChecked(true); } @@ -543,7 +548,7 @@ void TitleImportDialog::on_btnAppBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select title executable...", - QString::fromStdString(EmuDirectory), + emuDirectory, "DSiWare executables (*.app *.nds *.dsi *.srl);;Any file (*.*)"); if (file.isEmpty()) return; @@ -555,7 +560,7 @@ void TitleImportDialog::on_btnTmdBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select title metadata...", - QString::fromStdString(EmuDirectory), + emuDirectory, "DSiWare metadata (*.tmd);;Any file (*.*)"); if (file.isEmpty()) return; diff --git a/src/frontend/qt_sdl/TitleManagerDialog.h b/src/frontend/qt_sdl/TitleManagerDialog.h index 2e392ebf..984850b5 100644 --- a/src/frontend/qt_sdl/TitleManagerDialog.h +++ b/src/frontend/qt_sdl/TitleManagerDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -41,6 +41,8 @@ namespace Ui class TitleManagerDialog; class TitleImportDialog; +class EmuInstance; + class TitleManagerDialog : public QDialog { Q_OBJECT @@ -94,6 +96,8 @@ private slots: void onExportTitleData(); private: + EmuInstance* emuInstance; + melonDS::DSi_NAND::NANDMount nandmount; Ui::TitleManagerDialog* ui; diff --git a/src/frontend/qt_sdl/VideoSettingsDialog.cpp b/src/frontend/qt_sdl/VideoSettingsDialog.cpp index d5ee44c9..619ecda3 100644 --- a/src/frontend/qt_sdl/VideoSettingsDialog.cpp +++ b/src/frontend/qt_sdl/VideoSettingsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -16,84 +16,95 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#include #include #include #include "types.h" #include "Platform.h" #include "Config.h" +#include "GPU.h" +#include "main.h" #include "VideoSettingsDialog.h" #include "ui_VideoSettingsDialog.h" -inline bool UsesGL() +inline bool VideoSettingsDialog::UsesGL() { - return (Config::ScreenUseGL != 0) || (Config::_3DRenderer != 0); + auto& cfg = emuInstance->getGlobalConfig(); + return cfg.GetBool("Screen.UseGL") || (cfg.GetInt("3D.Renderer") != renderer3D_Software); } VideoSettingsDialog* VideoSettingsDialog::currentDlg = nullptr; +void VideoSettingsDialog::setEnabled() +{ + auto& cfg = emuInstance->getGlobalConfig(); + int renderer = cfg.GetInt("3D.Renderer"); + + bool softwareRenderer = renderer == renderer3D_Software; + ui->cbGLDisplay->setEnabled(softwareRenderer); + ui->cbSoftwareThreaded->setEnabled(softwareRenderer); + ui->cbxGLResolution->setEnabled(!softwareRenderer); + ui->cbBetterPolygons->setEnabled(renderer == renderer3D_OpenGL); + ui->cbxComputeHiResCoords->setEnabled(renderer == renderer3D_OpenGLCompute); +} VideoSettingsDialog::VideoSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::VideoSettingsDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - oldRenderer = Config::_3DRenderer; - oldGLDisplay = Config::ScreenUseGL; - oldVSync = Config::ScreenVSync; - oldVSyncInterval = Config::ScreenVSyncInterval; - oldSoftThreaded = Config::Threaded3D; - oldGLScale = Config::GL_ScaleFactor; - oldGLBetterPolygons = Config::GL_BetterPolygons; + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + auto& cfg = emuInstance->getGlobalConfig(); + oldRenderer = cfg.GetInt("3D.Renderer"); + oldGLDisplay = cfg.GetBool("Screen.UseGL"); + oldVSync = cfg.GetBool("Screen.VSync"); + oldVSyncInterval = cfg.GetInt("Screen.VSyncInterval"); + oldSoftThreaded = cfg.GetBool("3D.Soft.Threaded"); + oldGLScale = cfg.GetInt("3D.GL.ScaleFactor"); + oldGLBetterPolygons = cfg.GetBool("3D.GL.BetterPolygons"); + oldHiresCoordinates = cfg.GetBool("3D.GL.HiresCoordinates"); grp3DRenderer = new QButtonGroup(this); - grp3DRenderer->addButton(ui->rb3DSoftware, 0); - grp3DRenderer->addButton(ui->rb3DOpenGL, 1); + grp3DRenderer->addButton(ui->rb3DSoftware, renderer3D_Software); + grp3DRenderer->addButton(ui->rb3DOpenGL, renderer3D_OpenGL); + grp3DRenderer->addButton(ui->rb3DCompute, renderer3D_OpenGLCompute); #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) connect(grp3DRenderer, SIGNAL(buttonClicked(int)), this, SLOT(onChange3DRenderer(int))); #else connect(grp3DRenderer, SIGNAL(idClicked(int)), this, SLOT(onChange3DRenderer(int))); #endif - grp3DRenderer->button(Config::_3DRenderer)->setChecked(true); + grp3DRenderer->button(oldRenderer)->setChecked(true); #ifndef OGLRENDERER_ENABLED ui->rb3DOpenGL->setEnabled(false); #endif - ui->cbGLDisplay->setChecked(Config::ScreenUseGL != 0); +#ifdef __APPLE__ + ui->rb3DCompute->setEnabled(false); +#endif - ui->cbVSync->setChecked(Config::ScreenVSync != 0); - ui->sbVSyncInterval->setValue(Config::ScreenVSyncInterval); + ui->cbGLDisplay->setChecked(oldGLDisplay != 0); - ui->cbSoftwareThreaded->setChecked(Config::Threaded3D != 0); + ui->cbVSync->setChecked(oldVSync != 0); + ui->sbVSyncInterval->setValue(oldVSyncInterval); + + ui->cbSoftwareThreaded->setChecked(oldSoftThreaded); for (int i = 1; i <= 16; i++) ui->cbxGLResolution->addItem(QString("%1x native (%2x%3)").arg(i).arg(256*i).arg(192*i)); - ui->cbxGLResolution->setCurrentIndex(Config::GL_ScaleFactor-1); + ui->cbxGLResolution->setCurrentIndex(oldGLScale-1); - ui->cbBetterPolygons->setChecked(Config::GL_BetterPolygons != 0); + ui->cbBetterPolygons->setChecked(oldGLBetterPolygons != 0); + ui->cbxComputeHiResCoords->setChecked(oldHiresCoordinates != 0); - if (!Config::ScreenVSync) + if (!oldVSync) ui->sbVSyncInterval->setEnabled(false); setVsyncControlEnable(UsesGL()); - if (Config::_3DRenderer == 0) - { - ui->cbGLDisplay->setEnabled(true); - ui->cbSoftwareThreaded->setEnabled(true); - ui->cbxGLResolution->setEnabled(false); - ui->cbBetterPolygons->setEnabled(false); - } - else - { - ui->cbGLDisplay->setEnabled(false); - ui->cbSoftwareThreaded->setEnabled(false); - ui->cbxGLResolution->setEnabled(true); - ui->cbBetterPolygons->setEnabled(true); - } + setEnabled(); } VideoSettingsDialog::~VideoSettingsDialog() @@ -110,15 +121,23 @@ void VideoSettingsDialog::on_VideoSettingsDialog_accepted() void VideoSettingsDialog::on_VideoSettingsDialog_rejected() { + if (!((MainWindow*)parent())->getEmuInstance()) + { + closeDlg(); + return; + } + bool old_gl = UsesGL(); - Config::_3DRenderer = oldRenderer; - Config::ScreenUseGL = oldGLDisplay; - Config::ScreenVSync = oldVSync; - Config::ScreenVSyncInterval = oldVSyncInterval; - Config::Threaded3D = oldSoftThreaded; - Config::GL_ScaleFactor = oldGLScale; - Config::GL_BetterPolygons = oldGLBetterPolygons; + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetInt("3D.Renderer", oldRenderer); + cfg.SetBool("Screen.UseGL", oldGLDisplay); + cfg.SetBool("Screen.VSync", oldVSync); + cfg.SetInt("Screen.VSyncInterval", oldVSyncInterval); + cfg.SetBool("3D.Soft.Threaded", oldSoftThreaded); + cfg.SetInt("3D.GL.ScaleFactor", oldGLScale); + cfg.SetBool("3D.GL.BetterPolygons", oldGLBetterPolygons); + cfg.SetBool("3D.GL.HiresCoordinates", oldHiresCoordinates); emit updateVideoSettings(old_gl != UsesGL()); @@ -133,33 +152,22 @@ void VideoSettingsDialog::setVsyncControlEnable(bool hasOGL) void VideoSettingsDialog::onChange3DRenderer(int renderer) { - bool old_gl = (Config::ScreenUseGL != 0) || (Config::_3DRenderer != 0); + bool old_gl = UsesGL(); - Config::_3DRenderer = renderer; + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetInt("3D.Renderer", renderer); - if (renderer == 0) - { - ui->cbGLDisplay->setEnabled(true); - ui->cbSoftwareThreaded->setEnabled(true); - ui->cbxGLResolution->setEnabled(false); - ui->cbBetterPolygons->setEnabled(false); - } - else - { - ui->cbGLDisplay->setEnabled(false); - ui->cbSoftwareThreaded->setEnabled(false); - ui->cbxGLResolution->setEnabled(true); - ui->cbBetterPolygons->setEnabled(true); - } + setEnabled(); emit updateVideoSettings(old_gl != UsesGL()); } void VideoSettingsDialog::on_cbGLDisplay_stateChanged(int state) { - bool old_gl = (Config::ScreenUseGL != 0) || (Config::_3DRenderer != 0); + bool old_gl = UsesGL(); - Config::ScreenUseGL = (state != 0); + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetBool("Screen.UseGL", (state != 0)); setVsyncControlEnable(UsesGL()); @@ -170,19 +178,25 @@ void VideoSettingsDialog::on_cbVSync_stateChanged(int state) { bool vsync = (state != 0); ui->sbVSyncInterval->setEnabled(vsync); - Config::ScreenVSync = vsync; + + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetBool("Screen.VSync", vsync); + emit updateVideoSettings(false); } void VideoSettingsDialog::on_sbVSyncInterval_valueChanged(int val) { - Config::ScreenVSyncInterval = val; + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetInt("Screen.VSyncInterval", val); + emit updateVideoSettings(false); } void VideoSettingsDialog::on_cbSoftwareThreaded_stateChanged(int state) { - Config::Threaded3D = (state != 0); + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetBool("3D.Soft.Threaded", (state != 0)); emit updateVideoSettings(false); } @@ -192,7 +206,8 @@ void VideoSettingsDialog::on_cbxGLResolution_currentIndexChanged(int idx) // prevent a spurious change if (ui->cbxGLResolution->count() < 16) return; - Config::GL_ScaleFactor = idx+1; + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetInt("3D.GL.ScaleFactor", idx+1); setVsyncControlEnable(UsesGL()); @@ -201,7 +216,16 @@ void VideoSettingsDialog::on_cbxGLResolution_currentIndexChanged(int idx) void VideoSettingsDialog::on_cbBetterPolygons_stateChanged(int state) { - Config::GL_BetterPolygons = (state != 0); + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetBool("3D.GL.BetterPolygons", (state != 0)); + + emit updateVideoSettings(false); +} + +void VideoSettingsDialog::on_cbxComputeHiResCoords_stateChanged(int state) +{ + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetBool("3D.GL.HiresCoordinates", (state != 0)); emit updateVideoSettings(false); } diff --git a/src/frontend/qt_sdl/VideoSettingsDialog.h b/src/frontend/qt_sdl/VideoSettingsDialog.h index 29af8e15..e7ba5cc7 100644 --- a/src/frontend/qt_sdl/VideoSettingsDialog.h +++ b/src/frontend/qt_sdl/VideoSettingsDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -24,6 +24,7 @@ namespace Ui { class VideoSettingsDialog; } class VideoSettingsDialog; +class EmuInstance; class VideoSettingsDialog : public QDialog { @@ -33,6 +34,8 @@ public: explicit VideoSettingsDialog(QWidget* parent); ~VideoSettingsDialog(); + bool UsesGL(); + static VideoSettingsDialog* currentDlg; static VideoSettingsDialog* openDlg(QWidget* parent) { @@ -65,12 +68,15 @@ private slots: void on_cbxGLResolution_currentIndexChanged(int idx); void on_cbBetterPolygons_stateChanged(int state); + void on_cbxComputeHiResCoords_stateChanged(int state); void on_cbSoftwareThreaded_stateChanged(int state); private: void setVsyncControlEnable(bool hasOGL); + void setEnabled(); Ui::VideoSettingsDialog* ui; + EmuInstance* emuInstance; QButtonGroup* grp3DRenderer; @@ -81,6 +87,7 @@ private: int oldSoftThreaded; int oldGLScale; int oldGLBetterPolygons; + int oldHiresCoordinates; }; #endif // VIDEOSETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/VideoSettingsDialog.ui b/src/frontend/qt_sdl/VideoSettingsDialog.ui index 11cfe3d9..ff9baf8f 100644 --- a/src/frontend/qt_sdl/VideoSettingsDialog.ui +++ b/src/frontend/qt_sdl/VideoSettingsDialog.ui @@ -6,7 +6,7 @@ 0 0 - 408 + 427 262 @@ -24,7 +24,7 @@ QLayout::SetFixedSize - -1 + 6 @@ -39,13 +39,6 @@ - - - - <html><head/><body><p>The resolution at which the 3D graphics will be rendered. Higher resolutions improve graphics quality when the main window is enlarged, but may also cause glitches.</p></body></html> - - - @@ -56,6 +49,20 @@ + + + + <html><head/><body><p>The resolution at which the 3D graphics will be rendered. Higher resolutions improve graphics quality when the main window is enlarged, but may also cause glitches.</p></body></html> + + + + + + + Use high resolution coordinates + + + @@ -94,23 +101,7 @@ Display settings - - - - - 0 - 0 - - - - <html><head/><body><p>The interval at which to synchronize to the monitor's refresh rate. Set to 1 for a 60Hz monitor, 2 for 120Hz, ...</p></body></html> - - - VSync interval: - - - - + <html><head/><body><p>The interval at which to synchronize to the monitor's refresh rate. Set to 1 for a 60Hz monitor, 2 for 120Hz, ...</p></body></html> @@ -123,7 +114,7 @@ - + <html><head/><body><p>Use OpenGL to draw the DS screens to the main window. May result in better frame pacing. Mandatory when using the OpenGL 3D renderer.</p></body></html> @@ -133,17 +124,7 @@ - - - - <html><head/><body><p>When using OpenGL, synchronize the video output to your monitor's refresh rate.</p></body></html> - - - VSync - - - - + Qt::Vertical @@ -159,13 +140,39 @@ + + + + <html><head/><body><p>When using OpenGL, synchronize the video output to your monitor's refresh rate.</p></body></html> + + + VSync + + + + + + + + 0 + 0 + + + + <html><head/><body><p>The interval at which to synchronize to the monitor's refresh rate. Set to 1 for a 60Hz monitor, 2 for 120Hz, ...</p></body></html> + + + VSync interval: + + + <html><head/><body><p>The OpenGL renderer may be faster than software and supports graphical enhancements, but is more prone to glitches.</p></body></html> - OpenGL + OpenGL (Classic) @@ -186,6 +193,13 @@ + + + + OpenGL (Compute shader) + + + diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.cpp b/src/frontend/qt_sdl/WifiSettingsDialog.cpp index d71657a3..e0954c83 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.cpp +++ b/src/frontend/qt_sdl/WifiSettingsDialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -22,10 +22,10 @@ #include "types.h" #include "Platform.h" #include "Config.h" +#include "main.h" -#include "LAN_Socket.h" -#include "LAN_PCap.h" -#include "Wifi.h" +#include "Net.h" +#include "Net_PCap.h" #include "WifiSettingsDialog.h" #include "ui_WifiSettingsDialog.h" @@ -37,20 +37,29 @@ #define PCAP_NAME "libpcap" #endif +extern std::optional pcap; +extern melonDS::Net net; WifiSettingsDialog* WifiSettingsDialog::currentDlg = nullptr; bool WifiSettingsDialog::needsReset = false; -extern bool RunningSomething; - +void NetInit(); WifiSettingsDialog::WifiSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::WifiSettingsDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - haspcap = LAN_PCap::Init(false); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + auto& cfg = emuInstance->getGlobalConfig(); + + if (!pcap) + pcap = melonDS::LibPCap::New(); + + haspcap = pcap.has_value(); + if (pcap) + adapters = pcap->GetAdapters(); ui->rbDirectMode->setText("Direct mode (requires " PCAP_NAME " and ethernet connection)"); @@ -58,20 +67,21 @@ WifiSettingsDialog::WifiSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne ui->lblAdapterIP->setText("(none)"); int sel = 0; - for (int i = 0; i < LAN_PCap::NumAdapters; i++) + for (int i = 0; i < adapters.size(); i++) { - LAN_PCap::AdapterData* adapter = &LAN_PCap::Adapters[i]; + melonDS::AdapterData& adapter = adapters[i]; - ui->cbxDirectAdapter->addItem(QString(adapter->FriendlyName)); + ui->cbxDirectAdapter->addItem(QString(adapter.FriendlyName)); - if (!strncmp(adapter->DeviceName, Config::LANDevice.c_str(), 128)) + if (!strncmp(adapter.DeviceName, cfg.GetString("LAN.Device").c_str(), 128)) sel = i; } ui->cbxDirectAdapter->setCurrentIndex(sel); // errrr??? - ui->rbDirectMode->setChecked(Config::DirectLAN); - ui->rbIndirectMode->setChecked(!Config::DirectLAN); + bool direct = cfg.GetBool("LAN.DirectMode"); + ui->rbDirectMode->setChecked(direct); + ui->rbIndirectMode->setChecked(!direct); if (!haspcap) ui->rbDirectMode->setEnabled(false); updateAdapterControls(); @@ -84,26 +94,40 @@ WifiSettingsDialog::~WifiSettingsDialog() void WifiSettingsDialog::done(int r) { + if (!((MainWindow*)parent())->getEmuInstance()) + { + QDialog::done(r); + closeDlg(); + return; + } + needsReset = false; if (r == QDialog::Accepted) { - Config::DirectLAN = ui->rbDirectMode->isChecked(); + auto& cfg = emuInstance->getGlobalConfig(); + + cfg.SetBool("LAN.DirectMode", ui->rbDirectMode->isChecked()); int sel = ui->cbxDirectAdapter->currentIndex(); - if (sel < 0 || sel >= LAN_PCap::NumAdapters) sel = 0; - if (LAN_PCap::NumAdapters < 1) + if (sel < 0 || sel >= adapters.size()) sel = 0; + if (adapters.empty()) { - Config::LANDevice = ""; + cfg.SetString("LAN.Device", ""); } else { - Config::LANDevice = LAN_PCap::Adapters[sel].DeviceName; + cfg.SetString("LAN.Device", adapters[sel].DeviceName); } Config::Save(); } + Config::Table cfg = Config::GetGlobalTable(); + std::string devicename = cfg.GetString("LAN.Device"); + + NetInit(); + QDialog::done(r); closeDlg(); @@ -123,10 +147,9 @@ void WifiSettingsDialog::on_cbxDirectAdapter_currentIndexChanged(int sel) { if (!haspcap) return; - if (sel < 0 || sel >= LAN_PCap::NumAdapters) return; - if (LAN_PCap::NumAdapters < 1) return; + if (sel < 0 || sel >= adapters.size() || adapters.empty()) return; - LAN_PCap::AdapterData* adapter = &LAN_PCap::Adapters[sel]; + melonDS::AdapterData* adapter = &adapters[sel]; char tmp[64]; sprintf(tmp, "%02X:%02X:%02X:%02X:%02X:%02X", diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.h b/src/frontend/qt_sdl/WifiSettingsDialog.h index 82e1bd49..d78b0f65 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.h +++ b/src/frontend/qt_sdl/WifiSettingsDialog.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -20,10 +20,14 @@ #define WIFISETTINGSDIALOG_H #include +#include +#include "Net_PCap.h" namespace Ui { class WifiSettingsDialog; } class WifiSettingsDialog; +class EmuInstance; + class WifiSettingsDialog : public QDialog { Q_OBJECT @@ -61,10 +65,12 @@ private slots: private: Ui::WifiSettingsDialog* ui; + EmuInstance* emuInstance; bool haspcap; void updateAdapterControls(); + std::vector adapters; }; #endif // WIFISETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/Window.cpp b/src/frontend/qt_sdl/Window.cpp index 962fb76c..596a0f5c 100644 --- a/src/frontend/qt_sdl/Window.cpp +++ b/src/frontend/qt_sdl/Window.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -39,6 +39,7 @@ #include #include #include +#include #ifndef _WIN32 #include #include @@ -51,7 +52,6 @@ #endif #include "main.h" -#include "Input.h" #include "CheatsDialog.h" #include "DateTimeDialog.h" #include "EmuSettingsDialog.h" @@ -68,49 +68,79 @@ #include "RAMInfoDialog.h" #include "TitleManagerDialog.h" #include "PowerManagement/PowerManagementDialog.h" -#include "AudioInOut.h" #include "Platform.h" #include "Config.h" +#include "version.h" #include "Savestate.h" -#include "LocalMP.h" +#include "MPInterface.h" +#include "LANDialog.h" //#include "main_shaders.h" -#include "ROMManager.h" +#include "EmuInstance.h" #include "ArchiveUtil.h" #include "CameraManager.h" +#include "Window.h" +#include "AboutDialog.h" using namespace melonDS; -// TEMP -extern MainWindow* mainWindow; -extern EmuThread* emuThread; -extern bool RunningSomething; -extern QString NdsRomMimeType; -extern QStringList NdsRomExtensions; -extern QString GbaRomMimeType; -extern QStringList GbaRomExtensions; -extern QStringList ArchiveMimeTypes; -extern QStringList ArchiveExtensions; -/*static bool FileExtensionInList(const QString& filename, const QStringList& extensions, Qt::CaseSensitivity cs); -static bool MimeTypeInList(const QMimeType& mimetype, const QStringList& superTypeNames); -static bool NdsRomByExtension(const QString& filename); -static bool GbaRomByExtension(const QString& filename); -static bool SupportedArchiveByExtension(const QString& filename); -static bool NdsRomByMimetype(const QMimeType& mimetype); -static bool GbaRomByMimetype(const QMimeType& mimetype); -static bool SupportedArchiveByMimetype(const QMimeType& mimetype); -static bool ZstdNdsRomByExtension(const QString& filename); -static bool ZstdGbaRomByExtension(const QString& filename); -static bool FileIsSupportedFiletype(const QString& filename, bool insideArchive);*/ + + extern CameraManager* camManager[2]; extern bool camStarted[2]; -extern int videoRenderer; -extern bool videoSettingsDirty; +QString NdsRomMimeType = "application/x-nintendo-ds-rom"; +QStringList NdsRomExtensions { ".nds", ".srl", ".dsi", ".ids" }; + +QString GbaRomMimeType = "application/x-gba-rom"; +QStringList GbaRomExtensions { ".gba", ".agb" }; + + +// This list of supported archive formats is based on libarchive(3) version 3.6.2 (2022-12-09). +QStringList ArchiveMimeTypes +{ +#ifdef ARCHIVE_SUPPORT_ENABLED + "application/zip", + "application/x-7z-compressed", + "application/vnd.rar", // *.rar + "application/x-tar", + + "application/x-compressed-tar", // *.tar.gz + "application/x-xz-compressed-tar", + "application/x-bzip-compressed-tar", + "application/x-lz4-compressed-tar", + "application/x-zstd-compressed-tar", + + "application/x-tarz", // *.tar.Z + "application/x-lzip-compressed-tar", + "application/x-lzma-compressed-tar", + "application/x-lrzip-compressed-tar", + "application/x-tzo", // *.tar.lzo +#endif +}; + +QStringList ArchiveExtensions +{ +#ifdef ARCHIVE_SUPPORT_ENABLED + ".zip", ".7z", ".rar", ".tar", + + ".tar.gz", ".tgz", + ".tar.xz", ".txz", + ".tar.bz2", ".tbz2", + ".tar.lz4", ".tlz4", + ".tar.zst", ".tzst", + + ".tar.Z", ".taz", + ".tar.lz", + ".tar.lzma", ".tlz", + ".tar.lrz", ".tlrz", + ".tar.lzo", ".tzo" +#endif +}; // AAAAAAA static bool FileExtensionInList(const QString& filename, const QStringList& extensions, Qt::CaseSensitivity cs = Qt::CaseInsensitive) @@ -196,390 +226,456 @@ static void signalHandler(int) } #endif -MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) + +MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) : + QMainWindow(parent), + windowID(id), + emuInstance(inst), + globalCfg(inst->globalCfg), + localCfg(inst->localCfg), + windowCfg(localCfg.GetTable("Window"+std::to_string(id), "Window0")), + emuThread(inst->getEmuThread()), + enabledSaved(false), + focused(true) { #ifndef _WIN32 - if (socketpair(AF_UNIX, SOCK_STREAM, 0, signalFd)) + if (!parent) { - qFatal("Couldn't create socketpair"); + if (socketpair(AF_UNIX, SOCK_STREAM, 0, signalFd)) + { + qFatal("Couldn't create socketpair"); + } + + signalSn = new QSocketNotifier(signalFd[1], QSocketNotifier::Read, this); + connect(signalSn, SIGNAL(activated(int)), this, SLOT(onQuit())); + + struct sigaction sa; + + sa.sa_handler = signalHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_flags |= SA_RESTART; + sigaction(SIGINT, &sa, 0); } - - signalSn = new QSocketNotifier(signalFd[1], QSocketNotifier::Read, this); - connect(signalSn, SIGNAL(activated(int)), this, SLOT(onQuit())); - - struct sigaction sa; - - sa.sa_handler = signalHandler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = 0; - sa.sa_flags |= SA_RESTART; - sigaction(SIGINT, &sa, 0); #endif - oldW = Config::WindowWidth; - oldH = Config::WindowHeight; - oldMax = Config::WindowMaximized; + showOSD = windowCfg.GetBool("ShowOSD"); setWindowTitle("melonDS " MELONDS_VERSION); setAttribute(Qt::WA_DeleteOnClose); setAcceptDrops(true); setFocusPolicy(Qt::ClickFocus); - int inst = Platform::InstanceID(); - - QMenuBar* menubar = new QMenuBar(); - { - QMenu* menu = menubar->addMenu("File"); - - actOpenROM = menu->addAction("Open ROM..."); - connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile); - actOpenROM->setShortcut(QKeySequence(QKeySequence::StandardKey::Open)); - - /*actOpenROMArchive = menu->addAction("Open ROM inside archive..."); - connect(actOpenROMArchive, &QAction::triggered, this, &MainWindow::onOpenFileArchive); - actOpenROMArchive->setShortcut(QKeySequence(Qt::Key_O | Qt::CTRL | Qt::SHIFT));*/ - - recentMenu = menu->addMenu("Open recent"); - for (int i = 0; i < 10; ++i) - { - std::string item = Config::RecentROMList[i]; - if (!item.empty()) - recentFileList.push_back(QString::fromStdString(item)); - } - updateRecentFilesMenu(); - - //actBootFirmware = menu->addAction("Launch DS menu"); - actBootFirmware = menu->addAction("Boot firmware"); - connect(actBootFirmware, &QAction::triggered, this, &MainWindow::onBootFirmware); - - menu->addSeparator(); - - actCurrentCart = menu->addAction("DS slot: " + ROMManager::CartLabel()); - actCurrentCart->setEnabled(false); - - actInsertCart = menu->addAction("Insert cart..."); - connect(actInsertCart, &QAction::triggered, this, &MainWindow::onInsertCart); - - actEjectCart = menu->addAction("Eject cart"); - connect(actEjectCart, &QAction::triggered, this, &MainWindow::onEjectCart); - - menu->addSeparator(); - - actCurrentGBACart = menu->addAction("GBA slot: " + ROMManager::GBACartLabel()); - actCurrentGBACart->setEnabled(false); - - actInsertGBACart = menu->addAction("Insert ROM cart..."); - connect(actInsertGBACart, &QAction::triggered, this, &MainWindow::onInsertGBACart); - - { - QMenu* submenu = menu->addMenu("Insert add-on cart"); - - actInsertGBAAddon[0] = submenu->addAction("Memory expansion"); - actInsertGBAAddon[0]->setData(QVariant(GBAAddon_RAMExpansion)); - connect(actInsertGBAAddon[0], &QAction::triggered, this, &MainWindow::onInsertGBAAddon); - } - - actEjectGBACart = menu->addAction("Eject cart"); - connect(actEjectGBACart, &QAction::triggered, this, &MainWindow::onEjectGBACart); - - menu->addSeparator(); - - actImportSavefile = menu->addAction("Import savefile"); - connect(actImportSavefile, &QAction::triggered, this, &MainWindow::onImportSavefile); - - menu->addSeparator(); - - { - QMenu* submenu = menu->addMenu("Save state"); - - for (int i = 1; i < 9; i++) - { - actSaveState[i] = submenu->addAction(QString("%1").arg(i)); - actSaveState[i]->setShortcut(QKeySequence(Qt::ShiftModifier | (Qt::Key_F1+i-1))); - actSaveState[i]->setData(QVariant(i)); - connect(actSaveState[i], &QAction::triggered, this, &MainWindow::onSaveState); - } - - actSaveState[0] = submenu->addAction("File..."); - actSaveState[0]->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_F9)); - actSaveState[0]->setData(QVariant(0)); - connect(actSaveState[0], &QAction::triggered, this, &MainWindow::onSaveState); - } - { - QMenu* submenu = menu->addMenu("Load state"); - - for (int i = 1; i < 9; i++) - { - actLoadState[i] = submenu->addAction(QString("%1").arg(i)); - actLoadState[i]->setShortcut(QKeySequence(Qt::Key_F1+i-1)); - actLoadState[i]->setData(QVariant(i)); - connect(actLoadState[i], &QAction::triggered, this, &MainWindow::onLoadState); - } - - actLoadState[0] = submenu->addAction("File..."); - actLoadState[0]->setShortcut(QKeySequence(Qt::Key_F9)); - actLoadState[0]->setData(QVariant(0)); - connect(actLoadState[0], &QAction::triggered, this, &MainWindow::onLoadState); - } - - actUndoStateLoad = menu->addAction("Undo state load"); - actUndoStateLoad->setShortcut(QKeySequence(Qt::Key_F12)); - connect(actUndoStateLoad, &QAction::triggered, this, &MainWindow::onUndoStateLoad); - - menu->addSeparator(); - - actQuit = menu->addAction("Quit"); - connect(actQuit, &QAction::triggered, this, &MainWindow::onQuit); - actQuit->setShortcut(QKeySequence(QKeySequence::StandardKey::Quit)); - } - { - QMenu* menu = menubar->addMenu("System"); - - actPause = menu->addAction("Pause"); - actPause->setCheckable(true); - connect(actPause, &QAction::triggered, this, &MainWindow::onPause); - - actReset = menu->addAction("Reset"); - connect(actReset, &QAction::triggered, this, &MainWindow::onReset); - - actStop = menu->addAction("Stop"); - connect(actStop, &QAction::triggered, this, &MainWindow::onStop); - - actFrameStep = menu->addAction("Frame step"); - connect(actFrameStep, &QAction::triggered, this, &MainWindow::onFrameStep); - - menu->addSeparator(); - - actPowerManagement = menu->addAction("Power management"); - connect(actPowerManagement, &QAction::triggered, this, &MainWindow::onOpenPowerManagement); - - actDateTime = menu->addAction("Date and time"); - connect(actDateTime, &QAction::triggered, this, &MainWindow::onOpenDateTime); - - menu->addSeparator(); - - actEnableCheats = menu->addAction("Enable cheats"); - actEnableCheats->setCheckable(true); - connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats); - - //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); - - 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); - } - - { - 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"); - - actEmuSettings = menu->addAction("Emu settings"); - connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); - -#ifdef __APPLE__ - actPreferences = menu->addAction("Preferences..."); - connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); - actPreferences->setMenuRole(QAction::PreferencesRole); +#if QT_VERSION_MAJOR == 6 && WIN32 + // The "windows11" theme has pretty massive padding around menubar items, this makes Config and Help not fit in a window at 1x screen sizing + // So let's reduce the padding a bit. + if (QApplication::style()->name() == "windows11") + setStyleSheet("QMenuBar::item { padding: 4px 8px; }"); #endif - actInputConfig = menu->addAction("Input and hotkeys"); - connect(actInputConfig, &QAction::triggered, this, &MainWindow::onOpenInputConfig); - - actVideoSettings = menu->addAction("Video settings"); - connect(actVideoSettings, &QAction::triggered, this, &MainWindow::onOpenVideoSettings); - - actCameraSettings = menu->addAction("Camera settings"); - connect(actCameraSettings, &QAction::triggered, this, &MainWindow::onOpenCameraSettings); - - 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); - - 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); + //hasMenu = (!parent); + hasMenu = true; + if (hasMenu) + { + QMenuBar * menubar = new QMenuBar(); { - QMenu* submenu = menu->addMenu("Savestate settings"); + QMenu * menu = menubar->addMenu("File"); - actSavestateSRAMReloc = submenu->addAction("Separate savefiles"); - actSavestateSRAMReloc->setCheckable(true); - connect(actSavestateSRAMReloc, &QAction::triggered, this, &MainWindow::onChangeSavestateSRAMReloc); - } + actOpenROM = menu->addAction("Open ROM..."); + connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile); + actOpenROM->setShortcut(QKeySequence(QKeySequence::StandardKey::Open)); - menu->addSeparator(); + /*actOpenROMArchive = menu->addAction("Open ROM inside archive..."); + connect(actOpenROMArchive, &QAction::triggered, this, &MainWindow::onOpenFileArchive); + actOpenROMArchive->setShortcut(QKeySequence(Qt::Key_O | Qt::CTRL | Qt::SHIFT));*/ - { - QMenu* submenu = menu->addMenu("Screen size"); + recentMenu = menu->addMenu("Open recent"); + loadRecentFilesMenu(true); + + //actBootFirmware = menu->addAction("Launch DS menu"); + actBootFirmware = menu->addAction("Boot firmware"); + connect(actBootFirmware, &QAction::triggered, this, &MainWindow::onBootFirmware); + + menu->addSeparator(); + + actCurrentCart = menu->addAction("DS slot: " + emuInstance->cartLabel()); + actCurrentCart->setEnabled(false); + + actInsertCart = menu->addAction("Insert cart..."); + connect(actInsertCart, &QAction::triggered, this, &MainWindow::onInsertCart); + + actEjectCart = menu->addAction("Eject cart"); + connect(actEjectCart, &QAction::triggered, this, &MainWindow::onEjectCart); + + menu->addSeparator(); + + actCurrentGBACart = menu->addAction("GBA slot: " + emuInstance->gbaCartLabel()); + actCurrentGBACart->setEnabled(false); + + actInsertGBACart = menu->addAction("Insert ROM cart..."); + connect(actInsertGBACart, &QAction::triggered, this, &MainWindow::onInsertGBACart); - for (int i = 0; i < 4; i++) { - int data = i+1; - actScreenSize[i] = submenu->addAction(QString("%1x").arg(data)); - actScreenSize[i]->setData(QVariant(data)); - connect(actScreenSize[i], &QAction::triggered, this, &MainWindow::onChangeScreenSize); - } - } - { - QMenu* submenu = menu->addMenu("Screen rotation"); - grpScreenRotation = new QActionGroup(submenu); + QMenu * submenu = menu->addMenu("Insert add-on cart"); + QAction *act; - for (int i = 0; i < Frontend::screenRot_MAX; i++) - { - int data = i*90; - actScreenRotation[i] = submenu->addAction(QString("%1°").arg(data)); - actScreenRotation[i]->setActionGroup(grpScreenRotation); - actScreenRotation[i]->setData(QVariant(i)); - actScreenRotation[i]->setCheckable(true); - } - - connect(grpScreenRotation, &QActionGroup::triggered, this, &MainWindow::onChangeScreenRotation); - } - { - QMenu* submenu = menu->addMenu("Screen gap"); - grpScreenGap = new QActionGroup(submenu); - - const int screengap[] = {0, 1, 8, 64, 90, 128}; - - for (int i = 0; i < 6; i++) - { - int data = screengap[i]; - actScreenGap[i] = submenu->addAction(QString("%1 px").arg(data)); - actScreenGap[i]->setActionGroup(grpScreenGap); - actScreenGap[i]->setData(QVariant(data)); - actScreenGap[i]->setCheckable(true); - } - - connect(grpScreenGap, &QActionGroup::triggered, this, &MainWindow::onChangeScreenGap); - } - { - QMenu* submenu = menu->addMenu("Screen layout"); - grpScreenLayout = new QActionGroup(submenu); - - const char* screenlayout[] = {"Natural", "Vertical", "Horizontal", "Hybrid"}; - - for (int i = 0; i < Frontend::screenLayout_MAX; i++) - { - actScreenLayout[i] = submenu->addAction(QString(screenlayout[i])); - actScreenLayout[i]->setActionGroup(grpScreenLayout); - actScreenLayout[i]->setData(QVariant(i)); - actScreenLayout[i]->setCheckable(true); - } - - connect(grpScreenLayout, &QActionGroup::triggered, this, &MainWindow::onChangeScreenLayout); - - submenu->addSeparator(); - - actScreenSwap = submenu->addAction("Swap screens"); - actScreenSwap->setCheckable(true); - connect(actScreenSwap, &QAction::triggered, this, &MainWindow::onChangeScreenSwap); - } - { - QMenu* submenu = menu->addMenu("Screen sizing"); - grpScreenSizing = new QActionGroup(submenu); - - const char* screensizing[] = {"Even", "Emphasize top", "Emphasize bottom", "Auto", "Top only", "Bottom only"}; - - for (int i = 0; i < Frontend::screenSizing_MAX; i++) - { - actScreenSizing[i] = submenu->addAction(QString(screensizing[i])); - actScreenSizing[i]->setActionGroup(grpScreenSizing); - actScreenSizing[i]->setData(QVariant(i)); - actScreenSizing[i]->setCheckable(true); - } - - connect(grpScreenSizing, &QActionGroup::triggered, this, &MainWindow::onChangeScreenSizing); - - submenu->addSeparator(); - - actIntegerScaling = submenu->addAction("Force integer scaling"); - actIntegerScaling->setCheckable(true); - connect(actIntegerScaling, &QAction::triggered, this, &MainWindow::onChangeIntegerScaling); - } - { - QMenu* submenu = menu->addMenu("Aspect ratio"); - grpScreenAspectTop = new QActionGroup(submenu); - grpScreenAspectBot = new QActionGroup(submenu); - actScreenAspectTop = new QAction*[AspectRatiosNum]; - actScreenAspectBot = new QAction*[AspectRatiosNum]; - - for (int i = 0; i < 2; i++) - { - QActionGroup* group = grpScreenAspectTop; - QAction** actions = actScreenAspectTop; - - if (i == 1) + int addons[] = {GBAAddon_RAMExpansion, GBAAddon_RumblePak, -1}; + for (int i = 0; addons[i] != -1; i++) { - group = grpScreenAspectBot; - submenu->addSeparator(); - actions = actScreenAspectBot; + int addon = addons[i]; + act = submenu->addAction(emuInstance->gbaAddonName(addon)); + act->setData(QVariant(addon)); + connect(act, &QAction::triggered, this, &MainWindow::onInsertGBAAddon); + actInsertGBAAddon.append(act); + } + } + + actEjectGBACart = menu->addAction("Eject cart"); + connect(actEjectGBACart, &QAction::triggered, this, &MainWindow::onEjectGBACart); + + menu->addSeparator(); + + actImportSavefile = menu->addAction("Import savefile"); + connect(actImportSavefile, &QAction::triggered, this, &MainWindow::onImportSavefile); + + menu->addSeparator(); + + { + QMenu * submenu = menu->addMenu("Save state"); + + for (int i = 1; i < 9; i++) + { + actSaveState[i] = submenu->addAction(QString("%1").arg(i)); + actSaveState[i]->setShortcut(QKeySequence(Qt::ShiftModifier | (Qt::Key_F1 + i - 1))); + actSaveState[i]->setData(QVariant(i)); + connect(actSaveState[i], &QAction::triggered, this, &MainWindow::onSaveState); } - for (int j = 0; j < AspectRatiosNum; j++) + actSaveState[0] = submenu->addAction("File..."); + actSaveState[0]->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_F9)); + actSaveState[0]->setData(QVariant(0)); + connect(actSaveState[0], &QAction::triggered, this, &MainWindow::onSaveState); + } + { + QMenu * submenu = menu->addMenu("Load state"); + + for (int i = 1; i < 9; i++) { - 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); + actLoadState[i] = submenu->addAction(QString("%1").arg(i)); + actLoadState[i]->setShortcut(QKeySequence(Qt::Key_F1 + i - 1)); + actLoadState[i]->setData(QVariant(i)); + connect(actLoadState[i], &QAction::triggered, this, &MainWindow::onLoadState); } - connect(group, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspect); + actLoadState[0] = submenu->addAction("File..."); + actLoadState[0]->setShortcut(QKeySequence(Qt::Key_F9)); + actLoadState[0]->setData(QVariant(0)); + connect(actLoadState[0], &QAction::triggered, this, &MainWindow::onLoadState); + } + + actUndoStateLoad = menu->addAction("Undo state load"); + actUndoStateLoad->setShortcut(QKeySequence(Qt::Key_F12)); + connect(actUndoStateLoad, &QAction::triggered, this, &MainWindow::onUndoStateLoad); + + menu->addSeparator(); + actOpenConfig = menu->addAction("Open melonDS directory"); + connect(actOpenConfig, &QAction::triggered, this, [&]() + { + QDesktopServices::openUrl(QUrl::fromLocalFile(emuDirectory)); + }); + + menu->addSeparator(); + + actQuit = menu->addAction("Quit"); + connect(actQuit, &QAction::triggered, this, &MainWindow::onQuit); + actQuit->setShortcut(QKeySequence(QKeySequence::StandardKey::Quit)); + } + { + QMenu * menu = menubar->addMenu("System"); + + actPause = menu->addAction("Pause"); + actPause->setCheckable(true); + connect(actPause, &QAction::triggered, this, &MainWindow::onPause); + + actReset = menu->addAction("Reset"); + connect(actReset, &QAction::triggered, this, &MainWindow::onReset); + + actStop = menu->addAction("Stop"); + connect(actStop, &QAction::triggered, this, &MainWindow::onStop); + + actFrameStep = menu->addAction("Frame step"); + connect(actFrameStep, &QAction::triggered, this, &MainWindow::onFrameStep); + + menu->addSeparator(); + + actPowerManagement = menu->addAction("Power management"); + connect(actPowerManagement, &QAction::triggered, this, &MainWindow::onOpenPowerManagement); + + actDateTime = menu->addAction("Date and time"); + connect(actDateTime, &QAction::triggered, this, &MainWindow::onOpenDateTime); + + menu->addSeparator(); + + actEnableCheats = menu->addAction("Enable cheats"); + actEnableCheats->setCheckable(true); + connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats); + + //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); + + 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); + } + + { + menu->addSeparator(); + QMenu * submenu = menu->addMenu("Multiplayer"); + + actMPNewInstance = submenu->addAction("Launch new instance"); + connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance); + + submenu->addSeparator(); + + actLANStartHost = submenu->addAction("Host LAN game"); + connect(actLANStartHost, &QAction::triggered, this, &MainWindow::onLANStartHost); + + actLANStartClient = submenu->addAction("Join LAN game"); + connect(actLANStartClient, &QAction::triggered, this, &MainWindow::onLANStartClient); + + /*submenu->addSeparator(); + + actNPStartHost = submenu->addAction("NETPLAY HOST"); + connect(actNPStartHost, &QAction::triggered, this, &MainWindow::onNPStartHost); + + actNPStartClient = submenu->addAction("NETPLAY CLIENT"); + connect(actNPStartClient, &QAction::triggered, this, &MainWindow::onNPStartClient); + + actNPTest = submenu->addAction("NETPLAY GO"); + connect(actNPTest, &QAction::triggered, this, &MainWindow::onNPTest);*/ } } + { + QMenu * menu = menubar->addMenu("View"); - actScreenFiltering = menu->addAction("Screen filtering"); - actScreenFiltering->setCheckable(true); - connect(actScreenFiltering, &QAction::triggered, this, &MainWindow::onChangeScreenFiltering); + { + QMenu * submenu = menu->addMenu("Screen size"); - actShowOSD = menu->addAction("Show OSD"); - actShowOSD->setCheckable(true); - connect(actShowOSD, &QAction::triggered, this, &MainWindow::onChangeShowOSD); + for (int i = 0; i < 4; i++) + { + int data = i + 1; + actScreenSize[i] = submenu->addAction(QString("%1x").arg(data)); + actScreenSize[i]->setData(QVariant(data)); + connect(actScreenSize[i], &QAction::triggered, this, &MainWindow::onChangeScreenSize); + } + } + { + QMenu * submenu = menu->addMenu("Screen rotation"); + grpScreenRotation = new QActionGroup(submenu); - menu->addSeparator(); + for (int i = 0; i < screenRot_MAX; i++) + { + int data = i * 90; + actScreenRotation[i] = submenu->addAction(QString("%1°").arg(data)); + actScreenRotation[i]->setActionGroup(grpScreenRotation); + actScreenRotation[i]->setData(QVariant(i)); + actScreenRotation[i]->setCheckable(true); + } - actLimitFramerate = menu->addAction("Limit framerate"); - actLimitFramerate->setCheckable(true); - connect(actLimitFramerate, &QAction::triggered, this, &MainWindow::onChangeLimitFramerate); + connect(grpScreenRotation, &QActionGroup::triggered, this, &MainWindow::onChangeScreenRotation); + } + { + QMenu * submenu = menu->addMenu("Screen gap"); + grpScreenGap = new QActionGroup(submenu); - actAudioSync = menu->addAction("Audio sync"); - actAudioSync->setCheckable(true); - connect(actAudioSync, &QAction::triggered, this, &MainWindow::onChangeAudioSync); + const int screengap[] = {0, 1, 8, 64, 90, 128}; + + for (int i = 0; i < 6; i++) + { + int data = screengap[i]; + actScreenGap[i] = submenu->addAction(QString("%1 px").arg(data)); + actScreenGap[i]->setActionGroup(grpScreenGap); + actScreenGap[i]->setData(QVariant(data)); + actScreenGap[i]->setCheckable(true); + } + + connect(grpScreenGap, &QActionGroup::triggered, this, &MainWindow::onChangeScreenGap); + } + { + QMenu * submenu = menu->addMenu("Screen layout"); + grpScreenLayout = new QActionGroup(submenu); + + const char *screenlayout[] = {"Natural", "Vertical", "Horizontal", "Hybrid"}; + + for (int i = 0; i < screenLayout_MAX; i++) + { + actScreenLayout[i] = submenu->addAction(QString(screenlayout[i])); + actScreenLayout[i]->setActionGroup(grpScreenLayout); + actScreenLayout[i]->setData(QVariant(i)); + actScreenLayout[i]->setCheckable(true); + } + + connect(grpScreenLayout, &QActionGroup::triggered, this, &MainWindow::onChangeScreenLayout); + + submenu->addSeparator(); + + actScreenSwap = submenu->addAction("Swap screens"); + actScreenSwap->setCheckable(true); + connect(actScreenSwap, &QAction::triggered, this, &MainWindow::onChangeScreenSwap); + } + { + QMenu * submenu = menu->addMenu("Screen sizing"); + grpScreenSizing = new QActionGroup(submenu); + + const char *screensizing[] = {"Even", "Emphasize top", "Emphasize bottom", "Auto", "Top only", + "Bottom only"}; + + for (int i = 0; i < screenSizing_MAX; i++) + { + actScreenSizing[i] = submenu->addAction(QString(screensizing[i])); + actScreenSizing[i]->setActionGroup(grpScreenSizing); + actScreenSizing[i]->setData(QVariant(i)); + actScreenSizing[i]->setCheckable(true); + } + + connect(grpScreenSizing, &QActionGroup::triggered, this, &MainWindow::onChangeScreenSizing); + + submenu->addSeparator(); + + actIntegerScaling = submenu->addAction("Force integer scaling"); + actIntegerScaling->setCheckable(true); + connect(actIntegerScaling, &QAction::triggered, this, &MainWindow::onChangeIntegerScaling); + } + { + QMenu * submenu = menu->addMenu("Aspect ratio"); + grpScreenAspectTop = new QActionGroup(submenu); + grpScreenAspectBot = new QActionGroup(submenu); + actScreenAspectTop = new QAction *[AspectRatiosNum]; + actScreenAspectBot = new QAction *[AspectRatiosNum]; + + for (int i = 0; i < 2; i++) + { + QActionGroup * group = grpScreenAspectTop; + QAction **actions = actScreenAspectTop; + + if (i == 1) + { + group = grpScreenAspectBot; + submenu->addSeparator(); + actions = actScreenAspectBot; + } + + for (int j = 0; j < AspectRatiosNum; 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); + } + } + + menu->addSeparator(); + + actNewWindow = menu->addAction("Open new window"); + connect(actNewWindow, &QAction::triggered, this, &MainWindow::onOpenNewWindow); + + menu->addSeparator(); + + actScreenFiltering = menu->addAction("Screen filtering"); + actScreenFiltering->setCheckable(true); + connect(actScreenFiltering, &QAction::triggered, this, &MainWindow::onChangeScreenFiltering); + + actShowOSD = menu->addAction("Show OSD"); + actShowOSD->setCheckable(true); + connect(actShowOSD, &QAction::triggered, this, &MainWindow::onChangeShowOSD); + } + { + QMenu * menu = menubar->addMenu("Config"); + + actEmuSettings = menu->addAction("Emu settings"); + connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); + +#ifdef __APPLE__ + actPreferences = menu->addAction("Preferences..."); + connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); + actPreferences->setMenuRole(QAction::PreferencesRole); +#endif + + actInputConfig = menu->addAction("Input and hotkeys"); + connect(actInputConfig, &QAction::triggered, this, &MainWindow::onOpenInputConfig); + + actVideoSettings = menu->addAction("Video settings"); + connect(actVideoSettings, &QAction::triggered, this, &MainWindow::onOpenVideoSettings); + + actCameraSettings = menu->addAction("Camera settings"); + connect(actCameraSettings, &QAction::triggered, this, &MainWindow::onOpenCameraSettings); + + 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); + + 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); + + { + QMenu * submenu = menu->addMenu("Savestate settings"); + + actSavestateSRAMReloc = submenu->addAction("Separate savefiles"); + actSavestateSRAMReloc->setCheckable(true); + connect(actSavestateSRAMReloc, &QAction::triggered, this, &MainWindow::onChangeSavestateSRAMReloc); + } + + menu->addSeparator(); + + actLimitFramerate = menu->addAction("Limit framerate"); + actLimitFramerate->setCheckable(true); + connect(actLimitFramerate, &QAction::triggered, this, &MainWindow::onChangeLimitFramerate); + + actAudioSync = menu->addAction("Audio sync"); + actAudioSync->setCheckable(true); + connect(actAudioSync, &QAction::triggered, this, &MainWindow::onChangeAudioSync); + } + { + QMenu * menu = menubar->addMenu("Help"); + actAbout = menu->addAction("About..."); + connect(actAbout, &QAction::triggered, this, [&] + { + auto dialog = AboutDialog(this); + dialog.exec(); + }); + } + + setMenuBar(menubar); + + if (localCfg.GetString("Firmware.Username") == "Arisotura") + actMPNewInstance->setText("Fart"); } - setMenuBar(menubar); - - resize(Config::WindowWidth, Config::WindowHeight); - - if (Config::FirmwareUsername == "Arisotura") - actMPNewInstance->setText("Fart"); #ifdef Q_OS_MAC QPoint screenCenter = screen()->availableGeometry().center(); @@ -588,129 +684,166 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) move(frameGeo.topLeft()); #endif - if (oldMax) - showMaximized(); - else - show(); + std::string geom = windowCfg.GetString("Geometry"); + if (!geom.empty()) + { + QByteArray raw = QByteArray::fromStdString(geom); + QByteArray dec = QByteArray::fromBase64(raw, QByteArray::Base64Encoding | QByteArray::AbortOnBase64DecodingErrors); + if (!dec.isEmpty()) + restoreGeometry(dec); + // if the window was closed in fullscreen do not restore this + setWindowState(windowState() & ~Qt::WindowFullScreen); + } + show(); + panel = nullptr; createScreenPanel(); - actEjectCart->setEnabled(false); - actEjectGBACart->setEnabled(false); - - if (Config::ConsoleType == 1) + if (hasMenu) { - actInsertGBACart->setEnabled(false); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(false); - } + actEjectCart->setEnabled(false); + actEjectGBACart->setEnabled(false); - for (int i = 0; i < 9; i++) - { - actSaveState[i]->setEnabled(false); - actLoadState[i]->setEnabled(false); - } - actUndoStateLoad->setEnabled(false); - actImportSavefile->setEnabled(false); - - actPause->setEnabled(false); - actReset->setEnabled(false); - actStop->setEnabled(false); - actFrameStep->setEnabled(false); - - actDateTime->setEnabled(true); - actPowerManagement->setEnabled(false); - - actSetupCheats->setEnabled(false); - actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); - - actEnableCheats->setChecked(Config::EnableCheats); - - actROMInfo->setEnabled(false); - actRAMInfo->setEnabled(false); - - actSavestateSRAMReloc->setChecked(Config::SavestateRelocSRAM); - - actScreenRotation[Config::ScreenRotation]->setChecked(true); - - for (int i = 0; i < 6; i++) - { - if (actScreenGap[i]->data().toInt() == Config::ScreenGap) + if (globalCfg.GetInt("Emu.ConsoleType") == 1) { - actScreenGap[i]->setChecked(true); - break; + actInsertGBACart->setEnabled(false); + for (auto act: actInsertGBAAddon) + act->setEnabled(false); } - } - actScreenLayout[Config::ScreenLayout]->setChecked(true); - actScreenSizing[Config::ScreenSizing]->setChecked(true); - actIntegerScaling->setChecked(Config::IntegerScaling); + for (int i = 0; i < 9; i++) + { + actSaveState[i]->setEnabled(false); + actLoadState[i]->setEnabled(false); + } + actUndoStateLoad->setEnabled(false); + actImportSavefile->setEnabled(false); - actScreenSwap->setChecked(Config::ScreenSwap); + actPause->setEnabled(false); + actReset->setEnabled(false); + actStop->setEnabled(false); + actFrameStep->setEnabled(false); - for (int i = 0; i < AspectRatiosNum; i++) - { - if (Config::ScreenAspectTop == aspectRatios[i].id) - actScreenAspectTop[i]->setChecked(true); - if (Config::ScreenAspectBot == aspectRatios[i].id) - actScreenAspectBot[i]->setChecked(true); - } + actDateTime->setEnabled(true); + actPowerManagement->setEnabled(false); - actScreenFiltering->setChecked(Config::ScreenFilter); - actShowOSD->setChecked(Config::ShowOSD); + actEnableCheats->setEnabled(false); + actSetupCheats->setEnabled(false); + actTitleManager->setEnabled(!globalCfg.GetString("DSi.NANDPath").empty()); - actLimitFramerate->setChecked(Config::LimitFPS); - actAudioSync->setChecked(Config::AudioSync); + actEnableCheats->setChecked(localCfg.GetBool("EnableCheats")); - if (inst > 0) - { - actEmuSettings->setEnabled(false); - actVideoSettings->setEnabled(false); - actMPSettings->setEnabled(false); - actWifiSettings->setEnabled(false); - actInterfaceSettings->setEnabled(false); + actROMInfo->setEnabled(false); + actRAMInfo->setEnabled(false); + + actSavestateSRAMReloc->setChecked(globalCfg.GetBool("Savestate.RelocSRAM")); + + actScreenRotation[windowCfg.GetInt("ScreenRotation")]->setChecked(true); + + int screenGap = windowCfg.GetInt("ScreenGap"); + for (int i = 0; i < 6; i++) + { + if (actScreenGap[i]->data().toInt() == screenGap) + { + actScreenGap[i]->setChecked(true); + break; + } + } + + actScreenLayout[windowCfg.GetInt("ScreenLayout")]->setChecked(true); + actScreenSizing[windowCfg.GetInt("ScreenSizing")]->setChecked(true); + actIntegerScaling->setChecked(windowCfg.GetBool("IntegerScaling")); + + actScreenSwap->setChecked(windowCfg.GetBool("ScreenSwap")); + + int aspectTop = windowCfg.GetInt("ScreenAspectTop"); + int aspectBot = windowCfg.GetInt("ScreenAspectBot"); + for (int i = 0; i < AspectRatiosNum; i++) + { + if (aspectTop == aspectRatios[i].id) + actScreenAspectTop[i]->setChecked(true); + if (aspectBot == aspectRatios[i].id) + actScreenAspectBot[i]->setChecked(true); + } + + actScreenFiltering->setChecked(windowCfg.GetBool("ScreenFilter")); + actShowOSD->setChecked(showOSD); + + actLimitFramerate->setChecked(emuInstance->doLimitFPS); + actAudioSync->setChecked(emuInstance->doAudioSync); + + if (emuInstance->instanceID > 0) + { + actEmuSettings->setEnabled(false); + actVideoSettings->setEnabled(false); + actMPSettings->setEnabled(false); + actWifiSettings->setEnabled(false); + actInterfaceSettings->setEnabled(false); #ifdef __APPLE__ - actPreferences->setEnabled(false); + actPreferences->setEnabled(false); #endif // __APPLE__ + } + + if (emuThread->emuIsActive()) + onEmuStart(); } + + QObject::connect(qApp, &QApplication::applicationStateChanged, this, &MainWindow::onAppStateChanged); + onUpdateInterfaceSettings(); + + updateMPInterface(MPInterface::GetType()); } MainWindow::~MainWindow() { - delete[] actScreenAspectTop; - delete[] actScreenAspectBot; + if (hasMenu) + { + delete[] actScreenAspectTop; + delete[] actScreenAspectBot; + } } -void MainWindow::osdAddMessage(unsigned int color, const char* fmt, ...) +void MainWindow::osdAddMessage(unsigned int color, const char* msg) { - if (fmt == nullptr) - return; - - char msg[256]; - va_list args; - va_start(args, fmt); - vsnprintf(msg, 256, fmt, args); - va_end(args); - + if (!showOSD) return; panel->osdAddMessage(color, msg); } +void MainWindow::saveEnabled(bool enabled) +{ + if (enabledSaved) return; + windowCfg.SetBool("Enabled", enabled); + enabledSaved = true; +} + void MainWindow::closeEvent(QCloseEvent* event) { - if (hasOGL) - { - // we intentionally don't unpause here - emuThread->emuPause(); - emuThread->deinitContext(); - } + if (windowID == 0) + emuInstance->saveEnabledWindows(); + else + saveEnabled(false); + QByteArray geom = saveGeometry(); + QByteArray enc = geom.toBase64(QByteArray::Base64Encoding); + windowCfg.SetString("Geometry", enc.toStdString()); + Config::Save(); + + emuInstance->deleteWindow(windowID, false); + + // emuInstance may be deleted + // prevent use after free from us + emuInstance = nullptr; QMainWindow::closeEvent(event); } void MainWindow::createScreenPanel() { - hasOGL = (Config::ScreenUseGL != 0) || (Config::_3DRenderer != 0); + if (panel) delete panel; + panel = nullptr; + + hasOGL = globalCfg.GetBool("Screen.UseGL") || + (globalCfg.GetInt("3D.Renderer") != renderer3D_Software); if (hasOGL) { @@ -719,7 +852,15 @@ void MainWindow::createScreenPanel() panel = panelGL; - panelGL->createContext(); + // Check that creating the context hasn't failed + if (panelGL->createContext() == false) + { + Log(LogLevel::Error, "Failed to create OpenGL context, falling back to Software Renderer.\n"); + hasOGL = false; + + globalCfg.SetBool("Screen.UseGL", false); + globalCfg.SetInt("3D.Renderer", renderer3D_Software); + } } if (!hasOGL) @@ -730,8 +871,11 @@ void MainWindow::createScreenPanel() } setCentralWidget(panel); - actScreenFiltering->setEnabled(hasOGL); - panel->osdSetEnabled(Config::ShowOSD); + if (hasMenu) + actScreenFiltering->setEnabled(hasOGL); + panel->osdSetEnabled(showOSD); + + connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(repaint())); connect(this, SIGNAL(screenLayoutChange()), panel, SLOT(onScreenLayoutChanged())); emit screenLayoutChange(); @@ -745,7 +889,7 @@ GL::Context* MainWindow::getOGLContext() return glpanel->getContext(); } -/*void MainWindow::initOpenGL() +void MainWindow::initOpenGL() { if (!hasOGL) return; @@ -761,43 +905,28 @@ void MainWindow::deinitOpenGL() return glpanel->deinitOpenGL(); } +void MainWindow::setGLSwapInterval(int intv) +{ + if (!hasOGL) return; + + ScreenPanelGL* glpanel = static_cast(panel); + return glpanel->setSwapInterval(intv); +} + +void MainWindow::makeCurrentGL() +{ + if (!hasOGL) return; + + ScreenPanelGL* glpanel = static_cast(panel); + return glpanel->makeCurrentGL(); +} + void MainWindow::drawScreenGL() { if (!hasOGL) return; ScreenPanelGL* glpanel = static_cast(panel); return glpanel->drawScreenGL(); -}*/ - -void MainWindow::resizeEvent(QResizeEvent* event) -{ - int w = event->size().width(); - int h = event->size().height(); - - if (!isFullScreen()) - { - // this is ugly - // thing is, when maximizing the window, we first receive the resizeEvent - // with a new size matching the screen, then the changeEvent telling us that - // the maximized flag was updated - oldW = Config::WindowWidth; - oldH = Config::WindowHeight; - oldMax = isMaximized(); - - Config::WindowWidth = w; - Config::WindowHeight = h; - } -} - -void MainWindow::changeEvent(QEvent* event) -{ - if (isMaximized() && !oldMax) - { - Config::WindowWidth = oldW; - Config::WindowHeight = oldH; - } - - Config::WindowMaximized = isMaximized() ? 1:0; } void MainWindow::keyPressEvent(QKeyEvent* event) @@ -805,16 +934,16 @@ void MainWindow::keyPressEvent(QKeyEvent* event) if (event->isAutoRepeat()) return; // TODO!! REMOVE ME IN RELEASE BUILDS!! - //if (event->key() == Qt::Key_F11) NDS::debug(0); + //if (event->key() == Qt::Key_F11) emuThread->NDS->debug(0); - Input::KeyPress(event); + emuInstance->onKeyPress(event); } void MainWindow::keyReleaseEvent(QKeyEvent* event) { if (event->isAutoRepeat()) return; - Input::KeyRelease(event); + emuInstance->onKeyRelease(event); } @@ -838,20 +967,12 @@ void MainWindow::dropEvent(QDropEvent* event) QList urls = event->mimeData()->urls(); if (urls.count() > 1) return; // not handling more than one file at once - emuThread->emuPause(); - if (!verifySetup()) - { - emuThread->emuUnpause(); return; - } const QStringList file = splitArchivePath(urls.at(0).toLocalFile(), false); if (file.isEmpty()) - { - emuThread->emuUnpause(); return; - } const QString filename = file.last(); const bool romInsideArchive = file.size() > 1; @@ -865,11 +986,8 @@ void MainWindow::dropEvent(QDropEvent* event) if (isNdsRom) { - if (!ROMManager::LoadROM(emuThread, file, true)) + if (!emuThread->bootROM(file)) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the DS ROM."); - emuThread->emuUnpause(); return; } @@ -878,61 +996,68 @@ void MainWindow::dropEvent(QDropEvent* event) recentFileList.prepend(barredFilename); updateRecentFilesMenu(); - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); - emuThread->emuRun(); - updateCartInserted(false); } else if (isGbaRom) { - if (!ROMManager::LoadGBAROM(*emuThread->NDS, file)) + if (!emuThread->insertCart(file, true)) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM."); - emuThread->emuUnpause(); return; } - emuThread->emuUnpause(); - updateCartInserted(true); } else { QMessageBox::critical(this, "melonDS", "The file could not be recognized as a DS or GBA ROM."); - emuThread->emuUnpause(); return; } } void MainWindow::focusInEvent(QFocusEvent* event) { - AudioInOut::AudioMute(mainWindow); + onFocusIn(); } void MainWindow::focusOutEvent(QFocusEvent* event) { - AudioInOut::AudioMute(mainWindow); + onFocusOut(); +} + +void MainWindow::onFocusIn() +{ + focused = true; + if (emuInstance) + emuInstance->audioMute(); +} + +void MainWindow::onFocusOut() +{ + // focusOutEvent is called through the window close event handler + // prevent use after free + focused = false; + if (emuInstance) + emuInstance->audioMute(); } void MainWindow::onAppStateChanged(Qt::ApplicationState state) { if (state == Qt::ApplicationInactive) { - if (Config::PauseLostFocus && emuThread->emuIsRunning()) + emuInstance->keyReleaseAll(); + if (pauseOnLostFocus && emuThread->emuIsRunning()) emuThread->emuPause(); } else if (state == Qt::ApplicationActive) { - if (Config::PauseLostFocus && !pausedManually) + if (pauseOnLostFocus && !pausedManually) emuThread->emuUnpause(); } } bool MainWindow::verifySetup() { - QString res = ROMManager::VerifySetup(); + QString res = emuInstance->verifySetup(); if (!res.isEmpty()) { QMessageBox::critical(this, "melonDS", res); @@ -944,6 +1069,9 @@ bool MainWindow::verifySetup() bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) { + if (file.isEmpty() && gbafile.isEmpty()) + return false; + if (!verifySetup()) { return false; @@ -952,12 +1080,8 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) bool gbaloaded = false; if (!gbafile.isEmpty()) { - if (!ROMManager::LoadGBAROM(*emuThread->NDS, gbafile)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM."); + if (!emuThread->insertCart(gbafile, true)) return false; - } gbaloaded = true; } @@ -965,37 +1089,31 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) bool ndsloaded = false; if (!file.isEmpty()) { - if (!ROMManager::LoadROM(emuThread, file, true)) + if (boot) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - return false; + if (!emuThread->bootROM(file)) + return false; } + else + { + if (!emuThread->insertCart(file, false)) + return false; + } + recentFileList.removeAll(file.join("|")); recentFileList.prepend(file.join("|")); updateRecentFilesMenu(); ndsloaded = true; } - - if (boot) + else if (boot) { - if (ndsloaded) - { - emuThread->NDS->Start(); - emuThread->emuRun(); - } - else - { - onBootFirmware(); - } + if (!emuThread->bootFirmware()) + return false; } updateCartInserted(false); - if (gbaloaded) - { updateCartInserted(true); - } return true; } @@ -1106,6 +1224,8 @@ QString MainWindow::pickFileFromArchive(QString archiveFileName) QStringList MainWindow::pickROM(bool gba) { + emuThread->emuPause(); + const QString console = gba ? "GBA" : "DS"; const QStringList& romexts = gba ? GbaRomExtensions : NdsRomExtensions; @@ -1126,59 +1246,68 @@ QStringList MainWindow::pickROM(bool gba) const QString filename = QFileDialog::getOpenFileName( this, "Open " + console + " ROM", - QString::fromStdString(Config::LastROMFolder), + globalCfg.GetQString("LastROMFolder"), "All supported files (*" + allROMs + ")" + extraFilters ); - if (filename.isEmpty()) return {}; + if (filename.isEmpty()) + { + emuThread->emuUnpause(); + return {}; + } - Config::LastROMFolder = QFileInfo(filename).dir().path().toStdString(); - return splitArchivePath(filename, false); + globalCfg.SetQString("LastROMFolder", QFileInfo(filename).dir().path()); + auto ret = splitArchivePath(filename, false); + emuThread->emuUnpause(); + return ret; } void MainWindow::updateCartInserted(bool gba) { bool inserted; + QString label; if (gba) { - inserted = ROMManager::GBACartInserted() && (Config::ConsoleType == 0); - actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); - actEjectGBACart->setEnabled(inserted); + inserted = emuInstance->gbaCartInserted() && (emuInstance->getConsoleType() == 0); + label = "GBA slot: " + emuInstance->gbaCartLabel(); + + emuInstance->doOnAllWindows([=](MainWindow* win) + { + if (!win->hasMenu) return; + win->actCurrentGBACart->setText(label); + win->actEjectGBACart->setEnabled(inserted); + }); } else { - inserted = ROMManager::CartInserted(); - actCurrentCart->setText("DS slot: " + ROMManager::CartLabel()); - actEjectCart->setEnabled(inserted); - actImportSavefile->setEnabled(inserted); - actSetupCheats->setEnabled(inserted); - actROMInfo->setEnabled(inserted); - actRAMInfo->setEnabled(inserted); + inserted = emuInstance->cartInserted(); + label = "DS slot: " + emuInstance->cartLabel(); + + emuInstance->doOnAllWindows([=](MainWindow* win) + { + if (!win->hasMenu) return; + win->actCurrentCart->setText(label); + win->actEjectCart->setEnabled(inserted); + win->actImportSavefile->setEnabled(inserted); + win->actEnableCheats->setEnabled(inserted); + win->actSetupCheats->setEnabled(inserted); + win->actROMInfo->setEnabled(inserted); + win->actRAMInfo->setEnabled(inserted); + }); } } void MainWindow::onOpenFile() { - emuThread->emuPause(); - if (!verifySetup()) - { - emuThread->emuUnpause(); return; - } QStringList file = pickROM(false); if (file.isEmpty()) - { - emuThread->emuUnpause(); return; - } - if (!ROMManager::LoadROM(emuThread, file, true)) + if (!emuThread->bootROM(file)) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - emuThread->emuUnpause(); return; } @@ -1187,28 +1316,37 @@ void MainWindow::onOpenFile() recentFileList.prepend(filename); updateRecentFilesMenu(); - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); - emuThread->emuRun(); - updateCartInserted(false); } void MainWindow::onClearRecentFiles() { recentFileList.clear(); - for (int i = 0; i < 10; i++) - Config::RecentROMList[i] = ""; + globalCfg.GetArray("RecentROM").Clear(); updateRecentFilesMenu(); } -void MainWindow::updateRecentFilesMenu() +void MainWindow::loadRecentFilesMenu(bool loadcfg) { + if (loadcfg) + { + recentFileList.clear(); + + Config::Array recentROMs = globalCfg.GetArray("RecentROM"); + int numrecent = std::min(kMaxRecentROMs, (int) recentROMs.Size()); + for (int i = 0; i < numrecent; ++i) + { + std::string item = recentROMs.GetString(i); + if (!item.empty()) + recentFileList.push_back(QString::fromStdString(item)); + } + } + recentMenu->clear(); for (int i = 0; i < recentFileList.size(); ++i) { - if (i >= 10) break; + if (i >= kMaxRecentROMs) break; QString item_full = recentFileList.at(i); QString item_display = item_full; @@ -1235,8 +1373,6 @@ void MainWindow::updateRecentFilesMenu() QAction *actRecentFile_i = recentMenu->addAction(QString("%1. %2").arg(i+1).arg(item_display)); actRecentFile_i->setData(item_full); connect(actRecentFile_i, &QAction::triggered, this, &MainWindow::onClickRecentFile); - - Config::RecentROMList[i] = recentFileList.at(i).toStdString(); } while (recentFileList.size() > 10) @@ -1249,8 +1385,24 @@ void MainWindow::updateRecentFilesMenu() if (recentFileList.empty()) actClearRecentList->setEnabled(false); +} + +void MainWindow::updateRecentFilesMenu() +{ + Config::Array recentroms = globalCfg.GetArray("RecentROM"); + recentroms.Clear(); + + for (int i = 0; i < recentFileList.size(); ++i) + { + if (i >= kMaxRecentROMs) break; + + recentroms.SetQString(i, recentFileList.at(i)); + } Config::Save(); + loadRecentFilesMenu(false); + + emuInstance->broadcastCommand(InstCmd_UpdateRecentFiles); } void MainWindow::onClickRecentFile() @@ -1258,26 +1410,15 @@ void MainWindow::onClickRecentFile() QAction *act = (QAction *)sender(); QString filename = act->data().toString(); - emuThread->emuPause(); - if (!verifySetup()) - { - emuThread->emuUnpause(); return; - } const QStringList file = splitArchivePath(filename, true); if (file.isEmpty()) - { - emuThread->emuUnpause(); return; - } - if (!ROMManager::LoadROM(emuThread, file, true)) + if (!emuThread->bootROM(file)) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - emuThread->emuUnpause(); return; } @@ -1285,92 +1426,52 @@ void MainWindow::onClickRecentFile() recentFileList.prepend(filename); updateRecentFilesMenu(); - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); - emuThread->emuRun(); - updateCartInserted(false); } void MainWindow::onBootFirmware() { - emuThread->emuPause(); - if (!verifySetup()) - { - emuThread->emuUnpause(); return; - } - if (!ROMManager::BootToMenu(emuThread)) + if (!emuThread->bootFirmware()) { - // TODO: better error reporting? QMessageBox::critical(this, "melonDS", "This firmware is not bootable."); - emuThread->emuUnpause(); return; } - - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); - emuThread->emuRun(); } void MainWindow::onInsertCart() { - emuThread->emuPause(); - QStringList file = pickROM(false); if (file.isEmpty()) + return; + + if (!emuThread->insertCart(file, false)) { - emuThread->emuUnpause(); return; } - if (!ROMManager::LoadROM(emuThread, file, false)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - emuThread->emuUnpause(); - return; - } - - emuThread->emuUnpause(); - updateCartInserted(false); } void MainWindow::onEjectCart() { - emuThread->emuPause(); - - ROMManager::EjectCart(*emuThread->NDS); - - emuThread->emuUnpause(); - + emuThread->ejectCart(false); updateCartInserted(false); } void MainWindow::onInsertGBACart() { - emuThread->emuPause(); - QStringList file = pickROM(true); if (file.isEmpty()) + return; + + if (!emuThread->insertCart(file, true)) { - emuThread->emuUnpause(); return; } - if (!ROMManager::LoadGBAROM(*emuThread->NDS, file)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - emuThread->emuUnpause(); - return; - } - - emuThread->emuUnpause(); - updateCartInserted(true); } @@ -1379,23 +1480,13 @@ void MainWindow::onInsertGBAAddon() QAction* act = (QAction*)sender(); int type = act->data().toInt(); - emuThread->emuPause(); - - ROMManager::LoadGBAAddon(*emuThread->NDS, type); - - emuThread->emuUnpause(); - + emuThread->insertGBAAddon(type); updateCartInserted(true); } void MainWindow::onEjectGBACart() { - emuThread->emuPause(); - - ROMManager::EjectGBACart(*emuThread->NDS); - - emuThread->emuUnpause(); - + emuThread->ejectCart(true); updateCartInserted(true); } @@ -1403,205 +1494,164 @@ void MainWindow::onSaveState() { int slot = ((QAction*)sender())->data().toInt(); - emuThread->emuPause(); - - std::string filename; + QString filename; if (slot > 0) { - filename = ROMManager::GetSavestateName(slot); + filename = QString::fromStdString(emuInstance->getSavestateName(slot)); } else { // TODO: specific 'last directory' for savestate files? - QString qfilename = QFileDialog::getSaveFileName(this, + emuThread->emuPause(); + filename = QFileDialog::getSaveFileName(this, "Save state", - QString::fromStdString(Config::LastROMFolder), + globalCfg.GetQString("LastROMFolder"), "melonDS savestates (*.mln);;Any file (*.*)"); - if (qfilename.isEmpty()) - { - emuThread->emuUnpause(); + emuThread->emuUnpause(); + if (filename.isEmpty()) return; - } - - filename = qfilename.toStdString(); } - if (ROMManager::SaveState(*emuThread->NDS, filename)) + if (emuThread->saveState(filename)) { - if (slot > 0) osdAddMessage(0, "State saved to slot %d", slot); - else osdAddMessage(0, "State saved to file"); + if (slot > 0) emuInstance->osdAddMessage(0, "State saved to slot %d", slot); + else emuInstance->osdAddMessage(0, "State saved to file"); actLoadState[slot]->setEnabled(true); } else { - osdAddMessage(0xFFA0A0, "State save failed"); + emuInstance->osdAddMessage(0xFFA0A0, "State save failed"); } - - emuThread->emuUnpause(); } void MainWindow::onLoadState() { int slot = ((QAction*)sender())->data().toInt(); - emuThread->emuPause(); - - std::string filename; + QString filename; if (slot > 0) { - filename = ROMManager::GetSavestateName(slot); + filename = QString::fromStdString(emuInstance->getSavestateName(slot)); } else { // TODO: specific 'last directory' for savestate files? - QString qfilename = QFileDialog::getOpenFileName(this, + emuThread->emuPause(); + filename = QFileDialog::getOpenFileName(this, "Load state", - QString::fromStdString(Config::LastROMFolder), + globalCfg.GetQString("LastROMFolder"), "melonDS savestates (*.ml*);;Any file (*.*)"); - if (qfilename.isEmpty()) - { - emuThread->emuUnpause(); + emuThread->emuUnpause(); + if (filename.isEmpty()) return; - } - - filename = qfilename.toStdString(); } - if (!Platform::FileExists(filename)) + if (!Platform::FileExists(filename.toStdString())) { - if (slot > 0) osdAddMessage(0xFFA0A0, "State slot %d is empty", slot); - else osdAddMessage(0xFFA0A0, "State file does not exist"); + if (slot > 0) emuInstance->osdAddMessage(0xFFA0A0, "State slot %d is empty", slot); + else emuInstance->osdAddMessage(0xFFA0A0, "State file does not exist"); - emuThread->emuUnpause(); return; } - if (ROMManager::LoadState(*emuThread->NDS, filename)) + if (emuThread->loadState(filename)) { - if (slot > 0) osdAddMessage(0, "State loaded from slot %d", slot); - else osdAddMessage(0, "State loaded from file"); + if (slot > 0) emuInstance->osdAddMessage(0, "State loaded from slot %d", slot); + else emuInstance->osdAddMessage(0, "State loaded from file"); actUndoStateLoad->setEnabled(true); } else { - osdAddMessage(0xFFA0A0, "State load failed"); + emuInstance->osdAddMessage(0xFFA0A0, "State load failed"); } - - emuThread->emuUnpause(); } void MainWindow::onUndoStateLoad() { - emuThread->emuPause(); - ROMManager::UndoStateLoad(*emuThread->NDS); - emuThread->emuUnpause(); + emuThread->undoStateLoad(); - osdAddMessage(0, "State load undone"); + emuInstance->osdAddMessage(0, "State load undone"); } void MainWindow::onImportSavefile() { - emuThread->emuPause(); QString path = QFileDialog::getOpenFileName(this, "Select savefile", - QString::fromStdString(Config::LastROMFolder), + globalCfg.GetQString("LastROMFolder"), "Savefiles (*.sav *.bin *.dsv);;Any file (*.*)"); if (path.isEmpty()) - { - emuThread->emuUnpause(); return; - } - Platform::FileHandle* f = Platform::OpenFile(path.toStdString(), Platform::FileMode::Read); - if (!f) + if (!Platform::FileExists(path.toStdString())) { QMessageBox::critical(this, "melonDS", "Could not open the given savefile."); - emuThread->emuUnpause(); return; } - if (RunningSomething) + if (emuThread->emuIsActive()) { if (QMessageBox::warning(this, "melonDS", "The emulation will be reset and the current savefile overwritten.", QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) { - emuThread->emuUnpause(); return; } - - ROMManager::Reset(emuThread); } - u32 len = FileLength(f); - - std::unique_ptr data = std::make_unique(len); - Platform::FileRewind(f); - Platform::FileRead(data.get(), len, 1, f); - - assert(emuThread->NDS != nullptr); - emuThread->NDS->SetNDSSave(data.get(), len); - - CloseFile(f); - emuThread->emuUnpause(); + if (!emuThread->importSavefile(path)) + { + QMessageBox::critical(this, "melonDS", "Could not import the given savefile."); + return; + } } void MainWindow::onQuit() { #ifndef _WIN32 - signalSn->setEnabled(false); + if (!parentWidget()) + signalSn->setEnabled(false); #endif - QApplication::quit(); + close(); } void MainWindow::onPause(bool checked) { - if (!RunningSomething) return; + if (!emuThread->emuIsActive()) return; if (checked) { emuThread->emuPause(); - osdAddMessage(0, "Paused"); pausedManually = true; } else { emuThread->emuUnpause(); - osdAddMessage(0, "Resumed"); pausedManually = false; } } void MainWindow::onReset() { - if (!RunningSomething) return; + if (!emuThread->emuIsActive()) return; - emuThread->emuPause(); - - actUndoStateLoad->setEnabled(false); - - ROMManager::Reset(emuThread); - - osdAddMessage(0, "Reset"); - emuThread->emuRun(); + emuThread->emuReset(); } void MainWindow::onStop() { - if (!RunningSomething) return; + if (!emuThread->emuIsActive()) return; - emuThread->emuPause(); - emuThread->NDS->Stop(); + emuThread->emuStop(true); } void MainWindow::onFrameStep() { - if (!RunningSomething) return; + if (!emuThread->emuIsActive()) return; emuThread->emuFrameStep(); } @@ -1613,13 +1663,18 @@ void MainWindow::onOpenDateTime() void MainWindow::onOpenPowerManagement() { - PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this, emuThread); + PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this); } void MainWindow::onEnableCheats(bool checked) { - Config::EnableCheats = checked?1:0; - ROMManager::EnableCheats(*emuThread->NDS, Config::EnableCheats != 0); + localCfg.SetBool("EnableCheats", checked); + emuThread->enableCheats(checked); + + emuInstance->doOnAllWindows([=](MainWindow* win) + { + win->actEnableCheats->setChecked(checked); + }, windowID); } void MainWindow::onSetupCheats() @@ -1637,14 +1692,14 @@ void MainWindow::onCheatsDialogFinished(int res) void MainWindow::onROMInfo() { - auto cart = emuThread->NDS->NDSCartSlot.GetCart(); + auto cart = emuInstance->nds->NDSCartSlot.GetCart(); if (cart) - ROMInfoDialog* dlg = ROMInfoDialog::openDlg(this, *cart); + ROMInfoDialog* dlg = ROMInfoDialog::openDlg(this); } void MainWindow::onRAMInfo() { - RAMInfoDialog* dlg = RAMInfoDialog::openDlg(this, emuThread); + RAMInfoDialog* dlg = RAMInfoDialog::openDlg(this); } void MainWindow::onOpenTitleManager() @@ -1654,19 +1709,70 @@ void MainWindow::onOpenTitleManager() void MainWindow::onMPNewInstance() { - //QProcess::startDetached(QApplication::applicationFilePath()); - QProcess newinst; - newinst.setProgram(QApplication::applicationFilePath()); - newinst.setArguments(QApplication::arguments().mid(1, QApplication::arguments().length()-1)); + createEmuInstance(); +} -#ifdef __WIN32__ - newinst.setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args) - { - args->flags |= CREATE_NEW_CONSOLE; - }); -#endif +void MainWindow::onLANStartHost() +{ + if (!lanWarning(true)) return; + LANStartHostDialog::openDlg(this); +} - newinst.startDetached(); +void MainWindow::onLANStartClient() +{ + if (!lanWarning(false)) return; + LANStartClientDialog::openDlg(this); +} + +void MainWindow::onNPStartHost() +{ + //Netplay::StartHost(); + //NetplayStartHostDialog::openDlg(this); +} + +void MainWindow::onNPStartClient() +{ + //Netplay::StartClient(); + //NetplayStartClientDialog::openDlg(this); +} + +void MainWindow::onNPTest() +{ + // HAX + //Netplay::StartGame(); +} + +void MainWindow::updateMPInterface(MPInterfaceType type) +{ + if (!hasMenu) return; + + // MP interface was changed, reflect it in the UI + + bool enable = (type == MPInterface_Local); + actMPNewInstance->setEnabled(enable); + actLANStartHost->setEnabled(enable); + actLANStartClient->setEnabled(enable); + /*actNPStartHost->setEnabled(enable); + actNPStartClient->setEnabled(enable); + actNPTest->setEnabled(enable);*/ +} + +bool MainWindow::lanWarning(bool host) +{ + if (numEmuInstances() < 2) + return true; + + QString verb = host ? "host" : "join"; + QString msg = "Multiple emulator instances are currently open.\n" + "If you "+verb+" a LAN game now, all secondary instances will be closed.\n\n" + "Do you wish to continue?"; + + auto res = QMessageBox::warning(this, "melonDS", msg, QMessageBox::Yes|QMessageBox::No, QMessageBox::No); + if (res == QMessageBox::No) + return false; + + deleteAllEmuInstances(1); + return true; } void MainWindow::onOpenEmuSettings() @@ -1679,30 +1785,30 @@ void MainWindow::onOpenEmuSettings() void MainWindow::onEmuSettingsDialogFinished(int res) { - emuThread->emuUnpause(); - - if (Config::ConsoleType == 1) + if (globalCfg.GetInt("Emu.ConsoleType") == 1) { actInsertGBACart->setEnabled(false); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(false); + for (auto act : actInsertGBAAddon) + act->setEnabled(false); actEjectGBACart->setEnabled(false); } else { actInsertGBACart->setEnabled(true); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(true); - actEjectGBACart->setEnabled(ROMManager::GBACartInserted()); + for (auto act : actInsertGBAAddon) + act->setEnabled(true); + actEjectGBACart->setEnabled(emuInstance->gbaCartInserted()); } if (EmuSettingsDialog::needsReset) onReset(); - actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); + actCurrentGBACart->setText("GBA slot: " + emuInstance->gbaCartLabel()); - if (!RunningSomething) - actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); + if (!emuThread->emuIsActive()) + actTitleManager->setEnabled(!globalCfg.GetString("DSi.NANDPath").empty()); + + emuThread->emuUnpause(); } void MainWindow::onOpenInputConfig() @@ -1747,9 +1853,10 @@ void MainWindow::onCameraSettingsFinished(int res) void MainWindow::onOpenAudioSettings() { - AudioSettingsDialog* dlg = AudioSettingsDialog::openDlg(this, emuThread->emuIsActive(), emuThread); + AudioSettingsDialog* dlg = AudioSettingsDialog::openDlg(this); connect(emuThread, &EmuThread::syncVolumeLevel, dlg, &AudioSettingsDialog::onSyncVolumeLevel); connect(emuThread, &EmuThread::windowEmuStart, dlg, &AudioSettingsDialog::onConsoleReset); + connect(dlg, &AudioSettingsDialog::updateAudioVolume, this, &MainWindow::onUpdateAudioVolume); connect(dlg, &AudioSettingsDialog::updateAudioSettings, this, &MainWindow::onUpdateAudioSettings); connect(dlg, &AudioSettingsDialog::finished, this, &MainWindow::onAudioSettingsFinished); } @@ -1786,20 +1893,30 @@ void MainWindow::onPathSettingsFinished(int res) emuThread->emuUnpause(); } +void MainWindow::onUpdateAudioVolume(int vol, int dsisync) +{ + emuInstance->audioVolume = vol; + emuInstance->audioDSiVolumeSync = dsisync; +} + void MainWindow::onUpdateAudioSettings() { - assert(emuThread->NDS != nullptr); - emuThread->NDS->SPU.SetInterpolation(static_cast(Config::AudioInterp)); + if (!emuThread->emuIsActive()) return; + assert(emuInstance->nds != nullptr); - if (Config::AudioBitDepth == 0) - emuThread->NDS->SPU.SetDegrade10Bit(emuThread->NDS->ConsoleType == 0); + int interp = globalCfg.GetInt("Audio.Interpolation"); + emuInstance->nds->SPU.SetInterpolation(static_cast(interp)); + + int bitdepth = globalCfg.GetInt("Audio.BitDepth"); + if (bitdepth == 0) + emuInstance->nds->SPU.SetDegrade10Bit(emuInstance->nds->ConsoleType == 0); else - emuThread->NDS->SPU.SetDegrade10Bit(Config::AudioBitDepth == 1); + emuInstance->nds->SPU.SetDegrade10Bit(bitdepth == 1); } void MainWindow::onAudioSettingsFinished(int res) { - AudioInOut::UpdateSettings(*emuThread->NDS); + //AudioInOut::UpdateSettings(*emuThread->NDS); } void MainWindow::onOpenMPSettings() @@ -1812,8 +1929,9 @@ void MainWindow::onOpenMPSettings() void MainWindow::onMPSettingsFinished(int res) { - AudioInOut::AudioMute(mainWindow); - LocalMP::SetRecvTimeout(Config::MPRecvTimeout); + emuInstance->mpAudioMode = globalCfg.GetInt("MP.AudioMode"); + emuInstance->audioMute(); + MPInterface::Get().SetRecvTimeout(globalCfg.GetInt("MP.RecvTimeout")); emuThread->emuUnpause(); } @@ -1828,9 +1946,6 @@ void MainWindow::onOpenWifiSettings() void MainWindow::onWifiSettingsFinished(int res) { - Platform::LAN_DeInit(); - Platform::LAN_Init(); - if (WifiSettingsDialog::needsReset) onReset(); @@ -1842,12 +1957,17 @@ void MainWindow::onOpenInterfaceSettings() emuThread->emuPause(); InterfaceSettingsDialog* dlg = InterfaceSettingsDialog::openDlg(this); connect(dlg, &InterfaceSettingsDialog::finished, this, &MainWindow::onInterfaceSettingsFinished); - connect(dlg, &InterfaceSettingsDialog::updateMouseTimer, this, &MainWindow::onUpdateMouseTimer); + connect(dlg, &InterfaceSettingsDialog::updateInterfaceSettings, this, &MainWindow::onUpdateInterfaceSettings); } -void MainWindow::onUpdateMouseTimer() +void MainWindow::onUpdateInterfaceSettings() { - panel->mouseTimer->setInterval(Config::MouseHideSeconds*1000); + pauseOnLostFocus = globalCfg.GetBool("PauseLostFocus"); + emuInstance->targetFPS = globalCfg.GetDouble("TargetFPS"); + emuInstance->fastForwardFPS = globalCfg.GetDouble("FastForwardFPS"); + emuInstance->slowmoFPS = globalCfg.GetDouble("SlowmoFPS"); + panel->setMouseHide(globalCfg.GetBool("MouseHide"), + globalCfg.GetInt("MouseHideSeconds")*1000); } void MainWindow::onInterfaceSettingsFinished(int res) @@ -1857,7 +1977,7 @@ void MainWindow::onInterfaceSettingsFinished(int res) void MainWindow::onChangeSavestateSRAMReloc(bool checked) { - Config::SavestateRelocSRAM = checked?1:0; + globalCfg.SetBool("Savestate.RelocSRAM", checked); } void MainWindow::onChangeScreenSize() @@ -1870,7 +1990,7 @@ void MainWindow::onChangeScreenSize() void MainWindow::onChangeScreenRotation(QAction* act) { int rot = act->data().toInt(); - Config::ScreenRotation = rot; + windowCfg.SetInt("ScreenRotation", rot); emit screenLayoutChange(); } @@ -1878,7 +1998,7 @@ void MainWindow::onChangeScreenRotation(QAction* act) void MainWindow::onChangeScreenGap(QAction* act) { int gap = act->data().toInt(); - Config::ScreenGap = gap; + windowCfg.SetInt("ScreenGap", gap); emit screenLayoutChange(); } @@ -1886,30 +2006,32 @@ void MainWindow::onChangeScreenGap(QAction* act) void MainWindow::onChangeScreenLayout(QAction* act) { int layout = act->data().toInt(); - Config::ScreenLayout = layout; + windowCfg.SetInt("ScreenLayout", layout); emit screenLayoutChange(); } void MainWindow::onChangeScreenSwap(bool checked) { - Config::ScreenSwap = checked?1:0; + windowCfg.SetBool("ScreenSwap", checked); // Swap between top and bottom screen when displaying one screen. - if (Config::ScreenSizing == Frontend::screenSizing_TopOnly) + int sizing = windowCfg.GetInt("ScreenSizing"); + if (sizing == screenSizing_TopOnly) { // Bottom Screen. - Config::ScreenSizing = Frontend::screenSizing_BotOnly; - actScreenSizing[Frontend::screenSizing_TopOnly]->setChecked(false); - actScreenSizing[Config::ScreenSizing]->setChecked(true); + sizing = screenSizing_BotOnly; + actScreenSizing[screenSizing_TopOnly]->setChecked(false); + actScreenSizing[sizing]->setChecked(true); } - else if (Config::ScreenSizing == Frontend::screenSizing_BotOnly) + else if (sizing == screenSizing_BotOnly) { // Top Screen. - Config::ScreenSizing = Frontend::screenSizing_TopOnly; - actScreenSizing[Frontend::screenSizing_BotOnly]->setChecked(false); - actScreenSizing[Config::ScreenSizing]->setChecked(true); + sizing = screenSizing_TopOnly; + actScreenSizing[screenSizing_BotOnly]->setChecked(false); + actScreenSizing[sizing]->setChecked(true); } + windowCfg.SetInt("ScreenSizing", sizing); emit screenLayoutChange(); } @@ -1917,7 +2039,7 @@ void MainWindow::onChangeScreenSwap(bool checked) void MainWindow::onChangeScreenSizing(QAction* act) { int sizing = act->data().toInt(); - Config::ScreenSizing = sizing; + windowCfg.SetInt("ScreenSizing", sizing); emit screenLayoutChange(); } @@ -1929,11 +2051,11 @@ void MainWindow::onChangeScreenAspect(QAction* act) if (group == grpScreenAspectTop) { - Config::ScreenAspectTop = aspect; + windowCfg.SetInt("ScreenAspectTop", aspect); } else { - Config::ScreenAspectBot = aspect; + windowCfg.SetInt("ScreenAspectBot", aspect); } emit screenLayoutChange(); @@ -1941,32 +2063,41 @@ void MainWindow::onChangeScreenAspect(QAction* act) void MainWindow::onChangeIntegerScaling(bool checked) { - Config::IntegerScaling = checked?1:0; + windowCfg.SetBool("IntegerScaling", checked); emit screenLayoutChange(); } +void MainWindow::onOpenNewWindow() +{ + emuInstance->createWindow(); +} + void MainWindow::onChangeScreenFiltering(bool checked) { - Config::ScreenFilter = checked?1:0; + windowCfg.SetBool("ScreenFilter", checked); - emit screenLayoutChange(); + //emit screenLayoutChange(); + panel->setFilter(checked); } void MainWindow::onChangeShowOSD(bool checked) { - Config::ShowOSD = checked?1:0; - panel->osdSetEnabled(Config::ShowOSD); + showOSD = checked; + panel->osdSetEnabled(showOSD); + windowCfg.SetBool("ShowOSD", showOSD); } void MainWindow::onChangeLimitFramerate(bool checked) { - Config::LimitFPS = checked?1:0; + emuInstance->doLimitFPS = checked; + globalCfg.SetBool("LimitFPS", emuInstance->doLimitFPS); } void MainWindow::onChangeAudioSync(bool checked) { - Config::AudioSync = checked?1:0; + emuInstance->doAudioSync = checked; + globalCfg.SetBool("AudioSync", emuInstance->doAudioSync); } @@ -1975,47 +2106,54 @@ void MainWindow::onTitleUpdate(QString title) setWindowTitle(title); } -void ToggleFullscreen(MainWindow* mainWindow) +void MainWindow::toggleFullscreen() { - if (!mainWindow->isFullScreen()) + if (!isFullScreen()) { - mainWindow->showFullScreen(); - mainWindow->menuBar()->setFixedHeight(0); // Don't use hide() as menubar actions stop working + showFullScreen(); + if (hasMenu) + menuBar()->setFixedHeight(0); // Don't use hide() as menubar actions stop working } else { - mainWindow->showNormal(); - int menuBarHeight = mainWindow->menuBar()->sizeHint().height(); - mainWindow->menuBar()->setFixedHeight(menuBarHeight); + showNormal(); + if (hasMenu) + { + int menuBarHeight = menuBar()->sizeHint().height(); + menuBar()->setFixedHeight(menuBarHeight); + } } } void MainWindow::onFullscreenToggled() { - ToggleFullscreen(this); + toggleFullscreen(); } void MainWindow::onScreenEmphasisToggled() { - int currentSizing = Config::ScreenSizing; - if (currentSizing == Frontend::screenSizing_EmphTop) + int currentSizing = windowCfg.GetInt("ScreenSizing"); + if (currentSizing == screenSizing_EmphTop) { - Config::ScreenSizing = Frontend::screenSizing_EmphBot; + currentSizing = screenSizing_EmphBot; } - else if (currentSizing == Frontend::screenSizing_EmphBot) + else if (currentSizing == screenSizing_EmphBot) { - Config::ScreenSizing = Frontend::screenSizing_EmphTop; + currentSizing = screenSizing_EmphTop; } + windowCfg.SetInt("ScreenSizing", currentSizing); emit screenLayoutChange(); } void MainWindow::onEmuStart() { + if (!hasMenu) return; + for (int i = 1; i < 9; i++) { actSaveState[i]->setEnabled(true); - actLoadState[i]->setEnabled(ROMManager::SavestateExists(i)); + actLoadState[i]->setEnabled(emuInstance->savestateExists(i)); } actSaveState[0]->setEnabled(true); actLoadState[0]->setEnabled(true); @@ -2035,7 +2173,7 @@ void MainWindow::onEmuStart() void MainWindow::onEmuStop() { - emuThread->emuPause(); + if (!hasMenu) return; for (int i = 0; i < 9; i++) { @@ -2052,26 +2190,74 @@ void MainWindow::onEmuStop() actDateTime->setEnabled(true); actPowerManagement->setEnabled(false); - actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); + actTitleManager->setEnabled(!globalCfg.GetString("DSi.NANDPath").empty()); +} + +void MainWindow::onEmuPause(bool pause) +{ + if (!hasMenu) return; + + actPause->setChecked(pause); +} + +void MainWindow::onEmuReset() +{ + if (!hasMenu) return; + + actUndoStateLoad->setEnabled(false); } void MainWindow::onUpdateVideoSettings(bool glchange) { + if (!emuInstance) return; + + // if we have a parent window: pass the message over to the parent + // the topmost parent takes care of updating all the windows + MainWindow* parentwin = (MainWindow*)parentWidget(); + if (parentwin) + return parentwin->onUpdateVideoSettings(glchange); + + bool hadOGL = hasOGL; if (glchange) { emuThread->emuPause(); - if (hasOGL) emuThread->deinitContext(); + if (hadOGL) emuThread->deinitContext(windowID); - delete panel; createScreenPanel(); - connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(repaint())); } - videoSettingsDirty = true; + emuThread->updateVideoSettings(); + + if (glchange) + { + if (hasOGL) emuThread->initContext(windowID); + } + + // update any child windows we have + auto childwins = findChildren(nullptr, Qt::FindDirectChildrenOnly); + for (auto child: childwins) + { + // child windows may belong to a different instance + // in that case we need to signal their thread appropriately + auto thread = child->getEmuInstance()->getEmuThread(); + + if (glchange) + { + if (hadOGL) thread->deinitContext(child->windowID); + child->createScreenPanel(); + } + + if (child->getWindowID() == 0) + thread->updateVideoSettings(); + + if (glchange) + { + if (hasOGL) thread->initContext(child->windowID); + } + } if (glchange) { - if (hasOGL) emuThread->initContext(); emuThread->emuUnpause(); } } diff --git a/src/frontend/qt_sdl/Window.h b/src/frontend/qt_sdl/Window.h index bc207480..9f652f54 100644 --- a/src/frontend/qt_sdl/Window.h +++ b/src/frontend/qt_sdl/Window.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -20,7 +20,7 @@ #define WINDOW_H #include "glad/glad.h" -#include "FrontendUtil.h" +#include "ScreenLayout.h" #include "duckstation/gl/context.h" #include @@ -34,10 +34,15 @@ #include #include "Screen.h" +#include "Config.h" +#include "MPInterface.h" +class EmuInstance; class EmuThread; +const int kMaxRecentROMs = 10; + /* class WindowBase : public QMainWindow { @@ -100,26 +105,45 @@ class MainWindow : public QMainWindow Q_OBJECT public: - explicit MainWindow(QWidget* parent = nullptr); + explicit MainWindow(int id, EmuInstance* inst, QWidget* parent = nullptr); ~MainWindow(); - bool hasOGL; + EmuInstance* getEmuInstance() { return emuInstance; } + Config::Table& getWindowConfig() { return windowCfg; } + int getWindowID() { return windowID; } + + bool winHasMenu() { return hasMenu; } + + void saveEnabled(bool enabled); + + void toggleFullscreen(); + + bool hasOpenGL() { return hasOGL; } GL::Context* getOGLContext(); - /*void initOpenGL(); + void initOpenGL(); void deinitOpenGL(); - void drawScreenGL();*/ + void setGLSwapInterval(int intv); + void makeCurrentGL(); + void drawScreenGL(); bool preloadROMs(QStringList file, QStringList gbafile, bool boot); QStringList splitArchivePath(const QString& filename, bool useMemberSyntax); void onAppStateChanged(Qt::ApplicationState state); - void osdAddMessage(unsigned int color, const char* fmt, ...); + void onFocusIn(); + void onFocusOut(); + bool isFocused() { return focused; } + + void osdAddMessage(unsigned int color, const char* msg); + + // called when the MP interface is changed + void updateMPInterface(melonDS::MPInterfaceType type); + + void loadRecentFilesMenu(bool loadcfg); + //void updateVideoSettings(bool glchange); protected: - void resizeEvent(QResizeEvent* event) override; - void changeEvent(QEvent* event) override; - void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; @@ -161,6 +185,11 @@ private slots: void onRAMInfo(); void onOpenTitleManager(); void onMPNewInstance(); + void onLANStartHost(); + void onLANStartClient(); + void onNPStartHost(); + void onNPStartClient(); + void onNPTest(); void onOpenEmuSettings(); void onEmuSettingsDialogFinished(int res); @@ -170,6 +199,7 @@ private slots: void onOpenCameraSettings(); void onCameraSettingsFinished(int res); void onOpenAudioSettings(); + void onUpdateAudioVolume(int vol, int dsisync); void onUpdateAudioSettings(); void onAudioSettingsFinished(int res); void onOpenMPSettings(); @@ -182,7 +212,7 @@ private slots: void onPathSettingsFinished(int res); void onOpenInterfaceSettings(); void onInterfaceSettingsFinished(int res); - void onUpdateMouseTimer(); + void onUpdateInterfaceSettings(); void onChangeSavestateSRAMReloc(bool checked); void onChangeScreenSize(); void onChangeScreenRotation(QAction* act); @@ -192,6 +222,7 @@ private slots: void onChangeScreenSizing(QAction* act); void onChangeScreenAspect(QAction* act); void onChangeIntegerScaling(bool checked); + void onOpenNewWindow(); void onChangeScreenFiltering(bool checked); void onChangeShowOSD(bool checked); void onChangeLimitFramerate(bool checked); @@ -201,6 +232,8 @@ private slots: void onEmuStart(); void onEmuStop(); + void onEmuPause(bool pause); + void onEmuReset(); void onUpdateVideoSettings(bool glchange); @@ -223,14 +256,32 @@ private: void createScreenPanel(); - bool pausedManually = false; + bool lanWarning(bool host); - int oldW, oldH; - bool oldMax; + bool showOSD; + + bool hasOGL; + + bool pauseOnLostFocus; + bool pausedManually; + + int windowID; + bool enabledSaved; + + bool focused; + + EmuInstance* emuInstance; + EmuThread* emuThread; + + Config::Table& globalCfg; + Config::Table& localCfg; + Config::Table windowCfg; public: ScreenPanel* panel; + bool hasMenu; + QAction* actOpenROM; QAction* actBootFirmware; QAction* actCurrentCart; @@ -238,12 +289,13 @@ public: QAction* actEjectCart; QAction* actCurrentGBACart; QAction* actInsertGBACart; - QAction* actInsertGBAAddon[1]; + QList actInsertGBAAddon; QAction* actEjectGBACart; QAction* actImportSavefile; QAction* actSaveState[9]; QAction* actLoadState[9]; QAction* actUndoStateLoad; + QAction* actOpenConfig; QAction* actQuit; QAction* actPause; @@ -258,6 +310,11 @@ public: QAction* actRAMInfo; QAction* actTitleManager; QAction* actMPNewInstance; + QAction* actLANStartHost; + QAction* actLANStartClient; + QAction* actNPStartHost; + QAction* actNPStartClient; + QAction* actNPTest; QAction* actEmuSettings; #ifdef __APPLE__ @@ -275,25 +332,26 @@ public: QAction* actSavestateSRAMReloc; QAction* actScreenSize[4]; QActionGroup* grpScreenRotation; - QAction* actScreenRotation[Frontend::screenRot_MAX]; + QAction* actScreenRotation[screenRot_MAX]; QActionGroup* grpScreenGap; QAction* actScreenGap[6]; QActionGroup* grpScreenLayout; - QAction* actScreenLayout[Frontend::screenLayout_MAX]; + QAction* actScreenLayout[screenLayout_MAX]; QAction* actScreenSwap; QActionGroup* grpScreenSizing; - QAction* actScreenSizing[Frontend::screenSizing_MAX]; + QAction* actScreenSizing[screenSizing_MAX]; QAction* actIntegerScaling; QActionGroup* grpScreenAspectTop; QAction** actScreenAspectTop; QActionGroup* grpScreenAspectBot; QAction** actScreenAspectBot; + QAction* actNewWindow; QAction* actScreenFiltering; QAction* actShowOSD; QAction* actLimitFramerate; QAction* actAudioSync; + + QAction* actAbout; }; -void ToggleFullscreen(MainWindow* mainWindow); - #endif // WINDOW_H diff --git a/src/frontend/qt_sdl/font.h b/src/frontend/qt_sdl/font.h index 7945c515..6b9e8570 100644 --- a/src/frontend/qt_sdl/font.h +++ b/src/frontend/qt_sdl/font.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 38c0ab16..d940340a 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -22,23 +22,20 @@ #include #include -#include #include -#include -#include #include +#include #include #include -#include #include #include -#include #include #include #include #include #include +#include #ifndef _WIN32 #include #include @@ -56,210 +53,193 @@ #include "duckstation/gl/context.h" #include "main.h" -#include "Input.h" -#include "CheatsDialog.h" -#include "DateTimeDialog.h" -#include "EmuSettingsDialog.h" -#include "InputConfig/InputConfigDialog.h" -#include "VideoSettingsDialog.h" -#include "CameraSettingsDialog.h" -#include "AudioSettingsDialog.h" -#include "FirmwareSettingsDialog.h" -#include "PathSettingsDialog.h" -#include "MPSettingsDialog.h" -#include "WifiSettingsDialog.h" -#include "InterfaceSettingsDialog.h" -#include "ROMInfoDialog.h" -#include "RAMInfoDialog.h" -#include "TitleManagerDialog.h" -#include "PowerManagement/PowerManagementDialog.h" -#include "AudioInOut.h" - -#include "types.h" #include "version.h" -#include "FrontendUtil.h" - -#include "Args.h" -#include "NDS.h" -#include "NDSCart.h" -#include "GBACart.h" -#include "GPU.h" -#include "SPU.h" -#include "Wifi.h" -#include "Platform.h" -#include "LocalMP.h" #include "Config.h" -#include "RTC.h" -#include "DSi.h" -#include "DSi_I2C.h" -#include "GPU3D_Soft.h" -#include "GPU3D_OpenGL.h" -#include "Savestate.h" - -//#include "main_shaders.h" - -#include "ROMManager.h" +#include "EmuInstance.h" #include "ArchiveUtil.h" #include "CameraManager.h" +#include "MPInterface.h" +#include "Net.h" #include "CLI.h" -// TODO: uniform variable spelling +#include "Net_PCap.h" +#include "Net_Slirp.h" + using namespace melonDS; -QString NdsRomMimeType = "application/x-nintendo-ds-rom"; -QStringList NdsRomExtensions { ".nds", ".srl", ".dsi", ".ids" }; -QString GbaRomMimeType = "application/x-gba-rom"; -QStringList GbaRomExtensions { ".gba", ".agb" }; +QString* systemThemeName; -// This list of supported archive formats is based on libarchive(3) version 3.6.2 (2022-12-09). -QStringList ArchiveMimeTypes -{ -#ifdef ARCHIVE_SUPPORT_ENABLED - "application/zip", - "application/x-7z-compressed", - "application/vnd.rar", // *.rar - "application/x-tar", +QString emuDirectory; - "application/x-compressed-tar", // *.tar.gz - "application/x-xz-compressed-tar", - "application/x-bzip-compressed-tar", - "application/x-lz4-compressed-tar", - "application/x-zstd-compressed-tar", - - "application/x-tarz", // *.tar.Z - "application/x-lzip-compressed-tar", - "application/x-lzma-compressed-tar", - "application/x-lrzip-compressed-tar", - "application/x-tzo", // *.tar.lzo -#endif -}; - -QStringList ArchiveExtensions -{ -#ifdef ARCHIVE_SUPPORT_ENABLED - ".zip", ".7z", ".rar", ".tar", - - ".tar.gz", ".tgz", - ".tar.xz", ".txz", - ".tar.bz2", ".tbz2", - ".tar.lz4", ".tlz4", - ".tar.zst", ".tzst", - - ".tar.Z", ".taz", - ".tar.lz", - ".tar.lzma", ".tlz", - ".tar.lrz", ".tlrz", - ".tar.lzo", ".tzo" -#endif -}; - - -bool RunningSomething; - -MainWindow* mainWindow; -EmuThread* emuThread; - -int autoScreenSizing = 0; - -int videoRenderer; -bool videoSettingsDirty; +const int kMaxEmuInstances = 16; +EmuInstance* emuInstances[kMaxEmuInstances]; CameraManager* camManager[2]; bool camStarted[2]; -//extern int AspectRatiosNum; +std::optional pcap; +Net net; +QElapsedTimer sysTimer; - - -static bool FileExtensionInList(const QString& filename, const QStringList& extensions, Qt::CaseSensitivity cs = Qt::CaseInsensitive) +void NetInit() { - return std::any_of(extensions.cbegin(), extensions.cend(), [&](const auto& ext) { - return filename.endsWith(ext, cs); - }); -} + Config::Table cfg = Config::GetGlobalTable(); + if (cfg.GetBool("LAN.DirectMode")) + { + if (!pcap) + pcap = LibPCap::New(); -static bool MimeTypeInList(const QMimeType& mimetype, const QStringList& superTypeNames) -{ - return std::any_of(superTypeNames.cbegin(), superTypeNames.cend(), [&](const auto& superTypeName) { - return mimetype.inherits(superTypeName); - }); + if (pcap) + { + std::string devicename = cfg.GetString("LAN.Device"); + std::unique_ptr netPcap = pcap->Open(devicename, [](const u8* data, int len) { + net.RXEnqueue(data, len); + }); + + if (netPcap) + { + net.SetDriver(std::move(netPcap)); + } + } + } + else + { + net.SetDriver(std::make_unique([](const u8* data, int len) { + net.RXEnqueue(data, len); + })); + } } -static bool NdsRomByExtension(const QString& filename) +bool createEmuInstance() { - return FileExtensionInList(filename, NdsRomExtensions); + int id = -1; + for (int i = 0; i < kMaxEmuInstances; i++) + { + if (!emuInstances[i]) + { + id = i; + break; + } + } + + if (id == -1) + return false; + + auto inst = new EmuInstance(id); + emuInstances[id] = inst; + + return true; } -static bool GbaRomByExtension(const QString& filename) +void deleteEmuInstance(int id) { - return FileExtensionInList(filename, GbaRomExtensions); + auto inst = emuInstances[id]; + if (!inst) return; + + delete inst; + emuInstances[id] = nullptr; } -static bool SupportedArchiveByExtension(const QString& filename) +void deleteAllEmuInstances(int first) { - return FileExtensionInList(filename, ArchiveExtensions); + for (int i = first; i < kMaxEmuInstances; i++) + deleteEmuInstance(i); +} + +int numEmuInstances() +{ + int ret = 0; + + for (int i = 0; i < kMaxEmuInstances; i++) + { + if (emuInstances[i]) + ret++; + } + + return ret; } -static bool NdsRomByMimetype(const QMimeType& mimetype) +void broadcastInstanceCommand(int cmd, QVariant& param, int sourceinst) { - return mimetype.inherits(NdsRomMimeType); -} + for (int i = 0; i < kMaxEmuInstances; i++) + { + if (i == sourceinst) continue; + if (!emuInstances[i]) continue; -static bool GbaRomByMimetype(const QMimeType& mimetype) -{ - return mimetype.inherits(GbaRomMimeType); -} - -static bool SupportedArchiveByMimetype(const QMimeType& mimetype) -{ - return MimeTypeInList(mimetype, ArchiveMimeTypes); -} - -static bool ZstdNdsRomByExtension(const QString& filename) -{ - return filename.endsWith(".zst", Qt::CaseInsensitive) && - NdsRomByExtension(filename.left(filename.size() - 4)); -} - -static bool ZstdGbaRomByExtension(const QString& filename) -{ - return filename.endsWith(".zst", Qt::CaseInsensitive) && - GbaRomByExtension(filename.left(filename.size() - 4)); -} - -static bool FileIsSupportedFiletype(const QString& filename, bool insideArchive = false) -{ - if (ZstdNdsRomByExtension(filename) || ZstdGbaRomByExtension(filename)) - return true; - - if (NdsRomByExtension(filename) || GbaRomByExtension(filename) || SupportedArchiveByExtension(filename)) - return true; - - const auto matchmode = insideArchive ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault; - const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, matchmode); - return NdsRomByMimetype(mimetype) || GbaRomByMimetype(mimetype) || SupportedArchiveByMimetype(mimetype); + emuInstances[i]->handleCommand(cmd, param); + } } - - - -void emuStop() +void pathInit() { - RunningSomething = false; + // First, check for the portable directory next to the executable. + QString appdirpath = QCoreApplication::applicationDirPath(); + QString portablepath = appdirpath + QDir::separator() + "portable"; - emit emuThread->windowEmuStop(); +#if defined(__APPLE__) + // On Apple platforms we may need to navigate outside an app bundle. + // The executable directory would be "melonDS.app/Contents/MacOS", so we need to go a total of three steps up. + QDir bundledir(appdirpath); + if (bundledir.cd("..") && bundledir.cd("..") && bundledir.dirName().endsWith(".app") && bundledir.cd("..")) + { + portablepath = bundledir.absolutePath() + QDir::separator() + "portable"; + } +#endif + + QDir portabledir(portablepath); + if (portabledir.exists()) + { + emuDirectory = portabledir.absolutePath(); + } + else + { + // If no overrides are specified, use the default path. +#if defined(__WIN32__) && defined(WIN32_PORTABLE) + emuDirectory = appdirpath; +#else + QString confdir; + QDir config(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)); + config.mkdir("melonDS"); + confdir = config.absolutePath() + QDir::separator() + "melonDS"; + emuDirectory = confdir; +#endif + } } + +void setMPInterface(MPInterfaceType type) +{ + // switch to the requested MP interface + MPInterface::Set(type); + + // set receive timeout + // TODO: different settings per interface? + MPInterface::Get().SetRecvTimeout(Config::GetGlobalTable().GetInt("MP.RecvTimeout")); + + // update UI appropriately + // TODO: decide how to deal with multi-window when it becomes a thing + for (int i = 0; i < kMaxEmuInstances; i++) + { + EmuInstance* inst = emuInstances[i]; + if (!inst) continue; + + MainWindow* win = inst->getMainWindow(); + if (win) win->updateMPInterface(type); + } +} + + + MelonApplication::MelonApplication(int& argc, char** argv) : QApplication(argc, argv) { @@ -271,16 +251,19 @@ MelonApplication::MelonApplication(int& argc, char** argv) #endif } +// TODO: ROM loading should be moved to EmuInstance +// especially so the preloading below and in main() can be done in a nicer fashion + bool MelonApplication::event(QEvent *event) { if (event->type() == QEvent::FileOpen) { + EmuInstance* inst = emuInstances[0]; + MainWindow* win = inst->getMainWindow(); QFileOpenEvent *openEvent = static_cast(event); - emuThread->emuPause(); - const QStringList file = mainWindow->splitArchivePath(openEvent->file(), true); - if (!mainWindow->preloadROMs(file, {}, true)) - emuThread->emuUnpause(); + const QStringList file = win->splitArchivePath(openEvent->file(), true); + win->preloadROMs(file, {}, true); } return QApplication::event(event); @@ -288,10 +271,19 @@ bool MelonApplication::event(QEvent *event) int main(int argc, char** argv) { + sysTimer.start(); srand(time(nullptr)); + for (int i = 0; i < kMaxEmuInstances; i++) + emuInstances[i] = nullptr; + qputenv("QT_SCALE_FACTOR", "1"); +#if QT_VERSION_MAJOR == 6 && defined(__WIN32__) + // Allow using the system dark theme palette on Windows + qputenv("QT_QPA_PLATFORM", "windows:darkmode=2"); +#endif + printf("melonDS " MELONDS_VERSION "\n"); printf(MELONDS_URL "\n"); @@ -299,9 +291,8 @@ int main(int argc, char** argv) if (argc != 0 && (!strcasecmp(argv[0], "derpDS") || !strcasecmp(argv[0], "./derpDS"))) printf("did you just call me a derp???\n"); - Platform::Init(argc, argv); - MelonApplication melon(argc, argv); + pathInit(); CLI::CommandLineOptions* options = CLI::ManageArgs(melon); @@ -322,7 +313,7 @@ int main(int argc, char** argv) QString errorStr = "Failed to initialize SDL. This could indicate an issue with your audio driver.\n\nThe error was: "; errorStr += err; - QMessageBox::critical(NULL, "melonDS", errorStr); + QMessageBox::critical(nullptr, "melonDS", errorStr); return 1; } @@ -331,92 +322,77 @@ int main(int argc, char** argv) SDL_InitSubSystem(SDL_INIT_VIDEO); SDL_EnableScreenSaver(); SDL_DisableScreenSaver(); - Config::Load(); - -#define SANITIZE(var, min, max) { var = std::clamp(var, min, max); } - SANITIZE(Config::ConsoleType, 0, 1); -#ifdef OGLRENDERER_ENABLED - SANITIZE(Config::_3DRenderer, 0, 1); // 0 is the software renderer, 1 is the OpenGL renderer -#else - SANITIZE(Config::_3DRenderer, 0, 0); -#endif - SANITIZE(Config::ScreenVSyncInterval, 1, 20); - SANITIZE(Config::GL_ScaleFactor, 1, 16); - SANITIZE(Config::AudioInterp, 0, 3); - SANITIZE(Config::AudioVolume, 0, 256); - SANITIZE(Config::MicInputType, 0, (int)micInputType_MAX); - SANITIZE(Config::ScreenRotation, 0, (int)Frontend::screenRot_MAX); - SANITIZE(Config::ScreenGap, 0, 500); - SANITIZE(Config::ScreenLayout, 0, (int)Frontend::screenLayout_MAX); - SANITIZE(Config::ScreenSizing, 0, (int)Frontend::screenSizing_MAX); - SANITIZE(Config::ScreenAspectTop, 0, AspectRatiosNum); - SANITIZE(Config::ScreenAspectBot, 0, AspectRatiosNum); -#undef SANITIZE + if (!Config::Load()) + QMessageBox::critical(nullptr, + "melonDS", + "Unable to write to config.\nPlease check the write permissions of the folder you placed melonDS in."); camStarted[0] = false; camStarted[1] = false; camManager[0] = new CameraManager(0, 640, 480, true); camManager[1] = new CameraManager(1, 640, 480, true); - camManager[0]->setXFlip(Config::Camera[0].XFlip); - camManager[1]->setXFlip(Config::Camera[1].XFlip); + systemThemeName = new QString(QApplication::style()->objectName()); - Input::JoystickID = Config::JoystickID; - Input::OpenJoystick(); - - mainWindow = new MainWindow(); - if (options->fullscreen) - ToggleFullscreen(mainWindow); - - emuThread = new EmuThread(); - emuThread->start(); - emuThread->emuPause(); - - AudioInOut::Init(emuThread); - ROMManager::EnableCheats(*emuThread->NDS, Config::EnableCheats != 0); - AudioInOut::AudioMute(mainWindow); - - QObject::connect(&melon, &QApplication::applicationStateChanged, mainWindow, &MainWindow::onAppStateChanged); - - bool memberSyntaxUsed = false; - const auto prepareRomPath = [&](const std::optional& romPath, const std::optional& romArchivePath) -> QStringList { - if (!romPath.has_value()) - return {}; + Config::Table cfg = Config::GetGlobalTable(); + QString uitheme = cfg.GetQString("UITheme"); + if (!uitheme.isEmpty()) + { + QApplication::setStyle(uitheme); + } + } - if (romArchivePath.has_value()) - return { *romPath, *romArchivePath }; + // default MP interface type is local MP + // this will be changed if a LAN or netplay session is initiated + setMPInterface(MPInterface_Local); - const QStringList path = mainWindow->splitArchivePath(*romPath, true); - if (path.size() > 1) memberSyntaxUsed = true; - return path; - }; + NetInit(); - const QStringList dsfile = prepareRomPath(options->dsRomPath, options->dsRomArchivePath); - const QStringList gbafile = prepareRomPath(options->gbaRomPath, options->gbaRomArchivePath); + createEmuInstance(); - if (memberSyntaxUsed) printf("Warning: use the a.zip|b.nds format at your own risk!\n"); + { + MainWindow* win = emuInstances[0]->getMainWindow(); + bool memberSyntaxUsed = false; + const auto prepareRomPath = [&](const std::optional &romPath, + const std::optional &romArchivePath) -> QStringList + { + if (!romPath.has_value()) + return {}; - mainWindow->preloadROMs(dsfile, gbafile, options->boot); + if (romArchivePath.has_value()) + return {*romPath, *romArchivePath}; + + const QStringList path = win->splitArchivePath(*romPath, true); + if (path.size() > 1) memberSyntaxUsed = true; + return path; + }; + + const QStringList dsfile = prepareRomPath(options->dsRomPath, options->dsRomArchivePath); + const QStringList gbafile = prepareRomPath(options->gbaRomPath, options->gbaRomArchivePath); + + if (memberSyntaxUsed) printf("Warning: use the a.zip|b.nds format at your own risk!\n"); + + win->preloadROMs(dsfile, gbafile, options->boot); + + if (options->fullscreen) + win->toggleFullscreen(); + } int ret = melon.exec(); delete options; - emuThread->emuStop(); - emuThread->wait(); - delete emuThread; + // if we get here, all the existing emu instances should have been deleted already + // but with this we make extra sure they are all deleted + deleteAllEmuInstances(); - Input::CloseJoystick(); - - AudioInOut::DeInit(); delete camManager[0]; delete camManager[1]; Config::Save(); SDL_Quit(); - Platform::DeInit(); return ret; } @@ -426,6 +402,12 @@ int main(int argc, char** argv) int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdshow) { + if (AttachConsole(ATTACH_PARENT_PROCESS) && GetStdHandle(STD_OUTPUT_HANDLE)) + { + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + } + int ret = main(__argc, __argv); printf("\n\n>"); diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index 51157c6c..e0d38963 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -22,18 +22,23 @@ #include "glad/glad.h" #include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include "EmuInstance.h" #include "Window.h" #include "EmuThread.h" -#include "FrontendUtil.h" +#include "ScreenLayout.h" +#include "MPInterface.h" + +enum +{ + InstCmd_Pause, + InstCmd_Unpause, + + InstCmd_UpdateRecentFiles, + //InstCmd_UpdateVideoSettings, +}; class MelonApplication : public QApplication { @@ -44,4 +49,18 @@ public: bool event(QEvent* event) override; }; +extern QString* systemThemeName; +extern QString emuDirectory; + +extern QElapsedTimer sysTimer; + +bool createEmuInstance(); +void deleteEmuInstance(int id); +void deleteAllEmuInstances(int first = 0); +int numEmuInstances(); + +void broadcastInstanceCommand(int cmd, QVariant& param, int sourceinst); + +void setMPInterface(melonDS::MPInterfaceType type); + #endif // MAIN_H diff --git a/src/frontend/qt_sdl/main_shaders.h b/src/frontend/qt_sdl/main_shaders.h index fd9de038..b5c4663c 100644 --- a/src/frontend/qt_sdl/main_shaders.h +++ b/src/frontend/qt_sdl/main_shaders.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/frontend/qt_sdl/toml/toml.hpp b/src/frontend/qt_sdl/toml/toml.hpp new file mode 100644 index 00000000..6b0ca1ed --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml.hpp @@ -0,0 +1,38 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Toru Niina + * + * 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. + */ + +#ifndef TOML_FOR_MODERN_CPP +#define TOML_FOR_MODERN_CPP + +#define TOML11_VERSION_MAJOR 3 +#define TOML11_VERSION_MINOR 7 +#define TOML11_VERSION_PATCH 1 + +#include "toml/parser.hpp" +#include "toml/literal.hpp" +#include "toml/serializer.hpp" +#include "toml/get.hpp" +#include "toml/macros.hpp" + +#endif// TOML_FOR_MODERN_CPP diff --git a/src/frontend/qt_sdl/toml/toml/color.hpp b/src/frontend/qt_sdl/toml/toml/color.hpp new file mode 100644 index 00000000..4cb572cb --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/color.hpp @@ -0,0 +1,64 @@ +#ifndef TOML11_COLOR_HPP +#define TOML11_COLOR_HPP +#include +#include + +#ifdef TOML11_COLORIZE_ERROR_MESSAGE +#define TOML11_ERROR_MESSAGE_COLORIZED true +#else +#define TOML11_ERROR_MESSAGE_COLORIZED false +#endif + +namespace toml +{ + +// put ANSI escape sequence to ostream +namespace color_ansi +{ +namespace detail +{ +inline int colorize_index() +{ + static const int index = std::ios_base::xalloc(); + return index; +} +} // detail + +inline std::ostream& colorize(std::ostream& os) +{ + // by default, it is zero. + os.iword(detail::colorize_index()) = 1; + return os; +} +inline std::ostream& nocolorize(std::ostream& os) +{ + os.iword(detail::colorize_index()) = 0; + return os; +} +inline std::ostream& reset (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[00m";} return os;} +inline std::ostream& bold (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[01m";} return os;} +inline std::ostream& grey (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[30m";} return os;} +inline std::ostream& red (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[31m";} return os;} +inline std::ostream& green (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[32m";} return os;} +inline std::ostream& yellow (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[33m";} return os;} +inline std::ostream& blue (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[34m";} return os;} +inline std::ostream& magenta(std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[35m";} return os;} +inline std::ostream& cyan (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[36m";} return os;} +inline std::ostream& white (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[37m";} return os;} +} // color_ansi + +// ANSI escape sequence is the only and default colorization method currently +namespace color = color_ansi; + +} // toml +#endif// TOML11_COLOR_HPP diff --git a/src/frontend/qt_sdl/toml/toml/combinator.hpp b/src/frontend/qt_sdl/toml/toml/combinator.hpp new file mode 100644 index 00000000..33ecca1e --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/combinator.hpp @@ -0,0 +1,306 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_COMBINATOR_HPP +#define TOML11_COMBINATOR_HPP +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "region.hpp" +#include "result.hpp" +#include "traits.hpp" +#include "utility.hpp" + +// they scans characters and returns region if it matches to the condition. +// when they fail, it does not change the location. +// in lexer.hpp, these are used. + +namespace toml +{ +namespace detail +{ + +// to output character as an error message. +inline std::string show_char(const char c) +{ + // It suppresses an error that occurs only in Debug mode of MSVC++ on Windows. + // I'm not completely sure but they check the value of char to be in the + // range [0, 256) and some of the COMPLETELY VALID utf-8 character sometimes + // has negative value (if char has sign). So here it re-interprets c as + // unsigned char through pointer. In general, converting pointer to a + // pointer that has different type cause UB, but `(signed|unsigned)?char` + // are one of the exceptions. Converting pointer only to char and std::byte + // (c++17) are valid. + if(std::isgraph(*reinterpret_cast(std::addressof(c)))) + { + return std::string(1, c); + } + else + { + std::array buf; + buf.fill('\0'); + const auto r = std::snprintf( + buf.data(), buf.size(), "0x%02x", static_cast(c) & 0xFF); + (void) r; // Unused variable warning + assert(r == static_cast(buf.size()) - 1); + return std::string(buf.data()); + } +} + +template +struct character +{ + static constexpr char target = C; + + static result + invoke(location& loc) + { + if(loc.iter() == loc.end()) {return none();} + const auto first = loc.iter(); + + const char c = *(loc.iter()); + if(c != target) + { + return none(); + } + loc.advance(); // update location + + return ok(region(loc, first, loc.iter())); + } +}; +template +constexpr char character::target; + +// closed interval [Low, Up]. both Low and Up are included. +template +struct in_range +{ + // assuming ascii part of UTF-8... + static_assert(Low <= Up, "lower bound should be less than upper bound."); + + static constexpr char upper = Up; + static constexpr char lower = Low; + + static result + invoke(location& loc) + { + if(loc.iter() == loc.end()) {return none();} + const auto first = loc.iter(); + + const char c = *(loc.iter()); + if(c < lower || upper < c) + { + return none(); + } + + loc.advance(); + return ok(region(loc, first, loc.iter())); + } +}; +template constexpr char in_range::upper; +template constexpr char in_range::lower; + +// keep iterator if `Combinator` matches. otherwise, increment `iter` by 1 char. +// for detecting invalid characters, like control sequences in toml string. +template +struct exclude +{ + static result + invoke(location& loc) + { + if(loc.iter() == loc.end()) {return none();} + auto first = loc.iter(); + + auto rslt = Combinator::invoke(loc); + if(rslt.is_ok()) + { + loc.reset(first); + return none(); + } + loc.reset(std::next(first)); // XXX maybe loc.advance() is okay but... + return ok(region(loc, first, loc.iter())); + } +}; + +// increment `iter`, if matches. otherwise, just return empty string. +template +struct maybe +{ + static result + invoke(location& loc) + { + const auto rslt = Combinator::invoke(loc); + if(rslt.is_ok()) + { + return rslt; + } + return ok(region(loc)); + } +}; + +template +struct sequence; + +template +struct sequence +{ + static result + invoke(location& loc) + { + const auto first = loc.iter(); + auto rslt = Head::invoke(loc); + if(rslt.is_err()) + { + loc.reset(first); + return none(); + } + return sequence::invoke(loc, std::move(rslt.unwrap()), first); + } + + // called from the above function only, recursively. + template + static result + invoke(location& loc, region reg, Iterator first) + { + const auto rslt = Head::invoke(loc); + if(rslt.is_err()) + { + loc.reset(first); + return none(); + } + reg += rslt.unwrap(); // concat regions + return sequence::invoke(loc, std::move(reg), first); + } +}; + +template +struct sequence +{ + // would be called from sequence::invoke only. + template + static result + invoke(location& loc, region reg, Iterator first) + { + const auto rslt = Head::invoke(loc); + if(rslt.is_err()) + { + loc.reset(first); + return none(); + } + reg += rslt.unwrap(); // concat regions + return ok(reg); + } +}; + +template +struct either; + +template +struct either +{ + static result + invoke(location& loc) + { + const auto rslt = Head::invoke(loc); + if(rslt.is_ok()) {return rslt;} + return either::invoke(loc); + } +}; +template +struct either +{ + static result + invoke(location& loc) + { + return Head::invoke(loc); + } +}; + +template +struct repeat; + +template struct exactly{}; +template struct at_least{}; +struct unlimited{}; + +template +struct repeat> +{ + static result + invoke(location& loc) + { + region retval(loc); + const auto first = loc.iter(); + for(std::size_t i=0; i +struct repeat> +{ + static result + invoke(location& loc) + { + region retval(loc); + + const auto first = loc.iter(); + for(std::size_t i=0; i +struct repeat +{ + static result + invoke(location& loc) + { + region retval(loc); + while(true) + { + auto rslt = T::invoke(loc); + if(rslt.is_err()) + { + return ok(std::move(retval)); + } + retval += rslt.unwrap(); + } + } +}; + +} // detail +} // toml +#endif// TOML11_COMBINATOR_HPP diff --git a/src/frontend/qt_sdl/toml/toml/comments.hpp b/src/frontend/qt_sdl/toml/toml/comments.hpp new file mode 100644 index 00000000..ec250411 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/comments.hpp @@ -0,0 +1,472 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_COMMENTS_HPP +#define TOML11_COMMENTS_HPP +#include +#include +#include +#include +#include +#include +#include + +#ifdef TOML11_PRESERVE_COMMENTS_BY_DEFAULT +# define TOML11_DEFAULT_COMMENT_STRATEGY ::toml::preserve_comments +#else +# define TOML11_DEFAULT_COMMENT_STRATEGY ::toml::discard_comments +#endif + +// This file provides mainly two classes, `preserve_comments` and `discard_comments`. +// Those two are a container that have the same interface as `std::vector` +// but bahaves in the opposite way. `preserve_comments` is just the same as +// `std::vector` and each `std::string` corresponds to a comment line. +// Conversely, `discard_comments` discards all the strings and ignores everything +// assigned in it. `discard_comments` is always empty and you will encounter an +// error whenever you access to the element. +namespace toml +{ +struct discard_comments; // forward decl + +// use it in the following way +// +// const toml::basic_value data = +// toml::parse("example.toml"); +// +// the interface is almost the same as std::vector. +struct preserve_comments +{ + // `container_type` is not provided in discard_comments. + // do not use this inner-type in a generic code. + using container_type = std::vector; + + using size_type = container_type::size_type; + using difference_type = container_type::difference_type; + using value_type = container_type::value_type; + using reference = container_type::reference; + using const_reference = container_type::const_reference; + using pointer = container_type::pointer; + using const_pointer = container_type::const_pointer; + using iterator = container_type::iterator; + using const_iterator = container_type::const_iterator; + using reverse_iterator = container_type::reverse_iterator; + using const_reverse_iterator = container_type::const_reverse_iterator; + + preserve_comments() = default; + ~preserve_comments() = default; + preserve_comments(preserve_comments const&) = default; + preserve_comments(preserve_comments &&) = default; + preserve_comments& operator=(preserve_comments const&) = default; + preserve_comments& operator=(preserve_comments &&) = default; + + explicit preserve_comments(const std::vector& c): comments(c){} + explicit preserve_comments(std::vector&& c) + : comments(std::move(c)) + {} + preserve_comments& operator=(const std::vector& c) + { + comments = c; + return *this; + } + preserve_comments& operator=(std::vector&& c) + { + comments = std::move(c); + return *this; + } + + explicit preserve_comments(const discard_comments&) {} + + explicit preserve_comments(size_type n): comments(n) {} + preserve_comments(size_type n, const std::string& x): comments(n, x) {} + preserve_comments(std::initializer_list x): comments(x) {} + template + preserve_comments(InputIterator first, InputIterator last) + : comments(first, last) + {} + + template + void assign(InputIterator first, InputIterator last) {comments.assign(first, last);} + void assign(std::initializer_list ini) {comments.assign(ini);} + void assign(size_type n, const std::string& val) {comments.assign(n, val);} + + // Related to the issue #97. + // + // It is known that `std::vector::insert` and `std::vector::erase` in + // the standard library implementation included in GCC 4.8.5 takes + // `std::vector::iterator` instead of `std::vector::const_iterator`. + // Because of the const-correctness, we cannot convert a `const_iterator` to + // an `iterator`. It causes compilation error in GCC 4.8.5. +#if defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && !defined(__clang__) +# if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) <= 40805 +# define TOML11_WORKAROUND_GCC_4_8_X_STANDARD_LIBRARY_IMPLEMENTATION +# endif +#endif + +#ifdef TOML11_WORKAROUND_GCC_4_8_X_STANDARD_LIBRARY_IMPLEMENTATION + iterator insert(iterator p, const std::string& x) + { + return comments.insert(p, x); + } + iterator insert(iterator p, std::string&& x) + { + return comments.insert(p, std::move(x)); + } + void insert(iterator p, size_type n, const std::string& x) + { + return comments.insert(p, n, x); + } + template + void insert(iterator p, InputIterator first, InputIterator last) + { + return comments.insert(p, first, last); + } + void insert(iterator p, std::initializer_list ini) + { + return comments.insert(p, ini); + } + + template + iterator emplace(iterator p, Ts&& ... args) + { + return comments.emplace(p, std::forward(args)...); + } + + iterator erase(iterator pos) {return comments.erase(pos);} + iterator erase(iterator first, iterator last) + { + return comments.erase(first, last); + } +#else + iterator insert(const_iterator p, const std::string& x) + { + return comments.insert(p, x); + } + iterator insert(const_iterator p, std::string&& x) + { + return comments.insert(p, std::move(x)); + } + iterator insert(const_iterator p, size_type n, const std::string& x) + { + return comments.insert(p, n, x); + } + template + iterator insert(const_iterator p, InputIterator first, InputIterator last) + { + return comments.insert(p, first, last); + } + iterator insert(const_iterator p, std::initializer_list ini) + { + return comments.insert(p, ini); + } + + template + iterator emplace(const_iterator p, Ts&& ... args) + { + return comments.emplace(p, std::forward(args)...); + } + + iterator erase(const_iterator pos) {return comments.erase(pos);} + iterator erase(const_iterator first, const_iterator last) + { + return comments.erase(first, last); + } +#endif + + void swap(preserve_comments& other) {comments.swap(other.comments);} + + void push_back(const std::string& v) {comments.push_back(v);} + void push_back(std::string&& v) {comments.push_back(std::move(v));} + void pop_back() {comments.pop_back();} + + template + void emplace_back(Ts&& ... args) {comments.emplace_back(std::forward(args)...);} + + void clear() {comments.clear();} + + size_type size() const noexcept {return comments.size();} + size_type max_size() const noexcept {return comments.max_size();} + size_type capacity() const noexcept {return comments.capacity();} + bool empty() const noexcept {return comments.empty();} + + void reserve(size_type n) {comments.reserve(n);} + void resize(size_type n) {comments.resize(n);} + void resize(size_type n, const std::string& c) {comments.resize(n, c);} + void shrink_to_fit() {comments.shrink_to_fit();} + + reference operator[](const size_type n) noexcept {return comments[n];} + const_reference operator[](const size_type n) const noexcept {return comments[n];} + reference at(const size_type n) {return comments.at(n);} + const_reference at(const size_type n) const {return comments.at(n);} + reference front() noexcept {return comments.front();} + const_reference front() const noexcept {return comments.front();} + reference back() noexcept {return comments.back();} + const_reference back() const noexcept {return comments.back();} + + pointer data() noexcept {return comments.data();} + const_pointer data() const noexcept {return comments.data();} + + iterator begin() noexcept {return comments.begin();} + iterator end() noexcept {return comments.end();} + const_iterator begin() const noexcept {return comments.begin();} + const_iterator end() const noexcept {return comments.end();} + const_iterator cbegin() const noexcept {return comments.cbegin();} + const_iterator cend() const noexcept {return comments.cend();} + + reverse_iterator rbegin() noexcept {return comments.rbegin();} + reverse_iterator rend() noexcept {return comments.rend();} + const_reverse_iterator rbegin() const noexcept {return comments.rbegin();} + const_reverse_iterator rend() const noexcept {return comments.rend();} + const_reverse_iterator crbegin() const noexcept {return comments.crbegin();} + const_reverse_iterator crend() const noexcept {return comments.crend();} + + friend bool operator==(const preserve_comments&, const preserve_comments&); + friend bool operator!=(const preserve_comments&, const preserve_comments&); + friend bool operator< (const preserve_comments&, const preserve_comments&); + friend bool operator<=(const preserve_comments&, const preserve_comments&); + friend bool operator> (const preserve_comments&, const preserve_comments&); + friend bool operator>=(const preserve_comments&, const preserve_comments&); + + friend void swap(preserve_comments&, std::vector&); + friend void swap(std::vector&, preserve_comments&); + + private: + + container_type comments; +}; + +inline bool operator==(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments == rhs.comments;} +inline bool operator!=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments != rhs.comments;} +inline bool operator< (const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments < rhs.comments;} +inline bool operator<=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments <= rhs.comments;} +inline bool operator> (const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments > rhs.comments;} +inline bool operator>=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments >= rhs.comments;} + +inline void swap(preserve_comments& lhs, preserve_comments& rhs) +{ + lhs.swap(rhs); + return; +} +inline void swap(preserve_comments& lhs, std::vector& rhs) +{ + lhs.comments.swap(rhs); + return; +} +inline void swap(std::vector& lhs, preserve_comments& rhs) +{ + lhs.swap(rhs.comments); + return; +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const preserve_comments& com) +{ + for(const auto& c : com) + { + os << '#' << c << '\n'; + } + return os; +} + +namespace detail +{ + +// To provide the same interface with `preserve_comments`, `discard_comments` +// should have an iterator. But it does not contain anything, so we need to +// add an iterator that points nothing. +// +// It always points null, so DO NOT unwrap this iterator. It always crashes +// your program. +template +struct empty_iterator +{ + using value_type = T; + using reference_type = typename std::conditional::type; + using pointer_type = typename std::conditional::type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::random_access_iterator_tag; + + empty_iterator() = default; + ~empty_iterator() = default; + empty_iterator(empty_iterator const&) = default; + empty_iterator(empty_iterator &&) = default; + empty_iterator& operator=(empty_iterator const&) = default; + empty_iterator& operator=(empty_iterator &&) = default; + + // DO NOT call these operators. + reference_type operator*() const noexcept {std::terminate();} + pointer_type operator->() const noexcept {return nullptr;} + reference_type operator[](difference_type) const noexcept {return this->operator*();} + + // These operators do nothing. + empty_iterator& operator++() noexcept {return *this;} + empty_iterator operator++(int) noexcept {return *this;} + empty_iterator& operator--() noexcept {return *this;} + empty_iterator operator--(int) noexcept {return *this;} + + empty_iterator& operator+=(difference_type) noexcept {return *this;} + empty_iterator& operator-=(difference_type) noexcept {return *this;} + + empty_iterator operator+(difference_type) const noexcept {return *this;} + empty_iterator operator-(difference_type) const noexcept {return *this;} +}; + +template +bool operator==(const empty_iterator&, const empty_iterator&) noexcept {return true;} +template +bool operator!=(const empty_iterator&, const empty_iterator&) noexcept {return false;} +template +bool operator< (const empty_iterator&, const empty_iterator&) noexcept {return false;} +template +bool operator<=(const empty_iterator&, const empty_iterator&) noexcept {return true;} +template +bool operator> (const empty_iterator&, const empty_iterator&) noexcept {return false;} +template +bool operator>=(const empty_iterator&, const empty_iterator&) noexcept {return true;} + +template +typename empty_iterator::difference_type +operator-(const empty_iterator&, const empty_iterator&) noexcept {return 0;} + +template +empty_iterator +operator+(typename empty_iterator::difference_type, const empty_iterator& rhs) noexcept {return rhs;} +template +empty_iterator +operator+(const empty_iterator& lhs, typename empty_iterator::difference_type) noexcept {return lhs;} + +} // detail + +// The default comment type. It discards all the comments. It requires only one +// byte to contain, so the memory footprint is smaller than preserve_comments. +// +// It just ignores `push_back`, `insert`, `erase`, and any other modifications. +// IT always returns size() == 0, the iterator taken by `begin()` is always the +// same as that of `end()`, and accessing through `operator[]` or iterators +// always causes a segmentation fault. DO NOT access to the element of this. +// +// Why this is chose as the default type is because the last version (2.x.y) +// does not contain any comments in a value. To minimize the impact on the +// efficiency, this is chosen as a default. +// +// To reduce the memory footprint, later we can try empty base optimization (EBO). +struct discard_comments +{ + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using reference = std::string&; + using const_reference = std::string const&; + using pointer = std::string*; + using const_pointer = std::string const*; + using iterator = detail::empty_iterator; + using const_iterator = detail::empty_iterator; + using reverse_iterator = detail::empty_iterator; + using const_reverse_iterator = detail::empty_iterator; + + discard_comments() = default; + ~discard_comments() = default; + discard_comments(discard_comments const&) = default; + discard_comments(discard_comments &&) = default; + discard_comments& operator=(discard_comments const&) = default; + discard_comments& operator=(discard_comments &&) = default; + + explicit discard_comments(const std::vector&) noexcept {} + explicit discard_comments(std::vector&&) noexcept {} + discard_comments& operator=(const std::vector&) noexcept {return *this;} + discard_comments& operator=(std::vector&&) noexcept {return *this;} + + explicit discard_comments(const preserve_comments&) noexcept {} + + explicit discard_comments(size_type) noexcept {} + discard_comments(size_type, const std::string&) noexcept {} + discard_comments(std::initializer_list) noexcept {} + template + discard_comments(InputIterator, InputIterator) noexcept {} + + template + void assign(InputIterator, InputIterator) noexcept {} + void assign(std::initializer_list) noexcept {} + void assign(size_type, const std::string&) noexcept {} + + iterator insert(const_iterator, const std::string&) {return iterator{};} + iterator insert(const_iterator, std::string&&) {return iterator{};} + iterator insert(const_iterator, size_type, const std::string&) {return iterator{};} + template + iterator insert(const_iterator, InputIterator, InputIterator) {return iterator{};} + iterator insert(const_iterator, std::initializer_list) {return iterator{};} + + template + iterator emplace(const_iterator, Ts&& ...) {return iterator{};} + iterator erase(const_iterator) {return iterator{};} + iterator erase(const_iterator, const_iterator) {return iterator{};} + + void swap(discard_comments&) {return;} + + void push_back(const std::string&) {return;} + void push_back(std::string&& ) {return;} + void pop_back() {return;} + + template + void emplace_back(Ts&& ...) {return;} + + void clear() {return;} + + size_type size() const noexcept {return 0;} + size_type max_size() const noexcept {return 0;} + size_type capacity() const noexcept {return 0;} + bool empty() const noexcept {return true;} + + void reserve(size_type) {return;} + void resize(size_type) {return;} + void resize(size_type, const std::string&) {return;} + void shrink_to_fit() {return;} + + // DO NOT access to the element of this container. This container is always + // empty, so accessing through operator[], front/back, data causes address + // error. + + reference operator[](const size_type) noexcept {return *data();} + const_reference operator[](const size_type) const noexcept {return *data();} + reference at(const size_type) {throw std::out_of_range("toml::discard_comment is always empty.");} + const_reference at(const size_type) const {throw std::out_of_range("toml::discard_comment is always empty.");} + reference front() noexcept {return *data();} + const_reference front() const noexcept {return *data();} + reference back() noexcept {return *data();} + const_reference back() const noexcept {return *data();} + + pointer data() noexcept {return nullptr;} + const_pointer data() const noexcept {return nullptr;} + + iterator begin() noexcept {return iterator{};} + iterator end() noexcept {return iterator{};} + const_iterator begin() const noexcept {return const_iterator{};} + const_iterator end() const noexcept {return const_iterator{};} + const_iterator cbegin() const noexcept {return const_iterator{};} + const_iterator cend() const noexcept {return const_iterator{};} + + reverse_iterator rbegin() noexcept {return iterator{};} + reverse_iterator rend() noexcept {return iterator{};} + const_reverse_iterator rbegin() const noexcept {return const_iterator{};} + const_reverse_iterator rend() const noexcept {return const_iterator{};} + const_reverse_iterator crbegin() const noexcept {return const_iterator{};} + const_reverse_iterator crend() const noexcept {return const_iterator{};} +}; + +inline bool operator==(const discard_comments&, const discard_comments&) noexcept {return true;} +inline bool operator!=(const discard_comments&, const discard_comments&) noexcept {return false;} +inline bool operator< (const discard_comments&, const discard_comments&) noexcept {return false;} +inline bool operator<=(const discard_comments&, const discard_comments&) noexcept {return true;} +inline bool operator> (const discard_comments&, const discard_comments&) noexcept {return false;} +inline bool operator>=(const discard_comments&, const discard_comments&) noexcept {return true;} + +inline void swap(const discard_comments&, const discard_comments&) noexcept {return;} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const discard_comments&) +{ + return os; +} + +} // toml11 +#endif// TOML11_COMMENTS_HPP diff --git a/src/frontend/qt_sdl/toml/toml/datetime.hpp b/src/frontend/qt_sdl/toml/toml/datetime.hpp new file mode 100644 index 00000000..36db1e10 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/datetime.hpp @@ -0,0 +1,631 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_DATETIME_HPP +#define TOML11_DATETIME_HPP +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace toml +{ + +// To avoid non-threadsafe std::localtime. In C11 (not C++11!), localtime_s is +// provided in the absolutely same purpose, but C++11 is actually not compatible +// with C11. We need to dispatch the function depending on the OS. +namespace detail +{ +// TODO: find more sophisticated way to handle this +#if defined(_MSC_VER) +inline std::tm localtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::localtime_s(&dst, src); + if (result) { throw std::runtime_error("localtime_s failed."); } + return dst; +} +inline std::tm gmtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::gmtime_s(&dst, src); + if (result) { throw std::runtime_error("gmtime_s failed."); } + return dst; +} +#elif (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) || defined(_XOPEN_SOURCE) || defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || defined(_POSIX_SOURCE) +inline std::tm localtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::localtime_r(src, &dst); + if (!result) { throw std::runtime_error("localtime_r failed."); } + return dst; +} +inline std::tm gmtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::gmtime_r(src, &dst); + if (!result) { throw std::runtime_error("gmtime_r failed."); } + return dst; +} +#else // fallback. not threadsafe +inline std::tm localtime_s(const std::time_t* src) +{ + const auto result = std::localtime(src); + if (!result) { throw std::runtime_error("localtime failed."); } + return *result; +} +inline std::tm gmtime_s(const std::time_t* src) +{ + const auto result = std::gmtime(src); + if (!result) { throw std::runtime_error("gmtime failed."); } + return *result; +} +#endif +} // detail + +enum class month_t : std::uint8_t +{ + Jan = 0, + Feb = 1, + Mar = 2, + Apr = 3, + May = 4, + Jun = 5, + Jul = 6, + Aug = 7, + Sep = 8, + Oct = 9, + Nov = 10, + Dec = 11 +}; + +struct local_date +{ + std::int16_t year; // A.D. (like, 2018) + std::uint8_t month; // [0, 11] + std::uint8_t day; // [1, 31] + + local_date(int y, month_t m, int d) + : year (static_cast(y)), + month(static_cast(m)), + day (static_cast(d)) + {} + + explicit local_date(const std::tm& t) + : year (static_cast(t.tm_year + 1900)), + month(static_cast(t.tm_mon)), + day (static_cast(t.tm_mday)) + {} + + explicit local_date(const std::chrono::system_clock::time_point& tp) + { + const auto t = std::chrono::system_clock::to_time_t(tp); + const auto time = detail::localtime_s(&t); + *this = local_date(time); + } + + explicit local_date(const std::time_t t) + : local_date(std::chrono::system_clock::from_time_t(t)) + {} + + operator std::chrono::system_clock::time_point() const + { + // std::mktime returns date as local time zone. no conversion needed + std::tm t; + t.tm_sec = 0; + t.tm_min = 0; + t.tm_hour = 0; + t.tm_mday = static_cast(this->day); + t.tm_mon = static_cast(this->month); + t.tm_year = static_cast(this->year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + return std::chrono::system_clock::from_time_t(std::mktime(&t)); + } + + operator std::time_t() const + { + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); + } + + local_date() = default; + ~local_date() = default; + local_date(local_date const&) = default; + local_date(local_date&&) = default; + local_date& operator=(local_date const&) = default; + local_date& operator=(local_date&&) = default; +}; + +inline bool operator==(const local_date& lhs, const local_date& rhs) +{ + return std::make_tuple(lhs.year, lhs.month, lhs.day) == + std::make_tuple(rhs.year, rhs.month, rhs.day); +} +inline bool operator!=(const local_date& lhs, const local_date& rhs) +{ + return !(lhs == rhs); +} +inline bool operator< (const local_date& lhs, const local_date& rhs) +{ + return std::make_tuple(lhs.year, lhs.month, lhs.day) < + std::make_tuple(rhs.year, rhs.month, rhs.day); +} +inline bool operator<=(const local_date& lhs, const local_date& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +inline bool operator> (const local_date& lhs, const local_date& rhs) +{ + return !(lhs <= rhs); +} +inline bool operator>=(const local_date& lhs, const local_date& rhs) +{ + return !(lhs < rhs); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const local_date& date) +{ + os << std::setfill('0') << std::setw(4) << static_cast(date.year ) << '-'; + os << std::setfill('0') << std::setw(2) << static_cast(date.month) + 1 << '-'; + os << std::setfill('0') << std::setw(2) << static_cast(date.day ) ; + return os; +} + +struct local_time +{ + std::uint8_t hour; // [0, 23] + std::uint8_t minute; // [0, 59] + std::uint8_t second; // [0, 60] + std::uint16_t millisecond; // [0, 999] + std::uint16_t microsecond; // [0, 999] + std::uint16_t nanosecond; // [0, 999] + + local_time(int h, int m, int s, + int ms = 0, int us = 0, int ns = 0) + : hour (static_cast(h)), + minute(static_cast(m)), + second(static_cast(s)), + millisecond(static_cast(ms)), + microsecond(static_cast(us)), + nanosecond (static_cast(ns)) + {} + + explicit local_time(const std::tm& t) + : hour (static_cast(t.tm_hour)), + minute(static_cast(t.tm_min)), + second(static_cast(t.tm_sec)), + millisecond(0), microsecond(0), nanosecond(0) + {} + + template + explicit local_time(const std::chrono::duration& t) + { + const auto h = std::chrono::duration_cast(t); + this->hour = static_cast(h.count()); + const auto t2 = t - h; + const auto m = std::chrono::duration_cast(t2); + this->minute = static_cast(m.count()); + const auto t3 = t2 - m; + const auto s = std::chrono::duration_cast(t3); + this->second = static_cast(s.count()); + const auto t4 = t3 - s; + const auto ms = std::chrono::duration_cast(t4); + this->millisecond = static_cast(ms.count()); + const auto t5 = t4 - ms; + const auto us = std::chrono::duration_cast(t5); + this->microsecond = static_cast(us.count()); + const auto t6 = t5 - us; + const auto ns = std::chrono::duration_cast(t6); + this->nanosecond = static_cast(ns.count()); + } + + operator std::chrono::nanoseconds() const + { + return std::chrono::nanoseconds (this->nanosecond) + + std::chrono::microseconds(this->microsecond) + + std::chrono::milliseconds(this->millisecond) + + std::chrono::seconds(this->second) + + std::chrono::minutes(this->minute) + + std::chrono::hours(this->hour); + } + + local_time() = default; + ~local_time() = default; + local_time(local_time const&) = default; + local_time(local_time&&) = default; + local_time& operator=(local_time const&) = default; + local_time& operator=(local_time&&) = default; +}; + +inline bool operator==(const local_time& lhs, const local_time& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute, lhs.second, lhs.millisecond, lhs.microsecond, lhs.nanosecond) == + std::make_tuple(rhs.hour, rhs.minute, rhs.second, rhs.millisecond, rhs.microsecond, rhs.nanosecond); +} +inline bool operator!=(const local_time& lhs, const local_time& rhs) +{ + return !(lhs == rhs); +} +inline bool operator< (const local_time& lhs, const local_time& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute, lhs.second, lhs.millisecond, lhs.microsecond, lhs.nanosecond) < + std::make_tuple(rhs.hour, rhs.minute, rhs.second, rhs.millisecond, rhs.microsecond, rhs.nanosecond); +} +inline bool operator<=(const local_time& lhs, const local_time& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +inline bool operator> (const local_time& lhs, const local_time& rhs) +{ + return !(lhs <= rhs); +} +inline bool operator>=(const local_time& lhs, const local_time& rhs) +{ + return !(lhs < rhs); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const local_time& time) +{ + os << std::setfill('0') << std::setw(2) << static_cast(time.hour ) << ':'; + os << std::setfill('0') << std::setw(2) << static_cast(time.minute) << ':'; + os << std::setfill('0') << std::setw(2) << static_cast(time.second); + if(time.millisecond != 0 || time.microsecond != 0 || time.nanosecond != 0) + { + os << '.'; + os << std::setfill('0') << std::setw(3) << static_cast(time.millisecond); + if(time.microsecond != 0 || time.nanosecond != 0) + { + os << std::setfill('0') << std::setw(3) << static_cast(time.microsecond); + if(time.nanosecond != 0) + { + os << std::setfill('0') << std::setw(3) << static_cast(time.nanosecond); + } + } + } + return os; +} + +struct time_offset +{ + std::int8_t hour; // [-12, 12] + std::int8_t minute; // [-59, 59] + + time_offset(int h, int m) + : hour (static_cast(h)), + minute(static_cast(m)) + {} + + operator std::chrono::minutes() const + { + return std::chrono::minutes(this->minute) + + std::chrono::hours(this->hour); + } + + time_offset() = default; + ~time_offset() = default; + time_offset(time_offset const&) = default; + time_offset(time_offset&&) = default; + time_offset& operator=(time_offset const&) = default; + time_offset& operator=(time_offset&&) = default; +}; + +inline bool operator==(const time_offset& lhs, const time_offset& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute) == + std::make_tuple(rhs.hour, rhs.minute); +} +inline bool operator!=(const time_offset& lhs, const time_offset& rhs) +{ + return !(lhs == rhs); +} +inline bool operator< (const time_offset& lhs, const time_offset& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute) < + std::make_tuple(rhs.hour, rhs.minute); +} +inline bool operator<=(const time_offset& lhs, const time_offset& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +inline bool operator> (const time_offset& lhs, const time_offset& rhs) +{ + return !(lhs <= rhs); +} +inline bool operator>=(const time_offset& lhs, const time_offset& rhs) +{ + return !(lhs < rhs); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const time_offset& offset) +{ + if(offset.hour == 0 && offset.minute == 0) + { + os << 'Z'; + return os; + } + int minute = static_cast(offset.hour) * 60 + offset.minute; + if(minute < 0){os << '-'; minute = std::abs(minute);} else {os << '+';} + os << std::setfill('0') << std::setw(2) << minute / 60 << ':'; + os << std::setfill('0') << std::setw(2) << minute % 60; + return os; +} + +struct local_datetime +{ + local_date date; + local_time time; + + local_datetime(local_date d, local_time t): date(d), time(t) {} + + explicit local_datetime(const std::tm& t): date(t), time(t){} + + explicit local_datetime(const std::chrono::system_clock::time_point& tp) + { + const auto t = std::chrono::system_clock::to_time_t(tp); + std::tm ltime = detail::localtime_s(&t); + + this->date = local_date(ltime); + this->time = local_time(ltime); + + // std::tm lacks subsecond information, so diff between tp and tm + // can be used to get millisecond & microsecond information. + const auto t_diff = tp - + std::chrono::system_clock::from_time_t(std::mktime(<ime)); + this->time.millisecond = static_cast( + std::chrono::duration_cast(t_diff).count()); + this->time.microsecond = static_cast( + std::chrono::duration_cast(t_diff).count()); + this->time.nanosecond = static_cast( + std::chrono::duration_cast(t_diff).count()); + } + + explicit local_datetime(const std::time_t t) + : local_datetime(std::chrono::system_clock::from_time_t(t)) + {} + + operator std::chrono::system_clock::time_point() const + { + using internal_duration = + typename std::chrono::system_clock::time_point::duration; + + // Normally DST begins at A.M. 3 or 4. If we re-use conversion operator + // of local_date and local_time independently, the conversion fails if + // it is the day when DST begins or ends. Since local_date considers the + // time is 00:00 A.M. and local_time does not consider DST because it + // does not have any date information. We need to consider both date and + // time information at the same time to convert it correctly. + + std::tm t; + t.tm_sec = static_cast(this->time.second); + t.tm_min = static_cast(this->time.minute); + t.tm_hour = static_cast(this->time.hour); + t.tm_mday = static_cast(this->date.day); + t.tm_mon = static_cast(this->date.month); + t.tm_year = static_cast(this->date.year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + + // std::mktime returns date as local time zone. no conversion needed + auto dt = std::chrono::system_clock::from_time_t(std::mktime(&t)); + dt += std::chrono::duration_cast( + std::chrono::milliseconds(this->time.millisecond) + + std::chrono::microseconds(this->time.microsecond) + + std::chrono::nanoseconds (this->time.nanosecond)); + return dt; + } + + operator std::time_t() const + { + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); + } + + local_datetime() = default; + ~local_datetime() = default; + local_datetime(local_datetime const&) = default; + local_datetime(local_datetime&&) = default; + local_datetime& operator=(local_datetime const&) = default; + local_datetime& operator=(local_datetime&&) = default; +}; + +inline bool operator==(const local_datetime& lhs, const local_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time) == + std::make_tuple(rhs.date, rhs.time); +} +inline bool operator!=(const local_datetime& lhs, const local_datetime& rhs) +{ + return !(lhs == rhs); +} +inline bool operator< (const local_datetime& lhs, const local_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time) < + std::make_tuple(rhs.date, rhs.time); +} +inline bool operator<=(const local_datetime& lhs, const local_datetime& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +inline bool operator> (const local_datetime& lhs, const local_datetime& rhs) +{ + return !(lhs <= rhs); +} +inline bool operator>=(const local_datetime& lhs, const local_datetime& rhs) +{ + return !(lhs < rhs); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const local_datetime& dt) +{ + os << dt.date << 'T' << dt.time; + return os; +} + +struct offset_datetime +{ + local_date date; + local_time time; + time_offset offset; + + offset_datetime(local_date d, local_time t, time_offset o) + : date(d), time(t), offset(o) + {} + offset_datetime(const local_datetime& dt, time_offset o) + : date(dt.date), time(dt.time), offset(o) + {} + explicit offset_datetime(const local_datetime& ld) + : date(ld.date), time(ld.time), offset(get_local_offset(nullptr)) + // use the current local timezone offset + {} + explicit offset_datetime(const std::chrono::system_clock::time_point& tp) + : offset(0, 0) // use gmtime + { + const auto timet = std::chrono::system_clock::to_time_t(tp); + const auto tm = detail::gmtime_s(&timet); + this->date = local_date(tm); + this->time = local_time(tm); + } + explicit offset_datetime(const std::time_t& t) + : offset(0, 0) // use gmtime + { + const auto tm = detail::gmtime_s(&t); + this->date = local_date(tm); + this->time = local_time(tm); + } + explicit offset_datetime(const std::tm& t) + : offset(0, 0) // assume gmtime + { + this->date = local_date(t); + this->time = local_time(t); + } + + operator std::chrono::system_clock::time_point() const + { + // get date-time + using internal_duration = + typename std::chrono::system_clock::time_point::duration; + + // first, convert it to local date-time information in the same way as + // local_datetime does. later we will use time_t to adjust time offset. + std::tm t; + t.tm_sec = static_cast(this->time.second); + t.tm_min = static_cast(this->time.minute); + t.tm_hour = static_cast(this->time.hour); + t.tm_mday = static_cast(this->date.day); + t.tm_mon = static_cast(this->date.month); + t.tm_year = static_cast(this->date.year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + const std::time_t tp_loc = std::mktime(std::addressof(t)); + + auto tp = std::chrono::system_clock::from_time_t(tp_loc); + tp += std::chrono::duration_cast( + std::chrono::milliseconds(this->time.millisecond) + + std::chrono::microseconds(this->time.microsecond) + + std::chrono::nanoseconds (this->time.nanosecond)); + + // Since mktime uses local time zone, it should be corrected. + // `12:00:00+09:00` means `03:00:00Z`. So mktime returns `03:00:00Z` if + // we are in `+09:00` timezone. To represent `12:00:00Z` there, we need + // to add `+09:00` to `03:00:00Z`. + // Here, it uses the time_t converted from date-time info to handle + // daylight saving time. + const auto ofs = get_local_offset(std::addressof(tp_loc)); + tp += std::chrono::hours (ofs.hour); + tp += std::chrono::minutes(ofs.minute); + + // We got `12:00:00Z` by correcting local timezone applied by mktime. + // Then we will apply the offset. Let's say `12:00:00-08:00` is given. + // And now, we have `12:00:00Z`. `12:00:00-08:00` means `20:00:00Z`. + // So we need to subtract the offset. + tp -= std::chrono::minutes(this->offset); + return tp; + } + + operator std::time_t() const + { + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); + } + + offset_datetime() = default; + ~offset_datetime() = default; + offset_datetime(offset_datetime const&) = default; + offset_datetime(offset_datetime&&) = default; + offset_datetime& operator=(offset_datetime const&) = default; + offset_datetime& operator=(offset_datetime&&) = default; + + private: + + static time_offset get_local_offset(const std::time_t* tp) + { + // get local timezone with the same date-time information as mktime + const auto t = detail::localtime_s(tp); + + std::array buf; + const auto result = std::strftime(buf.data(), 6, "%z", &t); // +hhmm\0 + if(result != 5) + { + throw std::runtime_error("toml::offset_datetime: cannot obtain " + "timezone information of current env"); + } + const int ofs = std::atoi(buf.data()); + const int ofs_h = ofs / 100; + const int ofs_m = ofs - (ofs_h * 100); + return time_offset(ofs_h, ofs_m); + } +}; + +inline bool operator==(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time, lhs.offset) == + std::make_tuple(rhs.date, rhs.time, rhs.offset); +} +inline bool operator!=(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return !(lhs == rhs); +} +inline bool operator< (const offset_datetime& lhs, const offset_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time, lhs.offset) < + std::make_tuple(rhs.date, rhs.time, rhs.offset); +} +inline bool operator<=(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +inline bool operator> (const offset_datetime& lhs, const offset_datetime& rhs) +{ + return !(lhs <= rhs); +} +inline bool operator>=(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return !(lhs < rhs); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const offset_datetime& dt) +{ + os << dt.date << 'T' << dt.time << dt.offset; + return os; +} + +}//toml +#endif// TOML11_DATETIME diff --git a/src/frontend/qt_sdl/toml/toml/exception.hpp b/src/frontend/qt_sdl/toml/toml/exception.hpp new file mode 100644 index 00000000..c64651d0 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/exception.hpp @@ -0,0 +1,65 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_EXCEPTION_HPP +#define TOML11_EXCEPTION_HPP +#include +#include + +#include "source_location.hpp" + +namespace toml +{ + +struct exception : public std::exception +{ + public: + explicit exception(const source_location& loc): loc_(loc) {} + virtual ~exception() noexcept override = default; + virtual const char* what() const noexcept override {return "";} + virtual source_location const& location() const noexcept {return loc_;} + + protected: + source_location loc_; +}; + +struct syntax_error : public toml::exception +{ + public: + explicit syntax_error(const std::string& what_arg, const source_location& loc) + : exception(loc), what_(what_arg) + {} + virtual ~syntax_error() noexcept override = default; + virtual const char* what() const noexcept override {return what_.c_str();} + + protected: + std::string what_; +}; + +struct type_error : public toml::exception +{ + public: + explicit type_error(const std::string& what_arg, const source_location& loc) + : exception(loc), what_(what_arg) + {} + virtual ~type_error() noexcept override = default; + virtual const char* what() const noexcept override {return what_.c_str();} + + protected: + std::string what_; +}; + +struct internal_error : public toml::exception +{ + public: + explicit internal_error(const std::string& what_arg, const source_location& loc) + : exception(loc), what_(what_arg) + {} + virtual ~internal_error() noexcept override = default; + virtual const char* what() const noexcept override {return what_.c_str();} + + protected: + std::string what_; +}; + +} // toml +#endif // TOML_EXCEPTION diff --git a/src/frontend/qt_sdl/toml/toml/from.hpp b/src/frontend/qt_sdl/toml/toml/from.hpp new file mode 100644 index 00000000..10815caf --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/from.hpp @@ -0,0 +1,19 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_FROM_HPP +#define TOML11_FROM_HPP + +namespace toml +{ + +template +struct from; +// { +// static T from_toml(const toml::value& v) +// { +// // User-defined conversions ... +// } +// }; + +} // toml +#endif // TOML11_FROM_HPP diff --git a/src/frontend/qt_sdl/toml/toml/get.hpp b/src/frontend/qt_sdl/toml/toml/get.hpp new file mode 100644 index 00000000..901790b5 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/get.hpp @@ -0,0 +1,1119 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_GET_HPP +#define TOML11_GET_HPP +#include + +#include "from.hpp" +#include "result.hpp" +#include "value.hpp" + +namespace toml +{ + +// ============================================================================ +// exact toml::* type + +template class M, template class V> +detail::enable_if_t>::value, T> & +get(basic_value& v) +{ + return v.template cast>::value>(); +} + +template class M, template class V> +detail::enable_if_t>::value, T> const& +get(const basic_value& v) +{ + return v.template cast>::value>(); +} + +template class M, template class V> +detail::enable_if_t>::value, T> +get(basic_value&& v) +{ + return T(std::move(v).template cast>::value>()); +} + +// ============================================================================ +// T == toml::value; identity transformation. + +template class M, template class V> +inline detail::enable_if_t>::value, T>& +get(basic_value& v) +{ + return v; +} + +template class M, template class V> +inline detail::enable_if_t>::value, T> const& +get(const basic_value& v) +{ + return v; +} + +template class M, template class V> +inline detail::enable_if_t>::value, T> +get(basic_value&& v) +{ + return basic_value(std::move(v)); +} + +// ============================================================================ +// T == toml::basic_value; basic_value -> basic_value + +template class M, template class V> +inline detail::enable_if_t, + detail::negation>> + >::value, T> +get(const basic_value& v) +{ + return T(v); +} + +// ============================================================================ +// integer convertible from toml::Integer + +template class M, template class V> +inline detail::enable_if_t, // T is integral + detail::negation>, // but not bool + detail::negation< // but not toml::integer + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value& v) +{ + return static_cast(v.as_integer()); +} + +// ============================================================================ +// floating point convertible from toml::Float + +template class M, template class V> +inline detail::enable_if_t, // T is floating_point + detail::negation< // but not toml::floating + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value& v) +{ + return static_cast(v.as_floating()); +} + +// ============================================================================ +// std::string; toml uses its own toml::string, but it should be convertible to +// std::string seamlessly + +template class M, template class V> +inline detail::enable_if_t::value, std::string>& +get(basic_value& v) +{ + return v.as_string().str; +} + +template class M, template class V> +inline detail::enable_if_t::value, std::string> const& +get(const basic_value& v) +{ + return v.as_string().str; +} + +template class M, template class V> +inline detail::enable_if_t::value, std::string> +get(basic_value&& v) +{ + return std::string(std::move(v.as_string().str)); +} + +// ============================================================================ +// std::string_view + +#if defined(TOML11_USING_STRING_VIEW) && TOML11_USING_STRING_VIEW>0 +template class M, template class V> +inline detail::enable_if_t::value, std::string_view> +get(const basic_value& v) +{ + return std::string_view(v.as_string().str); +} +#endif + +// ============================================================================ +// std::chrono::duration from toml::local_time. + +template class M, template class V> +inline detail::enable_if_t::value, T> +get(const basic_value& v) +{ + return std::chrono::duration_cast( + std::chrono::nanoseconds(v.as_local_time())); +} + +// ============================================================================ +// std::chrono::system_clock::time_point from toml::datetime variants + +template class M, template class V> +inline detail::enable_if_t< + std::is_same::value, T> +get(const basic_value& v) +{ + switch(v.type()) + { + case value_t::local_date: + { + return std::chrono::system_clock::time_point(v.as_local_date()); + } + case value_t::local_datetime: + { + return std::chrono::system_clock::time_point(v.as_local_datetime()); + } + case value_t::offset_datetime: + { + return std::chrono::system_clock::time_point(v.as_offset_datetime()); + } + default: + { + throw type_error(detail::format_underline("toml::value: " + "bad_cast to std::chrono::system_clock::time_point", { + {v.location(), concat_to_string("the actual type is ", v.type())} + }), v.location()); + } + } +} + +// ============================================================================ +// forward declaration to use this recursively. ignore this and go ahead. + +// array-like type with push_back(value) method +template class M, template class V> +detail::enable_if_t, // T is a container + detail::has_push_back_method, // T::push_back(value) works + detail::negation< // but not toml::array + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value&); + +// array-like type without push_back(value) method +template class M, template class V> +detail::enable_if_t, // T is a container + detail::negation>, // w/o push_back(...) + detail::negation>, // T does not have special conversion + detail::negation< // not toml::array + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value&); + +// std::pair +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value&); + +// std::tuple +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value&); + +// map-like classes +template class M, template class V> +detail::enable_if_t, // T is map + detail::negation< // but not toml::table + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value&); + +// T.from_toml(v) +template class M, template class V> +detail::enable_if_t>>, + detail::has_from_toml_method, // but has from_toml(toml::value) + std::is_default_constructible // and default constructible + >::value, T> +get(const basic_value&); + +// toml::from::from_toml(v) +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value&); + +// T(const toml::value&) and T is not toml::basic_value, +// and it does not have `from` nor `from_toml`. +template class M, template class V> +detail::enable_if_t>, + std::is_constructible&>, + detail::negation>, + detail::negation> + >::value, T> +get(const basic_value&); + +// ============================================================================ +// array-like types; most likely STL container, like std::vector, etc. + +template class M, template class V> +detail::enable_if_t, // T is a container + detail::has_push_back_method, // container.push_back(elem) works + detail::negation< // but not toml::array + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value& v) +{ + using value_type = typename T::value_type; + const auto& ary = v.as_array(); + + T container; + try_reserve(container, ary.size()); + + for(const auto& elem : ary) + { + container.push_back(get(elem)); + } + return container; +} + +// ============================================================================ +// std::forward_list does not have push_back, insert, or emplace. +// It has insert_after, emplace_after, push_front. + +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value& v) +{ + using value_type = typename T::value_type; + T container; + for(const auto& elem : v.as_array()) + { + container.push_front(get(elem)); + } + container.reverse(); + return container; +} + +// ============================================================================ +// array-like types, without push_back(). most likely [std|boost]::array. + +template class M, template class V> +detail::enable_if_t, // T is a container + detail::negation>, // w/o push_back + detail::negation>, // T does not have special conversion + detail::negation< // T is not toml::array + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value& v) +{ + using value_type = typename T::value_type; + const auto& ar = v.as_array(); + + T container; + if(ar.size() != container.size()) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "toml::get: specified container size is ", container.size(), + " but there are ", ar.size(), " elements in toml array."), { + {v.location(), "here"} + })); + } + for(std::size_t i=0; i(ar[i]); + } + return container; +} + +// ============================================================================ +// std::pair. + +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value& v) +{ + using first_type = typename T::first_type; + using second_type = typename T::second_type; + + const auto& ar = v.as_array(); + if(ar.size() != 2) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "toml::get: specified std::pair but there are ", ar.size(), + " elements in toml array."), {{v.location(), "here"}})); + } + return std::make_pair(::toml::get(ar.at(0)), + ::toml::get(ar.at(1))); +} + +// ============================================================================ +// std::tuple. + +namespace detail +{ +template +T get_tuple_impl(const Array& a, index_sequence) +{ + return std::make_tuple( + ::toml::get::type>(a.at(I))...); +} +} // detail + +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value& v) +{ + const auto& ar = v.as_array(); + if(ar.size() != std::tuple_size::value) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "toml::get: specified std::tuple with ", + std::tuple_size::value, " elements, but there are ", ar.size(), + " elements in toml array."), {{v.location(), "here"}})); + } + return detail::get_tuple_impl(ar, + detail::make_index_sequence::value>{}); +} + +// ============================================================================ +// map-like types; most likely STL map, like std::map or std::unordered_map. + +template class M, template class V> +detail::enable_if_t, // T is map + detail::negation< // but not toml::array + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value& v) +{ + using key_type = typename T::key_type; + using mapped_type = typename T::mapped_type; + static_assert(std::is_convertible::value, + "toml::get only supports map type of which key_type is " + "convertible from std::string."); + T map; + for(const auto& kv : v.as_table()) + { + map.emplace(key_type(kv.first), get(kv.second)); + } + return map; +} + +// ============================================================================ +// user-defined, but compatible types. + +template class M, template class V> +detail::enable_if_t>>, + detail::has_from_toml_method, // but has from_toml(toml::value) memfn + std::is_default_constructible // and default constructible + >::value, T> +get(const basic_value& v) +{ + T ud; + ud.from_toml(v); + return ud; +} +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value& v) +{ + return ::toml::from::from_toml(v); +} + +template class M, template class V> +detail::enable_if_t>, // T is not a toml::value + std::is_constructible&>, // T is constructible from toml::value + detail::negation>, // and T does not have T.from_toml(v); + detail::negation> // and T does not have toml::from{}; + >::value, T> +get(const basic_value& v) +{ + return T(v); +} + +// ============================================================================ +// find + +// ---------------------------------------------------------------------------- +// these overloads do not require to set T. and returns value itself. +template class M, template class V> +basic_value const& find(const basic_value& v, const key& ky) +{ + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return tab.at(ky); +} +template class M, template class V> +basic_value& find(basic_value& v, const key& ky) +{ + auto& tab = v.as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return tab.at(ky); +} +template class M, template class V> +basic_value find(basic_value&& v, const key& ky) +{ + typename basic_value::table_type tab = std::move(v).as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return basic_value(std::move(tab.at(ky))); +} + +// ---------------------------------------------------------------------------- +// find(value, idx) +template class M, template class V> +basic_value const& +find(const basic_value& v, const std::size_t idx) +{ + const auto& ary = v.as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return ary.at(idx); +} +template class M, template class V> +basic_value& find(basic_value& v, const std::size_t idx) +{ + auto& ary = v.as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return ary.at(idx); +} +template class M, template class V> +basic_value find(basic_value&& v, const std::size_t idx) +{ + auto& ary = v.as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return basic_value(std::move(ary.at(idx))); +} + +// ---------------------------------------------------------------------------- +// find(value, key); + +template class M, template class V> +decltype(::toml::get(std::declval const&>())) +find(const basic_value& v, const key& ky) +{ + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return ::toml::get(tab.at(ky)); +} + +template class M, template class V> +decltype(::toml::get(std::declval&>())) +find(basic_value& v, const key& ky) +{ + auto& tab = v.as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return ::toml::get(tab.at(ky)); +} + +template class M, template class V> +decltype(::toml::get(std::declval&&>())) +find(basic_value&& v, const key& ky) +{ + typename basic_value::table_type tab = std::move(v).as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return ::toml::get(std::move(tab.at(ky))); +} + +// ---------------------------------------------------------------------------- +// find(value, idx) +template class M, template class V> +decltype(::toml::get(std::declval const&>())) +find(const basic_value& v, const std::size_t idx) +{ + const auto& ary = v.as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return ::toml::get(ary.at(idx)); +} +template class M, template class V> +decltype(::toml::get(std::declval&>())) +find(basic_value& v, const std::size_t idx) +{ + auto& ary = v.as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return ::toml::get(ary.at(idx)); +} +template class M, template class V> +decltype(::toml::get(std::declval&&>())) +find(basic_value&& v, const std::size_t idx) +{ + typename basic_value::array_type ary = std::move(v).as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return ::toml::get(std::move(ary.at(idx))); +} + +// -------------------------------------------------------------------------- +// toml::find(toml::value, toml::key, Ts&& ... keys) + +namespace detail +{ +// It suppresses warnings by -Wsign-conversion. Let's say we have the following +// code. +// ```cpp +// const auto x = toml::find(data, "array", 0); +// ``` +// Here, the type of literal number `0` is `int`. `int` is a signed integer. +// `toml::find` takes `std::size_t` as an index. So it causes implicit sign +// conversion and `-Wsign-conversion` warns about it. Using `0u` instead of `0` +// suppresses the warning, but it makes user code messy. +// To suppress this warning, we need to be aware of type conversion caused +// by `toml::find(v, key1, key2, ... keys)`. But the thing is that the types of +// keys can be any combination of {string-like, size_t-like}. Of course we can't +// write down all the combinations. Thus we need to use some function that +// recognize the type of argument and cast it into `std::string` or +// `std::size_t` depending on the context. +// `key_cast` does the job. It has 2 overloads. One is invoked when the +// argument type is an integer and cast the argument into `std::size_t`. The +// other is invoked when the argument type is not an integer, possibly one of +// std::string, const char[N] or const char*, and construct std::string from +// the argument. +// `toml::find(v, k1, k2, ... ks)` uses `key_cast` before passing `ks` to +// `toml::find(v, k)` to suppress -Wsign-conversion. + +template +enable_if_t>, + negation, bool>>>::value, std::size_t> +key_cast(T&& v) noexcept +{ + return std::size_t(v); +} +template +enable_if_t>, + negation, bool>>>>::value, std::string> +key_cast(T&& v) noexcept +{ + return std::string(std::forward(v)); +} +} // detail + +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +const basic_value& +find(const basic_value& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(v, detail::key_cast(k1)), + detail::key_cast(k2), std::forward(keys)...); +} +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +basic_value& +find(basic_value& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(v, detail::key_cast(k1)), + detail::key_cast(k2), std::forward(keys)...); +} +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +basic_value +find(basic_value&& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(std::move(v), std::forward(k1)), + detail::key_cast(k2), std::forward(keys)...); +} + +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +decltype(::toml::get(std::declval&>())) +find(const basic_value& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(v, detail::key_cast(k1)), + detail::key_cast(k2), std::forward(keys)...); +} +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +decltype(::toml::get(std::declval&>())) +find(basic_value& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(v, detail::key_cast(k1)), + detail::key_cast(k2), std::forward(keys)...); +} +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +decltype(::toml::get(std::declval&&>())) +find(basic_value&& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(std::move(v), detail::key_cast(k1)), + detail::key_cast(k2), std::forward(keys)...); +} + +// ============================================================================ +// get_or(value, fallback) + +template class M, template class V> +basic_value const& +get_or(const basic_value& v, const basic_value&) +{ + return v; +} +template class M, template class V> +basic_value& +get_or(basic_value& v, basic_value&) +{ + return v; +} +template class M, template class V> +basic_value +get_or(basic_value&& v, basic_value&&) +{ + return v; +} + +// ---------------------------------------------------------------------------- +// specialization for the exact toml types (return type becomes lvalue ref) + +template class M, template class V> +detail::enable_if_t< + detail::is_exact_toml_type>::value, T> const& +get_or(const basic_value& v, const T& opt) +{ + try + { + return get>(v); + } + catch(...) + { + return opt; + } +} +template class M, template class V> +detail::enable_if_t< + detail::is_exact_toml_type>::value, T>& +get_or(basic_value& v, T& opt) +{ + try + { + return get>(v); + } + catch(...) + { + return opt; + } +} +template class M, template class V> +detail::enable_if_t, + basic_value>::value, detail::remove_cvref_t> +get_or(basic_value&& v, T&& opt) +{ + try + { + return get>(std::move(v)); + } + catch(...) + { + return detail::remove_cvref_t(std::forward(opt)); + } +} + +// ---------------------------------------------------------------------------- +// specialization for std::string (return type becomes lvalue ref) + +template class M, template class V> +detail::enable_if_t, std::string>::value, + std::string> const& +get_or(const basic_value& v, const T& opt) +{ + try + { + return v.as_string().str; + } + catch(...) + { + return opt; + } +} +template class M, template class V> +detail::enable_if_t::value, std::string>& +get_or(basic_value& v, T& opt) +{ + try + { + return v.as_string().str; + } + catch(...) + { + return opt; + } +} +template class M, template class V> +detail::enable_if_t< + std::is_same, std::string>::value, std::string> +get_or(basic_value&& v, T&& opt) +{ + try + { + return std::move(v.as_string().str); + } + catch(...) + { + return std::string(std::forward(opt)); + } +} + +// ---------------------------------------------------------------------------- +// specialization for string literal + +template class M, template class V> +detail::enable_if_t::type>::value, std::string> +get_or(const basic_value& v, T&& opt) +{ + try + { + return std::move(v.as_string().str); + } + catch(...) + { + return std::string(std::forward(opt)); + } +} + +// ---------------------------------------------------------------------------- +// others (require type conversion and return type cannot be lvalue reference) + +template class M, template class V> +detail::enable_if_t, + basic_value>>, + detail::negation>>, + detail::negation::type>> + >::value, detail::remove_cvref_t> +get_or(const basic_value& v, T&& opt) +{ + try + { + return get>(v); + } + catch(...) + { + return detail::remove_cvref_t(std::forward(opt)); + } +} + +// =========================================================================== +// find_or(value, key, fallback) + +template class M, template class V> +basic_value const& +find_or(const basic_value& v, const key& ky, + const basic_value& opt) +{ + if(!v.is_table()) {return opt;} + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return tab.at(ky); +} + +template class M, template class V> +basic_value& +find_or(basic_value& v, const toml::key& ky, basic_value& opt) +{ + if(!v.is_table()) {return opt;} + auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return tab.at(ky); +} + +template class M, template class V> +basic_value +find_or(basic_value&& v, const toml::key& ky, basic_value&& opt) +{ + if(!v.is_table()) {return opt;} + auto tab = std::move(v).as_table(); + if(tab.count(ky) == 0) {return opt;} + return basic_value(std::move(tab.at(ky))); +} + +// --------------------------------------------------------------------------- +// exact types (return type can be a reference) +template class M, template class V> +detail::enable_if_t< + detail::is_exact_toml_type>::value, T> const& +find_or(const basic_value& v, const key& ky, const T& opt) +{ + if(!v.is_table()) {return opt;} + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return get_or(tab.at(ky), opt); +} + +template class M, template class V> +detail::enable_if_t< + detail::is_exact_toml_type>::value, T>& +find_or(basic_value& v, const toml::key& ky, T& opt) +{ + if(!v.is_table()) {return opt;} + auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return get_or(tab.at(ky), opt); +} + +template class M, template class V> +detail::enable_if_t< + detail::is_exact_toml_type>::value, + detail::remove_cvref_t> +find_or(basic_value&& v, const toml::key& ky, T&& opt) +{ + if(!v.is_table()) {return std::forward(opt);} + auto tab = std::move(v).as_table(); + if(tab.count(ky) == 0) {return std::forward(opt);} + return get_or(std::move(tab.at(ky)), std::forward(opt)); +} + +// --------------------------------------------------------------------------- +// std::string (return type can be a reference) + +template class M, template class V> +detail::enable_if_t::value, std::string> const& +find_or(const basic_value& v, const key& ky, const T& opt) +{ + if(!v.is_table()) {return opt;} + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return get_or(tab.at(ky), opt); +} +template class M, template class V> +detail::enable_if_t::value, std::string>& +find_or(basic_value& v, const toml::key& ky, T& opt) +{ + if(!v.is_table()) {return opt;} + auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return get_or(tab.at(ky), opt); +} +template class M, template class V> +detail::enable_if_t::value, std::string> +find_or(basic_value&& v, const toml::key& ky, T&& opt) +{ + if(!v.is_table()) {return std::forward(opt);} + auto tab = std::move(v).as_table(); + if(tab.count(ky) == 0) {return std::forward(opt);} + return get_or(std::move(tab.at(ky)), std::forward(opt)); +} + +// --------------------------------------------------------------------------- +// string literal (deduced as std::string) +template class M, template class V> +detail::enable_if_t< + detail::is_string_literal::type>::value, + std::string> +find_or(const basic_value& v, const toml::key& ky, T&& opt) +{ + if(!v.is_table()) {return std::string(opt);} + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return std::string(opt);} + return get_or(tab.at(ky), std::forward(opt)); +} + +// --------------------------------------------------------------------------- +// others (require type conversion and return type cannot be lvalue reference) +template class M, template class V> +detail::enable_if_t, basic_value>>, + // T is not std::string + detail::negation>>, + // T is not a string literal + detail::negation::type>> + >::value, detail::remove_cvref_t> +find_or(const basic_value& v, const toml::key& ky, T&& opt) +{ + if(!v.is_table()) {return std::forward(opt);} + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return std::forward(opt);} + return get_or(tab.at(ky), std::forward(opt)); +} + +// --------------------------------------------------------------------------- +// recursive find-or with type deduction (find_or(value, keys, opt)) + +template 1), std::nullptr_t> = nullptr> + // here we need to add SFINAE in the template parameter to avoid + // infinite recursion in type deduction on gcc +auto find_or(Value&& v, const toml::key& ky, Ks&& ... keys) + -> decltype(find_or(std::forward(v), ky, detail::last_one(std::forward(keys)...))) +{ + if(!v.is_table()) + { + return detail::last_one(std::forward(keys)...); + } + auto&& tab = std::forward(v).as_table(); + if(tab.count(ky) == 0) + { + return detail::last_one(std::forward(keys)...); + } + return find_or(std::forward(tab).at(ky), std::forward(keys)...); +} + +// --------------------------------------------------------------------------- +// recursive find_or with explicit type specialization, find_or(value, keys...) + +template 1), std::nullptr_t> = nullptr> + // here we need to add SFINAE in the template parameter to avoid + // infinite recursion in type deduction on gcc +auto find_or(Value&& v, const toml::key& ky, Ks&& ... keys) + -> decltype(find_or(std::forward(v), ky, detail::last_one(std::forward(keys)...))) +{ + if(!v.is_table()) + { + return detail::last_one(std::forward(keys)...); + } + auto&& tab = std::forward(v).as_table(); + if(tab.count(ky) == 0) + { + return detail::last_one(std::forward(keys)...); + } + return find_or(std::forward(tab).at(ky), std::forward(keys)...); +} + +// ============================================================================ +// expect + +template class M, template class V> +result expect(const basic_value& v) noexcept +{ + try + { + return ok(get(v)); + } + catch(const std::exception& e) + { + return err(e.what()); + } +} +template class M, template class V> +result +expect(const basic_value& v, const toml::key& k) noexcept +{ + try + { + return ok(find(v, k)); + } + catch(const std::exception& e) + { + return err(e.what()); + } +} + +} // toml +#endif// TOML11_GET diff --git a/src/frontend/qt_sdl/toml/toml/into.hpp b/src/frontend/qt_sdl/toml/toml/into.hpp new file mode 100644 index 00000000..74495560 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/into.hpp @@ -0,0 +1,19 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_INTO_HPP +#define TOML11_INTO_HPP + +namespace toml +{ + +template +struct into; +// { +// static toml::value into_toml(const T& user_defined_type) +// { +// // User-defined conversions ... +// } +// }; + +} // toml +#endif // TOML11_INTO_HPP diff --git a/src/frontend/qt_sdl/toml/toml/lexer.hpp b/src/frontend/qt_sdl/toml/toml/lexer.hpp new file mode 100644 index 00000000..ea5050b8 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/lexer.hpp @@ -0,0 +1,293 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_LEXER_HPP +#define TOML11_LEXER_HPP +#include +#include +#include +#include + +#include "combinator.hpp" + +namespace toml +{ +namespace detail +{ + +// these scans contents from current location in a container of char +// and extract a region that matches their own pattern. +// to see the implementation of each component, see combinator.hpp. + +using lex_wschar = either, character<'\t'>>; +using lex_ws = repeat>; +using lex_newline = either, + sequence, character<'\n'>>>; +using lex_lower = in_range<'a', 'z'>; +using lex_upper = in_range<'A', 'Z'>; +using lex_alpha = either; +using lex_digit = in_range<'0', '9'>; +using lex_nonzero = in_range<'1', '9'>; +using lex_oct_dig = in_range<'0', '7'>; +using lex_bin_dig = in_range<'0', '1'>; +using lex_hex_dig = either, in_range<'a', 'f'>>; + +using lex_hex_prefix = sequence, character<'x'>>; +using lex_oct_prefix = sequence, character<'o'>>; +using lex_bin_prefix = sequence, character<'b'>>; +using lex_underscore = character<'_'>; +using lex_plus = character<'+'>; +using lex_minus = character<'-'>; +using lex_sign = either; + +// digit | nonzero 1*(digit | _ digit) +using lex_unsigned_dec_int = either>, at_least<1>>>, + lex_digit>; +// (+|-)? unsigned_dec_int +using lex_dec_int = sequence, lex_unsigned_dec_int>; + +// hex_prefix hex_dig *(hex_dig | _ hex_dig) +using lex_hex_int = sequence>, unlimited>>>; +// oct_prefix oct_dig *(oct_dig | _ oct_dig) +using lex_oct_int = sequence>, unlimited>>>; +// bin_prefix bin_dig *(bin_dig | _ bin_dig) +using lex_bin_int = sequence>, unlimited>>>; + +// (dec_int | hex_int | oct_int | bin_int) +using lex_integer = either; + +// =========================================================================== + +using lex_inf = sequence, character<'n'>, character<'f'>>; +using lex_nan = sequence, character<'a'>, character<'n'>>; +using lex_special_float = sequence, either>; + +using lex_zero_prefixable_int = sequence>, unlimited>>; + +using lex_fractional_part = sequence, lex_zero_prefixable_int>; + +using lex_exponent_part = sequence, character<'E'>>, + maybe, lex_zero_prefixable_int>; + +using lex_float = either>>>>; + +// =========================================================================== + +using lex_true = sequence, character<'r'>, + character<'u'>, character<'e'>>; +using lex_false = sequence, character<'a'>, character<'l'>, + character<'s'>, character<'e'>>; +using lex_boolean = either; + +// =========================================================================== + +using lex_date_fullyear = repeat>; +using lex_date_month = repeat>; +using lex_date_mday = repeat>; +using lex_time_delim = either, character<'t'>, character<' '>>; +using lex_time_hour = repeat>; +using lex_time_minute = repeat>; +using lex_time_second = repeat>; +using lex_time_secfrac = sequence, + repeat>>; + +using lex_time_numoffset = sequence, character<'-'>>, + sequence, + lex_time_minute>>; +using lex_time_offset = either, character<'z'>, + lex_time_numoffset>; + +using lex_partial_time = sequence, + lex_time_minute, character<':'>, + lex_time_second, maybe>; +using lex_full_date = sequence, + lex_date_month, character<'-'>, + lex_date_mday>; +using lex_full_time = sequence; + +using lex_offset_date_time = sequence; +using lex_local_date_time = sequence; +using lex_local_date = lex_full_date; +using lex_local_time = lex_partial_time; + +// =========================================================================== + +using lex_quotation_mark = character<'"'>; +using lex_basic_unescaped = exclude, // 0x09 (tab) is allowed + in_range<0x0A, 0x1F>, + character<0x22>, character<0x5C>, + character<0x7F>>>; + +using lex_escape = character<'\\'>; +using lex_escape_unicode_short = sequence, + repeat>>; +using lex_escape_unicode_long = sequence, + repeat>>; +using lex_escape_seq_char = either, character<'\\'>, + character<'b'>, character<'f'>, + character<'n'>, character<'r'>, + character<'t'>, + lex_escape_unicode_short, + lex_escape_unicode_long + >; +using lex_escaped = sequence; +using lex_basic_char = either; +using lex_basic_string = sequence, + lex_quotation_mark>; + +// After toml post-v0.5.0, it is explicitly clarified how quotes in ml-strings +// are allowed to be used. +// After this, the following strings are *explicitly* allowed. +// - One or two `"`s in a multi-line basic string is allowed wherever it is. +// - Three consecutive `"`s in a multi-line basic string is considered as a delimiter. +// - One or two `"`s can appear just before or after the delimiter. +// ```toml +// str4 = """Here are two quotation marks: "". Simple enough.""" +// str5 = """Here are three quotation marks: ""\".""" +// str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" +// str7 = """"This," she said, "is just a pointless statement."""" +// ``` +// In the current implementation (v3.3.0), it is difficult to parse `str7` in +// the above example. It is difficult to recognize `"` at the end of string body +// collectly. It will be misunderstood as a `"""` delimiter and an additional, +// invalid `"`. Like this: +// ```console +// what(): [error] toml::parse_table: invalid line format +// --> hoge.toml +// | +// 13 | str7 = """"This," she said, "is just a pointless statement."""" +// | ^- expected newline, but got '"'. +// ``` +// As a quick workaround for this problem, `lex_ml_basic_string_delim` was +// split into two, `lex_ml_basic_string_open` and `lex_ml_basic_string_close`. +// `lex_ml_basic_string_open` allows only `"""`. `_close` allows 3-5 `"`s. +// In parse_ml_basic_string() function, the trailing `"`s will be attached to +// the string body. +// +using lex_ml_basic_string_delim = repeat>; +using lex_ml_basic_string_open = lex_ml_basic_string_delim; +using lex_ml_basic_string_close = sequence< + repeat>, + maybe, maybe + >; + +using lex_ml_basic_unescaped = exclude, // 0x09 is tab + in_range<0x0A, 0x1F>, + character<0x5C>, // backslash + character<0x7F>, // DEL + lex_ml_basic_string_delim>>; + +using lex_ml_basic_escaped_newline = sequence< + lex_escape, maybe, lex_newline, + repeat, unlimited>>; + +using lex_ml_basic_char = either; +using lex_ml_basic_body = repeat, + unlimited>; +using lex_ml_basic_string = sequence; + +using lex_literal_char = exclude, in_range<0x0A, 0x1F>, + character<0x7F>, character<0x27>>>; +using lex_apostrophe = character<'\''>; +using lex_literal_string = sequence, + lex_apostrophe>; + +// the same reason as above. +using lex_ml_literal_string_delim = repeat>; +using lex_ml_literal_string_open = lex_ml_literal_string_delim; +using lex_ml_literal_string_close = sequence< + repeat>, + maybe, maybe + >; + +using lex_ml_literal_char = exclude, + in_range<0x0A, 0x1F>, + character<0x7F>, + lex_ml_literal_string_delim>>; +using lex_ml_literal_body = repeat, + unlimited>; +using lex_ml_literal_string = sequence; + +using lex_string = either; + +// =========================================================================== +using lex_dot_sep = sequence, character<'.'>, maybe>; + +using lex_unquoted_key = repeat, character<'_'>>, + at_least<1>>; +using lex_quoted_key = either; +using lex_simple_key = either; +using lex_dotted_key = sequence, + at_least<1> + > + >; +using lex_key = either; + +using lex_keyval_sep = sequence, + character<'='>, + maybe>; + +using lex_std_table_open = character<'['>; +using lex_std_table_close = character<']'>; +using lex_std_table = sequence, + lex_key, + maybe, + lex_std_table_close>; + +using lex_array_table_open = sequence; +using lex_array_table_close = sequence; +using lex_array_table = sequence, + lex_key, + maybe, + lex_array_table_close>; + +using lex_utf8_1byte = in_range<0x00, 0x7F>; +using lex_utf8_2byte = sequence< + in_range(0xC2), static_cast(0xDF)>, + in_range(0x80), static_cast(0xBF)> + >; +using lex_utf8_3byte = sequence(0xE0)>, in_range(0xA0), static_cast(0xBF)>>, + sequence(0xE1), static_cast(0xEC)>, in_range(0x80), static_cast(0xBF)>>, + sequence(0xED)>, in_range(0x80), static_cast(0x9F)>>, + sequence(0xEE), static_cast(0xEF)>, in_range(0x80), static_cast(0xBF)>> + >, in_range(0x80), static_cast(0xBF)>>; +using lex_utf8_4byte = sequence(0xF0)>, in_range(0x90), static_cast(0xBF)>>, + sequence(0xF1), static_cast(0xF3)>, in_range(0x80), static_cast(0xBF)>>, + sequence(0xF4)>, in_range(0x80), static_cast(0x8F)>> + >, in_range(0x80), static_cast(0xBF)>, + in_range(0x80), static_cast(0xBF)>>; +using lex_utf8_code = either< + lex_utf8_1byte, + lex_utf8_2byte, + lex_utf8_3byte, + lex_utf8_4byte + >; + +using lex_comment_start_symbol = character<'#'>; +using lex_non_eol_ascii = either, in_range<0x20, 0x7E>>; +using lex_comment = sequence, unlimited>>; + +} // detail +} // toml +#endif // TOML_LEXER_HPP diff --git a/src/frontend/qt_sdl/toml/toml/literal.hpp b/src/frontend/qt_sdl/toml/toml/literal.hpp new file mode 100644 index 00000000..04fbbc13 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/literal.hpp @@ -0,0 +1,113 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_LITERAL_HPP +#define TOML11_LITERAL_HPP +#include "parser.hpp" + +namespace toml +{ +inline namespace literals +{ +inline namespace toml_literals +{ + +// implementation +inline ::toml::basic_value +literal_internal_impl(::toml::detail::location loc) +{ + using value_type = ::toml::basic_value< + TOML11_DEFAULT_COMMENT_STRATEGY, std::unordered_map, std::vector>; + // if there are some comments or empty lines, skip them. + using skip_line = ::toml::detail::repeat, + ::toml::detail::maybe<::toml::detail::lex_comment>, + ::toml::detail::lex_newline + >, ::toml::detail::at_least<1>>; + skip_line::invoke(loc); + + // if there are some whitespaces before a value, skip them. + using skip_ws = ::toml::detail::repeat< + ::toml::detail::lex_ws, ::toml::detail::at_least<1>>; + skip_ws::invoke(loc); + + // to distinguish arrays and tables, first check it is a table or not. + // + // "[1,2,3]"_toml; // this is an array + // "[table]"_toml; // a table that has an empty table named "table" inside. + // "[[1,2,3]]"_toml; // this is an array of arrays + // "[[table]]"_toml; // this is a table that has an array of tables inside. + // + // "[[1]]"_toml; // this can be both... (currently it becomes a table) + // "1 = [{}]"_toml; // this is a table that has an array of table named 1. + // "[[1,]]"_toml; // this is an array of arrays. + // "[[1],]"_toml; // this also. + + const auto the_front = loc.iter(); + + const bool is_table_key = ::toml::detail::lex_std_table::invoke(loc); + loc.reset(the_front); + + const bool is_aots_key = ::toml::detail::lex_array_table::invoke(loc); + loc.reset(the_front); + + // If it is neither a table-key or a array-of-table-key, it may be a value. + if(!is_table_key && !is_aots_key) + { + if(auto data = ::toml::detail::parse_value(loc)) + { + return data.unwrap(); + } + } + + // Note that still it can be a table, because the literal might be something + // like the following. + // ```cpp + // R"( // c++11 raw string literals + // key = "value" + // int = 42 + // )"_toml; + // ``` + // It is a valid toml file. + // It should be parsed as if we parse a file with this content. + + if(auto data = ::toml::detail::parse_toml_file(loc)) + { + return data.unwrap(); + } + else // none of them. + { + throw ::toml::syntax_error(data.unwrap_err(), source_location(loc)); + } + +} + +inline ::toml::basic_value +operator"" _toml(const char* str, std::size_t len) +{ + ::toml::detail::location loc( + std::string("TOML literal encoded in a C++ code"), + std::vector(str, str + len)); + // literal length does not include the null character at the end. + return literal_internal_impl(std::move(loc)); +} + +// value of __cplusplus in C++2a/20 mode is not fixed yet along compilers. +// So here we use the feature test macro for `char8_t` itself. +#if defined(__cpp_char8_t) && __cpp_char8_t >= 201811L +// value of u8"" literal has been changed from char to char8_t and char8_t is +// NOT compatible to char +inline ::toml::basic_value +operator"" _toml(const char8_t* str, std::size_t len) +{ + ::toml::detail::location loc( + std::string("TOML literal encoded in a C++ code"), + std::vector(reinterpret_cast(str), + reinterpret_cast(str) + len)); + return literal_internal_impl(std::move(loc)); +} +#endif + +} // toml_literals +} // literals +} // toml +#endif//TOML11_LITERAL_HPP diff --git a/src/frontend/qt_sdl/toml/toml/macros.hpp b/src/frontend/qt_sdl/toml/toml/macros.hpp new file mode 100644 index 00000000..e8f91aec --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/macros.hpp @@ -0,0 +1,121 @@ +#ifndef TOML11_MACROS_HPP +#define TOML11_MACROS_HPP + +#define TOML11_STRINGIZE_AUX(x) #x +#define TOML11_STRINGIZE(x) TOML11_STRINGIZE_AUX(x) + +#define TOML11_CONCATENATE_AUX(x, y) x##y +#define TOML11_CONCATENATE(x, y) TOML11_CONCATENATE_AUX(x, y) + +// ============================================================================ +// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE + +#ifndef TOML11_WITHOUT_DEFINE_NON_INTRUSIVE + +// ---------------------------------------------------------------------------- +// TOML11_ARGS_SIZE + +#define TOML11_INDEX_RSEQ() \ + 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, \ + 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 +#define TOML11_ARGS_SIZE_IMPL(\ + ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7, ARG8, ARG9, ARG10, \ + ARG11, ARG12, ARG13, ARG14, ARG15, ARG16, ARG17, ARG18, ARG19, ARG20, \ + ARG21, ARG22, ARG23, ARG24, ARG25, ARG26, ARG27, ARG28, ARG29, ARG30, \ + ARG31, ARG32, N, ...) N +#define TOML11_ARGS_SIZE_AUX(...) TOML11_ARGS_SIZE_IMPL(__VA_ARGS__) +#define TOML11_ARGS_SIZE(...) TOML11_ARGS_SIZE_AUX(__VA_ARGS__, TOML11_INDEX_RSEQ()) + +// ---------------------------------------------------------------------------- +// TOML11_FOR_EACH_VA_ARGS + +#define TOML11_FOR_EACH_VA_ARGS_AUX_1( FUNCTOR, ARG1 ) FUNCTOR(ARG1) +#define TOML11_FOR_EACH_VA_ARGS_AUX_2( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_1( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_3( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_2( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_4( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_3( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_5( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_4( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_6( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_5( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_7( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_6( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_8( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_7( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_9( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_8( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_10(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_9( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_11(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_10(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_12(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_11(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_13(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_12(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_14(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_13(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_15(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_14(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_16(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_15(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_17(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_16(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_18(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_17(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_19(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_18(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_20(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_19(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_21(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_20(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_22(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_21(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_23(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_22(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_24(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_23(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_25(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_24(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_26(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_25(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_27(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_26(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_28(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_27(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_29(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_28(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_30(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_29(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_31(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_30(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_32(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_31(FUNCTOR, __VA_ARGS__) + +#define TOML11_FOR_EACH_VA_ARGS(FUNCTOR, ...)\ + TOML11_CONCATENATE(TOML11_FOR_EACH_VA_ARGS_AUX_, TOML11_ARGS_SIZE(__VA_ARGS__))(FUNCTOR, __VA_ARGS__) + +// ---------------------------------------------------------------------------- +// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE + +// use it in the following way. +// ```cpp +// namespace foo +// { +// struct Foo +// { +// std::string s; +// double d; +// int i; +// }; +// } // foo +// +// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(foo::Foo, s, d, i) +// ``` +// And then you can use `toml::find(file, "foo");` +// +#define TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE(VAR_NAME)\ + obj.VAR_NAME = toml::find(v, TOML11_STRINGIZE(VAR_NAME)); + +#define TOML11_ASSIGN_MEMBER_VARIABLE_TO_VALUE(VAR_NAME)\ + v[TOML11_STRINGIZE(VAR_NAME)] = obj.VAR_NAME; + +#define TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(NAME, ...)\ + namespace toml { \ + template<> \ + struct from \ + { \ + template class T, \ + template class A> \ + static NAME from_toml(const basic_value& v) \ + { \ + NAME obj; \ + TOML11_FOR_EACH_VA_ARGS(TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE, __VA_ARGS__) \ + return obj; \ + } \ + }; \ + template<> \ + struct into \ + { \ + static value into_toml(const NAME& obj) \ + { \ + ::toml::value v = ::toml::table{}; \ + TOML11_FOR_EACH_VA_ARGS(TOML11_ASSIGN_MEMBER_VARIABLE_TO_VALUE, __VA_ARGS__) \ + return v; \ + } \ + }; \ + } /* toml */ + +#endif// TOML11_WITHOUT_DEFINE_NON_INTRUSIVE + +#endif// TOML11_MACROS_HPP diff --git a/src/frontend/qt_sdl/toml/toml/parser.hpp b/src/frontend/qt_sdl/toml/toml/parser.hpp new file mode 100644 index 00000000..bfa5531f --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/parser.hpp @@ -0,0 +1,2416 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_PARSER_HPP +#define TOML11_PARSER_HPP +#include +#include +#include + +#include "combinator.hpp" +#include "lexer.hpp" +#include "region.hpp" +#include "result.hpp" +#include "types.hpp" +#include "value.hpp" + +#ifndef TOML11_DISABLE_STD_FILESYSTEM +#ifdef __cpp_lib_filesystem +#if __has_include() +#define TOML11_HAS_STD_FILESYSTEM +#include +#endif // has_include() +#endif // __cpp_lib_filesystem +#endif // TOML11_DISABLE_STD_FILESYSTEM + +namespace toml +{ +namespace detail +{ + +inline result, std::string> +parse_boolean(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_boolean::invoke(loc)) + { + const auto reg = token.unwrap(); + if (reg.str() == "true") {return ok(std::make_pair(true, reg));} + else if(reg.str() == "false") {return ok(std::make_pair(false, reg));} + else // internal error. + { + throw internal_error(format_underline( + "toml::parse_boolean: internal error", + {{source_location(reg), "invalid token"}}), + source_location(reg)); + } + } + loc.reset(first); //rollback + return err(format_underline("toml::parse_boolean: ", + {{source_location(loc), "the next token is not a boolean"}})); +} + +inline result, std::string> +parse_binary_integer(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_bin_int::invoke(loc)) + { + auto str = token.unwrap().str(); + assert(str.size() > 2); // minimum -> 0b1 + integer retval(0), base(1); + for(auto i(str.rbegin()), e(str.rend() - 2); i!=e; ++i) + { + if (*i == '1'){retval += base; base *= 2;} + else if(*i == '0'){base *= 2;} + else if(*i == '_'){/* do nothing. */} + else // internal error. + { + throw internal_error(format_underline( + "toml::parse_integer: internal error", + {{source_location(token.unwrap()), "invalid token"}}), + source_location(loc)); + } + } + return ok(std::make_pair(retval, token.unwrap())); + } + loc.reset(first); + return err(format_underline("toml::parse_binary_integer:", + {{source_location(loc), "the next token is not an integer"}})); +} + +inline result, std::string> +parse_octal_integer(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_oct_int::invoke(loc)) + { + auto str = token.unwrap().str(); + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + str.erase(str.begin()); str.erase(str.begin()); // remove `0o` prefix + + std::istringstream iss(str); + integer retval(0); + iss >> std::oct >> retval; + return ok(std::make_pair(retval, token.unwrap())); + } + loc.reset(first); + return err(format_underline("toml::parse_octal_integer:", + {{source_location(loc), "the next token is not an integer"}})); +} + +inline result, std::string> +parse_hexadecimal_integer(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_hex_int::invoke(loc)) + { + auto str = token.unwrap().str(); + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + str.erase(str.begin()); str.erase(str.begin()); // remove `0x` prefix + + std::istringstream iss(str); + integer retval(0); + iss >> std::hex >> retval; + return ok(std::make_pair(retval, token.unwrap())); + } + loc.reset(first); + return err(format_underline("toml::parse_hexadecimal_integer", + {{source_location(loc), "the next token is not an integer"}})); +} + +inline result, std::string> +parse_integer(location& loc) +{ + const auto first = loc.iter(); + if(first != loc.end() && *first == '0') + { + const auto second = std::next(first); + if(second == loc.end()) // the token is just zero. + { + loc.advance(); + return ok(std::make_pair(0, region(loc, first, second))); + } + + if(*second == 'b') {return parse_binary_integer (loc);} // 0b1100 + if(*second == 'o') {return parse_octal_integer (loc);} // 0o775 + if(*second == 'x') {return parse_hexadecimal_integer(loc);} // 0xC0FFEE + + if(std::isdigit(*second)) + { + return err(format_underline("toml::parse_integer: " + "leading zero in an Integer is not allowed.", + {{source_location(loc), "leading zero"}})); + } + else if(std::isalpha(*second)) + { + return err(format_underline("toml::parse_integer: " + "unknown integer prefix appeared.", + {{source_location(loc), "none of 0x, 0o, 0b"}})); + } + } + + if(const auto token = lex_dec_int::invoke(loc)) + { + auto str = token.unwrap().str(); + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + std::istringstream iss(str); + integer retval(0); + iss >> retval; + return ok(std::make_pair(retval, token.unwrap())); + } + loc.reset(first); + return err(format_underline("toml::parse_integer: ", + {{source_location(loc), "the next token is not an integer"}})); +} + +inline result, std::string> +parse_floating(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_float::invoke(loc)) + { + auto str = token.unwrap().str(); + if(str == "inf" || str == "+inf") + { + if(std::numeric_limits::has_infinity) + { + return ok(std::make_pair( + std::numeric_limits::infinity(), token.unwrap())); + } + else + { + throw std::domain_error("toml::parse_floating: inf value found" + " but the current environment does not support inf. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard."); + } + } + else if(str == "-inf") + { + if(std::numeric_limits::has_infinity) + { + return ok(std::make_pair( + -std::numeric_limits::infinity(), token.unwrap())); + } + else + { + throw std::domain_error("toml::parse_floating: inf value found" + " but the current environment does not support inf. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard."); + } + } + else if(str == "nan" || str == "+nan") + { + if(std::numeric_limits::has_quiet_NaN) + { + return ok(std::make_pair( + std::numeric_limits::quiet_NaN(), token.unwrap())); + } + else if(std::numeric_limits::has_signaling_NaN) + { + return ok(std::make_pair( + std::numeric_limits::signaling_NaN(), token.unwrap())); + } + else + { + throw std::domain_error("toml::parse_floating: NaN value found" + " but the current environment does not support NaN. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard."); + } + } + else if(str == "-nan") + { + if(std::numeric_limits::has_quiet_NaN) + { + return ok(std::make_pair( + -std::numeric_limits::quiet_NaN(), token.unwrap())); + } + else if(std::numeric_limits::has_signaling_NaN) + { + return ok(std::make_pair( + -std::numeric_limits::signaling_NaN(), token.unwrap())); + } + else + { + throw std::domain_error("toml::parse_floating: NaN value found" + " but the current environment does not support NaN. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard."); + } + } + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + std::istringstream iss(str); + floating v(0.0); + iss >> v; + return ok(std::make_pair(v, token.unwrap())); + } + loc.reset(first); + return err(format_underline("toml::parse_floating: ", + {{source_location(loc), "the next token is not a float"}})); +} + +inline std::string read_utf8_codepoint(const region& reg, const location& loc) +{ + const auto str = reg.str().substr(1); + std::uint_least32_t codepoint; + std::istringstream iss(str); + iss >> std::hex >> codepoint; + + const auto to_char = [](const std::uint_least32_t i) noexcept -> char { + const auto uc = static_cast(i); + return *reinterpret_cast(std::addressof(uc)); + }; + + std::string character; + if(codepoint < 0x80) // U+0000 ... U+0079 ; just an ASCII. + { + character += static_cast(codepoint); + } + else if(codepoint < 0x800) //U+0080 ... U+07FF + { + // 110yyyyx 10xxxxxx; 0x3f == 0b0011'1111 + character += to_char(0xC0| codepoint >> 6); + character += to_char(0x80|(codepoint & 0x3F)); + } + else if(codepoint < 0x10000) // U+0800...U+FFFF + { + if(0xD800 <= codepoint && codepoint <= 0xDFFF) + { + throw syntax_error(format_underline( + "toml::read_utf8_codepoint: codepoints in the range " + "[0xD800, 0xDFFF] are not valid UTF-8.", {{ + source_location(loc), "not a valid UTF-8 codepoint" + }}), source_location(loc)); + } + assert(codepoint < 0xD800 || 0xDFFF < codepoint); + // 1110yyyy 10yxxxxx 10xxxxxx + character += to_char(0xE0| codepoint >> 12); + character += to_char(0x80|(codepoint >> 6 & 0x3F)); + character += to_char(0x80|(codepoint & 0x3F)); + } + else if(codepoint < 0x110000) // U+010000 ... U+10FFFF + { + // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx + character += to_char(0xF0| codepoint >> 18); + character += to_char(0x80|(codepoint >> 12 & 0x3F)); + character += to_char(0x80|(codepoint >> 6 & 0x3F)); + character += to_char(0x80|(codepoint & 0x3F)); + } + else // out of UTF-8 region + { + throw syntax_error(format_underline("toml::read_utf8_codepoint:" + " input codepoint is too large.", + {{source_location(loc), "should be in [0x00..0x10FFFF]"}}), + source_location(loc)); + } + return character; +} + +inline result parse_escape_sequence(location& loc) +{ + const auto first = loc.iter(); + if(first == loc.end() || *first != '\\') + { + return err(format_underline("toml::parse_escape_sequence: ", {{ + source_location(loc), "the next token is not a backslash \"\\\""}})); + } + loc.advance(); + switch(*loc.iter()) + { + case '\\':{loc.advance(); return ok(std::string("\\"));} + case '"' :{loc.advance(); return ok(std::string("\""));} + case 'b' :{loc.advance(); return ok(std::string("\b"));} + case 't' :{loc.advance(); return ok(std::string("\t"));} + case 'n' :{loc.advance(); return ok(std::string("\n"));} + case 'f' :{loc.advance(); return ok(std::string("\f"));} + case 'r' :{loc.advance(); return ok(std::string("\r"));} + case 'u' : + { + if(const auto token = lex_escape_unicode_short::invoke(loc)) + { + return ok(read_utf8_codepoint(token.unwrap(), loc)); + } + else + { + return err(format_underline("parse_escape_sequence: " + "invalid token found in UTF-8 codepoint uXXXX.", + {{source_location(loc), "here"}})); + } + } + case 'U': + { + if(const auto token = lex_escape_unicode_long::invoke(loc)) + { + return ok(read_utf8_codepoint(token.unwrap(), loc)); + } + else + { + return err(format_underline("parse_escape_sequence: " + "invalid token found in UTF-8 codepoint Uxxxxxxxx", + {{source_location(loc), "here"}})); + } + } + } + + const auto msg = format_underline("parse_escape_sequence: " + "unknown escape sequence appeared.", {{source_location(loc), + "escape sequence is one of \\, \", b, t, n, f, r, uxxxx, Uxxxxxxxx"}}, + /* Hints = */{"if you want to write backslash as just one backslash, " + "use literal string like: regex = '<\\i\\c*\\s*>'"}); + loc.reset(first); + return err(msg); +} + +inline std::ptrdiff_t check_utf8_validity(const std::string& reg) +{ + location loc("tmp", reg); + const auto u8 = repeat::invoke(loc); + if(!u8 || loc.iter() != loc.end()) + { + const auto error_location = std::distance(loc.begin(), loc.iter()); + assert(0 <= error_location); + return error_location; + } + return -1; +} + +inline result, std::string> +parse_ml_basic_string(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_ml_basic_string::invoke(loc)) + { + auto inner_loc = loc; + inner_loc.reset(first); + + std::string retval; + retval.reserve(token.unwrap().size()); + + auto delim = lex_ml_basic_string_open::invoke(inner_loc); + if(!delim) + { + throw internal_error(format_underline( + "parse_ml_basic_string: invalid token", + {{source_location(inner_loc), "should be \"\"\""}}), + source_location(inner_loc)); + } + // immediate newline is ignored (if exists) + /* discard return value */ lex_newline::invoke(inner_loc); + + delim = none(); + while(!delim) + { + using lex_unescaped_seq = repeat< + either, unlimited>; + if(auto unescaped = lex_unescaped_seq::invoke(inner_loc)) + { + retval += unescaped.unwrap().str(); + } + if(auto escaped = parse_escape_sequence(inner_loc)) + { + retval += escaped.unwrap(); + } + if(auto esc_nl = lex_ml_basic_escaped_newline::invoke(inner_loc)) + { + // ignore newline after escape until next non-ws char + } + if(inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "parse_ml_basic_string: unexpected end of region", + {{source_location(inner_loc), "not sufficient token"}}), + source_location(inner_loc)); + } + delim = lex_ml_basic_string_close::invoke(inner_loc); + } + // `lex_ml_basic_string_close` allows 3 to 5 `"`s to allow 1 or 2 `"`s + // at just before the delimiter. Here, we need to attach `"`s at the + // end of the string body, if it exists. + // For detail, see the definition of `lex_ml_basic_string_close`. + assert(std::all_of(delim.unwrap().first(), delim.unwrap().last(), + [](const char c) noexcept {return c == '\"';})); + switch(delim.unwrap().size()) + { + case 3: {break;} + case 4: {retval += "\""; break;} + case 5: {retval += "\"\""; break;} + default: + { + throw internal_error(format_underline( + "parse_ml_basic_string: closing delimiter has invalid length", + {{source_location(inner_loc), "end of this"}}), + source_location(inner_loc)); + } + } + + const auto err_loc = check_utf8_validity(token.unwrap().str()); + if(err_loc == -1) + { + return ok(std::make_pair(toml::string(retval), token.unwrap())); + } + else + { + inner_loc.reset(first); + inner_loc.advance(err_loc); + throw syntax_error(format_underline( + "parse_ml_basic_string: invalid utf8 sequence found", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + } + else + { + loc.reset(first); + return err(format_underline("toml::parse_ml_basic_string: " + "the next token is not a valid multiline string", + {{source_location(loc), "here"}})); + } +} + +inline result, std::string> +parse_basic_string(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_basic_string::invoke(loc)) + { + auto inner_loc = loc; + inner_loc.reset(first); + + auto quot = lex_quotation_mark::invoke(inner_loc); + if(!quot) + { + throw internal_error(format_underline("parse_basic_string: " + "invalid token", {{source_location(inner_loc), "should be \""}}), + source_location(inner_loc)); + } + + std::string retval; + retval.reserve(token.unwrap().size()); + + quot = none(); + while(!quot) + { + using lex_unescaped_seq = repeat; + if(auto unescaped = lex_unescaped_seq::invoke(inner_loc)) + { + retval += unescaped.unwrap().str(); + } + if(auto escaped = parse_escape_sequence(inner_loc)) + { + retval += escaped.unwrap(); + } + if(inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "parse_basic_string: unexpected end of region", + {{source_location(inner_loc), "not sufficient token"}}), + source_location(inner_loc)); + } + quot = lex_quotation_mark::invoke(inner_loc); + } + + const auto err_loc = check_utf8_validity(token.unwrap().str()); + if(err_loc == -1) + { + return ok(std::make_pair(toml::string(retval), token.unwrap())); + } + else + { + inner_loc.reset(first); + inner_loc.advance(err_loc); + throw syntax_error(format_underline( + "parse_ml_basic_string: invalid utf8 sequence found", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + } + else + { + loc.reset(first); // rollback + return err(format_underline("toml::parse_basic_string: " + "the next token is not a valid string", + {{source_location(loc), "here"}})); + } +} + +inline result, std::string> +parse_ml_literal_string(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_ml_literal_string::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto open = lex_ml_literal_string_open::invoke(inner_loc); + if(!open) + { + throw internal_error(format_underline( + "parse_ml_literal_string: invalid token", + {{source_location(inner_loc), "should be '''"}}), + source_location(inner_loc)); + } + // immediate newline is ignored (if exists) + /* discard return value */ lex_newline::invoke(inner_loc); + + const auto body = lex_ml_literal_body::invoke(inner_loc); + + const auto close = lex_ml_literal_string_close::invoke(inner_loc); + if(!close) + { + throw internal_error(format_underline( + "parse_ml_literal_string: invalid token", + {{source_location(inner_loc), "should be '''"}}), + source_location(inner_loc)); + } + // `lex_ml_literal_string_close` allows 3 to 5 `'`s to allow 1 or 2 `'`s + // at just before the delimiter. Here, we need to attach `'`s at the + // end of the string body, if it exists. + // For detail, see the definition of `lex_ml_basic_string_close`. + + std::string retval = body.unwrap().str(); + assert(std::all_of(close.unwrap().first(), close.unwrap().last(), + [](const char c) noexcept {return c == '\'';})); + switch(close.unwrap().size()) + { + case 3: {break;} + case 4: {retval += "'"; break;} + case 5: {retval += "''"; break;} + default: + { + throw internal_error(format_underline( + "parse_ml_literal_string: closing delimiter has invalid length", + {{source_location(inner_loc), "end of this"}}), + source_location(inner_loc)); + } + } + + const auto err_loc = check_utf8_validity(token.unwrap().str()); + if(err_loc == -1) + { + return ok(std::make_pair(toml::string(retval, toml::string_t::literal), + token.unwrap())); + } + else + { + inner_loc.reset(first); + inner_loc.advance(err_loc); + throw syntax_error(format_underline( + "parse_ml_basic_string: invalid utf8 sequence found", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + } + else + { + loc.reset(first); // rollback + return err(format_underline("toml::parse_ml_literal_string: " + "the next token is not a valid multiline literal string", + {{source_location(loc), "here"}})); + } +} + +inline result, std::string> +parse_literal_string(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_literal_string::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto open = lex_apostrophe::invoke(inner_loc); + if(!open) + { + throw internal_error(format_underline( + "parse_literal_string: invalid token", + {{source_location(inner_loc), "should be '"}}), + source_location(inner_loc)); + } + + const auto body = repeat::invoke(inner_loc); + + const auto close = lex_apostrophe::invoke(inner_loc); + if(!close) + { + throw internal_error(format_underline( + "parse_literal_string: invalid token", + {{source_location(inner_loc), "should be '"}}), + source_location(inner_loc)); + } + + const auto err_loc = check_utf8_validity(token.unwrap().str()); + if(err_loc == -1) + { + return ok(std::make_pair( + toml::string(body.unwrap().str(), toml::string_t::literal), + token.unwrap())); + } + else + { + inner_loc.reset(first); + inner_loc.advance(err_loc); + throw syntax_error(format_underline( + "parse_ml_basic_string: invalid utf8 sequence found", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + } + else + { + loc.reset(first); // rollback + return err(format_underline("toml::parse_literal_string: " + "the next token is not a valid literal string", + {{source_location(loc), "here"}})); + } +} + +inline result, std::string> +parse_string(location& loc) +{ + if(loc.iter() != loc.end() && *(loc.iter()) == '"') + { + if(loc.iter() + 1 != loc.end() && *(loc.iter() + 1) == '"' && + loc.iter() + 2 != loc.end() && *(loc.iter() + 2) == '"') + { + return parse_ml_basic_string(loc); + } + else + { + return parse_basic_string(loc); + } + } + else if(loc.iter() != loc.end() && *(loc.iter()) == '\'') + { + if(loc.iter() + 1 != loc.end() && *(loc.iter() + 1) == '\'' && + loc.iter() + 2 != loc.end() && *(loc.iter() + 2) == '\'') + { + return parse_ml_literal_string(loc); + } + else + { + return parse_literal_string(loc); + } + } + return err(format_underline("toml::parse_string: ", + {{source_location(loc), "the next token is not a string"}})); +} + +inline result, std::string> +parse_local_date(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_local_date::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto y = lex_date_fullyear::invoke(inner_loc); + if(!y || inner_loc.iter() == inner_loc.end() || *inner_loc.iter() != '-') + { + throw internal_error(format_underline( + "toml::parse_inner_local_date: invalid year format", + {{source_location(inner_loc), "should be `-`"}}), + source_location(inner_loc)); + } + inner_loc.advance(); + const auto m = lex_date_month::invoke(inner_loc); + if(!m || inner_loc.iter() == inner_loc.end() || *inner_loc.iter() != '-') + { + throw internal_error(format_underline( + "toml::parse_local_date: invalid month format", + {{source_location(inner_loc), "should be `-`"}}), + source_location(inner_loc)); + } + inner_loc.advance(); + const auto d = lex_date_mday::invoke(inner_loc); + if(!d) + { + throw internal_error(format_underline( + "toml::parse_local_date: invalid day format", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + + const auto year = static_cast(from_string(y.unwrap().str(), 0)); + const auto month = static_cast(from_string(m.unwrap().str(), 0)); + const auto day = static_cast(from_string(d.unwrap().str(), 0)); + + // We briefly check whether the input date is valid or not. But here, we + // only check if the RFC3339 compliance. + // Actually there are several special date that does not exist, + // because of historical reasons, such as 1582/10/5-1582/10/14 (only in + // several countries). But here, we do not care about such a complicated + // rule. It makes the code complicated and there is only low probability + // that such a specific date is needed in practice. If someone need to + // validate date accurately, that means that the one need a specialized + // library for their purpose in a different layer. + { + const bool is_leap = (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)); + const auto max_day = (month == 2) ? (is_leap ? 29 : 28) : + ((month == 4 || month == 6 || month == 9 || month == 11) ? 30 : 31); + + if((month < 1 || 12 < month) || (day < 1 || max_day < day)) + { + throw syntax_error(format_underline("toml::parse_date: " + "invalid date: it does not conform RFC3339.", {{ + source_location(loc), "month should be 01-12, day should be" + " 01-28,29,30,31, depending on month/year." + }}), source_location(inner_loc)); + } + } + return ok(std::make_pair(local_date(year, static_cast(month - 1), day), + token.unwrap())); + } + else + { + loc.reset(first); + return err(format_underline("toml::parse_local_date: ", + {{source_location(loc), "the next token is not a local_date"}})); + } +} + +inline result, std::string> +parse_local_time(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_local_time::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto h = lex_time_hour::invoke(inner_loc); + if(!h || inner_loc.iter() == inner_loc.end() || *inner_loc.iter() != ':') + { + throw internal_error(format_underline( + "toml::parse_local_time: invalid year format", + {{source_location(inner_loc), "should be `:`"}}), + source_location(inner_loc)); + } + inner_loc.advance(); + const auto m = lex_time_minute::invoke(inner_loc); + if(!m || inner_loc.iter() == inner_loc.end() || *inner_loc.iter() != ':') + { + throw internal_error(format_underline( + "toml::parse_local_time: invalid month format", + {{source_location(inner_loc), "should be `:`"}}), + source_location(inner_loc)); + } + inner_loc.advance(); + const auto s = lex_time_second::invoke(inner_loc); + if(!s) + { + throw internal_error(format_underline( + "toml::parse_local_time: invalid second format", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + + const int hour = from_string(h.unwrap().str(), 0); + const int minute = from_string(m.unwrap().str(), 0); + const int second = from_string(s.unwrap().str(), 0); + + if((hour < 0 || 23 < hour) || (minute < 0 || 59 < minute) || + (second < 0 || 60 < second)) // it may be leap second + { + throw syntax_error(format_underline("toml::parse_time: " + "invalid time: it does not conform RFC3339.", {{ + source_location(loc), "hour should be 00-23, minute should be" + " 00-59, second should be 00-60 (depending on the leap" + " second rules.)"}}), source_location(inner_loc)); + } + + local_time time(hour, minute, second, 0, 0); + + const auto before_secfrac = inner_loc.iter(); + if(const auto secfrac = lex_time_secfrac::invoke(inner_loc)) + { + auto sf = secfrac.unwrap().str(); + sf.erase(sf.begin()); // sf.front() == '.' + switch(sf.size() % 3) + { + case 2: sf += '0'; break; + case 1: sf += "00"; break; + case 0: break; + default: break; + } + if(sf.size() >= 9) + { + time.millisecond = from_string(sf.substr(0, 3), 0u); + time.microsecond = from_string(sf.substr(3, 3), 0u); + time.nanosecond = from_string(sf.substr(6, 3), 0u); + } + else if(sf.size() >= 6) + { + time.millisecond = from_string(sf.substr(0, 3), 0u); + time.microsecond = from_string(sf.substr(3, 3), 0u); + } + else if(sf.size() >= 3) + { + time.millisecond = from_string(sf, 0u); + time.microsecond = 0u; + } + } + else + { + if(before_secfrac != inner_loc.iter()) + { + throw internal_error(format_underline( + "toml::parse_local_time: invalid subsecond format", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + } + return ok(std::make_pair(time, token.unwrap())); + } + else + { + loc.reset(first); + return err(format_underline("toml::parse_local_time: ", + {{source_location(loc), "the next token is not a local_time"}})); + } +} + +inline result, std::string> +parse_local_datetime(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_local_date_time::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + const auto date = parse_local_date(inner_loc); + if(!date || inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "toml::parse_local_datetime: invalid datetime format", + {{source_location(inner_loc), "date, not datetime"}}), + source_location(inner_loc)); + } + const char delim = *(inner_loc.iter()); + if(delim != 'T' && delim != 't' && delim != ' ') + { + throw internal_error(format_underline( + "toml::parse_local_datetime: invalid datetime format", + {{source_location(inner_loc), "should be `T` or ` ` (space)"}}), + source_location(inner_loc)); + } + inner_loc.advance(); + const auto time = parse_local_time(inner_loc); + if(!time) + { + throw internal_error(format_underline( + "toml::parse_local_datetime: invalid datetime format", + {{source_location(inner_loc), "invalid time format"}}), + source_location(inner_loc)); + } + return ok(std::make_pair( + local_datetime(date.unwrap().first, time.unwrap().first), + token.unwrap())); + } + else + { + loc.reset(first); + return err(format_underline("toml::parse_local_datetime: ", + {{source_location(loc), "the next token is not a local_datetime"}})); + } +} + +inline result, std::string> +parse_offset_datetime(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_offset_date_time::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + const auto datetime = parse_local_datetime(inner_loc); + if(!datetime || inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "toml::parse_offset_datetime: invalid datetime format", + {{source_location(inner_loc), "date, not datetime"}}), + source_location(inner_loc)); + } + time_offset offset(0, 0); + if(const auto ofs = lex_time_numoffset::invoke(inner_loc)) + { + const auto str = ofs.unwrap().str(); + + const auto hour = from_string(str.substr(1,2), 0); + const auto minute = from_string(str.substr(4,2), 0); + + if((hour < 0 || 23 < hour) || (minute < 0 || 59 < minute)) + { + throw syntax_error(format_underline("toml::parse_offset_datetime: " + "invalid offset: it does not conform RFC3339.", {{ + source_location(loc), "month should be 01-12, day should be" + " 01-28,29,30,31, depending on month/year." + }}), source_location(inner_loc)); + } + + if(str.front() == '+') + { + offset = time_offset(hour, minute); + } + else + { + offset = time_offset(-hour, -minute); + } + } + else if(*inner_loc.iter() != 'Z' && *inner_loc.iter() != 'z') + { + throw internal_error(format_underline( + "toml::parse_offset_datetime: invalid datetime format", + {{source_location(inner_loc), "should be `Z` or `+HH:MM`"}}), + source_location(inner_loc)); + } + return ok(std::make_pair(offset_datetime(datetime.unwrap().first, offset), + token.unwrap())); + } + else + { + loc.reset(first); + return err(format_underline("toml::parse_offset_datetime: ", + {{source_location(loc), "the next token is not a offset_datetime"}})); + } +} + +inline result, std::string> +parse_simple_key(location& loc) +{ + if(const auto bstr = parse_basic_string(loc)) + { + return ok(std::make_pair(bstr.unwrap().first.str, bstr.unwrap().second)); + } + if(const auto lstr = parse_literal_string(loc)) + { + return ok(std::make_pair(lstr.unwrap().first.str, lstr.unwrap().second)); + } + if(const auto bare = lex_unquoted_key::invoke(loc)) + { + const auto reg = bare.unwrap(); + return ok(std::make_pair(reg.str(), reg)); + } + return err(format_underline("toml::parse_simple_key: ", + {{source_location(loc), "the next token is not a simple key"}})); +} + +// dotted key become vector of keys +inline result, region>, std::string> +parse_key(location& loc) +{ + const auto first = loc.iter(); + // dotted key -> `foo.bar.baz` where several single keys are chained by + // dots. Whitespaces between keys and dots are allowed. + if(const auto token = lex_dotted_key::invoke(loc)) + { + const auto reg = token.unwrap(); + location inner_loc(loc.name(), reg.str()); + std::vector keys; + + while(inner_loc.iter() != inner_loc.end()) + { + lex_ws::invoke(inner_loc); + if(const auto k = parse_simple_key(inner_loc)) + { + keys.push_back(k.unwrap().first); + } + else + { + throw internal_error(format_underline( + "toml::detail::parse_key: dotted key contains invalid key", + {{source_location(inner_loc), k.unwrap_err()}}), + source_location(inner_loc)); + } + + lex_ws::invoke(inner_loc); + if(inner_loc.iter() == inner_loc.end()) + { + break; + } + else if(*inner_loc.iter() == '.') + { + inner_loc.advance(); // to skip `.` + } + else + { + throw internal_error(format_underline("toml::parse_key: " + "dotted key contains invalid key ", + {{source_location(inner_loc), "should be `.`"}}), + source_location(inner_loc)); + } + } + return ok(std::make_pair(keys, reg)); + } + loc.reset(first); + + // simple_key: a single (basic_string|literal_string|bare key) + if(const auto smpl = parse_simple_key(loc)) + { + return ok(std::make_pair(std::vector(1, smpl.unwrap().first), + smpl.unwrap().second)); + } + return err(format_underline("toml::parse_key: an invalid key appeared.", + {{source_location(loc), "is not a valid key"}}, { + "bare keys : non-empty strings composed only of [A-Za-z0-9_-].", + "quoted keys: same as \"basic strings\" or 'literal strings'.", + "dotted keys: sequence of bare or quoted keys joined with a dot." + })); +} + +// forward-decl to implement parse_array and parse_table +template +result parse_value(location&); + +template +result, std::string> +parse_array(location& loc) +{ + using value_type = Value; + using array_type = typename value_type::array_type; + + const auto first = loc.iter(); + if(loc.iter() == loc.end()) + { + return err("toml::parse_array: input is empty"); + } + if(*loc.iter() != '[') + { + return err("toml::parse_array: token is not an array"); + } + loc.advance(); + + using lex_ws_comment_newline = repeat< + either, unlimited>; + + array_type retval; + while(loc.iter() != loc.end()) + { + lex_ws_comment_newline::invoke(loc); // skip + + if(loc.iter() != loc.end() && *loc.iter() == ']') + { + loc.advance(); // skip ']' + return ok(std::make_pair(retval, + region(loc, first, loc.iter()))); + } + + if(auto val = parse_value(loc)) + { + // After TOML v1.0.0-rc.1, array becomes to be able to have values + // with different types. So here we will omit this by default. + // + // But some of the test-suite checks if the parser accepts a hetero- + // geneous arrays, so we keep this for a while. +#ifdef TOML11_DISALLOW_HETEROGENEOUS_ARRAYS + if(!retval.empty() && retval.front().type() != val.as_ok().type()) + { + auto array_start_loc = loc; + array_start_loc.reset(first); + + throw syntax_error(format_underline("toml::parse_array: " + "type of elements should be the same each other.", { + {source_location(array_start_loc), "array starts here"}, + { + retval.front().location(), + "value has type " + stringize(retval.front().type()) + }, + { + val.unwrap().location(), + "value has different type, " + stringize(val.unwrap().type()) + } + }), source_location(loc)); + } +#endif + retval.push_back(std::move(val.unwrap())); + } + else + { + auto array_start_loc = loc; + array_start_loc.reset(first); + + throw syntax_error(format_underline("toml::parse_array: " + "value having invalid format appeared in an array", { + {source_location(array_start_loc), "array starts here"}, + {source_location(loc), "it is not a valid value."} + }), source_location(loc)); + } + + using lex_array_separator = sequence, character<','>>; + const auto sp = lex_array_separator::invoke(loc); + if(!sp) + { + lex_ws_comment_newline::invoke(loc); + if(loc.iter() != loc.end() && *loc.iter() == ']') + { + loc.advance(); // skip ']' + return ok(std::make_pair(retval, + region(loc, first, loc.iter()))); + } + else + { + auto array_start_loc = loc; + array_start_loc.reset(first); + + throw syntax_error(format_underline("toml::parse_array:" + " missing array separator `,` after a value", { + {source_location(array_start_loc), "array starts here"}, + {source_location(loc), "should be `,`"} + }), source_location(loc)); + } + } + } + loc.reset(first); + throw syntax_error(format_underline("toml::parse_array: " + "array did not closed by `]`", + {{source_location(loc), "should be closed"}}), + source_location(loc)); +} + +template +result, region>, Value>, std::string> +parse_key_value_pair(location& loc) +{ + using value_type = Value; + + const auto first = loc.iter(); + auto key_reg = parse_key(loc); + if(!key_reg) + { + std::string msg = std::move(key_reg.unwrap_err()); + // if the next token is keyvalue-separator, it means that there are no + // key. then we need to show error as "empty key is not allowed". + if(const auto keyval_sep = lex_keyval_sep::invoke(loc)) + { + loc.reset(first); + msg = format_underline("toml::parse_key_value_pair: " + "empty key is not allowed.", + {{source_location(loc), "key expected before '='"}}); + } + return err(std::move(msg)); + } + + const auto kvsp = lex_keyval_sep::invoke(loc); + if(!kvsp) + { + std::string msg; + // if the line contains '=' after the invalid sequence, possibly the + // error is in the key (like, invalid character in bare key). + const auto line_end = std::find(loc.iter(), loc.end(), '\n'); + if(std::find(loc.iter(), line_end, '=') != line_end) + { + msg = format_underline("toml::parse_key_value_pair: " + "invalid format for key", + {{source_location(loc), "invalid character in key"}}, + {"Did you forget '.' to separate dotted-key?", + "Allowed characters for bare key are [0-9a-zA-Z_-]."}); + } + else // if not, the error is lack of key-value separator. + { + msg = format_underline("toml::parse_key_value_pair: " + "missing key-value separator `=`", + {{source_location(loc), "should be `=`"}}); + } + loc.reset(first); + return err(std::move(msg)); + } + + const auto after_kvsp = loc.iter(); // err msg + auto val = parse_value(loc); + if(!val) + { + std::string msg; + loc.reset(after_kvsp); + // check there is something not a comment/whitespace after `=` + if(sequence, maybe, lex_newline>::invoke(loc)) + { + loc.reset(after_kvsp); + msg = format_underline("toml::parse_key_value_pair: " + "missing value after key-value separator '='", + {{source_location(loc), "expected value, but got nothing"}}); + } + else // there is something not a comment/whitespace, so invalid format. + { + msg = std::move(val.unwrap_err()); + } + loc.reset(first); + return err(msg); + } + return ok(std::make_pair(std::move(key_reg.unwrap()), + std::move(val.unwrap()))); +} + +// for error messages. +template +std::string format_dotted_keys(InputIterator first, const InputIterator last) +{ + static_assert(std::is_same::value_type>::value,""); + + std::string retval(*first++); + for(; first != last; ++first) + { + retval += '.'; + retval += *first; + } + return retval; +} + +// forward decl for is_valid_forward_table_definition +result, region>, std::string> +parse_table_key(location& loc); +template +result, std::string> +parse_inline_table(location& loc); + +// The following toml file is allowed. +// ```toml +// [a.b.c] # here, table `a` has element `b`. +// foo = "bar" +// [a] # merge a = {baz = "qux"} to a = {b = {...}} +// baz = "qux" +// ``` +// But the following is not allowed. +// ```toml +// [a] +// b.c.foo = "bar" +// [a] # error! the same table [a] defined! +// baz = "qux" +// ``` +// The following is neither allowed. +// ```toml +// a = { b.c.foo = "bar"} +// [a] # error! the same table [a] defined! +// baz = "qux" +// ``` +// Here, it parses region of `tab->at(k)` as a table key and check the depth +// of the key. If the key region points deeper node, it would be allowed. +// Otherwise, the key points the same node. It would be rejected. +template +bool is_valid_forward_table_definition(const Value& fwd, const Value& inserting, + Iterator key_first, Iterator key_curr, Iterator key_last) +{ + // ------------------------------------------------------------------------ + // check type of the value to be inserted/merged + + std::string inserting_reg = ""; + if(const auto ptr = detail::get_region(inserting)) + { + inserting_reg = ptr->str(); + } + location inserting_def("internal", std::move(inserting_reg)); + if(const auto inlinetable = parse_inline_table(inserting_def)) + { + // check if we are overwriting existing table. + // ```toml + // # NG + // a.b = 42 + // a = {d = 3.14} + // ``` + // Inserting an inline table to a existing super-table is not allowed in + // any case. If we found it, we can reject it without further checking. + return false; + } + + // Valid and invalid cases when inserting to the [a.b] table: + // + // ## Invalid + // + // ```toml + // # invalid + // [a] + // b.c.d = "foo" + // [a.b] # a.b is already defined and closed + // d = "bar" + // ``` + // ```toml + // # invalid + // a = {b.c.d = "foo"} + // [a.b] # a is already defined and inline table is closed + // d = "bar" + // ``` + // ```toml + // # invalid + // a.b.c.d = "foo" + // [a.b] # a.b is already defined and dotted-key table is closed + // d = "bar" + // ``` + // + // ## Valid + // + // ```toml + // # OK. a.b is defined, but is *overwritable* + // [a.b.c] + // d = "foo" + // [a.b] + // d = "bar" + // ``` + // ```toml + // # OK. a.b is defined, but is *overwritable* + // [a] + // b.c.d = "foo" + // b.e = "bar" + // ``` + + // ------------------------------------------------------------------------ + // check table defined before + + std::string internal = ""; + if(const auto ptr = detail::get_region(fwd)) + { + internal = ptr->str(); + } + location def("internal", std::move(internal)); + if(const auto tabkeys = parse_table_key(def)) // [table.key] + { + // table keys always contains all the nodes from the root. + const auto& tks = tabkeys.unwrap().first; + if(std::size_t(std::distance(key_first, key_last)) == tks.size() && + std::equal(tks.begin(), tks.end(), key_first)) + { + // the keys are equivalent. it is not allowed. + return false; + } + // the keys are not equivalent. it is allowed. + return true; + } + if(const auto dotkeys = parse_key(def)) // a.b.c = "foo" + { + // consider the following case. + // [a] + // b.c = {d = 42} + // [a.b.c] + // e = 2.71 + // this defines the table [a.b.c] twice. no? + if(const auto reopening_dotkey_by_table = parse_table_key(inserting_def)) + { + // re-opening a dotkey-defined table by a table is invalid. + // only dotkey can append a key-val. Like: + // ```toml + // a.b.c = "foo" + // a.b.d = "bar" # OK. reopen `a.b` by dotkey + // [a.b] + // e = "bar" # Invalid. re-opening `a.b` by [a.b] is not allowed. + // ``` + return false; + } + + // a dotted key starts from the node representing a table in which the + // dotted key belongs to. + const auto& dks = dotkeys.unwrap().first; + if(std::size_t(std::distance(key_curr, key_last)) == dks.size() && + std::equal(dks.begin(), dks.end(), key_curr)) + { + // the keys are equivalent. it is not allowed. + return false; + } + // the keys are not equivalent. it is allowed. + return true; + } + return false; +} + +template +result +insert_nested_key(typename Value::table_type& root, const Value& v, + InputIterator iter, const InputIterator last, + region key_reg, + const bool is_array_of_table = false) +{ + static_assert(std::is_same::value_type>::value,""); + + using value_type = Value; + using table_type = typename value_type::table_type; + using array_type = typename value_type::array_type; + + const auto first = iter; + assert(iter != last); + + table_type* tab = std::addressof(root); + for(; iter != last; ++iter) // search recursively + { + const key& k = *iter; + if(std::next(iter) == last) // k is the last key + { + // XXX if the value is array-of-tables, there can be several + // tables that are in the same array. in that case, we need to + // find the last element and insert it to there. + if(is_array_of_table) + { + if(tab->count(k) == 1) // there is already an array of table + { + if(tab->at(k).is_table()) + { + // show special err msg for conflicting table + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: array of table (\"", + format_dotted_keys(first, last), + "\") cannot be defined"), { + {tab->at(k).location(), "table already defined"}, + {v.location(), "this conflicts with the previous table"} + }), v.location()); + } + else if(!(tab->at(k).is_array())) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: array of table (\"", + format_dotted_keys(first, last), "\") collides with" + " existing value"), { + {tab->at(k).location(), + concat_to_string("this ", tab->at(k).type(), + " value already exists")}, + {v.location(), + "while inserting this array-of-tables"} + }), v.location()); + } + // the above if-else-if checks tab->at(k) is an array + auto& a = tab->at(k).as_array(); + // If table element is defined as [[array_of_tables]], it + // cannot be an empty array. If an array of tables is + // defined as `aot = []`, it cannot be appended. + if(a.empty() || !(a.front().is_table())) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: array of table (\"", + format_dotted_keys(first, last), "\") collides with" + " existing value"), { + {tab->at(k).location(), + concat_to_string("this ", tab->at(k).type(), + " value already exists")}, + {v.location(), + "while inserting this array-of-tables"} + }), v.location()); + } + // avoid conflicting array of table like the following. + // ```toml + // a = [{b = 42}] # define a as an array of *inline* tables + // [[a]] # a is an array of *multi-line* tables + // b = 54 + // ``` + // Here, from the type information, these cannot be detected + // because inline table is also a table. + // But toml v0.5.0 explicitly says it is invalid. The above + // array-of-tables has a static size and appending to the + // array is invalid. + // In this library, multi-line table value has a region + // that points to the key of the table (e.g. [[a]]). By + // comparing the first two letters in key, we can detect + // the array-of-table is inline or multiline. + if(const auto ptr = detail::get_region(a.front())) + { + if(ptr->str().substr(0,2) != "[[") + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: array of table (\"", + format_dotted_keys(first, last), "\") collides " + "with existing array-of-tables"), { + {tab->at(k).location(), + concat_to_string("this ", tab->at(k).type(), + " value has static size")}, + {v.location(), + "appending it to the statically sized array"} + }), v.location()); + } + } + a.push_back(v); + return ok(true); + } + else // if not, we need to create the array of table + { + // XXX: Consider the following array of tables. + // ```toml + // # This is a comment. + // [[aot]] + // foo = "bar" + // ``` + // Here, the comment is for `aot`. But here, actually two + // values are defined. An array that contains tables, named + // `aot`, and the 0th element of the `aot`, `{foo = "bar"}`. + // Those two are different from each other. But both of them + // points to the same portion of the TOML file, `[[aot]]`, + // so `key_reg.comments()` returns `# This is a comment`. + // If it is assigned as a comment of `aot` defined here, the + // comment will be duplicated. Both the `aot` itself and + // the 0-th element will have the same comment. This causes + // "duplication of the same comments" bug when the data is + // serialized. + // Next, consider the following. + // ```toml + // # comment 1 + // aot = [ + // # comment 2 + // {foo = "bar"}, + // ] + // ``` + // In this case, we can distinguish those two comments. So + // here we need to add "comment 1" to the `aot` and + // "comment 2" to the 0th element of that. + // To distinguish those two, we check the key region. + std::vector comments{/* empty by default */}; + if(key_reg.str().substr(0, 2) != "[[") + { + comments = key_reg.comments(); + } + value_type aot(array_type(1, v), key_reg, std::move(comments)); + tab->insert(std::make_pair(k, aot)); + return ok(true); + } + } // end if(array of table) + + if(tab->count(k) == 1) + { + if(tab->at(k).is_table() && v.is_table()) + { + if(!is_valid_forward_table_definition( + tab->at(k), v, first, iter, last)) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: table (\"", + format_dotted_keys(first, last), + "\") already exists."), { + {tab->at(k).location(), "table already exists here"}, + {v.location(), "table defined twice"} + }), v.location()); + } + // to allow the following toml file. + // [a.b.c] + // d = 42 + // [a] + // e = 2.71 + auto& t = tab->at(k).as_table(); + for(const auto& kv : v.as_table()) + { + if(tab->at(k).contains(kv.first)) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: value (\"", + format_dotted_keys(first, last), + "\") already exists."), { + {t.at(kv.first).location(), "already exists here"}, + {v.location(), "this defined twice"} + }), v.location()); + } + t[kv.first] = kv.second; + } + detail::change_region(tab->at(k), key_reg); + return ok(true); + } + else if(v.is_table() && + tab->at(k).is_array() && + tab->at(k).as_array().size() > 0 && + tab->at(k).as_array().front().is_table()) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: array of tables (\"", + format_dotted_keys(first, last), "\") already exists."), { + {tab->at(k).location(), "array of tables defined here"}, + {v.location(), "table conflicts with the previous array of table"} + }), v.location()); + } + else + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: value (\"", + format_dotted_keys(first, last), "\") already exists."), { + {tab->at(k).location(), "value already exists here"}, + {v.location(), "value defined twice"} + }), v.location()); + } + } + tab->insert(std::make_pair(k, v)); + return ok(true); + } + else // k is not the last one, we should insert recursively + { + // if there is no corresponding value, insert it first. + // related: you don't need to write + // # [x] + // # [x.y] + // to write + // [x.y.z] + if(tab->count(k) == 0) + { + // a table that is defined implicitly doesn't have any comments. + (*tab)[k] = value_type(table_type{}, key_reg, {/*no comment*/}); + } + + // type checking... + if(tab->at(k).is_table()) + { + // According to toml-lang/toml:36d3091b3 "Clarify that inline + // tables are immutable", check if it adds key-value pair to an + // inline table. + if(const auto* ptr = get_region(tab->at(k))) + { + // here, if the value is a (multi-line) table, the region + // should be something like `[table-name]`. + if(ptr->front() == '{') + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: inserting to an inline table (", + format_dotted_keys(first, std::next(iter)), + ") but inline tables are immutable"), { + {tab->at(k).location(), "inline tables are immutable"}, + {v.location(), "inserting this"} + }), v.location()); + } + } + tab = std::addressof((*tab)[k].as_table()); + } + else if(tab->at(k).is_array()) // inserting to array-of-tables? + { + auto& a = (*tab)[k].as_array(); + if(!a.back().is_table()) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: target (", + format_dotted_keys(first, std::next(iter)), + ") is neither table nor an array of tables"), { + {a.back().location(), concat_to_string( + "actual type is ", a.back().type())}, + {v.location(), "inserting this"} + }), v.location()); + } + tab = std::addressof(a.back().as_table()); + } + else + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: target (", + format_dotted_keys(first, std::next(iter)), + ") is neither table nor an array of tables"), { + {tab->at(k).location(), concat_to_string( + "actual type is ", tab->at(k).type())}, + {v.location(), "inserting this"} + }), v.location()); + } + } + } + return err(std::string("toml::detail::insert_nested_key: never reach here")); +} + +template +result, std::string> +parse_inline_table(location& loc) +{ + using value_type = Value; + using table_type = typename value_type::table_type; + + const auto first = loc.iter(); + table_type retval; + if(!(loc.iter() != loc.end() && *loc.iter() == '{')) + { + return err(format_underline("toml::parse_inline_table: ", + {{source_location(loc), "the next token is not an inline table"}})); + } + loc.advance(); + + // check if the inline table is an empty table = { } + maybe::invoke(loc); + if(loc.iter() != loc.end() && *loc.iter() == '}') + { + loc.advance(); // skip `}` + return ok(std::make_pair(retval, region(loc, first, loc.iter()))); + } + + // it starts from "{". it should be formatted as inline-table + while(loc.iter() != loc.end()) + { + const auto kv_r = parse_key_value_pair(loc); + if(!kv_r) + { + return err(kv_r.unwrap_err()); + } + + const auto& kvpair = kv_r.unwrap(); + const std::vector& keys = kvpair.first.first; + const auto& key_reg = kvpair.first.second; + const value_type& val = kvpair.second; + + const auto inserted = + insert_nested_key(retval, val, keys.begin(), keys.end(), key_reg); + if(!inserted) + { + throw internal_error("toml::parse_inline_table: " + "failed to insert value into table: " + inserted.unwrap_err(), + source_location(loc)); + } + + using lex_table_separator = sequence, character<','>>; + const auto sp = lex_table_separator::invoke(loc); + + if(!sp) + { + maybe::invoke(loc); + + if(loc.iter() == loc.end()) + { + throw syntax_error(format_underline( + "toml::parse_inline_table: missing table separator `}` ", + {{source_location(loc), "should be `}`"}}), + source_location(loc)); + } + else if(*loc.iter() == '}') + { + loc.advance(); // skip `}` + return ok(std::make_pair( + retval, region(loc, first, loc.iter()))); + } + else if(*loc.iter() == '#' || *loc.iter() == '\r' || *loc.iter() == '\n') + { + throw syntax_error(format_underline( + "toml::parse_inline_table: missing curly brace `}`", + {{source_location(loc), "should be `}`"}}), + source_location(loc)); + } + else + { + throw syntax_error(format_underline( + "toml::parse_inline_table: missing table separator `,` ", + {{source_location(loc), "should be `,`"}}), + source_location(loc)); + } + } + else // `,` is found + { + maybe::invoke(loc); + if(loc.iter() != loc.end() && *loc.iter() == '}') + { + throw syntax_error(format_underline( + "toml::parse_inline_table: trailing comma is not allowed in" + " an inline table", + {{source_location(loc), "should be `}`"}}), + source_location(loc)); + } + } + } + loc.reset(first); + throw syntax_error(format_underline("toml::parse_inline_table: " + "inline table did not closed by `}`", + {{source_location(loc), "should be closed"}}), + source_location(loc)); +} + +inline result guess_number_type(const location& l) +{ + // This function tries to find some (common) mistakes by checking characters + // that follows the last character of a value. But it is often difficult + // because some non-newline characters can appear after a value. E.g. + // spaces, tabs, commas (in an array or inline table), closing brackets + // (of an array or inline table), comment-sign (#). Since this function + // does not parse further, those characters are always allowed to be there. + location loc = l; + + if(lex_offset_date_time::invoke(loc)) {return ok(value_t::offset_datetime);} + loc.reset(l.iter()); + + if(lex_local_date_time::invoke(loc)) + { + // bad offset may appear after this. + if(loc.iter() != loc.end() && (*loc.iter() == '+' || *loc.iter() == '-' + || *loc.iter() == 'Z' || *loc.iter() == 'z')) + { + return err(format_underline("bad offset: should be [+-]HH:MM or Z", + {{source_location(loc), "[+-]HH:MM or Z"}}, + {"pass: +09:00, -05:30", "fail: +9:00, -5:30"})); + } + return ok(value_t::local_datetime); + } + loc.reset(l.iter()); + + if(lex_local_date::invoke(loc)) + { + // bad time may appear after this. + // A space is allowed as a delimiter between local time. But there are + // both cases in which a space becomes valid or invalid. + // - invalid: 2019-06-16 7:00:00 + // - valid : 2019-06-16 07:00:00 + if(loc.iter() != loc.end()) + { + const auto c = *loc.iter(); + if(c == 'T' || c == 't') + { + return err(format_underline("bad time: should be HH:MM:SS.subsec", + {{source_location(loc), "HH:MM:SS.subsec"}}, + {"pass: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", + "fail: 1979-05-27T7:32:00, 1979-05-27 17:32"})); + } + if('0' <= c && c <= '9') + { + return err(format_underline("bad time: missing T", + {{source_location(loc), "T or space required here"}}, + {"pass: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", + "fail: 1979-05-27T7:32:00, 1979-05-27 7:32"})); + } + if(c == ' ' && std::next(loc.iter()) != loc.end() && + ('0' <= *std::next(loc.iter()) && *std::next(loc.iter())<= '9')) + { + loc.advance(); + return err(format_underline("bad time: should be HH:MM:SS.subsec", + {{source_location(loc), "HH:MM:SS.subsec"}}, + {"pass: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", + "fail: 1979-05-27T7:32:00, 1979-05-27 7:32"})); + } + } + return ok(value_t::local_date); + } + loc.reset(l.iter()); + + if(lex_local_time::invoke(loc)) {return ok(value_t::local_time);} + loc.reset(l.iter()); + + if(lex_float::invoke(loc)) + { + if(loc.iter() != loc.end() && *loc.iter() == '_') + { + return err(format_underline("bad float: `_` should be surrounded by digits", + {{source_location(loc), "here"}}, + {"pass: +1.0, -2e-2, 3.141_592_653_589, inf, nan", + "fail: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); + } + return ok(value_t::floating); + } + loc.reset(l.iter()); + + if(lex_integer::invoke(loc)) + { + if(loc.iter() != loc.end()) + { + const auto c = *loc.iter(); + if(c == '_') + { + return err(format_underline("bad integer: `_` should be surrounded by digits", + {{source_location(loc), "here"}}, + {"pass: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", + "fail: 1__000, 0123"})); + } + if('0' <= c && c <= '9') + { + // leading zero. point '0' + loc.retrace(); + return err(format_underline("bad integer: leading zero", + {{source_location(loc), "here"}}, + {"pass: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", + "fail: 1__000, 0123"})); + } + if(c == ':' || c == '-') + { + return err(format_underline("bad datetime: invalid format", + {{source_location(loc), "here"}}, + {"pass: 1979-05-27T07:32:00-07:00, 1979-05-27 07:32:00.999999Z", + "fail: 1979-05-27T7:32:00-7:00, 1979-05-27 7:32-00:30"})); + } + if(c == '.' || c == 'e' || c == 'E') + { + return err(format_underline("bad float: invalid format", + {{source_location(loc), "here"}}, + {"pass: +1.0, -2e-2, 3.141_592_653_589, inf, nan", + "fail: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); + } + } + return ok(value_t::integer); + } + if(loc.iter() != loc.end() && *loc.iter() == '.') + { + return err(format_underline("bad float: invalid format", + {{source_location(loc), "integer part required before this"}}, + {"pass: +1.0, -2e-2, 3.141_592_653_589, inf, nan", + "fail: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); + } + if(loc.iter() != loc.end() && *loc.iter() == '_') + { + return err(format_underline("bad number: `_` should be surrounded by digits", + {{source_location(loc), "`_` is not surrounded by digits"}}, + {"pass: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", + "fail: 1__000, 0123"})); + } + return err(format_underline("bad format: unknown value appeared", + {{source_location(loc), "here"}})); +} + +inline result guess_value_type(const location& loc) +{ + switch(*loc.iter()) + { + case '"' : {return ok(value_t::string); } + case '\'': {return ok(value_t::string); } + case 't' : {return ok(value_t::boolean); } + case 'f' : {return ok(value_t::boolean); } + case '[' : {return ok(value_t::array); } + case '{' : {return ok(value_t::table); } + case 'i' : {return ok(value_t::floating);} // inf. + case 'n' : {return ok(value_t::floating);} // nan. + default : {return guess_number_type(loc);} + } +} + +template +result +parse_value_helper(result, std::string> rslt) +{ + if(rslt.is_ok()) + { + auto comments = rslt.as_ok().second.comments(); + return ok(Value(std::move(rslt.as_ok()), std::move(comments))); + } + else + { + return err(std::move(rslt.as_err())); + } +} + +template +result parse_value(location& loc) +{ + const auto first = loc.iter(); + if(first == loc.end()) + { + return err(format_underline("toml::parse_value: input is empty", + {{source_location(loc), ""}})); + } + + const auto type = guess_value_type(loc); + if(!type) + { + return err(type.unwrap_err()); + } + + switch(type.unwrap()) + { + case value_t::boolean : {return parse_value_helper(parse_boolean(loc) );} + case value_t::integer : {return parse_value_helper(parse_integer(loc) );} + case value_t::floating : {return parse_value_helper(parse_floating(loc) );} + case value_t::string : {return parse_value_helper(parse_string(loc) );} + case value_t::offset_datetime: {return parse_value_helper(parse_offset_datetime(loc) );} + case value_t::local_datetime : {return parse_value_helper(parse_local_datetime(loc) );} + case value_t::local_date : {return parse_value_helper(parse_local_date(loc) );} + case value_t::local_time : {return parse_value_helper(parse_local_time(loc) );} + case value_t::array : {return parse_value_helper(parse_array(loc) );} + case value_t::table : {return parse_value_helper(parse_inline_table(loc));} + default: + { + const auto msg = format_underline("toml::parse_value: " + "unknown token appeared", {{source_location(loc), "unknown"}}); + loc.reset(first); + return err(msg); + } + } +} + +inline result, region>, std::string> +parse_table_key(location& loc) +{ + if(auto token = lex_std_table::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto open = lex_std_table_open::invoke(inner_loc); + if(!open || inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "toml::parse_table_key: no `[`", + {{source_location(inner_loc), "should be `[`"}}), + source_location(inner_loc)); + } + // to skip [ a . b . c ] + // ^----------- this whitespace + lex_ws::invoke(inner_loc); + const auto keys = parse_key(inner_loc); + if(!keys) + { + throw internal_error(format_underline( + "toml::parse_table_key: invalid key", + {{source_location(inner_loc), "not key"}}), + source_location(inner_loc)); + } + // to skip [ a . b . c ] + // ^-- this whitespace + lex_ws::invoke(inner_loc); + const auto close = lex_std_table_close::invoke(inner_loc); + if(!close) + { + throw internal_error(format_underline( + "toml::parse_table_key: no `]`", + {{source_location(inner_loc), "should be `]`"}}), + source_location(inner_loc)); + } + + // after [table.key], newline or EOF(empty table) required. + if(loc.iter() != loc.end()) + { + using lex_newline_after_table_key = + sequence, maybe, lex_newline>; + const auto nl = lex_newline_after_table_key::invoke(loc); + if(!nl) + { + throw syntax_error(format_underline( + "toml::parse_table_key: newline required after [table.key]", + {{source_location(loc), "expected newline"}}), + source_location(loc)); + } + } + return ok(std::make_pair(keys.unwrap().first, token.unwrap())); + } + else + { + return err(format_underline("toml::parse_table_key: " + "not a valid table key", {{source_location(loc), "here"}})); + } +} + +inline result, region>, std::string> +parse_array_table_key(location& loc) +{ + if(auto token = lex_array_table::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto open = lex_array_table_open::invoke(inner_loc); + if(!open || inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "toml::parse_array_table_key: no `[[`", + {{source_location(inner_loc), "should be `[[`"}}), + source_location(inner_loc)); + } + lex_ws::invoke(inner_loc); + const auto keys = parse_key(inner_loc); + if(!keys) + { + throw internal_error(format_underline( + "toml::parse_array_table_key: invalid key", + {{source_location(inner_loc), "not a key"}}), + source_location(inner_loc)); + } + lex_ws::invoke(inner_loc); + const auto close = lex_array_table_close::invoke(inner_loc); + if(!close) + { + throw internal_error(format_underline( + "toml::parse_table_key: no `]]`", + {{source_location(inner_loc), "should be `]]`"}}), + source_location(inner_loc)); + } + + // after [[table.key]], newline or EOF(empty table) required. + if(loc.iter() != loc.end()) + { + using lex_newline_after_table_key = + sequence, maybe, lex_newline>; + const auto nl = lex_newline_after_table_key::invoke(loc); + if(!nl) + { + throw syntax_error(format_underline("toml::" + "parse_array_table_key: newline required after [[table.key]]", + {{source_location(loc), "expected newline"}}), + source_location(loc)); + } + } + return ok(std::make_pair(keys.unwrap().first, token.unwrap())); + } + else + { + return err(format_underline("toml::parse_array_table_key: " + "not a valid table key", {{source_location(loc), "here"}})); + } +} + +// parse table body (key-value pairs until the iter hits the next [tablekey]) +template +result +parse_ml_table(location& loc) +{ + using value_type = Value; + using table_type = typename value_type::table_type; + + const auto first = loc.iter(); + if(first == loc.end()) + { + return ok(table_type{}); + } + + // XXX at lest one newline is needed. + using skip_line = repeat< + sequence, maybe, lex_newline>, at_least<1>>; + skip_line::invoke(loc); + lex_ws::invoke(loc); + + table_type tab; + while(loc.iter() != loc.end()) + { + lex_ws::invoke(loc); + const auto before = loc.iter(); + if(const auto tmp = parse_array_table_key(loc)) // next table found + { + loc.reset(before); + return ok(tab); + } + if(const auto tmp = parse_table_key(loc)) // next table found + { + loc.reset(before); + return ok(tab); + } + + if(const auto kv = parse_key_value_pair(loc)) + { + const auto& kvpair = kv.unwrap(); + const std::vector& keys = kvpair.first.first; + const auto& key_reg = kvpair.first.second; + const value_type& val = kvpair.second; + const auto inserted = + insert_nested_key(tab, val, keys.begin(), keys.end(), key_reg); + if(!inserted) + { + return err(inserted.unwrap_err()); + } + } + else + { + return err(kv.unwrap_err()); + } + + // comment lines are skipped by the above function call. + // However, since the `skip_line` requires at least 1 newline, it fails + // if the file ends with ws and/or comment without newline. + // `skip_line` matches `ws? + comment? + newline`, not `ws` or `comment` + // itself. To skip the last ws and/or comment, call lexers. + // It does not matter if these fails, so the return value is discarded. + lex_ws::invoke(loc); + lex_comment::invoke(loc); + + // skip_line is (whitespace? comment? newline)_{1,}. multiple empty lines + // and comments after the last key-value pairs are allowed. + const auto newline = skip_line::invoke(loc); + if(!newline && loc.iter() != loc.end()) + { + const auto before2 = loc.iter(); + lex_ws::invoke(loc); // skip whitespace + const auto msg = format_underline("toml::parse_table: " + "invalid line format", {{source_location(loc), concat_to_string( + "expected newline, but got '", show_char(*loc.iter()), "'.")}}); + loc.reset(before2); + return err(msg); + } + + // the skip_lines only matches with lines that includes newline. + // to skip the last line that includes comment and/or whitespace + // but no newline, call them one more time. + lex_ws::invoke(loc); + lex_comment::invoke(loc); + } + return ok(tab); +} + +template +result parse_toml_file(location& loc) +{ + using value_type = Value; + using table_type = typename value_type::table_type; + + const auto first = loc.iter(); + if(first == loc.end()) + { + // For empty files, return an empty table with an empty region (zero-length). + // Without the region, error messages would miss the filename. + return ok(value_type(table_type{}, region(loc, first, first), {})); + } + + // put the first line as a region of a file + // Here first != loc.end(), so taking std::next is okay + const region file(loc, first, std::next(loc.iter())); + + // The first successive comments that are separated from the first value + // by an empty line are for a file itself. + // ```toml + // # this is a comment for a file. + // + // key = "the first value" + // ``` + // ```toml + // # this is a comment for "the first value". + // key = "the first value" + // ``` + std::vector comments; + using lex_first_comments = sequence< + repeat, lex_comment, lex_newline>, at_least<1>>, + sequence, lex_newline> + >; + if(const auto token = lex_first_comments::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + while(inner_loc.iter() != inner_loc.end()) + { + maybe::invoke(inner_loc); // remove ws if exists + if(lex_newline::invoke(inner_loc)) + { + assert(inner_loc.iter() == inner_loc.end()); + break; // empty line found. + } + auto com = lex_comment::invoke(inner_loc).unwrap().str(); + com.erase(com.begin()); // remove # sign + comments.push_back(std::move(com)); + lex_newline::invoke(inner_loc); + } + } + + table_type data; + // root object is also a table, but without [tablename] + if(const auto tab = parse_ml_table(loc)) + { + data = std::move(tab.unwrap()); + } + else // failed (empty table is regarded as success in parse_ml_table) + { + return err(tab.unwrap_err()); + } + while(loc.iter() != loc.end()) + { + // here, the region of [table] is regarded as the table-key because + // the table body is normally too big and it is not so informative + // if the first key-value pair of the table is shown in the error + // message. + if(const auto tabkey = parse_array_table_key(loc)) + { + const auto tab = parse_ml_table(loc); + if(!tab){return err(tab.unwrap_err());} + + const auto& tk = tabkey.unwrap(); + const auto& keys = tk.first; + const auto& reg = tk.second; + + const auto inserted = insert_nested_key(data, + value_type(tab.unwrap(), reg, reg.comments()), + keys.begin(), keys.end(), reg, + /*is_array_of_table=*/ true); + if(!inserted) {return err(inserted.unwrap_err());} + + continue; + } + if(const auto tabkey = parse_table_key(loc)) + { + const auto tab = parse_ml_table(loc); + if(!tab){return err(tab.unwrap_err());} + + const auto& tk = tabkey.unwrap(); + const auto& keys = tk.first; + const auto& reg = tk.second; + + const auto inserted = insert_nested_key(data, + value_type(tab.unwrap(), reg, reg.comments()), + keys.begin(), keys.end(), reg); + if(!inserted) {return err(inserted.unwrap_err());} + + continue; + } + return err(format_underline("toml::parse_toml_file: " + "unknown line appeared", {{source_location(loc), "unknown format"}})); + } + + return ok(Value(std::move(data), file, comments)); +} + +} // detail + +template class Table = std::unordered_map, + template class Array = std::vector> +basic_value +parse(std::istream& is, const std::string& fname = "unknown file") +{ + using value_type = basic_value; + + const auto beg = is.tellg(); + is.seekg(0, std::ios::end); + const auto end = is.tellg(); + const auto fsize = end - beg; + is.seekg(beg); + + // read whole file as a sequence of char + assert(fsize >= 0); + std::vector letters(static_cast(fsize)); + is.read(letters.data(), fsize); + + // append LF. + // Although TOML does not require LF at the EOF, to make parsing logic + // simpler, we "normalize" the content by adding LF if it does not exist. + // It also checks if the last char is CR, to avoid changing the meaning. + // This is not the *best* way to deal with the last character, but is a + // simple and quick fix. + if(!letters.empty() && letters.back() != '\n' && letters.back() != '\r') + { + letters.push_back('\n'); + } + + detail::location loc(std::move(fname), std::move(letters)); + + // skip BOM if exists. + // XXX component of BOM (like 0xEF) exceeds the representable range of + // signed char, so on some (actually, most) of the environment, these cannot + // be compared to char. However, since we are always out of luck, we need to + // check our chars are equivalent to BOM. To do this, first we need to + // convert char to unsigned char to guarantee the comparability. + if(loc.source()->size() >= 3) + { + std::array BOM; + std::memcpy(BOM.data(), loc.source()->data(), 3); + if(BOM[0] == 0xEF && BOM[1] == 0xBB && BOM[2] == 0xBF) + { + loc.advance(3); // BOM found. skip. + } + } + + const auto data = detail::parse_toml_file(loc); + if(!data) + { + throw syntax_error(data.unwrap_err(), source_location(loc)); + } + return data.unwrap(); +} + +template class Table = std::unordered_map, + template class Array = std::vector> +basic_value parse(const std::string& fname) +{ + std::ifstream ifs(fname.c_str(), std::ios_base::binary); + if(!ifs.good()) + { + throw std::runtime_error("toml::parse: file open error -> " + fname); + } + return parse(ifs, fname); +} + +#ifdef TOML11_HAS_STD_FILESYSTEM +// This function just forwards `parse("filename.toml")` to std::string version +// to avoid the ambiguity in overload resolution. +// +// Both std::string and std::filesystem::path are convertible from const char*. +// Without this, both parse(std::string) and parse(std::filesystem::path) +// matches to parse("filename.toml"). This breaks the existing code. +// +// This function exactly matches to the invocation with c-string. +// So this function is preferred than others and the ambiguity disappears. +template class Table = std::unordered_map, + template class Array = std::vector> +basic_value parse(const char* fname) +{ + return parse(std::string(fname)); +} + +template class Table = std::unordered_map, + template class Array = std::vector> +basic_value parse(const std::filesystem::path& fpath) +{ + std::ifstream ifs(fpath, std::ios_base::binary); + if(!ifs.good()) + { + throw std::runtime_error("toml::parse: file open error -> " + + fpath.string()); + } + return parse(ifs, fpath.string()); +} +#endif // TOML11_HAS_STD_FILESYSTEM + +} // toml +#endif// TOML11_PARSER_HPP diff --git a/src/frontend/qt_sdl/toml/toml/region.hpp b/src/frontend/qt_sdl/toml/toml/region.hpp new file mode 100644 index 00000000..2e01e51d --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/region.hpp @@ -0,0 +1,417 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_REGION_HPP +#define TOML11_REGION_HPP +#include +#include +#include +#include +#include +#include +#include +#include "color.hpp" + +namespace toml +{ +namespace detail +{ + +// helper function to avoid std::string(0, 'c') or std::string(iter, iter) +template +std::string make_string(Iterator first, Iterator last) +{ + if(first == last) {return "";} + return std::string(first, last); +} +inline std::string make_string(std::size_t len, char c) +{ + if(len == 0) {return "";} + return std::string(len, c); +} + +// region_base is a base class of location and region that are defined below. +// it will be used to generate better error messages. +struct region_base +{ + region_base() = default; + virtual ~region_base() = default; + region_base(const region_base&) = default; + region_base(region_base&& ) = default; + region_base& operator=(const region_base&) = default; + region_base& operator=(region_base&& ) = default; + + virtual bool is_ok() const noexcept {return false;} + virtual char front() const noexcept {return '\0';} + + virtual std::string str() const {return std::string("unknown region");} + virtual std::string name() const {return std::string("unknown file");} + virtual std::string line() const {return std::string("unknown line");} + virtual std::string line_num() const {return std::string("?");} + + // length of the region + virtual std::size_t size() const noexcept {return 0;} + // number of characters in the line before the region + virtual std::size_t before() const noexcept {return 0;} + // number of characters in the line after the region + virtual std::size_t after() const noexcept {return 0;} + + virtual std::vector comments() const {return {};} + // ```toml + // # comment_before + // key = "value" # comment_inline + // ``` +}; + +// location represents a position in a container, which contains a file content. +// it can be considered as a region that contains only one character. +// +// it contains pointer to the file content and iterator that points the current +// location. +struct location final : public region_base +{ + using const_iterator = typename std::vector::const_iterator; + using difference_type = typename const_iterator::difference_type; + using source_ptr = std::shared_ptr>; + + location(std::string source_name, std::vector cont) + : source_(std::make_shared>(std::move(cont))), + line_number_(1), source_name_(std::move(source_name)), iter_(source_->cbegin()) + {} + location(std::string source_name, const std::string& cont) + : source_(std::make_shared>(cont.begin(), cont.end())), + line_number_(1), source_name_(std::move(source_name)), iter_(source_->cbegin()) + {} + + location(const location&) = default; + location(location&&) = default; + location& operator=(const location&) = default; + location& operator=(location&&) = default; + ~location() = default; + + bool is_ok() const noexcept override {return static_cast(source_);} + char front() const noexcept override {return *iter_;} + + // this const prohibits codes like `++(loc.iter())`. + const const_iterator iter() const noexcept {return iter_;} + + const_iterator begin() const noexcept {return source_->cbegin();} + const_iterator end() const noexcept {return source_->cend();} + + // XXX `location::line_num()` used to be implemented using `std::count` to + // count a number of '\n'. But with a long toml file (typically, 10k lines), + // it becomes intolerably slow because each time it generates error messages, + // it counts '\n' from thousands of characters. To workaround it, I decided + // to introduce `location::line_number_` member variable and synchronize it + // to the location changes the point to look. So an overload of `iter()` + // which returns mutable reference is removed and `advance()`, `retrace()` + // and `reset()` is added. + void advance(difference_type n = 1) noexcept + { + this->line_number_ += static_cast( + std::count(this->iter_, std::next(this->iter_, n), '\n')); + this->iter_ += n; + return; + } + void retrace(difference_type n = 1) noexcept + { + this->line_number_ -= static_cast( + std::count(std::prev(this->iter_, n), this->iter_, '\n')); + this->iter_ -= n; + return; + } + void reset(const_iterator rollback) noexcept + { + // since c++11, std::distance works in both ways for random-access + // iterators and returns a negative value if `first > last`. + if(0 <= std::distance(rollback, this->iter_)) // rollback < iter + { + this->line_number_ -= static_cast( + std::count(rollback, this->iter_, '\n')); + } + else // iter < rollback [[unlikely]] + { + this->line_number_ += static_cast( + std::count(this->iter_, rollback, '\n')); + } + this->iter_ = rollback; + return; + } + + std::string str() const override {return make_string(1, *this->iter());} + std::string name() const override {return source_name_;} + + std::string line_num() const override + { + return std::to_string(this->line_number_); + } + + std::string line() const override + { + return make_string(this->line_begin(), this->line_end()); + } + + const_iterator line_begin() const noexcept + { + using reverse_iterator = std::reverse_iterator; + return std::find(reverse_iterator(this->iter()), + reverse_iterator(this->begin()), '\n').base(); + } + const_iterator line_end() const noexcept + { + return std::find(this->iter(), this->end(), '\n'); + } + + // location is always points a character. so the size is 1. + std::size_t size() const noexcept override + { + return 1u; + } + std::size_t before() const noexcept override + { + const auto sz = std::distance(this->line_begin(), this->iter()); + assert(sz >= 0); + return static_cast(sz); + } + std::size_t after() const noexcept override + { + const auto sz = std::distance(this->iter(), this->line_end()); + assert(sz >= 0); + return static_cast(sz); + } + + source_ptr const& source() const& noexcept {return source_;} + source_ptr&& source() && noexcept {return std::move(source_);} + + private: + + source_ptr source_; + std::size_t line_number_; + std::string source_name_; + const_iterator iter_; +}; + +// region represents a range in a container, which contains a file content. +// +// it contains pointer to the file content and iterator that points the first +// and last location. +struct region final : public region_base +{ + using const_iterator = typename std::vector::const_iterator; + using source_ptr = std::shared_ptr>; + + // delete default constructor. source_ never be null. + region() = delete; + + explicit region(const location& loc) + : source_(loc.source()), source_name_(loc.name()), + first_(loc.iter()), last_(loc.iter()) + {} + explicit region(location&& loc) + : source_(loc.source()), source_name_(loc.name()), + first_(loc.iter()), last_(loc.iter()) + {} + + region(const location& loc, const_iterator f, const_iterator l) + : source_(loc.source()), source_name_(loc.name()), first_(f), last_(l) + {} + region(location&& loc, const_iterator f, const_iterator l) + : source_(loc.source()), source_name_(loc.name()), first_(f), last_(l) + {} + + region(const region&) = default; + region(region&&) = default; + region& operator=(const region&) = default; + region& operator=(region&&) = default; + ~region() = default; + + region& operator+=(const region& other) + { + // different regions cannot be concatenated + assert(this->begin() == other.begin() && this->end() == other.end() && + this->last_ == other.first_); + + this->last_ = other.last_; + return *this; + } + + bool is_ok() const noexcept override {return static_cast(source_);} + char front() const noexcept override {return *first_;} + + std::string str() const override {return make_string(first_, last_);} + std::string line() const override + { + if(this->contain_newline()) + { + return make_string(this->line_begin(), + std::find(this->line_begin(), this->last(), '\n')); + } + return make_string(this->line_begin(), this->line_end()); + } + std::string line_num() const override + { + return std::to_string(1 + std::count(this->begin(), this->first(), '\n')); + } + + std::size_t size() const noexcept override + { + const auto sz = std::distance(first_, last_); + assert(sz >= 0); + return static_cast(sz); + } + std::size_t before() const noexcept override + { + const auto sz = std::distance(this->line_begin(), this->first()); + assert(sz >= 0); + return static_cast(sz); + } + std::size_t after() const noexcept override + { + const auto sz = std::distance(this->last(), this->line_end()); + assert(sz >= 0); + return static_cast(sz); + } + + bool contain_newline() const noexcept + { + return std::find(this->first(), this->last(), '\n') != this->last(); + } + + const_iterator line_begin() const noexcept + { + using reverse_iterator = std::reverse_iterator; + return std::find(reverse_iterator(this->first()), + reverse_iterator(this->begin()), '\n').base(); + } + const_iterator line_end() const noexcept + { + return std::find(this->last(), this->end(), '\n'); + } + + const_iterator begin() const noexcept {return source_->cbegin();} + const_iterator end() const noexcept {return source_->cend();} + const_iterator first() const noexcept {return first_;} + const_iterator last() const noexcept {return last_;} + + source_ptr const& source() const& noexcept {return source_;} + source_ptr&& source() && noexcept {return std::move(source_);} + + std::string name() const override {return source_name_;} + + std::vector comments() const override + { + // assuming the current region (`*this`) points a value. + // ```toml + // a = "value" + // ^^^^^^^- this region + // ``` + using rev_iter = std::reverse_iterator; + + std::vector com{}; + { + // find comments just before the current region. + // ```toml + // # this should be collected. + // # this also. + // a = value # not this. + // ``` + + // # this is a comment for `a`, not array elements. + // a = [1, 2, 3, 4, 5] + if(this->first() == std::find_if(this->line_begin(), this->first(), + [](const char c) noexcept -> bool {return c == '[' || c == '{';})) + { + auto iter = this->line_begin(); // points the first character + while(iter != this->begin()) + { + iter = std::prev(iter); + + // range [line_start, iter) represents the previous line + const auto line_start = std::find( + rev_iter(iter), rev_iter(this->begin()), '\n').base(); + const auto comment_found = std::find(line_start, iter, '#'); + if(comment_found == iter) + { + break; // comment not found. + } + + // exclude the following case. + // > a = "foo" # comment // <-- this is not a comment for b but a. + // > b = "current value" + if(std::all_of(line_start, comment_found, + [](const char c) noexcept -> bool { + return c == ' ' || c == '\t'; + })) + { + // unwrap the first '#' by std::next. + auto s = make_string(std::next(comment_found), iter); + if(!s.empty() && s.back() == '\r') {s.pop_back();} + com.push_back(std::move(s)); + } + else + { + break; + } + iter = line_start; + } + } + } + + if(com.size() > 1) + { + std::reverse(com.begin(), com.end()); + } + + { + // find comments just after the current region. + // ```toml + // # not this. + // a = value # this one. + // a = [ # not this (technically difficult) + // + // ] # and this. + // ``` + // The reason why it's difficult is that it requires parsing in the + // following case. + // ```toml + // a = [ 10 # this comment is for `10`. not for `a` but `a[0]`. + // # ... + // ] # this is apparently a comment for a. + // + // b = [ + // 3.14 ] # there is no way to add a comment to `3.14` currently. + // + // c = [ + // 3.14 # do this if you need a comment here. + // ] + // ``` + const auto comment_found = + std::find(this->last(), this->line_end(), '#'); + if(comment_found != this->line_end()) // '#' found + { + // table = {key = "value"} # what is this for? + // the above comment is not for "value", but {key="value"}. + if(comment_found == std::find_if(this->last(), comment_found, + [](const char c) noexcept -> bool { + return !(c == ' ' || c == '\t' || c == ','); + })) + { + // unwrap the first '#' by std::next. + auto s = make_string(std::next(comment_found), this->line_end()); + if(!s.empty() && s.back() == '\r') {s.pop_back();} + com.push_back(std::move(s)); + } + } + } + return com; + } + + private: + + source_ptr source_; + std::string source_name_; + const_iterator first_, last_; +}; + +} // detail +} // toml +#endif// TOML11_REGION_H diff --git a/src/frontend/qt_sdl/toml/toml/result.hpp b/src/frontend/qt_sdl/toml/toml/result.hpp new file mode 100644 index 00000000..77cd46c6 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/result.hpp @@ -0,0 +1,717 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_RESULT_HPP +#define TOML11_RESULT_HPP +#include "traits.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace toml +{ + +template +struct success +{ + using value_type = T; + value_type value; + + explicit success(const value_type& v) + noexcept(std::is_nothrow_copy_constructible::value) + : value(v) + {} + explicit success(value_type&& v) + noexcept(std::is_nothrow_move_constructible::value) + : value(std::move(v)) + {} + + template + explicit success(U&& v): value(std::forward(v)) {} + + template + explicit success(const success& v): value(v.value) {} + template + explicit success(success&& v): value(std::move(v.value)) {} + + ~success() = default; + success(const success&) = default; + success(success&&) = default; + success& operator=(const success&) = default; + success& operator=(success&&) = default; +}; + +template +struct failure +{ + using value_type = T; + value_type value; + + explicit failure(const value_type& v) + noexcept(std::is_nothrow_copy_constructible::value) + : value(v) + {} + explicit failure(value_type&& v) + noexcept(std::is_nothrow_move_constructible::value) + : value(std::move(v)) + {} + + template + explicit failure(U&& v): value(std::forward(v)) {} + + template + explicit failure(const failure& v): value(v.value) {} + template + explicit failure(failure&& v): value(std::move(v.value)) {} + + ~failure() = default; + failure(const failure&) = default; + failure(failure&&) = default; + failure& operator=(const failure&) = default; + failure& operator=(failure&&) = default; +}; + +template +success::type>::type> +ok(T&& v) +{ + return success< + typename std::remove_cv::type>::type + >(std::forward(v)); +} +template +failure::type>::type> +err(T&& v) +{ + return failure< + typename std::remove_cv::type>::type + >(std::forward(v)); +} + +inline success ok(const char* literal) +{ + return success(std::string(literal)); +} +inline failure err(const char* literal) +{ + return failure(std::string(literal)); +} + + +template +struct result +{ + using value_type = T; + using error_type = E; + using success_type = success; + using failure_type = failure; + + result(const success_type& s): is_ok_(true) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(s); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + result(const failure_type& f): is_ok_(false) + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(f); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + result(success_type&& s): is_ok_(true) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(s)); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + result(failure_type&& f): is_ok_(false) + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(f)); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + + template + result(const success& s): is_ok_(true) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(s.value); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + template + result(const failure& f): is_ok_(false) + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(f.value); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + template + result(success&& s): is_ok_(true) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(s.value)); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + template + result(failure&& f): is_ok_(false) + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(f.value)); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + + result& operator=(const success_type& s) + { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new(std::addressof(this->succ)) success_type(s); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + return *this; + } + result& operator=(const failure_type& f) + { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new(std::addressof(this->fail)) failure_type(f); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + return *this; + } + result& operator=(success_type&& s) + { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(s)); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + return *this; + } + result& operator=(failure_type&& f) + { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(f)); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + return *this; + } + + template + result& operator=(const success& s) + { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new(std::addressof(this->succ)) success_type(s.value); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + return *this; + } + template + result& operator=(const failure& f) + { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new(std::addressof(this->fail)) failure_type(f.value); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + return *this; + } + template + result& operator=(success&& s) + { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(s.value)); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + return *this; + } + template + result& operator=(failure&& f) + { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(f.value)); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + return *this; + } + + ~result() noexcept {this->cleanup();} + + result(const result& other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(other.as_ok()); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(other.as_err()); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + } + result(result&& other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + } + + template + result(const result& other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(other.as_ok()); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(other.as_err()); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + } + template + result(result&& other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + } + + result& operator=(const result& other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(other.as_ok()); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(other.as_err()); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + result& operator=(result&& other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + + template + result& operator=(const result& other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(other.as_ok()); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(other.as_err()); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + template + result& operator=(result&& other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + + bool is_ok() const noexcept {return is_ok_;} + bool is_err() const noexcept {return !is_ok_;} + + operator bool() const noexcept {return is_ok_;} + + value_type& unwrap() & + { + if(is_err()) + { + throw std::runtime_error("toml::result: bad unwrap: " + + format_error(this->as_err())); + } + return this->succ.value; + } + value_type const& unwrap() const& + { + if(is_err()) + { + throw std::runtime_error("toml::result: bad unwrap: " + + format_error(this->as_err())); + } + return this->succ.value; + } + value_type&& unwrap() && + { + if(is_err()) + { + throw std::runtime_error("toml::result: bad unwrap: " + + format_error(this->as_err())); + } + return std::move(this->succ.value); + } + + value_type& unwrap_or(value_type& opt) & + { + if(is_err()) {return opt;} + return this->succ.value; + } + value_type const& unwrap_or(value_type const& opt) const& + { + if(is_err()) {return opt;} + return this->succ.value; + } + value_type unwrap_or(value_type opt) && + { + if(is_err()) {return opt;} + return this->succ.value; + } + + error_type& unwrap_err() & + { + if(is_ok()) {throw std::runtime_error("toml::result: bad unwrap_err");} + return this->fail.value; + } + error_type const& unwrap_err() const& + { + if(is_ok()) {throw std::runtime_error("toml::result: bad unwrap_err");} + return this->fail.value; + } + error_type&& unwrap_err() && + { + if(is_ok()) {throw std::runtime_error("toml::result: bad unwrap_err");} + return std::move(this->fail.value); + } + + value_type& as_ok() & noexcept {return this->succ.value;} + value_type const& as_ok() const& noexcept {return this->succ.value;} + value_type&& as_ok() && noexcept {return std::move(this->succ.value);} + + error_type& as_err() & noexcept {return this->fail.value;} + error_type const& as_err() const& noexcept {return this->fail.value;} + error_type&& as_err() && noexcept {return std::move(this->fail.value);} + + + // prerequisities + // F: T -> U + // retval: result + template + result, error_type> + map(F&& f) & + { + if(this->is_ok()){return ok(f(this->as_ok()));} + return err(this->as_err()); + } + template + result, error_type> + map(F&& f) const& + { + if(this->is_ok()){return ok(f(this->as_ok()));} + return err(this->as_err()); + } + template + result, error_type> + map(F&& f) && + { + if(this->is_ok()){return ok(f(std::move(this->as_ok())));} + return err(std::move(this->as_err())); + } + + // prerequisities + // F: E -> F + // retval: result + template + result> + map_err(F&& f) & + { + if(this->is_err()){return err(f(this->as_err()));} + return ok(this->as_ok()); + } + template + result> + map_err(F&& f) const& + { + if(this->is_err()){return err(f(this->as_err()));} + return ok(this->as_ok()); + } + template + result> + map_err(F&& f) && + { + if(this->is_err()){return err(f(std::move(this->as_err())));} + return ok(std::move(this->as_ok())); + } + + // prerequisities + // F: T -> U + // retval: U + template + detail::return_type_of_t + map_or_else(F&& f, U&& opt) & + { + if(this->is_err()){return std::forward(opt);} + return f(this->as_ok()); + } + template + detail::return_type_of_t + map_or_else(F&& f, U&& opt) const& + { + if(this->is_err()){return std::forward(opt);} + return f(this->as_ok()); + } + template + detail::return_type_of_t + map_or_else(F&& f, U&& opt) && + { + if(this->is_err()){return std::forward(opt);} + return f(std::move(this->as_ok())); + } + + // prerequisities + // F: E -> U + // retval: U + template + detail::return_type_of_t + map_err_or_else(F&& f, U&& opt) & + { + if(this->is_ok()){return std::forward(opt);} + return f(this->as_err()); + } + template + detail::return_type_of_t + map_err_or_else(F&& f, U&& opt) const& + { + if(this->is_ok()){return std::forward(opt);} + return f(this->as_err()); + } + template + detail::return_type_of_t + map_err_or_else(F&& f, U&& opt) && + { + if(this->is_ok()){return std::forward(opt);} + return f(std::move(this->as_err())); + } + + // prerequisities: + // F: func T -> U + // toml::err(error_type) should be convertible to U. + // normally, type U is another result and E is convertible to F + template + detail::return_type_of_t + and_then(F&& f) & + { + if(this->is_ok()){return f(this->as_ok());} + return err(this->as_err()); + } + template + detail::return_type_of_t + and_then(F&& f) const& + { + if(this->is_ok()){return f(this->as_ok());} + return err(this->as_err()); + } + template + detail::return_type_of_t + and_then(F&& f) && + { + if(this->is_ok()){return f(std::move(this->as_ok()));} + return err(std::move(this->as_err())); + } + + // prerequisities: + // F: func E -> U + // toml::ok(value_type) should be convertible to U. + // normally, type U is another result and T is convertible to S + template + detail::return_type_of_t + or_else(F&& f) & + { + if(this->is_err()){return f(this->as_err());} + return ok(this->as_ok()); + } + template + detail::return_type_of_t + or_else(F&& f) const& + { + if(this->is_err()){return f(this->as_err());} + return ok(this->as_ok()); + } + template + detail::return_type_of_t + or_else(F&& f) && + { + if(this->is_err()){return f(std::move(this->as_err()));} + return ok(std::move(this->as_ok())); + } + + // if *this is error, returns *this. otherwise, returns other. + result and_other(const result& other) const& + { + return this->is_err() ? *this : other; + } + result and_other(result&& other) && + { + return this->is_err() ? std::move(*this) : std::move(other); + } + + // if *this is okay, returns *this. otherwise, returns other. + result or_other(const result& other) const& + { + return this->is_ok() ? *this : other; + } + result or_other(result&& other) && + { + return this->is_ok() ? std::move(*this) : std::move(other); + } + + void swap(result& other) + { + result tmp(std::move(*this)); + *this = std::move(other); + other = std::move(tmp); + return ; + } + + private: + + static std::string format_error(std::exception const& excpt) + { + return std::string(excpt.what()); + } + template::value, std::nullptr_t>::type = nullptr> + static std::string format_error(U const& others) + { + std::ostringstream oss; oss << others; + return oss.str(); + } + + void cleanup() noexcept + { + if(this->is_ok_) {this->succ.~success_type();} + else {this->fail.~failure_type();} + return; + } + + private: + + bool is_ok_; + union + { + success_type succ; + failure_type fail; + }; +}; + +template +void swap(result& lhs, result& rhs) +{ + lhs.swap(rhs); + return; +} + +// this might be confusing because it eagerly evaluated, while in the other +// cases operator && and || are short-circuited. +// +// template +// inline result +// operator&&(const result& lhs, const result& rhs) noexcept +// { +// return lhs.is_ok() ? rhs : lhs; +// } +// +// template +// inline result +// operator||(const result& lhs, const result& rhs) noexcept +// { +// return lhs.is_ok() ? lhs : rhs; +// } + +// ---------------------------------------------------------------------------- +// re-use result as a optional with none_t + +namespace detail +{ +struct none_t {}; +inline bool operator==(const none_t&, const none_t&) noexcept {return true;} +inline bool operator!=(const none_t&, const none_t&) noexcept {return false;} +inline bool operator< (const none_t&, const none_t&) noexcept {return false;} +inline bool operator<=(const none_t&, const none_t&) noexcept {return true;} +inline bool operator> (const none_t&, const none_t&) noexcept {return false;} +inline bool operator>=(const none_t&, const none_t&) noexcept {return true;} +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const none_t&) +{ + os << "none"; + return os; +} +inline failure none() noexcept {return failure{none_t{}};} +} // detail +} // toml11 +#endif// TOML11_RESULT_H diff --git a/src/frontend/qt_sdl/toml/toml/serializer.hpp b/src/frontend/qt_sdl/toml/toml/serializer.hpp new file mode 100644 index 00000000..88ae775a --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/serializer.hpp @@ -0,0 +1,922 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_SERIALIZER_HPP +#define TOML11_SERIALIZER_HPP +#include +#include + +#include + +#include "lexer.hpp" +#include "value.hpp" + +namespace toml +{ + +// This function serialize a key. It checks a string is a bare key and +// escapes special characters if the string is not compatible to a bare key. +// ```cpp +// std::string k("non.bare.key"); // the key itself includes `.`s. +// std::string formatted = toml::format_key(k); +// assert(formatted == "\"non.bare.key\""); +// ``` +// +// This function is exposed to make it easy to write a user-defined serializer. +// Since toml restricts characters available in a bare key, generally a string +// should be escaped. But checking whether a string needs to be surrounded by +// a `"` and escaping some special character is boring. +template +std::basic_string +format_key(const std::basic_string& k) +{ + if(k.empty()) + { + return std::string("\"\""); + } + + // check the key can be a bare (unquoted) key + detail::location loc(k, std::vector(k.begin(), k.end())); + detail::lex_unquoted_key::invoke(loc); + if(loc.iter() == loc.end()) + { + return k; // all the tokens are consumed. the key is unquoted-key. + } + + //if it includes special characters, then format it in a "quoted" key. + std::basic_string serialized("\""); + for(const char c : k) + { + switch(c) + { + case '\\': {serialized += "\\\\"; break;} + case '\"': {serialized += "\\\""; break;} + case '\b': {serialized += "\\b"; break;} + case '\t': {serialized += "\\t"; break;} + case '\f': {serialized += "\\f"; break;} + case '\n': {serialized += "\\n"; break;} + case '\r': {serialized += "\\r"; break;} + default : {serialized += c; break;} + } + } + serialized += "\""; + return serialized; +} + +template +std::basic_string +format_keys(const std::vector>& keys) +{ + if(keys.empty()) + { + return std::string("\"\""); + } + + std::basic_string serialized; + for(const auto& ky : keys) + { + serialized += format_key(ky); + serialized += charT('.'); + } + serialized.pop_back(); // remove the last dot '.' + return serialized; +} + +template +struct serializer +{ + static_assert(detail::is_basic_value::value, + "toml::serializer is for toml::value and its variants, " + "toml::basic_value<...>."); + + using value_type = Value; + using key_type = typename value_type::key_type ; + using comment_type = typename value_type::comment_type ; + using boolean_type = typename value_type::boolean_type ; + using integer_type = typename value_type::integer_type ; + using floating_type = typename value_type::floating_type ; + using string_type = typename value_type::string_type ; + using local_time_type = typename value_type::local_time_type ; + using local_date_type = typename value_type::local_date_type ; + using local_datetime_type = typename value_type::local_datetime_type ; + using offset_datetime_type = typename value_type::offset_datetime_type; + using array_type = typename value_type::array_type ; + using table_type = typename value_type::table_type ; + + serializer(const std::size_t w = 80u, + const int float_prec = std::numeric_limits::max_digits10, + const bool can_be_inlined = false, + const bool no_comment = false, + std::vector ks = {}, + const bool value_has_comment = false) + : can_be_inlined_(can_be_inlined), no_comment_(no_comment), + value_has_comment_(value_has_comment && !no_comment), + float_prec_(float_prec), width_(w), keys_(std::move(ks)) + {} + ~serializer() = default; + + std::string operator()(const boolean_type& b) const + { + return b ? "true" : "false"; + } + std::string operator()(const integer_type i) const + { + return std::to_string(i); + } + std::string operator()(const floating_type f) const + { + if(std::isnan(f)) + { + if(std::signbit(f)) + { + return std::string("-nan"); + } + else + { + return std::string("nan"); + } + } + else if(!std::isfinite(f)) + { + if(std::signbit(f)) + { + return std::string("-inf"); + } + else + { + return std::string("inf"); + } + } + + const auto fmt = "%.*g"; + const auto bsz = std::snprintf(nullptr, 0, fmt, this->float_prec_, f); + // +1 for null character(\0) + std::vector buf(static_cast(bsz + 1), '\0'); + std::snprintf(buf.data(), buf.size(), fmt, this->float_prec_, f); + + std::string token(buf.begin(), std::prev(buf.end())); + if(!token.empty() && token.back() == '.') // 1. => 1.0 + { + token += '0'; + } + + const auto e = std::find_if( + token.cbegin(), token.cend(), [](const char c) noexcept -> bool { + return c == 'e' || c == 'E'; + }); + const auto has_exponent = (token.cend() != e); + const auto has_fraction = (token.cend() != std::find( + token.cbegin(), token.cend(), '.')); + + if(!has_exponent && !has_fraction) + { + // the resulting value does not have any float specific part! + token += ".0"; + } + return token; + } + std::string operator()(const string_type& s) const + { + if(s.kind == string_t::basic) + { + if((std::find(s.str.cbegin(), s.str.cend(), '\n') != s.str.cend() || + std::find(s.str.cbegin(), s.str.cend(), '\"') != s.str.cend()) && + this->width_ != (std::numeric_limits::max)()) + { + // if linefeed or double-quote is contained, + // make it multiline basic string. + const auto escaped = this->escape_ml_basic_string(s.str); + std::string open("\"\"\""); + std::string close("\"\"\""); + if(escaped.find('\n') != std::string::npos || + this->width_ < escaped.size() + 6) + { + // if the string body contains newline or is enough long, + // add newlines after and before delimiters. + open += "\n"; + close = std::string("\\\n") + close; + } + return open + escaped + close; + } + + // no linefeed. try to make it oneline-string. + std::string oneline = this->escape_basic_string(s.str); + if(oneline.size() + 2 < width_ || width_ < 2) + { + const std::string quote("\""); + return quote + oneline + quote; + } + + // the line is too long compared to the specified width. + // split it into multiple lines. + std::string token("\"\"\"\n"); + while(!oneline.empty()) + { + if(oneline.size() < width_) + { + token += oneline; + oneline.clear(); + } + else if(oneline.at(width_-2) == '\\') + { + token += oneline.substr(0, width_-2); + token += "\\\n"; + oneline.erase(0, width_-2); + } + else + { + token += oneline.substr(0, width_-1); + token += "\\\n"; + oneline.erase(0, width_-1); + } + } + return token + std::string("\\\n\"\"\""); + } + else // the string `s` is literal-string. + { + if(std::find(s.str.cbegin(), s.str.cend(), '\n') != s.str.cend() || + std::find(s.str.cbegin(), s.str.cend(), '\'') != s.str.cend() ) + { + std::string open("'''"); + if(this->width_ + 6 < s.str.size()) + { + open += '\n'; // the first newline is ignored by TOML spec + } + const std::string close("'''"); + return open + s.str + close; + } + else + { + const std::string quote("'"); + return quote + s.str + quote; + } + } + } + + std::string operator()(const local_date_type& d) const + { + std::ostringstream oss; + oss << d; + return oss.str(); + } + std::string operator()(const local_time_type& t) const + { + std::ostringstream oss; + oss << t; + return oss.str(); + } + std::string operator()(const local_datetime_type& dt) const + { + std::ostringstream oss; + oss << dt; + return oss.str(); + } + std::string operator()(const offset_datetime_type& odt) const + { + std::ostringstream oss; + oss << odt; + return oss.str(); + } + + std::string operator()(const array_type& v) const + { + if(v.empty()) + { + return std::string("[]"); + } + if(this->is_array_of_tables(v)) + { + return make_array_of_tables(v); + } + + // not an array of tables. normal array. + // first, try to make it inline if none of the elements have a comment. + if( ! this->has_comment_inside(v)) + { + const auto inl = this->make_inline_array(v); + if(inl.size() < this->width_ && + std::find(inl.cbegin(), inl.cend(), '\n') == inl.cend()) + { + return inl; + } + } + + // if the length exceeds this->width_, print multiline array. + // key = [ + // # ... + // 42, + // ... + // ] + std::string token; + std::string current_line; + token += "[\n"; + for(const auto& item : v) + { + if( ! item.comments().empty() && !no_comment_) + { + // if comment exists, the element must be the only element in the line. + // e.g. the following is not allowed. + // ```toml + // array = [ + // # comment for what? + // 1, 2, 3, 4, 5 + // ] + // ``` + if(!current_line.empty()) + { + if(current_line.back() != '\n') + { + current_line += '\n'; + } + token += current_line; + current_line.clear(); + } + for(const auto& c : item.comments()) + { + token += '#'; + token += c; + token += '\n'; + } + token += toml::visit(*this, item); + if(!token.empty() && token.back() == '\n') {token.pop_back();} + token += ",\n"; + continue; + } + std::string next_elem; + if(item.is_table()) + { + serializer ser(*this); + ser.can_be_inlined_ = true; + ser.width_ = (std::numeric_limits::max)(); + next_elem += toml::visit(ser, item); + } + else + { + next_elem += toml::visit(*this, item); + } + + // comma before newline. + if(!next_elem.empty() && next_elem.back() == '\n') {next_elem.pop_back();} + + // if current line does not exceeds the width limit, continue. + if(current_line.size() + next_elem.size() + 1 < this->width_) + { + current_line += next_elem; + current_line += ','; + } + else if(current_line.empty()) + { + // if current line was empty, force put the next_elem because + // next_elem is not splittable + token += next_elem; + token += ",\n"; + // current_line is kept empty + } + else // reset current_line + { + assert(current_line.back() == ','); + token += current_line; + token += '\n'; + current_line = next_elem; + current_line += ','; + } + } + if(!current_line.empty()) + { + if(!current_line.empty() && current_line.back() != '\n') + { + current_line += '\n'; + } + token += current_line; + } + token += "]\n"; + return token; + } + + // templatize for any table-like container + std::string operator()(const table_type& v) const + { + // if an element has a comment, then it can't be inlined. + // table = {# how can we write a comment for this? key = "value"} + if(this->can_be_inlined_ && !(this->has_comment_inside(v))) + { + std::string token; + if(!this->keys_.empty()) + { + token += format_key(this->keys_.back()); + token += " = "; + } + token += this->make_inline_table(v); + if(token.size() < this->width_ && + token.end() == std::find(token.begin(), token.end(), '\n')) + { + return token; + } + } + + std::string token; + if(!keys_.empty()) + { + token += '['; + token += format_keys(keys_); + token += "]\n"; + } + token += this->make_multiline_table(v); + return token; + } + + private: + + std::string escape_basic_string(const std::string& s) const + { + //XXX assuming `s` is a valid utf-8 sequence. + std::string retval; + for(const char c : s) + { + switch(c) + { + case '\\': {retval += "\\\\"; break;} + case '\"': {retval += "\\\""; break;} + case '\b': {retval += "\\b"; break;} + case '\t': {retval += "\\t"; break;} + case '\f': {retval += "\\f"; break;} + case '\n': {retval += "\\n"; break;} + case '\r': {retval += "\\r"; break;} + default : + { + if((0x00 <= c && c <= 0x08) || (0x0A <= c && c <= 0x1F) || c == 0x7F) + { + retval += "\\u00"; + retval += char(48 + (c / 16)); + retval += char((c % 16 < 10 ? 48 : 55) + (c % 16)); + } + else + { + retval += c; + } + } + } + } + return retval; + } + + std::string escape_ml_basic_string(const std::string& s) const + { + std::string retval; + for(auto i=s.cbegin(), e=s.cend(); i!=e; ++i) + { + switch(*i) + { + case '\\': {retval += "\\\\"; break;} + // One or two consecutive "s are allowed. + // Later we will check there are no three consecutive "s. + // case '\"': {retval += "\\\""; break;} + case '\b': {retval += "\\b"; break;} + case '\t': {retval += "\\t"; break;} + case '\f': {retval += "\\f"; break;} + case '\n': {retval += "\n"; break;} + case '\r': + { + if(std::next(i) != e && *std::next(i) == '\n') + { + retval += "\r\n"; + ++i; + } + else + { + retval += "\\r"; + } + break; + } + default : + { + const auto c = *i; + if((0x00 <= c && c <= 0x08) || (0x0A <= c && c <= 0x1F) || c == 0x7F) + { + retval += "\\u00"; + retval += char(48 + (c / 16)); + retval += char((c % 16 < 10 ? 48 : 55) + (c % 16)); + } + else + { + retval += c; + } + } + + } + } + // Only 1 or 2 consecutive `"`s are allowed in multiline basic string. + // 3 consecutive `"`s are considered as a closing delimiter. + // We need to check if there are 3 or more consecutive `"`s and insert + // backslash to break them down into several short `"`s like the `str6` + // in the following example. + // ```toml + // str4 = """Here are two quotation marks: "". Simple enough.""" + // # str5 = """Here are three quotation marks: """.""" # INVALID + // str5 = """Here are three quotation marks: ""\".""" + // str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" + // ``` + auto found_3_quotes = retval.find("\"\"\""); + while(found_3_quotes != std::string::npos) + { + retval.replace(found_3_quotes, 3, "\"\"\\\""); + found_3_quotes = retval.find("\"\"\""); + } + return retval; + } + + // if an element of a table or an array has a comment, it cannot be inlined. + bool has_comment_inside(const array_type& a) const noexcept + { + // if no_comment is set, comments would not be written. + if(this->no_comment_) {return false;} + + for(const auto& v : a) + { + if(!v.comments().empty()) {return true;} + } + return false; + } + bool has_comment_inside(const table_type& t) const noexcept + { + // if no_comment is set, comments would not be written. + if(this->no_comment_) {return false;} + + for(const auto& kv : t) + { + if(!kv.second.comments().empty()) {return true;} + } + return false; + } + + std::string make_inline_array(const array_type& v) const + { + assert(!has_comment_inside(v)); + std::string token; + token += '['; + bool is_first = true; + for(const auto& item : v) + { + if(is_first) {is_first = false;} else {token += ',';} + token += visit(serializer( + (std::numeric_limits::max)(), this->float_prec_, + /* inlined */ true, /*no comment*/ false, /*keys*/ {}, + /*has_comment*/ !item.comments().empty()), item); + } + token += ']'; + return token; + } + + std::string make_inline_table(const table_type& v) const + { + assert(!has_comment_inside(v)); + assert(this->can_be_inlined_); + std::string token; + token += '{'; + bool is_first = true; + for(const auto& kv : v) + { + // in inline tables, trailing comma is not allowed (toml-lang #569). + if(is_first) {is_first = false;} else {token += ',';} + token += format_key(kv.first); + token += '='; + token += visit(serializer( + (std::numeric_limits::max)(), this->float_prec_, + /* inlined */ true, /*no comment*/ false, /*keys*/ {}, + /*has_comment*/ !kv.second.comments().empty()), kv.second); + } + token += '}'; + return token; + } + + std::string make_multiline_table(const table_type& v) const + { + std::string token; + + // print non-table elements first. + // ```toml + // [foo] # a table we're writing now here + // key = "value" # <- non-table element, "key" + // # ... + // [foo.bar] # <- table element, "bar" + // ``` + // because after printing [foo.bar], the remaining non-table values will + // be assigned into [foo.bar], not [foo]. Those values should be printed + // earlier. + for(const auto& kv : v) + { + if(kv.second.is_table() || is_array_of_tables(kv.second)) + { + continue; + } + + token += write_comments(kv.second); + + const auto key_and_sep = format_key(kv.first) + " = "; + const auto residual_width = (this->width_ > key_and_sep.size()) ? + this->width_ - key_and_sep.size() : 0; + token += key_and_sep; + token += visit(serializer(residual_width, this->float_prec_, + /*can be inlined*/ true, /*no comment*/ false, /*keys*/ {}, + /*has_comment*/ !kv.second.comments().empty()), kv.second); + + if(token.back() != '\n') + { + token += '\n'; + } + } + + // normal tables / array of tables + + // after multiline table appeared, the other tables cannot be inline + // because the table would be assigned into the table. + // [foo] + // ... + // bar = {...} # <- bar will be a member of [foo]. + bool multiline_table_printed = false; + for(const auto& kv : v) + { + if(!kv.second.is_table() && !is_array_of_tables(kv.second)) + { + continue; // other stuff are already serialized. skip them. + } + + std::vector ks(this->keys_); + ks.push_back(kv.first); + + auto tmp = visit(serializer(this->width_, this->float_prec_, + !multiline_table_printed, this->no_comment_, ks, + /*has_comment*/ !kv.second.comments().empty()), kv.second); + + // If it is the first time to print a multi-line table, it would be + // helpful to separate normal key-value pair and subtables by a + // newline. + // (this checks if the current key-value pair contains newlines. + // but it is not perfect because multi-line string can also contain + // a newline. in such a case, an empty line will be written) TODO + if((!multiline_table_printed) && + std::find(tmp.cbegin(), tmp.cend(), '\n') != tmp.cend()) + { + multiline_table_printed = true; + token += '\n'; // separate key-value pairs and subtables + + token += write_comments(kv.second); + token += tmp; + + // care about recursive tables (all tables in each level prints + // newline and there will be a full of newlines) + if(tmp.substr(tmp.size() - 2, 2) != "\n\n" && + tmp.substr(tmp.size() - 4, 4) != "\r\n\r\n" ) + { + token += '\n'; + } + } + else + { + token += write_comments(kv.second); + token += tmp; + token += '\n'; + } + } + return token; + } + + std::string make_array_of_tables(const array_type& v) const + { + // if it's not inlined, we need to add `[[table.key]]`. + // but if it can be inlined, we can format it as the following. + // ``` + // table.key = [ + // {...}, + // # comment + // {...}, + // ] + // ``` + // This function checks if inlinization is possible or not, and then + // format the array-of-tables in a proper way. + // + // Note about comments: + // + // If the array itself has a comment (value_has_comment_ == true), we + // should try to make it inline. + // ```toml + // # comment about array + // array = [ + // # comment about table element + // {of = "table"} + // ] + // ``` + // If it is formatted as a multiline table, the two comments becomes + // indistinguishable. + // ```toml + // # comment about array + // # comment about table element + // [[array]] + // of = "table" + // ``` + // So we need to try to make it inline, and it force-inlines regardless + // of the line width limit. + // It may fail if the element of a table has comment. In that case, + // the array-of-tables will be formatted as a multiline table. + if(this->can_be_inlined_ || this->value_has_comment_) + { + std::string token; + if(!keys_.empty()) + { + token += format_key(keys_.back()); + token += " = "; + } + + bool failed = false; + token += "[\n"; + for(const auto& item : v) + { + // if an element of the table has a comment, the table + // cannot be inlined. + if(this->has_comment_inside(item.as_table())) + { + failed = true; + break; + } + // write comments for the table itself + token += write_comments(item); + + const auto t = this->make_inline_table(item.as_table()); + + if(t.size() + 1 > width_ || // +1 for the last comma {...}, + std::find(t.cbegin(), t.cend(), '\n') != t.cend()) + { + // if the value itself has a comment, ignore the line width limit + if( ! this->value_has_comment_) + { + failed = true; + break; + } + } + token += t; + token += ",\n"; + } + + if( ! failed) + { + token += "]\n"; + return token; + } + // if failed, serialize them as [[array.of.tables]]. + } + + std::string token; + for(const auto& item : v) + { + token += write_comments(item); + token += "[["; + token += format_keys(keys_); + token += "]]\n"; + token += this->make_multiline_table(item.as_table()); + } + return token; + } + + std::string write_comments(const value_type& v) const + { + std::string retval; + if(this->no_comment_) {return retval;} + + for(const auto& c : v.comments()) + { + retval += '#'; + retval += c; + retval += '\n'; + } + return retval; + } + + bool is_array_of_tables(const value_type& v) const + { + if(!v.is_array() || v.as_array().empty()) {return false;} + return is_array_of_tables(v.as_array()); + } + bool is_array_of_tables(const array_type& v) const + { + // Since TOML v0.5.0, heterogeneous arrays are allowed. So we need to + // check all the element in an array to check if the array is an array + // of tables. + return std::all_of(v.begin(), v.end(), [](const value_type& elem) { + return elem.is_table(); + }); + } + + private: + + bool can_be_inlined_; + bool no_comment_; + bool value_has_comment_; + int float_prec_; + std::size_t width_; + std::vector keys_; +}; + +template class M, template class V> +std::string +format(const basic_value& v, std::size_t w = 80u, + int fprec = std::numeric_limits::max_digits10, + bool no_comment = false, bool force_inline = false) +{ + using value_type = basic_value; + // if value is a table, it is considered to be a root object. + // the root object can't be an inline table. + if(v.is_table()) + { + std::ostringstream oss; + if(!v.comments().empty()) + { + oss << v.comments(); + oss << '\n'; // to split the file comment from the first element + } + const auto serialized = visit(serializer(w, fprec, false, no_comment), v); + oss << serialized; + return oss.str(); + } + return visit(serializer(w, fprec, force_inline), v); +} + +namespace detail +{ +template +int comment_index(std::basic_ostream&) +{ + static const int index = std::ios_base::xalloc(); + return index; +} +} // detail + +template +std::basic_ostream& +nocomment(std::basic_ostream& os) +{ + // by default, it is zero. and by default, it shows comments. + os.iword(detail::comment_index(os)) = 1; + return os; +} + +template +std::basic_ostream& +showcomment(std::basic_ostream& os) +{ + // by default, it is zero. and by default, it shows comments. + os.iword(detail::comment_index(os)) = 0; + return os; +} + +template class M, template class V> +std::basic_ostream& +operator<<(std::basic_ostream& os, const basic_value& v) +{ + using value_type = basic_value; + + // get status of std::setw(). + const auto w = static_cast(os.width()); + const int fprec = static_cast(os.precision()); + os.width(0); + + // by default, iword is initialized by 0. And by default, toml11 outputs + // comments. So `0` means showcomment. 1 means nocommnet. + const bool no_comment = (1 == os.iword(detail::comment_index(os))); + + if(!no_comment && v.is_table() && !v.comments().empty()) + { + os << v.comments(); + os << '\n'; // to split the file comment from the first element + } + // the root object can't be an inline table. so pass `false`. + const auto serialized = visit(serializer(w, fprec, no_comment, false), v); + os << serialized; + + // if v is a non-table value, and has only one comment, then + // put a comment just after a value. in the following way. + // + // ```toml + // key = "value" # comment. + // ``` + // + // Since the top-level toml object is a table, one who want to put a + // non-table toml value must use this in a following way. + // + // ```cpp + // toml::value v; + // std::cout << "user-defined-key = " << v << std::endl; + // ``` + // + // In this case, it is impossible to put comments before key-value pair. + // The only way to preserve comments is to put all of them after a value. + if(!no_comment && !v.is_table() && !v.comments().empty()) + { + os << " #"; + for(const auto& c : v.comments()) {os << c;} + } + return os; +} + +} // toml +#endif// TOML11_SERIALIZER_HPP diff --git a/src/frontend/qt_sdl/toml/toml/source_location.hpp b/src/frontend/qt_sdl/toml/toml/source_location.hpp new file mode 100644 index 00000000..fa175b5b --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/source_location.hpp @@ -0,0 +1,233 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_SOURCE_LOCATION_HPP +#define TOML11_SOURCE_LOCATION_HPP +#include +#include + +#include "region.hpp" + +namespace toml +{ + +// A struct to contain location in a toml file. +// The interface imitates std::experimental::source_location, +// but not completely the same. +// +// It would be constructed by toml::value. It can be used to generate +// user-defined error messages. +// +// - std::uint_least32_t line() const noexcept +// - returns the line number where the region is on. +// - std::uint_least32_t column() const noexcept +// - returns the column number where the region starts. +// - std::uint_least32_t region() const noexcept +// - returns the size of the region. +// +// +-- line() +-- region of interest (region() == 9) +// v .---+---. +// 12 | value = "foo bar" +// ^ +// +-- column() +// +// - std::string const& file_name() const noexcept; +// - name of the file. +// - std::string const& line_str() const noexcept; +// - the whole line that contains the region of interest. +// +struct source_location +{ + public: + + source_location() + : line_num_(1), column_num_(1), region_size_(1), + file_name_("unknown file"), line_str_("") + {} + + explicit source_location(const detail::region_base* reg) + : line_num_(1), column_num_(1), region_size_(1), + file_name_("unknown file"), line_str_("") + { + if(reg) + { + if(reg->line_num() != detail::region_base().line_num()) + { + line_num_ = static_cast( + std::stoul(reg->line_num())); + } + column_num_ = static_cast(reg->before() + 1); + region_size_ = static_cast(reg->size()); + file_name_ = reg->name(); + line_str_ = reg->line(); + } + } + + explicit source_location(const detail::region& reg) + : line_num_(static_cast(std::stoul(reg.line_num()))), + column_num_(static_cast(reg.before() + 1)), + region_size_(static_cast(reg.size())), + file_name_(reg.name()), + line_str_ (reg.line()) + {} + explicit source_location(const detail::location& loc) + : line_num_(static_cast(std::stoul(loc.line_num()))), + column_num_(static_cast(loc.before() + 1)), + region_size_(static_cast(loc.size())), + file_name_(loc.name()), + line_str_ (loc.line()) + {} + + ~source_location() = default; + source_location(source_location const&) = default; + source_location(source_location &&) = default; + source_location& operator=(source_location const&) = default; + source_location& operator=(source_location &&) = default; + + std::uint_least32_t line() const noexcept {return line_num_;} + std::uint_least32_t column() const noexcept {return column_num_;} + std::uint_least32_t region() const noexcept {return region_size_;} + + std::string const& file_name() const noexcept {return file_name_;} + std::string const& line_str() const noexcept {return line_str_;} + + private: + + std::uint_least32_t line_num_; + std::uint_least32_t column_num_; + std::uint_least32_t region_size_; + std::string file_name_; + std::string line_str_; +}; + +namespace detail +{ + +// internal error message generation. +inline std::string format_underline(const std::string& message, + const std::vector>& loc_com, + const std::vector& helps = {}, + const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED) +{ + std::size_t line_num_width = 0; + for(const auto& lc : loc_com) + { + std::uint_least32_t line = lc.first.line(); + std::size_t digit = 0; + while(line != 0) + { + line /= 10; + digit += 1; + } + line_num_width = (std::max)(line_num_width, digit); + } + // 1 is the minimum width + line_num_width = std::max(line_num_width, 1); + + std::ostringstream retval; + + if(colorize) + { + retval << color::colorize; // turn on ANSI color + } + + // XXX + // Here, before `colorize` support, it does not output `[error]` prefix + // automatically. So some user may output it manually and this change may + // duplicate the prefix. To avoid it, check the first 7 characters and + // if it is "[error]", it removes that part from the message shown. + if(message.size() > 7 && message.substr(0, 7) == "[error]") + { + retval << color::bold << color::red << "[error]" << color::reset + << color::bold << message.substr(7) << color::reset << '\n'; + } + else + { + retval << color::bold << color::red << "[error] " << color::reset + << color::bold << message << color::reset << '\n'; + } + + const auto format_one_location = [line_num_width] + (std::ostringstream& oss, + const source_location& loc, const std::string& comment) -> void + { + oss << ' ' << color::bold << color::blue + << std::setw(static_cast(line_num_width)) + << std::right << loc.line() << " | " << color::reset + << loc.line_str() << '\n'; + + oss << make_string(line_num_width + 1, ' ') + << color::bold << color::blue << " | " << color::reset + << make_string(loc.column()-1 /*1-origin*/, ' '); + + if(loc.region() == 1) + { + // invalid + // ^------ + oss << color::bold << color::red << "^---" << color::reset; + } + else + { + // invalid + // ~~~~~~~ + const auto underline_len = (std::min)( + static_cast(loc.region()), loc.line_str().size()); + oss << color::bold << color::red + << make_string(underline_len, '~') << color::reset; + } + oss << ' '; + oss << comment; + return; + }; + + assert(!loc_com.empty()); + + // --> example.toml + // | + retval << color::bold << color::blue << " --> " << color::reset + << loc_com.front().first.file_name() << '\n'; + retval << make_string(line_num_width + 1, ' ') + << color::bold << color::blue << " |\n" << color::reset; + // 1 | key value + // | ^--- missing = + format_one_location(retval, loc_com.front().first, loc_com.front().second); + + // process the rest of the locations + for(std::size_t i=1; i filename.toml" again + { + retval << color::bold << color::blue << " --> " << color::reset + << curr.first.file_name() << '\n'; + retval << make_string(line_num_width + 1, ' ') + << color::bold << color::blue << " |\n" << color::reset; + } + + format_one_location(retval, curr.first, curr.second); + } + + if(!helps.empty()) + { + retval << '\n'; + retval << make_string(line_num_width + 1, ' '); + retval << color::bold << color::blue << " |" << color::reset; + for(const auto& help : helps) + { + retval << color::bold << "\nHint: " << color::reset; + retval << help; + } + } + return retval.str(); +} + +} // detail +} // toml +#endif// TOML11_SOURCE_LOCATION_HPP diff --git a/src/frontend/qt_sdl/toml/toml/storage.hpp b/src/frontend/qt_sdl/toml/toml/storage.hpp new file mode 100644 index 00000000..202f9035 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/storage.hpp @@ -0,0 +1,43 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_STORAGE_HPP +#define TOML11_STORAGE_HPP +#include "utility.hpp" + +namespace toml +{ +namespace detail +{ + +// this contains pointer and deep-copy the content if copied. +// to avoid recursive pointer. +template +struct storage +{ + using value_type = T; + + explicit storage(value_type const& v): ptr(toml::make_unique(v)) {} + explicit storage(value_type&& v): ptr(toml::make_unique(std::move(v))) {} + ~storage() = default; + storage(const storage& rhs): ptr(toml::make_unique(*rhs.ptr)) {} + storage& operator=(const storage& rhs) + { + this->ptr = toml::make_unique(*rhs.ptr); + return *this; + } + storage(storage&&) = default; + storage& operator=(storage&&) = default; + + bool is_ok() const noexcept {return static_cast(ptr);} + + value_type& value() & noexcept {return *ptr;} + value_type const& value() const& noexcept {return *ptr;} + value_type&& value() && noexcept {return std::move(*ptr);} + + private: + std::unique_ptr ptr; +}; + +} // detail +} // toml +#endif// TOML11_STORAGE_HPP diff --git a/src/frontend/qt_sdl/toml/toml/string.hpp b/src/frontend/qt_sdl/toml/toml/string.hpp new file mode 100644 index 00000000..def3e57c --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/string.hpp @@ -0,0 +1,228 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_STRING_HPP +#define TOML11_STRING_HPP + +#include "version.hpp" + +#include + +#include +#include + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201703L +#if __has_include() +#define TOML11_USING_STRING_VIEW 1 +#include +#endif +#endif + +namespace toml +{ + +enum class string_t : std::uint8_t +{ + basic = 0, + literal = 1, +}; + +struct string +{ + string() = default; + ~string() = default; + string(const string& s) = default; + string(string&& s) = default; + string& operator=(const string& s) = default; + string& operator=(string&& s) = default; + + string(const std::string& s): kind(string_t::basic), str(s){} + string(const std::string& s, string_t k): kind(k), str(s){} + string(const char* s): kind(string_t::basic), str(s){} + string(const char* s, string_t k): kind(k), str(s){} + + string(std::string&& s): kind(string_t::basic), str(std::move(s)){} + string(std::string&& s, string_t k): kind(k), str(std::move(s)){} + + string& operator=(const std::string& s) + {kind = string_t::basic; str = s; return *this;} + string& operator=(std::string&& s) + {kind = string_t::basic; str = std::move(s); return *this;} + + operator std::string& () & noexcept {return str;} + operator std::string const& () const& noexcept {return str;} + operator std::string&& () && noexcept {return std::move(str);} + + string& operator+=(const char* rhs) {str += rhs; return *this;} + string& operator+=(const char rhs) {str += rhs; return *this;} + string& operator+=(const std::string& rhs) {str += rhs; return *this;} + string& operator+=(const string& rhs) {str += rhs.str; return *this;} + +#if defined(TOML11_USING_STRING_VIEW) && TOML11_USING_STRING_VIEW>0 + explicit string(std::string_view s): kind(string_t::basic), str(s){} + string(std::string_view s, string_t k): kind(k), str(s){} + + string& operator=(std::string_view s) + {kind = string_t::basic; str = s; return *this;} + + explicit operator std::string_view() const noexcept + {return std::string_view(str);} + + string& operator+=(const std::string_view& rhs) {str += rhs; return *this;} +#endif + + string_t kind; + std::string str; +}; + +inline bool operator==(const string& lhs, const string& rhs) +{ + return lhs.kind == rhs.kind && lhs.str == rhs.str; +} +inline bool operator!=(const string& lhs, const string& rhs) +{ + return !(lhs == rhs); +} +inline bool operator<(const string& lhs, const string& rhs) +{ + return (lhs.kind == rhs.kind) ? (lhs.str < rhs.str) : (lhs.kind < rhs.kind); +} +inline bool operator>(const string& lhs, const string& rhs) +{ + return rhs < lhs; +} +inline bool operator<=(const string& lhs, const string& rhs) +{ + return !(rhs < lhs); +} +inline bool operator>=(const string& lhs, const string& rhs) +{ + return !(lhs < rhs); +} + +inline bool +operator==(const string& lhs, const std::string& rhs) {return lhs.str == rhs;} +inline bool +operator!=(const string& lhs, const std::string& rhs) {return lhs.str != rhs;} +inline bool +operator< (const string& lhs, const std::string& rhs) {return lhs.str < rhs;} +inline bool +operator> (const string& lhs, const std::string& rhs) {return lhs.str > rhs;} +inline bool +operator<=(const string& lhs, const std::string& rhs) {return lhs.str <= rhs;} +inline bool +operator>=(const string& lhs, const std::string& rhs) {return lhs.str >= rhs;} + +inline bool +operator==(const std::string& lhs, const string& rhs) {return lhs == rhs.str;} +inline bool +operator!=(const std::string& lhs, const string& rhs) {return lhs != rhs.str;} +inline bool +operator< (const std::string& lhs, const string& rhs) {return lhs < rhs.str;} +inline bool +operator> (const std::string& lhs, const string& rhs) {return lhs > rhs.str;} +inline bool +operator<=(const std::string& lhs, const string& rhs) {return lhs <= rhs.str;} +inline bool +operator>=(const std::string& lhs, const string& rhs) {return lhs >= rhs.str;} + +inline bool +operator==(const string& lhs, const char* rhs) {return lhs.str == std::string(rhs);} +inline bool +operator!=(const string& lhs, const char* rhs) {return lhs.str != std::string(rhs);} +inline bool +operator< (const string& lhs, const char* rhs) {return lhs.str < std::string(rhs);} +inline bool +operator> (const string& lhs, const char* rhs) {return lhs.str > std::string(rhs);} +inline bool +operator<=(const string& lhs, const char* rhs) {return lhs.str <= std::string(rhs);} +inline bool +operator>=(const string& lhs, const char* rhs) {return lhs.str >= std::string(rhs);} + +inline bool +operator==(const char* lhs, const string& rhs) {return std::string(lhs) == rhs.str;} +inline bool +operator!=(const char* lhs, const string& rhs) {return std::string(lhs) != rhs.str;} +inline bool +operator< (const char* lhs, const string& rhs) {return std::string(lhs) < rhs.str;} +inline bool +operator> (const char* lhs, const string& rhs) {return std::string(lhs) > rhs.str;} +inline bool +operator<=(const char* lhs, const string& rhs) {return std::string(lhs) <= rhs.str;} +inline bool +operator>=(const char* lhs, const string& rhs) {return std::string(lhs) >= rhs.str;} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const string& s) +{ + if(s.kind == string_t::basic) + { + if(std::find(s.str.cbegin(), s.str.cend(), '\n') != s.str.cend()) + { + // it contains newline. make it multiline string. + os << "\"\"\"\n"; + for(auto i=s.str.cbegin(), e=s.str.cend(); i!=e; ++i) + { + switch(*i) + { + case '\\': {os << "\\\\"; break;} + case '\"': {os << "\\\""; break;} + case '\b': {os << "\\b"; break;} + case '\t': {os << "\\t"; break;} + case '\f': {os << "\\f"; break;} + case '\n': {os << '\n'; break;} + case '\r': + { + // since it is a multiline string, + // CRLF is not needed to be escaped. + if(std::next(i) != e && *std::next(i) == '\n') + { + os << "\r\n"; + ++i; + } + else + { + os << "\\r"; + } + break; + } + default: {os << *i; break;} + } + } + os << "\\\n\"\"\""; + return os; + } + // no newline. make it inline. + os << "\""; + for(const auto c : s.str) + { + switch(c) + { + case '\\': {os << "\\\\"; break;} + case '\"': {os << "\\\""; break;} + case '\b': {os << "\\b"; break;} + case '\t': {os << "\\t"; break;} + case '\f': {os << "\\f"; break;} + case '\n': {os << "\\n"; break;} + case '\r': {os << "\\r"; break;} + default : {os << c; break;} + } + } + os << "\""; + return os; + } + // the string `s` is literal-string. + if(std::find(s.str.cbegin(), s.str.cend(), '\n') != s.str.cend() || + std::find(s.str.cbegin(), s.str.cend(), '\'') != s.str.cend() ) + { + // contains newline or single quote. make it multiline. + os << "'''\n" << s.str << "'''"; + return os; + } + // normal literal string + os << '\'' << s.str << '\''; + return os; +} + +} // toml +#endif// TOML11_STRING_H diff --git a/src/frontend/qt_sdl/toml/toml/traits.hpp b/src/frontend/qt_sdl/toml/toml/traits.hpp new file mode 100644 index 00000000..255d9e88 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/traits.hpp @@ -0,0 +1,328 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_TRAITS_HPP +#define TOML11_TRAITS_HPP + +#include "from.hpp" +#include "into.hpp" +#include "version.hpp" + +#include +#include +#include +#include +#include +#include + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201703L +#if __has_include() +#include +#endif // has_include() +#endif // cplusplus >= C++17 + +namespace toml +{ +template class T, template class A> +class basic_value; + +namespace detail +{ +// --------------------------------------------------------------------------- +// check whether type T is a kind of container/map class + +struct has_iterator_impl +{ + template static std::true_type check(typename T::iterator*); + template static std::false_type check(...); +}; +struct has_value_type_impl +{ + template static std::true_type check(typename T::value_type*); + template static std::false_type check(...); +}; +struct has_key_type_impl +{ + template static std::true_type check(typename T::key_type*); + template static std::false_type check(...); +}; +struct has_mapped_type_impl +{ + template static std::true_type check(typename T::mapped_type*); + template static std::false_type check(...); +}; +struct has_reserve_method_impl +{ + template static std::false_type check(...); + template static std::true_type check( + decltype(std::declval().reserve(std::declval()))*); +}; +struct has_push_back_method_impl +{ + template static std::false_type check(...); + template static std::true_type check( + decltype(std::declval().push_back(std::declval()))*); +}; +struct is_comparable_impl +{ + template static std::false_type check(...); + template static std::true_type check( + decltype(std::declval() < std::declval())*); +}; + +struct has_from_toml_method_impl +{ + template class Tb, template class A> + static std::true_type check( + decltype(std::declval().from_toml( + std::declval<::toml::basic_value>()))*); + + template class Tb, template class A> + static std::false_type check(...); +}; +struct has_into_toml_method_impl +{ + template + static std::true_type check(decltype(std::declval().into_toml())*); + template + static std::false_type check(...); +}; + +struct has_specialized_from_impl +{ + template + static std::false_type check(...); + template)> + static std::true_type check(::toml::from*); +}; +struct has_specialized_into_impl +{ + template + static std::false_type check(...); + template)> + static std::true_type check(::toml::from*); +}; + + +/// Intel C++ compiler can not use decltype in parent class declaration, here +/// is a hack to work around it. https://stackoverflow.com/a/23953090/4692076 +#ifdef __INTEL_COMPILER +#define decltype(...) std::enable_if::type +#endif + +template +struct has_iterator : decltype(has_iterator_impl::check(nullptr)){}; +template +struct has_value_type : decltype(has_value_type_impl::check(nullptr)){}; +template +struct has_key_type : decltype(has_key_type_impl::check(nullptr)){}; +template +struct has_mapped_type : decltype(has_mapped_type_impl::check(nullptr)){}; +template +struct has_reserve_method : decltype(has_reserve_method_impl::check(nullptr)){}; +template +struct has_push_back_method : decltype(has_push_back_method_impl::check(nullptr)){}; +template +struct is_comparable : decltype(is_comparable_impl::check(nullptr)){}; + +template class Tb, template class A> +struct has_from_toml_method +: decltype(has_from_toml_method_impl::check(nullptr)){}; + +template +struct has_into_toml_method +: decltype(has_into_toml_method_impl::check(nullptr)){}; + +template +struct has_specialized_from : decltype(has_specialized_from_impl::check(nullptr)){}; +template +struct has_specialized_into : decltype(has_specialized_into_impl::check(nullptr)){}; + +#ifdef __INTEL_COMPILER +#undef decltype +#endif + +// --------------------------------------------------------------------------- +// C++17 and/or/not + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201703L + +using std::conjunction; +using std::disjunction; +using std::negation; + +#else + +template struct conjunction : std::true_type{}; +template struct conjunction : T{}; +template +struct conjunction : + std::conditional(T::value), conjunction, T>::type +{}; + +template struct disjunction : std::false_type{}; +template struct disjunction : T {}; +template +struct disjunction : + std::conditional(T::value), T, disjunction>::type +{}; + +template +struct negation : std::integral_constant(T::value)>{}; + +#endif + +// --------------------------------------------------------------------------- +// type checkers + +template struct is_std_pair : std::false_type{}; +template +struct is_std_pair> : std::true_type{}; + +template struct is_std_tuple : std::false_type{}; +template +struct is_std_tuple> : std::true_type{}; + +template struct is_std_forward_list : std::false_type{}; +template +struct is_std_forward_list> : std::true_type{}; + +template struct is_chrono_duration: std::false_type{}; +template +struct is_chrono_duration>: std::true_type{}; + +template +struct is_map : conjunction< // map satisfies all the following conditions + has_iterator, // has T::iterator + has_value_type, // has T::value_type + has_key_type, // has T::key_type + has_mapped_type // has T::mapped_type + >{}; +template struct is_map : is_map{}; +template struct is_map : is_map{}; +template struct is_map : is_map{}; +template struct is_map : is_map{}; + +template +struct is_container : conjunction< + negation>, // not a map + negation>, // not a std::string +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201703L +#if __has_include() + negation>, // not a std::string_view +#endif // has_include() +#endif + has_iterator, // has T::iterator + has_value_type // has T::value_type + >{}; +template struct is_container : is_container{}; +template struct is_container : is_container{}; +template struct is_container : is_container{}; +template struct is_container : is_container{}; + +template +struct is_basic_value: std::false_type{}; +template struct is_basic_value : is_basic_value{}; +template struct is_basic_value : is_basic_value{}; +template struct is_basic_value : is_basic_value{}; +template struct is_basic_value : is_basic_value{}; +template class M, template class V> +struct is_basic_value<::toml::basic_value>: std::true_type{}; + +// --------------------------------------------------------------------------- +// C++14 index_sequence + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201402L + +using std::index_sequence; +using std::make_index_sequence; + +#else + +template struct index_sequence{}; + +template struct push_back_index_sequence{}; +template +struct push_back_index_sequence, N> +{ + typedef index_sequence type; +}; + +template +struct index_sequence_maker +{ + typedef typename push_back_index_sequence< + typename index_sequence_maker::type, N>::type type; +}; +template<> +struct index_sequence_maker<0> +{ + typedef index_sequence<0> type; +}; +template +using make_index_sequence = typename index_sequence_maker::type; + +#endif // cplusplus >= 2014 + +// --------------------------------------------------------------------------- +// C++14 enable_if_t + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201402L + +using std::enable_if_t; + +#else + +template +using enable_if_t = typename std::enable_if::type; + +#endif // cplusplus >= 2014 + +// --------------------------------------------------------------------------- +// return_type_of_t + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201703L && defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable>=201703 + +template +using return_type_of_t = std::invoke_result_t; + +#else +// result_of is deprecated after C++17 +template +using return_type_of_t = typename std::result_of::type; + +#endif + +// --------------------------------------------------------------------------- +// is_string_literal +// +// to use this, pass `typename remove_reference::type` to T. + +template +struct is_string_literal: +disjunction< + std::is_same, + conjunction< + std::is_array, + std::is_same::type> + > + >{}; + +// --------------------------------------------------------------------------- +// C++20 remove_cvref_t + +template +struct remove_cvref +{ + using type = typename std::remove_cv< + typename std::remove_reference::type>::type; +}; + +template +using remove_cvref_t = typename remove_cvref::type; + +}// detail +}//toml +#endif // TOML_TRAITS diff --git a/src/frontend/qt_sdl/toml/toml/types.hpp b/src/frontend/qt_sdl/toml/toml/types.hpp new file mode 100644 index 00000000..1e420e7f --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/types.hpp @@ -0,0 +1,173 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_TYPES_HPP +#define TOML11_TYPES_HPP +#include +#include + +#include "comments.hpp" +#include "datetime.hpp" +#include "string.hpp" +#include "traits.hpp" + +namespace toml +{ + +template class Table, // map-like class + template class Array> // vector-like class +class basic_value; + +using character = char; +using key = std::string; + +#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ <= 4 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wshadow" +#endif + +using boolean = bool; +using integer = std::int64_t; +using floating = double; // "float" is a keyword, cannot use it here. +// the following stuffs are structs defined here, so aliases are not needed. +// - string +// - offset_datetime +// - offset_datetime +// - local_datetime +// - local_date +// - local_time + +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic pop +#endif + +// default toml::value and default array/table. these are defined after defining +// basic_value itself. +// using value = basic_value; +// using array = typename value::array_type; +// using table = typename value::table_type; + +// to avoid warnings about `value_t::integer` is "shadowing" toml::integer in +// GCC -Wshadow=global. +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic push +# if 7 <= __GNUC__ +# pragma GCC diagnostic ignored "-Wshadow=global" +# else // gcc-6 or older +# pragma GCC diagnostic ignored "-Wshadow" +# endif +#endif +enum class value_t : std::uint8_t +{ + empty = 0, + boolean = 1, + integer = 2, + floating = 3, + string = 4, + offset_datetime = 5, + local_datetime = 6, + local_date = 7, + local_time = 8, + array = 9, + table = 10, +}; +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic pop +#endif + +template +inline std::basic_ostream& +operator<<(std::basic_ostream& os, value_t t) +{ + switch(t) + { + case value_t::boolean : os << "boolean"; return os; + case value_t::integer : os << "integer"; return os; + case value_t::floating : os << "floating"; return os; + case value_t::string : os << "string"; return os; + case value_t::offset_datetime : os << "offset_datetime"; return os; + case value_t::local_datetime : os << "local_datetime"; return os; + case value_t::local_date : os << "local_date"; return os; + case value_t::local_time : os << "local_time"; return os; + case value_t::array : os << "array"; return os; + case value_t::table : os << "table"; return os; + case value_t::empty : os << "empty"; return os; + default : os << "unknown"; return os; + } +} + +template, + typename alloc = std::allocator> +inline std::basic_string stringize(value_t t) +{ + std::basic_ostringstream oss; + oss << t; + return oss.str(); +} + +namespace detail +{ + +// helper to define a type that represents a value_t value. +template +using value_t_constant = std::integral_constant; + +// meta-function that convertes from value_t to the exact toml type that corresponds to. +// It takes toml::basic_value type because array and table types depend on it. +template struct enum_to_type {using type = void ;}; +template struct enum_to_type{using type = void ;}; +template struct enum_to_type{using type = boolean ;}; +template struct enum_to_type{using type = integer ;}; +template struct enum_to_type{using type = floating ;}; +template struct enum_to_type{using type = string ;}; +template struct enum_to_type{using type = offset_datetime ;}; +template struct enum_to_type{using type = local_datetime ;}; +template struct enum_to_type{using type = local_date ;}; +template struct enum_to_type{using type = local_time ;}; +template struct enum_to_type{using type = typename Value::array_type;}; +template struct enum_to_type{using type = typename Value::table_type;}; + +// meta-function that converts from an exact toml type to the enum that corresponds to. +template +struct type_to_enum : std::conditional< + std::is_same::value, // if T == array_type, + value_t_constant, // then value_t::array + typename std::conditional< // else... + std::is_same::value, // if T == table_type + value_t_constant, // then value_t::table + value_t_constant // else value_t::empty + >::type + >::type {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; + +// meta-function that checks the type T is the same as one of the toml::* types. +template +struct is_exact_toml_type : disjunction< + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same + >{}; +template struct is_exact_toml_type : is_exact_toml_type{}; +template struct is_exact_toml_type : is_exact_toml_type{}; +template struct is_exact_toml_type : is_exact_toml_type{}; +template struct is_exact_toml_type: is_exact_toml_type{}; + +} // detail +} // toml + +#endif// TOML11_TYPES_H diff --git a/src/frontend/qt_sdl/toml/toml/utility.hpp b/src/frontend/qt_sdl/toml/toml/utility.hpp new file mode 100644 index 00000000..53a18b94 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/utility.hpp @@ -0,0 +1,150 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_UTILITY_HPP +#define TOML11_UTILITY_HPP +#include +#include +#include + +#include "traits.hpp" +#include "version.hpp" + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201402L +# define TOML11_MARK_AS_DEPRECATED(msg) [[deprecated(msg)]] +#elif defined(__GNUC__) +# define TOML11_MARK_AS_DEPRECATED(msg) __attribute__((deprecated(msg))) +#elif defined(_MSC_VER) +# define TOML11_MARK_AS_DEPRECATED(msg) __declspec(deprecated(msg)) +#else +# define TOML11_MARK_AS_DEPRECATED +#endif + +namespace toml +{ + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201402L + +using std::make_unique; + +#else + +template +inline std::unique_ptr make_unique(Ts&& ... args) +{ + return std::unique_ptr(new T(std::forward(args)...)); +} + +#endif // TOML11_CPLUSPLUS_STANDARD_VERSION >= 2014 + +namespace detail +{ +template +void try_reserve_impl(Container& container, std::size_t N, std::true_type) +{ + container.reserve(N); + return; +} +template +void try_reserve_impl(Container&, std::size_t, std::false_type) noexcept +{ + return; +} +} // detail + +template +void try_reserve(Container& container, std::size_t N) +{ + if(N <= container.size()) {return;} + detail::try_reserve_impl(container, N, detail::has_reserve_method{}); + return; +} + +namespace detail +{ +inline std::string concat_to_string_impl(std::ostringstream& oss) +{ + return oss.str(); +} +template +std::string concat_to_string_impl(std::ostringstream& oss, T&& head, Ts&& ... tail) +{ + oss << std::forward(head); + return concat_to_string_impl(oss, std::forward(tail) ... ); +} +} // detail + +template +std::string concat_to_string(Ts&& ... args) +{ + std::ostringstream oss; + oss << std::boolalpha << std::fixed; + return detail::concat_to_string_impl(oss, std::forward(args) ...); +} + +template +T from_string(const std::string& str, T opt) +{ + T v(opt); + std::istringstream iss(str); + iss >> v; + return v; +} + +namespace detail +{ +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201402L +template +decltype(auto) last_one(T&& tail) noexcept +{ + return std::forward(tail); +} + +template +decltype(auto) last_one(T&& /*head*/, Ts&& ... tail) noexcept +{ + return last_one(std::forward(tail)...); +} +#else // C++11 +// The following code +// ```cpp +// 1 | template +// 2 | auto last_one(T&& /*head*/, Ts&& ... tail) +// 3 | -> decltype(last_one(std::forward(tail)...)) +// 4 | { +// 5 | return last_one(std::forward(tail)...); +// 6 | } +// ``` +// does not work because the function `last_one(...)` is not yet defined at +// line #3, so `decltype()` cannot deduce the type returned from `last_one`. +// So we need to determine return type in a different way, like a meta func. + +template +struct last_one_in_pack +{ + using type = typename last_one_in_pack::type; +}; +template +struct last_one_in_pack +{ + using type = T; +}; +template +using last_one_in_pack_t = typename last_one_in_pack::type; + +template +T&& last_one(T&& tail) noexcept +{ + return std::forward(tail); +} +template +enable_if_t<(sizeof...(Ts) > 0), last_one_in_pack_t> +last_one(T&& /*head*/, Ts&& ... tail) +{ + return last_one(std::forward(tail)...); +} + +#endif +} // detail + +}// toml +#endif // TOML11_UTILITY diff --git a/src/frontend/qt_sdl/toml/toml/value.hpp b/src/frontend/qt_sdl/toml/toml/value.hpp new file mode 100644 index 00000000..1b43db8d --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/value.hpp @@ -0,0 +1,2035 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_VALUE_HPP +#define TOML11_VALUE_HPP +#include + +#include "comments.hpp" +#include "exception.hpp" +#include "into.hpp" +#include "region.hpp" +#include "source_location.hpp" +#include "storage.hpp" +#include "traits.hpp" +#include "types.hpp" +#include "utility.hpp" + +namespace toml +{ + +namespace detail +{ + +// to show error messages. not recommended for users. +template +inline region_base const* get_region(const Value& v) +{ + return v.region_info_.get(); +} + +template +void change_region(Value& v, region reg) +{ + v.region_info_ = std::make_shared(std::move(reg)); + return; +} + +template +[[noreturn]] inline void +throw_bad_cast(const std::string& funcname, value_t actual, const Value& v) +{ + throw type_error(detail::format_underline( + concat_to_string(funcname, "bad_cast to ", Expected), { + {v.location(), concat_to_string("the actual type is ", actual)} + }), v.location()); +} + +// Throw `out_of_range` from `toml::value::at()` and `toml::find()` +// after generating an error message. +// +// The implementation is a bit complicated and there are many edge-cases. +// If you are not interested in the error message generation, just skip this. +template +[[noreturn]] void +throw_key_not_found_error(const Value& v, const key& ky) +{ + // The top-level table has its region at the first character of the file. + // That means that, in the case when a key is not found in the top-level + // table, the error message points to the first character. If the file has + // its first table at the first line, the error message would be like this. + // ```console + // [error] key "a" not found + // --> example.toml + // | + // 1 | [table] + // | ^------ in this table + // ``` + // It actually points to the top-level table at the first character, + // not `[table]`. But it is too confusing. To avoid the confusion, the error + // message should explicitly say "key not found in the top-level table", + // or "the parsed file is empty" if there is no content at all (0 bytes in file). + const auto loc = v.location(); + if(loc.line() == 1 && loc.region() == 0) + { + // First line with a zero-length region means "empty file". + // The region will be generated at `parse_toml_file` function + // if the file contains no bytes. + throw std::out_of_range(format_underline(concat_to_string( + "key \"", ky, "\" not found in the top-level table"), { + {loc, "the parsed file is empty"} + })); + } + else if(loc.line() == 1 && loc.region() == 1) + { + // Here it assumes that top-level table starts at the first character. + // The region corresponds to the top-level table will be generated at + // `parse_toml_file` function. + // It also assumes that the top-level table size is just one and + // the line number is `1`. It is always satisfied. And those conditions + // are satisfied only if the table is the top-level table. + // + // 1. one-character dot-key at the first line + // ```toml + // a.b = "c" + // ``` + // toml11 counts whole key as the table key. Here, `a.b` is the region + // of the table "a". It could be counter intuitive, but it works. + // The size of the region is 3, not 1. The above example is the shortest + // dot-key example. The size cannot be 1. + // + // 2. one-character inline-table at the first line + // ```toml + // a = {b = "c"} + // ``` + // toml11 considers the inline table body as the table region. Here, + // `{b = "c"}` is the region of the table "a". The size of the region + // is 9, not 1. The shotest inline table still has two characters, `{` + // and `}`. The size cannot be 1. + // + // 3. one-character table declaration at the first line + // ```toml + // [a] + // ``` + // toml11 considers the whole table key as the table region. Here, + // `[a]` is the table region. The size is 3, not 1. + // + throw std::out_of_range(format_underline(concat_to_string( + "key \"", ky, "\" not found in the top-level table"), { + {loc, "the top-level table starts here"} + })); + } + else + { + // normal table. + throw std::out_of_range(format_underline(concat_to_string( + "key \"", ky, "\" not found"), { {loc, "in this table"} })); + } +} + +// switch by `value_t` at the compile time. +template +struct switch_cast {}; +#define TOML11_GENERATE_SWITCH_CASTER(TYPE) \ + template<> \ + struct switch_cast \ + { \ + template \ + static typename Value::TYPE##_type& invoke(Value& v) \ + { \ + return v.as_##TYPE(); \ + } \ + template \ + static typename Value::TYPE##_type const& invoke(const Value& v) \ + { \ + return v.as_##TYPE(); \ + } \ + template \ + static typename Value::TYPE##_type&& invoke(Value&& v) \ + { \ + return std::move(v).as_##TYPE(); \ + } \ + }; \ + /**/ +TOML11_GENERATE_SWITCH_CASTER(boolean) +TOML11_GENERATE_SWITCH_CASTER(integer) +TOML11_GENERATE_SWITCH_CASTER(floating) +TOML11_GENERATE_SWITCH_CASTER(string) +TOML11_GENERATE_SWITCH_CASTER(offset_datetime) +TOML11_GENERATE_SWITCH_CASTER(local_datetime) +TOML11_GENERATE_SWITCH_CASTER(local_date) +TOML11_GENERATE_SWITCH_CASTER(local_time) +TOML11_GENERATE_SWITCH_CASTER(array) +TOML11_GENERATE_SWITCH_CASTER(table) + +#undef TOML11_GENERATE_SWITCH_CASTER + +}// detail + +template class Table = std::unordered_map, + template class Array = std::vector> +class basic_value +{ + template + static void assigner(T& dst, U&& v) + { + const auto tmp = ::new(std::addressof(dst)) T(std::forward(v)); + assert(tmp == std::addressof(dst)); + (void)tmp; + } + + using region_base = detail::region_base; + + template class T, + template class A> + friend class basic_value; + + public: + + using comment_type = Comment; + using key_type = ::toml::key; + using value_type = basic_value; + using boolean_type = ::toml::boolean; + using integer_type = ::toml::integer; + using floating_type = ::toml::floating; + using string_type = ::toml::string; + using local_time_type = ::toml::local_time; + using local_date_type = ::toml::local_date; + using local_datetime_type = ::toml::local_datetime; + using offset_datetime_type = ::toml::offset_datetime; + using array_type = Array; + using table_type = Table; + + public: + + basic_value() noexcept + : type_(value_t::empty), + region_info_(std::make_shared(region_base{})) + {} + ~basic_value() noexcept {this->cleanup();} + + basic_value(const basic_value& v) + : type_(v.type()), region_info_(v.region_info_), comments_(v.comments_) + { + switch(v.type()) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : assigner(array_ , v.array_ ); break; + case value_t::table : assigner(table_ , v.table_ ); break; + default: break; + } + } + basic_value(basic_value&& v) + : type_(v.type()), region_info_(std::move(v.region_info_)), + comments_(std::move(v.comments_)) + { + switch(this->type_) // here this->type_ is already initialized + { + case value_t::boolean : assigner(boolean_ , std::move(v.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(v.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(v.floating_ )); break; + case value_t::string : assigner(string_ , std::move(v.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(v.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(v.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(v.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(v.local_time_ )); break; + case value_t::array : assigner(array_ , std::move(v.array_ )); break; + case value_t::table : assigner(table_ , std::move(v.table_ )); break; + default: break; + } + } + basic_value& operator=(const basic_value& v) + { + this->cleanup(); + this->region_info_ = v.region_info_; + this->comments_ = v.comments_; + this->type_ = v.type(); + switch(this->type_) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : assigner(array_ , v.array_ ); break; + case value_t::table : assigner(table_ , v.table_ ); break; + default: break; + } + return *this; + } + basic_value& operator=(basic_value&& v) + { + this->cleanup(); + this->region_info_ = std::move(v.region_info_); + this->comments_ = std::move(v.comments_); + this->type_ = v.type(); + switch(this->type_) + { + case value_t::boolean : assigner(boolean_ , std::move(v.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(v.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(v.floating_ )); break; + case value_t::string : assigner(string_ , std::move(v.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(v.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(v.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(v.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(v.local_time_ )); break; + case value_t::array : assigner(array_ , std::move(v.array_ )); break; + case value_t::table : assigner(table_ , std::move(v.table_ )); break; + default: break; + } + return *this; + } + + // overwrite comments ---------------------------------------------------- + + basic_value(const basic_value& v, std::vector com) + : type_(v.type()), region_info_(v.region_info_), + comments_(std::move(com)) + { + switch(v.type()) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : assigner(array_ , v.array_ ); break; + case value_t::table : assigner(table_ , v.table_ ); break; + default: break; + } + } + + basic_value(basic_value&& v, std::vector com) + : type_(v.type()), region_info_(std::move(v.region_info_)), + comments_(std::move(com)) + { + switch(this->type_) // here this->type_ is already initialized + { + case value_t::boolean : assigner(boolean_ , std::move(v.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(v.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(v.floating_ )); break; + case value_t::string : assigner(string_ , std::move(v.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(v.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(v.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(v.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(v.local_time_ )); break; + case value_t::array : assigner(array_ , std::move(v.array_ )); break; + case value_t::table : assigner(table_ , std::move(v.table_ )); break; + default: break; + } + } + + // ----------------------------------------------------------------------- + // conversion between different basic_values. + template class T, + template class A> + basic_value(const basic_value& v) + : type_(v.type()), region_info_(v.region_info_), comments_(v.comments()) + { + switch(v.type()) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : + { + array_type tmp(v.as_array(std::nothrow).begin(), + v.as_array(std::nothrow).end()); + assigner(array_, std::move(tmp)); + break; + } + case value_t::table : + { + table_type tmp(v.as_table(std::nothrow).begin(), + v.as_table(std::nothrow).end()); + assigner(table_, std::move(tmp)); + break; + } + default: break; + } + } + template class T, + template class A> + basic_value(const basic_value& v, std::vector com) + : type_(v.type()), region_info_(v.region_info_), + comments_(std::move(com)) + { + switch(v.type()) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : + { + array_type tmp(v.as_array(std::nothrow).begin(), + v.as_array(std::nothrow).end()); + assigner(array_, std::move(tmp)); + break; + } + case value_t::table : + { + table_type tmp(v.as_table(std::nothrow).begin(), + v.as_table(std::nothrow).end()); + assigner(table_, std::move(tmp)); + break; + } + default: break; + } + } + template class T, + template class A> + basic_value& operator=(const basic_value& v) + { + this->region_info_ = v.region_info_; + this->comments_ = comment_type(v.comments()); + this->type_ = v.type(); + switch(v.type()) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : + { + array_type tmp(v.as_array(std::nothrow).begin(), + v.as_array(std::nothrow).end()); + assigner(array_, std::move(tmp)); + break; + } + case value_t::table : + { + table_type tmp(v.as_table(std::nothrow).begin(), + v.as_table(std::nothrow).end()); + assigner(table_, std::move(tmp)); + break; + } + default: break; + } + return *this; + } + + // boolean ============================================================== + + basic_value(boolean b) + : type_(value_t::boolean), + region_info_(std::make_shared(region_base{})) + { + assigner(this->boolean_, b); + } + basic_value& operator=(boolean b) + { + this->cleanup(); + this->type_ = value_t::boolean; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->boolean_, b); + return *this; + } + basic_value(boolean b, std::vector com) + : type_(value_t::boolean), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->boolean_, b); + } + + // integer ============================================================== + + template, detail::negation>>::value, + std::nullptr_t>::type = nullptr> + basic_value(T i) + : type_(value_t::integer), + region_info_(std::make_shared(region_base{})) + { + assigner(this->integer_, static_cast(i)); + } + + template, detail::negation>>::value, + std::nullptr_t>::type = nullptr> + basic_value& operator=(T i) + { + this->cleanup(); + this->type_ = value_t::integer; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->integer_, static_cast(i)); + return *this; + } + + template, detail::negation>>::value, + std::nullptr_t>::type = nullptr> + basic_value(T i, std::vector com) + : type_(value_t::integer), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->integer_, static_cast(i)); + } + + // floating ============================================================= + + template::value, std::nullptr_t>::type = nullptr> + basic_value(T f) + : type_(value_t::floating), + region_info_(std::make_shared(region_base{})) + { + assigner(this->floating_, static_cast(f)); + } + + + template::value, std::nullptr_t>::type = nullptr> + basic_value& operator=(T f) + { + this->cleanup(); + this->type_ = value_t::floating; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->floating_, static_cast(f)); + return *this; + } + + template::value, std::nullptr_t>::type = nullptr> + basic_value(T f, std::vector com) + : type_(value_t::floating), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->floating_, f); + } + + // string =============================================================== + + basic_value(toml::string s) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, std::move(s)); + } + basic_value& operator=(toml::string s) + { + this->cleanup(); + this->type_ = value_t::string ; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->string_, s); + return *this; + } + basic_value(toml::string s, std::vector com) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->string_, std::move(s)); + } + + basic_value(std::string s) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(std::move(s))); + } + basic_value& operator=(std::string s) + { + this->cleanup(); + this->type_ = value_t::string ; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->string_, toml::string(std::move(s))); + return *this; + } + basic_value(std::string s, string_t kind) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(std::move(s), kind)); + } + basic_value(std::string s, std::vector com) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->string_, toml::string(std::move(s))); + } + basic_value(std::string s, string_t kind, std::vector com) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->string_, toml::string(std::move(s), kind)); + } + + basic_value(const char* s) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(std::string(s))); + } + basic_value& operator=(const char* s) + { + this->cleanup(); + this->type_ = value_t::string ; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->string_, toml::string(std::string(s))); + return *this; + } + basic_value(const char* s, string_t kind) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(std::string(s), kind)); + } + basic_value(const char* s, std::vector com) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->string_, toml::string(std::string(s))); + } + basic_value(const char* s, string_t kind, std::vector com) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->string_, toml::string(std::string(s), kind)); + } + +#if defined(TOML11_USING_STRING_VIEW) && TOML11_USING_STRING_VIEW>0 + basic_value(std::string_view s) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(s)); + } + basic_value& operator=(std::string_view s) + { + this->cleanup(); + this->type_ = value_t::string ; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->string_, toml::string(s)); + return *this; + } + basic_value(std::string_view s, std::vector com) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->string_, toml::string(s)); + } + basic_value(std::string_view s, string_t kind) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(s, kind)); + } + basic_value(std::string_view s, string_t kind, std::vector com) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->string_, toml::string(s, kind)); + } +#endif + + // local date =========================================================== + + basic_value(const local_date& ld) + : type_(value_t::local_date), + region_info_(std::make_shared(region_base{})) + { + assigner(this->local_date_, ld); + } + basic_value& operator=(const local_date& ld) + { + this->cleanup(); + this->type_ = value_t::local_date; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->local_date_, ld); + return *this; + } + basic_value(const local_date& ld, std::vector com) + : type_(value_t::local_date), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->local_date_, ld); + } + + // local time =========================================================== + + basic_value(const local_time& lt) + : type_(value_t::local_time), + region_info_(std::make_shared(region_base{})) + { + assigner(this->local_time_, lt); + } + basic_value(const local_time& lt, std::vector com) + : type_(value_t::local_time), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->local_time_, lt); + } + basic_value& operator=(const local_time& lt) + { + this->cleanup(); + this->type_ = value_t::local_time; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->local_time_, lt); + return *this; + } + + template + basic_value(const std::chrono::duration& dur) + : type_(value_t::local_time), + region_info_(std::make_shared(region_base{})) + { + assigner(this->local_time_, local_time(dur)); + } + template + basic_value(const std::chrono::duration& dur, + std::vector com) + : type_(value_t::local_time), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->local_time_, local_time(dur)); + } + template + basic_value& operator=(const std::chrono::duration& dur) + { + this->cleanup(); + this->type_ = value_t::local_time; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->local_time_, local_time(dur)); + return *this; + } + + // local datetime ======================================================= + + basic_value(const local_datetime& ldt) + : type_(value_t::local_datetime), + region_info_(std::make_shared(region_base{})) + { + assigner(this->local_datetime_, ldt); + } + basic_value(const local_datetime& ldt, std::vector com) + : type_(value_t::local_datetime), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->local_datetime_, ldt); + } + basic_value& operator=(const local_datetime& ldt) + { + this->cleanup(); + this->type_ = value_t::local_datetime; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->local_datetime_, ldt); + return *this; + } + + // offset datetime ====================================================== + + basic_value(const offset_datetime& odt) + : type_(value_t::offset_datetime), + region_info_(std::make_shared(region_base{})) + { + assigner(this->offset_datetime_, odt); + } + basic_value(const offset_datetime& odt, std::vector com) + : type_(value_t::offset_datetime), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->offset_datetime_, odt); + } + basic_value& operator=(const offset_datetime& odt) + { + this->cleanup(); + this->type_ = value_t::offset_datetime; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->offset_datetime_, odt); + return *this; + } + basic_value(const std::chrono::system_clock::time_point& tp) + : type_(value_t::offset_datetime), + region_info_(std::make_shared(region_base{})) + { + assigner(this->offset_datetime_, offset_datetime(tp)); + } + basic_value(const std::chrono::system_clock::time_point& tp, + std::vector com) + : type_(value_t::offset_datetime), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->offset_datetime_, offset_datetime(tp)); + } + basic_value& operator=(const std::chrono::system_clock::time_point& tp) + { + this->cleanup(); + this->type_ = value_t::offset_datetime; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->offset_datetime_, offset_datetime(tp)); + return *this; + } + + // array ================================================================ + + basic_value(const array_type& ary) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})) + { + assigner(this->array_, ary); + } + basic_value(const array_type& ary, std::vector com) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->array_, ary); + } + basic_value& operator=(const array_type& ary) + { + this->cleanup(); + this->type_ = value_t::array ; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->array_, ary); + return *this; + } + + // array (initializer_list) ---------------------------------------------- + + template::value, + std::nullptr_t>::type = nullptr> + basic_value(std::initializer_list list) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})) + { + array_type ary(list.begin(), list.end()); + assigner(this->array_, std::move(ary)); + } + template::value, + std::nullptr_t>::type = nullptr> + basic_value(std::initializer_list list, std::vector com) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + array_type ary(list.begin(), list.end()); + assigner(this->array_, std::move(ary)); + } + template::value, + std::nullptr_t>::type = nullptr> + basic_value& operator=(std::initializer_list list) + { + this->cleanup(); + this->type_ = value_t::array; + this->region_info_ = std::make_shared(region_base{}); + + array_type ary(list.begin(), list.end()); + assigner(this->array_, std::move(ary)); + return *this; + } + + // array (STL Containers) ------------------------------------------------ + + template>, + detail::is_container + >::value, std::nullptr_t>::type = nullptr> + basic_value(const T& list) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})) + { + static_assert(std::is_convertible::value, + "elements of a container should be convertible to toml::value"); + + array_type ary(list.size()); + std::copy(list.begin(), list.end(), ary.begin()); + assigner(this->array_, std::move(ary)); + } + template>, + detail::is_container + >::value, std::nullptr_t>::type = nullptr> + basic_value(const T& list, std::vector com) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + static_assert(std::is_convertible::value, + "elements of a container should be convertible to toml::value"); + + array_type ary(list.size()); + std::copy(list.begin(), list.end(), ary.begin()); + assigner(this->array_, std::move(ary)); + } + template>, + detail::is_container + >::value, std::nullptr_t>::type = nullptr> + basic_value& operator=(const T& list) + { + static_assert(std::is_convertible::value, + "elements of a container should be convertible to toml::value"); + + this->cleanup(); + this->type_ = value_t::array; + this->region_info_ = std::make_shared(region_base{}); + + array_type ary(list.size()); + std::copy(list.begin(), list.end(), ary.begin()); + assigner(this->array_, std::move(ary)); + return *this; + } + + // table ================================================================ + + basic_value(const table_type& tab) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})) + { + assigner(this->table_, tab); + } + basic_value(const table_type& tab, std::vector com) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->table_, tab); + } + basic_value& operator=(const table_type& tab) + { + this->cleanup(); + this->type_ = value_t::table; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->table_, tab); + return *this; + } + + // initializer-list ------------------------------------------------------ + + basic_value(std::initializer_list> list) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})) + { + table_type tab; + for(const auto& elem : list) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + } + + basic_value(std::initializer_list> list, + std::vector com) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + table_type tab; + for(const auto& elem : list) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + } + basic_value& operator=(std::initializer_list> list) + { + this->cleanup(); + this->type_ = value_t::table; + this->region_info_ = std::make_shared(region_base{}); + + table_type tab; + for(const auto& elem : list) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + return *this; + } + + // other table-like ----------------------------------------------------- + + template>, + detail::is_map + >::value, std::nullptr_t>::type = nullptr> + basic_value(const Map& mp) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})) + { + table_type tab; + for(const auto& elem : mp) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + } + template>, + detail::is_map + >::value, std::nullptr_t>::type = nullptr> + basic_value(const Map& mp, std::vector com) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + table_type tab; + for(const auto& elem : mp) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + } + template>, + detail::is_map + >::value, std::nullptr_t>::type = nullptr> + basic_value& operator=(const Map& mp) + { + this->cleanup(); + this->type_ = value_t::table; + this->region_info_ = std::make_shared(region_base{}); + + table_type tab; + for(const auto& elem : mp) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + return *this; + } + + // user-defined ========================================================= + + // convert using into_toml() method ------------------------------------- + + template::value, std::nullptr_t>::type = nullptr> + basic_value(const T& ud): basic_value(ud.into_toml()) {} + + template::value, std::nullptr_t>::type = nullptr> + basic_value(const T& ud, std::vector com) + : basic_value(ud.into_toml(), std::move(com)) + {} + template::value, std::nullptr_t>::type = nullptr> + basic_value& operator=(const T& ud) + { + *this = ud.into_toml(); + return *this; + } + + // convert using into struct ----------------------------------------- + + template)> + basic_value(const T& ud): basic_value(::toml::into::into_toml(ud)) {} + template)> + basic_value(const T& ud, std::vector com) + : basic_value(::toml::into::into_toml(ud), std::move(com)) + {} + template)> + basic_value& operator=(const T& ud) + { + *this = ::toml::into::into_toml(ud); + return *this; + } + + // for internal use ------------------------------------------------------ + // + // Those constructors take detail::region that contains parse result. + + basic_value(boolean b, detail::region reg, std::vector cm) + : type_(value_t::boolean), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->boolean_, b); + } + template, detail::negation> + >::value, std::nullptr_t>::type = nullptr> + basic_value(T i, detail::region reg, std::vector cm) + : type_(value_t::integer), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->integer_, static_cast(i)); + } + template::value, std::nullptr_t>::type = nullptr> + basic_value(T f, detail::region reg, std::vector cm) + : type_(value_t::floating), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->floating_, static_cast(f)); + } + basic_value(toml::string s, detail::region reg, + std::vector cm) + : type_(value_t::string), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->string_, std::move(s)); + } + basic_value(const local_date& ld, detail::region reg, + std::vector cm) + : type_(value_t::local_date), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->local_date_, ld); + } + basic_value(const local_time& lt, detail::region reg, + std::vector cm) + : type_(value_t::local_time), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->local_time_, lt); + } + basic_value(const local_datetime& ldt, detail::region reg, + std::vector cm) + : type_(value_t::local_datetime), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->local_datetime_, ldt); + } + basic_value(const offset_datetime& odt, detail::region reg, + std::vector cm) + : type_(value_t::offset_datetime), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->offset_datetime_, odt); + } + basic_value(const array_type& ary, detail::region reg, + std::vector cm) + : type_(value_t::array), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->array_, ary); + } + basic_value(const table_type& tab, detail::region reg, + std::vector cm) + : type_(value_t::table), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->table_, tab); + } + + template::value, + std::nullptr_t>::type = nullptr> + basic_value(std::pair parse_result, std::vector com) + : basic_value(std::move(parse_result.first), + std::move(parse_result.second), + std::move(com)) + {} + + // type checking and casting ============================================ + + template::value, + std::nullptr_t>::type = nullptr> + bool is() const noexcept + { + return detail::type_to_enum::value == this->type_; + } + bool is(value_t t) const noexcept {return t == this->type_;} + + bool is_uninitialized() const noexcept {return this->is(value_t::empty );} + bool is_boolean() const noexcept {return this->is(value_t::boolean );} + bool is_integer() const noexcept {return this->is(value_t::integer );} + bool is_floating() const noexcept {return this->is(value_t::floating );} + bool is_string() const noexcept {return this->is(value_t::string );} + bool is_offset_datetime() const noexcept {return this->is(value_t::offset_datetime);} + bool is_local_datetime() const noexcept {return this->is(value_t::local_datetime );} + bool is_local_date() const noexcept {return this->is(value_t::local_date );} + bool is_local_time() const noexcept {return this->is(value_t::local_time );} + bool is_array() const noexcept {return this->is(value_t::array );} + bool is_table() const noexcept {return this->is(value_t::table );} + + value_t type() const noexcept {return type_;} + + template + typename detail::enum_to_type::type& cast() & + { + if(this->type_ != T) + { + detail::throw_bad_cast("toml::value::cast: ", this->type_, *this); + } + return detail::switch_cast::invoke(*this); + } + template + typename detail::enum_to_type::type const& cast() const& + { + if(this->type_ != T) + { + detail::throw_bad_cast("toml::value::cast: ", this->type_, *this); + } + return detail::switch_cast::invoke(*this); + } + template + typename detail::enum_to_type::type&& cast() && + { + if(this->type_ != T) + { + detail::throw_bad_cast("toml::value::cast: ", this->type_, *this); + } + return detail::switch_cast::invoke(std::move(*this)); + } + + // ------------------------------------------------------------------------ + // nothrow version + + boolean const& as_boolean (const std::nothrow_t&) const& noexcept {return this->boolean_;} + integer const& as_integer (const std::nothrow_t&) const& noexcept {return this->integer_;} + floating const& as_floating (const std::nothrow_t&) const& noexcept {return this->floating_;} + string const& as_string (const std::nothrow_t&) const& noexcept {return this->string_;} + offset_datetime const& as_offset_datetime(const std::nothrow_t&) const& noexcept {return this->offset_datetime_;} + local_datetime const& as_local_datetime (const std::nothrow_t&) const& noexcept {return this->local_datetime_;} + local_date const& as_local_date (const std::nothrow_t&) const& noexcept {return this->local_date_;} + local_time const& as_local_time (const std::nothrow_t&) const& noexcept {return this->local_time_;} + array_type const& as_array (const std::nothrow_t&) const& noexcept {return this->array_.value();} + table_type const& as_table (const std::nothrow_t&) const& noexcept {return this->table_.value();} + + boolean & as_boolean (const std::nothrow_t&) & noexcept {return this->boolean_;} + integer & as_integer (const std::nothrow_t&) & noexcept {return this->integer_;} + floating & as_floating (const std::nothrow_t&) & noexcept {return this->floating_;} + string & as_string (const std::nothrow_t&) & noexcept {return this->string_;} + offset_datetime& as_offset_datetime(const std::nothrow_t&) & noexcept {return this->offset_datetime_;} + local_datetime & as_local_datetime (const std::nothrow_t&) & noexcept {return this->local_datetime_;} + local_date & as_local_date (const std::nothrow_t&) & noexcept {return this->local_date_;} + local_time & as_local_time (const std::nothrow_t&) & noexcept {return this->local_time_;} + array_type & as_array (const std::nothrow_t&) & noexcept {return this->array_.value();} + table_type & as_table (const std::nothrow_t&) & noexcept {return this->table_.value();} + + boolean && as_boolean (const std::nothrow_t&) && noexcept {return std::move(this->boolean_);} + integer && as_integer (const std::nothrow_t&) && noexcept {return std::move(this->integer_);} + floating && as_floating (const std::nothrow_t&) && noexcept {return std::move(this->floating_);} + string && as_string (const std::nothrow_t&) && noexcept {return std::move(this->string_);} + offset_datetime&& as_offset_datetime(const std::nothrow_t&) && noexcept {return std::move(this->offset_datetime_);} + local_datetime && as_local_datetime (const std::nothrow_t&) && noexcept {return std::move(this->local_datetime_);} + local_date && as_local_date (const std::nothrow_t&) && noexcept {return std::move(this->local_date_);} + local_time && as_local_time (const std::nothrow_t&) && noexcept {return std::move(this->local_time_);} + array_type && as_array (const std::nothrow_t&) && noexcept {return std::move(this->array_.value());} + table_type && as_table (const std::nothrow_t&) && noexcept {return std::move(this->table_.value());} + + // ======================================================================== + // throw version + // ------------------------------------------------------------------------ + // const reference {{{ + + boolean const& as_boolean() const& + { + if(this->type_ != value_t::boolean) + { + detail::throw_bad_cast( + "toml::value::as_boolean(): ", this->type_, *this); + } + return this->boolean_; + } + integer const& as_integer() const& + { + if(this->type_ != value_t::integer) + { + detail::throw_bad_cast( + "toml::value::as_integer(): ", this->type_, *this); + } + return this->integer_; + } + floating const& as_floating() const& + { + if(this->type_ != value_t::floating) + { + detail::throw_bad_cast( + "toml::value::as_floating(): ", this->type_, *this); + } + return this->floating_; + } + string const& as_string() const& + { + if(this->type_ != value_t::string) + { + detail::throw_bad_cast( + "toml::value::as_string(): ", this->type_, *this); + } + return this->string_; + } + offset_datetime const& as_offset_datetime() const& + { + if(this->type_ != value_t::offset_datetime) + { + detail::throw_bad_cast( + "toml::value::as_offset_datetime(): ", this->type_, *this); + } + return this->offset_datetime_; + } + local_datetime const& as_local_datetime() const& + { + if(this->type_ != value_t::local_datetime) + { + detail::throw_bad_cast( + "toml::value::as_local_datetime(): ", this->type_, *this); + } + return this->local_datetime_; + } + local_date const& as_local_date() const& + { + if(this->type_ != value_t::local_date) + { + detail::throw_bad_cast( + "toml::value::as_local_date(): ", this->type_, *this); + } + return this->local_date_; + } + local_time const& as_local_time() const& + { + if(this->type_ != value_t::local_time) + { + detail::throw_bad_cast( + "toml::value::as_local_time(): ", this->type_, *this); + } + return this->local_time_; + } + array_type const& as_array() const& + { + if(this->type_ != value_t::array) + { + detail::throw_bad_cast( + "toml::value::as_array(): ", this->type_, *this); + } + return this->array_.value(); + } + table_type const& as_table() const& + { + if(this->type_ != value_t::table) + { + detail::throw_bad_cast( + "toml::value::as_table(): ", this->type_, *this); + } + return this->table_.value(); + } + // }}} + // ------------------------------------------------------------------------ + // nonconst reference {{{ + + boolean & as_boolean() & + { + if(this->type_ != value_t::boolean) + { + detail::throw_bad_cast( + "toml::value::as_boolean(): ", this->type_, *this); + } + return this->boolean_; + } + integer & as_integer() & + { + if(this->type_ != value_t::integer) + { + detail::throw_bad_cast( + "toml::value::as_integer(): ", this->type_, *this); + } + return this->integer_; + } + floating & as_floating() & + { + if(this->type_ != value_t::floating) + { + detail::throw_bad_cast( + "toml::value::as_floating(): ", this->type_, *this); + } + return this->floating_; + } + string & as_string() & + { + if(this->type_ != value_t::string) + { + detail::throw_bad_cast( + "toml::value::as_string(): ", this->type_, *this); + } + return this->string_; + } + offset_datetime & as_offset_datetime() & + { + if(this->type_ != value_t::offset_datetime) + { + detail::throw_bad_cast( + "toml::value::as_offset_datetime(): ", this->type_, *this); + } + return this->offset_datetime_; + } + local_datetime & as_local_datetime() & + { + if(this->type_ != value_t::local_datetime) + { + detail::throw_bad_cast( + "toml::value::as_local_datetime(): ", this->type_, *this); + } + return this->local_datetime_; + } + local_date & as_local_date() & + { + if(this->type_ != value_t::local_date) + { + detail::throw_bad_cast( + "toml::value::as_local_date(): ", this->type_, *this); + } + return this->local_date_; + } + local_time & as_local_time() & + { + if(this->type_ != value_t::local_time) + { + detail::throw_bad_cast( + "toml::value::as_local_time(): ", this->type_, *this); + } + return this->local_time_; + } + array_type & as_array() & + { + if(this->type_ != value_t::array) + { + detail::throw_bad_cast( + "toml::value::as_array(): ", this->type_, *this); + } + return this->array_.value(); + } + table_type & as_table() & + { + if(this->type_ != value_t::table) + { + detail::throw_bad_cast( + "toml::value::as_table(): ", this->type_, *this); + } + return this->table_.value(); + } + + // }}} + // ------------------------------------------------------------------------ + // rvalue reference {{{ + + boolean && as_boolean() && + { + if(this->type_ != value_t::boolean) + { + detail::throw_bad_cast( + "toml::value::as_boolean(): ", this->type_, *this); + } + return std::move(this->boolean_); + } + integer && as_integer() && + { + if(this->type_ != value_t::integer) + { + detail::throw_bad_cast( + "toml::value::as_integer(): ", this->type_, *this); + } + return std::move(this->integer_); + } + floating && as_floating() && + { + if(this->type_ != value_t::floating) + { + detail::throw_bad_cast( + "toml::value::as_floating(): ", this->type_, *this); + } + return std::move(this->floating_); + } + string && as_string() && + { + if(this->type_ != value_t::string) + { + detail::throw_bad_cast( + "toml::value::as_string(): ", this->type_, *this); + } + return std::move(this->string_); + } + offset_datetime && as_offset_datetime() && + { + if(this->type_ != value_t::offset_datetime) + { + detail::throw_bad_cast( + "toml::value::as_offset_datetime(): ", this->type_, *this); + } + return std::move(this->offset_datetime_); + } + local_datetime && as_local_datetime() && + { + if(this->type_ != value_t::local_datetime) + { + detail::throw_bad_cast( + "toml::value::as_local_datetime(): ", this->type_, *this); + } + return std::move(this->local_datetime_); + } + local_date && as_local_date() && + { + if(this->type_ != value_t::local_date) + { + detail::throw_bad_cast( + "toml::value::as_local_date(): ", this->type_, *this); + } + return std::move(this->local_date_); + } + local_time && as_local_time() && + { + if(this->type_ != value_t::local_time) + { + detail::throw_bad_cast( + "toml::value::as_local_time(): ", this->type_, *this); + } + return std::move(this->local_time_); + } + array_type && as_array() && + { + if(this->type_ != value_t::array) + { + detail::throw_bad_cast( + "toml::value::as_array(): ", this->type_, *this); + } + return std::move(this->array_.value()); + } + table_type && as_table() && + { + if(this->type_ != value_t::table) + { + detail::throw_bad_cast( + "toml::value::as_table(): ", this->type_, *this); + } + return std::move(this->table_.value()); + } + // }}} + + // accessors ============================================================= + // + // may throw type_error or out_of_range + // + value_type& at(const key& k) + { + if(!this->is_table()) + { + detail::throw_bad_cast( + "toml::value::at(key): ", this->type_, *this); + } + if(this->as_table(std::nothrow).count(k) == 0) + { + detail::throw_key_not_found_error(*this, k); + } + return this->as_table(std::nothrow).at(k); + } + value_type const& at(const key& k) const + { + if(!this->is_table()) + { + detail::throw_bad_cast( + "toml::value::at(key): ", this->type_, *this); + } + if(this->as_table(std::nothrow).count(k) == 0) + { + detail::throw_key_not_found_error(*this, k); + } + return this->as_table(std::nothrow).at(k); + } + value_type& operator[](const key& k) + { + if(this->is_uninitialized()) + { + *this = table_type{}; + } + else if(!this->is_table()) // initialized, but not a table + { + detail::throw_bad_cast( + "toml::value::operator[](key): ", this->type_, *this); + } + return this->as_table(std::nothrow)[k]; + } + + value_type& at(const std::size_t idx) + { + if(!this->is_array()) + { + detail::throw_bad_cast( + "toml::value::at(idx): ", this->type_, *this); + } + if(this->as_array(std::nothrow).size() <= idx) + { + throw std::out_of_range(detail::format_underline( + "toml::value::at(idx): no element corresponding to the index", { + {this->location(), concat_to_string("the length is ", + this->as_array(std::nothrow).size(), + ", and the specified index is ", idx)} + })); + } + return this->as_array().at(idx); + } + value_type const& at(const std::size_t idx) const + { + if(!this->is_array()) + { + detail::throw_bad_cast( + "toml::value::at(idx): ", this->type_, *this); + } + if(this->as_array(std::nothrow).size() <= idx) + { + throw std::out_of_range(detail::format_underline( + "toml::value::at(idx): no element corresponding to the index", { + {this->location(), concat_to_string("the length is ", + this->as_array(std::nothrow).size(), + ", and the specified index is ", idx)} + })); + } + return this->as_array(std::nothrow).at(idx); + } + + value_type& operator[](const std::size_t idx) noexcept + { + // no check... + return this->as_array(std::nothrow)[idx]; + } + value_type const& operator[](const std::size_t idx) const noexcept + { + // no check... + return this->as_array(std::nothrow)[idx]; + } + + void push_back(const value_type& x) + { + if(!this->is_array()) + { + detail::throw_bad_cast( + "toml::value::push_back(value): ", this->type_, *this); + } + this->as_array(std::nothrow).push_back(x); + return; + } + void push_back(value_type&& x) + { + if(!this->is_array()) + { + detail::throw_bad_cast( + "toml::value::push_back(value): ", this->type_, *this); + } + this->as_array(std::nothrow).push_back(std::move(x)); + return; + } + + template + value_type& emplace_back(Ts&& ... args) + { + if(!this->is_array()) + { + detail::throw_bad_cast( + "toml::value::emplace_back(...): ", this->type_, *this); + } + this->as_array(std::nothrow).emplace_back(std::forward(args) ...); + return this->as_array(std::nothrow).back(); + } + + std::size_t size() const + { + switch(this->type_) + { + case value_t::array: + { + return this->as_array(std::nothrow).size(); + } + case value_t::table: + { + return this->as_table(std::nothrow).size(); + } + case value_t::string: + { + return this->as_string(std::nothrow).str.size(); + } + default: + { + throw type_error(detail::format_underline( + "toml::value::size(): bad_cast to container types", { + {this->location(), + concat_to_string("the actual type is ", this->type_)} + }), this->location()); + } + } + } + + std::size_t count(const key_type& k) const + { + if(!this->is_table()) + { + detail::throw_bad_cast( + "toml::value::count(key): ", this->type_, *this); + } + return this->as_table(std::nothrow).count(k); + } + + bool contains(const key_type& k) const + { + if(!this->is_table()) + { + detail::throw_bad_cast( + "toml::value::contains(key): ", this->type_, *this); + } + return (this->as_table(std::nothrow).count(k) != 0); + } + + source_location location() const + { + return source_location(this->region_info_.get()); + } + + comment_type const& comments() const noexcept {return this->comments_;} + comment_type& comments() noexcept {return this->comments_;} + + private: + + void cleanup() noexcept + { + switch(this->type_) + { + case value_t::string : {string_.~string(); return;} + case value_t::array : {array_.~array_storage(); return;} + case value_t::table : {table_.~table_storage(); return;} + default : return; + } + } + + // for error messages + template + friend region_base const* detail::get_region(const Value& v); + + template + friend void detail::change_region(Value& v, detail::region reg); + + private: + + using array_storage = detail::storage; + using table_storage = detail::storage; + + value_t type_; + union + { + boolean boolean_; + integer integer_; + floating floating_; + string string_; + offset_datetime offset_datetime_; + local_datetime local_datetime_; + local_date local_date_; + local_time local_time_; + array_storage array_; + table_storage table_; + }; + std::shared_ptr region_info_; + comment_type comments_; +}; + +// default toml::value and default array/table. +// TOML11_DEFAULT_COMMENT_STRATEGY is defined in comments.hpp +using value = basic_value; +using array = typename value::array_type; +using table = typename value::table_type; + +template class T, template class A> +inline bool +operator==(const basic_value& lhs, const basic_value& rhs) +{ + if(lhs.type() != rhs.type()) {return false;} + if(lhs.comments() != rhs.comments()) {return false;} + + switch(lhs.type()) + { + case value_t::boolean : + { + return lhs.as_boolean() == rhs.as_boolean(); + } + case value_t::integer : + { + return lhs.as_integer() == rhs.as_integer(); + } + case value_t::floating : + { + return lhs.as_floating() == rhs.as_floating(); + } + case value_t::string : + { + return lhs.as_string() == rhs.as_string(); + } + case value_t::offset_datetime: + { + return lhs.as_offset_datetime() == rhs.as_offset_datetime(); + } + case value_t::local_datetime: + { + return lhs.as_local_datetime() == rhs.as_local_datetime(); + } + case value_t::local_date: + { + return lhs.as_local_date() == rhs.as_local_date(); + } + case value_t::local_time: + { + return lhs.as_local_time() == rhs.as_local_time(); + } + case value_t::array : + { + return lhs.as_array() == rhs.as_array(); + } + case value_t::table : + { + return lhs.as_table() == rhs.as_table(); + } + case value_t::empty : {return true; } + default: {return false;} + } +} + +template class T, template class A> +inline bool operator!=(const basic_value& lhs, const basic_value& rhs) +{ + return !(lhs == rhs); +} + +template class T, template class A> +typename std::enable_if::array_type>, + detail::is_comparable::table_type> + >::value, bool>::type +operator<(const basic_value& lhs, const basic_value& rhs) +{ + if(lhs.type() != rhs.type()){return (lhs.type() < rhs.type());} + switch(lhs.type()) + { + case value_t::boolean : + { + return lhs.as_boolean() < rhs.as_boolean() || + (lhs.as_boolean() == rhs.as_boolean() && + lhs.comments() < rhs.comments()); + } + case value_t::integer : + { + return lhs.as_integer() < rhs.as_integer() || + (lhs.as_integer() == rhs.as_integer() && + lhs.comments() < rhs.comments()); + } + case value_t::floating : + { + return lhs.as_floating() < rhs.as_floating() || + (lhs.as_floating() == rhs.as_floating() && + lhs.comments() < rhs.comments()); + } + case value_t::string : + { + return lhs.as_string() < rhs.as_string() || + (lhs.as_string() == rhs.as_string() && + lhs.comments() < rhs.comments()); + } + case value_t::offset_datetime: + { + return lhs.as_offset_datetime() < rhs.as_offset_datetime() || + (lhs.as_offset_datetime() == rhs.as_offset_datetime() && + lhs.comments() < rhs.comments()); + } + case value_t::local_datetime: + { + return lhs.as_local_datetime() < rhs.as_local_datetime() || + (lhs.as_local_datetime() == rhs.as_local_datetime() && + lhs.comments() < rhs.comments()); + } + case value_t::local_date: + { + return lhs.as_local_date() < rhs.as_local_date() || + (lhs.as_local_date() == rhs.as_local_date() && + lhs.comments() < rhs.comments()); + } + case value_t::local_time: + { + return lhs.as_local_time() < rhs.as_local_time() || + (lhs.as_local_time() == rhs.as_local_time() && + lhs.comments() < rhs.comments()); + } + case value_t::array : + { + return lhs.as_array() < rhs.as_array() || + (lhs.as_array() == rhs.as_array() && + lhs.comments() < rhs.comments()); + } + case value_t::table : + { + return lhs.as_table() < rhs.as_table() || + (lhs.as_table() == rhs.as_table() && + lhs.comments() < rhs.comments()); + } + case value_t::empty : + { + return lhs.comments() < rhs.comments(); + } + default: + { + return lhs.comments() < rhs.comments(); + } + } +} + +template class T, template class A> +typename std::enable_if::array_type>, + detail::is_comparable::table_type> + >::value, bool>::type +operator<=(const basic_value& lhs, const basic_value& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +template class T, template class A> +typename std::enable_if::array_type>, + detail::is_comparable::table_type> + >::value, bool>::type +operator>(const basic_value& lhs, const basic_value& rhs) +{ + return !(lhs <= rhs); +} +template class T, template class A> +typename std::enable_if::array_type>, + detail::is_comparable::table_type> + >::value, bool>::type +operator>=(const basic_value& lhs, const basic_value& rhs) +{ + return !(lhs < rhs); +} + +template class T, template class A> +inline std::string format_error(const std::string& err_msg, + const basic_value& v, const std::string& comment, + std::vector hints = {}, + const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED) +{ + return detail::format_underline(err_msg, {{v.location(), comment}}, + std::move(hints), colorize); +} + +template class T, template class A> +inline std::string format_error(const std::string& err_msg, + const toml::basic_value& v1, const std::string& comment1, + const toml::basic_value& v2, const std::string& comment2, + std::vector hints = {}, + const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED) +{ + return detail::format_underline(err_msg, { + {v1.location(), comment1}, {v2.location(), comment2} + }, std::move(hints), colorize); +} + +template class T, template class A> +inline std::string format_error(const std::string& err_msg, + const toml::basic_value& v1, const std::string& comment1, + const toml::basic_value& v2, const std::string& comment2, + const toml::basic_value& v3, const std::string& comment3, + std::vector hints = {}, + const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED) +{ + return detail::format_underline(err_msg, {{v1.location(), comment1}, + {v2.location(), comment2}, {v3.location(), comment3} + }, std::move(hints), colorize); +} + +template class T, template class A> +detail::return_type_of_t +visit(Visitor&& visitor, const toml::basic_value& v) +{ + switch(v.type()) + { + case value_t::boolean : {return visitor(v.as_boolean ());} + case value_t::integer : {return visitor(v.as_integer ());} + case value_t::floating : {return visitor(v.as_floating ());} + case value_t::string : {return visitor(v.as_string ());} + case value_t::offset_datetime: {return visitor(v.as_offset_datetime());} + case value_t::local_datetime : {return visitor(v.as_local_datetime ());} + case value_t::local_date : {return visitor(v.as_local_date ());} + case value_t::local_time : {return visitor(v.as_local_time ());} + case value_t::array : {return visitor(v.as_array ());} + case value_t::table : {return visitor(v.as_table ());} + case value_t::empty : break; + default: break; + } + throw std::runtime_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid basic_value.", v, "here")); +} + +template class T, template class A> +detail::return_type_of_t +visit(Visitor&& visitor, toml::basic_value& v) +{ + switch(v.type()) + { + case value_t::boolean : {return visitor(v.as_boolean ());} + case value_t::integer : {return visitor(v.as_integer ());} + case value_t::floating : {return visitor(v.as_floating ());} + case value_t::string : {return visitor(v.as_string ());} + case value_t::offset_datetime: {return visitor(v.as_offset_datetime());} + case value_t::local_datetime : {return visitor(v.as_local_datetime ());} + case value_t::local_date : {return visitor(v.as_local_date ());} + case value_t::local_time : {return visitor(v.as_local_time ());} + case value_t::array : {return visitor(v.as_array ());} + case value_t::table : {return visitor(v.as_table ());} + case value_t::empty : break; + default: break; + } + throw std::runtime_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid basic_value.", v, "here")); +} + +template class T, template class A> +detail::return_type_of_t +visit(Visitor&& visitor, toml::basic_value&& v) +{ + switch(v.type()) + { + case value_t::boolean : {return visitor(std::move(v.as_boolean ()));} + case value_t::integer : {return visitor(std::move(v.as_integer ()));} + case value_t::floating : {return visitor(std::move(v.as_floating ()));} + case value_t::string : {return visitor(std::move(v.as_string ()));} + case value_t::offset_datetime: {return visitor(std::move(v.as_offset_datetime()));} + case value_t::local_datetime : {return visitor(std::move(v.as_local_datetime ()));} + case value_t::local_date : {return visitor(std::move(v.as_local_date ()));} + case value_t::local_time : {return visitor(std::move(v.as_local_time ()));} + case value_t::array : {return visitor(std::move(v.as_array ()));} + case value_t::table : {return visitor(std::move(v.as_table ()));} + case value_t::empty : break; + default: break; + } + throw std::runtime_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid basic_value.", v, "here")); +} + +}// toml +#endif// TOML11_VALUE diff --git a/src/frontend/qt_sdl/toml/toml/version.hpp b/src/frontend/qt_sdl/toml/toml/version.hpp new file mode 100644 index 00000000..9cbfa39b --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/version.hpp @@ -0,0 +1,42 @@ +#ifndef TOML11_VERSION_HPP +#define TOML11_VERSION_HPP + +// This file checks C++ version. + +#ifndef __cplusplus +# error "__cplusplus is not defined" +#endif + +// Since MSVC does not define `__cplusplus` correctly unless you pass +// `/Zc:__cplusplus` when compiling, the workaround macros are added. +// Those enables you to define version manually or to use MSVC specific +// version macro automatically. +// +// The value of `__cplusplus` macro is defined in the C++ standard spec, but +// MSVC ignores the value, maybe because of backward compatibility. Instead, +// MSVC defines _MSVC_LANG that has the same value as __cplusplus defined in +// the C++ standard. First we check the manual version definition, and then +// we check if _MSVC_LANG is defined. If neither, use normal `__cplusplus`. +// +// FYI: https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus?view=msvc-170 +// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=msvc-170 +// +#if defined(TOML11_ENFORCE_CXX11) +# define TOML11_CPLUSPLUS_STANDARD_VERSION 201103L +#elif defined(TOML11_ENFORCE_CXX14) +# define TOML11_CPLUSPLUS_STANDARD_VERSION 201402L +#elif defined(TOML11_ENFORCE_CXX17) +# define TOML11_CPLUSPLUS_STANDARD_VERSION 201703L +#elif defined(TOML11_ENFORCE_CXX20) +# define TOML11_CPLUSPLUS_STANDARD_VERSION 202002L +#elif defined(_MSVC_LANG) && defined(_MSC_VER) && 1910 <= _MSC_VER +# define TOML11_CPLUSPLUS_STANDARD_VERSION _MSVC_LANG +#else +# define TOML11_CPLUSPLUS_STANDARD_VERSION __cplusplus +#endif + +#if TOML11_CPLUSPLUS_STANDARD_VERSION < 201103L && _MSC_VER < 1900 +# error "toml11 requires C++11 or later." +#endif + +#endif// TOML11_VERSION_HPP diff --git a/src/melonDLDI.h b/src/melonDLDI.h index 908a2f60..b99b2a30 100644 --- a/src/melonDLDI.h +++ b/src/melonDLDI.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/net/CMakeLists.txt b/src/net/CMakeLists.txt new file mode 100644 index 00000000..6ca24de6 --- /dev/null +++ b/src/net/CMakeLists.txt @@ -0,0 +1,35 @@ +include(FixInterfaceIncludes) + +add_library(net-utils STATIC + Net.cpp + Net_PCap.cpp + Net_Slirp.cpp + PacketDispatcher.cpp + LocalMP.cpp + LAN.cpp + Netplay.cpp + MPInterface.cpp +) + +target_include_directories(net-utils PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") +target_include_directories(net-utils PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") + + +option(USE_SYSTEM_LIBSLIRP "Use system libslirp instead of the bundled version" OFF) +if (USE_SYSTEM_LIBSLIRP) + pkg_check_modules(Slirp REQUIRED IMPORTED_TARGET slirp) + fix_interface_includes(PkgConfig::Slirp) + target_link_libraries(net-utils PUBLIC PkgConfig::Slirp) +else() + add_subdirectory(libslirp EXCLUDE_FROM_ALL) + target_link_libraries(net-utils PUBLIC slirp) +endif() + +if (USE_VCPKG) + find_package(unofficial-enet CONFIG REQUIRED) + target_link_libraries(net-utils PRIVATE unofficial::enet::enet) +else() + pkg_check_modules(ENet REQUIRED IMPORTED_TARGET libenet) + fix_interface_includes(PkgConfig::ENet) + target_link_libraries(net-utils PUBLIC PkgConfig::ENet) +endif() diff --git a/src/net/LAN.cpp b/src/net/LAN.cpp new file mode 100644 index 00000000..ebc66fd8 --- /dev/null +++ b/src/net/LAN.cpp @@ -0,0 +1,1091 @@ +/* + Copyright 2016-2024 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 + +#ifdef __WIN32__ + #include + #include + + #define socket_t SOCKET + #define sockaddr_t SOCKADDR + #define sockaddr_in_t SOCKADDR_IN +#else + #include + #include + #include + #include + + #define socket_t int + #define sockaddr_t struct sockaddr + #define sockaddr_in_t struct sockaddr_in + #define closesocket close +#endif + +#ifndef INVALID_SOCKET + #define INVALID_SOCKET (socket_t)-1 +#endif + +#include "LAN.h" + + +namespace melonDS +{ + +const u32 kDiscoveryMagic = 0x444E414C; // LAND +const u32 kLANMagic = 0x504E414C; // LANP +const u32 kPacketMagic = 0x4946494E; // NIFI + +const u32 kProtocolVersion = 1; + +const u32 kLocalhost = 0x0100007F; + +enum +{ + Chan_Cmd = 0, // channel 0 -- control commands + Chan_MP, // channel 1 -- MP data exchange +}; + +enum +{ + Cmd_ClientInit = 1, // 01 -- host->client -- init new client and assign ID + Cmd_PlayerInfo, // 02 -- client->host -- send client player info to host + Cmd_PlayerList, // 03 -- host->client -- broadcast updated player list + Cmd_PlayerConnect, // 04 -- both -- signal connected state (ready to receive MP frames) + Cmd_PlayerDisconnect, // 05 -- both -- signal disconnected state (not receiving MP frames) +}; + +const int kDiscoveryPort = 7063; +const int kLANPort = 7064; + + +LAN::LAN() noexcept : Inited(false) +{ + DiscoveryMutex = Platform::Mutex_Create(); + PlayersMutex = Platform::Mutex_Create(); + + DiscoverySocket = INVALID_SOCKET; + DiscoveryLastTick = 0; + + Active = false; + IsHost = false; + Host = nullptr; + //Lag = false; + + memset(RemotePeers, 0, sizeof(RemotePeers)); + memset(Players, 0, sizeof(Players)); + NumPlayers = 0; + MaxPlayers = 0; + + ConnectedBitmask = 0; + + MPRecvTimeout = 25; + LastHostID = -1; + LastHostPeer = nullptr; + + FrameCount = 0; + + // TODO make this somewhat nicer + if (enet_initialize() != 0) + { + Platform::Log(Platform::LogLevel::Error, "LAN: failed to initialize enet\n"); + return; + } + + Platform::Log(Platform::LogLevel::Info, "LAN: enet initialized\n"); + Inited = true; +} + +LAN::~LAN() noexcept +{ + EndSession(); + + Inited = false; + enet_deinitialize(); + + Platform::Mutex_Free(DiscoveryMutex); + Platform::Mutex_Free(PlayersMutex); + + Platform::Log(Platform::LogLevel::Info, "LAN: enet deinitialized\n"); +} + + +std::map LAN::GetDiscoveryList() +{ + Platform::Mutex_Lock(DiscoveryMutex); + auto ret = DiscoveryList; + Platform::Mutex_Unlock(DiscoveryMutex); + return ret; +} + +std::vector LAN::GetPlayerList() +{ + Platform::Mutex_Lock(PlayersMutex); + + std::vector ret; + for (int i = 0; i < 16; i++) + { + if (Players[i].Status == Player_None) continue; + + // make a copy of the player entry, fix up the address field + Player newp = Players[i]; + if (newp.ID == MyPlayer.ID) + { + newp.IsLocalPlayer = true; + newp.Address = kLocalhost; + } + else + { + newp.IsLocalPlayer = false; + if (newp.Status == Player_Host) + newp.Address = HostAddress; + } + + ret.push_back(newp); + } + + Platform::Mutex_Unlock(PlayersMutex); + return ret; +} + + +bool LAN::StartDiscovery() +{ + if (!Inited) return false; + + int res; + + DiscoverySocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (DiscoverySocket < 0) + { + DiscoverySocket = INVALID_SOCKET; + return false; + } + + sockaddr_in_t saddr; + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = htonl(INADDR_ANY); + saddr.sin_port = htons(kDiscoveryPort); + res = bind(DiscoverySocket, (const sockaddr_t*)&saddr, sizeof(saddr)); + if (res < 0) + { + closesocket(DiscoverySocket); + DiscoverySocket = INVALID_SOCKET; + return false; + } + + int opt_true = 1; + res = setsockopt(DiscoverySocket, SOL_SOCKET, SO_BROADCAST, (const char*)&opt_true, sizeof(int)); + if (res < 0) + { + closesocket(DiscoverySocket); + DiscoverySocket = INVALID_SOCKET; + return false; + } + + DiscoveryLastTick = (u32)Platform::GetMSCount(); + DiscoveryList.clear(); + + Active = true; + return true; +} + +void LAN::EndDiscovery() +{ + if (!Inited) return; + + if (DiscoverySocket != INVALID_SOCKET) + { + closesocket(DiscoverySocket); + DiscoverySocket = INVALID_SOCKET; + } + + if (!IsHost) + Active = false; +} + +bool LAN::StartHost(const char* playername, int numplayers) +{ + if (!Inited) return false; + if (numplayers > 16) return false; + + ENetAddress addr; + addr.host = ENET_HOST_ANY; + addr.port = kLANPort; + + Host = enet_host_create(&addr, 16, 2, 0, 0); + if (!Host) + { + return false; + } + + Platform::Mutex_Lock(PlayersMutex); + + Player* player = &Players[0]; + memset(player, 0, sizeof(Player)); + player->ID = 0; + strncpy(player->Name, playername, 31); + player->Status = Player_Host; + player->Address = kLocalhost; + NumPlayers = 1; + MaxPlayers = numplayers; + memcpy(&MyPlayer, player, sizeof(Player)); + + Platform::Mutex_Unlock(PlayersMutex); + + HostAddress = kLocalhost; + LastHostID = -1; + LastHostPeer = nullptr; + + Active = true; + IsHost = true; + + StartDiscovery(); + return true; +} + +bool LAN::StartClient(const char* playername, const char* host) +{ + if (!Inited) return false; + + Host = enet_host_create(nullptr, 16, 2, 0, 0); + if (!Host) + { + return false; + } + + ENetAddress addr; + enet_address_set_host(&addr, host); + addr.port = kLANPort; + ENetPeer* peer = enet_host_connect(Host, &addr, 2, 0); + if (!peer) + { + enet_host_destroy(Host); + Host = nullptr; + return false; + } + + Platform::Mutex_Lock(PlayersMutex); + + Player* player = &MyPlayer; + memset(player, 0, sizeof(Player)); + player->ID = 0; + strncpy(player->Name, playername, 31); + player->Status = Player_Connecting; + + Platform::Mutex_Unlock(PlayersMutex); + + ENetEvent event; + int conn = 0; + u32 starttick = (u32)Platform::GetMSCount(); + const int conntimeout = 5000; + for (;;) + { + u32 curtick = (u32)Platform::GetMSCount(); + if (curtick < starttick) break; + int timeout = conntimeout - (int)(curtick - starttick); + if (timeout < 0) break; + if (enet_host_service(Host, &event, timeout) > 0) + { + if (conn == 0 && event.type == ENET_EVENT_TYPE_CONNECT) + { + conn = 1; + } + else if (conn == 1 && event.type == ENET_EVENT_TYPE_RECEIVE) + { + u8* data = event.packet->data; + if (event.channelID != Chan_Cmd) continue; + if (data[0] != Cmd_ClientInit) continue; + if (event.packet->dataLength != 11) continue; + + u32 magic = data[1] | (data[2] << 8) | (data[3] << 16) | (data[4] << 24); + u32 version = data[5] | (data[6] << 8) | (data[7] << 16) | (data[8] << 24); + if (magic != kLANMagic) continue; + if (version != kProtocolVersion) continue; + if (data[10] > 16) continue; + + MaxPlayers = data[10]; + + // send player information + MyPlayer.ID = data[9]; + u8 cmd[9+sizeof(Player)]; + cmd[0] = Cmd_PlayerInfo; + cmd[1] = (u8)kLANMagic; + cmd[2] = (u8)(kLANMagic >> 8); + cmd[3] = (u8)(kLANMagic >> 16); + cmd[4] = (u8)(kLANMagic >> 24); + cmd[5] = (u8)kProtocolVersion; + cmd[6] = (u8)(kProtocolVersion >> 8); + cmd[7] = (u8)(kProtocolVersion >> 16); + cmd[8] = (u8)(kProtocolVersion >> 24); + memcpy(&cmd[9], &MyPlayer, sizeof(Player)); + ENetPacket* pkt = enet_packet_create(cmd, 9+sizeof(Player), ENET_PACKET_FLAG_RELIABLE); + enet_peer_send(event.peer, Chan_Cmd, pkt); + + conn = 2; + break; + } + else if (event.type == ENET_EVENT_TYPE_DISCONNECT) + { + conn = 0; + break; + } + } + else + break; + } + + if (conn != 2) + { + enet_peer_reset(peer); + enet_host_destroy(Host); + Host = nullptr; + return false; + } + + HostAddress = addr.host; + LastHostID = -1; + LastHostPeer = nullptr; + RemotePeers[0] = peer; + peer->data = &Players[0]; + + Active = true; + IsHost = false; + return true; +} + +void LAN::EndSession() +{ + if (!Active) return; + if (IsHost) EndDiscovery(); + + Active = false; + + while (!RXQueue.empty()) + { + ENetPacket* packet = RXQueue.front(); + RXQueue.pop(); + enet_packet_destroy(packet); + } + + for (int i = 0; i < 16; i++) + { + if (i == MyPlayer.ID) continue; + + if (RemotePeers[i]) + enet_peer_disconnect(RemotePeers[i], 0); + + RemotePeers[i] = nullptr; + } + + enet_host_destroy(Host); + Host = nullptr; + IsHost = false; +} + + +void LAN::ProcessDiscovery() +{ + if (DiscoverySocket == INVALID_SOCKET) + return; + + u32 tick = (u32)Platform::GetMSCount(); + if ((tick - DiscoveryLastTick) < 1000) + return; + + DiscoveryLastTick = tick; + + if (IsHost) + { + // advertise this LAN session over the network + + DiscoveryData beacon; + memset(&beacon, 0, sizeof(beacon)); + beacon.Magic = kDiscoveryMagic; + beacon.Version = kProtocolVersion; + beacon.Tick = tick; + snprintf(beacon.SessionName, 64, "%s's game", MyPlayer.Name); + beacon.NumPlayers = NumPlayers; + beacon.MaxPlayers = MaxPlayers; + beacon.Status = 0; // TODO + + sockaddr_in_t saddr; + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = htonl(INADDR_BROADCAST); + saddr.sin_port = htons(kDiscoveryPort); + + sendto(DiscoverySocket, (const char*)&beacon, sizeof(beacon), 0, (const sockaddr_t*)&saddr, sizeof(saddr)); + } + else + { + Platform::Mutex_Lock(DiscoveryMutex); + + // listen for LAN sessions + + fd_set fd; + struct timeval tv; + for (;;) + { + FD_ZERO(&fd); FD_SET(DiscoverySocket, &fd); + tv.tv_sec = 0; tv.tv_usec = 0; + if (!select(DiscoverySocket+1, &fd, nullptr, nullptr, &tv)) + break; + + DiscoveryData beacon; + sockaddr_in_t raddr; + socklen_t ralen = sizeof(raddr); + + int rlen = recvfrom(DiscoverySocket, (char*)&beacon, sizeof(beacon), 0, (sockaddr_t*)&raddr, &ralen); + if (rlen < sizeof(beacon)) continue; + if (beacon.Magic != kDiscoveryMagic) continue; + if (beacon.Version != kProtocolVersion) continue; + if (beacon.MaxPlayers > 16) continue; + if (beacon.NumPlayers > beacon.MaxPlayers) continue; + + u32 key = ntohl(raddr.sin_addr.s_addr); + + if (DiscoveryList.find(key) != DiscoveryList.end()) + { + if (beacon.Tick <= DiscoveryList[key].Tick) + continue; + } + + beacon.Magic = tick; + beacon.SessionName[63] = '\0'; + DiscoveryList[key] = beacon; + } + + // cleanup: remove hosts that haven't given a sign of life in the last 5 seconds + + std::vector deletelist; + + for (const auto& [key, data] : DiscoveryList) + { + u32 age = tick - data.Magic; + if (age < 5000) continue; + + deletelist.push_back(key); + } + + for (const auto& key : deletelist) + { + DiscoveryList.erase(key); + } + + Platform::Mutex_Unlock(DiscoveryMutex); + } +} + +void LAN::HostUpdatePlayerList() +{ + u8 cmd[2+sizeof(Players)]; + cmd[0] = Cmd_PlayerList; + cmd[1] = (u8)NumPlayers; + memcpy(&cmd[2], Players, sizeof(Players)); + ENetPacket* pkt = enet_packet_create(cmd, 2+sizeof(Players), ENET_PACKET_FLAG_RELIABLE); + enet_host_broadcast(Host, Chan_Cmd, pkt); +} + +void LAN::ClientUpdatePlayerList() +{ +} + +void LAN::ProcessHostEvent(ENetEvent& event) +{ + switch (event.type) + { + case ENET_EVENT_TYPE_CONNECT: + { + if ((NumPlayers >= MaxPlayers) || (NumPlayers >= 16)) + { + // game is full, reject connection + enet_peer_disconnect(event.peer, 0); + break; + } + + // client connected; assign player number + + int id; + for (id = 0; id < 16; id++) + { + if (id >= NumPlayers) break; + if (Players[id].Status == Player_None) break; + } + + if (id < 16) + { + u8 cmd[11]; + cmd[0] = Cmd_ClientInit; + cmd[1] = (u8)kLANMagic; + cmd[2] = (u8)(kLANMagic >> 8); + cmd[3] = (u8)(kLANMagic >> 16); + cmd[4] = (u8)(kLANMagic >> 24); + cmd[5] = (u8)kProtocolVersion; + cmd[6] = (u8)(kProtocolVersion >> 8); + cmd[7] = (u8)(kProtocolVersion >> 16); + cmd[8] = (u8)(kProtocolVersion >> 24); + cmd[9] = (u8)id; + cmd[10] = MaxPlayers; + ENetPacket* pkt = enet_packet_create(cmd, 11, ENET_PACKET_FLAG_RELIABLE); + enet_peer_send(event.peer, Chan_Cmd, pkt); + + Platform::Mutex_Lock(PlayersMutex); + + Players[id].ID = id; + Players[id].Status = Player_Connecting; + Players[id].Address = event.peer->address.host; + event.peer->data = &Players[id]; + NumPlayers++; + + Platform::Mutex_Unlock(PlayersMutex); + + RemotePeers[id] = event.peer; + } + else + { + // ??? + enet_peer_disconnect(event.peer, 0); + } + } + break; + + case ENET_EVENT_TYPE_DISCONNECT: + { + Player* player = (Player*)event.peer->data; + if (!player) break; + + ConnectedBitmask &= ~(1 << player->ID); + + int id = player->ID; + RemotePeers[id] = nullptr; + + player->ID = 0; + player->Status = Player_None; + NumPlayers--; + + // broadcast updated player list + HostUpdatePlayerList(); + } + break; + + case ENET_EVENT_TYPE_RECEIVE: + { + if (event.packet->dataLength < 1) break; + + u8* data = (u8*)event.packet->data; + switch (data[0]) + { + case Cmd_PlayerInfo: // client sending player info + { + if (event.packet->dataLength != (9+sizeof(Player))) break; + + u32 magic = data[1] | (data[2] << 8) | (data[3] << 16) | (data[4] << 24); + u32 version = data[5] | (data[6] << 8) | (data[7] << 16) | (data[8] << 24); + if ((magic != kLANMagic) || (version != kProtocolVersion)) + { + enet_peer_disconnect(event.peer, 0); + break; + } + + Player player; + memcpy(&player, &data[9], sizeof(Player)); + player.Name[31] = '\0'; + + Player* hostside = (Player*)event.peer->data; + if (player.ID != hostside->ID) + { + enet_peer_disconnect(event.peer, 0); + break; + } + + Platform::Mutex_Lock(PlayersMutex); + + player.Status = Player_Client; + player.Address = event.peer->address.host; + memcpy(hostside, &player, sizeof(Player)); + + Platform::Mutex_Unlock(PlayersMutex); + + // broadcast updated player list + HostUpdatePlayerList(); + } + break; + + case Cmd_PlayerConnect: // player connected + { + if (event.packet->dataLength != 1) break; + Player* player = (Player*)event.peer->data; + if (!player) break; + + ConnectedBitmask |= (1 << player->ID); + } + break; + + case Cmd_PlayerDisconnect: // player disconnected + { + if (event.packet->dataLength != 1) break; + Player* player = (Player*)event.peer->data; + if (!player) break; + + ConnectedBitmask &= ~(1 << player->ID); + } + break; + } + + enet_packet_destroy(event.packet); + } + break; + } +} + +void LAN::ProcessClientEvent(ENetEvent& event) +{ + switch (event.type) + { + case ENET_EVENT_TYPE_CONNECT: + { + // another client is establishing a direct connection to us + + int playerid = -1; + for (int i = 0; i < 16; i++) + { + Player* player = &Players[i]; + if (i == MyPlayer.ID) continue; + if (player->Status != Player_Client) continue; + + if (player->Address == event.peer->address.host) + { + playerid = i; + break; + } + } + + if (playerid < 0) + { + enet_peer_disconnect(event.peer, 0); + break; + } + + RemotePeers[playerid] = event.peer; + event.peer->data = &Players[playerid]; + } + break; + + case ENET_EVENT_TYPE_DISCONNECT: + { + Player* player = (Player*)event.peer->data; + if (!player) break; + + ConnectedBitmask &= ~(1 << player->ID); + + int id = player->ID; + RemotePeers[id] = nullptr; + + Platform::Mutex_Lock(PlayersMutex); + player->Status = Player_Disconnected; + Platform::Mutex_Unlock(PlayersMutex); + + ClientUpdatePlayerList(); + } + break; + + case ENET_EVENT_TYPE_RECEIVE: + { + if (event.packet->dataLength < 1) break; + + u8* data = (u8*)event.packet->data; + switch (data[0]) + { + case Cmd_PlayerList: // host sending player list + { + if (event.packet->dataLength != (2+sizeof(Players))) break; + if (data[1] > 16) break; + + Platform::Mutex_Lock(PlayersMutex); + + NumPlayers = data[1]; + memcpy(Players, &data[2], sizeof(Players)); + for (int i = 0; i < 16; i++) + { + Players[i].Name[31] = '\0'; + } + + Platform::Mutex_Unlock(PlayersMutex); + + // establish connections to any new clients + for (int i = 0; i < 16; i++) + { + Player* player = &Players[i]; + if (i == MyPlayer.ID) continue; + if (player->Status != Player_Client) continue; + + if (!RemotePeers[i]) + { + ENetAddress peeraddr; + peeraddr.host = player->Address; + peeraddr.port = kLANPort; + ENetPeer* peer = enet_host_connect(Host, &peeraddr, 2, 0); + if (!peer) + { + // TODO deal with this + continue; + } + } + } + } + break; + + case Cmd_PlayerConnect: // player connected + { + if (event.packet->dataLength != 1) break; + Player* player = (Player*)event.peer->data; + if (!player) break; + + ConnectedBitmask |= (1 << player->ID); + } + break; + + case Cmd_PlayerDisconnect: // player disconnected + { + if (event.packet->dataLength != 1) break; + Player* player = (Player*)event.peer->data; + if (!player) break; + + ConnectedBitmask &= ~(1 << player->ID); + } + break; + } + + enet_packet_destroy(event.packet); + } + break; + } +} + +void LAN::ProcessEvent(ENetEvent& event) +{ + if (IsHost) + ProcessHostEvent(event); + else + ProcessClientEvent(event); +} + +// 0 = per-frame processing of events and eventual misc. frame +// 1 = checking if a misc. frame has arrived +// 2 = waiting for a MP frame +void LAN::ProcessLAN(int type) +{ + if (!Host) return; + + u32 time_last = (u32)Platform::GetMSCount(); + + // see if we have queued packets already, get rid of the stale ones + // any incoming packet should be consumed by the core quickly, so if + // they've been sitting in the queue for more than one frame's time, + // we can assume they're stale + while (!RXQueue.empty()) + { + ENetPacket* enetpacket = RXQueue.front(); + MPPacketHeader* header = (MPPacketHeader*)&enetpacket->data[0]; + u32 packettime = header->Magic; + + if ((packettime > time_last) || (packettime < (time_last - 16))) + { + RXQueue.pop(); + enet_packet_destroy(enetpacket); + } + else + { + // we got a packet, depending on what the caller wants we might be able to return now + if (type == 2) return; + if (type == 1) + { + // if looking for a misc. frame, we shouldn't be receiving a MP frame + if (header->Type == 0) + return; + + RXQueue.pop(); + enet_packet_destroy(enetpacket); + } + + break; + } + } + + int timeout = (type == 2) ? MPRecvTimeout : 0; + time_last = (u32)Platform::GetMSCount(); + + ENetEvent event; + while (enet_host_service(Host, &event, timeout) > 0) + { + if (event.type == ENET_EVENT_TYPE_RECEIVE && event.channelID == Chan_MP) + { + MPPacketHeader* header = (MPPacketHeader*)&event.packet->data[0]; + + bool good = true; + if (event.packet->dataLength < sizeof(MPPacketHeader)) + good = false; + else if (header->Magic != 0x4946494E) + good = false; + else if (header->SenderID == MyPlayer.ID) + good = false; + + if (!good) + { + enet_packet_destroy(event.packet); + } + else + { + // mark this packet with the time it was received + header->Magic = (u32)Platform::GetMSCount(); + + event.packet->userData = event.peer; + RXQueue.push(event.packet); + + // return now -- if we are receiving MP frames, if we keep going + // we'll consume too many even if we have no timeout set + return; + } + } + else + { + ProcessEvent(event); + } + + if (type == 2) + { + u32 time = (u32)Platform::GetMSCount(); + if (time < time_last) return; + timeout -= (int)(time - time_last); + if (timeout <= 0) return; + time_last = time; + } + } +} + +void LAN::Process() +{ + if (!Active) return; + + ProcessDiscovery(); + ProcessLAN(0); + + FrameCount++; + if (FrameCount >= 60) + { + FrameCount = 0; + + Platform::Mutex_Lock(PlayersMutex); + + for (int i = 0; i < 16; i++) + { + if (Players[i].Status == Player_None) continue; + if (i == MyPlayer.ID) continue; + if (!RemotePeers[i]) continue; + + Players[i].Ping = RemotePeers[i]->roundTripTime; + } + + Platform::Mutex_Unlock(PlayersMutex); + } +} + + +void LAN::Begin(int inst) +{ + if (!Host) return; + + ConnectedBitmask |= (1 << MyPlayer.ID); + LastHostID = -1; + LastHostPeer = nullptr; + + u8 cmd = Cmd_PlayerConnect; + ENetPacket* pkt = enet_packet_create(&cmd, 1, ENET_PACKET_FLAG_RELIABLE); + enet_host_broadcast(Host, Chan_Cmd, pkt); +} + +void LAN::End(int inst) +{ + if (!Host) return; + + ConnectedBitmask &= ~(1 << MyPlayer.ID); + + u8 cmd = Cmd_PlayerDisconnect; + ENetPacket* pkt = enet_packet_create(&cmd, 1, ENET_PACKET_FLAG_RELIABLE); + enet_host_broadcast(Host, Chan_Cmd, pkt); +} + + +int LAN::SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp) +{ + if (!Host) return 0; + + // TODO make the reliable part optional? + //u32 flags = ENET_PACKET_FLAG_RELIABLE; + u32 flags = ENET_PACKET_FLAG_UNSEQUENCED; + + ENetPacket* enetpacket = enet_packet_create(nullptr, sizeof(MPPacketHeader)+len, flags); + + MPPacketHeader pktheader; + pktheader.Magic = 0x4946494E; + pktheader.SenderID = MyPlayer.ID; + pktheader.Type = type; + pktheader.Length = len; + pktheader.Timestamp = timestamp; + memcpy(&enetpacket->data[0], &pktheader, sizeof(MPPacketHeader)); + if (len) + memcpy(&enetpacket->data[sizeof(MPPacketHeader)], packet, len); + + if (((type & 0xFFFF) == 2) && LastHostPeer) + enet_peer_send(LastHostPeer, Chan_MP, enetpacket); + else + enet_host_broadcast(Host, Chan_MP, enetpacket); + enet_host_flush(Host); + + return len; +} + +int LAN::RecvPacketGeneric(u8* packet, bool block, u64* timestamp) +{ + if (!Host) return 0; + + ProcessLAN(block ? 2 : 1); + if (RXQueue.empty()) return 0; + + ENetPacket* enetpacket = RXQueue.front(); + RXQueue.pop(); + MPPacketHeader* header = (MPPacketHeader*)&enetpacket->data[0]; + + u32 len = header->Length; + if (len) + { + if (len > 2048) len = 2048; + + memcpy(packet, &enetpacket->data[sizeof(MPPacketHeader)], len); + + if (header->Type == 1) + { + LastHostID = header->SenderID; + LastHostPeer = (ENetPeer*)enetpacket->userData; + } + } + + if (timestamp) *timestamp = header->Timestamp; + enet_packet_destroy(enetpacket); + return len; +} + + +int LAN::SendPacket(int inst, u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(0, packet, len, timestamp); +} + +int LAN::RecvPacket(int inst, u8* packet, u64* timestamp) +{ + return RecvPacketGeneric(packet, false, timestamp); +} + + +int LAN::SendCmd(int inst, u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(1, packet, len, timestamp); +} + +int LAN::SendReply(int inst, u8* packet, int len, u64 timestamp, u16 aid) +{ + return SendPacketGeneric(2 | (aid<<16), packet, len, timestamp); +} + +int LAN::SendAck(int inst, u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(3, packet, len, timestamp); +} + +int LAN::RecvHostPacket(int inst, u8* packet, u64* timestamp) +{ + if (LastHostID != -1) + { + // check if the host is still connected + + if (!(ConnectedBitmask & (1<data[0]; + bool good = true; + if ((header->Type & 0xFFFF) != 2) + good = false; + else if (header->Timestamp < (timestamp - 32)) + good = false; + + if (good) + { + u32 len = header->Length; + if (len) + { + if (len > 1024) len = 1024; + + u32 aid = header->Type >> 16; + memcpy(&packets[(aid-1)*1024], &enetpacket->data[sizeof(MPPacketHeader)], len); + + ret |= (1<SenderID); + if (((myinstmask & ConnectedBitmask) == ConnectedBitmask) || + ((ret & aidmask) == aidmask)) + { + // all the clients have sent their reply + enet_packet_destroy(enetpacket); + return ret; + } + } + + enet_packet_destroy(enetpacket); + } +} + +} diff --git a/src/net/LAN.h b/src/net/LAN.h new file mode 100644 index 00000000..87282539 --- /dev/null +++ b/src/net/LAN.h @@ -0,0 +1,156 @@ +/* + Copyright 2016-2024 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 LAN_H +#define LAN_H + +#include +#include +#include +#include + +#include + +#ifndef socket_t + #ifdef __WIN32__ + #include + #define socket_t SOCKET + #else + #define socket_t int + #endif +#endif + +#include "types.h" +#include "Platform.h" +#include "MPInterface.h" + +namespace melonDS +{ + +class LAN : public MPInterface +{ +public: + LAN() noexcept; + LAN(const LAN&) = delete; + LAN& operator=(const LAN&) = delete; + LAN(LAN&& other) = delete; + LAN& operator=(LAN&& other) = delete; + ~LAN() noexcept; + + enum PlayerStatus + { + Player_None = 0, // no player in this entry + Player_Client, // game client + Player_Host, // game host + Player_Connecting, // player still connecting + Player_Disconnected, // player disconnected + }; + + struct Player + { + int ID; + char Name[32]; + PlayerStatus Status; + u32 Address; + + bool IsLocalPlayer; + u32 Ping; + }; + + struct DiscoveryData + { + u32 Magic; + u32 Version; + u32 Tick; + char SessionName[64]; + u8 NumPlayers; + u8 MaxPlayers; + u8 Status; // 0=idle 1=playing + }; + + bool StartDiscovery(); + void EndDiscovery(); + bool StartHost(const char* player, int numplayers); + bool StartClient(const char* player, const char* host); + void EndSession(); + + std::map GetDiscoveryList(); + std::vector GetPlayerList(); + int GetNumPlayers() { return NumPlayers; } + int GetMaxPlayers() { return MaxPlayers; } + + void Process() override; + + void Begin(int inst) override; + void End(int inst) override; + + int SendPacket(int inst, u8* data, int len, u64 timestamp) override; + int RecvPacket(int inst, u8* data, u64* timestamp) override; + int SendCmd(int inst, u8* data, int len, u64 timestamp) override; + int SendReply(int inst, u8* data, int len, u64 timestamp, u16 aid) override; + int SendAck(int inst, u8* data, int len, u64 timestamp) override; + int RecvHostPacket(int inst, u8* data, u64* timestamp) override; + u16 RecvReplies(int inst, u8* data, u64 timestamp, u16 aidmask) override; + +private: + bool Inited; + bool Active; + bool IsHost; + + ENetHost* Host; + ENetPeer* RemotePeers[16]; + + socket_t DiscoverySocket; + u32 DiscoveryLastTick; + std::map DiscoveryList; + Platform::Mutex* DiscoveryMutex; + + Player Players[16]; + int NumPlayers; + int MaxPlayers; + Platform::Mutex* PlayersMutex; + + Player MyPlayer; + u32 HostAddress; + + u16 ConnectedBitmask; + + int MPRecvTimeout; + int LastHostID; + ENetPeer* LastHostPeer; + std::queue RXQueue; + + u32 FrameCount; + + void ProcessDiscovery(); + + void HostUpdatePlayerList(); + void ClientUpdatePlayerList(); + + void ProcessHostEvent(ENetEvent& event); + void ProcessClientEvent(ENetEvent& event); + void ProcessEvent(ENetEvent& event); + void ProcessLAN(int type); + + int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp); + int RecvPacketGeneric(u8* packet, bool block, u64* timestamp); +}; + +} + +#endif // LAN_H diff --git a/src/net/LocalMP.cpp b/src/net/LocalMP.cpp new file mode 100644 index 00000000..a789964e --- /dev/null +++ b/src/net/LocalMP.cpp @@ -0,0 +1,367 @@ +/* + Copyright 2016-2024 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 "LocalMP.h" + +using namespace melonDS; +using namespace melonDS::Platform; + +using Platform::Log; +using Platform::LogLevel; + +namespace melonDS +{ + +LocalMP::LocalMP() noexcept : + MPQueueLock(Mutex_Create()) +{ + memset(MPPacketQueue, 0, kPacketQueueSize); + memset(MPReplyQueue, 0, kReplyQueueSize); + memset(&MPStatus, 0, sizeof(MPStatus)); + memset(PacketReadOffset, 0, sizeof(PacketReadOffset)); + memset(ReplyReadOffset, 0, sizeof(ReplyReadOffset)); + + // 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 + + for (int i = 0; i < 32; i++) + { + SemPool[i] = Semaphore_Create(); + } + + Log(LogLevel::Info, "MP comm init OK\n"); +} + +LocalMP::~LocalMP() noexcept +{ + for (int i = 0; i < 32; i++) + { + Semaphore_Free(SemPool[i]); + SemPool[i] = nullptr; + } + + Mutex_Free(MPQueueLock); +} + +void LocalMP::Begin(int inst) +{ + Mutex_Lock(MPQueueLock); + PacketReadOffset[inst] = MPStatus.PacketWriteOffset; + ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset; + Semaphore_Reset(SemPool[inst]); + Semaphore_Reset(SemPool[16 + inst]); + MPStatus.ConnectedBitmask |= (1 << inst); + Mutex_Unlock(MPQueueLock); +} + +void LocalMP::End(int inst) +{ + Mutex_Lock(MPQueueLock); + MPStatus.ConnectedBitmask &= ~(1 << inst); + Mutex_Unlock(MPQueueLock); +} + +void LocalMP::FIFORead(int inst, int fifo, void* buf, int len) noexcept +{ + u8* data; + + u32 offset, datalen; + if (fifo == 0) + { + offset = PacketReadOffset[inst]; + data = MPPacketQueue; + datalen = kPacketQueueSize; + } + else + { + offset = ReplyReadOffset[inst]; + data = MPReplyQueue; + datalen = kReplyQueueSize; + } + + if ((offset + len) >= datalen) + { + u32 part1 = datalen - offset; + memcpy(buf, &data[offset], part1); + memcpy(&((u8*)buf)[part1], data, len - part1); + offset = len - part1; + } + else + { + memcpy(buf, &data[offset], len); + offset += len; + } + + if (fifo == 0) PacketReadOffset[inst] = offset; + else ReplyReadOffset[inst] = offset; +} + +void LocalMP::FIFOWrite(int inst, int fifo, void* buf, int len) noexcept +{ + u8* data; + + u32 offset, datalen; + if (fifo == 0) + { + offset = MPStatus.PacketWriteOffset; + data = MPPacketQueue; + datalen = kPacketQueueSize; + } + else + { + offset = MPStatus.ReplyWriteOffset; + data = MPReplyQueue; + datalen = kReplyQueueSize; + } + + if ((offset + len) >= datalen) + { + u32 part1 = datalen - offset; + memcpy(&data[offset], buf, part1); + memcpy(data, &((u8*)buf)[part1], len - part1); + offset = len - part1; + } + else + { + memcpy(&data[offset], buf, len); + offset += len; + } + + if (fifo == 0) MPStatus.PacketWriteOffset = offset; + else MPStatus.ReplyWriteOffset = offset; +} + +int LocalMP::SendPacketGeneric(int inst, u32 type, u8* packet, int len, u64 timestamp) noexcept +{ + if (len > kMaxFrameSize) + { + Log(LogLevel::Warn, "wifi: attempting to send frame too big (len=%d max=%d)\n", len, kMaxFrameSize); + return 0; + } + + Mutex_Lock(MPQueueLock); + + u16 mask = MPStatus.ConnectedBitmask; + + // TODO: check if the FIFO is full! + + MPPacketHeader pktheader; + pktheader.Magic = 0x4946494E; + pktheader.SenderID = inst; + pktheader.Type = type; + pktheader.Length = len; + pktheader.Timestamp = timestamp; + + type &= 0xFFFF; + int nfifo = (type == 2) ? 1 : 0; + FIFOWrite(inst, nfifo, &pktheader, sizeof(pktheader)); + if (len) + FIFOWrite(inst, 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 + MPStatus.MPHostinst = inst; + MPStatus.MPReplyBitmask = 0; + ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset; + Semaphore_Reset(SemPool[16 + inst]); + } + else if (type == 2) + { + MPStatus.MPReplyBitmask |= (1 << inst); + } + + Mutex_Unlock(MPQueueLock); + + if (type == 2) + { + Semaphore_Post(SemPool[16 + MPStatus.MPHostinst]); + } + else + { + for (int i = 0; i < 16; i++) + { + if (mask & (1<= kPacketQueueSize) + PacketReadOffset[inst] -= kPacketQueueSize; + + Mutex_Unlock(MPQueueLock); + continue; + } + + if (pktheader.Length) + { + FIFORead(inst, 0, packet, pktheader.Length); + + if (pktheader.Type == 1) + LastHostID = pktheader.SenderID; + } + + if (timestamp) *timestamp = pktheader.Timestamp; + Mutex_Unlock(MPQueueLock); + return pktheader.Length; + } +} + +int LocalMP::SendPacket(int inst, u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(inst, 0, packet, len, timestamp); +} + +int LocalMP::RecvPacket(int inst, u8* packet, u64* timestamp) +{ + return RecvPacketGeneric(inst, packet, false, timestamp); +} + +int LocalMP::SendCmd(int inst, u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(inst, 1, packet, len, timestamp); +} + +int LocalMP::SendReply(int inst, u8* packet, int len, u64 timestamp, u16 aid) +{ + return SendPacketGeneric(inst, 2 | (aid<<16), packet, len, timestamp); +} + +int LocalMP::SendAck(int inst, u8* packet, int len, u64 timestamp) +{ + return SendPacketGeneric(inst, 3, packet, len, timestamp); +} + +int LocalMP::RecvHostPacket(int inst, u8* packet, u64* timestamp) +{ + if (LastHostID != -1) + { + // check if the host is still connected + + u16 curinstmask = MPStatus.ConnectedBitmask; + + if (!(curinstmask & (1 << LastHostID))) + return -1; + } + + return RecvPacketGeneric(inst, packet, true, timestamp); +} + +u16 LocalMP::RecvReplies(int inst, u8* packets, u64 timestamp, u16 aidmask) +{ + u16 ret = 0; + u16 myinstmask = (1 << inst); + u16 curinstmask; + + curinstmask = MPStatus.ConnectedBitmask; + + // if all clients have left: return early + if ((myinstmask & curinstmask) == curinstmask) + return 0; + + for (;;) + { + if (!Semaphore_TryWait(SemPool[16+inst], RecvTimeout)) + { + // no more replies available + return ret; + } + + Mutex_Lock(MPQueueLock); + + MPPacketHeader pktheader = {}; + FIFORead(inst, 1, &pktheader, sizeof(pktheader)); + + if (pktheader.Magic != 0x4946494E) + { + Log(LogLevel::Warn, "REPLY FIFO OVERFLOW\n"); + ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset; + Semaphore_Reset(SemPool[16 + inst]); + Mutex_Unlock(MPQueueLock); + return 0; + } + + if ((pktheader.SenderID == inst) || // packet we sent out (shouldn't happen, but hey) + (pktheader.Timestamp < (timestamp - 32))) // stale packet + { + // skip this packet + ReplyReadOffset[inst] += pktheader.Length; + if (ReplyReadOffset[inst] >= kReplyQueueSize) + ReplyReadOffset[inst] -= kReplyQueueSize; + + Mutex_Unlock(MPQueueLock); + continue; + } + + if (pktheader.Length) + { + u32 aid = (pktheader.Type >> 16); + FIFORead(inst, 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 + + Mutex_Unlock(MPQueueLock); + return ret; + } + + Mutex_Unlock(MPQueueLock); + } +} + +} + diff --git a/src/net/LocalMP.h b/src/net/LocalMP.h new file mode 100644 index 00000000..8688d8e1 --- /dev/null +++ b/src/net/LocalMP.h @@ -0,0 +1,82 @@ +/* + Copyright 2016-2024 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" +#include "Platform.h" +#include "MPInterface.h" + +namespace melonDS +{ +struct MPStatusData +{ + u16 ConnectedBitmask; // bitmask of which instances are ready to send/receive packets + u32 PacketWriteOffset; + u32 ReplyWriteOffset; + u16 MPHostinst; // instance ID from which the last CMD frame was sent + u16 MPReplyBitmask; // bitmask of which clients replied in time +}; + +constexpr u32 kPacketQueueSize = 0x10000; +constexpr u32 kReplyQueueSize = 0x10000; +constexpr u32 kMaxFrameSize = 0x948; + +class LocalMP : public MPInterface +{ +public: + LocalMP() noexcept; + LocalMP(const LocalMP&) = delete; + LocalMP& operator=(const LocalMP&) = delete; + LocalMP(LocalMP&& other) = delete; + LocalMP& operator=(LocalMP&& other) = delete; + ~LocalMP() noexcept; + + void Process() {} + + void Begin(int inst); + void End(int inst); + + int SendPacket(int inst, u8* data, int len, u64 timestamp); + int RecvPacket(int inst, u8* data, u64* timestamp); + int SendCmd(int inst, u8* data, int len, u64 timestamp); + int SendReply(int inst, u8* data, int len, u64 timestamp, u16 aid); + int SendAck(int inst, u8* data, int len, u64 timestamp); + int RecvHostPacket(int inst, u8* data, u64* timestamp); + u16 RecvReplies(int inst, u8* data, u64 timestamp, u16 aidmask); + +private: + void FIFORead(int inst, int fifo, void* buf, int len) noexcept; + void FIFOWrite(int inst, int fifo, void* buf, int len) noexcept; + int SendPacketGeneric(int inst, u32 type, u8* packet, int len, u64 timestamp) noexcept; + int RecvPacketGeneric(int inst, u8* packet, bool block, u64* timestamp) noexcept; + + Platform::Mutex* MPQueueLock; + MPStatusData MPStatus {}; + u8 MPPacketQueue[kPacketQueueSize] {}; + u8 MPReplyQueue[kReplyQueueSize] {}; + u32 PacketReadOffset[16] {}; + u32 ReplyReadOffset[16] {}; + + int LastHostID = -1; + Platform::Semaphore* SemPool[32] {}; +}; +} + +#endif // LOCALMP_H diff --git a/src/net/MPInterface.cpp b/src/net/MPInterface.cpp new file mode 100644 index 00000000..39d1915d --- /dev/null +++ b/src/net/MPInterface.cpp @@ -0,0 +1,68 @@ +/* + Copyright 2016-2024 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 "MPInterface.h" +#include "LocalMP.h" +#include "LAN.h" + +namespace melonDS +{ + +class DummyMP : public MPInterface +{ +public: + void Process() override {} + + void Begin(int inst) override {} + void End(int inst) override {} + + int SendPacket(int inst, u8* data, int len, u64 timestamp) override { return 0; } + int RecvPacket(int inst, u8* data, u64* timestamp) override { return 0; } + int SendCmd(int inst, u8* data, int len, u64 timestamp) override { return 0; } + int SendReply(int inst, u8* data, int len, u64 timestamp, u16 aid) override { return 0; } + int SendAck(int inst, u8* data, int len, u64 timestamp) override { return 0; } + int RecvHostPacket(int inst, u8* data, u64* timestamp) override { return 0; } + u16 RecvReplies(int inst, u8* data, u64 timestamp, u16 aidmask) override { return 0; } +}; + + +std::unique_ptr MPInterface::Current(std::make_unique()); +MPInterfaceType MPInterface::CurrentType = MPInterface_Dummy; + + +void MPInterface::Set(MPInterfaceType type) +{ + switch (type) + { + case MPInterface_Local: + Current = std::make_unique(); + break; + + case MPInterface_LAN: + Current = std::make_unique(); + break; + + default: + Current = std::make_unique(); + break; + } + + CurrentType = type; +} + +} diff --git a/src/net/MPInterface.h b/src/net/MPInterface.h new file mode 100644 index 00000000..eb5bef88 --- /dev/null +++ b/src/net/MPInterface.h @@ -0,0 +1,82 @@ +/* + Copyright 2016-2024 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 MPINTERFACE_H +#define MPINTERFACE_H + +#include +#include "types.h" + +namespace melonDS +{ + +// TODO: provision for excluding unwanted interfaces at compile time +enum MPInterfaceType +{ + MPInterface_Dummy = -1, + MPInterface_Local, + MPInterface_LAN, + MPInterface_Netplay, +}; + +struct MPPacketHeader +{ + u32 Magic; + u32 SenderID; + u32 Type; // 0=regular 1=CMD 2=reply 3=ack + u32 Length; + u64 Timestamp; +}; + +class MPInterface +{ +public: + virtual ~MPInterface() = default; + + static MPInterface& Get() { return *Current; } + static MPInterfaceType GetType() { return CurrentType; } + static void Set(MPInterfaceType type); + + [[nodiscard]] int GetRecvTimeout() const noexcept { return RecvTimeout; } + void SetRecvTimeout(int timeout) noexcept { RecvTimeout = timeout; } + + // function called every video frame + virtual void Process() = 0; + + virtual void Begin(int inst) = 0; + virtual void End(int inst) = 0; + + virtual int SendPacket(int inst, u8* data, int len, u64 timestamp) = 0; + virtual int RecvPacket(int inst, u8* data, u64* timestamp) = 0; + virtual int SendCmd(int inst, u8* data, int len, u64 timestamp) = 0; + virtual int SendReply(int inst, u8* data, int len, u64 timestamp, u16 aid) = 0; + virtual int SendAck(int inst, u8* data, int len, u64 timestamp) = 0; + virtual int RecvHostPacket(int inst, u8* data, u64* timestamp) = 0; + virtual u16 RecvReplies(int inst, u8* data, u64 timestamp, u16 aidmask) = 0; + +protected: + int RecvTimeout = 25; + +private: + static MPInterfaceType CurrentType; + static std::unique_ptr Current; +}; + +} + +#endif // MPINTERFACE_H diff --git a/src/net/Net.cpp b/src/net/Net.cpp new file mode 100644 index 00000000..5c169f35 --- /dev/null +++ b/src/net/Net.cpp @@ -0,0 +1,70 @@ +/* + Copyright 2016-2024 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 "Net.h" +#include "PacketDispatcher.h" +#include "Platform.h" + +namespace melonDS +{ + +using Platform::Log; +using Platform::LogLevel; + +void Net::RegisterInstance(int inst) +{ + Dispatcher.registerInstance(inst); +} + +void Net::UnregisterInstance(int inst) +{ + Dispatcher.unregisterInstance(inst); +} + + +void Net::RXEnqueue(const void* buf, int len) +{ + Dispatcher.sendPacket(nullptr, 0, buf, len, 16, 0xFFFF); +} + + +int Net::SendPacket(u8* data, int len, int inst) +{ + if (!Driver) + return 0; + + return Driver->SendPacket(data, len); +} + +int Net::RecvPacket(u8* data, int inst) +{ + if (!Driver) + return 0; + + Driver->RecvCheck(); + + int ret = 0; + if (!Dispatcher.recvPacket(nullptr, nullptr, data, &ret, inst)) + return 0; + + return ret; +} + +} diff --git a/src/net/Net.h b/src/net/Net.h new file mode 100644 index 00000000..4229468c --- /dev/null +++ b/src/net/Net.h @@ -0,0 +1,61 @@ +/* + Copyright 2016-2024 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 NET_H +#define NET_H + +#include + +#include "types.h" +#include "PacketDispatcher.h" +#include "NetDriver.h" + +namespace melonDS +{ + +class Net +{ +public: + Net() noexcept = default; + Net(const Net&) = delete; + Net& operator=(const Net&) = delete; + // Not movable because of callbacks that point to this object + Net(Net&& other) = delete; + Net& operator=(Net&& other) = delete; + ~Net() noexcept = default; + + void RegisterInstance(int inst); + void UnregisterInstance(int inst); + + void RXEnqueue(const void* buf, int len); + + int SendPacket(u8* data, int len, int inst); + int RecvPacket(u8* data, int inst); + + void SetDriver(std::unique_ptr&& driver) noexcept { Driver = std::move(driver); } + [[nodiscard]] std::unique_ptr& GetDriver() noexcept { return Driver; } + [[nodiscard]] const std::unique_ptr& GetDriver() const noexcept { return Driver; } + +private: + PacketDispatcher Dispatcher {}; + std::unique_ptr Driver = nullptr; +}; + +} + +#endif // NET_H diff --git a/src/frontend/qt_sdl/LAN_Socket.h b/src/net/NetDriver.h similarity index 69% rename from src/frontend/qt_sdl/LAN_Socket.h rename to src/net/NetDriver.h index 043e1330..2575fdea 100644 --- a/src/frontend/qt_sdl/LAN_Socket.h +++ b/src/net/NetDriver.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -16,24 +16,20 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#ifndef LAN_SOCKET_H -#define LAN_SOCKET_H +#ifndef MELONDS_NETDRIVER_H +#define MELONDS_NETDRIVER_H #include "types.h" -namespace LAN_Socket +namespace melonDS { -using namespace melonDS; - -// - - -bool Init(); -void DeInit(); - -int SendPacket(u8* data, int len); -int RecvPacket(u8* data); - +class NetDriver +{ +public: + virtual ~NetDriver() = default; + virtual int SendPacket(u8* data, int len) noexcept = 0; + virtual void RecvCheck() noexcept = 0; +}; } -#endif // LAN_SOCKET_H +#endif //MELONDS_NETDRIVER_H diff --git a/src/net/Net_PCap.cpp b/src/net/Net_PCap.cpp new file mode 100644 index 00000000..e92ad06d --- /dev/null +++ b/src/net/Net_PCap.cpp @@ -0,0 +1,461 @@ +/* + Copyright 2016-2024 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 "Net.h" +#include "Net_PCap.h" +#include "Platform.h" + +#ifdef __WIN32__ + #include +#else + #include + #include + #include + #ifdef __linux__ + #include + #else + #include + #include + #endif +#endif + +using namespace melonDS; +using Platform::Log; +using Platform::LogLevel; + +// welp +#ifndef PCAP_OPENFLAG_PROMISCUOUS +#define PCAP_OPENFLAG_PROMISCUOUS 1 +#endif + +namespace melonDS +{ + +const char* PCapLibNames[] = +{ +#ifdef __WIN32__ + // TODO: name for npcap in non-WinPCap mode + "wpcap.dll", +#elif defined(__APPLE__) + "libpcap.A.dylib", + "libpcap.dylib", +#else + // Linux lib names + "libpcap.so.1", + "libpcap.so", +#endif + nullptr +}; + +std::optional LibPCap::New() noexcept +{ + for (int i = 0; PCapLibNames[i]; i++) + { + Platform::DynamicLibrary* lib = Platform::DynamicLibrary_Load(PCapLibNames[i]); + if (!lib) continue; + + LibPCap pcap; + // Use a custom deleter to clean up the DLL automatically + // (in this case, the deleter is the DynamicLibrary_Unload function) + pcap.PCapLib = std::shared_ptr(lib, Platform::DynamicLibrary_Unload); + + if (!TryLoadPCap(pcap, lib)) + { + Platform::DynamicLibrary_Unload(lib); + continue; + } + + Log(LogLevel::Info, "PCap: lib %s, init successful\n", PCapLibNames[i]); + return pcap; + } + + Log(LogLevel::Error, "PCap: init failed\n"); + return std::nullopt; +} + +LibPCap::LibPCap(LibPCap&& other) noexcept +{ + PCapLib = std::move(other.PCapLib); + findalldevs = other.findalldevs; + freealldevs = other.freealldevs; + open_live = other.open_live; + close = other.close; + setnonblock = other.setnonblock; + sendpacket = other.sendpacket; + dispatch = other.dispatch; + next = other.next; + + other.PCapLib = nullptr; + other.findalldevs = nullptr; + other.freealldevs = nullptr; + other.open_live = nullptr; + other.close = nullptr; + other.setnonblock = nullptr; + other.sendpacket = nullptr; + other.dispatch = nullptr; + other.next = nullptr; +} + +LibPCap& LibPCap::operator=(LibPCap&& other) noexcept +{ + if (this != &other) + { + PCapLib = nullptr; + // Unloads the DLL due to the custom deleter + + PCapLib = std::move(other.PCapLib); + findalldevs = other.findalldevs; + freealldevs = other.freealldevs; + open_live = other.open_live; + close = other.close; + setnonblock = other.setnonblock; + sendpacket = other.sendpacket; + dispatch = other.dispatch; + next = other.next; + + other.PCapLib = nullptr; + other.findalldevs = nullptr; + other.freealldevs = nullptr; + other.open_live = nullptr; + other.close = nullptr; + other.setnonblock = nullptr; + other.sendpacket = nullptr; + other.dispatch = nullptr; + other.next = nullptr; + } + + return *this; +} + +bool LibPCap::TryLoadPCap(LibPCap& pcap, Platform::DynamicLibrary *lib) noexcept +{ + pcap.findalldevs = (pcap_findalldevs_t)Platform::DynamicLibrary_LoadFunction(lib, "pcap_findalldevs"); + if (!pcap.findalldevs) return false; + + pcap.freealldevs = (pcap_freealldevs_t)Platform::DynamicLibrary_LoadFunction(lib, "pcap_freealldevs"); + if (!pcap.freealldevs) return false; + + pcap.open_live = (pcap_open_live_t)Platform::DynamicLibrary_LoadFunction(lib, "pcap_open_live"); + if (!pcap.open_live) return false; + + pcap.close = (pcap_close_t)Platform::DynamicLibrary_LoadFunction(lib, "pcap_close"); + if (!pcap.close) return false; + + pcap.setnonblock = (pcap_setnonblock_t)Platform::DynamicLibrary_LoadFunction(lib, "pcap_setnonblock"); + if (!pcap.setnonblock) return false; + + pcap.sendpacket = (pcap_sendpacket_t)Platform::DynamicLibrary_LoadFunction(lib, "pcap_sendpacket"); + if (!pcap.sendpacket) return false; + + pcap.dispatch = (pcap_dispatch_t)Platform::DynamicLibrary_LoadFunction(lib, "pcap_dispatch"); + if (!pcap.dispatch) return false; + + pcap.next = (pcap_next_t)Platform::DynamicLibrary_LoadFunction(lib, "pcap_next"); + if (!pcap.next) return false; + + return true; +} + +std::vector LibPCap::GetAdapters() const noexcept +{ + // TODO: how to deal with cases where an adapter is unplugged or changes config?? + if (!IsValid()) + { + Log(LogLevel::Error, "PCap: instance not initialized\n"); + return {}; + } + + char errbuf[PCAP_ERRBUF_SIZE]; + + pcap_if_t* alldevs = nullptr; + if (int ret = findalldevs(&alldevs, errbuf); ret < 0) + { // If there was an error... + errbuf[PCAP_ERRBUF_SIZE - 1] = '\0'; + Log(LogLevel::Error, "PCap: Error %d finding devices: %s\n", ret, errbuf); + } + + if (alldevs == nullptr) + { // If no devices were found... + Log(LogLevel::Warn, "PCap: no devices available\n"); + return {}; + } + + std::vector adapters; + for (pcap_if_t* dev = alldevs; dev != nullptr; dev = dev->next) + { + adapters.emplace_back(); // Add a new (empty) adapter to the list + AdapterData& adata = adapters.back(); + strncpy(adata.DeviceName, dev->name, 127); + adata.DeviceName[127] = '\0'; + adata.Flags = dev->flags; + +#ifndef __WIN32__ + strncpy(adata.FriendlyName, adata.DeviceName, 127); + adata.FriendlyName[127] = '\0'; +#endif // __WIN32__ + } + +#ifdef __WIN32__ + + ULONG bufsize = 16384; + IP_ADAPTER_ADDRESSES* buf = (IP_ADAPTER_ADDRESSES*)HeapAlloc(GetProcessHeap(), 0, bufsize); + ULONG uret = GetAdaptersAddresses(AF_INET, 0, nullptr, buf, &bufsize); + if (uret == ERROR_BUFFER_OVERFLOW) + { + HeapFree(GetProcessHeap(), 0, buf); + buf = (IP_ADAPTER_ADDRESSES*)HeapAlloc(GetProcessHeap(), 0, bufsize); + uret = GetAdaptersAddresses(AF_INET, 0, nullptr, buf, &bufsize); + } + if (uret != ERROR_SUCCESS) + { + Log(LogLevel::Error, "GetAdaptersAddresses() shat itself: %08X\n", uret); + freealldevs(alldevs); + return {}; + } + + for (AdapterData& adata : adapters) + { + IP_ADAPTER_ADDRESSES* addr = buf; + while (addr) + { + if (strcmp(addr->AdapterName, &adata.DeviceName[12])) + { + addr = addr->Next; + continue; + } + + WideCharToMultiByte(CP_UTF8, 0, addr->FriendlyName, 127, adata.FriendlyName, 127, nullptr, nullptr); + adata.FriendlyName[127] = '\0'; + + WideCharToMultiByte(CP_UTF8, 0, addr->Description, 127, adata.Description, 127, nullptr, nullptr); + adata.Description[127] = '\0'; + + if (addr->PhysicalAddressLength != 6) + { + Log(LogLevel::Warn, "weird MAC addr length %d for %s\n", addr->PhysicalAddressLength, addr->AdapterName); + } + else + memcpy(adata.MAC, addr->PhysicalAddress, 6); + + IP_ADAPTER_UNICAST_ADDRESS* ipaddr = addr->FirstUnicastAddress; + while (ipaddr) + { + SOCKADDR* sa = ipaddr->Address.lpSockaddr; + if (sa->sa_family == AF_INET) + { + struct in_addr sa4 = ((sockaddr_in*)sa)->sin_addr; + memcpy(adata.IP_v4, &sa4, 4); + } + + ipaddr = ipaddr->Next; + } + + break; + } + } + + HeapFree(GetProcessHeap(), 0, buf); + +#else + + struct ifaddrs* addrs; + if (getifaddrs(&addrs) != 0) + { + Log(LogLevel::Error, "getifaddrs() shat itself :(\n"); + return {}; + } + + for (const AdapterData& adata : adapters) + { + struct ifaddrs* curaddr = addrs; + while (curaddr) + { + if (strcmp(curaddr->ifa_name, adata.DeviceName)) + { + curaddr = curaddr->ifa_next; + continue; + } + + if (!curaddr->ifa_addr) + { + Log(LogLevel::Error, "Device (%s) does not have an address :/\n", curaddr->ifa_name); + curaddr = curaddr->ifa_next; + continue; + } + + u16 af = curaddr->ifa_addr->sa_family; + if (af == AF_INET) + { + struct sockaddr_in* sa = (sockaddr_in*)curaddr->ifa_addr; + memcpy((void*)adata.IP_v4, &sa->sin_addr, 4); + } +#ifdef __linux__ + else if (af == AF_PACKET) + { + struct sockaddr_ll* sa = (sockaddr_ll*)curaddr->ifa_addr; + if (sa->sll_halen != 6) + Log(LogLevel::Warn, "weird MAC length %d for %s\n", sa->sll_halen, curaddr->ifa_name); + else + memcpy((void*)adata.MAC, sa->sll_addr, 6); + } +#else + else if (af == AF_LINK) + { + struct sockaddr_dl* sa = (sockaddr_dl*)curaddr->ifa_addr; + if (sa->sdl_alen != 6) + Log(LogLevel::Warn, "weird MAC length %d for %s\n", sa->sdl_alen, curaddr->ifa_name); + else + memcpy((void*)adata.MAC, LLADDR(sa), 6); + } +#endif + curaddr = curaddr->ifa_next; + } + } + + freeifaddrs(addrs); + +#endif // __WIN32__ + + freealldevs(alldevs); + return adapters; +} + +std::unique_ptr LibPCap::Open(const AdapterData& device, const Platform::SendPacketCallback& handler) const noexcept +{ + return Open(device.DeviceName, handler); +} + +std::unique_ptr LibPCap::Open(std::string_view devicename, const Platform::SendPacketCallback& handler) const noexcept +{ + char errbuf[PCAP_ERRBUF_SIZE]; + pcap_t* adapter = open_live(devicename.data(), 2048, PCAP_OPENFLAG_PROMISCUOUS, 1, errbuf); + if (!adapter) + { + errbuf[PCAP_ERRBUF_SIZE - 1] = '\0'; + Log(LogLevel::Error, "PCap: failed to open adapter: %s\n", errbuf); + return nullptr; + } + + if (int err = setnonblock(adapter, 1, errbuf); err < 0) + { + errbuf[PCAP_ERRBUF_SIZE - 1] = '\0'; + Log(LogLevel::Error, "PCap: failed to set nonblocking mode with %d: %s\n", err, errbuf); + close(adapter); + return nullptr; + } + + std::unique_ptr pcap = std::make_unique(); + pcap->PCapAdapter = adapter; + pcap->Callback = handler; + pcap->PCapLib = PCapLib; + pcap->close = close; + pcap->sendpacket = sendpacket; + pcap->dispatch = dispatch; + + return pcap; +} + +Net_PCap::Net_PCap(Net_PCap&& other) noexcept +{ + PCapAdapter = other.PCapAdapter; + PCapLib = std::move(other.PCapLib); + close = other.close; + sendpacket = other.sendpacket; + dispatch = other.dispatch; + Callback = std::move(other.Callback); + + other.PCapAdapter = nullptr; + other.close = nullptr; + other.PCapLib = nullptr; + other.sendpacket = nullptr; + other.dispatch = nullptr; + other.Callback = nullptr; +} + +Net_PCap& Net_PCap::operator=(Net_PCap&& other) noexcept +{ + if (this != &other) + { + if (close && PCapAdapter) + { + close(PCapAdapter); + PCapAdapter = nullptr; + } + + PCapAdapter = other.PCapAdapter; + PCapLib = std::move(other.PCapLib); + close = other.close; + sendpacket = other.sendpacket; + dispatch = other.dispatch; + Callback = std::move(other.Callback); + + other.PCapAdapter = nullptr; + other.close = nullptr; + other.PCapLib = nullptr; + other.sendpacket = nullptr; + other.dispatch = nullptr; + other.Callback = nullptr; + } + + return *this; +} + +Net_PCap::~Net_PCap() noexcept +{ + if (close && PCapAdapter) + { + close(PCapAdapter); + PCapAdapter = nullptr; + } + // PCapLib will be freed at this point (shared_ptr + custom deleter) +} + +void Net_PCap::RXCallback(u_char* userdata, const struct pcap_pkthdr* header, const u_char* data) noexcept +{ + Net_PCap& self = *reinterpret_cast(userdata); + if (self.Callback) + self.Callback(data, header->len); +} + +int Net_PCap::SendPacket(u8* data, int len) noexcept +{ + if (PCapAdapter == nullptr || data == nullptr) + return 0; + + if (len > 2048) + { + Log(LogLevel::Error, "Net_SendPacket: error: packet too long (%d)\n", len); + return 0; + } + + sendpacket(PCapAdapter, data, len); + // TODO: check success + return len; +} + +void Net_PCap::RecvCheck() noexcept +{ + if (PCapAdapter == nullptr || dispatch == nullptr) + return; + + dispatch(PCapAdapter, 1, RXCallback, reinterpret_cast(this)); +} + +} diff --git a/src/net/Net_PCap.h b/src/net/Net_PCap.h new file mode 100644 index 00000000..cd2ab90b --- /dev/null +++ b/src/net/Net_PCap.h @@ -0,0 +1,136 @@ +/* + Copyright 2016-2024 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 NET_PCAP_H +#define NET_PCAP_H + +#include +#include +#include +#include +#include +#include + +#include "types.h" +#include "Platform.h" +#include "NetDriver.h" + + +namespace melonDS +{ +struct AdapterData +{ + char DeviceName[128]; + char FriendlyName[128]; + char Description[128]; + + u8 MAC[6]; + u8 IP_v4[4]; + + /// The flags on the pcap_if_t that was used to populate this struct + u32 Flags; +}; + +typedef int (*pcap_findalldevs_t)(pcap_if_t** alldevs, char* errbuf); +typedef void (*pcap_freealldevs_t)(pcap_if_t* alldevs); +typedef pcap_t* (*pcap_open_live_t)(const char* src, int snaplen, int flags, int readtimeout, char* errbuf); +typedef void (*pcap_close_t)(pcap_t* dev); +typedef int (*pcap_setnonblock_t)(pcap_t* dev, int nonblock, char* errbuf); +typedef int (*pcap_sendpacket_t)(pcap_t* dev, const u_char* data, int len); +typedef int (*pcap_dispatch_t)(pcap_t* dev, int num, pcap_handler callback, u_char* data); +typedef const u_char* (*pcap_next_t)(pcap_t* dev, struct pcap_pkthdr* hdr); + +class Net_PCap; + +class LibPCap +{ +public: + static std::optional New() noexcept; + LibPCap(const LibPCap&) = delete; + LibPCap& operator=(const LibPCap&) = delete; + LibPCap(LibPCap&&) noexcept; + LibPCap& operator=(LibPCap&&) noexcept; + ~LibPCap() noexcept = default; + + [[nodiscard]] std::unique_ptr Open(std::string_view devicename, const Platform::SendPacketCallback& handler) const noexcept; + [[nodiscard]] std::unique_ptr Open(const AdapterData& device, const Platform::SendPacketCallback& handler) const noexcept; + + // so that Net_PCap objects can safely outlive LibPCap + // (because the actual DLL will be kept loaded until no shared_ptrs remain) + std::shared_ptr PCapLib = nullptr; + pcap_findalldevs_t findalldevs = nullptr; + pcap_freealldevs_t freealldevs = nullptr; + pcap_open_live_t open_live = nullptr; + pcap_close_t close = nullptr; + pcap_setnonblock_t setnonblock = nullptr; + pcap_sendpacket_t sendpacket = nullptr; + pcap_dispatch_t dispatch = nullptr; + pcap_next_t next = nullptr; + + [[nodiscard]] bool IsValid() const noexcept + { + return + PCapLib != nullptr && + findalldevs != nullptr && + freealldevs != nullptr && + open_live != nullptr && + close != nullptr && + setnonblock != nullptr && + sendpacket != nullptr && + dispatch != nullptr && + next != nullptr + ; + } + + /// @return List of all network interfaces available at the time of the call + [[nodiscard]] std::vector GetAdapters() const noexcept; +private: + static bool TryLoadPCap(LibPCap& pcap, Platform::DynamicLibrary *lib) noexcept; + LibPCap() noexcept = default; +}; + +class Net_PCap : public NetDriver +{ +public: + Net_PCap() noexcept = default; + ~Net_PCap() noexcept override; + Net_PCap(const Net_PCap&) = delete; + Net_PCap& operator=(const Net_PCap&) = delete; + Net_PCap(Net_PCap&& other) noexcept; + Net_PCap& operator=(Net_PCap&& other) noexcept; + + int SendPacket(u8* data, int len) noexcept override; + void RecvCheck() noexcept override; +private: + friend class LibPCap; + static void RXCallback(u_char* userdata, const pcap_pkthdr* header, const u_char* data) noexcept; + + pcap_t* PCapAdapter = nullptr; + Platform::SendPacketCallback Callback; + + // To avoid undefined behavior in case the original LibPCap object is destroyed + // before this interface is cleaned up + std::shared_ptr PCapLib = nullptr; + pcap_close_t close = nullptr; + pcap_sendpacket_t sendpacket = nullptr; + pcap_dispatch_t dispatch = nullptr; +}; + +} + +#endif // NET_PCAP_H diff --git a/src/frontend/qt_sdl/LAN_Socket.cpp b/src/net/Net_Slirp.cpp similarity index 79% rename from src/frontend/qt_sdl/LAN_Socket.cpp rename to src/net/Net_Slirp.cpp index e938af80..0386c586 100644 --- a/src/frontend/qt_sdl/LAN_Socket.cpp +++ b/src/net/Net_Slirp.cpp @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -16,17 +16,14 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -// indirect LAN interface, powered by BSD sockets. - #include -#include #include -#include "Wifi.h" -#include "LAN_Socket.h" +#include "Net.h" +#include "Net_Slirp.h" #include "FIFO.h" #include "Platform.h" -#include +#include #ifdef __WIN32__ #include @@ -37,9 +34,7 @@ #include #endif -using namespace melonDS; - -namespace LAN_Socket +namespace melonDS { using Platform::Log; @@ -52,17 +47,6 @@ const u32 kClientIP = kSubnet | 0x10; const u8 kServerMAC[6] = {0x00, 0xAB, 0x33, 0x28, 0x99, 0x44}; -FIFO> 2)> RXBuffer; - -u32 IPv4ID; - -Slirp* Ctx = nullptr; - -/*const int FDListMax = 64; -struct pollfd FDList[FDListMax]; -int FDListSize;*/ - - #ifdef __WIN32__ #define poll WSAPoll @@ -85,24 +69,7 @@ int clock_gettime(int, struct timespec *spec) #endif // __WIN32__ -void RXEnqueue(const void* buf, int len) -{ - int alignedlen = (len + 3) & ~3; - int totallen = alignedlen + 4; - - if (!RXBuffer.CanFit(totallen >> 2)) - { - Log(LogLevel::Warn, "slirp: !! NOT ENOUGH SPACE IN RX BUFFER\n"); - return; - } - - u32 header = (alignedlen & 0xFFFF) | (len << 16); - RXBuffer.Write(header); - for (int i = 0; i < alignedlen; i += 4) - RXBuffer.Write(((u32*)buf)[i>>2]); -} - -ssize_t SlirpCbSendPacket(const void* buf, size_t len, void* opaque) +ssize_t Net_Slirp::SlirpCbSendPacket(const void* buf, size_t len, void* opaque) noexcept { if (len > 2048) { @@ -112,7 +79,11 @@ ssize_t SlirpCbSendPacket(const void* buf, size_t len, void* opaque) Log(LogLevel::Debug, "slirp: response packet of %zu bytes, type %04X\n", len, ntohs(((u16*)buf)[6])); - RXEnqueue(buf, len); + Net_Slirp& self = *static_cast(opaque); + if (self.Callback) + { + self.Callback((const u8*)buf, len); + } return len; } @@ -145,40 +116,11 @@ void SlirpCbTimerMod(void* timer, int64_t expire_time, void* opaque) void SlirpCbRegisterPollFD(int fd, void* opaque) { Log(LogLevel::Debug, "Slirp: register poll FD %d\n", fd); - - /*if (FDListSize >= FDListMax) - { - printf("!! SLIRP FD LIST FULL\n"); - return; - } - - for (int i = 0; i < FDListSize; i++) - { - if (FDList[i].fd == fd) return; - } - - FDList[FDListSize].fd = fd; - FDListSize++;*/ } void SlirpCbUnregisterPollFD(int fd, void* opaque) { Log(LogLevel::Debug, "Slirp: unregister poll FD %d\n", fd); - - /*if (FDListSize < 1) - { - printf("!! SLIRP FD LIST EMPTY\n"); - return; - } - - for (int i = 0; i < FDListSize; i++) - { - if (FDList[i].fd == fd) - { - FDListSize--; - FDList[i] = FDList[FDListSize]; - } - }*/ } void SlirpCbNotify(void* opaque) @@ -186,7 +128,7 @@ void SlirpCbNotify(void* opaque) Log(LogLevel::Debug, "Slirp: notify???\n"); } -SlirpCb cb = +const SlirpCb Net_Slirp::cb = { .send_packet = SlirpCbSendPacket, .guest_error = SlirpCbGuestError, @@ -199,14 +141,9 @@ SlirpCb cb = .notify = SlirpCbNotify }; -bool Init() +Net_Slirp::Net_Slirp(const Platform::SendPacketCallback& callback) noexcept : Callback(callback) { - IPv4ID = 0; - - //FDListSize = 0; - //memset(FDList, 0, sizeof(FDList)); - - SlirpConfig cfg; + SlirpConfig cfg {}; memset(&cfg, 0, sizeof(cfg)); cfg.version = 1; @@ -218,12 +155,10 @@ bool Init() *(u32*)&cfg.vdhcp_start = htonl(kClientIP); *(u32*)&cfg.vnameserver = htonl(kDNSIP); - Ctx = slirp_new(&cfg, &cb, nullptr); - - return true; + Ctx = slirp_new(&cfg, &cb, this); } -void DeInit() +Net_Slirp::~Net_Slirp() noexcept { if (Ctx) { @@ -232,7 +167,6 @@ void DeInit() } } - void FinishUDPFrame(u8* data, int len) { u8* ipheader = &data[0xE]; @@ -272,7 +206,7 @@ void FinishUDPFrame(u8* data, int len) *(u16*)&udpheader[6] = htons(tmp); } -void HandleDNSFrame(u8* data, int len) +void Net_Slirp::HandleDNSFrame(u8* data, int len) noexcept { u8* ipheader = &data[0xE]; u8* udpheader = &data[0x22]; @@ -425,16 +359,17 @@ void HandleDNSFrame(u8* data, int len) if (framelen & 1) { *out++ = 0; framelen++; } FinishUDPFrame(resp, framelen); - RXEnqueue(resp, framelen); + if (Callback) + Callback(resp, framelen); } -int SendPacket(u8* data, int len) +int Net_Slirp::SendPacket(u8* data, int len) noexcept { if (!Ctx) return 0; if (len > 2048) { - Log(LogLevel::Error, "LAN_SendPacket: error: packet too long (%d)\n", len); + Log(LogLevel::Error, "Net_SendPacket: error: packet too long (%d)\n", len); return 0; } @@ -458,21 +393,17 @@ int SendPacket(u8* data, int len) return len; } -const int PollListMax = 64; -struct pollfd PollList[PollListMax]; -int PollListSize; - -int SlirpCbAddPoll(int fd, int events, void* opaque) +int Net_Slirp::SlirpCbAddPoll(int fd, int events, void* opaque) noexcept { - if (PollListSize >= PollListMax) + Net_Slirp& self = *static_cast(opaque); + + if (self.PollListSize >= PollListMax) { Log(LogLevel::Error, "slirp: POLL LIST FULL\n"); return -1; } - int idx = PollListSize++; - - //printf("Slirp: add poll: fd=%d, idx=%d, events=%08X\n", fd, idx, events); + int idx = self.PollListSize++; u16 evt = 0; @@ -486,20 +417,20 @@ int SlirpCbAddPoll(int fd, int events, void* opaque) if (events & SLIRP_POLL_HUP) evt |= POLLHUP; #endif // !__WIN32__ - PollList[idx].fd = fd; - PollList[idx].events = evt; + self.PollList[idx].fd = fd; + self.PollList[idx].events = evt; return idx; } -int SlirpCbGetREvents(int idx, void* opaque) +int Net_Slirp::SlirpCbGetREvents(int idx, void* opaque) noexcept { - if (idx < 0 || idx >= PollListSize) + Net_Slirp& self = *static_cast(opaque); + + if (idx < 0 || idx >= self.PollListSize) return 0; - //printf("Slirp: get revents, idx=%d, res=%04X\n", idx, FDList[idx].revents); - - u16 evt = PollList[idx].revents; + u16 evt = self.PollList[idx].revents; int ret = 0; if (evt & POLLIN) ret |= SLIRP_POLL_IN; @@ -511,33 +442,18 @@ int SlirpCbGetREvents(int idx, void* opaque) return ret; } -int RecvPacket(u8* data) +void Net_Slirp::RecvCheck() noexcept { - if (!Ctx) return 0; - - int ret = 0; + if (!Ctx) return; //if (PollListSize > 0) { u32 timeout = 0; PollListSize = 0; - slirp_pollfds_fill(Ctx, &timeout, SlirpCbAddPoll, nullptr); + slirp_pollfds_fill(Ctx, &timeout, SlirpCbAddPoll, this); int res = poll(PollList, PollListSize, timeout); - slirp_pollfds_poll(Ctx, res<0, SlirpCbGetREvents, nullptr); + slirp_pollfds_poll(Ctx, res<0, SlirpCbGetREvents, this); } - - if (!RXBuffer.IsEmpty()) - { - u32 header = RXBuffer.Read(); - u32 len = header & 0xFFFF; - - for (int i = 0; i < len; i += 4) - ((u32*)data)[i>>2] = RXBuffer.Read(); - - ret = header >> 16; - } - - return ret; } } diff --git a/src/net/Net_Slirp.h b/src/net/Net_Slirp.h new file mode 100644 index 00000000..5f9b6587 --- /dev/null +++ b/src/net/Net_Slirp.h @@ -0,0 +1,67 @@ +/* + Copyright 2016-2024 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 NET_SLIRP_H +#define NET_SLIRP_H + +#include "types.h" +#include "FIFO.h" +#include "Platform.h" +#include "NetDriver.h" + +#include + +#ifdef __WIN32__ + #include +#else + #include +#endif + +struct Slirp; + +namespace melonDS +{ +class Net_Slirp : public NetDriver +{ +public: + explicit Net_Slirp(const Platform::SendPacketCallback& callback) noexcept; + Net_Slirp(const Net_Slirp&) = delete; + Net_Slirp& operator=(const Net_Slirp&) = delete; + Net_Slirp(Net_Slirp&& other) noexcept; + Net_Slirp& operator=(Net_Slirp&& other) noexcept; + ~Net_Slirp() noexcept override; + + int SendPacket(u8* data, int len) noexcept override; + void RecvCheck() noexcept override; +private: + static constexpr int PollListMax = 64; + static const SlirpCb cb; + static int SlirpCbGetREvents(int idx, void* opaque) noexcept; + static int SlirpCbAddPoll(int fd, int events, void* opaque) noexcept; + static ssize_t SlirpCbSendPacket(const void* buf, size_t len, void* opaque) noexcept; + void HandleDNSFrame(u8* data, int len) noexcept; + + Platform::SendPacketCallback Callback; + pollfd PollList[PollListMax] {}; + int PollListSize = 0; + FIFO> 2)> RXBuffer {}; + u32 IPv4ID = 0; + Slirp* Ctx = nullptr; +}; +} +#endif // NET_SLIRP_H diff --git a/src/net/Netplay.cpp b/src/net/Netplay.cpp new file mode 100644 index 00000000..1dc04282 --- /dev/null +++ b/src/net/Netplay.cpp @@ -0,0 +1,1084 @@ +/* + Copyright 2016-2024 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 +#include + +#include + +#include "NDS.h" +#include "NDSCart.h" +//#include "IPC.h" +#include "Netplay.h" +//#include "Input.h" +//#include "ROMManager.h" +//#include "Config.h" +#include "Savestate.h" +#include "Platform.h" + +using namespace melonDS; + +namespace Netplay +{ + +bool Active; +bool IsHost; +bool IsMirror; + +ENetHost* Host; +ENetHost* MirrorHost; + +Player Players[16]; +int NumPlayers; + +Player MyPlayer; +u32 HostAddress; +bool Lag; + +int NumMirrorClients; + +struct InputFrame +{ + u32 FrameNum; + u32 KeyMask; + u32 Touching; + u32 TouchX, TouchY; +}; + +std::queue InputQueue; + +enum +{ + Blob_CartROM = 0, + Blob_CartSRAM, + Blob_InitState, + + Blob_MAX +}; + +const u32 kChunkSize = 0x10000; +u8 ChunkBuffer[0x10 + kChunkSize]; +u8* Blobs[Blob_MAX]; +u32 BlobLens[Blob_MAX]; +int CurBlobType; +u32 CurBlobLen; + + +bool Init() +{ + Active = false; + IsHost = false; + IsMirror = false; + Host = nullptr; + MirrorHost = nullptr; + Lag = false; + + memset(Players, 0, sizeof(Players)); + NumPlayers = 0; + + NumMirrorClients = 0; + + for (int i = 0; i < Blob_MAX; i++) + { + Blobs[i] = nullptr; + BlobLens[i] = 0; + } + CurBlobType = -1; + CurBlobLen = 0; + + /*if (enet_initialize() != 0) + { + printf("enet shat itself :(\n"); + return false; + } + + printf("enet init OK\n");*/ + return true; +} + +void DeInit() +{ + // TODO: cleanup resources properly!! + + //enet_deinitialize(); +} + + +void StartHost(const char* playername, int port) +{ + ENetAddress addr; + addr.host = ENET_HOST_ANY; + addr.port = port; + + Host = enet_host_create(&addr, 16, 1, 0, 0); + if (!Host) + { + printf("host shat itself :(\n"); + return; + } + + Player* player = &Players[0]; + memset(player, 0, sizeof(Player)); + player->ID = 0; + strncpy(player->Name, playername, 31); + player->Status = 2; + player->Address = 0x0100007F; + NumPlayers = 1; + memcpy(&MyPlayer, player, sizeof(Player)); + + HostAddress = 0x0100007F; + + NumMirrorClients = 0; + + ENetAddress mirroraddr; + mirroraddr.host = ENET_HOST_ANY; + mirroraddr.port = port + 1; +printf("host mirror host connecting to %08X:%d\n", mirroraddr.host, mirroraddr.port); + MirrorHost = enet_host_create(&mirroraddr, 16, 2, 0, 0); + if (!MirrorHost) + { + printf("mirror host shat itself :(\n"); + return; + } + + Active = true; + IsHost = true; + IsMirror = false; + + //netplayDlg->updatePlayerList(Players, NumPlayers); +} + +void StartClient(const char* playername, const char* host, int port) +{ + Host = enet_host_create(nullptr, 1, 1, 0, 0); + if (!Host) + { + printf("client shat itself :(\n"); + return; + } + + printf("client created, connecting (%s, %s:%d)\n", playername, host, port); + + ENetAddress addr; + enet_address_set_host(&addr, host); + addr.port = port; + ENetPeer* peer = enet_host_connect(Host, &addr, 1, 0); + if (!peer) + { + printf("connect shat itself :(\n"); + return; + } + + ENetEvent event; + bool conn = false; + if (enet_host_service(Host, &event, 5000) > 0) + { + if (event.type == ENET_EVENT_TYPE_CONNECT) + { + printf("connected!\n"); + conn = true; + } + } + + if (!conn) + { + printf("connection failed\n"); + enet_peer_reset(peer); + return; + } + + Player* player = &MyPlayer; + memset(player, 0, sizeof(Player)); + player->ID = 0; + strncpy(player->Name, playername, 31); + player->Status = 3; + + HostAddress = addr.host; + + Active = true; + IsHost = false; + IsMirror = false; +} + +void StartMirror(const Player* player) +{ + for (int i = 0; i < Blob_MAX; i++) + { + Blobs[i] = nullptr; + BlobLens[i] = 0; + } + CurBlobType = -1; + CurBlobLen = 0; + + MirrorHost = enet_host_create(nullptr, 1, 2, 0, 0); + if (!MirrorHost) + { + printf("mirror shat itself :(\n"); + return; + } + + printf("mirror created, connecting\n"); + + ENetAddress addr; + addr.host = player->Address; + addr.port = 8064+1 + player->ID; // FIXME!!!!!!!!!! + printf("mirror client connecting to %08X:%d\n", addr.host, addr.port); + ENetPeer* peer = enet_host_connect(MirrorHost, &addr, 2, 0); + if (!peer) + { + printf("connect shat itself :(\n"); + return; + } + + ENetEvent event; + bool conn = false; + if (enet_host_service(MirrorHost, &event, 5000) > 0) + { + if (event.type == ENET_EVENT_TYPE_CONNECT) + { + printf("connected!\n"); + conn = true; + } + } + + if (!conn) + { + printf("connection failed\n"); + enet_peer_reset(peer); + return; + } + + memcpy(&MyPlayer, player, sizeof(Player)); + + HostAddress = addr.host; + + Active = true; + IsHost = false; + IsMirror = true; +} + + +u32 PlayerAddress(int id) +{ + if (id < 0 || id > 16) return 0; + + u32 ret = Players[id].Address; + if (ret == 0x0100007F) ret = HostAddress; + return ret; +} + + +bool SpawnMirrorInstance(Player player) +{ +#if 0 + u16 curmask = IPC::GetInstanceBitmask(); + + 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 + + if (!newinst.startDetached()) + return false; + + // try to determine the ID of the new instance + + int newid = -1; + for (int tries = 0; tries < 10; tries++) + { + QThread::usleep(100 * 1000); + + u16 newmask = IPC::GetInstanceBitmask(); + if (newmask == curmask) continue; + + newmask &= ~curmask; + for (int id = 0; id < 16; id++) + { + if (newmask & (1 << id)) + { + newid = id; + break; + } + } + } + + if (newid == -1) return false; + + // setup that instance + printf("netplay: spawned mirror instance for player %d with ID %d, configuring\n", player.ID, newid); + + //std::string rompath = ROMManager::FullROMPath.join('|').toStdString(); + //IPC::SendCommandStr(1< 0) + { + buf[0] = 0x02; + *(u32*)&buf[12] = 0; + + for (u32 pos = 0; pos < len; pos += kChunkSize) + { + u32 chunklen = kChunkSize; + if ((pos + chunklen) > len) + chunklen = len - pos; + + *(u32*)&buf[8] = pos; + memcpy(&buf[16], &data[pos], chunklen); + + ENetPacket* pkt = enet_packet_create(buf, 16+chunklen, ENET_PACKET_FLAG_RELIABLE); + enet_host_broadcast(MirrorHost, 1, pkt); + //enet_host_flush(MirrorHost); + } + } + + buf[0] = 0x03; + + pkt = enet_packet_create(buf, 8, ENET_PACKET_FLAG_RELIABLE); + enet_host_broadcast(MirrorHost, 1, pkt); + + return true; +} + +void RecvBlobFromMirrorHost(ENetPeer* peer, ENetPacket* pkt) +{ + u8* buf = pkt->data; + if (buf[0] == 0x01) + { + if (CurBlobType != -1) return; + if (pkt->dataLength != 8) return; + + int type = buf[1]; + if (type > Blob_MAX) return; + + u32 len = *(u32*)&buf[4]; + if (len > 0x40000000) return; + + if (Blobs[type] != nullptr) return; + if (BlobLens[type] != 0) return; +printf("[MC] start blob type=%d len=%d\n", type, len); + if (len) Blobs[type] = new u8[len]; + BlobLens[type] = len; + + CurBlobType = type; + CurBlobLen = len; + + ENetEvent evt; + while (enet_host_service(MirrorHost, &evt, 5000) > 0) + { + if (evt.type == ENET_EVENT_TYPE_RECEIVE && evt.channelID == 1) + { + RecvBlobFromMirrorHost(evt.peer, evt.packet); + if (evt.packet->dataLength >= 1 && evt.packet->data[0] == 0x03) + { + printf("[MC] blob done while in fast recv loop\n"); + break; + } + } + else + { + printf("[MC] fast recv loop aborted because evt %d ch %d\n", evt.type, evt.channelID); + break; + } + } + } + else if (buf[0] == 0x02) + { + if (CurBlobType < 0 || CurBlobType > Blob_MAX) return; + if (pkt->dataLength > (16+kChunkSize)) return; + + int type = buf[1]; + if (type != CurBlobType) return; + + u32 len = *(u32*)&buf[4]; + if (len != CurBlobLen) return; + + u32 pos = *(u32*)&buf[8]; + if (pos >= len) return; + if ((pos + (pkt->dataLength-16)) > len) return; + + u8* dst = Blobs[type]; + if (!dst) return; + if (BlobLens[type] != len) return; + printf("[MC] recv blob data, type=%d pos=%08X len=%08X data=%08lX\n", type, pos, len, pkt->dataLength-16); + memcpy(&dst[pos], &buf[16], pkt->dataLength-16); + } + else if (buf[0] == 0x03) + { + if (CurBlobType < 0 || CurBlobType > Blob_MAX) return; + if (pkt->dataLength != 8) return; + + int type = buf[1]; + if (type != CurBlobType) return; + + u32 len = *(u32*)&buf[4]; + if (len != CurBlobLen) return; +printf("[MC] finish blob type=%d len=%d\n", type, len); + CurBlobType = -1; + CurBlobLen = 0; + } + else if (buf[0] == 0x04) + { + if (pkt->dataLength != 2) return; + + bool res = false; +#if 0 + // reset + NDS::SetConsoleType(buf[1]); + NDS::EjectCart(); + NDS::Reset(); + //SetBatteryLevels(); + + if (Blobs[Blob_CartROM]) + { + res = NDS::LoadCart(Blobs[Blob_CartROM], BlobLens[Blob_CartROM], + Blobs[Blob_CartSRAM], BlobLens[Blob_CartSRAM]); + if (!res) + { + printf("!!!! FAIL!!\n"); + return; + } + } + + if (res) + { + ROMManager::CartType = 0; + //ROMManager::NDSSave = new SaveManager(savname); + + //LoadCheats(); + } +#endif + // load initial state + // TODO: terrible hack!! + #if 0 + FILE* f = Platform::OpenFile("netplay2.mln", "wb"); + fwrite(Blobs[Blob_InitState], BlobLens[Blob_InitState], 1, f); + fclose(f); + Savestate* state = new Savestate("netplay2.mln", false); + NDS::DoSavestate(state); + delete state; + + for (int i = 0; i < Blob_MAX; i++) + { + if (Blobs[i]) delete[] Blobs[i]; + Blobs[i] = nullptr; + BlobLens[i] = 0; + } + + /*Savestate* zorp = new Savestate("netplay3.mln", true); + NDS::DoSavestate(zorp); + delete zorp;*/ + +printf("[MC] state loaded, PC=%08X/%08X\n", NDS::GetPC(0), NDS::GetPC(1)); + ENetPacket* resp = enet_packet_create(buf, 1, ENET_PACKET_FLAG_RELIABLE); + enet_peer_send(peer, 1, resp); + #endif + } + else if (buf[0] == 0x05) + { + printf("[MIRROR CLIENT] start\n"); + StartLocal(); + } +} + +void SyncMirrorClients() +{ + printf("[MIRROR HOST] syncing clients\n"); + +#if 0 + SendBlobToMirrorClients(Blob_CartSRAM, NDSCart::GetSaveMemoryLength(), NDSCart::GetSaveMemory()); + + // send initial state + // TODO: this is a terrible hack! + /*printf("[MH] state start\n"); + Savestate* state = new Savestate("netplay.mln", true); + NDS::DoSavestate(state); + delete state; + printf("[MH] state taken: PC=%08X/%08X\n", NDS::GetPC(0), NDS::GetPC(1)); + FILE* f = Platform::OpenLocalFile("netplay.mln", "rb"); + printf("[MH] state=%d\n", f?1:0); + fseek(f, 0, SEEK_END); + u32 flen = ftell(f); + fseek(f, 0, SEEK_SET); + u8* statebuf = new u8[flen]; + fread(statebuf, flen, 1, f); + fclose(f); + printf("[MH] state read, len=%d\n", flen); + SendBlobToMirrorClients(Blob_InitState, flen, statebuf); + printf("[MH] state sent\n"); + delete[] statebuf;*/ + + u8 data[2]; + data[0] = 0x04; + data[1] = (u8)Config::ConsoleType; + ENetPacket* pkt = enet_packet_create(&data, 2, ENET_PACKET_FLAG_RELIABLE); + enet_host_broadcast(MirrorHost, 1, pkt); + //enet_host_flush(MirrorHost); + + // wait for all clients to have caught up + int ngood = 0; + ENetEvent evt; + while (enet_host_service(MirrorHost, &evt, 300000) > 0) + {printf("EVENT %d CH %d\n", evt.type, evt.channelID); + if (evt.type == ENET_EVENT_TYPE_RECEIVE && evt.channelID == 1) + { + if (evt.packet->dataLength == 1 && evt.packet->data[0] == 0x04) + ngood++; + } + else + break; + + if (ngood >= (NumPlayers-1)) + break; + } + + if (ngood != (NumPlayers-1)) + printf("!!! BAD!! %d %d\n", ngood, NumPlayers); + + printf("[MIRROR HOST] clients synced\n"); + + // start + + data[0] = 0x05; + pkt = enet_packet_create(&data, 1, ENET_PACKET_FLAG_RELIABLE); + enet_host_broadcast(MirrorHost, 1, pkt); + //enet_host_flush(MirrorHost); + + StartLocal(); +#endif +} + +void StartGame() +{ + if (!IsHost) + { + printf("?????\n"); + return; + } + + // spawn mirror instances as needed + for (int i = 1; i < NumPlayers; i++) + { + SpawnMirrorInstance(Players[i]); + } + + //SyncMirrorClients(); + + // tell remote peers to start game + u8 cmd[1] = {0x04}; + ENetPacket* pkt = enet_packet_create(cmd, sizeof(cmd), ENET_PACKET_FLAG_RELIABLE); + enet_host_broadcast(Host, 0, pkt); + + // tell other mirror instances to start the game + //IPC::SendCommand(0xFFFF, IPC::Cmd_Start, 0, nullptr); + + // TO START MIRROR CLIENT SHITO + // + // 1. NDS::Reset() + // 2. load ROM + // 3. load state + + // start game locally + //StartLocal(); +} + +void StartLocal() +{ + for (int i = 0; i < 4; i++) + { + InputFrame frame; + frame.FrameNum = i; + frame.KeyMask = 0xFFF; + frame.Touching = 0; + frame.TouchX = 0; + frame.TouchY = 0; + InputQueue.push(frame); + } + + //NDS::Start(); + //emuThread->emuRun(); +} + + +void ProcessHost() +{ + if (!Host) return; + + ENetEvent event; + while (enet_host_service(Host, &event, 0) > 0) + { + switch (event.type) + { + case ENET_EVENT_TYPE_CONNECT: + { + // client connected; assign player number + + int id; + for (id = 0; id < 16; id++) + { + if (id >= NumPlayers) break; + if (Players[id].Status == 0) break; + } + + if (id < 16) + { + u8 cmd[2]; + cmd[0] = 0x01; + cmd[1] = (u8)id; + ENetPacket* pkt = enet_packet_create(cmd, 2, ENET_PACKET_FLAG_RELIABLE); + enet_peer_send(event.peer, 0, pkt); + + Players[id].ID = id; + Players[id].Status = 3; + Players[id].Address = event.peer->address.host; + event.peer->data = &Players[id]; + NumPlayers++; + } + } + break; + + case ENET_EVENT_TYPE_DISCONNECT: + { + // TODO + printf("disco\n"); + } + break; + + case ENET_EVENT_TYPE_RECEIVE: + { + if (event.packet->dataLength < 1) break; + + u8* data = (u8*)event.packet->data; + switch (data[0]) + { + case 0x02: // client sending player info + { + if (event.packet->dataLength != (1+sizeof(Player))) break; + + Player player; + memcpy(&player, &data[1], sizeof(Player)); + player.Name[31] = '\0'; + + Player* hostside = (Player*)event.peer->data; + if (player.ID != hostside->ID) + { + printf("what??? %d =/= %d\n", player.ID, hostside->ID); + // TODO: disconnect + break; + } + + player.Status = 1; + player.Address = event.peer->address.host; + memcpy(hostside, &player, sizeof(Player)); + + // broadcast updated player list + u8 cmd[2+sizeof(Players)]; + cmd[0] = 0x03; + cmd[1] = (u8)NumPlayers; + memcpy(&cmd[2], Players, sizeof(Players)); + ENetPacket* pkt = enet_packet_create(cmd, 2+sizeof(Players), ENET_PACKET_FLAG_RELIABLE); + enet_host_broadcast(Host, 0, pkt); + + //netplayDlg->updatePlayerList(Players, NumPlayers); + } + break; + } + } + break; + } + } +} + +void ProcessClient() +{ + if (!Host) return; + + ENetEvent event; + while (enet_host_service(Host, &event, 0) > 0) + { + switch (event.type) + { + case ENET_EVENT_TYPE_CONNECT: + printf("schmo\n"); + break; + + case ENET_EVENT_TYPE_DISCONNECT: + { + // TODO + printf("shma\n"); + } + break; + + case ENET_EVENT_TYPE_RECEIVE: + { + if (event.packet->dataLength < 1) break; + + u8* data = (u8*)event.packet->data; + switch (data[0]) + { + case 0x01: // host sending player ID + { + if (event.packet->dataLength != 2) break; + + NumMirrorClients = 0; + + // create mirror host + ENetAddress mirroraddr; + mirroraddr.host = ENET_HOST_ANY; + mirroraddr.port = 8064+1 + data[1]; // FIXME!!!! +printf("client mirror host connecting to %08X:%d\n", mirroraddr.host, mirroraddr.port); + MirrorHost = enet_host_create(&mirroraddr, 16, 2, 0, 0); + if (!MirrorHost) + { + printf("mirror host shat itself :(\n"); + break; + } + + // send player information + MyPlayer.ID = data[1]; + u8 cmd[1+sizeof(Player)]; + cmd[0] = 0x02; + memcpy(&cmd[1], &MyPlayer, sizeof(Player)); + ENetPacket* pkt = enet_packet_create(cmd, 1+sizeof(Player), ENET_PACKET_FLAG_RELIABLE); + enet_peer_send(event.peer, 0, pkt); + } + break; + + case 0x03: // host sending player list + { + if (event.packet->dataLength != (2+sizeof(Players))) break; + if (data[1] > 16) break; + + NumPlayers = data[1]; + memcpy(Players, &data[2], sizeof(Players)); + for (int i = 0; i < 16; i++) + { + Players[i].Name[31] = '\0'; + } + + //netplayDlg->updatePlayerList(Players, NumPlayers); + } + break; + + case 0x04: // start game + { + // spawn mirror instances as needed + for (int i = 0; i < NumPlayers; i++) + { + if (i != MyPlayer.ID) + SpawnMirrorInstance(Players[i]); + } + + //SyncMirrorClients(); +printf("bourf\n"); + // tell other mirror instances to start the game + //IPC::SendCommand(0xFFFF, IPC::Cmd_Start, 0, nullptr); +printf("birf\n"); + // start game locally + //StartLocal(); + } + break; + } + } + break; + } + } +} + +void ProcessMirrorHost() +{ + if (!MirrorHost) return; +#if 0 + bool block = false; + ENetEvent event; + while (enet_host_service(MirrorHost, &event, block ? 5000 : 0) > 0) + { + switch (event.type) + { + case ENET_EVENT_TYPE_CONNECT: + printf("[MIRROR HOST] mirror client connected\n"); + NumMirrorClients++; + event.peer->data = (void*)0; + + if (NumMirrorClients >= NumPlayers) + { + printf("??????\n"); + } + else if (NumMirrorClients == (NumPlayers-1)) + { + // all mirror clients are connected, we're ready to go + SyncMirrorClients(); + //StartLocal(); + } + break; + + case ENET_EVENT_TYPE_DISCONNECT: + { + // TODO + printf("[MIRROR HOST] mirror client disconnected\n"); + NumMirrorClients--; + } + break; + + case ENET_EVENT_TYPE_RECEIVE: + if (event.channelID == 0) + { + if (event.packet->dataLength != 4) break; + /*u8* data = (u8*)event.packet->data; + + if (data[0]) + { + event.peer->data = (void*)1; + block = true; + } + else + { + event.peer->data = (void*)0; + block = false; + + for (int i = 0; i < MirrorHost->peerCount; i++) + { + ENetPeer* peer = &(MirrorHost->peers[i]); + if (peer->state != ENET_PEER_STATE_CONNECTED) continue; + if (peer->data != (void*)0) + { + block = true; + break; + } + } + }*/ + s32 clientframes = *(s32*)event.packet->data; +//printf("[SYNC] HOST=%d CLIENT=%d\n", NDS::NumFrames, clientframes); + if (clientframes < (((s32)NDS::NumFrames) - 16)) + { + event.peer->data = (void*)1; + block = true; + } + else + { + event.peer->data = (void*)0; + block = false; + + for (int i = 0; i < MirrorHost->peerCount; i++) + { + ENetPeer* peer = &(MirrorHost->peers[i]); + if (peer->state != ENET_PEER_STATE_CONNECTED) continue; + if (peer->data != (void*)0) + { + block = true; + break; + } + } + } + } + break; + } + } +#endif +} + +void ProcessMirrorClient() +{ + if (!MirrorHost) return; +#if 0 + bool block = false; + if (emuThread->emuIsRunning())// && NDS::NumFrames > 4) + { + if (InputQueue.empty()) + block = true; + } + + ENetEvent event; + while (enet_host_service(MirrorHost, &event, block ? 5000 : 0) > 0) + { + switch (event.type) + { + case ENET_EVENT_TYPE_CONNECT: + printf("schmu\n"); + Lag = false; + break; + + case ENET_EVENT_TYPE_DISCONNECT: + { + // TODO + printf("shmz\n"); + } + break; + + case ENET_EVENT_TYPE_RECEIVE://printf("RX %d %d\n", event.channelID, event.packet->dataLength); + if (event.channelID == 0) + { + if (event.packet->dataLength != sizeof(InputFrame)) break; + + u8* data = (u8*)event.packet->data; + InputFrame frame; + memcpy(&frame, data, sizeof(InputFrame)); + InputQueue.push(frame); + + /*bool lag = (InputQueue.size() > 4*2); + if (lag != Lag) + { + // let the mirror host know they are running too fast for us +printf("mirror client lag notify: %d\n", lag); + u8 data = lag ? 1 : 0; + ENetPacket* pkt = enet_packet_create(&data, 1, ENET_PACKET_FLAG_RELIABLE); + enet_peer_send(event.peer, 0, pkt); + + Lag = lag; + }*/ + { + ENetPacket* pkt = enet_packet_create(&NDS::NumFrames, 4, ENET_PACKET_FLAG_RELIABLE); + enet_peer_send(event.peer, 0, pkt); + //enet_host_flush(MirrorHost); + } + } + else if (event.channelID == 1) + { + RecvBlobFromMirrorHost(event.peer, event.packet); + } + break; + } + + if (block) break; + } +#endif +} + +void ProcessFrame() +{ + if (IsMirror) + { + ProcessMirrorClient(); + } + else + { + if (IsHost) + { + ProcessHost(); + } + else + { + ProcessClient(); + } + + ProcessMirrorHost(); + } +} + +void ProcessInput() +{ + // netplay input processing + // + // N = current frame # + // L = amount of lag frames + // + // host side: + // we take the current input (normally meant for frame N) + // and delay it to frame N+L + // + // client side: + // we receive input from the host + // apply each input to the frame it's assigned to + // before running a frame, we need to wait to have received input for it + // TODO: alert host if we are running too far behind +#if 0 + if (!IsMirror) + { + u32 lag = 4; // TODO: make configurable!! + + InputFrame frame; + frame.FrameNum = NDS::NumFrames + lag; + frame.KeyMask = Input::InputMask; + frame.Touching = Input::Touching ? 1:0; + frame.TouchX = Input::TouchX; + frame.TouchY = Input::TouchY; + // TODO: other shit! (some hotkeys for example?) + + InputQueue.push(frame); + + u8 cmd[sizeof(InputFrame)]; + memcpy(cmd, &frame, sizeof(InputFrame)); + ENetPacket* pkt = enet_packet_create(cmd, sizeof(cmd), ENET_PACKET_FLAG_RELIABLE); + enet_host_broadcast(MirrorHost, 0, pkt); + //enet_host_flush(MirrorHost); + } + + if (InputQueue.empty()) + { + //if (NDS::NumFrames > 4) + printf("Netplay: BAD! INPUT QUEUE EMPTY\n"); + return; + } + + InputFrame& frame = InputQueue.front(); + + if (frame.FrameNum < NDS::NumFrames) + { + // TODO: this situation is a desync + printf("Netplay: BAD! LAGGING BEHIND\n"); + while (frame.FrameNum < NDS::NumFrames) + { + if (InputQueue.size() < 2) break; + InputQueue.pop(); + frame = InputQueue.front(); + } + } + + if (frame.FrameNum > NDS::NumFrames) + { + // frame in the future, ignore + return; + } + + // apply this input frame + if (frame.KeyMask != 0xFFF) printf("[%08d] INPUT=%08X (%08d) (backlog=%d)\n", NDS::NumFrames, frame.KeyMask, frame.FrameNum, InputQueue.size()); + NDS::SetKeyMask(frame.KeyMask); + if (frame.Touching) NDS::TouchScreen(frame.TouchX, frame.TouchY); + else NDS::ReleaseScreen(); + + InputQueue.pop(); +#endif +} + +} diff --git a/src/frontend/qt_sdl/LocalMP.h b/src/net/Netplay.h similarity index 56% rename from src/frontend/qt_sdl/LocalMP.h rename to src/net/Netplay.h index e7b4188a..1eff54b0 100644 --- a/src/frontend/qt_sdl/LocalMP.h +++ b/src/net/Netplay.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -16,31 +16,42 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#ifndef LOCALMP_H -#define LOCALMP_H +#ifndef NETPLAY_H +#define NETPLAY_H #include "types.h" -namespace LocalMP +namespace Netplay { -using namespace melonDS; +struct Player +{ + int ID; + char Name[32]; + int Status; // 0=no player 1=normal 2=host 3=connecting + melonDS::u32 Address; +}; + + +extern bool Active; + bool Init(); void DeInit(); -void SetRecvTimeout(int timeout); +void StartHost(const char* player, int port); +void StartClient(const char* player, const char* host, int port); +void StartMirror(const Player* player); -void Begin(); -void End(); +melonDS::u32 PlayerAddress(int id); -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); +void StartGame(); +void StartLocal(); + +void StartGame(); + +void ProcessFrame(); +void ProcessInput(); } -#endif // LOCALMP_H +#endif // NETPLAY_H diff --git a/src/net/PacketDispatcher.cpp b/src/net/PacketDispatcher.cpp new file mode 100644 index 00000000..c9e0d274 --- /dev/null +++ b/src/net/PacketDispatcher.cpp @@ -0,0 +1,160 @@ +/* + Copyright 2016-2024 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 "PacketDispatcher.h" + +using namespace melonDS; + +struct PacketHeader +{ + u32 magic; + u32 senderID; + u32 headerLength; + u32 dataLength; +}; + +const u32 kPacketMagic = 0x4B504C4D; + + +PacketDispatcher::PacketDispatcher() : mutex(Platform::Mutex_Create()) +{ + instanceMask = 0; +} + +PacketDispatcher::~PacketDispatcher() +{ + Platform::Mutex_Free(mutex); +} + + +void PacketDispatcher::registerInstance(int inst) +{ + Mutex_Lock(mutex); + + instanceMask |= (1 << inst); + packetQueues[inst] = std::make_unique(); + + Mutex_Unlock(mutex); +} + +void PacketDispatcher::unregisterInstance(int inst) +{ + Mutex_Lock(mutex); + + instanceMask &= ~(1 << inst); + packetQueues[inst] = nullptr; + + Mutex_Unlock(mutex); +} + + +void PacketDispatcher::clear() +{ + Mutex_Lock(mutex); + for (int i = 0; i < 16; i++) + { + if (!(instanceMask & (1 << i))) + continue; + + packetQueues[i]->Clear(); + } + Mutex_Unlock(mutex); +} + + +void PacketDispatcher::sendPacket(const void* header, int headerlen, const void* data, int datalen, int sender, u16 recv_mask) +{ + if (!header) headerlen = 0; + if (!data) datalen = 0; + if ((!headerlen) && (!datalen)) return; + if ((sizeof(PacketHeader) + headerlen + datalen) >= 0x8000) return; + if (sender < 0 || sender > 16) return; + + recv_mask &= instanceMask; + if (sender < 16) recv_mask &= ~(1 << sender); + if (!recv_mask) return; + + PacketHeader phdr; + phdr.magic = kPacketMagic; + phdr.senderID = sender; + phdr.headerLength = headerlen; + phdr.dataLength = datalen; + + int totallen = sizeof(phdr) + headerlen + datalen; + + Mutex_Lock(mutex); + for (int i = 0; i < 16; i++) + { + if (!(recv_mask & (1 << i))) + continue; + + PacketQueue* queue = packetQueues[i].get(); + + // if we run out of space: discard old packets + while (!queue->CanFit(totallen)) + { + PacketHeader tmp; + queue->Read(&tmp, sizeof(tmp)); + queue->Skip(tmp.headerLength + tmp.dataLength); + } + + queue->Write(&phdr, sizeof(phdr)); + if (headerlen) queue->Write(header, headerlen); + if (datalen) queue->Write(data, datalen); + } + Mutex_Unlock(mutex); +} + +bool PacketDispatcher::recvPacket(void *header, int *headerlen, void *data, int *datalen, int receiver) +{ + if ((!header) && (!data)) return false; + if (receiver < 0 || receiver > 15) return false; + + Mutex_Lock(mutex); + PacketQueue* queue = packetQueues[receiver].get(); + + PacketHeader phdr; + if (!queue->Read(&phdr, sizeof(phdr))) + { + Mutex_Unlock(mutex); + return false; + } + + if (phdr.magic != kPacketMagic) + { + Mutex_Unlock(mutex); + return false; + } + + if (phdr.headerLength) + { + if (headerlen) *headerlen = phdr.headerLength; + if (header) queue->Read(header, phdr.headerLength); + else queue->Skip(phdr.headerLength); + } + + if (phdr.dataLength) + { + if (datalen) *datalen = phdr.dataLength; + if (data) queue->Read(data, phdr.dataLength); + else queue->Skip(phdr.dataLength); + } + + Mutex_Unlock(mutex); + return true; +} diff --git a/src/net/PacketDispatcher.h b/src/net/PacketDispatcher.h new file mode 100644 index 00000000..d7f37c9a --- /dev/null +++ b/src/net/PacketDispatcher.h @@ -0,0 +1,50 @@ +/* + Copyright 2016-2024 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 PACKETDISPATCHER_H +#define PACKETDISPATCHER_H + +#include +#include +#include "Platform.h" +#include "types.h" +#include "FIFO.h" + +using PacketQueue = melonDS::RingBuffer<0x8000>; + +class PacketDispatcher +{ +public: + PacketDispatcher(); + ~PacketDispatcher(); + + void registerInstance(int inst); + void unregisterInstance(int inst); + + void clear(); + + void sendPacket(const void* header, int headerlen, const void* data, int datalen, int sender, melonDS::u16 recv_mask); + bool recvPacket(void* header, int* headerlen, void* data, int* datalen, int receiver); + +private: + melonDS::Platform::Mutex* mutex; + melonDS::u16 instanceMask; + std::array, 16> packetQueues {}; +}; + +#endif // PACKETDISPATCHER_H diff --git a/src/net/libslirp/.clang-format b/src/net/libslirp/.clang-format new file mode 100644 index 00000000..17fb49fe --- /dev/null +++ b/src/net/libslirp/.clang-format @@ -0,0 +1,58 @@ +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +--- +Language: Cpp +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false # although we like it, it creates churn +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: true +AlignOperands: true +AlignTrailingComments: false # churn +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None # AlwaysBreakAfterDefinitionReturnType is taken into account +AlwaysBreakBeforeMultilineStrings: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterStruct: false + AfterUnion: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: false +BreakStringLiterals: true +ColumnLimit: 80 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '.*_BEGIN$' # only PREC_BEGIN ? +MacroBlockEnd: '.*_END$' +MaxEmptyLinesToKeep: 2 +PointerAlignment: Right +ReflowComments: true +SortIncludes: false +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInContainerLiterals: true +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +UseTab: Never +... diff --git a/src/net/libslirp/.gitignore b/src/net/libslirp/.gitignore new file mode 100644 index 00000000..bd362c29 --- /dev/null +++ b/src/net/libslirp/.gitignore @@ -0,0 +1,11 @@ +*.[aod] +*.gcda +*.gcno +*.gcov +*.lib +*.obj +/build/ +/TAGS +/cscope* +/src/libslirp-version.h +/tags diff --git a/src/net/libslirp/.gitlab-ci.yml b/src/net/libslirp/.gitlab-ci.yml new file mode 100644 index 00000000..7c4584eb --- /dev/null +++ b/src/net/libslirp/.gitlab-ci.yml @@ -0,0 +1,110 @@ +image: fedora:latest + +variables: + DEPS: meson ninja-build + gcc libasan liblsan libubsan pkg-config glib2-devel + mingw64-gcc mingw64-pkg-config mingw64-glib2 + clang-analyzer git-core + +before_script: + - dnf install -y $DEPS + - git fetch --tags https://gitlab.freedesktop.org/slirp/libslirp.git + - git describe + +build: + script: + - meson --werror build || (cat build/meson-logs/meson-log.txt && exit 1) + - ninja -C build + - (cd build && meson test) || (cat build/meson-logs/testlog.txt && exit 1) + - ninja -C build scan-build + +build-asan: + script: + - CFLAGS=-fsanitize=address meson --werror build || (cat build/meson-logs/meson-log.txt && exit 1) + - ninja -C build + - (cd build && ASAN_OPTIONS=detect_leaks=0 meson test) || (cat build/meson-logs/testlog.txt && exit 1) + +build-lsan: + script: + - CFLAGS=-fsanitize=leak meson --werror build || (cat build/meson-logs/meson-log.txt && exit 1) + - ninja -C build + - (cd build && meson test) || (cat build/meson-logs/testlog.txt && exit 1) + +build-usan: + script: + - CFLAGS=-fsanitize=undefined meson --werror build || (cat build/meson-logs/meson-log.txt && exit 1) + - ninja -C build + - (cd build && meson test) || (cat build/meson-logs/testlog.txt && exit 1) + +fuzz: + parallel: + matrix: + - TARGET: [arp, ip-header, udp, udp-h, tftp, dhcp, icmp, tcp, tcp-h, ndp, ip6-header, udp6, udp6-h, tftp6, icmp6, tcp6, tcp6-h] + script: + - CC=clang CXX=clang++ meson build -Dllvm-fuzz=true || (cat build/meson-logs/meson-log.txt && exit 1) + - ninja -C build + - build/fuzzing/fuzz-$TARGET -seed=1234 -runs=1000000 fuzzing/IN_$TARGET + artifacts: + when: on_failure + paths: + - crash-* + - leak-* + - oom-* + - timeout-* + +build-mingw64: + script: + - (mkdir buildw && cd buildw && mingw64-meson --werror) || (cat buildw/meson-logs/meson-log.txt && exit 1) + - ninja -C buildw + +Coverity: + only: + refs: + - master + - coverity + script: + - dnf update -y + - dnf install -y curl clang + - curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 + --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN + - tar xfz /tmp/cov-analysis-linux64.tgz + - CC=clang meson build + - cov-analysis-linux64-*/bin/cov-build --dir cov-int ninja -C build + - tar cfz cov-int.tar.gz cov-int + - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME + --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL + --form file=@cov-int.tar.gz --form version="`git describe --tags`" + --form description="`git describe --tags` / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID " + +integration-slirp4netns: + variables: + SLIRP4NETNS_VERSION: "v1.1.12" + # Consumed by `make benchmark` + BENCHMARK_IPERF3_DURATION: "10" + script: + # Install libslirp + - meson build + - ninja -C build install + # Register the path of libslirp.so.0 + - echo /usr/local/lib64 >/etc/ld.so.conf.d/libslirp.conf + - ldconfig + # Install the dependencies of slirp4netns and its test suite + # TODO: install udhcpc for `slirp4netns/tests/test-slirp4netns-dhcp.sh` (currently skipped, due to lack of udhcpc) + - dnf install -y autoconf automake findutils iperf3 iproute iputils jq libcap-devel libseccomp-devel nmap-ncat util-linux + # Check whether the runner environment is configured correctly + - unshare -rn true || (echo Make sure you have relaxed seccomp and appamor && exit 1) + - unshare -rn ip tap add tap0 mode tap || (echo Make sure you have /dev/net/tun && exit 1) + # Install slirp4netns + - git clone https://github.com/rootless-containers/slirp4netns -b "${SLIRP4NETNS_VERSION}" + - cd slirp4netns + - ./autogen.sh + - ./configure + - make + - make install + - slirp4netns --version + # Run slirp4netns integration test + - make distcheck || (cat $(find . -name 'test-suite.log' ) && exit 1) + # Run benchmark test to ensure that libslirp can actually handle packets, with several MTU configurations + - make benchmark MTU=1500 + - make benchmark MTU=512 + - make benchmark MTU=65520 diff --git a/src/net/libslirp/.gitpublish b/src/net/libslirp/.gitpublish new file mode 100644 index 00000000..7b852951 --- /dev/null +++ b/src/net/libslirp/.gitpublish @@ -0,0 +1,3 @@ +[gitpublishprofile "default"] +base = master +to = slirp@lists.freedesktop.org diff --git a/src/net/libslirp/CHANGELOG.md b/src/net/libslirp/CHANGELOG.md new file mode 100644 index 00000000..4684bce5 --- /dev/null +++ b/src/net/libslirp/CHANGELOG.md @@ -0,0 +1,238 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [4.8.0] - TODO + +## Security + + - tcp: Fix testing for last fragment + - tftp: Fix use-after-free + +### Added + + - Add support for Haiku !123 + - ncsi: Add manufacturer's ID !122 + - ncsi: Add Get Version ID command !122 + - ncsi: Add out-of-band ethernet address !125 + - ncsi: Add Mellanox Get Mac Address handler !125 + - icmp6: Add echo request forwarding support + - Add fuzzing infrastructure + +### Fixed + + - Fix missing cleanups + - windows: Build fixes + - ipv6: Use target address from Neighbor Advertisement !129 + - dns: Reject domain-search when any entry ends with ".." + - dns: Use localhost as dns when /etc/resolv.conf empty !130 + - icmp: Handle ICMP packets as IPPROTO_IP on BSD !133 + - eth: pad ethernet frames to 60 bytes #34 + +### Removed + + - windows: Bump the minimum Windows version to Windows 7 + +## [4.7.0] - 2022-04-26 + +### Added + + - Allow disabling the internal DHCP server !22 + - icmp: Support falling back on trying a SOCK_RAW socket !92 + - Support Unix sockets in hostfwd !103 + - IPv6 DNS proxying support !110 + - bootp: add support for UEFI HTTP boot !111 + - New callback that supports CFI better !117 + +### Fixed + + - dhcp: Always send DHCP_OPT_LEN bytes in options !97 + - Fix Haiku build !98 !99 + - Fix memory leak when using libresolv !100 + - Ensure sin6_scope_id is zero for global addresses !102 + - resolv: fix IPv6 resolution on Darwin !104 + - socket: Initialize so_type in socreate !109 + - Handle ECONNABORTED from recv !116 + +## [4.6.1] - 2021-06-18 + +### Fixed + + - Fix DHCP regression introduced in 4.6.0. !95 + +## [4.6.0] - 2021-06-14 + +### Added + + - mbuf: Add debugging helpers for allocation. !90 + +### Changed + + - Revert "Set macOS deployment target to macOS 10.4". !93 + +### Fixed + + - mtod()-related buffer overflows (CVE-2021-3592 #44, CVE-2021-3593 #45, + CVE-2021-3594 #47, CVE-2021-3595 #46). + - poll_fd: add missing fd registration for UDP and ICMP + - ncsi: make ncsi_calculate_checksum work with unaligned data. !89 + - Various typos and doc fixes. !88 + +## [4.5.0] - 2021-05-18 + +### Added + + - IPv6 forwarding. !62 !75 !77 + - slirp_neighbor_info() to dump the ARP/NDP tables. !71 + +### Changed + + - Lazy guest address resolution for IPv6. !81 + - Improve signal handling when spawning a child. !61 + - Set macOS deployment target to macOS 10.4. !72 + - slirp_add_hostfwd: Ensure all error paths set errno. !80 + - More API documentation. + +### Fixed + + - Assertion failure on unspecified IPv6 address. !86 + - Disable polling for PRI on MacOS, fixing some closing streams issues. !73 + - Various memory leak fixes on fastq/batchq. !68 + - Memory leak on IPv6 fast-send. !67 + - Slow socket response on Windows. !64 + - Misc build and code cleanups. !60 !63 !76 !79 !84 + +## [4.4.0] - 2020-12-02 + +### Added + + - udp, udp6, icmp: handle TTL value. !48 + - Enable forwarding ICMP errors. !49 + - Add DNS resolving for iOS. !54 + +### Changed + + - Improve meson subproject() support. !53 + - Removed Makefile-based build system. !56 + +### Fixed + + - socket: consume empty packets. !55 + - check pkt_len before reading protocol header (CVE-2020-29129). !57 + - ip_stripoptions use memmove (fixes undefined behaviour). !47 + - various Coverity-related changes/fixes. + +## [4.3.1] - 2020-07-08 + +### Changed + + - A silent truncation could occur in `slirp_fmt()`, which will now print a + critical message. See also #22. + +### Fixed + + - CVE-2020-10756 - Drop bogus IPv6 messages that could lead to data leakage. + See !44 and !42. + - Fix win32 builds by using the SLIRP_PACKED definition. + - Various coverity scan errors fixed. !41 + - Fix new GCC warnings. !43 + +## [4.3.0] - 2020-04-22 + +### Added + + - `SLIRP_VERSION_STRING` macro, with the git sha suffix when building from git + - `SlirpConfig.disable_dns`, to disable DNS redirection #16 + +### Changed + + - `slirp_version_string()` now has the git sha suffix when building form git + - Limit DNS redirection to port 53 #16 + +### Fixed + + - Fix build regression with mingw & NetBSD + - Fix use-afte-free in `ip_reass()` (CVE-2020-1983) + +## [4.2.0] - 2020-03-17 + +### Added + + - New API function `slirp_add_unix`: add a forward rule to a Unix socket. + - New API function `slirp_remove_guestfwd`: remove a forward rule previously + added by `slirp_add_exec`, `slirp_add_unix` or `slirp_add_guestfwd` + - New `SlirpConfig.outbound_addr{,6}` fields to bind output socket to a + specific address + +### Changed + + - socket: do not fallback on host loopback if `get_dns_addr()` failed + or the address is in slirp network + +### Fixed + + - ncsi: fix checksum OOB memory access + - `tcp_emu()`: fix OOB accesses + - tftp: restrict relative path access + - state: fix loading of guestfwd state + +## [4.1.0] - 2019-12-02 + +### Added + + - The `slirp_new()` API, simpler and more extensible than `slirp_init()`. + - Allow custom MTU configuration. + - Option to disable host loopback connections. + - CI now runs scan-build too. + +### Changed + + - Disable `tcp_emu()` by default. `tcp_emu()` is known to have caused + several CVEs, and not useful today in most cases. The feature can + be still enabled by setting `SlirpConfig.enable_emu` to true. + - meson build system is now `subproject()` friendly. + - Replace remaining `malloc()`/`free()` with glib (which aborts on OOM) + - Various code cleanups. + +### Deprecated + + - The `slirp_init()` API. + +### Fixed + + - `getpeername()` error after `shutdown(SHUT_WR)`. + - Exec forward: correctly parse command lines that contain spaces. + - Allow 0.0.0.0 destination address. + - Make host receive broadcast packets. + - Various memory related fixes (heap overflow, leaks, NULL + dereference). + - Compilation warnings, dead code. + +## [4.0.0] - 2019-05-24 + +### Added + + - Installable as a shared library. + - meson build system + (& make build system for in-tree QEMU integration) + +### Changed + + - Standalone project, removing any QEMU dependency. + - License clarifications. + +[Unreleased]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.8.0...master +[4.8.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.7.0...v4.8.0 +[4.7.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.6.1...v4.7.0 +[4.6.1]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.6.0...v4.6.1 +[4.6.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.5.0...v4.6.0 +[4.5.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.4.0...v4.5.0 +[4.4.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.3.1...v4.4.0 +[4.3.1]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.3.0...v4.3.1 +[4.3.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.2.0...v4.3.0 +[4.2.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.1.0...v4.2.0 +[4.1.0]: https://gitlab.freedesktop.org/slirp/libslirp/compare/v4.0.0...v4.1.0 +[4.0.0]: https://gitlab.freedesktop.org/slirp/libslirp/commits/v4.0.0 diff --git a/src/net/libslirp/CMakeLists.txt b/src/net/libslirp/CMakeLists.txt new file mode 100644 index 00000000..99d0b3f7 --- /dev/null +++ b/src/net/libslirp/CMakeLists.txt @@ -0,0 +1,67 @@ +cmake_minimum_required(VERSION 3.16) + +project(libslirp VERSION 4.8.0 LANGUAGES C) + +set(SLIRP_MAJOR_VERSION "${libslirp_VERSION_MAJOR}") +set(SLIRP_MINOR_VERSION "${libslirp_VERSION_MINOR}") +set(SLIRP_MICRO_VERSION "${libslirp_VERSION_PATCH}") +set(SLIRP_VERSION_STRING "\"${libslirp_VERSION}\"") + +set(SOURCES + src/arp_table.c + src/bootp.c + src/cksum.c + src/dhcpv6.c + src/dnssearch.c + src/if.c + src/ip6_icmp.c + src/ip6_input.c + src/ip6_output.c + src/ip_icmp.c + src/ip_input.c + src/ip_output.c + src/mbuf.c + src/misc.c + src/ncsi.c + src/ndp_table.c + src/sbuf.c + src/slirp.c + src/socket.c + src/state.c + src/stream.c + src/tcp_input.c + src/tcp_output.c + src/tcp_subr.c + src/tcp_timer.c + src/tftp.c + src/udp6.c + src/udp.c + src/util.c + src/version.c + src/vmstate.c + + # glib shim + glib/glib.c +) + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/libslirp-version.h.in" "${CMAKE_CURRENT_BINARY_DIR}/libslirp-version.h") + +add_library(slirp STATIC ${SOURCES}) +target_compile_definitions(slirp PUBLIC LIBSLIRP_STATIC_BUILD) + +target_include_directories(slirp SYSTEM PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/glib") +target_include_directories(slirp SYSTEM PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src") +target_include_directories(slirp SYSTEM PUBLIC "${CMAKE_CURRENT_BINARY_DIR}") + +target_compile_definitions(slirp PRIVATE BUILDING_LIBSLIRP) +target_compile_definitions(slirp PRIVATE "G_LOG_DOMAIN=\"Slirp\"") + +if (WIN32) + target_link_libraries(slirp PRIVATE ws2_32 iphlpapi) +elseif(HAIKU) + target_Link_libraries(slirp PRIVATE network) +elseif(APPLE) + target_link_libraries(slirp PRIVATE resolv) +else() + set_source_files_properties(glib/glib.c PROPERTIES COMPILE_FLAGS -fvisibility=hidden) +endif() diff --git a/src/net/libslirp/COPYRIGHT b/src/net/libslirp/COPYRIGHT new file mode 100644 index 00000000..ed49512d --- /dev/null +++ b/src/net/libslirp/COPYRIGHT @@ -0,0 +1,62 @@ +Slirp was written by Danny Gasparovski. +Copyright (c), 1995,1996 All Rights Reserved. + +Slirp is free software; "free" as in you don't have to pay for it, and you +are free to do whatever you want with it. I do not accept any donations, +monetary or otherwise, for Slirp. Instead, I would ask you to pass this +potential donation to your favorite charity. In fact, I encourage +*everyone* who finds Slirp useful to make a small donation to their +favorite charity (for example, GreenPeace). This is not a requirement, but +a suggestion from someone who highly values the service they provide. + +The copyright terms and conditions: + +---BEGIN--- + + Copyright (c) 1995,1996 Danny Gasparovski. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + DANNY GASPAROVSKI OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---END--- + +This basically means you can do anything you want with the software, except +1) call it your own, and 2) claim warranty on it. There is no warranty for +this software. None. Nada. If you lose a million dollars while using +Slirp, that's your loss not mine. So, ***USE AT YOUR OWN RISK!***. + +If these conditions cannot be met due to legal restrictions (E.g. where it +is against the law to give out Software without warranty), you must cease +using the software and delete all copies you have. + +Slirp uses code that is copyrighted by the following people/organizations: + +Juha Pirkola. +Gregory M. Christy. +The Regents of the University of California. +Carnegie Mellon University. +The Australian National University. +RSA Data Security, Inc. + +Please read the top of each source file for the details on the various +copyrights. diff --git a/src/net/libslirp/README.md b/src/net/libslirp/README.md new file mode 100644 index 00000000..9f9c1b14 --- /dev/null +++ b/src/net/libslirp/README.md @@ -0,0 +1,60 @@ +# libslirp + +libslirp is a user-mode networking library used by virtual machines, +containers or various tools. + +## Getting Started + +### Prerequisites + +A C compiler, meson and glib2 development libraries. + +(see also [.gitlab-ci.yml](.gitlab-ci.yml) DEPS variable for the list +of dependencies on Fedora) + +### Building + +You may build and install the shared library with meson: + +``` sh +meson build +ninja -C build install +``` +And configure QEMU with --enable-slirp=system to link against it. + +(QEMU may build with the submodule static library using --enable-slirp=git) + +### Testing + +Unfortunately, there are no automated tests available. + +You may run QEMU ``-net user`` linked with your development version. + +## Contributing + +Feel free to open issues on the [project +issues](https://gitlab.freedesktop.org/slirp/libslirp/issues) page. + +You may clone the [gitlab +project](https://gitlab.freedesktop.org/slirp/libslirp) and create a +merge request. + +Contributing with gitlab allows gitlab workflow, tracking issues, +running CI etc. + +Alternatively, you may send patches to slirp@lists.freedesktop.org +mailing list. + +## Versioning + +We intend to use [libtool's +versioning](https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html) +for the shared libraries and use [SemVer](http://semver.org/) for +project versions. + +For the versions available, see the [tags on this +repository](https://gitlab.freedesktop.org/slirp/libslirp/releases). + +## License + +See the [COPYRIGHT](COPYRIGHT) file for details. diff --git a/src/net/libslirp/fuzzing/IN_arp/arp.pcap b/src/net/libslirp/fuzzing/IN_arp/arp.pcap new file mode 100644 index 00000000..f920e213 Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_arp/arp.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_dhcp/dhcp.pkt b/src/net/libslirp/fuzzing/IN_dhcp/dhcp.pkt new file mode 100644 index 00000000..92000377 Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_dhcp/dhcp.pkt differ diff --git a/src/net/libslirp/fuzzing/IN_dhcp/dhcp_capture.pcap b/src/net/libslirp/fuzzing/IN_dhcp/dhcp_capture.pcap new file mode 100644 index 00000000..99ae0a83 Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_dhcp/dhcp_capture.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_icmp/icmp_capture.pcap b/src/net/libslirp/fuzzing/IN_icmp/icmp_capture.pcap new file mode 100644 index 00000000..6a0f0b83 Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_icmp/icmp_capture.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_icmp/ping_10-0-2-2.pcap b/src/net/libslirp/fuzzing/IN_icmp/ping_10-0-2-2.pcap new file mode 100644 index 00000000..69b60fbc Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_icmp/ping_10-0-2-2.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_icmp6/icmp_capture.pcap b/src/net/libslirp/fuzzing/IN_icmp6/icmp_capture.pcap new file mode 100644 index 00000000..365bed10 Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_icmp6/icmp_capture.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_icmp6/ndp.pcap b/src/net/libslirp/fuzzing/IN_icmp6/ndp.pcap new file mode 100644 index 00000000..74443f1e --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_icmp6/ndp.pcap @@ -0,0 +1 @@ +../IN_ndp/ndp.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_icmp6/ping_10-0-2-2.pcap b/src/net/libslirp/fuzzing/IN_icmp6/ping_10-0-2-2.pcap new file mode 100644 index 00000000..87f63899 Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_icmp6/ping_10-0-2-2.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_ip-header/DNS_freedesktop_1-1-1-1.pcap b/src/net/libslirp/fuzzing/IN_ip-header/DNS_freedesktop_1-1-1-1.pcap new file mode 100644 index 00000000..09b9ce1c --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip-header/DNS_freedesktop_1-1-1-1.pcap @@ -0,0 +1 @@ +../IN_udp/DNS_freedesktop_1-1-1-1.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ip-header/dhcp.pkt b/src/net/libslirp/fuzzing/IN_ip-header/dhcp.pkt new file mode 100644 index 00000000..b1a9e045 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip-header/dhcp.pkt @@ -0,0 +1 @@ +../IN_dhcp/dhcp.pkt \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ip-header/dhcp_capture.pcap b/src/net/libslirp/fuzzing/IN_ip-header/dhcp_capture.pcap new file mode 100644 index 00000000..e2d9c0db --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip-header/dhcp_capture.pcap @@ -0,0 +1 @@ +../IN_dhcp/dhcp_capture.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ip-header/icmp_capture.pcap b/src/net/libslirp/fuzzing/IN_ip-header/icmp_capture.pcap new file mode 100644 index 00000000..a9ec1936 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip-header/icmp_capture.pcap @@ -0,0 +1 @@ +../IN_icmp/icmp_capture.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ip-header/nc-10.0.2.2-8080.pcap b/src/net/libslirp/fuzzing/IN_ip-header/nc-10.0.2.2-8080.pcap new file mode 100644 index 00000000..c99b568e --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip-header/nc-10.0.2.2-8080.pcap @@ -0,0 +1 @@ +../IN_tcp/nc-10.0.2.2-8080.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ip-header/nc-ident.pcap b/src/net/libslirp/fuzzing/IN_ip-header/nc-ident.pcap new file mode 100644 index 00000000..51ecdf0e --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip-header/nc-ident.pcap @@ -0,0 +1 @@ +../IN_tcp/nc-ident.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ip-header/ping_10-0-2-2.pcap b/src/net/libslirp/fuzzing/IN_ip-header/ping_10-0-2-2.pcap new file mode 100644 index 00000000..9a71a0e4 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip-header/ping_10-0-2-2.pcap @@ -0,0 +1 @@ +../IN_icmp/ping_10-0-2-2.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ip-header/tcp_qemucapt.pcap b/src/net/libslirp/fuzzing/IN_ip-header/tcp_qemucapt.pcap new file mode 100644 index 00000000..8daa990e --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip-header/tcp_qemucapt.pcap @@ -0,0 +1 @@ +../IN_tcp/tcp_qemucapt.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ip-header/tftp-get-blah.pkt b/src/net/libslirp/fuzzing/IN_ip-header/tftp-get-blah.pkt new file mode 100644 index 00000000..c95e90d9 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip-header/tftp-get-blah.pkt @@ -0,0 +1 @@ +../IN_tftp/tftp-get-blah.pkt \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ip-header/tftp_capture.pcap b/src/net/libslirp/fuzzing/IN_ip-header/tftp_capture.pcap new file mode 100644 index 00000000..cbaf3ab0 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip-header/tftp_capture.pcap @@ -0,0 +1 @@ +../IN_tftp/tftp_capture.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ip-header/tftp_get_libslirp-txt.pcap b/src/net/libslirp/fuzzing/IN_ip-header/tftp_get_libslirp-txt.pcap new file mode 100644 index 00000000..8aa1945d --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip-header/tftp_get_libslirp-txt.pcap @@ -0,0 +1 @@ +../IN_tftp/tftp_get_libslirp-txt.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ip6-header/DNS_freedesktop_1-1-1-1.pcap b/src/net/libslirp/fuzzing/IN_ip6-header/DNS_freedesktop_1-1-1-1.pcap new file mode 100644 index 00000000..651c2739 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip6-header/DNS_freedesktop_1-1-1-1.pcap @@ -0,0 +1 @@ +../IN_udp6/DNS_freedesktop_1-1-1-1.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ip6-header/icmp_capture.pcap b/src/net/libslirp/fuzzing/IN_ip6-header/icmp_capture.pcap new file mode 100644 index 00000000..519f78a7 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip6-header/icmp_capture.pcap @@ -0,0 +1 @@ +../IN_icmp6/icmp_capture.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ip6-header/ping_10-0-2-2.pcap b/src/net/libslirp/fuzzing/IN_ip6-header/ping_10-0-2-2.pcap new file mode 100644 index 00000000..632a2868 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip6-header/ping_10-0-2-2.pcap @@ -0,0 +1 @@ +../IN_icmp6/ping_10-0-2-2.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ip6-header/tcp_qemucapt.pcap b/src/net/libslirp/fuzzing/IN_ip6-header/tcp_qemucapt.pcap new file mode 100644 index 00000000..b444205b --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip6-header/tcp_qemucapt.pcap @@ -0,0 +1 @@ +../IN_tcp6/tcp_qemucapt.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ip6-header/tftp_capture.pcap b/src/net/libslirp/fuzzing/IN_ip6-header/tftp_capture.pcap new file mode 100644 index 00000000..308ab28e --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip6-header/tftp_capture.pcap @@ -0,0 +1 @@ +../IN_udp6/tftp_capture.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ip6-header/tftp_get_libslirp-txt.pcap b/src/net/libslirp/fuzzing/IN_ip6-header/tftp_get_libslirp-txt.pcap new file mode 100644 index 00000000..d3b4e76d --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_ip6-header/tftp_get_libslirp-txt.pcap @@ -0,0 +1 @@ +../IN_udp6/tftp_get_libslirp-txt.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_ndp/ndp.pcap b/src/net/libslirp/fuzzing/IN_ndp/ndp.pcap new file mode 100644 index 00000000..ed8db971 Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_ndp/ndp.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_tcp-d b/src/net/libslirp/fuzzing/IN_tcp-d new file mode 100644 index 00000000..1bca80b4 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_tcp-d @@ -0,0 +1 @@ +IN_tcp \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_tcp-h b/src/net/libslirp/fuzzing/IN_tcp-h new file mode 100644 index 00000000..1bca80b4 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_tcp-h @@ -0,0 +1 @@ +IN_tcp \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_tcp/nc-10.0.2.2-8080.pcap b/src/net/libslirp/fuzzing/IN_tcp/nc-10.0.2.2-8080.pcap new file mode 100644 index 00000000..48bd8816 Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_tcp/nc-10.0.2.2-8080.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_tcp/nc-ident.pcap b/src/net/libslirp/fuzzing/IN_tcp/nc-ident.pcap new file mode 100644 index 00000000..3d0421b5 Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_tcp/nc-ident.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_tcp/tcp_qemucapt.pcap b/src/net/libslirp/fuzzing/IN_tcp/tcp_qemucapt.pcap new file mode 100644 index 00000000..83a0eddf Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_tcp/tcp_qemucapt.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_tcp6-d b/src/net/libslirp/fuzzing/IN_tcp6-d new file mode 100644 index 00000000..2ad34597 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_tcp6-d @@ -0,0 +1 @@ +IN_tcp6 \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_tcp6-h b/src/net/libslirp/fuzzing/IN_tcp6-h new file mode 100644 index 00000000..2ad34597 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_tcp6-h @@ -0,0 +1 @@ +IN_tcp6 \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_tcp6/tcp_qemucapt.pcap b/src/net/libslirp/fuzzing/IN_tcp6/tcp_qemucapt.pcap new file mode 100644 index 00000000..d7936b32 Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_tcp6/tcp_qemucapt.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_tftp/tftp-get-blah.pkt b/src/net/libslirp/fuzzing/IN_tftp/tftp-get-blah.pkt new file mode 100644 index 00000000..c540ccf3 Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_tftp/tftp-get-blah.pkt differ diff --git a/src/net/libslirp/fuzzing/IN_tftp/tftp_capture.pcap b/src/net/libslirp/fuzzing/IN_tftp/tftp_capture.pcap new file mode 100644 index 00000000..6fed4277 Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_tftp/tftp_capture.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_tftp/tftp_get_libslirp-txt.pcap b/src/net/libslirp/fuzzing/IN_tftp/tftp_get_libslirp-txt.pcap new file mode 100644 index 00000000..18defe4d Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_tftp/tftp_get_libslirp-txt.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_tftp6/tftp_capture.pcap b/src/net/libslirp/fuzzing/IN_tftp6/tftp_capture.pcap new file mode 100644 index 00000000..dd3ee718 Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_tftp6/tftp_capture.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_tftp6/tftp_get_libslirp-txt.pcap b/src/net/libslirp/fuzzing/IN_tftp6/tftp_get_libslirp-txt.pcap new file mode 100644 index 00000000..caa144a0 Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_tftp6/tftp_get_libslirp-txt.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_udp-h b/src/net/libslirp/fuzzing/IN_udp-h new file mode 100644 index 00000000..d1958903 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_udp-h @@ -0,0 +1 @@ +IN_udp \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_udp/DNS_freedesktop_1-1-1-1.pcap b/src/net/libslirp/fuzzing/IN_udp/DNS_freedesktop_1-1-1-1.pcap new file mode 100644 index 00000000..9950156a Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_udp/DNS_freedesktop_1-1-1-1.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_udp/dhcp.pkt b/src/net/libslirp/fuzzing/IN_udp/dhcp.pkt new file mode 100644 index 00000000..b1a9e045 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_udp/dhcp.pkt @@ -0,0 +1 @@ +../IN_dhcp/dhcp.pkt \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_udp/dhcp_capture.pcap b/src/net/libslirp/fuzzing/IN_udp/dhcp_capture.pcap new file mode 100644 index 00000000..e2d9c0db --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_udp/dhcp_capture.pcap @@ -0,0 +1 @@ +../IN_dhcp/dhcp_capture.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_udp/tftp-get-blah.pkt b/src/net/libslirp/fuzzing/IN_udp/tftp-get-blah.pkt new file mode 100644 index 00000000..c95e90d9 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_udp/tftp-get-blah.pkt @@ -0,0 +1 @@ +../IN_tftp/tftp-get-blah.pkt \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_udp/tftp_capture.pcap b/src/net/libslirp/fuzzing/IN_udp/tftp_capture.pcap new file mode 100644 index 00000000..cbaf3ab0 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_udp/tftp_capture.pcap @@ -0,0 +1 @@ +../IN_tftp/tftp_capture.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_udp/tftp_get_libslirp-txt.pcap b/src/net/libslirp/fuzzing/IN_udp/tftp_get_libslirp-txt.pcap new file mode 100644 index 00000000..8aa1945d --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_udp/tftp_get_libslirp-txt.pcap @@ -0,0 +1 @@ +../IN_tftp/tftp_get_libslirp-txt.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_udp6-h b/src/net/libslirp/fuzzing/IN_udp6-h new file mode 100644 index 00000000..4d7837b0 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_udp6-h @@ -0,0 +1 @@ +IN_udp6 \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_udp6/DNS_freedesktop_1-1-1-1.pcap b/src/net/libslirp/fuzzing/IN_udp6/DNS_freedesktop_1-1-1-1.pcap new file mode 100644 index 00000000..87abc123 Binary files /dev/null and b/src/net/libslirp/fuzzing/IN_udp6/DNS_freedesktop_1-1-1-1.pcap differ diff --git a/src/net/libslirp/fuzzing/IN_udp6/tftp_capture.pcap b/src/net/libslirp/fuzzing/IN_udp6/tftp_capture.pcap new file mode 100644 index 00000000..9bd68303 --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_udp6/tftp_capture.pcap @@ -0,0 +1 @@ +../IN_tftp6/tftp_capture.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/IN_udp6/tftp_get_libslirp-txt.pcap b/src/net/libslirp/fuzzing/IN_udp6/tftp_get_libslirp-txt.pcap new file mode 100644 index 00000000..39fc722b --- /dev/null +++ b/src/net/libslirp/fuzzing/IN_udp6/tftp_get_libslirp-txt.pcap @@ -0,0 +1 @@ +../IN_tftp6/tftp_get_libslirp-txt.pcap \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/README.md b/src/net/libslirp/fuzzing/README.md new file mode 100644 index 00000000..a028a98b --- /dev/null +++ b/src/net/libslirp/fuzzing/README.md @@ -0,0 +1,59 @@ +# Fuzzing libslirp state and instructions + +## Current state +We chose to use libFuzzer because of its custom mutator feature, which allows to keep coherent informations inside the packets being sent to libslirp. This ease the process of fuzzing as packets are less likely to be rejected early during processing them. + +In the current state, the `meson.build` file is not compatible with the original one used by libSlirp main repository but it should be easy to merge them in a clean way. Also **in the current state, it seems that there is a memory leak inside the fuzzing code**, which make it run out of memory. The current goal is to find and get rid of this leak to allow fuzzing for longer without the process being interrupted because of it. + +Six harness are currently available, more are to be added later to focus on other parts of the code : + +- **fuzz-ip-header** : the mutator focuses on the ip header field informations, +- **fuzz-udp** : the mutator only work on udp packets, mutating the udp header and content, or only one or the other (-h,-d), +- **fuzz-tcp** : the mutator targets tcp packets, header+data or only one or the other, or only one or the other (-h,-d), +- **fuzz-icmp** : the mutator focuses on icmp packets, + +These harness should be good starting examples on how to fuzz libslirp using libFuzzer. + +## Running the fuzzer + +Building the fuzzers/harness requires the use of clang as libFuzzer is part of LLVM. +You can build it running : + +`CC=clang meson build && ninja -C build` + +It will build the fuzzer in the ./build/fuzzing/ directory. + +A script named `fuzzing/coverage.py` is available to generate coverage informations. **It makes a lot of assumptions on the directory structure** and should be read before use. + +To run the fuzzer, simply run some of: + +- `build/fuzzing/fuzz-ip-header fuzzing/IN_ip-header` +- `build/fuzzing/fuzz-udp fuzzing/IN_udp` +- `build/fuzzing/fuzz-udp-h fuzzing/IN_udp-h` +- `build/fuzzing/fuzz-tftp fuzzing/IN_tftp` +- `build/fuzzing/fuzz-dhcp fuzzing/IN_dhcp` +- `build/fuzzing/fuzz-icmp fuzzing/IN_icmp` +- `build/fuzzing/fuzz-tcp fuzzing/IN_tcp` + +Your current directory should be a separate directory as crashes to it. New inputs found by the fuzzer will go directly in the `IN` folder. + +# Adding new files to the corpus + +In its current state, the fuzzing code is taking pcap files as input, we produced some using `tcpdump` on linux inside qemu with default settings. +Those files should be captured using the `EN10MB (Ethernet)` data link type, this can be set with the flag `-y` but it seems this can't be done while listening on all interfaces (`-i any`). +New files should give new coverage, to ensure a new file is usefull the `coverage.py` script (see next section) can be used to compare the coverage with and without that new file. + +# Coverage + +The `coverage.py` script allows to see coverage informations about the corpus. It makes a lot of assumptions on the directory structure so it should be read and probably modified before running it. +It must be called with the protocol to cover: `python coverage.py udp report`. +To generate coverage informations, the following flags are passed to the fuzzer and libslirp : + +- g +- fsanitize-coverage=edge,indirect-calls,trace-cmp +- fprofile-instr-generate +- fcoverage-mapping + +The last 2 arguments should also be passed to the linker. + +Then the `llvm-profdata` and `llvm-cov` tools can be used to generate a report and a fancy set of HTML files with line-coverage informations. diff --git a/src/net/libslirp/fuzzing/coverage.py b/src/net/libslirp/fuzzing/coverage.py new file mode 100644 index 00000000..861f2acf --- /dev/null +++ b/src/net/libslirp/fuzzing/coverage.py @@ -0,0 +1,37 @@ +from os import chdir,listdir,environ +from os.path import isfile,join,isdir +from subprocess import DEVNULL, run +import sys + +ignored_files = "-ignore-filename-regex=glib -ignore-filename-regex=fuzz -ignore-filename-regex=helper -ignore-filename-regex=h$" + +if __name__ == "__main__": + chdir("build/fuzzing/out") + available_targets = [exe for exe in listdir("../") if isfile(join("..", exe))] + available_corpus_path = [exe for exe in listdir("../../../fuzzing/") if isdir(join("../../../fuzzing/", exe))] + available_result_types = ["export", "show", "report"] + if len(sys.argv) != 4 or sys.argv[1] not in available_targets or sys.argv[2] not in available_corpus_path or sys.argv[3] not in available_result_types: + print("usage : python coverage.py fuzz_target IN_protol result_type") + print(" - available targets : ") + print(available_targets) + print(" - available_corpus_path : ") + print(available_corpus_path) + print(" - available result types : ") + print(available_result_types) + exit(0) + fuzzing_target = sys.argv[1] + corpus_path = "../../../fuzzing/"+sys.argv[2]+"/" + result_type = sys.argv[3] + if fuzzing_target in available_targets: + environ["LLVM_PROFILE_FILE"] = fuzzing_target + "_%p.profraw" + corpus = listdir(corpus_path) + for f in corpus: + #print(corpus_path+f) + run(["../" + fuzzing_target, corpus_path+f,"-detect_leaks=0"], stdin=DEVNULL, stdout=DEVNULL, stderr=DEVNULL) + run(["llvm-profdata merge -sparse " + fuzzing_target + "_*.profraw -o " + fuzzing_target + ".profdata"], shell=True) + if result_type == "export" : + run(["llvm-cov show ../" + fuzzing_target + " -format=html -output-dir=../report -instr-profile=" + fuzzing_target + ".profdata " + ignored_files], shell=True) + elif result_type == "show" : + run(["llvm-cov show ../" + fuzzing_target + " -instr-profile=" + fuzzing_target + ".profdata " + ignored_files], shell=True) + else: + run(["llvm-cov report ../" + fuzzing_target + " -instr-profile=" + fuzzing_target + ".profdata " + ignored_files], shell=True) diff --git a/src/net/libslirp/fuzzing/fuzz-input.options b/src/net/libslirp/fuzzing/fuzz-input.options new file mode 100644 index 00000000..79488880 --- /dev/null +++ b/src/net/libslirp/fuzzing/fuzz-input.options @@ -0,0 +1,2 @@ +[libfuzzer] +max_len = 1024 diff --git a/src/net/libslirp/fuzzing/fuzz-main.c b/src/net/libslirp/fuzzing/fuzz-main.c new file mode 100644 index 00000000..1de031c2 --- /dev/null +++ b/src/net/libslirp/fuzzing/fuzz-main.c @@ -0,0 +1,35 @@ +#include +#include + +#define MIN_NUMBER_OF_RUNS 1 +#define EXIT_TEST_SKIP 77 + +extern int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size); + +int main(int argc, char **argv) +{ + int i, j; + + for (i = 1; i < argc; i++) { + GError *err = NULL; + char *name = argv[i]; + char *buf; + size_t size; + + if (!g_file_get_contents(name, &buf, &size, &err)) { + g_warning("Failed to read '%s': %s", name, err->message); + g_clear_error(&err); + return EXIT_FAILURE; + } + + g_print("%s...\n", name); + for (j = 0; j < MIN_NUMBER_OF_RUNS; j++) { + if (LLVMFuzzerTestOneInput((void *)buf, size) == EXIT_TEST_SKIP) { + return EXIT_TEST_SKIP; + } + } + g_free(buf); + } + + return EXIT_SUCCESS; +} diff --git a/src/net/libslirp/fuzzing/helper.c b/src/net/libslirp/fuzzing/helper.c new file mode 100644 index 00000000..399cfb5c --- /dev/null +++ b/src/net/libslirp/fuzzing/helper.c @@ -0,0 +1,271 @@ +#include "helper.h" +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "slirp_base_fuzz.h" + +#define MIN_NUMBER_OF_RUNS 1 +#define EXIT_TEST_SKIP 77 + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); +struct in6_addr ip6_host; +struct in6_addr ip6_dns; + +/// Function to compute the checksum of the ip header, should be compatible with +/// TCP and UDP checksum calculation too. +uint16_t compute_checksum(uint8_t *Data, size_t Size) +{ + uint32_t sum = 0; + uint16_t *Data_as_u16 = (uint16_t *)Data; + + for (size_t i = 0; i < Size / 2; i++) { + uint16_t val = ntohs(*(Data_as_u16 + i)); + sum += val; + } + if (Size % 2 == 1) + sum += Data[Size - 1] << 8; + + uint16_t carry = sum >> 16; + uint32_t sum_val = carry + (sum & 0xFFFF); + uint16_t result = (sum_val >> 16) + (sum_val & 0xFFFF); + return ~result; +} + +int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + /* FIXME: fail on some addr? */ + return 0; +} + +int listen(int sockfd, int backlog) +{ + return 0; +} + +int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + /* FIXME: fail on some addr? */ + return 0; +} + +ssize_t send(int sockfd, const void *buf, size_t len, int flags) +{ + /* FIXME: partial send? */ + return len; +} + +ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + /* FIXME: partial send? */ + return len; +} + +ssize_t recv(int sockfd, void *buf, size_t len, int flags) +{ + memset(buf, 0, len); + return len / 2; +} + +ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen) +{ + memset(buf, 0, len); + memset(src_addr, 0, *addrlen); + return len / 2; +} + +int setsockopt(int sockfd, int level, int optname, const void *optval, + socklen_t optlen) +{ + return 0; +} + +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +static void empty_logging_func(const gchar *log_domain, + GLogLevelFlags log_level, const gchar *message, + gpointer user_data) +{ +} +#endif + +/* Disables logging for oss-fuzz. Must be used with each target. */ +static void fuzz_set_logging_func(void) +{ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + g_log_set_default_handler(empty_logging_func, NULL); +#endif +} + +static ssize_t send_packet(const void *pkt, size_t pkt_len, void *opaque) +{ + return pkt_len; +} + +static int64_t clock_get_ns(void *opaque) +{ + return 0; +} + +static void *timer_new(SlirpTimerCb cb, void *cb_opaque, void *opaque) +{ + return NULL; +} + +static void timer_mod(void *timer, int64_t expire_timer, void *opaque) +{ +} + +static void timer_free(void *timer, void *opaque) +{ +} + +static void guest_error(const char *msg, void *opaque) +{ +} + +static void register_poll_fd(int fd, void *opaque) +{ +} + +static void unregister_poll_fd(int fd, void *opaque) +{ +} + +static void notify(void *opaque) +{ +} + +static const SlirpCb slirp_cb = { + .send_packet = send_packet, + .guest_error = guest_error, + .clock_get_ns = clock_get_ns, + .timer_new = timer_new, + .timer_mod = timer_mod, + .timer_free = timer_free, + .register_poll_fd = register_poll_fd, + .unregister_poll_fd = unregister_poll_fd, + .notify = notify, +}; + +#define MAX_EVID 1024 +static int fake_events[MAX_EVID]; + +static int add_poll_cb(int fd, int events, void *opaque) +{ + g_assert(fd < G_N_ELEMENTS(fake_events)); + fake_events[fd] = events; + return fd; +} + +static int get_revents_cb(int idx, void *opaque) +{ + return fake_events[idx] & ~(SLIRP_POLL_ERR | SLIRP_POLL_HUP); +} + +// Fuzzing strategy is the following : +// LLVMFuzzerTestOneInput : +// - build a slirp instance, +// - extract the packets from the pcap one by one, +// - send the data to `slirp_input` +// - call `slirp_pollfds_fill` and `slirp_pollfds_poll` to advance slirp +// - cleanup slirp when the whole pcap has been unwrapped. +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + Slirp *slirp = NULL; + struct in_addr net = { .s_addr = htonl(0x0a000200) }; /* 10.0.2.0 */ + struct in_addr mask = { .s_addr = htonl(0xffffff00) }; /* 255.255.255.0 */ + struct in_addr host = { .s_addr = htonl(0x0a000202) }; /* 10.0.2.2 */ + struct in_addr fwd = { .s_addr = htonl(0x0a000205) }; /* 10.0.2.5 */ + struct in_addr dhcp = { .s_addr = htonl(0x0a00020f) }; /* 10.0.2.15 */ + struct in_addr dns = { .s_addr = htonl(0x0a000203) }; /* 10.0.2.3 */ + struct in6_addr ip6_prefix; + int ret, vprefix6_len = 64; + const char *vhostname = NULL; + const char *tftp_server_name = NULL; + const char *tftp_export = "fuzzing/tftp"; + const char *bootfile = NULL; + const char **dnssearch = NULL; + const char *vdomainname = NULL; + const pcap_hdr_t *hdr = (const void *)data; + const pcaprec_hdr_t *rec = NULL; + uint32_t timeout = 0; + + if (size < sizeof(pcap_hdr_t)) { + return 0; + } + data += sizeof(*hdr); + size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + setenv("SLIRP_FUZZING", "1", 0); + + fuzz_set_logging_func(); + + ret = inet_pton(AF_INET6, "fec0::", &ip6_prefix); + g_assert_cmpint(ret, ==, 1); + + ip6_host = ip6_prefix; + ip6_host.s6_addr[15] |= 2; + ip6_dns = ip6_prefix; + ip6_dns.s6_addr[15] |= 3; + + slirp = + slirp_init(false, true, net, mask, host, true, ip6_prefix, vprefix6_len, + ip6_host, vhostname, tftp_server_name, tftp_export, bootfile, + dhcp, dns, ip6_dns, dnssearch, vdomainname, &slirp_cb, NULL); + + slirp_add_exec(slirp, "cat", &fwd, 1234); + + + for ( ; size > sizeof(*rec); data += rec->incl_len, size -= rec->incl_len) { + rec = (const void *)data; + data += sizeof(*rec); + size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + g_debug("unsupported rec->incl_len != rec->orig_len"); + break; + } + if (rec->incl_len > size) { + break; + } + + if (rec->incl_len >= 14) { + if (data[12] == 0x08 && data[13] == 0x00) { + /* IPv4 */ + if (rec->incl_len >= 14 + 16) { + uint32_t ipsource = * (uint32_t*) (data + 14 + 12); + + // This an answer, which we will produce, so don't receive + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + } else if (data[12] == 0x86 && data[13] == 0xdd) { + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (data + 14 + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + } + } + + slirp_input(slirp, data, rec->incl_len); + slirp_pollfds_fill(slirp, &timeout, add_poll_cb, NULL); + slirp_pollfds_poll(slirp, 0, get_revents_cb, NULL); + } + + slirp_cleanup(slirp); + + return 0; +} diff --git a/src/net/libslirp/fuzzing/helper.h b/src/net/libslirp/fuzzing/helper.h new file mode 100644 index 00000000..92b5f620 --- /dev/null +++ b/src/net/libslirp/fuzzing/helper.h @@ -0,0 +1,24 @@ +#ifndef _HELPER_H +#define _HELPER_H + +#ifdef _WIN32 +/* as defined in sdkddkver.h */ +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 /* Vista */ +#endif +#include +#endif + +#include +#include +#include + +#define PSEUDO_IP_SIZE (4*2 + 4) +#define PSEUDO_IPV6_SIZE (16*2 + 4) + +uint16_t compute_checksum(uint8_t *Data, size_t Size); + +extern struct in6_addr ip6_host; +extern struct in6_addr ip6_dns; + +#endif /* _HELPER_H */ diff --git a/src/net/libslirp/fuzzing/meson.build b/src/net/libslirp/fuzzing/meson.build new file mode 100644 index 00000000..c2017951 --- /dev/null +++ b/src/net/libslirp/fuzzing/meson.build @@ -0,0 +1,64 @@ +extra_sources = [] +extra_cargs = [] +extra_ldargs = [] +fuzzing_engine = [] + + +extra_cargs += '-g' +if fuzzer_build + extra_cargs += '-fsanitize=fuzzer,address' + extra_cargs += '-fsanitize-coverage=edge,indirect-calls,trace-cmp' + extra_cargs += '-DCUSTOM_MUTATOR' + extra_cargs += '-fprofile-instr-generate' + extra_cargs += '-fcoverage-mapping' + + extra_ldargs += '-fsanitize=fuzzer,address' + extra_ldargs += '-fprofile-instr-generate' + extra_ldargs += '-fcoverage-mapping' +endif + +deps = [glib_dep, libslirp_dep, platform_deps] + +exes = [ + ['fuzz-arp', ['slirp_fuzz_arp.c', 'helper.c']], + ['fuzz-ip-header', ['slirp_fuzz_ip_header.c', 'helper.c']], + ['fuzz-udp', ['slirp_fuzz_udp.c', 'helper.c']], + ['fuzz-udp-h', ['slirp_fuzz_udp_header.c', 'helper.c']], + ['fuzz-udp-d', ['slirp_fuzz_udp_data.c', 'helper.c']], + ['fuzz-tftp', ['slirp_fuzz_udp_data.c', 'helper.c']], + ['fuzz-dhcp', ['slirp_fuzz_udp_data.c', 'helper.c']], + ['fuzz-tcp', ['slirp_fuzz_tcp.c', 'helper.c']], + ['fuzz-tcp-h', ['slirp_fuzz_tcp_header.c', 'helper.c']], + ['fuzz-tcp-d', ['slirp_fuzz_tcp_data.c', 'helper.c']], + ['fuzz-icmp', ['slirp_fuzz_icmp.c', 'helper.c']], + + ['fuzz-ndp', ['slirp_fuzz_icmp6.c', 'helper.c']], + ['fuzz-ip6-header', ['slirp_fuzz_ip6_header.c', 'helper.c']], + ['fuzz-udp6', ['slirp_fuzz_udp6.c', 'helper.c']], + ['fuzz-udp6-h', ['slirp_fuzz_udp6_header.c', 'helper.c']], + ['fuzz-udp6-d', ['slirp_fuzz_udp6_data.c', 'helper.c']], + ['fuzz-tftp6', ['slirp_fuzz_udp6_data.c', 'helper.c']], + ['fuzz-tcp6', ['slirp_fuzz_tcp6.c', 'helper.c']], + ['fuzz-tcp6-h', ['slirp_fuzz_tcp6_header.c', 'helper.c']], + ['fuzz-tcp6-d', ['slirp_fuzz_tcp6_data.c', 'helper.c']], + ['fuzz-icmp6', ['slirp_fuzz_icmp6.c', 'helper.c']], + ] + +if fuzzer_build + foreach exe : exes + executable( + exe[0], exe[1], + dependencies : deps, + c_args: extra_cargs, + link_args: extra_ldargs, + ) + endforeach +endif + +if fuzz_reproduce + executable(['reproducer', ['reproducer.c', 'helper.c']], + dependencies: deps, + c_args: extra_cargs, + link_args: extra_ldargs, + ) +endif diff --git a/src/net/libslirp/fuzzing/oss-fuzz.sh b/src/net/libslirp/fuzzing/oss-fuzz.sh new file mode 100644 index 00000000..0561bdba --- /dev/null +++ b/src/net/libslirp/fuzzing/oss-fuzz.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +set -ex + +export CC=${CC:-clang} +export CXX=${CXX:-clang++} +export WORK=${WORK:-$(pwd)} +export OUT=${OUT:-$(pwd)/out} + +build=$WORK/build +rm -rf $build +mkdir -p $build +mkdir -p $OUT + +fuzzflag="oss-fuzz=true" +if [ -z "$FUZZING_ENGINE" ]; then + fuzzflag="llvm-fuzz=true" +fi + +meson $build \ + -D$fuzzflag \ + -Db_lundef=false \ + -Ddefault_library=static \ + -Dstatic=true \ + -Dbuildtype=debugoptimized + +ninja -C $build + +zip -jqr $OUT/fuzz-arp_seed_corpus.zip "$(dirname "$0")/IN_arp" +zip -jqr $OUT/fuzz-ip-header_seed_corpus.zip "$(dirname "$0")/IN_ip-header" +zip -jqr $OUT/fuzz-udp_seed_corpus.zip "$(dirname "$0")/IN_udp" +zip -jqr $OUT/fuzz-udp-h_seed_corpus.zip "$(dirname "$0")/IN_udp-h" +zip -jqr $OUT/fuzz-tftp_seed_corpus.zip "$(dirname "$0")/IN_tftp" +zip -jqr $OUT/fuzz-dhcp_seed_corpus.zip "$(dirname "$0")/IN_dhcp" +zip -jqr $OUT/fuzz-icmp_seed_corpus.zip "$(dirname "$0")/IN_icmp" +zip -jqr $OUT/fuzz-tcp_seed_corpus.zip "$(dirname "$0")/IN_tcp" +zip -jqr $OUT/fuzz-tcp-h_seed_corpus.zip "$(dirname "$0")/IN_tcp-h" + +zip -jqr $OUT/fuzz-ndp_seed_corpus.zip "$(dirname "$0")/IN_ndp" +zip -jqr $OUT/fuzz-ip6-header_seed_corpus.zip "$(dirname "$0")/IN_ip6-header" +zip -jqr $OUT/fuzz-udp6_seed_corpus.zip "$(dirname "$0")/IN_udp6" +zip -jqr $OUT/fuzz-udp6-h_seed_corpus.zip "$(dirname "$0")/IN_udp6-h" +zip -jqr $OUT/fuzz-tftp6_seed_corpus.zip "$(dirname "$0")/IN_tftp6" +zip -jqr $OUT/fuzz-icmp6_seed_corpus.zip "$(dirname "$0")/IN_icmp6" +zip -jqr $OUT/fuzz-tcp6_seed_corpus.zip "$(dirname "$0")/IN_tcp6" + +find $build -type f -executable -name "fuzz-*" -exec mv {} $OUT \; +find $build -type f -name "*.options" -exec mv {} $OUT \; diff --git a/src/net/libslirp/fuzzing/reproducer.c b/src/net/libslirp/fuzzing/reproducer.c new file mode 100644 index 00000000..49704af7 --- /dev/null +++ b/src/net/libslirp/fuzzing/reproducer.c @@ -0,0 +1,45 @@ +#ifdef _WIN32 +/* as defined in sdkddkver.h */ +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 /* Vista */ +#endif +#include +#endif + +#include +#include +#include "../src/libslirp.h" +#include "helper.h" + +#define MIN_NUMBER_OF_RUNS 1 +#define EXIT_TEST_SKIP 77 + +extern int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); + +int main(int argc, char **argv) +{ + int i, j; + + for (i = 1; i < argc; i++) { + GError *err = NULL; + char *name = argv[i]; + char *buf; + size_t size; + + if (!g_file_get_contents(name, &buf, &size, &err)) { + g_warning("Failed to read '%s': %s", name, err->message); + g_clear_error(&err); + return EXIT_FAILURE; + } + + g_print("%s...\n", name); + for (j = 0; j < MIN_NUMBER_OF_RUNS; j++) { + if (LLVMFuzzerTestOneInput((void *)buf, size) == EXIT_TEST_SKIP) { + return EXIT_TEST_SKIP; + } + } + g_free(buf); + } + + return EXIT_SUCCESS; +} diff --git a/src/net/libslirp/fuzzing/slirp_base_fuzz.h b/src/net/libslirp/fuzzing/slirp_base_fuzz.h new file mode 100644 index 00000000..05b32d66 --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_base_fuzz.h @@ -0,0 +1,23 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" + +/* Structure for the fuzzers */ +typedef struct pcap_hdr_s { + guint32 magic_number; /* magic number */ + guint16 version_major; /* major version number */ + guint16 version_minor; /* minor version number */ + gint32 thiszone; /* GMT to local correction */ + guint32 sigfigs; /* accuracy of timestamps */ + guint32 snaplen; /* max length of captured packets, in octets */ + guint32 network; /* data link type */ +} pcap_hdr_t; + +typedef struct pcaprec_hdr_s { + guint32 ts_sec; /* timestamp seconds */ + guint32 ts_usec; /* timestamp microseconds */ + guint32 incl_len; /* number of octets of packet saved in file */ + guint32 orig_len; /* actual length of packet */ +} pcaprec_hdr_t; \ No newline at end of file diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_arp.c b/src/net/libslirp/fuzzing/slirp_fuzz_arp.c new file mode 100644 index 00000000..c403e164 --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_arp.c @@ -0,0 +1,90 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *arp_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + arp_data = Data_ptr + 14; + + uint8_t Data_to_mutate[MaxSize]; + uint16_t arp_size = rec->incl_len - 14; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the ip header, maybe the IPs or + // total length should be excluded ? + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, arp_data, arp_size); + + // Call to libfuzzer's mutation function. + // For now we dont want to change the header size as it would require to + // resize the `Data` array to include the new bytes inside the whole + // packet. + // This should be easy as LibFuzzer probably does it by itself or + // reserved enough space in Data beforehand, needs some research to + // confirm. + // FIXME: allow up to grow header size to 60 bytes, + // requires to update the `header length` before calculating + // checksum + LLVMFuzzerMutate(Data_to_mutate, arp_size, arp_size); + + // Copy the mutated data back to the `Data` array + memcpy(arp_data, Data_to_mutate, arp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_icmp.c b/src/net/libslirp/fuzzing/slirp_fuzz_icmp.c new file mode 100644 index 00000000..8a422c4b --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_icmp.c @@ -0,0 +1,129 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + uint32_t ipsource; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 16) { + ipsource = * (uint32_t*) (ip_data + 12); + + // This an answer, which we will produce, so don't mutate + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + + // Exclude packets that are not ICMP from the mutation strategy + if (ip_data[9] != IPPROTO_ICMP) + continue; + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl = (ip_data[0] & 0xF); + uint8_t ip_hl_in_bytes = ip_hl * 4; /* ip header length */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_icmp = ip_data + ip_hl_in_bytes; + uint16_t total_length = + ntohs(*((uint16_t *)ip_data + 1)); // network order to host order + uint16_t icmp_size = + (total_length - ip_hl_in_bytes); /* total length -> is stored at the + offset 2 in the header */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use udp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (icmp_size > MaxSize || icmp_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in icmp + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, start_of_icmp, icmp_size); + + // Call to libfuzzer's mutation function. + // For now we dont want to change the header size as it would require to + // resize the `Data` array to include the new bytes inside the whole + // packet. + // This should be easy as LibFuzzer probably does it by itself or + // reserved enough space in Data beforehand, needs some research to + // confirm. + // FIXME: allow up to grow header size to 60 bytes, + // requires to update the `header length` before calculating + // checksum + LLVMFuzzerMutate(Data_to_mutate, icmp_size, icmp_size); + + // Set the `checksum` field to 0 and calculate the new checksum + *(uint16_t *)(Data_to_mutate + 2) = 0; + uint16_t new_checksum = + compute_checksum(Data_to_mutate, icmp_size); + *(uint16_t *)(Data_to_mutate + 2) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(start_of_icmp, Data_to_mutate, icmp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_icmp6.c b/src/net/libslirp/fuzzing/slirp_fuzz_icmp6.c new file mode 100644 index 00000000..bbe041c9 --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_icmp6.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (ip_data + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + + // Exclude packets that are not ICMP from the mutation strategy + if (ip_data[6] != IPPROTO_ICMPV6) + continue; + + // Allocate a bit more than needed, this is useful for + // checksum calculation. + uint8_t Data_to_mutate[MaxSize + PSEUDO_IPV6_SIZE]; + uint8_t ip_hl_in_bytes = sizeof(struct ip6); /* ip header length */ + + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_icmp = ip_data + ip_hl_in_bytes; + uint16_t icmp_size = ntohs(*(uint16_t *)(ip_data + 4)); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use udp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (icmp_size > MaxSize || icmp_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in icmp + memset(Data_to_mutate, 0, MaxSize + PSEUDO_IPV6_SIZE); + memcpy(Data_to_mutate, start_of_icmp, icmp_size); + + // Call to libfuzzer's mutation function. + // For now we dont want to change the header size as it would require to + // resize the `Data` array to include the new bytes inside the whole + // packet. + // This should be easy as LibFuzzer probably does it by itself or + // reserved enough space in Data beforehand, needs some research to + // confirm. + // FIXME: allow up to grow header size to 60 bytes, + // requires to update the `header length` before calculating + // checksum + LLVMFuzzerMutate(Data_to_mutate, icmp_size, icmp_size); + + // Set the `checksum` field to 0 and calculate the new checksum + *(uint16_t *)(Data_to_mutate + 2) = 0; + // Copy the source and destination IP addresses, the tcp length and + // protocol number at the end of the `Data_to_mutate` array to calculate + // the new checksum. + memcpy(Data_to_mutate + icmp_size, ip_data + 8, 16*2); + + *(Data_to_mutate + icmp_size + 16*2 + 1) = IPPROTO_ICMPV6; + + *(Data_to_mutate + icmp_size + 16*2 + 2) = (uint8_t)(icmp_size / 256); + *(Data_to_mutate + icmp_size + 16*2 + 3) = (uint8_t)(icmp_size % 256); + + uint16_t new_checksum = + compute_checksum(Data_to_mutate, icmp_size + PSEUDO_IPV6_SIZE); + *(uint16_t *)(Data_to_mutate + 2) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(start_of_icmp, Data_to_mutate, icmp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_ip6_header.c b/src/net/libslirp/fuzzing/slirp_fuzz_ip6_header.c new file mode 100644 index 00000000..4714f92d --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_ip6_header.c @@ -0,0 +1,103 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (ip_data + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl_in_bytes = sizeof(struct ip6); /* ip header length */ + + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the ip header, maybe the IPs or + // total length should be excluded ? + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, ip_data, ip_hl_in_bytes); + + // Call to libfuzzer's mutation function. + // For now we dont want to change the header size as it would require to + // resize the `Data` array to include the new bytes inside the whole + // packet. + // This should be easy as LibFuzzer probably does it by itself or + // reserved enough space in Data beforehand, needs some research to + // confirm. + // FIXME: allow up to grow header size to 60 bytes, + // requires to update the `header length` before calculating + // checksum + LLVMFuzzerMutate(Data_to_mutate, ip_hl_in_bytes, ip_hl_in_bytes); + + // Copy the mutated data back to the `Data` array + memcpy(ip_data, Data_to_mutate, ip_hl_in_bytes); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_ip_header.c b/src/net/libslirp/fuzzing/slirp_fuzz_ip_header.c new file mode 100644 index 00000000..fe07540e --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_ip_header.c @@ -0,0 +1,112 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + uint32_t ipsource; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 16) { + ipsource = * (uint32_t*) (ip_data + 12); + + // This an answer, which we will produce, so don't mutate + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl = (ip_data[0] & 0xF); + uint8_t ip_hl_in_bytes = ip_hl * 4; /* ip header length */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > MaxSize || ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the ip header, maybe the IPs or + // total length should be excluded ? + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, ip_data, ip_hl_in_bytes); + + // Call to libfuzzer's mutation function. + // For now we dont want to change the header size as it would require to + // resize the `Data` array to include the new bytes inside the whole + // packet. + // This should be easy as LibFuzzer probably does it by itself or + // reserved enough space in Data beforehand, needs some research to + // confirm. + // FIXME: allow up to grow header size to 60 bytes, + // requires to update the `header length` before calculating + // checksum + LLVMFuzzerMutate(Data_to_mutate, ip_hl_in_bytes, ip_hl_in_bytes); + + // Set the `checksum` field to 0 and calculate the new checksum + *(uint16_t *)(Data_to_mutate + 10) = 0; + uint16_t new_checksum = + compute_checksum(Data_to_mutate, ip_hl_in_bytes); + *(uint16_t *)(Data_to_mutate + 10) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(ip_data, Data_to_mutate, ip_hl_in_bytes); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_tcp.c b/src/net/libslirp/fuzzing/slirp_fuzz_tcp.c new file mode 100644 index 00000000..461d4304 --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_tcp.c @@ -0,0 +1,138 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + uint32_t ipsource; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 16) { + ipsource = * (uint32_t*) (ip_data + 12); + + // This an answer, which we will produce, so don't mutate + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + + // Exclude packets that are not TCP from the mutation strategy + if (ip_data[9] != IPPROTO_TCP) + continue; + + // Allocate a bit more than needed, this is useful for + // checksum calculation. + uint8_t Data_to_mutate[MaxSize + PSEUDO_IP_SIZE]; + uint8_t ip_hl = (ip_data[0] & 0xF); + uint8_t ip_hl_in_bytes = ip_hl * 4; /* ip header length */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_tcp = ip_data + ip_hl_in_bytes; + uint16_t total_length = ntohs(*((uint16_t *)ip_data + 1)); + uint16_t tcp_size = (total_length - (uint16_t)ip_hl_in_bytes); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use tcp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (tcp_size > MaxSize || tcp_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the tcp packet + memset(Data_to_mutate, 0, MaxSize + PSEUDO_IP_SIZE); + memcpy(Data_to_mutate, start_of_tcp, tcp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole TCP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the TCP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate, tcp_size, tcp_size); + + // Set the `checksum` field to 0 to calculate the new checksum + + *(uint16_t *)(Data_to_mutate + 16) = (uint16_t)0; + // Copy the source and destination IP addresses, the tcp length and + // protocol number at the end of the `Data_to_mutate` array to calculate + // the new checksum. + memcpy(Data_to_mutate + tcp_size, ip_data + 12, 4*2); + + *(Data_to_mutate + tcp_size + 9) = IPPROTO_TCP; + + *(Data_to_mutate + tcp_size + 10) = (uint8_t)(tcp_size / 256); + *(Data_to_mutate + tcp_size + 11) = (uint8_t)(tcp_size % 256); + + uint16_t new_checksum = + compute_checksum(Data_to_mutate, tcp_size + PSEUDO_IP_SIZE); + *(uint16_t *)(Data_to_mutate + 16) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(start_of_tcp, Data_to_mutate, tcp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_tcp6.c b/src/net/libslirp/fuzzing/slirp_fuzz_tcp6.c new file mode 100644 index 00000000..64ad9032 --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_tcp6.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (ip_data + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + + // Exclude packets that are not TCP from the mutation strategy + if (ip_data[6] != IPPROTO_TCP) + continue; + + // Allocate a bit more than needed, this is useful for + // checksum calculation. + uint8_t Data_to_mutate[MaxSize + PSEUDO_IPV6_SIZE]; + uint8_t ip_hl_in_bytes = sizeof(struct ip6); /* ip header length */ + + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_tcp = ip_data + ip_hl_in_bytes; + uint16_t tcp_size = ntohs(*(uint16_t *)(ip_data + 4)); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use tcp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (tcp_size > MaxSize || tcp_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the tcp packet + memset(Data_to_mutate, 0, MaxSize + PSEUDO_IPV6_SIZE); + memcpy(Data_to_mutate, start_of_tcp, tcp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole TCP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the TCP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate, tcp_size, tcp_size); + + // Set the `checksum` field to 0 to calculate the new checksum + + *(uint16_t *)(Data_to_mutate + 16) = (uint16_t)0; + // Copy the source and destination IP addresses, the tcp length and + // protocol number at the end of the `Data_to_mutate` array to calculate + // the new checksum. + memcpy(Data_to_mutate + tcp_size, ip_data + 8, 16*2); + + *(Data_to_mutate + tcp_size + 16*2 + 1) = IPPROTO_TCP; + + *(Data_to_mutate + tcp_size + 16*2 + 2) = (uint8_t)(tcp_size / 256); + *(Data_to_mutate + tcp_size + 16*2 + 3) = (uint8_t)(tcp_size % 256); + + uint16_t new_checksum = + compute_checksum(Data_to_mutate, tcp_size + PSEUDO_IPV6_SIZE); + *(uint16_t *)(Data_to_mutate + 16) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(start_of_tcp, Data_to_mutate, tcp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_tcp6_data.c b/src/net/libslirp/fuzzing/slirp_fuzz_tcp6_data.c new file mode 100644 index 00000000..87982b0c --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_tcp6_data.c @@ -0,0 +1,137 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (ip_data + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + + // Exclude packets that are not TCP from the mutation strategy + if (ip_data[6] != IPPROTO_TCP) + continue; + + // Allocate a bit more than needed, this is useful for + // checksum calculation. + uint8_t Data_to_mutate[MaxSize + PSEUDO_IPV6_SIZE]; + uint8_t ip_hl_in_bytes = sizeof(struct ip6); /* ip header length */ + + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_tcp = ip_data + ip_hl_in_bytes; + uint8_t tcp_header_size = 4 * (*(start_of_tcp + 12) >> 4); + uint8_t *start_of_data = ip_data + ip_hl_in_bytes + tcp_header_size; + uint16_t tcp_size = ntohs(*(uint16_t *)(ip_data + 4)); + uint16_t tcp_data_size = (tcp_size - (uint16_t)tcp_header_size); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use tcp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (tcp_data_size > MaxSize || tcp_data_size > rec->incl_len - 14 - ip_hl_in_bytes - tcp_header_size) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the tcp packet + memset(Data_to_mutate, 0, MaxSize + PSEUDO_IPV6_SIZE); + memcpy(Data_to_mutate, start_of_tcp, tcp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole TCP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the TCP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate + tcp_header_size, tcp_data_size, tcp_data_size); + + // Set the `checksum` field to 0 to calculate the new checksum + + *(uint16_t *)(Data_to_mutate + 16) = (uint16_t)0; + // Copy the source and destination IP addresses, the tcp length and + // protocol number at the end of the `Data_to_mutate` array to calculate + // the new checksum. + memcpy(Data_to_mutate + tcp_size, ip_data + 8, 16*2); + + *(Data_to_mutate + tcp_size + 16*2 + 1) = IPPROTO_TCP; + + *(Data_to_mutate + tcp_size + 16*2 + 2) = (uint8_t)(tcp_size / 256); + *(Data_to_mutate + tcp_size + 16*2 + 3) = (uint8_t)(tcp_size % 256); + + uint16_t new_checksum = + compute_checksum(Data_to_mutate, tcp_size + PSEUDO_IPV6_SIZE); + *(uint16_t *)(Data_to_mutate + 16) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(start_of_tcp, Data_to_mutate, tcp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_tcp6_header.c b/src/net/libslirp/fuzzing/slirp_fuzz_tcp6_header.c new file mode 100644 index 00000000..e6c8b774 --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_tcp6_header.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (ip_data + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + + // Exclude packets that are not TCP from the mutation strategy + if (ip_data[6] != IPPROTO_TCP) + continue; + + // Allocate a bit more than needed, this is useful for + // checksum calculation. + uint8_t Data_to_mutate[MaxSize + PSEUDO_IPV6_SIZE]; + uint8_t ip_hl_in_bytes = sizeof(struct ip6); /* ip header length */ + + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_tcp = ip_data + ip_hl_in_bytes; + uint8_t tcp_header_size = (*(start_of_tcp + 12) >> 4) * 4; + uint16_t tcp_size = ntohs(*(uint16_t *)(ip_data + 4)); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use tcp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (tcp_size > MaxSize || tcp_size > rec->incl_len - 14 - ip_hl_in_bytes || + tcp_header_size > MaxSize || tcp_header_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the tcp packet + memset(Data_to_mutate, 0, MaxSize + PSEUDO_IPV6_SIZE); + memcpy(Data_to_mutate, start_of_tcp, tcp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole TCP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the TCP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate, tcp_header_size, tcp_header_size); + + // Set the `checksum` field to 0 to calculate the new checksum + + *(uint16_t *)(Data_to_mutate + 16) = (uint16_t)0; + // Copy the source and destination IP addresses, the tcp length and + // protocol number at the end of the `Data_to_mutate` array to calculate + // the new checksum. + memcpy(Data_to_mutate + tcp_size, ip_data + 8, 16*2); + + *(Data_to_mutate + tcp_size + 16*2 + 1) = IPPROTO_TCP; + + *(Data_to_mutate + tcp_size + 16*2 + 2) = (uint8_t)(tcp_size / 256); + *(Data_to_mutate + tcp_size + 16*2 + 3) = (uint8_t)(tcp_size % 256); + + uint16_t new_checksum = + compute_checksum(Data_to_mutate, tcp_size + PSEUDO_IPV6_SIZE); + *(uint16_t *)(Data_to_mutate + 16) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(start_of_tcp, Data_to_mutate, tcp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_tcp_data.c b/src/net/libslirp/fuzzing/slirp_fuzz_tcp_data.c new file mode 100644 index 00000000..05971c09 --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_tcp_data.c @@ -0,0 +1,141 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + uint32_t ipsource; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 16) { + ipsource = * (uint32_t*) (ip_data + 12); + + // This an answer, which we will produce, so don't mutate + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + + // Exclude packets that are not TCP from the mutation strategy + if (ip_data[9] != IPPROTO_TCP) + continue; + + // Allocate a bit more than needed, this is useful for + // checksum calculation. + uint8_t Data_to_mutate[MaxSize + PSEUDO_IP_SIZE]; + uint8_t ip_hl = (ip_data[0] & 0xF); + uint8_t ip_hl_in_bytes = ip_hl * 4; /* ip header length */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_tcp = ip_data + ip_hl_in_bytes; + uint8_t tcp_header_size = 4 * (*(start_of_tcp + 12) >> 4); + uint8_t *start_of_data = ip_data + ip_hl_in_bytes + tcp_header_size; + uint16_t total_length = ntohs(*((uint16_t *)ip_data + 1)); + uint16_t tcp_size = (total_length - (uint16_t)ip_hl_in_bytes); + uint16_t tcp_data_size = (tcp_size - (uint16_t)tcp_header_size); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use tcp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (tcp_data_size > MaxSize || tcp_data_size > rec->incl_len - 14 - ip_hl_in_bytes - tcp_header_size) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the tcp packet + memset(Data_to_mutate, 0, MaxSize + PSEUDO_IP_SIZE); + memcpy(Data_to_mutate, start_of_tcp, tcp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole TCP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the TCP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate + tcp_header_size, tcp_data_size, tcp_data_size); + + // Set the `checksum` field to 0 to calculate the new checksum + + *(uint16_t *)(Data_to_mutate + 16) = (uint16_t)0; + // Copy the source and destination IP addresses, the tcp length and + // protocol number at the end of the `Data_to_mutate` array to calculate + // the new checksum. + memcpy(Data_to_mutate + tcp_size, ip_data + 12, 4*2); + + *(Data_to_mutate + tcp_size + 9) = IPPROTO_TCP; + + *(Data_to_mutate + tcp_size + 10) = (uint8_t)(tcp_size / 256); + *(Data_to_mutate + tcp_size + 11) = (uint8_t)(tcp_size % 256); + + uint16_t new_checksum = + compute_checksum(Data_to_mutate, tcp_size + PSEUDO_IP_SIZE); + *(uint16_t *)(Data_to_mutate + 16) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(start_of_tcp, Data_to_mutate, tcp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_tcp_header.c b/src/net/libslirp/fuzzing/slirp_fuzz_tcp_header.c new file mode 100644 index 00000000..96904ab2 --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_tcp_header.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + uint32_t ipsource; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 16) { + ipsource = * (uint32_t*) (ip_data + 12); + + // This an answer, which we will produce, so don't mutate + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + + // Exclude packets that are not TCP from the mutation strategy + if (ip_data[9] != IPPROTO_TCP) + continue; + + // Allocate a bit more than needed, this is useful for + // checksum calculation. + uint8_t Data_to_mutate[MaxSize + PSEUDO_IP_SIZE]; + uint8_t ip_hl = (ip_data[0] & 0xF); + uint8_t ip_hl_in_bytes = ip_hl * 4; /* ip header length */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_tcp = ip_data + ip_hl_in_bytes; + uint8_t tcp_header_size = (*(start_of_tcp + 12) >> 4) * 4; + uint16_t total_length = ntohs(*((uint16_t *)ip_data + 1)); + uint16_t tcp_size = (total_length - (uint16_t)ip_hl_in_bytes); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use tcp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (tcp_size > MaxSize || tcp_size > rec->incl_len - 14 - ip_hl_in_bytes || + tcp_header_size > MaxSize || tcp_header_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the tcp packet + memset(Data_to_mutate, 0, MaxSize + PSEUDO_IP_SIZE); + memcpy(Data_to_mutate, start_of_tcp, tcp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole TCP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the TCP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate, tcp_header_size, tcp_header_size); + + // Set the `checksum` field to 0 to calculate the new checksum + + *(uint16_t *)(Data_to_mutate + 16) = (uint16_t)0; + // Copy the source and destination IP addresses, the tcp length and + // protocol number at the end of the `Data_to_mutate` array to calculate + // the new checksum. + memcpy(Data_to_mutate + tcp_size, ip_data + 12, 4*2); + + *(Data_to_mutate + tcp_size + 9) = IPPROTO_TCP; + + *(Data_to_mutate + tcp_size + 10) = (uint8_t)(tcp_size / 256); + *(Data_to_mutate + tcp_size + 11) = (uint8_t)(tcp_size % 256); + + uint16_t new_checksum = + compute_checksum(Data_to_mutate, tcp_size + PSEUDO_IP_SIZE); + *(uint16_t *)(Data_to_mutate + 16) = htons(new_checksum); + + // Copy the mutated data back to the `Data` array + memcpy(start_of_tcp, Data_to_mutate, tcp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_udp.c b/src/net/libslirp/fuzzing/slirp_fuzz_udp.c new file mode 100644 index 00000000..272258e4 --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_udp.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + uint32_t ipsource; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 16) { + ipsource = * (uint32_t*) (ip_data + 12); + + // This an answer, which we will produce, so don't mutate + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + + // Exclude packets that are not UDP from the mutation strategy + if (ip_data[9] != IPPROTO_UDP) + continue; + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl = (ip_data[0] & 0xF); + uint8_t ip_hl_in_bytes = ip_hl * 4; /* ip header length */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_udp = ip_data + ip_hl_in_bytes; + uint16_t udp_size = ntohs(*(uint16_t *)(start_of_udp + 4)); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use udp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (udp_size > MaxSize || udp_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the udp packet + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, start_of_udp, udp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole UDP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the UDP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate, udp_size, udp_size); + + // Drop checksum + *(uint16_t *)(Data_to_mutate + 6) = 0; + + // Copy the mutated data back to the `Data` array + memcpy(start_of_udp, Data_to_mutate, udp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_udp6.c b/src/net/libslirp/fuzzing/slirp_fuzz_udp6.c new file mode 100644 index 00000000..4b00de75 --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_udp6.c @@ -0,0 +1,120 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (ip_data + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + + // Exclude packets that are not UDP from the mutation strategy + if (ip_data[6] != IPPROTO_UDP) + continue; + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl_in_bytes = sizeof(struct ip6); /* ip header length */ + + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_udp = ip_data + ip_hl_in_bytes; + uint16_t udp_size = ntohs(*(uint16_t *)(ip_data + 4)); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use udp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (udp_size > MaxSize || udp_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the udp packet + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, start_of_udp, udp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole UDP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the UDP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate, udp_size, udp_size); + + // Drop checksum + // Stricto sensu, UDPv6 makes checksums mandatory, but libslirp doesn't + // check that actually + *(uint16_t *)(Data_to_mutate + 6) = 0; + + // Copy the mutated data back to the `Data` array + memcpy(start_of_udp, Data_to_mutate, udp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_udp6_data.c b/src/net/libslirp/fuzzing/slirp_fuzz_udp6_data.c new file mode 100644 index 00000000..83068557 --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_udp6_data.c @@ -0,0 +1,122 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (ip_data + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + + // Exclude packets that are not UDP from the mutation strategy + if (ip_data[6] != IPPROTO_UDP) + continue; + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl_in_bytes = sizeof(struct ip6); /* ip header length */ + + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_udp = ip_data + ip_hl_in_bytes; + uint8_t udp_header_size = 8; + uint16_t udp_size = ntohs(*(uint16_t *)(ip_data + 4)); + uint16_t udp_data_size = (udp_size - (uint16_t)udp_header_size); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use udp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (udp_data_size > MaxSize || udp_data_size > rec->incl_len - 14 - ip_hl_in_bytes - udp_header_size) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the udp packet + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, start_of_udp, udp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole UDP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the UDP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate + udp_header_size, udp_data_size, udp_data_size); + + // Drop checksum + // Stricto sensu, UDPv6 makes checksums mandatory, but libslirp doesn't + // check that actually + *(uint16_t *)(Data_to_mutate + 6) = 0; + + // Copy the mutated data back to the `Data` array + memcpy(start_of_udp, Data_to_mutate, udp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_udp6_header.c b/src/net/libslirp/fuzzing/slirp_fuzz_udp6_header.c new file mode 100644 index 00000000..a2734b46 --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_udp6_header.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "../src/ip6.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 24) { + struct in6_addr *ipsource = (struct in6_addr *) (ip_data + 8); + + // This an answer, which we will produce, so don't receive + if (in6_equal(ipsource, &ip6_host) || in6_equal(ipsource, &ip6_dns)) + continue; + } + + // Exclude packets that are not UDP from the mutation strategy + if (ip_data[6] != IPPROTO_UDP) + continue; + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl_in_bytes = sizeof(struct ip6); /* ip header length */ + + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_udp = ip_data + ip_hl_in_bytes; + uint8_t udp_header_size = 8; + uint16_t udp_size = ntohs(*(uint16_t *)(ip_data + 4)); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use udp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (udp_size > MaxSize || udp_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the udp packet + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, start_of_udp, udp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole UDP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the UDP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate, udp_header_size, udp_header_size); + + // Drop checksum + // Stricto sensu, UDPv6 makes checksums mandatory, but libslirp doesn't + // check that actually + *(uint16_t *)(Data_to_mutate + 6) = 0; + + // Copy the mutated data back to the `Data` array + memcpy(start_of_udp, Data_to_mutate, udp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_udp_data.c b/src/net/libslirp/fuzzing/slirp_fuzz_udp_data.c new file mode 100644 index 00000000..bc8930c9 --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_udp_data.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + uint32_t ipsource; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 16) { + ipsource = * (uint32_t*) (ip_data + 12); + + // This an answer, which we will produce, so don't mutate + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + + // Exclude packets that are not UDP from the mutation strategy + if (ip_data[9] != IPPROTO_UDP) + continue; + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl = (ip_data[0] & 0xF); + uint8_t ip_hl_in_bytes = ip_hl * 4; /* ip header length */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_udp = ip_data + ip_hl_in_bytes; + uint8_t udp_header_size = 8; + uint16_t udp_size = ntohs(*(uint16_t *)(start_of_udp + 4)); + uint16_t udp_data_size = (udp_size - (uint16_t)udp_header_size); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use udp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (udp_data_size > MaxSize || udp_data_size > rec->incl_len - 14 - ip_hl_in_bytes - udp_header_size) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the udp packet + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, start_of_udp, udp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole UDP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the UDP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate + udp_header_size, udp_data_size, udp_data_size); + + // Drop checksum + *(uint16_t *)(Data_to_mutate + 6) = 0; + + // Copy the mutated data back to the `Data` array + memcpy(start_of_udp, Data_to_mutate, udp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/slirp_fuzz_udp_header.c b/src/net/libslirp/fuzzing/slirp_fuzz_udp_header.c new file mode 100644 index 00000000..b2dc2729 --- /dev/null +++ b/src/net/libslirp/fuzzing/slirp_fuzz_udp_header.c @@ -0,0 +1,122 @@ +#include +#include +#include +#include "../src/libslirp.h" +#include "helper.h" +#include "slirp_base_fuzz.h" + +#ifdef CUSTOM_MUTATOR +extern size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed); + +/// This is a custom mutator, this allows us to mutate only specific parts of +/// the input and fix the checksum so the packet isn't rejected for bad reasons. +extern size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) +{ + size_t current_size = Size; + uint8_t *Data_ptr = Data; + uint8_t *ip_data; + uint32_t ipsource; + bool mutated = false; + + pcap_hdr_t *hdr = (void *)Data_ptr; + pcaprec_hdr_t *rec = NULL; + + if (current_size < sizeof(pcap_hdr_t)) { + return 0; + } + + Data_ptr += sizeof(*hdr); + current_size -= sizeof(*hdr); + + if (hdr->magic_number == 0xd4c3b2a1) { + g_debug("FIXME: byteswap fields"); + return 0; + } /* else assume native pcap file */ + if (hdr->network != 1) { + return 0; + } + + for ( ; current_size > sizeof(*rec); Data_ptr += rec->incl_len, current_size -= rec->incl_len) { + rec = (void *)Data_ptr; + Data_ptr += sizeof(*rec); + current_size -= sizeof(*rec); + + if (rec->incl_len != rec->orig_len) { + return 0; + } + if (rec->incl_len > current_size) { + return 0; + } + if (rec->incl_len < 14 + 1) { + return 0; + } + + ip_data = Data_ptr + 14; + + if (rec->incl_len >= 14 + 16) { + ipsource = * (uint32_t*) (ip_data + 12); + + // This an answer, which we will produce, so don't mutate + if (ipsource == htonl(0x0a000202) || ipsource == htonl(0x0a000203)) + continue; + } + + // Exclude packets that are not UDP from the mutation strategy + if (ip_data[9] != IPPROTO_UDP) + continue; + + uint8_t Data_to_mutate[MaxSize]; + uint8_t ip_hl = (ip_data[0] & 0xF); + uint8_t ip_hl_in_bytes = ip_hl * 4; /* ip header length */ + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use ip_hl_in_bytes inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (ip_hl_in_bytes > rec->incl_len - 14) + return 0; + + uint8_t *start_of_udp = ip_data + ip_hl_in_bytes; + uint8_t udp_header_size = 8; + uint16_t udp_size = ntohs(*(uint16_t *)(start_of_udp + 4)); + + // The size inside the packet can't be trusted, if it is too big it can + // lead to heap overflows in the fuzzing code. + // Fixme : don't use udp_size inside the fuzzing code, maybe use the + // rec->incl_len and manually calculate the size. + if (udp_size > MaxSize || udp_size > rec->incl_len - 14 - ip_hl_in_bytes) + return 0; + + // Copy interesting data to the `Data_to_mutate` array + // here we want to fuzz everything in the udp packet + memset(Data_to_mutate, 0, MaxSize); + memcpy(Data_to_mutate, start_of_udp, udp_size); + + // Call to libfuzzer's mutation function. + // Pass the whole UDP packet, mutate it and then fix checksum value + // so the packet isn't rejected. + // The new size of the data is returned by LLVMFuzzerMutate. + // Fixme: allow to change the size of the UDP packet, this will require + // to fix the size before calculating the new checksum and change + // how the Data_ptr is advanced. + // Most offsets bellow should be good for when the switch will be + // done to avoid overwriting new/mutated data. + LLVMFuzzerMutate(Data_to_mutate, udp_header_size, udp_header_size); + + // Drop checksum + *(uint16_t *)(Data_to_mutate + 6) = 0; + + // Copy the mutated data back to the `Data` array + memcpy(start_of_udp, Data_to_mutate, udp_size); + + mutated = true; + } + + if (!mutated) + return 0; + + return Size; +} +#endif // CUSTOM_MUTATOR diff --git a/src/net/libslirp/fuzzing/tftp/toto b/src/net/libslirp/fuzzing/tftp/toto new file mode 100644 index 00000000..0b70c57e Binary files /dev/null and b/src/net/libslirp/fuzzing/tftp/toto differ diff --git a/src/net/libslirp/glib/glib.c b/src/net/libslirp/glib/glib.c new file mode 100644 index 00000000..16bc9dcc --- /dev/null +++ b/src/net/libslirp/glib/glib.c @@ -0,0 +1,132 @@ +// +// Created by nhp on 14-05-24. +// + +#include +#include +#include +#include +#include + +#include "glib.h" + +GString* g_string_new(gchar* initial) { + GString* str = g_new0(GString, 1); + + if (initial != NULL) { + int len = strlen(initial); + gchar* p = malloc(len); + memcpy(p, initial, len); + str->str = p; + str->len = len; + str->allocated_len = len; + } else { + gchar* p = malloc(64); + str->str = p; + str->len = 0; + str->allocated_len = 64; + } + return str; +} + +gchar* g_string_free(GString* str, gboolean free_segment) { + char* seg = str->str; + free(str); + if (free_segment) { + free(str->str); + return NULL; + } + return seg; +} + +void g_string_append_printf(GString* str, const gchar* format, ...) { + va_list args; + va_start(args, format); + int need_len = vsnprintf(NULL, 0, format, args); + va_end(args); + + if (str->len + need_len + 1 < str->allocated_len) { + gsize new_len = str->len + need_len + 1; + gchar* newp = realloc(str->str, new_len); + str->str = newp; + str->allocated_len = new_len; + str->len = new_len - 1; + } + + gchar* temp = malloc(need_len + 1); + va_start(args, format); + vsnprintf(temp, need_len, format, args); + va_end(args); + + strcat(str->str, temp); + free(temp); +} + +gchar* g_strstr_len(const gchar* haystack, int len, const gchar* needle) { + if (len == -1) return strstr(haystack, needle); + size_t needle_len = strlen(needle); + for (int i = 0; i < len; i++) { + size_t found = 0; + for (int j = i; j < len; j++) { + if (haystack[j] == needle[j - i]) found++; + else break; + if (found == needle_len) return (gchar*) haystack + j; + } + } + return NULL; +} + +gchar* g_strdup(const gchar* str) { + if (str == NULL) return NULL; + else return strdup(str); +} + +int g_strv_length(GStrv strings) { + gint count = 0; + while (strings[count]) + count++; + return count; +} + +void g_strfreev(GStrv strings) { + for (int i = 0; strings[i] != NULL; i++) { + free(strings[i]); + } +} + +// This is not good but all we're using slirp for is beaming pokemans over the internet so it's probably okay +gint g_rand_int_range(GRand* grand, gint min, gint max) { + double r = (double) rand(); + double range = (double) (max - min); + double r2 = (r / (double) RAND_MAX) * range; + return MIN(max, ((int) r2) + min); +} + +GRand* g_rand_new() { + return malloc(sizeof(GRand)); +} + +void g_rand_free(GRand* rand) { + free(rand); +} + +void g_error_free(GError* error) { + free(error); +} + +gboolean g_shell_parse_argv(const gchar* command_line, gint* argcp, gchar*** argvp, GError** error) { + const gchar* message = "Unimplemented."; + GError* err = malloc(sizeof(GError)); + err->message = message; + *error = err; + return false; +} + +gboolean g_spawn_async_with_fds(const gchar *working_directory, gchar **argv, + gchar **envp, GSpawnFlags flags, + GSpawnChildSetupFunc child_setup, + gpointer user_data, GPid *child_pid, gint stdin_fd, + gint stdout_fd, gint stderr_fd, GError **error) +{ + return false; +} diff --git a/src/net/libslirp/glib/glib.h b/src/net/libslirp/glib/glib.h new file mode 100644 index 00000000..3563f7f5 --- /dev/null +++ b/src/net/libslirp/glib/glib.h @@ -0,0 +1,162 @@ +#ifndef GLIB_SHIM_H +#define GLIB_SHIM_H + +#include +#include +#include +#include +#include +#include +#include + +#if defined(WIN32) || defined(_WIN32) || defined(_MSC_VER) +#define G_OS_WIN32 1 +#endif + +#define G_LITTLE_ENDIAN 0 +#define G_BIG_ENDIAN 1 +#define G_BYTE_ORDER G_LITTLE_ENDIAN + +#define GUINT16_FROM_BE(n) ntohs(n) +#define GUINT16_TO_BE(n) htons(n) +#define GUINT32_FROM_BE(n) ntohl(n) +#define GUINT32_TO_BE(n) htonl(n) + +#define GINT16_TO_BE(n) (int16_t) htons(n) +#define GINT16_FROM_BE(n) (int16_t) ntohs(n) +#define GINT32_TO_BE(n) (int32_t) htonl(n) +#define GINT32_FROM_BE(n) (int32_t) ntohl(n) + +#define G_N_ELEMENTS(arr) (sizeof(arr) / sizeof(arr[0])) + +#define G_GNUC_PRINTF(x, y) + +#define GLIB_CHECK_VERSION(x, y, z) 1 +#define G_STATIC_ASSERT(...) +#define g_assert assert +#define G_UNLIKELY(x) __builtin_expect(x, 0) + +#define g_return_if_fail(expr) \ + do { \ + if (!(expr)) \ + return; \ + } while (false) + +#define g_return_val_if_fail(expr, val) \ + do { \ + if (!(expr)) \ + return (val); \ + } while (false) + +#define g_warn_if_reached() \ + do { \ + g_warning("g_warn_if_reached: Reached " __FILE__ ":%d", __LINE__); \ + } while (false) + + +#define g_warn_if_fail(expr) \ + do { \ + if (!(expr)) \ + g_warning("g_warn_if_fail: Expression '" #expr "' failed at " __FILE__ ":%d", __LINE__); \ + } while (false) + +#define g_assert_not_reached() \ + do { \ + assert(false && "g_assert_not_reached"); \ + __builtin_unreachable(); \ + } while (false) + +#define GLIB_SIZEOF_VOID_P 8 +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef TRUE +#define TRUE true +#endif +#ifndef FALSE +#define FALSE false +#endif + +typedef bool gboolean; +typedef char gchar; +typedef int gint; +typedef size_t gsize; +typedef void* gpointer; + +#define g_debug(format, ...) printf("[" G_LOG_DOMAIN ": debug] " format, ##__VA_ARGS__) +#define g_warning(format, ...) printf("[" G_LOG_DOMAIN ": warning] " format, ##__VA_ARGS__) +#define g_error(format, ...) printf("[" G_LOG_DOMAIN ": error] " format, ##__VA_ARGS__) +#define g_critical(format, ...) printf("[" G_LOG_DOMAIN ": critical] " format, ##__VA_ARGS__) + +#define g_new(type, count) (type*) (count > 0 ? malloc(sizeof(type) * count) : NULL) +#define g_new0(type, count) (type*) (count > 0 ? calloc(count, sizeof(type)) : NULL) + +#define g_malloc malloc +#define g_malloc0(size) calloc(1, size) +#define g_realloc realloc +#define g_free free + +#define g_getenv(var) getenv(var) + +typedef struct GString { + gchar* str; + gsize len; + gsize allocated_len; +} GString; + +typedef gchar** GStrv; + +GString* g_string_new(gchar* initial); +gchar* g_string_free(GString* string, gboolean free_segment); +void g_string_append_printf(GString* gstr, const gchar* format, ...); +gchar* g_strstr_len(const gchar* haystack, int len, const gchar* needle); +gchar* g_strdup(const gchar* str); +#ifdef _MSC_VER +#define g_ascii_strcasecmp(a, b) stricmp(a, b) +#else +#define g_ascii_strcasecmp(a, b) strcasecmp(a, b) +#endif + +#define g_str_has_prefix(str, pfx) (strncmp(str, pfx, strlen(pfx)) == 0) +#define g_snprintf snprintf +#define g_vsnprintf vsnprintf + +gint g_strv_length(GStrv strings); +void g_strfreev(GStrv strings); + +typedef uint32_t GRand; +gint g_rand_int_range(GRand* grand, gint min, gint max); +GRand* g_rand_new(); +void g_rand_free(GRand* rand); + +typedef struct GError { + const gchar* message; +} GError; + +void g_error_free(GError* error); +#define g_strerror(err) strerror(err) + +typedef void (*GSpawnChildSetupFunc)(gpointer ptr); +typedef enum GSpawnFlags { + G_SPAWN_SEARCH_PATH +} GSpawnFlags; + +typedef gint GPid; + +gboolean g_shell_parse_argv(const gchar* command_line, gint* argcp, gchar*** argvp, GError** error); + +gboolean g_spawn_async_with_fds(const gchar *working_directory, gchar **argv, + gchar **envp, GSpawnFlags flags, + GSpawnChildSetupFunc child_setup, + gpointer user_data, GPid *child_pid, gint stdin_fd, + gint stdout_fd, gint stderr_fd, GError **error); + +typedef struct { gchar* key; int value; } GDebugKey; +#define g_parse_debug_string(str, keys, nkeys) 0 + + +#endif diff --git a/src/net/libslirp/meson.build b/src/net/libslirp/meson.build new file mode 100644 index 00000000..29dc0ad8 --- /dev/null +++ b/src/net/libslirp/meson.build @@ -0,0 +1,248 @@ +project('libslirp', 'c', + version : '4.8.0', + license : 'BSD-3-Clause', + default_options : ['warning_level=1', 'c_std=gnu99'], + meson_version : '>= 0.50', +) + +version = meson.project_version() +varr = version.split('.') +major_version = varr[0] +minor_version = varr[1] +micro_version = varr[2] + +conf = configuration_data() +conf.set('SLIRP_MAJOR_VERSION', major_version) +conf.set('SLIRP_MINOR_VERSION', minor_version) +conf.set('SLIRP_MICRO_VERSION', micro_version) + +want_ossfuzz = get_option('oss-fuzz') +want_libfuzzer = get_option('llvm-fuzz') +fuzz_reproduce = get_option('fuzz-reproduce') +if want_ossfuzz and want_libfuzzer + error('only one of oss-fuzz and llvm-fuzz can be specified') +endif +fuzzer_build = want_ossfuzz or want_libfuzzer +if fuzzer_build and fuzz_reproduce + error('fuzzer build and reproducer build are mutually exclusive') +endif + +cc = meson.get_compiler('c') +add_languages('cpp', required : fuzzer_build) + +if get_option('static') == true + add_global_arguments('-static', language : 'c') +endif + +if cc.get_argument_syntax() != 'msvc' + r = run_command('build-aux/git-version-gen', + '@0@/.tarball-version'.format(meson.current_source_dir()), + check : false) + + full_version = r.stdout().strip() + if r.returncode() != 0 or full_version.startswith('UNKNOWN') + full_version = meson.project_version() + elif not full_version.startswith(meson.project_version()) + error('meson.build project version @0@ does not match git-describe output @1@' + .format(meson.project_version(), full_version)) + endif +else + full_version = meson.project_version() +endif +conf.set_quoted('SLIRP_VERSION_STRING', full_version + get_option('version_suffix')) + +# libtool versioning - this applies to libslirp +# +# See http://sources.redhat.com/autobook/autobook/autobook_91.html#SEC91 for details +# +# - If interfaces have been changed or added, but binary compatibility +# has been preserved, change: +# CURRENT += 1 +# REVISION = 0 +# AGE += 1 +# - If binary compatibility has been broken (eg removed or changed +# interfaces), change: +# CURRENT += 1 +# REVISION = 0 +# AGE = 0 +# - If the interface is the same as the previous version, but bugs are +# fixed, change: +# REVISION += 1 +lt_current = 4 +lt_revision = 0 +lt_age = 4 +lt_version = '@0@.@1@.@2@'.format(lt_current - lt_age, lt_age, lt_revision) + +host_system = host_machine.system() + +#glib_dep = dependency('glib-2.0', static : get_option('static')) + +glib_dep = declare_dependency( + include_directories: [include_directories('glib', is_system: true)], + sources: ['glib/glib.c'] +) + +add_project_arguments(cc.get_supported_arguments('-Wmissing-prototypes', '-Wstrict-prototypes', + '-Wredundant-decls', '-Wundef', '-Wwrite-strings'), + language: 'c', native: false) + +platform_deps = [] + +if host_system == 'windows' + platform_deps += [ + cc.find_library('ws2_32'), + cc.find_library('iphlpapi') + ] +elif host_system == 'darwin' + platform_deps += [ + cc.find_library('resolv') + ] +elif host_system == 'haiku' + platform_deps += [ + cc.find_library('network') + ] +endif + +cargs = [ + '-DG_LOG_DOMAIN="Slirp"', + '-DBUILDING_LIBSLIRP', +] + +if cc.check_header('valgrind/valgrind.h') + cargs += [ '-DHAVE_VALGRIND=1' ] +endif + +sources = [ + 'src/arp_table.c', + 'src/bootp.c', + 'src/cksum.c', + 'src/dhcpv6.c', + 'src/dnssearch.c', + 'src/if.c', + 'src/ip6_icmp.c', + 'src/ip6_input.c', + 'src/ip6_output.c', + 'src/ip_icmp.c', + 'src/ip_input.c', + 'src/ip_output.c', + 'src/mbuf.c', + 'src/misc.c', + 'src/ncsi.c', + 'src/ndp_table.c', + 'src/sbuf.c', + 'src/slirp.c', + 'src/socket.c', + 'src/state.c', + 'src/stream.c', + 'src/tcp_input.c', + 'src/tcp_output.c', + 'src/tcp_subr.c', + 'src/tcp_timer.c', + 'src/tftp.c', + 'src/udp.c', + 'src/udp6.c', + 'src/util.c', + 'src/version.c', + 'src/vmstate.c', +] + +mapfile = 'src/libslirp.map' +vflag = [] +vflag_test = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile) +if cc.has_link_argument('-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), 'src/libslirp.test.map')) + vflag += vflag_test +endif + +if fuzzer_build + cargs += '-fsanitize-coverage=edge,indirect-calls,trace-cmp' + cargs += '-fsanitize=fuzzer-no-link,address' + cargs += '-fprofile-instr-generate' + cargs += '-fcoverage-mapping' + cargs += '-g' + cargs += '-DSLIRP_DEBUG' + vflag += '-fsanitize=fuzzer-no-link,address' + vflag += '-fsanitize-coverage=edge,indirect-calls,trace-cmp' + vflag += '-fprofile-instr-generate' + vflag += '-fcoverage-mapping' +endif +if fuzz_reproduce + cargs += '-DSLIRP_DEBUG' + cargs += '-g' +endif + +install_devel = not meson.is_subproject() + +configure_file( + input : 'src/libslirp-version.h.in', + output : 'libslirp-version.h', + install : install_devel, + install_dir : join_paths(get_option('includedir'), 'slirp'), + configuration : conf +) + +if fuzzer_build or fuzz_reproduce + lib = static_library('slirp', sources, + c_args : cargs, + link_args : vflag, + link_depends : mapfile, + dependencies : [glib_dep, platform_deps], + ) +else + lib = library('slirp', sources, + version : lt_version, + c_args : cargs, + link_args : vflag, + link_depends : mapfile, + dependencies : [glib_dep, platform_deps], + install : install_devel or get_option('default_library') == 'shared', + ) +endif + +pingtest = executable('pingtest', 'test/pingtest.c', + link_with: [ lib ], + c_args : cargs, + link_args : vflag, + include_directories: [ 'src' ], + dependencies : [ platform_deps ] +) + +test('ping', pingtest) + +ncsitest = executable('ncsitest', 'test/ncsitest.c', + link_with: [lib], + c_args : cargs, + link_args : vflag, + include_directories: ['src'], + dependencies: [glib_dep, platform_deps] +) + +test('ncsi', ncsitest) + +if install_devel + install_headers(['src/libslirp.h'], subdir : 'slirp') + + pkg = import('pkgconfig') + + pkg.generate( + version : version, + libraries : lib, + requires : [ + 'glib-2.0', + ], + name : 'slirp', + description : 'User-space network stack', + filebase : 'slirp', + subdirs : 'slirp', + ) +else + if get_option('default_library') == 'both' + lib = lib.get_static_lib() + endif +endif + +libslirp_dep = declare_dependency( + link_with : lib, + include_directories : [include_directories('src'), include_directories('.')], +) + +subdir('fuzzing') diff --git a/src/net/libslirp/meson_options.txt b/src/net/libslirp/meson_options.txt new file mode 100644 index 00000000..17709ec3 --- /dev/null +++ b/src/net/libslirp/meson_options.txt @@ -0,0 +1,14 @@ +option('version_suffix', type: 'string', value: '', + description: 'Suffix to append to SLIRP_VERSION_STRING') + +option('oss-fuzz', type : 'boolean', value : 'false', + description : 'build against oss-fuzz') + +option('llvm-fuzz', type : 'boolean', value : 'false', + description : 'build against LLVM libFuzzer') + +option('fuzz-reproduce', type : 'boolean', value : 'false', + description : 'build a standalone executable to reproduce fuzz cases') + +option('static', type : 'boolean', value : 'false', + description : 'build static binary, only for debugging, otherwise rather use --default-library static') diff --git a/src/net/libslirp/src/arp_table.c b/src/net/libslirp/src/arp_table.c new file mode 100644 index 00000000..3cf2ecc2 --- /dev/null +++ b/src/net/libslirp/src/arp_table.c @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: MIT */ +/* + * ARP table + * + * Copyright (c) 2011 AdaCore + * + * 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. + */ + +#include "slirp.h" + +#include + +void arp_table_add(Slirp *slirp, uint32_t ip_addr, + const uint8_t ethaddr[ETH_ALEN]) +{ + const uint32_t broadcast_addr = + ~slirp->vnetwork_mask.s_addr | slirp->vnetwork_addr.s_addr; + ArpTable *arptbl = &slirp->arp_table; + int i; + char ethaddr_str[ETH_ADDRSTRLEN]; + char addr[INET_ADDRSTRLEN]; + + DEBUG_CALL("arp_table_add"); + DEBUG_ARG("ip = %s", inet_ntop(AF_INET, &(struct in_addr){ .s_addr = ip_addr }, + addr, sizeof(addr))); + DEBUG_ARG("hw addr = %s", slirp_ether_ntoa(ethaddr, ethaddr_str, + sizeof(ethaddr_str))); + + if (ip_addr == 0 || ip_addr == 0xffffffff || ip_addr == broadcast_addr) { + /* Do not register broadcast addresses */ + return; + } + + /* Search for an entry */ + for (i = 0; i < ARP_TABLE_SIZE; i++) { + if (arptbl->table[i].ar_sip == ip_addr) { + /* Update the entry */ + memcpy(arptbl->table[i].ar_sha, ethaddr, ETH_ALEN); + return; + } + } + + /* No entry found, create a new one */ + arptbl->table[arptbl->next_victim].ar_sip = ip_addr; + memcpy(arptbl->table[arptbl->next_victim].ar_sha, ethaddr, ETH_ALEN); + arptbl->next_victim = (arptbl->next_victim + 1) % ARP_TABLE_SIZE; +} + +bool arp_table_search(Slirp *slirp, uint32_t ip_addr, + uint8_t out_ethaddr[ETH_ALEN]) +{ + const uint32_t broadcast_addr = + ~slirp->vnetwork_mask.s_addr | slirp->vnetwork_addr.s_addr; + ArpTable *arptbl = &slirp->arp_table; + int i; + char ethaddr_str[ETH_ADDRSTRLEN]; + char addr[INET_ADDRSTRLEN]; + + DEBUG_CALL("arp_table_search"); + DEBUG_ARG("ip = %s", inet_ntop(AF_INET, &(struct in_addr){ .s_addr = ip_addr }, + addr, sizeof(addr))); + + /* If broadcast address */ + if (ip_addr == 0 || ip_addr == 0xffffffff || ip_addr == broadcast_addr) { + /* return Ethernet broadcast address */ + memset(out_ethaddr, 0xff, ETH_ALEN); + return 1; + } + + for (i = 0; i < ARP_TABLE_SIZE; i++) { + if (arptbl->table[i].ar_sip == ip_addr) { + memcpy(out_ethaddr, arptbl->table[i].ar_sha, ETH_ALEN); + DEBUG_ARG("found hw addr = %s", + slirp_ether_ntoa(out_ethaddr, ethaddr_str, + sizeof(ethaddr_str))); + return 1; + } + } + + return 0; +} diff --git a/src/net/libslirp/src/bootp.c b/src/net/libslirp/src/bootp.c new file mode 100644 index 00000000..147955fc --- /dev/null +++ b/src/net/libslirp/src/bootp.c @@ -0,0 +1,398 @@ +/* SPDX-License-Identifier: MIT */ +/* + * QEMU BOOTP/DHCP server + * + * Copyright (c) 2004 Fabrice Bellard + * + * 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. + */ +#include "slirp.h" + +#if defined(_WIN32) +/* Windows ntohl() returns an u_long value. + * Add a type cast to match the format strings. */ +#define ntohl(n) ((uint32_t)ntohl(n)) +#endif + +/* XXX: only DHCP is supported */ + +#define LEASE_TIME (24 * 3600) + +#define UEFI_HTTP_VENDOR_CLASS_ID "HTTPClient" + +static const uint8_t rfc1533_cookie[] = { RFC1533_COOKIE }; + +#define DPRINTF(...) DEBUG_RAW_CALL(__VA_ARGS__) + +static BOOTPClient *get_new_addr(Slirp *slirp, struct in_addr *paddr, + const uint8_t *macaddr) +{ + BOOTPClient *bc; + int i; + + for (i = 0; i < NB_BOOTP_CLIENTS; i++) { + bc = &slirp->bootp_clients[i]; + if (!bc->allocated || !memcmp(macaddr, bc->macaddr, 6)) + goto found; + } + return NULL; +found: + bc = &slirp->bootp_clients[i]; + bc->allocated = 1; + paddr->s_addr = slirp->vdhcp_startaddr.s_addr + htonl(i); + return bc; +} + +static BOOTPClient *request_addr(Slirp *slirp, const struct in_addr *paddr, + const uint8_t *macaddr) +{ + uint32_t req_addr = ntohl(paddr->s_addr); + uint32_t dhcp_addr = ntohl(slirp->vdhcp_startaddr.s_addr); + BOOTPClient *bc; + + if (req_addr >= dhcp_addr && req_addr < (dhcp_addr + NB_BOOTP_CLIENTS)) { + bc = &slirp->bootp_clients[req_addr - dhcp_addr]; + if (!bc->allocated || !memcmp(macaddr, bc->macaddr, 6)) { + bc->allocated = 1; + return bc; + } + } + return NULL; +} + +static BOOTPClient *find_addr(Slirp *slirp, struct in_addr *paddr, + const uint8_t *macaddr) +{ + BOOTPClient *bc; + int i; + + for (i = 0; i < NB_BOOTP_CLIENTS; i++) { + if (!memcmp(macaddr, slirp->bootp_clients[i].macaddr, 6)) + goto found; + } + return NULL; +found: + bc = &slirp->bootp_clients[i]; + bc->allocated = 1; + paddr->s_addr = slirp->vdhcp_startaddr.s_addr + htonl(i); + return bc; +} + +static void dhcp_decode(const struct bootp_t *bp, + const uint8_t *bp_end, + int *pmsg_type, + struct in_addr *preq_addr) +{ + const uint8_t *p; + int len, tag; + + *pmsg_type = 0; + preq_addr->s_addr = htonl(0L); + + p = bp->bp_vend; + if (memcmp(p, rfc1533_cookie, 4) != 0) + return; + p += 4; + while (p < bp_end) { + tag = p[0]; + if (tag == RFC1533_PAD) { + p++; + } else if (tag == RFC1533_END) { + break; + } else { + p++; + if (p >= bp_end) + break; + len = *p++; + if (p + len > bp_end) { + break; + } + DPRINTF("dhcp: tag=%d len=%d\n", tag, len); + + switch (tag) { + case RFC2132_MSG_TYPE: + if (len >= 1) + *pmsg_type = p[0]; + break; + case RFC2132_REQ_ADDR: + if (len >= 4) { + memcpy(&(preq_addr->s_addr), p, 4); + } + break; + default: + break; + } + p += len; + } + } + if (*pmsg_type == DHCPREQUEST && preq_addr->s_addr == htonl(0L) && + bp->bp_ciaddr.s_addr) { + memcpy(&(preq_addr->s_addr), &bp->bp_ciaddr, 4); + } +} + +static void bootp_reply(Slirp *slirp, + const struct bootp_t *bp, + const uint8_t *bp_end) +{ + BOOTPClient *bc = NULL; + struct mbuf *m; + struct bootp_t *rbp; + struct sockaddr_in saddr, daddr; + struct in_addr preq_addr; + int dhcp_msg_type, val; + uint8_t *q; + uint8_t *end; + uint8_t client_ethaddr[ETH_ALEN]; + + /* extract exact DHCP msg type */ + dhcp_decode(bp, bp_end, &dhcp_msg_type, &preq_addr); + DPRINTF("bootp packet op=%d msgtype=%d", bp->bp_op, dhcp_msg_type); + if (preq_addr.s_addr != htonl(0L)) + DPRINTF(" req_addr=%08" PRIx32 "\n", ntohl(preq_addr.s_addr)); + else { + DPRINTF("\n"); + } + + if (dhcp_msg_type == 0) + dhcp_msg_type = DHCPREQUEST; /* Force reply for old BOOTP clients */ + + if (dhcp_msg_type != DHCPDISCOVER && dhcp_msg_type != DHCPREQUEST) + return; + + /* Get client's hardware address from bootp request */ + memcpy(client_ethaddr, bp->bp_hwaddr, ETH_ALEN); + + m = m_get(slirp); + if (!m) { + return; + } + m->m_data += IF_MAXLINKHDR; + m_inc(m, sizeof(struct bootp_t) + DHCP_OPT_LEN); + rbp = (struct bootp_t *)m->m_data; + m->m_data += sizeof(struct udpiphdr); + memset(rbp, 0, sizeof(struct bootp_t) + DHCP_OPT_LEN); + + if (dhcp_msg_type == DHCPDISCOVER) { + if (preq_addr.s_addr != htonl(0L)) { + bc = request_addr(slirp, &preq_addr, client_ethaddr); + if (bc) { + daddr.sin_addr = preq_addr; + } + } + if (!bc) { + new_addr: + bc = get_new_addr(slirp, &daddr.sin_addr, client_ethaddr); + if (!bc) { + DPRINTF("no address left\n"); + return; + } + } + memcpy(bc->macaddr, client_ethaddr, ETH_ALEN); + } else if (preq_addr.s_addr != htonl(0L)) { + bc = request_addr(slirp, &preq_addr, client_ethaddr); + if (bc) { + daddr.sin_addr = preq_addr; + memcpy(bc->macaddr, client_ethaddr, ETH_ALEN); + } else { + /* DHCPNAKs should be sent to broadcast */ + daddr.sin_addr.s_addr = 0xffffffff; + } + } else { + bc = find_addr(slirp, &daddr.sin_addr, bp->bp_hwaddr); + if (!bc) { + /* if never assigned, behaves as if it was already + assigned (windows fix because it remembers its address) */ + goto new_addr; + } + } + + /* Update ARP table for this IP address */ + arp_table_add(slirp, daddr.sin_addr.s_addr, client_ethaddr); + + saddr.sin_addr = slirp->vhost_addr; + saddr.sin_port = htons(BOOTP_SERVER); + + daddr.sin_port = htons(BOOTP_CLIENT); + + rbp->bp_op = BOOTP_REPLY; + rbp->bp_xid = bp->bp_xid; + rbp->bp_htype = 1; + rbp->bp_hlen = 6; + memcpy(rbp->bp_hwaddr, bp->bp_hwaddr, ETH_ALEN); + + rbp->bp_yiaddr = daddr.sin_addr; /* Client IP address */ + rbp->bp_siaddr = saddr.sin_addr; /* Server IP address */ + + q = rbp->bp_vend; + end = rbp->bp_vend + DHCP_OPT_LEN; + memcpy(q, rfc1533_cookie, 4); + q += 4; + + if (bc) { + DPRINTF("%s addr=%08" PRIx32 "\n", + (dhcp_msg_type == DHCPDISCOVER) ? "offered" : "ack'ed", + ntohl(daddr.sin_addr.s_addr)); + + if (dhcp_msg_type == DHCPDISCOVER) { + *q++ = RFC2132_MSG_TYPE; + *q++ = 1; + *q++ = DHCPOFFER; + } else /* DHCPREQUEST */ { + *q++ = RFC2132_MSG_TYPE; + *q++ = 1; + *q++ = DHCPACK; + } + + if (slirp->bootp_filename) { + g_assert(strlen(slirp->bootp_filename) < sizeof(rbp->bp_file)); + strcpy(rbp->bp_file, slirp->bootp_filename); + } + + *q++ = RFC2132_SRV_ID; + *q++ = 4; + memcpy(q, &saddr.sin_addr, 4); + q += 4; + + *q++ = RFC1533_NETMASK; + *q++ = 4; + memcpy(q, &slirp->vnetwork_mask, 4); + q += 4; + + if (!slirp->restricted) { + *q++ = RFC1533_GATEWAY; + *q++ = 4; + memcpy(q, &saddr.sin_addr, 4); + q += 4; + + *q++ = RFC1533_DNS; + *q++ = 4; + memcpy(q, &slirp->vnameserver_addr, 4); + q += 4; + } + + *q++ = RFC2132_LEASE_TIME; + *q++ = 4; + val = htonl(LEASE_TIME); + memcpy(q, &val, 4); + q += 4; + + if (*slirp->client_hostname) { + val = strlen(slirp->client_hostname); + if (q + val + 2 >= end) { + g_warning("DHCP packet size exceeded, " + "omitting host name option."); + } else { + *q++ = RFC1533_HOSTNAME; + *q++ = val; + memcpy(q, slirp->client_hostname, val); + q += val; + } + } + + if (slirp->vdomainname) { + val = strlen(slirp->vdomainname); + if (q + val + 2 >= end) { + g_warning("DHCP packet size exceeded, " + "omitting domain name option."); + } else { + *q++ = RFC1533_DOMAINNAME; + *q++ = val; + memcpy(q, slirp->vdomainname, val); + q += val; + } + } + + if (slirp->tftp_server_name) { + val = strlen(slirp->tftp_server_name); + if (q + val + 2 >= end) { + g_warning("DHCP packet size exceeded, " + "omitting tftp-server-name option."); + } else { + *q++ = RFC2132_TFTP_SERVER_NAME; + *q++ = val; + memcpy(q, slirp->tftp_server_name, val); + q += val; + } + } + + if (slirp->vdnssearch) { + val = slirp->vdnssearch_len; + if (q + val >= end) { + g_warning("DHCP packet size exceeded, " + "omitting domain-search option."); + } else { + memcpy(q, slirp->vdnssearch, val); + q += val; + } + } + + /* this allows to support UEFI HTTP boot: according to the UEFI + specification, DHCP server must send vendor class identifier option + set to "HTTPClient" string, when responding to DHCP requests as part + of the UEFI HTTP boot + + we assume that, if the bootfile parameter was configured as an http + URL, the user intends to perform UEFI HTTP boot, so send this option + automatically */ + if (slirp->bootp_filename && g_str_has_prefix(slirp->bootp_filename, "http://")) { + val = strlen(UEFI_HTTP_VENDOR_CLASS_ID); + if (q + val + 2 >= end) { + g_warning("DHCP packet size exceeded, " + "omitting vendor class id option."); + } else { + *q++ = RFC2132_VENDOR_CLASS_ID; + *q++ = val; + memcpy(q, UEFI_HTTP_VENDOR_CLASS_ID, val); + q += val; + } + } + } else { + static const char nak_msg[] = "requested address not available"; + + DPRINTF("nak'ed addr=%08" PRIx32 "\n", ntohl(preq_addr.s_addr)); + + *q++ = RFC2132_MSG_TYPE; + *q++ = 1; + *q++ = DHCPNAK; + + *q++ = RFC2132_MESSAGE; + *q++ = sizeof(nak_msg) - 1; + memcpy(q, nak_msg, sizeof(nak_msg) - 1); + q += sizeof(nak_msg) - 1; + } + assert(q < end); + *q++ = RFC1533_END; + + daddr.sin_addr.s_addr = 0xffffffffu; + + assert(q <= end); + + m->m_len = sizeof(struct bootp_t) + (end - rbp->bp_vend) - sizeof(struct ip) - sizeof(struct udphdr); + udp_output(NULL, m, &saddr, &daddr, IPTOS_LOWDELAY); +} + +void bootp_input(struct mbuf *m) +{ + struct bootp_t *bp = mtod_check(m, sizeof(struct bootp_t)); + + if (!m->slirp->disable_dhcp && bp && bp->bp_op == BOOTP_REQUEST) { + bootp_reply(m->slirp, bp, m_end(m)); + } +} diff --git a/src/net/libslirp/src/bootp.h b/src/net/libslirp/src/bootp.h new file mode 100644 index 00000000..c5109602 --- /dev/null +++ b/src/net/libslirp/src/bootp.h @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* bootp/dhcp defines */ + +#ifndef SLIRP_BOOTP_H +#define SLIRP_BOOTP_H + +#define BOOTP_SERVER 67 +#define BOOTP_CLIENT 68 + +#define BOOTP_REQUEST 1 +#define BOOTP_REPLY 2 + +#define RFC1533_COOKIE 99, 130, 83, 99 +#define RFC1533_PAD 0 +#define RFC1533_NETMASK 1 +#define RFC1533_TIMEOFFSET 2 +#define RFC1533_GATEWAY 3 +#define RFC1533_TIMESERVER 4 +#define RFC1533_IEN116NS 5 +#define RFC1533_DNS 6 +#define RFC1533_LOGSERVER 7 +#define RFC1533_COOKIESERVER 8 +#define RFC1533_LPRSERVER 9 +#define RFC1533_IMPRESSSERVER 10 +#define RFC1533_RESOURCESERVER 11 +#define RFC1533_HOSTNAME 12 +#define RFC1533_BOOTFILESIZE 13 +#define RFC1533_MERITDUMPFILE 14 +#define RFC1533_DOMAINNAME 15 +#define RFC1533_SWAPSERVER 16 +#define RFC1533_ROOTPATH 17 +#define RFC1533_EXTENSIONPATH 18 +#define RFC1533_IPFORWARDING 19 +#define RFC1533_IPSOURCEROUTING 20 +#define RFC1533_IPPOLICYFILTER 21 +#define RFC1533_IPMAXREASSEMBLY 22 +#define RFC1533_IPTTL 23 +#define RFC1533_IPMTU 24 +#define RFC1533_IPMTUPLATEAU 25 +#define RFC1533_INTMTU 26 +#define RFC1533_INTLOCALSUBNETS 27 +#define RFC1533_INTBROADCAST 28 +#define RFC1533_INTICMPDISCOVER 29 +#define RFC1533_INTICMPRESPOND 30 +#define RFC1533_INTROUTEDISCOVER 31 +#define RFC1533_INTROUTESOLICIT 32 +#define RFC1533_INTSTATICROUTES 33 +#define RFC1533_LLTRAILERENCAP 34 +#define RFC1533_LLARPCACHETMO 35 +#define RFC1533_LLETHERNETENCAP 36 +#define RFC1533_TCPTTL 37 +#define RFC1533_TCPKEEPALIVETMO 38 +#define RFC1533_TCPKEEPALIVEGB 39 +#define RFC1533_NISDOMAIN 40 +#define RFC1533_NISSERVER 41 +#define RFC1533_NTPSERVER 42 +#define RFC1533_VENDOR 43 +#define RFC1533_NBNS 44 +#define RFC1533_NBDD 45 +#define RFC1533_NBNT 46 +#define RFC1533_NBSCOPE 47 +#define RFC1533_XFS 48 +#define RFC1533_XDM 49 + +#define RFC2132_REQ_ADDR 50 +#define RFC2132_LEASE_TIME 51 +#define RFC2132_MSG_TYPE 53 +#define RFC2132_SRV_ID 54 +#define RFC2132_PARAM_LIST 55 +#define RFC2132_MESSAGE 56 +#define RFC2132_MAX_SIZE 57 +#define RFC2132_RENEWAL_TIME 58 +#define RFC2132_REBIND_TIME 59 +#define RFC2132_VENDOR_CLASS_ID 60 +#define RFC2132_TFTP_SERVER_NAME 66 + +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPACK 5 +#define DHCPNAK 6 + +#define RFC1533_VENDOR_MAJOR 0 +#define RFC1533_VENDOR_MINOR 0 + +#define RFC1533_VENDOR_MAGIC 128 +#define RFC1533_VENDOR_ADDPARM 129 +#define RFC1533_VENDOR_ETHDEV 130 +#define RFC1533_VENDOR_HOWTO 132 +#define RFC1533_VENDOR_MNUOPTS 160 +#define RFC1533_VENDOR_SELECTION 176 +#define RFC1533_VENDOR_MOTD 184 +#define RFC1533_VENDOR_NUMOFMOTD 8 +#define RFC1533_VENDOR_IMG 192 +#define RFC1533_VENDOR_NUMOFIMG 16 + +#define RFC1533_END 255 +#define BOOTP_VENDOR_LEN 64 +#define DHCP_OPT_LEN 312 + +struct bootp_t { + struct ip ip; + struct udphdr udp; + uint8_t bp_op; + uint8_t bp_htype; + uint8_t bp_hlen; + uint8_t bp_hops; + uint32_t bp_xid; + uint16_t bp_secs; + uint16_t unused; + struct in_addr bp_ciaddr; + struct in_addr bp_yiaddr; + struct in_addr bp_siaddr; + struct in_addr bp_giaddr; + uint8_t bp_hwaddr[16]; + uint8_t bp_sname[64]; + char bp_file[128]; + uint8_t bp_vend[]; +}; + +typedef struct { + uint16_t allocated; + uint8_t macaddr[6]; +} BOOTPClient; + +#define NB_BOOTP_CLIENTS 16 + +/* Process a bootp packet from the guest */ +void bootp_input(struct mbuf *m); + +#endif diff --git a/src/net/libslirp/src/cksum.c b/src/net/libslirp/src/cksum.c new file mode 100644 index 00000000..b1cb97b7 --- /dev/null +++ b/src/net/libslirp/src/cksum.c @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1988, 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)in_cksum.c 8.1 (Berkeley) 6/10/93 + * in_cksum.c,v 1.2 1994/08/02 07:48:16 davidg Exp + */ + +#include "slirp.h" + +/* + * Checksum routine for Internet Protocol family headers (Portable Version). + * + * This routine is very heavily used in the network + * code and should be modified for each CPU to be as fast as possible. + * + * XXX Since we will never span more than 1 mbuf, we can optimise this + */ + +#define ADDCARRY(x) (x > 65535 ? x -= 65535 : x) +#define REDUCE \ + { \ + l_util.l = sum; \ + sum = l_util.s[0] + l_util.s[1]; \ + ADDCARRY(sum); \ + } + +int cksum(struct mbuf *m, int len) +{ + register uint16_t *w; + register int sum = 0; + register int mlen = 0; + int byte_swapped = 0; + + union { + uint8_t c[2]; + uint16_t s; + } s_util; + union { + uint16_t s[2]; + uint32_t l; + } l_util; + + if (m->m_len == 0) + goto cont; + w = mtod(m, uint16_t *); + + mlen = m->m_len; + + if (len < mlen) + mlen = len; + len -= mlen; + /* + * Force to even boundary. + */ + if ((1 & (uintptr_t)w) && (mlen > 0)) { + REDUCE; + sum <<= 8; + s_util.c[0] = *(uint8_t *)w; + w = (uint16_t *)((int8_t *)w + 1); + mlen--; + byte_swapped = 1; + } + /* + * Unroll the loop to make overhead from + * branches &c small. + */ + while ((mlen -= 32) >= 0) { + sum += w[0]; + sum += w[1]; + sum += w[2]; + sum += w[3]; + sum += w[4]; + sum += w[5]; + sum += w[6]; + sum += w[7]; + sum += w[8]; + sum += w[9]; + sum += w[10]; + sum += w[11]; + sum += w[12]; + sum += w[13]; + sum += w[14]; + sum += w[15]; + w += 16; + } + mlen += 32; + while ((mlen -= 8) >= 0) { + sum += w[0]; + sum += w[1]; + sum += w[2]; + sum += w[3]; + w += 4; + } + mlen += 8; + if (mlen == 0 && byte_swapped == 0) + goto cont; + REDUCE; + while ((mlen -= 2) >= 0) { + sum += *w++; + } + + if (byte_swapped) { + REDUCE; + sum <<= 8; + if (mlen == -1) { + s_util.c[1] = *(uint8_t *)w; + sum += s_util.s; + mlen = 0; + } else + + mlen = -1; + } else if (mlen == -1) + s_util.c[0] = *(uint8_t *)w; + +cont: + if (len) { + DEBUG_ERROR("cksum: out of data"); + DEBUG_ERROR(" len = %d", len); + } + if (mlen == -1) { + /* The last mbuf has odd # of bytes. Follow the + standard (the odd byte may be shifted left by 8 bits + or not as determined by endian-ness of the machine) */ + s_util.c[1] = 0; + sum += s_util.s; + } + REDUCE; + return (~sum & 0xffff); +} + +int ip6_cksum(struct mbuf *m) +{ + /* TODO: Optimize this by being able to pass the ip6_pseudohdr to cksum + * separately from the mbuf */ + struct ip6 save_ip, *ip = mtod(m, struct ip6 *); + struct ip6_pseudohdr *ih = mtod(m, struct ip6_pseudohdr *); + int sum; + + save_ip = *ip; + + ih->ih_src = save_ip.ip_src; + ih->ih_dst = save_ip.ip_dst; + ih->ih_pl = htonl((uint32_t)ntohs(save_ip.ip_pl)); + ih->ih_zero_hi = 0; + ih->ih_zero_lo = 0; + ih->ih_nh = save_ip.ip_nh; + + sum = cksum(m, ((int)sizeof(struct ip6_pseudohdr)) + ntohl(ih->ih_pl)); + + *ip = save_ip; + + return sum; +} diff --git a/src/net/libslirp/src/debug.h b/src/net/libslirp/src/debug.h new file mode 100644 index 00000000..f4da00cf --- /dev/null +++ b/src/net/libslirp/src/debug.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#ifndef DEBUG_H_ +#define DEBUG_H_ + +#define DBG_CALL (1 << 0) +#define DBG_MISC (1 << 1) +#define DBG_ERROR (1 << 2) +#define DBG_TFTP (1 << 3) +#define DBG_VERBOSE_CALL (1 << 4) + +extern int slirp_debug; + +#define DEBUG_CALL(name) \ + do { \ + if (G_UNLIKELY(slirp_debug & DBG_CALL)) { \ + g_debug(name "..."); \ + } \ + } while (0) + +#define DEBUG_VERBOSE_CALL(name) \ + do { \ + if (G_UNLIKELY(slirp_debug & DBG_VERBOSE_CALL)) { \ + g_debug(name "..."); \ + } \ + } while (0) + +#define DEBUG_RAW_CALL(...) \ + do { \ + if (G_UNLIKELY(slirp_debug & DBG_CALL)) { \ + g_debug(__VA_ARGS__); \ + } \ + } while (0) + +#define DEBUG_ARG(...) \ + do { \ + if (G_UNLIKELY(slirp_debug & DBG_CALL)) { \ + g_debug(" " __VA_ARGS__); \ + } \ + } while (0) + +#define DEBUG_MISC(...) \ + do { \ + if (G_UNLIKELY(slirp_debug & DBG_MISC)) { \ + g_debug(__VA_ARGS__); \ + } \ + } while (0) + +#define DEBUG_ERROR(...) \ + do { \ + if (G_UNLIKELY(slirp_debug & DBG_ERROR)) { \ + g_debug(__VA_ARGS__); \ + } \ + } while (0) + +#define DEBUG_TFTP(...) \ + do { \ + if (G_UNLIKELY(slirp_debug & DBG_TFTP)) { \ + g_debug(__VA_ARGS__); \ + } \ + } while (0) + +#endif /* DEBUG_H_ */ diff --git a/src/net/libslirp/src/dhcpv6.c b/src/net/libslirp/src/dhcpv6.c new file mode 100644 index 00000000..77b451b9 --- /dev/null +++ b/src/net/libslirp/src/dhcpv6.c @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * SLIRP stateless DHCPv6 + * + * We only support stateless DHCPv6, e.g. for network booting. + * See RFC 3315, RFC 3736, RFC 3646 and RFC 5970 for details. + * + * Copyright 2016 Thomas Huth, Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "slirp.h" +#include "dhcpv6.h" + +/* DHCPv6 message types */ +#define MSGTYPE_REPLY 7 +#define MSGTYPE_INFO_REQUEST 11 + +/* DHCPv6 option types */ +#define OPTION_CLIENTID 1 +#define OPTION_IAADDR 5 +#define OPTION_ORO 6 +#define OPTION_DNS_SERVERS 23 +#define OPTION_BOOTFILE_URL 59 + +struct requested_infos { + uint8_t *client_id; + int client_id_len; + bool want_dns; + bool want_boot_url; +}; + +/** + * Analyze the info request message sent by the client to see what data it + * provided and what it wants to have. The information is gathered in the + * "requested_infos" struct. Note that client_id (if provided) points into + * the odata region, thus the caller must keep odata valid as long as it + * needs to access the requested_infos struct. + */ +static int dhcpv6_parse_info_request(Slirp *slirp, uint8_t *odata, int olen, + struct requested_infos *ri) +{ + int i, req_opt; + + while (olen > 4) { + /* Parse one option */ + int option = odata[0] << 8 | odata[1]; + int len = odata[2] << 8 | odata[3]; + + if (len + 4 > olen) { + slirp->cb->guest_error("Guest sent bad DHCPv6 packet!", + slirp->opaque); + return -E2BIG; + } + + switch (option) { + case OPTION_IAADDR: + /* According to RFC3315, we must discard requests with IA option */ + return -EINVAL; + case OPTION_CLIENTID: + if (len > 256) { + /* Avoid very long IDs which could cause problems later */ + return -E2BIG; + } + ri->client_id = odata + 4; + ri->client_id_len = len; + break; + case OPTION_ORO: /* Option request option */ + if (len & 1) { + return -EINVAL; + } + /* Check which options the client wants to have */ + for (i = 0; i < len; i += 2) { + req_opt = odata[4 + i] << 8 | odata[4 + i + 1]; + switch (req_opt) { + case OPTION_DNS_SERVERS: + ri->want_dns = true; + break; + case OPTION_BOOTFILE_URL: + ri->want_boot_url = true; + break; + default: + DEBUG_MISC("dhcpv6: Unsupported option request %d", + req_opt); + } + } + break; + default: + DEBUG_MISC("dhcpv6 info req: Unsupported option %d, len=%d", option, + len); + } + + odata += len + 4; + olen -= len + 4; + } + + return 0; +} + + +/** + * Handle information request messages + */ +static void dhcpv6_info_request(Slirp *slirp, struct sockaddr_in6 *srcsas, + uint32_t xid, uint8_t *odata, int olen) +{ + struct requested_infos ri = { NULL }; + struct sockaddr_in6 sa6, da6; + struct mbuf *m; + uint8_t *resp; + + if (dhcpv6_parse_info_request(slirp, odata, olen, &ri) < 0) { + return; + } + + m = m_get(slirp); + if (!m) { + return; + } + memset(m->m_data, 0, m->m_size); + m->m_data += IF_MAXLINKHDR; + resp = (uint8_t *)m->m_data + sizeof(struct ip6) + sizeof(struct udphdr); + + /* Fill in response */ + *resp++ = MSGTYPE_REPLY; + *resp++ = (uint8_t)(xid >> 16); + *resp++ = (uint8_t)(xid >> 8); + *resp++ = (uint8_t)xid; + + if (ri.client_id) { + *resp++ = OPTION_CLIENTID >> 8; /* option-code high byte */ + *resp++ = OPTION_CLIENTID; /* option-code low byte */ + *resp++ = ri.client_id_len >> 8; /* option-len high byte */ + *resp++ = ri.client_id_len; /* option-len low byte */ + memcpy(resp, ri.client_id, ri.client_id_len); + resp += ri.client_id_len; + } + if (ri.want_dns) { + *resp++ = OPTION_DNS_SERVERS >> 8; /* option-code high byte */ + *resp++ = OPTION_DNS_SERVERS; /* option-code low byte */ + *resp++ = 0; /* option-len high byte */ + *resp++ = 16; /* option-len low byte */ + memcpy(resp, &slirp->vnameserver_addr6, 16); + resp += 16; + } + if (ri.want_boot_url) { + uint8_t *sa = slirp->vhost_addr6.s6_addr; + int slen, smaxlen; + + *resp++ = OPTION_BOOTFILE_URL >> 8; /* option-code high byte */ + *resp++ = OPTION_BOOTFILE_URL; /* option-code low byte */ + smaxlen = (uint8_t *)m->m_data + slirp->if_mtu - (resp + 2); + slen = slirp_fmt((char *)resp + 2, smaxlen, + "tftp://[%02x%02x:%02x%02x:%02x%02x:%02x%02x:" + "%02x%02x:%02x%02x:%02x%02x:%02x%02x]/%s", + sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], sa[6], sa[7], + sa[8], sa[9], sa[10], sa[11], sa[12], sa[13], sa[14], + sa[15], slirp->bootp_filename); + *resp++ = slen >> 8; /* option-len high byte */ + *resp++ = slen; /* option-len low byte */ + resp += slen; + } + + sa6.sin6_addr = slirp->vhost_addr6; + sa6.sin6_port = DHCPV6_SERVER_PORT; + da6.sin6_addr = srcsas->sin6_addr; + da6.sin6_port = srcsas->sin6_port; + m->m_data += sizeof(struct ip6) + sizeof(struct udphdr); + m->m_len = resp - (uint8_t *)m->m_data; + udp6_output(NULL, m, &sa6, &da6); +} + +/** + * Handle DHCPv6 messages sent by the client + */ +void dhcpv6_input(struct sockaddr_in6 *srcsas, struct mbuf *m) +{ + uint8_t *data = (uint8_t *)m->m_data + sizeof(struct udphdr); + int data_len = m->m_len - sizeof(struct udphdr); + uint32_t xid; + + if (data_len < 4) { + return; + } + + xid = ntohl(*(uint32_t *)data) & 0xffffff; + + switch (data[0]) { + case MSGTYPE_INFO_REQUEST: + dhcpv6_info_request(m->slirp, srcsas, xid, &data[4], data_len - 4); + break; + default: + DEBUG_MISC("dhcpv6_input: Unsupported message type 0x%x", data[0]); + } +} diff --git a/src/net/libslirp/src/dhcpv6.h b/src/net/libslirp/src/dhcpv6.h new file mode 100644 index 00000000..c0b4a248 --- /dev/null +++ b/src/net/libslirp/src/dhcpv6.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Definitions and prototypes for SLIRP stateless DHCPv6 + * + * Copyright 2016 Thomas Huth, Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef SLIRP_DHCPV6_H +#define SLIRP_DHCPV6_H + +#define DHCPV6_SERVER_PORT 547 + +#define ALLDHCP_MULTICAST \ + { \ + .s6_addr = { \ + 0xff, \ + 0x02, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x01, \ + 0x00, \ + 0x02 \ + } \ + } + +#define in6_dhcp_multicast(a) in6_equal(a, &(struct in6_addr)ALLDHCP_MULTICAST) + +/* Process a DHCPv6 packet from the guest */ +void dhcpv6_input(struct sockaddr_in6 *srcsas, struct mbuf *m); + +#endif diff --git a/src/net/libslirp/src/dnssearch.c b/src/net/libslirp/src/dnssearch.c new file mode 100644 index 00000000..cbd1a197 --- /dev/null +++ b/src/net/libslirp/src/dnssearch.c @@ -0,0 +1,306 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Domain search option for DHCP (RFC 3397) + * + * Copyright (c) 2012 Klaus Stengel + * + * 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. + */ + +#include "slirp.h" + +static const uint8_t RFC3397_OPT_DOMAIN_SEARCH = 119; +static const uint8_t MAX_OPT_LEN = 255; +static const uint8_t OPT_HEADER_LEN = 2; +static const uint8_t REFERENCE_LEN = 2; + +struct compact_domain; + +typedef struct compact_domain { + struct compact_domain *self; + struct compact_domain *refdom; + uint8_t *labels; + size_t len; + size_t common_octets; +} CompactDomain; + +static size_t domain_suffix_diffoff(const CompactDomain *a, + const CompactDomain *b) +{ + size_t la = a->len, lb = b->len; + uint8_t *da = a->labels + la, *db = b->labels + lb; + size_t i, lm = (la < lb) ? la : lb; + + for (i = 0; i < lm; i++) { + da--; + db--; + if (*da != *db) { + break; + } + } + return i; +} + +static int domain_suffix_ord(const void *cva, const void *cvb) +{ + const CompactDomain *a = cva, *b = cvb; + size_t la = a->len, lb = b->len; + size_t doff = domain_suffix_diffoff(a, b); + uint8_t ca = a->labels[la - doff]; + uint8_t cb = b->labels[lb - doff]; + + if (ca < cb) { + return -1; + } + if (ca > cb) { + return 1; + } + if (la < lb) { + return -1; + } + if (la > lb) { + return 1; + } + return 0; +} + +static size_t domain_common_label(CompactDomain *a, CompactDomain *b) +{ + size_t res, doff = domain_suffix_diffoff(a, b); + uint8_t *first_eq_pos = a->labels + (a->len - doff); + uint8_t *label = a->labels; + + while (*label && label < first_eq_pos) { + label += *label + 1; + } + res = a->len - (label - a->labels); + /* only report if it can help to reduce the packet size */ + return (res > REFERENCE_LEN) ? res : 0; +} + +static void domain_fixup_order(CompactDomain *cd, size_t n) +{ + size_t i; + + for (i = 0; i < n; i++) { + CompactDomain *cur = cd + i, *next = cd[i].self; + + while (!cur->common_octets) { + CompactDomain *tmp = next->self; /* backup target value */ + + next->self = cur; + cur->common_octets++; + + cur = next; + next = tmp; + } + } +} + +static void domain_mklabels(CompactDomain *cd, const char *input) +{ + uint8_t *len_marker = cd->labels; + uint8_t *output = len_marker; /* pre-incremented */ + const char *in = input; + char cur_chr; + size_t len = 0; + + if (cd->len == 0) { + goto fail; + } + cd->len++; + + do { + cur_chr = *in++; + if (cur_chr == '.' || cur_chr == '\0') { + len = output - len_marker; + if ((len == 0 && cur_chr == '.') || len >= 64) { + goto fail; + } + *len_marker = len; + + output++; + len_marker = output; + } else { + output++; + *output = cur_chr; + } + } while (cur_chr != '\0'); + + /* ensure proper zero-termination */ + if (len != 0) { + *len_marker = 0; + cd->len++; + } + return; + +fail: + g_warning("failed to parse domain name '%s'\n", input); + cd->len = 0; +} + +static void domain_mkxrefs(CompactDomain *doms, CompactDomain *last, + size_t depth) +{ + CompactDomain *i = doms, *target = doms; + + do { + if (i->labels < target->labels) { + target = i; + } + } while (i++ != last); + + for (i = doms; i != last; i++) { + CompactDomain *group_last; + size_t next_depth; + + if (i->common_octets == depth) { + continue; + } + + next_depth = -1; + for (group_last = i; group_last != last; group_last++) { + size_t co = group_last->common_octets; + if (co <= depth) { + break; + } + if (co < next_depth) { + next_depth = co; + } + } + domain_mkxrefs(i, group_last, next_depth); + + i = group_last; + if (i == last) { + break; + } + } + + if (depth == 0) { + return; + } + + i = doms; + do { + if (i != target && i->refdom == NULL) { + i->refdom = target; + i->common_octets = depth; + } + } while (i++ != last); +} + +static size_t domain_compactify(CompactDomain *domains, size_t n) +{ + uint8_t *start = domains->self->labels, *outptr = start; + size_t i; + + for (i = 0; i < n; i++) { + CompactDomain *cd = domains[i].self; + CompactDomain *rd = cd->refdom; + + if (rd != NULL) { + size_t moff = (rd->labels - start) + (rd->len - cd->common_octets); + if (moff < 0x3FFFu) { + cd->len -= cd->common_octets - 2; + cd->labels[cd->len - 1] = moff & 0xFFu; + cd->labels[cd->len - 2] = 0xC0u | (moff >> 8); + } + } + + if (cd->labels != outptr) { + memmove(outptr, cd->labels, cd->len); + cd->labels = outptr; + } + outptr += cd->len; + } + return outptr - start; +} + +int translate_dnssearch(Slirp *s, const char **names) +{ + size_t blocks, bsrc_start, bsrc_end, bdst_start; + size_t i, num_domains, memreq = 0; + uint8_t *result = NULL, *outptr; + CompactDomain *domains = NULL; + + num_domains = g_strv_length((GStrv)(void *)names); + if (num_domains == 0) { + return -2; + } + + domains = g_malloc(num_domains * sizeof(*domains)); + + for (i = 0; i < num_domains; i++) { + size_t nlen = strlen(names[i]); + memreq += nlen + 2; /* 1 zero octet + 1 label length octet */ + domains[i].self = domains + i; + domains[i].len = nlen; + domains[i].common_octets = 0; + domains[i].refdom = NULL; + } + + /* reserve extra 2 header bytes for each 255 bytes of output */ + memreq += DIV_ROUND_UP(memreq, MAX_OPT_LEN) * OPT_HEADER_LEN; + result = g_malloc(memreq * sizeof(*result)); + + outptr = result; + for (i = 0; i < num_domains; i++) { + domains[i].labels = outptr; + domain_mklabels(domains + i, names[i]); + if (domains[i].len == 0) { + /* Bogus entry, reject it all */ + g_free(domains); + g_free(result); + return -1; + } + outptr += domains[i].len; + } + + qsort(domains, num_domains, sizeof(*domains), domain_suffix_ord); + domain_fixup_order(domains, num_domains); + + for (i = 1; i < num_domains; i++) { + size_t cl = domain_common_label(domains + i - 1, domains + i); + domains[i - 1].common_octets = cl; + } + + domain_mkxrefs(domains, domains + num_domains - 1, 0); + memreq = domain_compactify(domains, num_domains); + + blocks = DIV_ROUND_UP(memreq, MAX_OPT_LEN); + bsrc_end = memreq; + bsrc_start = (blocks - 1) * MAX_OPT_LEN; + bdst_start = bsrc_start + blocks * OPT_HEADER_LEN; + memreq += blocks * OPT_HEADER_LEN; + + while (blocks--) { + size_t len = bsrc_end - bsrc_start; + memmove(result + bdst_start, result + bsrc_start, len); + result[bdst_start - 2] = RFC3397_OPT_DOMAIN_SEARCH; + result[bdst_start - 1] = len; + bsrc_end = bsrc_start; + bsrc_start -= MAX_OPT_LEN; + bdst_start -= MAX_OPT_LEN + OPT_HEADER_LEN; + } + + g_free(domains); + s->vdnssearch = result; + s->vdnssearch_len = memreq; + return 0; +} diff --git a/src/net/libslirp/src/if.c b/src/net/libslirp/src/if.c new file mode 100644 index 00000000..c49a64ce --- /dev/null +++ b/src/net/libslirp/src/if.c @@ -0,0 +1,201 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" + +static void ifs_insque(struct mbuf *ifm, struct mbuf *ifmhead) +{ + ifm->m_nextpkt = ifmhead->m_nextpkt; + ifmhead->m_nextpkt = ifm; + ifm->m_prevpkt = ifmhead; + ifm->m_nextpkt->m_prevpkt = ifm; +} + +void if_init(Slirp *slirp) +{ + slirp->if_fastq.qh_link = slirp->if_fastq.qh_rlink = &slirp->if_fastq; + slirp->if_batchq.qh_link = slirp->if_batchq.qh_rlink = &slirp->if_batchq; +} + +/* + * if_output: Queue packet into an output queue. + * There are 2 output queue's, if_fastq and if_batchq. + * Each output queue is a doubly linked list of double linked lists + * of mbufs, each list belonging to one "session" (socket). This + * way, we can output packets fairly by sending one packet from each + * session, instead of all the packets from one session, then all packets + * from the next session, etc. Packets on the if_fastq get absolute + * priority, but if one session hogs the link, it gets "downgraded" + * to the batchq until it runs out of packets, then it'll return + * to the fastq (eg. if the user does an ls -alR in a telnet session, + * it'll temporarily get downgraded to the batchq) + */ +void if_output(struct socket *so, struct mbuf *ifm) +{ + Slirp *slirp = ifm->slirp; + M_DUP_DEBUG(slirp, ifm, 0, 0); + + struct mbuf *ifq; + int on_fastq = 1; + + DEBUG_CALL("if_output"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("ifm = %p", ifm); + + /* + * First remove the mbuf from m_usedlist, + * since we're gonna use m_next and m_prev ourselves + * XXX Shouldn't need this, gotta change dtom() etc. + */ + if (ifm->m_flags & M_USEDLIST) { + slirp_remque(ifm); + ifm->m_flags &= ~M_USEDLIST; + } + + /* + * See if there's already a batchq list for this session. + * This can include an interactive session, which should go on fastq, + * but gets too greedy... hence it'll be downgraded from fastq to batchq. + * We mustn't put this packet back on the fastq (or we'll send it out of + * order) + * XXX add cache here? + */ + if (so) { + for (ifq = (struct mbuf *)slirp->if_batchq.qh_rlink; + (struct slirp_quehead *)ifq != &slirp->if_batchq; + ifq = ifq->m_prev) { + if (so == ifq->m_so) { + /* A match! */ + ifm->m_so = so; + ifs_insque(ifm, ifq->m_prevpkt); + goto diddit; + } + } + } + + /* No match, check which queue to put it on */ + if (so && (so->so_iptos & IPTOS_LOWDELAY)) { + ifq = (struct mbuf *)slirp->if_fastq.qh_rlink; + on_fastq = 1; + /* + * Check if this packet is a part of the last + * packet's session + */ + if (ifq->m_so == so) { + ifm->m_so = so; + ifs_insque(ifm, ifq->m_prevpkt); + goto diddit; + } + } else { + ifq = (struct mbuf *)slirp->if_batchq.qh_rlink; + } + + /* Create a new doubly linked list for this session */ + ifm->m_so = so; + ifs_init(ifm); + slirp_insque(ifm, ifq); + +diddit: + if (so) { + /* Update *_queued */ + so->so_queued++; + so->so_nqueued++; + /* + * Check if the interactive session should be downgraded to + * the batchq. A session is downgraded if it has queued 6 + * packets without pausing, and at least 3 of those packets + * have been sent over the link + * (XXX These are arbitrary numbers, probably not optimal..) + */ + if (on_fastq && + ((so->so_nqueued >= 6) && (so->so_nqueued - so->so_queued) >= 3)) { + /* Remove from current queue... */ + slirp_remque(ifm->m_nextpkt); + + /* ...And insert in the new. That'll teach ya! */ + slirp_insque(ifm->m_nextpkt, &slirp->if_batchq); + } + } + + /* + * This prevents us from malloc()ing too many mbufs + */ + if_start(ifm->slirp); +} + +void if_start(Slirp *slirp) +{ + uint64_t now = slirp->cb->clock_get_ns(slirp->opaque); + bool from_batchq = false; + struct mbuf *ifm, *ifm_next, *ifqt; + + DEBUG_VERBOSE_CALL("if_start"); + + if (slirp->if_start_busy) { + return; + } + slirp->if_start_busy = true; + + struct mbuf *batch_head = NULL; + if (slirp->if_batchq.qh_link != &slirp->if_batchq) { + batch_head = (struct mbuf *)slirp->if_batchq.qh_link; + } + + if (slirp->if_fastq.qh_link != &slirp->if_fastq) { + ifm_next = (struct mbuf *)slirp->if_fastq.qh_link; + } else if (batch_head) { + /* Nothing on fastq, pick up from batchq */ + ifm_next = batch_head; + from_batchq = true; + } else { + ifm_next = NULL; + } + + while (ifm_next) { + ifm = ifm_next; + + ifm_next = ifm->m_next; + if ((struct slirp_quehead *)ifm_next == &slirp->if_fastq) { + /* No more packets in fastq, switch to batchq */ + ifm_next = batch_head; + from_batchq = true; + } + if ((struct slirp_quehead *)ifm_next == &slirp->if_batchq) { + /* end of batchq */ + ifm_next = NULL; + } + + /* Try to send packet unless it already expired */ + if (ifm->expiration_date >= now && !if_encap(slirp, ifm)) { + /* Packet is delayed due to pending ARP or NDP resolution */ + continue; + } + + /* Remove it from the queue */ + ifqt = ifm->m_prev; + slirp_remque(ifm); + + /* If there are more packets for this session, re-queue them */ + if (ifm->m_nextpkt != ifm) { + struct mbuf *next = ifm->m_nextpkt; + + slirp_insque(next, ifqt); + ifs_remque(ifm); + if (!from_batchq) { + ifm_next = next; + } + } + + /* Update so_queued */ + if (ifm->m_so && --ifm->m_so->so_queued == 0) { + /* If there's no more queued, reset nqueued */ + ifm->m_so->so_nqueued = 0; + } + + m_free(ifm); + } + + slirp->if_start_busy = false; +} diff --git a/src/net/libslirp/src/if.h b/src/net/libslirp/src/if.h new file mode 100644 index 00000000..7cf9d275 --- /dev/null +++ b/src/net/libslirp/src/if.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#ifndef IF_H +#define IF_H + +#define IF_COMPRESS 0x01 /* We want compression */ +#define IF_NOCOMPRESS 0x02 /* Do not do compression */ +#define IF_AUTOCOMP 0x04 /* Autodetect (default) */ +#define IF_NOCIDCOMP 0x08 /* CID compression */ + +#define IF_MTU_DEFAULT 1500 +#define IF_MTU_MIN 68 +#define IF_MTU_MAX 65521 +#define IF_MRU_DEFAULT 1500 +#define IF_MRU_MIN 68 +#define IF_MRU_MAX 65521 +#define IF_COMP IF_AUTOCOMP /* Flags for compression */ + +/* 2 for alignment, 14 for ethernet */ +#define IF_MAXLINKHDR (2 + ETH_HLEN) + +#endif diff --git a/src/net/libslirp/src/ip.h b/src/net/libslirp/src/ip.h new file mode 100644 index 00000000..f0859f0c --- /dev/null +++ b/src/net/libslirp/src/ip.h @@ -0,0 +1,238 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip.h 8.1 (Berkeley) 6/10/93 + * ip.h,v 1.3 1994/08/21 05:27:30 paul Exp + */ + +#ifndef IP_H +#define IP_H + +#include + +#if G_BYTE_ORDER == G_BIG_ENDIAN +#undef NTOHL +#undef NTOHS +#undef HTONL +#undef HTONS +#define NTOHL(d) +#define NTOHS(d) +#define HTONL(d) +#define HTONS(d) +#else +#ifndef NTOHL +#define NTOHL(d) ((d) = ntohl((d))) +#endif +#ifndef NTOHS +#define NTOHS(d) ((d) = ntohs((uint16_t)(d))) +#endif +#ifndef HTONL +#define HTONL(d) ((d) = htonl((d))) +#endif +#ifndef HTONS +#define HTONS(d) ((d) = htons((uint16_t)(d))) +#endif +#endif + +typedef uint32_t n_long; /* long as received from the net */ + +/* + * Definitions for internet protocol version 4. + * Per RFC 791, September 1981. + */ +#define IPVERSION 4 + +/* + * Structure of an internet header, naked of options. + */ +SLIRP_PACKED_BEGIN +struct ip { +#if (G_BYTE_ORDER == G_BIG_ENDIAN) && !defined(_MSC_VER) + uint8_t ip_v : 4, /* version */ + ip_hl : 4; /* header length */ +#else + uint8_t ip_hl : 4, /* header length */ + ip_v : 4; /* version */ +#endif + uint8_t ip_tos; /* type of service */ + uint16_t ip_len; /* total length */ + uint16_t ip_id; /* identification */ + uint16_t ip_off; /* fragment offset field */ +#define IP_DF 0x4000 /* don't fragment flag */ +#define IP_MF 0x2000 /* more fragments flag */ +#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ + uint8_t ip_ttl; /* time to live */ + uint8_t ip_p; /* protocol */ + uint16_t ip_sum; /* checksum */ + struct in_addr ip_src, ip_dst; /* source and dest address */ +} SLIRP_PACKED_END; + +#define IP_MAXPACKET 65535 /* maximum packet size */ + +/* + * Definitions for IP type of service (ip_tos) + */ +#define IPTOS_LOWDELAY 0x10 +#define IPTOS_THROUGHPUT 0x08 +#define IPTOS_RELIABILITY 0x04 + +/* + * Definitions for options. + */ +#define IPOPT_COPIED(o) ((o)&0x80) +#define IPOPT_CLASS(o) ((o)&0x60) +#define IPOPT_NUMBER(o) ((o)&0x1f) + +#define IPOPT_CONTROL 0x00 +#define IPOPT_RESERVED1 0x20 +#define IPOPT_DEBMEAS 0x40 +#define IPOPT_RESERVED2 0x60 + +#define IPOPT_EOL 0 /* end of option list */ +#define IPOPT_NOP 1 /* no operation */ + +#define IPOPT_RR 7 /* record packet route */ +#define IPOPT_TS 68 /* timestamp */ +#define IPOPT_SECURITY 130 /* provide s,c,h,tcc */ +#define IPOPT_LSRR 131 /* loose source route */ +#define IPOPT_SATID 136 /* satnet id */ +#define IPOPT_SSRR 137 /* strict source route */ + +/* + * Offsets to fields in options other than EOL and NOP. + */ +#define IPOPT_OPTVAL 0 /* option ID */ +#define IPOPT_OLEN 1 /* option length */ +#define IPOPT_OFFSET 2 /* offset within option */ +#define IPOPT_MINOFF 4 /* min value of above */ + +/* + * Time stamp option structure. + */ +SLIRP_PACKED_BEGIN +struct ip_timestamp { + uint8_t ipt_code; /* IPOPT_TS */ + uint8_t ipt_len; /* size of structure (variable) */ + uint8_t ipt_ptr; /* index of current entry */ +#if (G_BYTE_ORDER == G_BIG_ENDIAN) && !defined(_MSC_VER) + uint8_t ipt_oflw : 4, /* overflow counter */ + ipt_flg : 4; /* flags, see below */ +#else + uint8_t ipt_flg : 4, /* flags, see below */ + ipt_oflw : 4; /* overflow counter */ +#endif + union ipt_timestamp { + n_long ipt_time[1]; + struct ipt_ta { + struct in_addr ipt_addr; + n_long ipt_time; + } ipt_ta[1]; + } ipt_timestamp; +} SLIRP_PACKED_END; + +/* flag bits for ipt_flg */ +#define IPOPT_TS_TSONLY 0 /* timestamps only */ +#define IPOPT_TS_TSANDADDR 1 /* timestamps and addresses */ +#define IPOPT_TS_PRESPEC 3 /* specified modules only */ + +/* bits for security (not byte swapped) */ +#define IPOPT_SECUR_UNCLASS 0x0000 +#define IPOPT_SECUR_CONFID 0xf135 +#define IPOPT_SECUR_EFTO 0x789a +#define IPOPT_SECUR_MMMM 0xbc4d +#define IPOPT_SECUR_RESTR 0xaf13 +#define IPOPT_SECUR_SECRET 0xd788 +#define IPOPT_SECUR_TOPSECRET 0x6bc5 + +/* + * Internet implementation parameters. + */ +#define MAXTTL 255 /* maximum time to live (seconds) */ +#define IPDEFTTL 64 /* default ttl, from RFC 1340 */ +#define IPFRAGTTL 60 /* time to live for frags, slowhz */ +#define IPTTLDEC 1 /* subtracted when forwarding */ + +#define IP_MSS 576 /* default maximum segment size */ + +#if GLIB_SIZEOF_VOID_P == 4 +SLIRP_PACKED_BEGIN +struct mbuf_ptr { + struct mbuf *mptr; + uint32_t dummy; +} SLIRP_PACKED_END; +#else +SLIRP_PACKED_BEGIN +struct mbuf_ptr { + struct mbuf *mptr; +} SLIRP_PACKED_END; +#endif +struct qlink { + void *next, *prev; +}; + +/* + * Overlay for ip header used by other protocols (tcp, udp). + */ +SLIRP_PACKED_BEGIN +struct ipovly { + struct mbuf_ptr ih_mbuf; /* backpointer to mbuf */ + uint8_t ih_x1; /* (unused) */ + uint8_t ih_pr; /* protocol */ + uint16_t ih_len; /* protocol length */ + struct in_addr ih_src; /* source internet address */ + struct in_addr ih_dst; /* destination internet address */ +} SLIRP_PACKED_END; + +/* + * Ip reassembly queue structure. Each fragment + * being reassembled is attached to one of these structures. + * They are timed out after ipq_ttl drops to 0, and may also + * be reclaimed if memory becomes tight. + */ +struct ipq { + struct qlink ip_link; /* to other reass headers */ + uint8_t ipq_ttl; /* time for reass q to live */ + uint8_t ipq_p; /* protocol of this fragment */ + uint16_t ipq_id; /* sequence id for reassembly */ + struct in_addr ipq_src, ipq_dst; +}; + +struct ipas { + struct qlink link; + union { + struct ipq ipq; + struct ip ipf_ip; + }; +}; + +#define ipf_off ipf_ip.ip_off +#define ipf_tos ipf_ip.ip_tos +#define ipf_len ipf_ip.ip_len + +#endif diff --git a/src/net/libslirp/src/ip6.h b/src/net/libslirp/src/ip6.h new file mode 100644 index 00000000..50765e6a --- /dev/null +++ b/src/net/libslirp/src/ip6.h @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#ifndef SLIRP_IP6_H +#define SLIRP_IP6_H + +#include +#include + +#include "util.h" + +#define ALLNODES_MULTICAST \ + { \ + .s6_addr = { \ + 0xff, \ + 0x02, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x01 \ + } \ + } + +#define SOLICITED_NODE_PREFIX \ + { \ + .s6_addr = { \ + 0xff, \ + 0x02, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x01, \ + 0xff, \ + 0x00, \ + 0x00, \ + 0x00 \ + } \ + } + +#define LINKLOCAL_ADDR \ + { \ + .s6_addr = { \ + 0xfe, \ + 0x80, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x02 \ + } \ + } + +#define ZERO_ADDR \ + { \ + .s6_addr = { \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00, \ + 0x00 \ + } \ + } + +/* Check that two IPv6 addresses are equal */ +static inline bool in6_equal(const struct in6_addr *a, const struct in6_addr *b) +{ + return memcmp(a, b, sizeof(*a)) == 0; +} + +/* Check that two IPv6 addresses are equal in their network part */ +static inline bool in6_equal_net(const struct in6_addr *a, + const struct in6_addr *b, int prefix_len) +{ + if (memcmp(a, b, prefix_len / 8) != 0) { + return 0; + } + + if (prefix_len % 8 == 0) { + return 1; + } + + return a->s6_addr[prefix_len / 8] >> (8 - (prefix_len % 8)) == + b->s6_addr[prefix_len / 8] >> (8 - (prefix_len % 8)); +} + +/* Check that two IPv6 addresses are equal in their machine part */ +static inline bool in6_equal_mach(const struct in6_addr *a, + const struct in6_addr *b, int prefix_len) +{ + if (memcmp(&(a->s6_addr[DIV_ROUND_UP(prefix_len, 8)]), + &(b->s6_addr[DIV_ROUND_UP(prefix_len, 8)]), + 16 - DIV_ROUND_UP(prefix_len, 8)) != 0) { + return 0; + } + + if (prefix_len % 8 == 0) { + return 1; + } + + return (a->s6_addr[prefix_len / 8] & + ((1U << (8 - (prefix_len % 8))) - 1)) == + (b->s6_addr[prefix_len / 8] & ((1U << (8 - (prefix_len % 8))) - 1)); +} + +/* Check that the IPv6 is equal to the virtual router */ +#define in6_equal_router(a) \ + ((in6_equal_net(a, &slirp->vprefix_addr6, slirp->vprefix_len) && \ + in6_equal_mach(a, &slirp->vhost_addr6, slirp->vprefix_len)) || \ + (in6_equal_net(a, &(struct in6_addr)LINKLOCAL_ADDR, 64) && \ + in6_equal_mach(a, &slirp->vhost_addr6, 64))) + +/* Check that the IPv6 is equal to the virtual DNS server */ +#define in6_equal_dns(a) \ + ((in6_equal_net(a, &slirp->vprefix_addr6, slirp->vprefix_len) && \ + in6_equal_mach(a, &slirp->vnameserver_addr6, slirp->vprefix_len)) || \ + (in6_equal_net(a, &(struct in6_addr)LINKLOCAL_ADDR, 64) && \ + in6_equal_mach(a, &slirp->vnameserver_addr6, 64))) + +/* Check that the IPv6 is equal to the host */ +#define in6_equal_host(a) (in6_equal_router(a) || in6_equal_dns(a)) + +/* Check that the IPv6 is within the sollicited node multicast network */ +#define in6_solicitednode_multicast(a) \ + (in6_equal_net(a, &(struct in6_addr)SOLICITED_NODE_PREFIX, 104)) + +/* Check that the IPv6 is zero */ +#define in6_zero(a) (in6_equal(a, &(struct in6_addr)ZERO_ADDR)) + +/* Compute emulated host MAC address from its ipv6 address */ +static inline void in6_compute_ethaddr(struct in6_addr ip, + uint8_t eth[ETH_ALEN]) +{ + eth[0] = 0x52; + eth[1] = 0x56; + memcpy(ð[2], &ip.s6_addr[16 - (ETH_ALEN - 2)], ETH_ALEN - 2); +} + +/* + * Definitions for internet protocol version 6. + * Per RFC 2460, December 1998. + */ +#define IP6VERSION 6 +#define IP6_HOP_LIMIT 255 + +/* + * Structure of an internet header, naked of options. + */ +struct ip6 { +#if (G_BYTE_ORDER == G_BIG_ENDIAN) && !defined(_MSC_VER) + uint8_t ip_v : 4, /* version */ + ip_tc_hi : 4; /* traffic class */ + uint8_t ip_tc_lo : 4, ip_fl_hi : 4; /* flow label */ +#else + uint8_t ip_tc_hi : 4, ip_v : 4; + uint8_t ip_fl_hi : 4, ip_tc_lo : 4; +#endif + uint16_t ip_fl_lo; + uint16_t ip_pl; /* payload length */ + uint8_t ip_nh; /* next header */ + uint8_t ip_hl; /* hop limit */ + struct in6_addr ip_src, ip_dst; /* source and dest address */ +}; + +/* + * IPv6 pseudo-header used by upper-layer protocols + */ +struct ip6_pseudohdr { + struct in6_addr ih_src; /* source internet address */ + struct in6_addr ih_dst; /* destination internet address */ + uint32_t ih_pl; /* upper-layer packet length */ + uint16_t ih_zero_hi; /* zero */ + uint8_t ih_zero_lo; /* zero */ + uint8_t ih_nh; /* next header */ +}; + +/* + * We don't want to mark these ip6 structs as packed as they are naturally + * correctly aligned; instead assert that there is no stray padding. + * If we marked the struct as packed then we would be unable to take + * the address of any of the fields in it. + */ +G_STATIC_ASSERT(sizeof(struct ip6) == 40); +G_STATIC_ASSERT(sizeof(struct ip6_pseudohdr) == 40); + +#endif diff --git a/src/net/libslirp/src/ip6_icmp.c b/src/net/libslirp/src/ip6_icmp.c new file mode 100644 index 00000000..3a5878fd --- /dev/null +++ b/src/net/libslirp/src/ip6_icmp.c @@ -0,0 +1,652 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#include "slirp.h" +#include "ip6_icmp.h" + +#define NDP_Interval \ + g_rand_int_range(slirp->grand, NDP_MinRtrAdvInterval, NDP_MaxRtrAdvInterval) + +/* The message sent when emulating PING */ +/* Be nice and tell them it's just a pseudo-ping packet */ +static const char icmp6_ping_msg[] = + "This is a pseudo-PING packet used by Slirp to emulate ICMPV6 ECHO-REQUEST " + "packets.\n"; + +void icmp6_post_init(Slirp *slirp) +{ + if (!slirp->in6_enabled) { + return; + } + + slirp->ra_timer = + slirp_timer_new(slirp, SLIRP_TIMER_RA, NULL); + slirp->cb->timer_mod(slirp->ra_timer, + slirp->cb->clock_get_ns(slirp->opaque) / SCALE_MS + + NDP_Interval, + slirp->opaque); +} + +void icmp6_cleanup(Slirp *slirp) +{ + if (!slirp->in6_enabled) { + return; + } + + slirp->cb->timer_free(slirp->ra_timer, slirp->opaque); +} + +/* Send ICMP packet to the Internet, and save it to so_m */ +static int icmp6_send(struct socket *so, struct mbuf *m, int hlen) +{ + Slirp *slirp = m->slirp; + + struct sockaddr_in6 addr; + + /* + * The behavior of reading SOCK_DGRAM+IPPROTO_ICMP sockets is inconsistent + * between host OSes. On Linux, only the ICMP header and payload is + * included. On macOS/Darwin, the socket acts like a raw socket and + * includes the IP header as well. On other BSDs, SOCK_DGRAM+IPPROTO_ICMP + * sockets aren't supported at all, so we treat them like raw sockets. It + * isn't possible to detect this difference at runtime, so we must use an + * #ifdef to determine if we need to remove the IP header. + */ +#if defined(BSD) && !defined(__GNU__) + so->so_type = IPPROTO_IPV6; +#else + so->so_type = IPPROTO_ICMPV6; +#endif + + so->s = slirp_socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6); + if (so->s == -1) { + if (errno == EAFNOSUPPORT + || errno == EPROTONOSUPPORT + || errno == EACCES) { + /* Kernel doesn't support or allow ping sockets. */ + so->so_type = IPPROTO_IPV6; + so->s = slirp_socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + } + } + if (so->s == -1) { + return -1; + } + so->slirp->cb->register_poll_fd(so->s, so->slirp->opaque); + + if (slirp_bind_outbound(so, AF_INET6) != 0) { + // bind failed - close socket + closesocket(so->s); + so->s = -1; + return -1; + } + + M_DUP_DEBUG(slirp, m, 0, 0); + struct ip6 *ip = mtod(m, struct ip6 *); + + so->so_m = m; + so->so_faddr6 = ip->ip_dst; + so->so_laddr6 = ip->ip_src; + so->so_state = SS_ISFCONNECTED; + so->so_expire = curtime + SO_EXPIRE; + + addr.sin6_family = AF_INET6; + addr.sin6_addr = so->so_faddr6; + + slirp_insque(so, &so->slirp->icmp); + + if (sendto(so->s, m->m_data + hlen, m->m_len - hlen, 0, + (struct sockaddr *)&addr, sizeof(addr)) == -1) { + DEBUG_MISC("icmp6_input icmp sendto tx errno = %d-%s", errno, + strerror(errno)); + icmp6_send_error(m, ICMP6_UNREACH, ICMP6_UNREACH_NO_ROUTE); + icmp_detach(so); + } + + return 0; +} + +static void icmp6_send_echoreply(struct mbuf *m, Slirp *slirp, struct ip6 *ip, + struct icmp6 *icmp) +{ + struct mbuf *t = m_get(slirp); + t->m_len = sizeof(struct ip6) + ntohs(ip->ip_pl); + memcpy(t->m_data, m->m_data, t->m_len); + + /* IPv6 Packet */ + struct ip6 *rip = mtod(t, struct ip6 *); + rip->ip_dst = ip->ip_src; + rip->ip_src = ip->ip_dst; + + /* ICMPv6 packet */ + t->m_data += sizeof(struct ip6); + struct icmp6 *ricmp = mtod(t, struct icmp6 *); + ricmp->icmp6_type = ICMP6_ECHO_REPLY; + ricmp->icmp6_cksum = 0; + + /* Checksum */ + t->m_data -= sizeof(struct ip6); + ricmp->icmp6_cksum = ip6_cksum(t); + + ip6_output(NULL, t, 0); +} + +void icmp6_forward_error(struct mbuf *m, uint8_t type, uint8_t code, struct in6_addr *src) +{ + Slirp *slirp = m->slirp; + struct mbuf *t; + struct ip6 *ip = mtod(m, struct ip6 *); + char addrstr[INET6_ADDRSTRLEN]; + + DEBUG_CALL("icmp6_send_error"); + DEBUG_ARG("type = %d, code = %d", type, code); + + if (IN6_IS_ADDR_MULTICAST(&ip->ip_src) || in6_zero(&ip->ip_src)) { + /* TODO icmp error? */ + return; + } + + t = m_get(slirp); + + /* IPv6 packet */ + struct ip6 *rip = mtod(t, struct ip6 *); + rip->ip_src = *src; + rip->ip_dst = ip->ip_src; + inet_ntop(AF_INET6, &rip->ip_dst, addrstr, INET6_ADDRSTRLEN); + DEBUG_ARG("target = %s", addrstr); + + rip->ip_nh = IPPROTO_ICMPV6; + const int error_data_len = MIN( + m->m_len, slirp->if_mtu - (sizeof(struct ip6) + ICMP6_ERROR_MINLEN)); + rip->ip_pl = htons(ICMP6_ERROR_MINLEN + error_data_len); + t->m_len = sizeof(struct ip6) + ntohs(rip->ip_pl); + + /* ICMPv6 packet */ + t->m_data += sizeof(struct ip6); + struct icmp6 *ricmp = mtod(t, struct icmp6 *); + ricmp->icmp6_type = type; + ricmp->icmp6_code = code; + ricmp->icmp6_cksum = 0; + + switch (type) { + case ICMP6_UNREACH: + case ICMP6_TIMXCEED: + ricmp->icmp6_err.unused = 0; + break; + case ICMP6_TOOBIG: + ricmp->icmp6_err.mtu = htonl(slirp->if_mtu); + break; + case ICMP6_PARAMPROB: + /* TODO: Handle this case */ + break; + default: + g_assert_not_reached(); + } + t->m_data += ICMP6_ERROR_MINLEN; + memcpy(t->m_data, m->m_data, error_data_len); + + /* Checksum */ + t->m_data -= ICMP6_ERROR_MINLEN; + t->m_data -= sizeof(struct ip6); + ricmp->icmp6_cksum = ip6_cksum(t); + + ip6_output(NULL, t, 0); +} + +void icmp6_send_error(struct mbuf *m, uint8_t type, uint8_t code) +{ + struct in6_addr src = LINKLOCAL_ADDR; + icmp6_forward_error(m, type, code, &src); +} + +/* + * Reflect the ip packet back to the source + */ +void icmp6_reflect(struct mbuf *m) +{ + register struct ip6 *ip = mtod(m, struct ip6 *); + int hlen = sizeof(struct ip6); + register struct icmp6 *icp; + + /* + * Send an icmp packet back to the ip level, + * after supplying a checksum. + */ + m->m_data += hlen; + m->m_len -= hlen; + icp = mtod(m, struct icmp6 *); + + icp->icmp6_type = ICMP6_ECHO_REPLY; + + m->m_data -= hlen; + m->m_len += hlen; + + icp->icmp6_cksum = 0; + icp->icmp6_cksum = ip6_cksum(m); + + ip->ip_hl = MAXTTL; + { /* swap */ + struct in6_addr icmp_dst; + icmp_dst = ip->ip_dst; + ip->ip_dst = ip->ip_src; + ip->ip_src = icmp_dst; + } + + ip6_output((struct socket *)NULL, m, 0); +} + +void icmp6_receive(struct socket *so) +{ + struct mbuf *m = so->so_m; + int hlen = sizeof(struct ip6); + uint8_t error_code; + struct icmp6 *icp; + int id, seq, len; + + m->m_data += hlen; + m->m_len -= hlen; + icp = mtod(m, struct icmp6 *); + + id = icp->icmp6_id; + seq = icp->icmp6_seq; + len = recv(so->s, icp, M_ROOM(m), 0); + + icp->icmp6_id = id; + icp->icmp6_seq = seq; + + m->m_data -= hlen; + m->m_len += hlen; + + if (len == -1 || len == 0) { + if (errno == ENETUNREACH) { + error_code = ICMP6_UNREACH_NO_ROUTE; + } else { + error_code = ICMP6_UNREACH_ADDRESS; + } + DEBUG_MISC(" udp icmp rx errno = %d-%s", errno, strerror(errno)); + icmp6_send_error(so->so_m, ICMP_UNREACH, error_code); + } else { + icmp6_reflect(so->so_m); + so->so_m = NULL; /* Don't m_free() it again! */ + } + icmp_detach(so); +} + +/* + * Send NDP Router Advertisement + */ +static void ndp_send_ra(Slirp *slirp) +{ + DEBUG_CALL("ndp_send_ra"); + + /* Build IPv6 packet */ + struct mbuf *t = m_get(slirp); + struct ip6 *rip = mtod(t, struct ip6 *); + size_t pl_size = 0; + struct in6_addr addr; + uint32_t scope_id; + + rip->ip_src = (struct in6_addr)LINKLOCAL_ADDR; + rip->ip_dst = (struct in6_addr)ALLNODES_MULTICAST; + rip->ip_nh = IPPROTO_ICMPV6; + + /* Build ICMPv6 packet */ + t->m_data += sizeof(struct ip6); + struct icmp6 *ricmp = mtod(t, struct icmp6 *); + ricmp->icmp6_type = ICMP6_NDP_RA; + ricmp->icmp6_code = 0; + ricmp->icmp6_cksum = 0; + + /* NDP */ + ricmp->icmp6_nra.chl = NDP_AdvCurHopLimit; + ricmp->icmp6_nra.M = NDP_AdvManagedFlag; + ricmp->icmp6_nra.O = NDP_AdvOtherConfigFlag; + ricmp->icmp6_nra.reserved = 0; + ricmp->icmp6_nra.lifetime = htons(NDP_AdvDefaultLifetime); + ricmp->icmp6_nra.reach_time = htonl(NDP_AdvReachableTime); + ricmp->icmp6_nra.retrans_time = htonl(NDP_AdvRetransTime); + t->m_data += ICMP6_NDP_RA_MINLEN; + pl_size += ICMP6_NDP_RA_MINLEN; + + /* Source link-layer address (NDP option) */ + struct ndpopt *opt = mtod(t, struct ndpopt *); + opt->ndpopt_type = NDPOPT_LINKLAYER_SOURCE; + opt->ndpopt_len = NDPOPT_LINKLAYER_LEN / 8; + in6_compute_ethaddr(rip->ip_src, opt->ndpopt_linklayer); + t->m_data += NDPOPT_LINKLAYER_LEN; + pl_size += NDPOPT_LINKLAYER_LEN; + + /* Prefix information (NDP option) */ + struct ndpopt *opt2 = mtod(t, struct ndpopt *); + opt2->ndpopt_type = NDPOPT_PREFIX_INFO; + opt2->ndpopt_len = NDPOPT_PREFIXINFO_LEN / 8; + opt2->ndpopt_prefixinfo.prefix_length = slirp->vprefix_len; + opt2->ndpopt_prefixinfo.L = 1; + opt2->ndpopt_prefixinfo.A = 1; + opt2->ndpopt_prefixinfo.reserved1 = 0; + opt2->ndpopt_prefixinfo.valid_lt = htonl(NDP_AdvValidLifetime); + opt2->ndpopt_prefixinfo.pref_lt = htonl(NDP_AdvPrefLifetime); + opt2->ndpopt_prefixinfo.reserved2 = 0; + opt2->ndpopt_prefixinfo.prefix = slirp->vprefix_addr6; + t->m_data += NDPOPT_PREFIXINFO_LEN; + pl_size += NDPOPT_PREFIXINFO_LEN; + + /* Prefix information (NDP option) */ + if (get_dns6_addr(&addr, &scope_id) >= 0) { + /* Host system does have an IPv6 DNS server, announce our proxy. */ + struct ndpopt *opt3 = mtod(t, struct ndpopt *); + opt3->ndpopt_type = NDPOPT_RDNSS; + opt3->ndpopt_len = NDPOPT_RDNSS_LEN / 8; + opt3->ndpopt_rdnss.reserved = 0; + opt3->ndpopt_rdnss.lifetime = htonl(2 * NDP_MaxRtrAdvInterval); + opt3->ndpopt_rdnss.addr = slirp->vnameserver_addr6; + t->m_data += NDPOPT_RDNSS_LEN; + pl_size += NDPOPT_RDNSS_LEN; + } + + rip->ip_pl = htons(pl_size); + t->m_data -= sizeof(struct ip6) + pl_size; + t->m_len = sizeof(struct ip6) + pl_size; + + /* ICMPv6 Checksum */ + ricmp->icmp6_cksum = ip6_cksum(t); + + ip6_output(NULL, t, 0); +} + +void ra_timer_handler(Slirp *slirp, void *unused) +{ + slirp->cb->timer_mod(slirp->ra_timer, + slirp->cb->clock_get_ns(slirp->opaque) / SCALE_MS + + NDP_Interval, + slirp->opaque); + ndp_send_ra(slirp); +} + +/* + * Send NDP Neighbor Solitication + */ +void ndp_send_ns(Slirp *slirp, struct in6_addr addr) +{ + char addrstr[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, &addr, addrstr, INET6_ADDRSTRLEN); + + DEBUG_CALL("ndp_send_ns"); + DEBUG_ARG("target = %s", addrstr); + + /* Build IPv6 packet */ + struct mbuf *t = m_get(slirp); + struct ip6 *rip = mtod(t, struct ip6 *); + rip->ip_src = slirp->vhost_addr6; + rip->ip_dst = (struct in6_addr)SOLICITED_NODE_PREFIX; + memcpy(&rip->ip_dst.s6_addr[13], &addr.s6_addr[13], 3); + rip->ip_nh = IPPROTO_ICMPV6; + rip->ip_pl = htons(ICMP6_NDP_NS_MINLEN + NDPOPT_LINKLAYER_LEN); + t->m_len = sizeof(struct ip6) + ntohs(rip->ip_pl); + + /* Build ICMPv6 packet */ + t->m_data += sizeof(struct ip6); + struct icmp6 *ricmp = mtod(t, struct icmp6 *); + ricmp->icmp6_type = ICMP6_NDP_NS; + ricmp->icmp6_code = 0; + ricmp->icmp6_cksum = 0; + + /* NDP */ + ricmp->icmp6_nns.reserved = 0; + ricmp->icmp6_nns.target = addr; + + /* Build NDP option */ + t->m_data += ICMP6_NDP_NS_MINLEN; + struct ndpopt *opt = mtod(t, struct ndpopt *); + opt->ndpopt_type = NDPOPT_LINKLAYER_SOURCE; + opt->ndpopt_len = NDPOPT_LINKLAYER_LEN / 8; + in6_compute_ethaddr(slirp->vhost_addr6, opt->ndpopt_linklayer); + + /* ICMPv6 Checksum */ + t->m_data -= ICMP6_NDP_NA_MINLEN; + t->m_data -= sizeof(struct ip6); + ricmp->icmp6_cksum = ip6_cksum(t); + + ip6_output(NULL, t, 1); +} + +/* + * Send NDP Neighbor Advertisement + */ +static void ndp_send_na(Slirp *slirp, struct ip6 *ip, struct icmp6 *icmp) +{ + /* Build IPv6 packet */ + struct mbuf *t = m_get(slirp); + struct ip6 *rip = mtod(t, struct ip6 *); + rip->ip_src = icmp->icmp6_nns.target; + if (in6_zero(&ip->ip_src)) { + rip->ip_dst = (struct in6_addr)ALLNODES_MULTICAST; + } else { + rip->ip_dst = ip->ip_src; + } + rip->ip_nh = IPPROTO_ICMPV6; + rip->ip_pl = htons(ICMP6_NDP_NA_MINLEN + NDPOPT_LINKLAYER_LEN); + t->m_len = sizeof(struct ip6) + ntohs(rip->ip_pl); + + /* Build ICMPv6 packet */ + t->m_data += sizeof(struct ip6); + struct icmp6 *ricmp = mtod(t, struct icmp6 *); + ricmp->icmp6_type = ICMP6_NDP_NA; + ricmp->icmp6_code = 0; + ricmp->icmp6_cksum = 0; + + /* NDP */ + ricmp->icmp6_nna.R = NDP_IsRouter; + ricmp->icmp6_nna.S = !IN6_IS_ADDR_MULTICAST(&rip->ip_dst); + ricmp->icmp6_nna.O = 1; + ricmp->icmp6_nna.reserved_1 = 0; + ricmp->icmp6_nna.reserved_2 = 0; + ricmp->icmp6_nna.reserved_3 = 0; + ricmp->icmp6_nna.target = icmp->icmp6_nns.target; + + /* Build NDP option */ + t->m_data += ICMP6_NDP_NA_MINLEN; + struct ndpopt *opt = mtod(t, struct ndpopt *); + opt->ndpopt_type = NDPOPT_LINKLAYER_TARGET; + opt->ndpopt_len = NDPOPT_LINKLAYER_LEN / 8; + in6_compute_ethaddr(ricmp->icmp6_nna.target, opt->ndpopt_linklayer); + + /* ICMPv6 Checksum */ + t->m_data -= ICMP6_NDP_NA_MINLEN; + t->m_data -= sizeof(struct ip6); + ricmp->icmp6_cksum = ip6_cksum(t); + + ip6_output(NULL, t, 0); +} + +/* + * Process a NDP message + */ +static void ndp_input(struct mbuf *m, Slirp *slirp, struct ip6 *ip, + struct icmp6 *icmp) +{ + g_assert(M_ROOMBEFORE(m) >= ETH_HLEN); + + m->m_len += ETH_HLEN; + m->m_data -= ETH_HLEN; + struct ethhdr *eth = mtod(m, struct ethhdr *); + m->m_len -= ETH_HLEN; + m->m_data += ETH_HLEN; + + switch (icmp->icmp6_type) { + case ICMP6_NDP_RS: + DEBUG_CALL(" type = Router Solicitation"); + if (ip->ip_hl == 255 && icmp->icmp6_code == 0 && + ntohs(ip->ip_pl) >= ICMP6_NDP_RS_MINLEN) { + /* Gratuitous NDP */ + ndp_table_add(slirp, ip->ip_src, eth->h_source); + + ndp_send_ra(slirp); + } + break; + + case ICMP6_NDP_RA: + DEBUG_CALL(" type = Router Advertisement"); + slirp->cb->guest_error("Warning: guest sent NDP RA, but shouldn't", + slirp->opaque); + break; + + case ICMP6_NDP_NS: + DEBUG_CALL(" type = Neighbor Solicitation"); + if (ip->ip_hl == 255 && icmp->icmp6_code == 0 && + !IN6_IS_ADDR_MULTICAST(&icmp->icmp6_nns.target) && + ntohs(ip->ip_pl) >= ICMP6_NDP_NS_MINLEN && + (!in6_zero(&ip->ip_src) || + in6_solicitednode_multicast(&ip->ip_dst))) { + if (in6_equal_host(&icmp->icmp6_nns.target)) { + /* Gratuitous NDP */ + ndp_table_add(slirp, ip->ip_src, eth->h_source); + ndp_send_na(slirp, ip, icmp); + } + } + break; + + case ICMP6_NDP_NA: + DEBUG_CALL(" type = Neighbor Advertisement"); + if (ip->ip_hl == 255 && icmp->icmp6_code == 0 && + ntohs(ip->ip_pl) >= ICMP6_NDP_NA_MINLEN && + !IN6_IS_ADDR_MULTICAST(&icmp->icmp6_nna.target) && + (!IN6_IS_ADDR_MULTICAST(&ip->ip_dst) || icmp->icmp6_nna.S == 0)) { + ndp_table_add(slirp, icmp->icmp6_nna.target, eth->h_source); + } + break; + + case ICMP6_NDP_REDIRECT: + DEBUG_CALL(" type = Redirect"); + slirp->cb->guest_error( + "Warning: guest sent NDP REDIRECT, but shouldn't", slirp->opaque); + break; + } +} + +/* + * Process a received ICMPv6 message. + */ +void icmp6_input(struct mbuf *m) +{ + Slirp *slirp = m->slirp; + /* NDP reads the ethernet header for gratuitous NDP */ + M_DUP_DEBUG(slirp, m, 1, ETH_HLEN); + + struct icmp6 *icmp; + struct ip6 *ip = mtod(m, struct ip6 *); + int hlen = sizeof(struct ip6); + + DEBUG_CALL("icmp6_input"); + DEBUG_ARG("m = %p", m); + DEBUG_ARG("m_len = %d", m->m_len); + + if (ntohs(ip->ip_pl) < ICMP6_MINLEN) { + freeit: + m_free(m); + goto end_error; + } + + if (ip6_cksum(m)) { + goto freeit; + } + + m->m_len -= hlen; + m->m_data += hlen; + icmp = mtod(m, struct icmp6 *); + m->m_len += hlen; + m->m_data -= hlen; + + DEBUG_ARG("icmp6_type = %d", icmp->icmp6_type); + switch (icmp->icmp6_type) { + case ICMP6_ECHO_REQUEST: + if (in6_equal_host(&ip->ip_dst)) { + icmp6_send_echoreply(m, slirp, ip, icmp); + } else if (slirp->restricted) { + goto freeit; + } else { + struct socket *so; + struct sockaddr_storage addr; + int ttl; + + so = socreate(slirp, IPPROTO_ICMPV6); + if (icmp6_send(so, m, hlen) == 0) { + /* We could send this as ICMP, good! */ + return; + } + + /* We could not send this as ICMP, try to send it on UDP echo + * service (7), wishfully hoping that it is open there. */ + + if (udp_attach(so, AF_INET6) == -1) { + DEBUG_MISC("icmp6_input udp_attach errno = %d-%s", errno, + strerror(errno)); + sofree(so); + m_free(m); + goto end_error; + } + so->so_m = m; + so->so_ffamily = AF_INET6; + so->so_faddr6 = ip->ip_dst; + so->so_fport = htons(7); + so->so_lfamily = AF_INET6; + so->so_laddr6 = ip->ip_src; + so->so_lport = htons(9); + so->so_state = SS_ISFCONNECTED; + + /* Send the packet */ + addr = so->fhost.ss; + if (sotranslate_out(so, &addr) < 0) { + icmp6_send_error(m, ICMP6_UNREACH, ICMP6_UNREACH_NO_ROUTE); + udp_detach(so); + return; + } + + /* + * Check for TTL + */ + ttl = ip->ip_hl-1; + if (ttl <= 0) { + DEBUG_MISC("udp ttl exceeded"); + icmp6_send_error(m, ICMP6_TIMXCEED, ICMP6_TIMXCEED_INTRANS); + udp_detach(so); + break; + } + setsockopt(so->s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); + + if (sendto(so->s, icmp6_ping_msg, strlen(icmp6_ping_msg), 0, + (struct sockaddr *)&addr, sockaddr_size(&addr)) == -1) { + DEBUG_MISC("icmp6_input udp sendto tx errno = %d-%s", errno, + strerror(errno)); + icmp6_send_error(m, ICMP6_UNREACH, ICMP6_UNREACH_NO_ROUTE); + udp_detach(so); + } + } /* if (in6_equal_host(&ip->ip_dst)) */ + break; + + case ICMP6_NDP_RS: + case ICMP6_NDP_RA: + case ICMP6_NDP_NS: + case ICMP6_NDP_NA: + case ICMP6_NDP_REDIRECT: + ndp_input(m, slirp, ip, icmp); + m_free(m); + break; + + case ICMP6_UNREACH: + case ICMP6_TOOBIG: + case ICMP6_TIMXCEED: + case ICMP6_PARAMPROB: + /* XXX? report error? close socket? */ + default: + m_free(m); + break; + } + +end_error: + /* m is m_free()'d xor put in a socket xor or given to ip_send */ + return; +} diff --git a/src/net/libslirp/src/ip6_icmp.h b/src/net/libslirp/src/ip6_icmp.h new file mode 100644 index 00000000..7f8bc60b --- /dev/null +++ b/src/net/libslirp/src/ip6_icmp.h @@ -0,0 +1,246 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#ifndef SLIRP_IP6_ICMP_H +#define SLIRP_IP6_ICMP_H + +/* + * Interface Control Message Protocol version 6 Definitions. + * Per RFC 4443, March 2006. + * + * Network Discover Protocol Definitions. + * Per RFC 4861, September 2007. + */ + +struct icmp6_echo { /* Echo Messages */ + uint16_t id; + uint16_t seq_num; +}; + +union icmp6_error_body { + uint32_t unused; + uint32_t pointer; + uint32_t mtu; +}; + +/* + * NDP Messages + */ +struct ndp_rs { /* Router Solicitation Message */ + uint32_t reserved; +}; + +struct ndp_ra { /* Router Advertisement Message */ + uint8_t chl; /* Cur Hop Limit */ +#if (G_BYTE_ORDER == G_BIG_ENDIAN) && !defined(_MSC_VER) + uint8_t M : 1, O : 1, reserved : 6; +#else + uint8_t reserved : 6, O : 1, M : 1; +#endif + uint16_t lifetime; /* Router Lifetime */ + uint32_t reach_time; /* Reachable Time */ + uint32_t retrans_time; /* Retrans Timer */ +}; + +G_STATIC_ASSERT(sizeof(struct ndp_ra) == 12); + +struct ndp_ns { /* Neighbor Solicitation Message */ + uint32_t reserved; + struct in6_addr target; /* Target Address */ +}; + +G_STATIC_ASSERT(sizeof(struct ndp_ns) == 20); + +struct ndp_na { /* Neighbor Advertisement Message */ +#if (G_BYTE_ORDER == G_BIG_ENDIAN) && !defined(_MSC_VER) + uint8_t R : 1, /* Router Flag */ + S : 1, /* Solicited Flag */ + O : 1, /* Override Flag */ + reserved_1 : 5; +#else + uint8_t reserved_1 : 5, O : 1, S : 1, R : 1; +#endif + uint8_t reserved_2; + uint16_t reserved_3; + struct in6_addr target; /* Target Address */ +}; + +G_STATIC_ASSERT(sizeof(struct ndp_na) == 20); + +struct ndp_redirect { + uint32_t reserved; + struct in6_addr target; /* Target Address */ + struct in6_addr dest; /* Destination Address */ +}; + +G_STATIC_ASSERT(sizeof(struct ndp_redirect) == 36); + +/* + * Structure of an icmpv6 header. + */ +struct icmp6 { + uint8_t icmp6_type; /* type of message, see below */ + uint8_t icmp6_code; /* type sub code */ + uint16_t icmp6_cksum; /* ones complement cksum of struct */ + union { + union icmp6_error_body error_body; + struct icmp6_echo echo; + struct ndp_rs ndp_rs; + struct ndp_ra ndp_ra; + struct ndp_ns ndp_ns; + struct ndp_na ndp_na; + struct ndp_redirect ndp_redirect; + } icmp6_body; +#define icmp6_err icmp6_body.error_body +#define icmp6_echo icmp6_body.echo +#define icmp6_id icmp6_body.echo.id +#define icmp6_seq icmp6_body.echo.seq_num +#define icmp6_nrs icmp6_body.ndp_rs +#define icmp6_nra icmp6_body.ndp_ra +#define icmp6_nns icmp6_body.ndp_ns +#define icmp6_nna icmp6_body.ndp_na +#define icmp6_redirect icmp6_body.ndp_redirect +}; + +G_STATIC_ASSERT(sizeof(struct icmp6) == 40); + +#define ICMP6_MINLEN 4 +#define ICMP6_ERROR_MINLEN 8 +#define ICMP6_ECHO_MINLEN 8 +#define ICMP6_NDP_RS_MINLEN 8 +#define ICMP6_NDP_RA_MINLEN 16 +#define ICMP6_NDP_NS_MINLEN 24 +#define ICMP6_NDP_NA_MINLEN 24 +#define ICMP6_NDP_REDIRECT_MINLEN 40 + +/* + * NDP Options + */ +SLIRP_PACKED_BEGIN +struct ndpopt { + uint8_t ndpopt_type; /* Option type */ + uint8_t ndpopt_len; /* /!\ In units of 8 octets */ + union { + unsigned char linklayer_addr[6]; /* Source/Target Link-layer */ +#define ndpopt_linklayer ndpopt_body.linklayer_addr + SLIRP_PACKED_BEGIN + struct prefixinfo { /* Prefix Information */ + uint8_t prefix_length; +#if (G_BYTE_ORDER == G_BIG_ENDIAN) && !defined(_MSC_VER) + uint8_t L : 1, A : 1, reserved1 : 6; +#else + uint8_t reserved1 : 6, A : 1, L : 1; +#endif + uint32_t valid_lt; /* Valid Lifetime */ + uint32_t pref_lt; /* Preferred Lifetime */ + uint32_t reserved2; + struct in6_addr prefix; + } SLIRP_PACKED_END prefixinfo; +#define ndpopt_prefixinfo ndpopt_body.prefixinfo + SLIRP_PACKED_BEGIN + struct rdnss { + uint16_t reserved; + uint32_t lifetime; + struct in6_addr addr; + } SLIRP_PACKED_END rdnss; +#define ndpopt_rdnss ndpopt_body.rdnss + } ndpopt_body; +} SLIRP_PACKED_END; + +/* NDP options type */ +#define NDPOPT_LINKLAYER_SOURCE 1 /* Source Link-Layer Address */ +#define NDPOPT_LINKLAYER_TARGET 2 /* Target Link-Layer Address */ +#define NDPOPT_PREFIX_INFO 3 /* Prefix Information */ +#define NDPOPT_RDNSS 25 /* Recursive DNS Server Address */ + +/* NDP options size, in octets. */ +#define NDPOPT_LINKLAYER_LEN 8 +#define NDPOPT_PREFIXINFO_LEN 32 +#define NDPOPT_RDNSS_LEN 24 + +/* + * Definition of type and code field values. + * Per https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xml + * Last Updated 2012-11-12 + */ + +/* Errors */ +#define ICMP6_UNREACH 1 /* Destination Unreachable */ +#define ICMP6_UNREACH_NO_ROUTE 0 /* no route to dest */ +#define ICMP6_UNREACH_DEST_PROHIB 1 /* com with dest prohibited */ +#define ICMP6_UNREACH_SCOPE 2 /* beyond scope of src addr */ +#define ICMP6_UNREACH_ADDRESS 3 /* address unreachable */ +#define ICMP6_UNREACH_PORT 4 /* port unreachable */ +#define ICMP6_UNREACH_SRC_FAIL 5 /* src addr failed */ +#define ICMP6_UNREACH_REJECT_ROUTE 6 /* reject route to dest */ +#define ICMP6_UNREACH_SRC_HDR_ERROR 7 /* error in src routing header */ +#define ICMP6_TOOBIG 2 /* Packet Too Big */ +#define ICMP6_TIMXCEED 3 /* Time Exceeded */ +#define ICMP6_TIMXCEED_INTRANS 0 /* hop limit exceeded in transit */ +#define ICMP6_TIMXCEED_REASS 1 /* ttl=0 in reass */ +#define ICMP6_PARAMPROB 4 /* Parameter Problem */ +#define ICMP6_PARAMPROB_HDR_FIELD 0 /* err header field */ +#define ICMP6_PARAMPROB_NXTHDR_TYPE 1 /* unrecognized Next Header type */ +#define ICMP6_PARAMPROB_IPV6_OPT 2 /* unrecognized IPv6 option */ + +/* Informational Messages */ +#define ICMP6_ECHO_REQUEST 128 /* Echo Request */ +#define ICMP6_ECHO_REPLY 129 /* Echo Reply */ +#define ICMP6_NDP_RS 133 /* Router Solicitation (NDP) */ +#define ICMP6_NDP_RA 134 /* Router Advertisement (NDP) */ +#define ICMP6_NDP_NS 135 /* Neighbor Solicitation (NDP) */ +#define ICMP6_NDP_NA 136 /* Neighbor Advertisement (NDP) */ +#define ICMP6_NDP_REDIRECT 137 /* Redirect Message (NDP) */ + +/* + * Router Configuration Variables (rfc4861#section-6) + */ +#define NDP_IsRouter 1 +#define NDP_AdvSendAdvertisements 1 +#define NDP_MaxRtrAdvInterval 600000 +#define NDP_MinRtrAdvInterval \ + ((NDP_MaxRtrAdvInterval >= 9) ? NDP_MaxRtrAdvInterval / 3 : \ + NDP_MaxRtrAdvInterval) +#define NDP_AdvManagedFlag 0 +#define NDP_AdvOtherConfigFlag 0 +#define NDP_AdvLinkMTU 0 +#define NDP_AdvReachableTime 0 +#define NDP_AdvRetransTime 0 +#define NDP_AdvCurHopLimit 64 +#define NDP_AdvDefaultLifetime ((3 * NDP_MaxRtrAdvInterval) / 1000) +#define NDP_AdvValidLifetime 86400 +#define NDP_AdvOnLinkFlag 1 +#define NDP_AdvPrefLifetime 14400 +#define NDP_AdvAutonomousFlag 1 + +/* Called from slirp_new, but after other initialization */ +void icmp6_post_init(Slirp *slirp); + +/* Called from slirp_cleanup */ +void icmp6_cleanup(Slirp *slirp); + +/* Process an ICMPv6 packet from the guest */ +void icmp6_input(struct mbuf *); + +/* Send an ICMPv6 error related to the given packet, using the given ICMPv6 type and code, using the given source */ +void icmp6_forward_error(struct mbuf *m, uint8_t type, uint8_t code, struct in6_addr *src); + +/* Similar to icmp6_forward_error, but use the link-local address as source */ +void icmp6_send_error(struct mbuf *m, uint8_t type, uint8_t code); + +/* Forward the ICMP packet to the guest (probably a ping reply) */ +void icmp6_reflect(struct mbuf *); + +/* Handle ICMP data from the ICMP socket, and forward it to the guest (using so_m as reference) */ +void icmp6_receive(struct socket *so); + +/* Send a neighbour sollicitation, to resolve the given IPV6 address */ +void ndp_send_ns(Slirp *slirp, struct in6_addr addr); + +/* Timer handler for router advertisement, to send it and reschedule the timer */ +void ra_timer_handler(Slirp *slirp, void *unused); + +#endif diff --git a/src/net/libslirp/src/ip6_input.c b/src/net/libslirp/src/ip6_input.c new file mode 100644 index 00000000..4aca0828 --- /dev/null +++ b/src/net/libslirp/src/ip6_input.c @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#include "slirp.h" +#include "ip6_icmp.h" + +/* + * IP initialization: fill in IP protocol switch table. + * All protocols not implemented in kernel go to raw IP protocol handler. + */ +void ip6_post_init(Slirp *slirp) +{ + icmp6_post_init(slirp); +} + +void ip6_cleanup(Slirp *slirp) +{ + icmp6_cleanup(slirp); +} + +void ip6_input(struct mbuf *m) +{ + Slirp *slirp = m->slirp; + /* NDP reads the ethernet header for gratuitous NDP */ + M_DUP_DEBUG(slirp, m, 1, TCPIPHDR_DELTA + 2 + ETH_HLEN); + + struct ip6 *ip6; + + if (!slirp->in6_enabled) { + goto bad; + } + + DEBUG_CALL("ip6_input"); + DEBUG_ARG("m = %p", m); + DEBUG_ARG("m_len = %d", m->m_len); + + if (m->m_len < sizeof(struct ip6)) { + goto bad; + } + + ip6 = mtod(m, struct ip6 *); + + if (ip6->ip_v != IP6VERSION) { + goto bad; + } + + if (ntohs(ip6->ip_pl) + sizeof(struct ip6) > slirp->if_mtu) { + icmp6_send_error(m, ICMP6_TOOBIG, 0); + goto bad; + } + + // Check if the message size is big enough to hold what's + // set in the payload length header. If not this is an invalid + // packet + if (m->m_len < ntohs(ip6->ip_pl) + sizeof(struct ip6)) { + goto bad; + } + + /* check ip_ttl for a correct ICMP reply */ + if (ip6->ip_hl == 0) { + icmp6_send_error(m, ICMP6_TIMXCEED, ICMP6_TIMXCEED_INTRANS); + goto bad; + } + + /* + * Switch out to protocol's input routine. + */ + switch (ip6->ip_nh) { + case IPPROTO_TCP: + NTOHS(ip6->ip_pl); + tcp_input(m, sizeof(struct ip6), (struct socket *)NULL, AF_INET6); + break; + case IPPROTO_UDP: + udp6_input(m); + break; + case IPPROTO_ICMPV6: + icmp6_input(m); + break; + default: + m_free(m); + } + return; +bad: + m_free(m); +} diff --git a/src/net/libslirp/src/ip6_output.c b/src/net/libslirp/src/ip6_output.c new file mode 100644 index 00000000..834f1c0a --- /dev/null +++ b/src/net/libslirp/src/ip6_output.c @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#include "slirp.h" + +/* Number of packets queued before we start sending + * (to prevent allocing too many mbufs) */ +#define IF6_THRESH 10 + +/* + * IPv6 output. The packet in mbuf chain m contains a IP header + */ +int ip6_output(struct socket *so, struct mbuf *m, int fast) +{ + Slirp *slirp = m->slirp; + M_DUP_DEBUG(slirp, m, 0, 0); + + struct ip6 *ip = mtod(m, struct ip6 *); + + DEBUG_CALL("ip6_output"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("m = %p", m); + + /* Fill IPv6 header */ + ip->ip_v = IP6VERSION; + ip->ip_hl = IP6_HOP_LIMIT; + ip->ip_tc_hi = 0; + ip->ip_tc_lo = 0; + ip->ip_fl_hi = 0; + ip->ip_fl_lo = 0; + + if (fast) { + /* We cannot fast-send non-multicast, we'd need a NDP NS */ + assert(IN6_IS_ADDR_MULTICAST(&ip->ip_dst)); + if_encap(m->slirp, m); + m_free(m); + } else { + if_output(so, m); + } + + return 0; +} diff --git a/src/net/libslirp/src/ip_icmp.c b/src/net/libslirp/src/ip_icmp.c new file mode 100644 index 00000000..74524fd3 --- /dev/null +++ b/src/net/libslirp/src/ip_icmp.c @@ -0,0 +1,547 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip_icmp.c 8.2 (Berkeley) 1/4/94 + * ip_icmp.c,v 1.7 1995/05/30 08:09:42 rgrimes Exp + */ + +#include "slirp.h" +#include "ip_icmp.h" + +#ifndef _WIN32 +#include +#endif + +#ifndef WITH_ICMP_ERROR_MSG +#define WITH_ICMP_ERROR_MSG 0 +#endif + +/* The message sent when emulating PING */ +/* Be nice and tell them it's just a pseudo-ping packet */ +static const char icmp_ping_msg[] = + "This is a pseudo-PING packet used by Slirp to emulate ICMP ECHO-REQUEST " + "packets.\n"; + +/* list of actions for icmp_send_error() on RX of an icmp message */ +static const int icmp_flush[19] = { + /* ECHO REPLY (0) */ 0, + 1, + 1, + /* DEST UNREACH (3) */ 1, + /* SOURCE QUENCH (4)*/ 1, + /* REDIRECT (5) */ 1, + 1, + 1, + /* ECHO (8) */ 0, + /* ROUTERADVERT (9) */ 1, + /* ROUTERSOLICIT (10) */ 1, + /* TIME EXCEEDED (11) */ 1, + /* PARAMETER PROBLEM (12) */ 1, + /* TIMESTAMP (13) */ 0, + /* TIMESTAMP REPLY (14) */ 0, + /* INFO (15) */ 0, + /* INFO REPLY (16) */ 0, + /* ADDR MASK (17) */ 0, + /* ADDR MASK REPLY (18) */ 0 +}; + +void icmp_init(Slirp *slirp) +{ + slirp->icmp.so_next = slirp->icmp.so_prev = &slirp->icmp; + slirp->icmp_last_so = &slirp->icmp; +} + +void icmp_cleanup(Slirp *slirp) +{ + struct socket *so, *so_next; + + for (so = slirp->icmp.so_next; so != &slirp->icmp; so = so_next) { + so_next = so->so_next; + icmp_detach(so); + } +} + +/* Send ICMP packet to the Internet, and save it to so_m */ +static int icmp_send(struct socket *so, struct mbuf *m, int hlen) +{ + Slirp *slirp = m->slirp; + + struct sockaddr_in addr; + + /* + * The behavior of reading SOCK_DGRAM+IPPROTO_ICMP sockets is inconsistent + * between host OSes. On Linux, only the ICMP header and payload is + * included. On macOS/Darwin, the socket acts like a raw socket and + * includes the IP header as well. On other BSDs, SOCK_DGRAM+IPPROTO_ICMP + * sockets aren't supported at all, so we treat them like raw sockets. It + * isn't possible to detect this difference at runtime, so we must use an + * #ifdef to determine if we need to remove the IP header. + */ +#if defined(BSD) && !defined(__GNU__) + so->so_type = IPPROTO_IP; +#else + so->so_type = IPPROTO_ICMP; +#endif + + so->s = slirp_socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); + if (so->s == -1) { + if (errno == EAFNOSUPPORT + || errno == EPROTONOSUPPORT + || errno == EACCES) { + /* Kernel doesn't support or allow ping sockets. */ + so->so_type = IPPROTO_IP; + so->s = slirp_socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + } + } + if (so->s == -1) { + return -1; + } + so->slirp->cb->register_poll_fd(so->s, so->slirp->opaque); + + if (slirp_bind_outbound(so, AF_INET) != 0) { + // bind failed - close socket + closesocket(so->s); + so->s = -1; + return -1; + } + + M_DUP_DEBUG(slirp, m, 0, 0); + struct ip *ip = mtod(m, struct ip *); + + so->so_m = m; + so->so_faddr = ip->ip_dst; + so->so_laddr = ip->ip_src; + so->so_iptos = ip->ip_tos; + so->so_state = SS_ISFCONNECTED; + so->so_expire = curtime + SO_EXPIRE; + + addr.sin_family = AF_INET; + addr.sin_addr = so->so_faddr; + + slirp_insque(so, &so->slirp->icmp); + + if (sendto(so->s, m->m_data + hlen, m->m_len - hlen, 0, + (struct sockaddr *)&addr, sizeof(addr)) == -1) { + DEBUG_MISC("icmp_input icmp sendto tx errno = %d-%s", errno, + strerror(errno)); + icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, strerror(errno)); + icmp_detach(so); + } + + return 0; +} + +void icmp_detach(struct socket *so) +{ + so->slirp->cb->unregister_poll_fd(so->s, so->slirp->opaque); + closesocket(so->s); + sofree(so); +} + +/* + * Process a received ICMP message. + */ +void icmp_input(struct mbuf *m, int hlen) +{ + Slirp *slirp = m->slirp; + M_DUP_DEBUG(slirp, m, 0, 0); + + register struct icmp *icp; + register struct ip *ip = mtod(m, struct ip *); + int icmplen = ip->ip_len; + + DEBUG_CALL("icmp_input"); + DEBUG_ARG("m = %p", m); + DEBUG_ARG("m_len = %d", m->m_len); + + /* + * Locate icmp structure in mbuf, and check + * that its not corrupted and of at least minimum length. + */ + if (icmplen < ICMP_MINLEN) { /* min 8 bytes payload */ + freeit: + m_free(m); + goto end_error; + } + + m->m_len -= hlen; + m->m_data += hlen; + icp = mtod(m, struct icmp *); + if (cksum(m, icmplen)) { + goto freeit; + } + m->m_len += hlen; + m->m_data -= hlen; + + DEBUG_ARG("icmp_type = %d", icp->icmp_type); + switch (icp->icmp_type) { + case ICMP_ECHO: + ip->ip_len += hlen; /* since ip_input subtracts this */ + if (ip->ip_dst.s_addr == slirp->vhost_addr.s_addr || + ip->ip_dst.s_addr == slirp->vnameserver_addr.s_addr) { + icmp_reflect(m); + } else if (slirp->restricted) { + goto freeit; + } else { + struct socket *so; + struct sockaddr_storage addr; + int ttl; + + so = socreate(slirp, IPPROTO_ICMP); + if (icmp_send(so, m, hlen) == 0) { + /* We could send this as ICMP, good! */ + return; + } + + /* We could not send this as ICMP, try to send it on UDP echo + * service (7), wishfully hoping that it is open there. */ + + if (udp_attach(so, AF_INET) == -1) { + DEBUG_MISC("icmp_input udp_attach errno = %d-%s", errno, + strerror(errno)); + sofree(so); + m_free(m); + goto end_error; + } + so->so_m = m; + so->so_ffamily = AF_INET; + so->so_faddr = ip->ip_dst; + so->so_fport = htons(7); + so->so_lfamily = AF_INET; + so->so_laddr = ip->ip_src; + so->so_lport = htons(9); + so->so_iptos = ip->ip_tos; + so->so_state = SS_ISFCONNECTED; + + /* Send the packet */ + addr = so->fhost.ss; + if (sotranslate_out(so, &addr) < 0) { + icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, + strerror(errno)); + udp_detach(so); + return; + } + + /* + * Check for TTL + */ + ttl = ip->ip_ttl-1; + if (ttl <= 0) { + DEBUG_MISC("udp ttl exceeded"); + icmp_send_error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, 0, + NULL); + udp_detach(so); + break; + } + setsockopt(so->s, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); + + if (sendto(so->s, icmp_ping_msg, strlen(icmp_ping_msg), 0, + (struct sockaddr *)&addr, sockaddr_size(&addr)) == -1) { + DEBUG_MISC("icmp_input udp sendto tx errno = %d-%s", errno, + strerror(errno)); + icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, + strerror(errno)); + udp_detach(so); + } + } /* if ip->ip_dst.s_addr == alias_addr.s_addr */ + break; + case ICMP_UNREACH: + /* XXX? report error? close socket? */ + case ICMP_TIMXCEED: + case ICMP_PARAMPROB: + case ICMP_SOURCEQUENCH: + case ICMP_TSTAMP: + case ICMP_MASKREQ: + case ICMP_REDIRECT: + m_free(m); + break; + + default: + m_free(m); + } /* switch */ + +end_error: + /* m is m_free()'d xor put in a socket xor or given to ip_send */ + return; +} + + +/* + * Send an ICMP message in response to a situation + * + * RFC 1122: 3.2.2 MUST send at least the IP header and 8 bytes of header. + *MAY send more (we do). MUST NOT change this header information. MUST NOT reply + *to a multicast/broadcast IP address. MUST NOT reply to a multicast/broadcast + *MAC address. MUST reply to only the first fragment. + */ +/* + * Send ICMP_UNREACH back to the source regarding msrc. + * mbuf *msrc is used as a template, but is NOT m_free()'d. + * It is reported as the bad ip packet. The header should + * be fully correct and in host byte order. + * ICMP fragmentation is illegal. All machines must accept 576 bytes in one + * packet. The maximum payload is 576-20(ip hdr)-8(icmp hdr)=548 + */ + +#define ICMP_MAXDATALEN (IP_MSS - 28) +void icmp_forward_error(struct mbuf *msrc, uint8_t type, uint8_t code, int minsize, + const char *message, struct in_addr *src) +{ + unsigned hlen, shlen, s_ip_len; + register struct ip *ip; + register struct icmp *icp; + register struct mbuf *m; + + DEBUG_CALL("icmp_send_error"); + DEBUG_ARG("msrc = %p", msrc); + DEBUG_ARG("msrc_len = %d", msrc->m_len); + + if (type != ICMP_UNREACH && type != ICMP_TIMXCEED) + goto end_error; + + /* check msrc */ + if (!msrc) + goto end_error; + ip = mtod(msrc, struct ip *); + if (slirp_debug & DBG_MISC) { + char addr_src[INET_ADDRSTRLEN]; + char addr_dst[INET_ADDRSTRLEN]; + + inet_ntop(AF_INET, &ip->ip_src, addr_src, sizeof(addr_src)); + inet_ntop(AF_INET, &ip->ip_dst, addr_dst, sizeof(addr_dst)); + DEBUG_MISC(" %.16s to %.16s", addr_src, addr_dst); + } + if (ip->ip_off & IP_OFFMASK) + goto end_error; /* Only reply to fragment 0 */ + + /* Do not reply to source-only IPs */ + if ((ip->ip_src.s_addr & htonl(~(0xf << 28))) == 0) { + goto end_error; + } + + shlen = ip->ip_hl << 2; + s_ip_len = ip->ip_len; + if (ip->ip_p == IPPROTO_ICMP) { + icp = (struct icmp *)((char *)ip + shlen); + /* + * Assume any unknown ICMP type is an error. This isn't + * specified by the RFC, but think about it.. + */ + if (icp->icmp_type > 18 || icmp_flush[icp->icmp_type]) + goto end_error; + } + + /* make a copy */ + m = m_get(msrc->slirp); + if (!m) { + goto end_error; + } + + { + int new_m_size; + new_m_size = + sizeof(struct ip) + ICMP_MINLEN + msrc->m_len + ICMP_MAXDATALEN; + if (new_m_size > m->m_size) + m_inc(m, new_m_size); + } + memcpy(m->m_data, msrc->m_data, msrc->m_len); + m->m_len = msrc->m_len; /* copy msrc to m */ + + /* make the header of the reply packet */ + ip = mtod(m, struct ip *); + hlen = sizeof(struct ip); /* no options in reply */ + + /* fill in icmp */ + m->m_data += hlen; + m->m_len -= hlen; + + icp = mtod(m, struct icmp *); + + if (minsize) + s_ip_len = shlen + ICMP_MINLEN; /* return header+8b only */ + else if (s_ip_len > ICMP_MAXDATALEN) /* maximum size */ + s_ip_len = ICMP_MAXDATALEN; + + m->m_len = ICMP_MINLEN + s_ip_len; /* 8 bytes ICMP header */ + + /* min. size = 8+sizeof(struct ip)+8 */ + + icp->icmp_type = type; + icp->icmp_code = code; + icp->icmp_id = 0; + icp->icmp_seq = 0; + + memcpy(&icp->icmp_ip, msrc->m_data, s_ip_len); /* report the ip packet */ + HTONS(icp->icmp_ip.ip_len); + HTONS(icp->icmp_ip.ip_id); + HTONS(icp->icmp_ip.ip_off); + + if (message && WITH_ICMP_ERROR_MSG) { /* append message to ICMP packet */ + int message_len; + char *cpnt; + message_len = strlen(message); + if (message_len > ICMP_MAXDATALEN) + message_len = ICMP_MAXDATALEN; + cpnt = (char *)m->m_data + m->m_len; + memcpy(cpnt, message, message_len); + m->m_len += message_len; + } + + icp->icmp_cksum = 0; + icp->icmp_cksum = cksum(m, m->m_len); + + m->m_data -= hlen; + m->m_len += hlen; + + /* fill in ip */ + ip->ip_hl = hlen >> 2; + ip->ip_len = m->m_len; + + ip->ip_tos = ((ip->ip_tos & 0x1E) | 0xC0); /* high priority for errors */ + + ip->ip_ttl = MAXTTL; + ip->ip_p = IPPROTO_ICMP; + ip->ip_dst = ip->ip_src; /* ip addresses */ + ip->ip_src = *src; + + ip_output((struct socket *)NULL, m); + +end_error: + return; +} +#undef ICMP_MAXDATALEN + +void icmp_send_error(struct mbuf *msrc, uint8_t type, uint8_t code, int minsize, + const char *message) +{ + icmp_forward_error(msrc, type, code, minsize, message, &msrc->slirp->vhost_addr); +} + +/* + * Reflect the ip packet back to the source + */ +void icmp_reflect(struct mbuf *m) +{ + register struct ip *ip = mtod(m, struct ip *); + int hlen = ip->ip_hl << 2; + int optlen = hlen - sizeof(struct ip); + register struct icmp *icp; + + /* + * Send an icmp packet back to the ip level, + * after supplying a checksum. + */ + m->m_data += hlen; + m->m_len -= hlen; + icp = mtod(m, struct icmp *); + + icp->icmp_type = ICMP_ECHOREPLY; + icp->icmp_cksum = 0; + icp->icmp_cksum = cksum(m, ip->ip_len - hlen); + + m->m_data -= hlen; + m->m_len += hlen; + + /* fill in ip */ + if (optlen > 0) { + /* + * Strip out original options by copying rest of first + * mbuf's data back, and adjust the IP length. + */ + memmove((char *)(ip + 1), (char *)ip + hlen, + (unsigned)(m->m_len - hlen)); + hlen -= optlen; + ip->ip_hl = hlen >> 2; + ip->ip_len -= optlen; + m->m_len -= optlen; + } + + ip->ip_ttl = MAXTTL; + { /* swap */ + struct in_addr icmp_dst; + icmp_dst = ip->ip_dst; + ip->ip_dst = ip->ip_src; + ip->ip_src = icmp_dst; + } + + ip_output((struct socket *)NULL, m); +} + +void icmp_receive(struct socket *so) +{ + struct mbuf *m = so->so_m; + struct ip *ip = mtod(m, struct ip *); + int hlen = ip->ip_hl << 2; + uint8_t error_code; + struct icmp *icp; + int id, len; + + m->m_data += hlen; + m->m_len -= hlen; + icp = mtod(m, struct icmp *); + + id = icp->icmp_id; + len = recv(so->s, icp, M_ROOM(m), 0); + + if (so->so_type == IPPROTO_IP) { + if (len >= sizeof(struct ip)) { + struct ip *inner_ip = mtod(m, struct ip *); + int inner_hlen = inner_ip->ip_hl << 2; + if (inner_hlen > len) { + len = -1; + errno = -EINVAL; + } else { + len -= inner_hlen; + memmove(icp, (unsigned char *)icp + inner_hlen, len); + } + } else { + len = -1; + errno = -EINVAL; + } + } + + icp->icmp_id = id; + + m->m_data -= hlen; + m->m_len += hlen; + + if (len == -1 || len == 0) { + if (errno == ENETUNREACH) { + error_code = ICMP_UNREACH_NET; + } else { + error_code = ICMP_UNREACH_HOST; + } + DEBUG_MISC(" udp icmp rx errno = %d-%s", errno, strerror(errno)); + icmp_send_error(so->so_m, ICMP_UNREACH, error_code, 0, strerror(errno)); + } else { + icmp_reflect(so->so_m); + so->so_m = NULL; /* Don't m_free() it again! */ + } + icmp_detach(so); +} diff --git a/src/net/libslirp/src/ip_icmp.h b/src/net/libslirp/src/ip_icmp.h new file mode 100644 index 00000000..aad04165 --- /dev/null +++ b/src/net/libslirp/src/ip_icmp.h @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip_icmp.h 8.1 (Berkeley) 6/10/93 + * ip_icmp.h,v 1.4 1995/05/30 08:09:43 rgrimes Exp + */ + +#ifndef NETINET_IP_ICMP_H +#define NETINET_IP_ICMP_H + +/* + * Interface Control Message Protocol Definitions. + * Per RFC 792, September 1981. + */ + +typedef uint32_t n_time; + +/* + * Structure of an icmp header. + */ +struct icmp { + uint8_t icmp_type; /* type of message, see below */ + uint8_t icmp_code; /* type sub code */ + uint16_t icmp_cksum; /* ones complement cksum of struct */ + union { + uint8_t ih_pptr; /* ICMP_PARAMPROB */ + struct in_addr ih_gwaddr; /* ICMP_REDIRECT */ + struct ih_idseq { + uint16_t icd_id; + uint16_t icd_seq; + } ih_idseq; + int ih_void; + + /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */ + struct ih_pmtu { + uint16_t ipm_void; + uint16_t ipm_nextmtu; + } ih_pmtu; + } icmp_hun; +#define icmp_pptr icmp_hun.ih_pptr +#define icmp_gwaddr icmp_hun.ih_gwaddr +#define icmp_id icmp_hun.ih_idseq.icd_id +#define icmp_seq icmp_hun.ih_idseq.icd_seq +#define icmp_void icmp_hun.ih_void +#define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void +#define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu + union { + struct id_ts { + n_time its_otime; + n_time its_rtime; + n_time its_ttime; + } id_ts; + struct id_ip { + struct ip idi_ip; + /* options and then 64 bits of data */ + } id_ip; + uint32_t id_mask; + char id_data[1]; + } icmp_dun; +#define icmp_otime icmp_dun.id_ts.its_otime +#define icmp_rtime icmp_dun.id_ts.its_rtime +#define icmp_ttime icmp_dun.id_ts.its_ttime +#define icmp_ip icmp_dun.id_ip.idi_ip +#define icmp_mask icmp_dun.id_mask +#define icmp_data icmp_dun.id_data +}; + +/* + * Lower bounds on packet lengths for various types. + * For the error advice packets must first ensure that the + * packet is large enough to contain the returned ip header. + * Only then can we do the check to see if 64 bits of packet + * data have been returned, since we need to check the returned + * ip header length. + */ +#define ICMP_MINLEN 8 /* abs minimum */ +#define ICMP_TSLEN (8 + 3 * sizeof(n_time)) /* timestamp */ +#define ICMP_MASKLEN 12 /* address mask */ +#define ICMP_ADVLENMIN (8 + sizeof(struct ip) + 8) /* min */ +#define ICMP_ADVLEN(p) (8 + ((p)->icmp_ip.ip_hl << 2) + 8) +/* N.B.: must separately check that ip_hl >= 5 */ + +/* + * Definition of type and code field values. + */ +#define ICMP_ECHOREPLY 0 /* echo reply */ +#define ICMP_UNREACH 3 /* dest unreachable, codes: */ +#define ICMP_UNREACH_NET 0 /* bad net */ +#define ICMP_UNREACH_HOST 1 /* bad host */ +#define ICMP_UNREACH_PROTOCOL 2 /* bad protocol */ +#define ICMP_UNREACH_PORT 3 /* bad port */ +#define ICMP_UNREACH_NEEDFRAG 4 /* IP_DF caused drop */ +#define ICMP_UNREACH_SRCFAIL 5 /* src route failed */ +#define ICMP_UNREACH_NET_UNKNOWN 6 /* unknown net */ +#define ICMP_UNREACH_HOST_UNKNOWN 7 /* unknown host */ +#define ICMP_UNREACH_ISOLATED 8 /* src host isolated */ +#define ICMP_UNREACH_NET_PROHIB 9 /* prohibited access */ +#define ICMP_UNREACH_HOST_PROHIB 10 /* ditto */ +#define ICMP_UNREACH_TOSNET 11 /* bad tos for net */ +#define ICMP_UNREACH_TOSHOST 12 /* bad tos for host */ +#define ICMP_SOURCEQUENCH 4 /* packet lost, slow down */ +#define ICMP_REDIRECT 5 /* shorter route, codes: */ +#define ICMP_REDIRECT_NET 0 /* for network */ +#define ICMP_REDIRECT_HOST 1 /* for host */ +#define ICMP_REDIRECT_TOSNET 2 /* for tos and net */ +#define ICMP_REDIRECT_TOSHOST 3 /* for tos and host */ +#define ICMP_ECHO 8 /* echo service */ +#define ICMP_ROUTERADVERT 9 /* router advertisement */ +#define ICMP_ROUTERSOLICIT 10 /* router solicitation */ +#define ICMP_TIMXCEED 11 /* time exceeded, code: */ +#define ICMP_TIMXCEED_INTRANS 0 /* ttl==0 in transit */ +#define ICMP_TIMXCEED_REASS 1 /* ttl==0 in reass */ +#define ICMP_PARAMPROB 12 /* ip header bad */ +#define ICMP_PARAMPROB_OPTABSENT 1 /* req. opt. absent */ +#define ICMP_TSTAMP 13 /* timestamp request */ +#define ICMP_TSTAMPREPLY 14 /* timestamp reply */ +#define ICMP_IREQ 15 /* information request */ +#define ICMP_IREQREPLY 16 /* information reply */ +#define ICMP_MASKREQ 17 /* address mask request */ +#define ICMP_MASKREPLY 18 /* address mask reply */ + +#define ICMP_MAXTYPE 18 + +#define ICMP_INFOTYPE(type) \ + ((type) == ICMP_ECHOREPLY || (type) == ICMP_ECHO || \ + (type) == ICMP_ROUTERADVERT || (type) == ICMP_ROUTERSOLICIT || \ + (type) == ICMP_TSTAMP || (type) == ICMP_TSTAMPREPLY || \ + (type) == ICMP_IREQ || (type) == ICMP_IREQREPLY || \ + (type) == ICMP_MASKREQ || (type) == ICMP_MASKREPLY) + +/* Called from slirp_new */ +void icmp_init(Slirp *slirp); + +/* Called from slirp_cleanup */ +void icmp_cleanup(Slirp *slirp); + +/* Process an ICMP packet from the guest */ +void icmp_input(struct mbuf *, int); + +/* Send an ICMP error related to the given packet, using the given ICMP type and code, appending the given message (if enabled at compilation), and using the given source. If minsize is sent, send only header + 8B of the given packet, otherwise send it all */ +void icmp_forward_error(struct mbuf *msrc, uint8_t type, uint8_t code, int minsize, + const char *message, struct in_addr *src); + +/* Similar to icmp_forward_error, but use the virtual host address as source */ +void icmp_send_error(struct mbuf *msrc, uint8_t type, uint8_t code, int minsize, + const char *message); + +/* Forward the ICMP packet to the guest (probably a ping reply) */ +void icmp_reflect(struct mbuf *); + +/* Handle ICMP data from the ICMP socket, and forward it to the guest (using so_m as reference) */ +void icmp_receive(struct socket *so); + +/* Forget about this pending ICMP request */ +void icmp_detach(struct socket *so); + +#endif diff --git a/src/net/libslirp/src/ip_input.c b/src/net/libslirp/src/ip_input.c new file mode 100644 index 00000000..0a4b008a --- /dev/null +++ b/src/net/libslirp/src/ip_input.c @@ -0,0 +1,450 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip_input.c 8.2 (Berkeley) 1/4/94 + * ip_input.c,v 1.11 1994/11/16 10:17:08 jkh Exp + */ + +/* + * Changes and additions relating to SLiRP are + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" +#include "ip_icmp.h" + +static struct ip *ip_reass(Slirp *slirp, struct ip *ip, struct ipq *fp); +static void ip_freef(Slirp *slirp, struct ipq *fp); +static void ip_enq(register struct ipas *p, register struct ipas *prev); +static void ip_deq(register struct ipas *p); + +/* + * IP initialization: fill in IP protocol switch table. + * All protocols not implemented in kernel go to raw IP protocol handler. + */ +void ip_init(Slirp *slirp) +{ + slirp->ipq.ip_link.next = slirp->ipq.ip_link.prev = &slirp->ipq.ip_link; + udp_init(slirp); + tcp_init(slirp); + icmp_init(slirp); +} + +void ip_cleanup(Slirp *slirp) +{ + udp_cleanup(slirp); + tcp_cleanup(slirp); + icmp_cleanup(slirp); +} + +/* + * Ip input routine. Checksum and byte swap header. If fragmented + * try to reassemble. Process options. Pass to next level. + */ +void ip_input(struct mbuf *m) +{ + Slirp *slirp = m->slirp; + M_DUP_DEBUG(slirp, m, 0, TCPIPHDR_DELTA); + + register struct ip *ip; + int hlen; + + if (!slirp->in_enabled) { + goto bad; + } + + DEBUG_CALL("ip_input"); + DEBUG_ARG("m = %p", m); + DEBUG_ARG("m_len = %d", m->m_len); + + if (m->m_len < sizeof(struct ip)) { + goto bad; + } + + ip = mtod(m, struct ip *); + + if (ip->ip_v != IPVERSION) { + goto bad; + } + + hlen = ip->ip_hl << 2; + if (hlen < sizeof(struct ip) || hlen > m->m_len) { /* min header length */ + goto bad; /* or packet too short */ + } + + /* keep ip header intact for ICMP reply + * ip->ip_sum = cksum(m, hlen); + * if (ip->ip_sum) { + */ + if (cksum(m, hlen)) { + goto bad; + } + + /* + * Convert fields to host representation. + */ + NTOHS(ip->ip_len); + if (ip->ip_len < hlen) { + goto bad; + } + NTOHS(ip->ip_id); + NTOHS(ip->ip_off); + + /* + * Check that the amount of data in the buffers + * is as at least much as the IP header would have us expect. + * Trim mbufs if longer than we expect. + * Drop packet if shorter than we expect. + */ + if (m->m_len < ip->ip_len) { + goto bad; + } + + /* Should drop packet if mbuf too long? hmmm... */ + if (m->m_len > ip->ip_len) + m_adj(m, ip->ip_len - m->m_len); + + /* check ip_ttl for a correct ICMP reply */ + if (ip->ip_ttl == 0) { + icmp_send_error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, 0, "ttl"); + goto bad; + } + + /* + * If offset or IP_MF are set, must reassemble. + * Otherwise, nothing need be done. + * (We could look in the reassembly queue to see + * if the packet was previously fragmented, + * but it's not worth the time; just let them time out.) + * + * XXX This should fail, don't fragment yet + */ + if (ip->ip_off & ~IP_DF) { + register struct ipq *q; + struct qlink *l; + /* + * Look for queue of fragments + * of this datagram. + */ + for (l = slirp->ipq.ip_link.next; l != &slirp->ipq.ip_link; + l = l->next) { + q = container_of(l, struct ipq, ip_link); + if (ip->ip_id == q->ipq_id && + ip->ip_src.s_addr == q->ipq_src.s_addr && + ip->ip_dst.s_addr == q->ipq_dst.s_addr && + ip->ip_p == q->ipq_p) + goto found; + } + q = NULL; + found: + + /* + * Adjust ip_len to not reflect header, + * set ip_mff if more fragments are expected, + * convert offset of this to bytes. + */ + ip->ip_len -= hlen; + if (ip->ip_off & IP_MF) + ip->ip_tos |= 1; + else + ip->ip_tos &= ~1; + + ip->ip_off <<= 3; + + /* + * If datagram marked as having more fragments + * or if this is not the first fragment, + * attempt reassembly; if it succeeds, proceed. + */ + if (ip->ip_tos & 1 || ip->ip_off) { + ip = ip_reass(slirp, ip, q); + if (ip == NULL) + return; + m = dtom(slirp, ip); + } else if (q) + ip_freef(slirp, q); + + } else + ip->ip_len -= hlen; + + /* + * Switch out to protocol's input routine. + */ + switch (ip->ip_p) { + case IPPROTO_TCP: + tcp_input(m, hlen, (struct socket *)NULL, AF_INET); + break; + case IPPROTO_UDP: + udp_input(m, hlen); + break; + case IPPROTO_ICMP: + icmp_input(m, hlen); + break; + default: + m_free(m); + } + return; +bad: + m_free(m); +} + +#define iptoas(P) container_of((P), struct ipas, ipf_ip) +#define astoip(P) (&(P)->ipf_ip) +/* + * Take incoming datagram fragment and try to + * reassemble it into whole datagram. If a chain for + * reassembly of this datagram already exists, then it + * is given as q; otherwise have to make a chain. + */ +static struct ip *ip_reass(Slirp *slirp, struct ip *ip, struct ipq *q) +{ + register struct mbuf *m = dtom(slirp, ip); + struct ipas *first = container_of(q, struct ipas, ipq); + register struct ipas *cursor; + int hlen = ip->ip_hl << 2; + int i, next; + + DEBUG_CALL("ip_reass"); + DEBUG_ARG("ip = %p", ip); + DEBUG_ARG("q = %p", q); + DEBUG_ARG("m = %p", m); + + /* + * Presence of header sizes in mbufs + * would confuse code below. + * Fragment m_data is concatenated. + */ + m->m_data += hlen; + m->m_len -= hlen; + + /* + * If first fragment to arrive, create a reassembly queue. + */ + if (q == NULL) { + struct mbuf *t = m_get(slirp); + + if (t == NULL) { + goto dropfrag; + } + first = mtod(t, struct ipas *); + q = &first->ipq; + slirp_insque(&q->ip_link, &slirp->ipq.ip_link); + q->ipq_ttl = IPFRAGTTL; + q->ipq_p = ip->ip_p; + q->ipq_id = ip->ip_id; + first->link.next = first->link.prev = first; + q->ipq_src = ip->ip_src; + q->ipq_dst = ip->ip_dst; + cursor = first; + goto insert; + } + + /* + * Find a segment which begins after this one does. + */ + for (cursor = first->link.next; cursor != first; cursor = cursor->link.next) + if (cursor->ipf_off > ip->ip_off) + break; + + /* + * If there is a preceding segment, it may provide some of + * our data already. If so, drop the data from the incoming + * segment. If it provides all of our data, drop us. + */ + if (cursor->link.prev != first) { + struct ipas *pq = cursor->link.prev; + i = pq->ipf_off + pq->ipf_len - ip->ip_off; + if (i > 0) { + if (i >= ip->ip_len) + goto dropfrag; + m_adj(dtom(slirp, ip), i); + ip->ip_off += i; + ip->ip_len -= i; + } + } + + /* + * While we overlap succeeding segments trim them or, + * if they are completely covered, dequeue them. + */ + while (cursor != first && ip->ip_off + ip->ip_len > cursor->ipf_off) { + struct ipas *prev; + i = (ip->ip_off + ip->ip_len) - cursor->ipf_off; + if (i < cursor->ipf_len) { + cursor->ipf_len -= i; + cursor->ipf_off += i; + m_adj(dtom(slirp, cursor), i); + break; + } + prev = cursor; + cursor = cursor->link.next; + ip_deq(prev); + m_free(dtom(slirp, prev)); + } + +insert: + /* + * Stick new segment in its place; + * check for complete reassembly. + */ + ip_enq(iptoas(ip), cursor->link.prev); + next = 0; + for (cursor = first->link.next; cursor != first; cursor = cursor->link.next) { + if (cursor->ipf_off != next) + return NULL; + next += cursor->ipf_len; + } + if (((struct ipas *)(cursor->link.prev))->ipf_tos & 1) + return NULL; + + /* + * Reassembly is complete; concatenate fragments. + */ + cursor = first->link.next; + m = dtom(slirp, cursor); + int delta = (char *)cursor - (m->m_flags & M_EXT ? m->m_ext : m->m_dat); + + cursor = cursor->link.next; + while (cursor != first) { + struct mbuf *t = dtom(slirp, cursor); + cursor = cursor->link.next; + m_cat(m, t); + } + + /* + * Create header for new ip packet by + * modifying header of first packet; + * dequeue and discard fragment reassembly header. + * Make header visible. + */ + cursor = first->link.next; + + /* + * If the fragments concatenated to an mbuf that's bigger than the total + * size of the fragment and the mbuf was not already using an m_ext buffer, + * then an m_ext buffer was allocated. But q->ipq_next points to the old + * buffer (in the mbuf), so we must point ip into the new buffer. + */ + if (m->m_flags & M_EXT) { + cursor = (struct ipas *)(m->m_ext + delta); + } + + ip = astoip(cursor); + ip->ip_len = next; + ip->ip_tos &= ~1; + ip->ip_src = q->ipq_src; + ip->ip_dst = q->ipq_dst; + slirp_remque(&q->ip_link); + m_free(dtom(slirp, q)); + m->m_len += (ip->ip_hl << 2); + m->m_data -= (ip->ip_hl << 2); + + return ip; + +dropfrag: + m_free(m); + return NULL; +} + +/* + * Free a fragment reassembly header and all + * associated datagrams. + */ +static void ip_freef(Slirp *slirp, struct ipq *q) +{ + struct ipas *first = container_of(q, struct ipas, ipq); + register struct ipas *cursor, *next; + + for (cursor = first->link.next; cursor != first; cursor = next) { + next = cursor->link.next; + ip_deq(cursor); + m_free(dtom(slirp, cursor)); + } + slirp_remque(&q->ip_link); + m_free(dtom(slirp, q)); +} + +/* + * Put an ip fragment on a reassembly chain. + * Like slirp_insque, but pointers in middle of structure. + */ +static void ip_enq(register struct ipas *p, register struct ipas *prev) +{ + DEBUG_CALL("ip_enq"); + DEBUG_ARG("prev = %p", prev); + p->link.prev = prev; + p->link.next = prev->link.next; + ((struct ipas *)(prev->link.next))->link.prev = p; + prev->link.next = p; +} + +/* + * To ip_enq as slirp_remque is to slirp_insque. + */ +static void ip_deq(register struct ipas *p) +{ + ((struct ipas *)(p->link.prev))->link.next = p->link.next; + ((struct ipas *)(p->link.next))->link.prev = p->link.prev; +} + +void ip_slowtimo(Slirp *slirp) +{ + struct qlink *l; + + DEBUG_CALL("ip_slowtimo"); + + l = slirp->ipq.ip_link.next; + + if (l == NULL) + return; + + while (l != &slirp->ipq.ip_link) { + struct ipq *q = container_of(l, struct ipq, ip_link); + l = l->next; + if (--q->ipq_ttl == 0) { + ip_freef(slirp, q); + } + } +} + +void ip_stripoptions(register struct mbuf *m) +{ + register int i; + struct ip *ip = mtod(m, struct ip *); + register char *opts; + int olen; + + olen = (ip->ip_hl << 2) - sizeof(struct ip); + opts = (char *)(ip + 1); + i = m->m_len - (sizeof(struct ip) + olen); + memmove(opts, opts + olen, (unsigned)i); + m->m_len -= olen; + + ip->ip_hl = sizeof(struct ip) >> 2; +} diff --git a/src/net/libslirp/src/ip_output.c b/src/net/libslirp/src/ip_output.c new file mode 100644 index 00000000..4f626059 --- /dev/null +++ b/src/net/libslirp/src/ip_output.c @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ip_output.c 8.3 (Berkeley) 1/21/94 + * ip_output.c,v 1.9 1994/11/16 10:17:10 jkh Exp + */ + +/* + * Changes and additions relating to SLiRP are + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" + +/* Number of packets queued before we start sending + * (to prevent allocing too many mbufs) */ +#define IF_THRESH 10 + +/* + * IP output. The packet in mbuf chain m contains a skeletal IP + * header (with len, off, ttl, proto, tos, src, dst). + * The mbuf chain containing the packet will be freed. + * The mbuf opt, if present, will not be freed. + */ +int ip_output(struct socket *so, struct mbuf *m0) +{ + Slirp *slirp = m0->slirp; + M_DUP_DEBUG(slirp, m0, 0, 0); + + register struct ip *ip; + register struct mbuf *m = m0; + register int hlen = sizeof(struct ip); + int len, off, error = 0; + + DEBUG_CALL("ip_output"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("m0 = %p", m0); + + ip = mtod(m, struct ip *); + /* + * Fill in IP header. + */ + ip->ip_v = IPVERSION; + ip->ip_off &= IP_DF; + ip->ip_id = htons(slirp->ip_id++); + ip->ip_hl = hlen >> 2; + + /* + * If small enough for interface, can just send directly. + */ + if ((uint16_t)ip->ip_len <= slirp->if_mtu) { + ip->ip_len = htons((uint16_t)ip->ip_len); + ip->ip_off = htons((uint16_t)ip->ip_off); + ip->ip_sum = 0; + ip->ip_sum = cksum(m, hlen); + + if_output(so, m); + goto done; + } + + /* + * Too large for interface; fragment if possible. + * Must be able to put at least 8 bytes per fragment. + */ + if (ip->ip_off & IP_DF) { + error = -1; + goto bad; + } + + len = (slirp->if_mtu - hlen) & ~7; /* ip databytes per packet */ + if (len < 8) { + error = -1; + goto bad; + } + + { + int mhlen, firstlen = len; + struct mbuf **mnext = &m->m_nextpkt; + + /* + * Loop through length of segment after first fragment, + * make new header and copy data of each part and link onto chain. + */ + m0 = m; + mhlen = sizeof(struct ip); + for (off = hlen + len; off < (uint16_t)ip->ip_len; off += len) { + register struct ip *mhip; + m = m_get(slirp); + if (m == NULL) { + error = -1; + goto sendorfree; + } + m->m_data += IF_MAXLINKHDR; + mhip = mtod(m, struct ip *); + *mhip = *ip; + + m->m_len = mhlen; + mhip->ip_off = ((off - hlen) >> 3) + (ip->ip_off & ~IP_MF); + if (ip->ip_off & IP_MF) + mhip->ip_off |= IP_MF; + if (off + len >= (uint16_t)ip->ip_len) + len = (uint16_t)ip->ip_len - off; + else + mhip->ip_off |= IP_MF; + mhip->ip_len = htons((uint16_t)(len + mhlen)); + + if (m_copy(m, m0, off, len) < 0) { + error = -1; + goto sendorfree; + } + + mhip->ip_off = htons((uint16_t)mhip->ip_off); + mhip->ip_sum = 0; + mhip->ip_sum = cksum(m, mhlen); + *mnext = m; + mnext = &m->m_nextpkt; + } + /* + * Update first fragment by trimming what's been copied out + * and updating header, then send each fragment (in order). + */ + m = m0; + m_adj(m, hlen + firstlen - (uint16_t)ip->ip_len); + ip->ip_len = htons((uint16_t)m->m_len); + ip->ip_off = htons((uint16_t)(ip->ip_off | IP_MF)); + ip->ip_sum = 0; + ip->ip_sum = cksum(m, hlen); + sendorfree: + for (m = m0; m; m = m0) { + m0 = m->m_nextpkt; + m->m_nextpkt = NULL; + if (error == 0) + if_output(so, m); + else + m_free(m); + } + } + +done: + return (error); + +bad: + m_free(m0); + goto done; +} diff --git a/src/net/libslirp/src/libslirp-version.h.in b/src/net/libslirp/src/libslirp-version.h.in new file mode 100644 index 00000000..faa6c859 --- /dev/null +++ b/src/net/libslirp/src/libslirp-version.h.in @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#ifndef LIBSLIRP_VERSION_H_ +#define LIBSLIRP_VERSION_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define SLIRP_MAJOR_VERSION @SLIRP_MAJOR_VERSION@ +#define SLIRP_MINOR_VERSION @SLIRP_MINOR_VERSION@ +#define SLIRP_MICRO_VERSION @SLIRP_MICRO_VERSION@ +#define SLIRP_VERSION_STRING @SLIRP_VERSION_STRING@ + +#define SLIRP_CHECK_VERSION(major,minor,micro) \ + (SLIRP_MAJOR_VERSION > (major) || \ + (SLIRP_MAJOR_VERSION == (major) && SLIRP_MINOR_VERSION > (minor)) || \ + (SLIRP_MAJOR_VERSION == (major) && SLIRP_MINOR_VERSION == (minor) && \ + SLIRP_MICRO_VERSION >= (micro))) + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* LIBSLIRP_VERSION_H_ */ diff --git a/src/net/libslirp/src/libslirp.h b/src/net/libslirp/src/libslirp.h new file mode 100644 index 00000000..68f7edfa --- /dev/null +++ b/src/net/libslirp/src/libslirp.h @@ -0,0 +1,346 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#ifndef LIBSLIRP_H +#define LIBSLIRP_H + +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#include +typedef SSIZE_T slirp_ssize_t; +#ifndef LIBSLIRP_STATIC_BUILD +# ifdef BUILDING_LIBSLIRP +# define SLIRP_EXPORT __declspec(dllexport) +# else +# define SLIRP_EXPORT __declspec(dllimport) +# endif +#else +# define SLIRP_EXPORT +#endif +#else +#include +typedef ssize_t slirp_ssize_t; +#include +#include +#define SLIRP_EXPORT +#endif + +#include "libslirp-version.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Opaque structure containing the slirp state */ +typedef struct Slirp Slirp; + +/* Flags passed to SlirpAddPollCb and to be returned by SlirpGetREventsCb. */ +enum { + SLIRP_POLL_IN = 1 << 0, + SLIRP_POLL_OUT = 1 << 1, + SLIRP_POLL_PRI = 1 << 2, + SLIRP_POLL_ERR = 1 << 3, + SLIRP_POLL_HUP = 1 << 4, +}; + +/* Callback for application to get data from the guest */ +typedef slirp_ssize_t (*SlirpReadCb)(void *buf, size_t len, void *opaque); +/* Callback for application to send data to the guest */ +typedef slirp_ssize_t (*SlirpWriteCb)(const void *buf, size_t len, void *opaque); +/* Timer callback */ +typedef void (*SlirpTimerCb)(void *opaque); +/* Callback for libslirp to register polling callbacks */ +typedef int (*SlirpAddPollCb)(int fd, int events, void *opaque); +/* Callback for libslirp to get polling result */ +typedef int (*SlirpGetREventsCb)(int idx, void *opaque); + +/* For now libslirp creates only a timer for the IPv6 RA */ +typedef enum SlirpTimerId { + SLIRP_TIMER_RA, + SLIRP_TIMER_NUM, +} SlirpTimerId; + +/* + * Callbacks from slirp, to be set by the application. + * + * The opaque parameter is set to the opaque pointer given in the slirp_new / + * slirp_init call. + */ +typedef struct SlirpCb { + /* + * Send an ethernet frame to the guest network. The opaque parameter is the + * one given to slirp_init(). If the guest is not ready to receive a frame, + * the function can just drop the data. TCP will then handle retransmissions + * at a lower pace. + * <0 reports an IO error. + */ + SlirpWriteCb send_packet; + /* Print a message for an error due to guest misbehavior. */ + void (*guest_error)(const char *msg, void *opaque); + /* Return the virtual clock value in nanoseconds */ + int64_t (*clock_get_ns)(void *opaque); + /* Create a new timer with the given callback and opaque data. Not + * needed if timer_new_opaque is provided. */ + void *(*timer_new)(SlirpTimerCb cb, void *cb_opaque, void *opaque); + /* Remove and free a timer */ + void (*timer_free)(void *timer, void *opaque); + /* Modify a timer to expire at @expire_time (ms) */ + void (*timer_mod)(void *timer, int64_t expire_time, void *opaque); + /* Register a fd for future polling */ + void (*register_poll_fd)(int fd, void *opaque); + /* Unregister a fd */ + void (*unregister_poll_fd)(int fd, void *opaque); + /* Kick the io-thread, to signal that new events may be processed because some TCP buffer + * can now receive more data, i.e. slirp_socket_can_recv will return 1. */ + void (*notify)(void *opaque); + + /* + * Fields introduced in SlirpConfig version 4 begin + */ + + /* Initialization has completed and a Slirp* has been created. */ + void (*init_completed)(Slirp *slirp, void *opaque); + /* Create a new timer. When the timer fires, the application passes + * the SlirpTimerId and cb_opaque to slirp_handle_timer. */ + void *(*timer_new_opaque)(SlirpTimerId id, void *cb_opaque, void *opaque); +} SlirpCb; + +#define SLIRP_CONFIG_VERSION_MIN 1 +#define SLIRP_CONFIG_VERSION_MAX 5 + +typedef struct SlirpConfig { + /* Version must be provided */ + uint32_t version; + /* + * Fields introduced in SlirpConfig version 1 begin + */ + /* Whether to prevent the guest from accessing the Internet */ + int restricted; + /* Whether IPv4 is enabled */ + bool in_enabled; + /* Virtual network for the guest */ + struct in_addr vnetwork; + /* Mask for the virtual network for the guest */ + struct in_addr vnetmask; + /* Virtual address for the host exposed to the guest */ + struct in_addr vhost; + /* Whether IPv6 is enabled */ + bool in6_enabled; + /* Virtual IPv6 network for the guest */ + struct in6_addr vprefix_addr6; + /* Len of the virtual IPv6 network for the guest */ + uint8_t vprefix_len; + /* Virtual address for the host exposed to the guest */ + struct in6_addr vhost6; + /* Hostname exposed to the guest in DHCP hostname option */ + const char *vhostname; + /* Hostname exposed to the guest in the DHCP TFTP server name option */ + const char *tftp_server_name; + /* Path of the files served by TFTP */ + const char *tftp_path; + /* Boot file name exposed to the guest via DHCP */ + const char *bootfile; + /* Start of the DHCP range */ + struct in_addr vdhcp_start; + /* Virtual address for the DNS server exposed to the guest */ + struct in_addr vnameserver; + /* Virtual IPv6 address for the DNS server exposed to the guest */ + struct in6_addr vnameserver6; + /* DNS search names exposed to the guest via DHCP */ + const char **vdnssearch; + /* Domain name exposed to the guest via DHCP */ + const char *vdomainname; + /* MTU when sending packets to the guest */ + /* Default: IF_MTU_DEFAULT */ + size_t if_mtu; + /* MRU when receiving packets from the guest */ + /* Default: IF_MRU_DEFAULT */ + size_t if_mru; + /* Prohibit connecting to 127.0.0.1:* */ + bool disable_host_loopback; + /* + * Enable emulation code (*warning*: this code isn't safe, it is not + * recommended to enable it) + */ + bool enable_emu; + + /* + * Fields introduced in SlirpConfig version 2 begin + */ + /* Address to be used when sending data to the Internet */ + struct sockaddr_in *outbound_addr; + /* IPv6 Address to be used when sending data to the Internet */ + struct sockaddr_in6 *outbound_addr6; + + /* + * Fields introduced in SlirpConfig version 3 begin + */ + /* slirp will not redirect/serve any DNS packet */ + bool disable_dns; + + /* + * Fields introduced in SlirpConfig version 4 begin + */ + /* slirp will not reply to any DHCP requests */ + bool disable_dhcp; + + /* + * Fields introduced in SlirpConfig version 5 begin + */ + /* Manufacturer ID (IANA Private Enterprise number) */ + uint32_t mfr_id; + /* + * MAC address allocated for an out-of-band management controller, to be + * retrieved through NC-SI. + */ + uint8_t oob_eth_addr[6]; +} SlirpConfig; + +/* Create a new instance of a slirp stack */ +SLIRP_EXPORT +Slirp *slirp_new(const SlirpConfig *cfg, const SlirpCb *callbacks, + void *opaque); +/* slirp_init is deprecated in favor of slirp_new */ +SLIRP_EXPORT +Slirp *slirp_init(int restricted, bool in_enabled, struct in_addr vnetwork, + struct in_addr vnetmask, struct in_addr vhost, + bool in6_enabled, struct in6_addr vprefix_addr6, + uint8_t vprefix_len, struct in6_addr vhost6, + const char *vhostname, const char *tftp_server_name, + const char *tftp_path, const char *bootfile, + struct in_addr vdhcp_start, struct in_addr vnameserver, + struct in6_addr vnameserver6, const char **vdnssearch, + const char *vdomainname, const SlirpCb *callbacks, + void *opaque); +/* Shut down an instance of a slirp stack */ +SLIRP_EXPORT +void slirp_cleanup(Slirp *slirp); + +/* This is called by the application when it is about to sleep through poll(). + * *timeout is set to the amount of virtual time (in ms) that the application intends to + * wait (UINT32_MAX if infinite). slirp_pollfds_fill updates it according to + * e.g. TCP timers, so the application knows it should sleep a smaller amount of + * time. slirp_pollfds_fill calls add_poll for each file descriptor + * that should be monitored along the sleep. The opaque pointer is passed as + * such to add_poll, and add_poll returns an index. */ +SLIRP_EXPORT +void slirp_pollfds_fill(Slirp *slirp, uint32_t *timeout, + SlirpAddPollCb add_poll, void *opaque); + +/* This is called by the application after sleeping, to report which file + * descriptors are available. slirp_pollfds_poll calls get_revents on each file + * descriptor, giving it the index that add_poll returned during the + * slirp_pollfds_fill call, to know whether the descriptor is available for + * read/write/etc. (SLIRP_POLL_*) + * select_error should be passed 1 if poll() returned an error. */ +SLIRP_EXPORT +void slirp_pollfds_poll(Slirp *slirp, int select_error, + SlirpGetREventsCb get_revents, void *opaque); + +/* This is called by the application when the guest emits a packet on the + * guest network, to be interpreted by slirp. */ +SLIRP_EXPORT +void slirp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len); + +/* This is called by the application when a timer expires, if it provides + * the timer_new_opaque callback. It is not needed if the application only + * uses timer_new. */ +SLIRP_EXPORT +void slirp_handle_timer(Slirp *slirp, SlirpTimerId id, void *cb_opaque); + +/* These set up / remove port forwarding between a host port in the real world + * and the guest network. + * Note: guest_addr must be in network order, while guest_port must be in host + * order. + */ +SLIRP_EXPORT +int slirp_add_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr, + int host_port, struct in_addr guest_addr, int guest_port); +SLIRP_EXPORT +int slirp_remove_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr, + int host_port); + +#define SLIRP_HOSTFWD_UDP 1 +#define SLIRP_HOSTFWD_V6ONLY 2 +SLIRP_EXPORT +int slirp_add_hostxfwd(Slirp *slirp, + const struct sockaddr *haddr, socklen_t haddrlen, + const struct sockaddr *gaddr, socklen_t gaddrlen, + int flags); +SLIRP_EXPORT +int slirp_remove_hostxfwd(Slirp *slirp, + const struct sockaddr *haddr, socklen_t haddrlen, + int flags); + +/* Set up port forwarding between a port in the guest network and a + * command running on the host */ +SLIRP_EXPORT +int slirp_add_exec(Slirp *slirp, const char *cmdline, + struct in_addr *guest_addr, int guest_port); +/* Set up port forwarding between a port in the guest network and a + * Unix port on the host */ +SLIRP_EXPORT +int slirp_add_unix(Slirp *slirp, const char *unixsock, + struct in_addr *guest_addr, int guest_port); +/* Set up port forwarding between a port in the guest network and a + * callback that will receive the data coming from the port */ +SLIRP_EXPORT +int slirp_add_guestfwd(Slirp *slirp, SlirpWriteCb write_cb, void *opaque, + struct in_addr *guest_addr, int guest_port); + +/* TODO: rather identify a guestfwd through an opaque pointer instead of through + * the guest_addr */ + +/* This is called by the application for a guestfwd, to determine how much data + * can be received by the forwarded port through a call to slirp_socket_recv. */ +SLIRP_EXPORT +size_t slirp_socket_can_recv(Slirp *slirp, struct in_addr guest_addr, + int guest_port); +/* This is called by the application for a guestfwd, to provide the data to be + * sent on the forwarded port */ +SLIRP_EXPORT +void slirp_socket_recv(Slirp *slirp, struct in_addr guest_addr, int guest_port, + const uint8_t *buf, int size); + +/* Remove entries added by slirp_add_exec, slirp_add_unix or slirp_add_guestfwd */ +SLIRP_EXPORT +int slirp_remove_guestfwd(Slirp *slirp, struct in_addr guest_addr, + int guest_port); + +/* Return a human-readable state of the slirp stack */ +SLIRP_EXPORT +char *slirp_connection_info(Slirp *slirp); + +/* Return a human-readable state of the NDP/ARP tables */ +SLIRP_EXPORT +char *slirp_neighbor_info(Slirp *slirp); + +/* Save the slirp state through the write_cb. The opaque pointer is passed as + * such to the write_cb. */ +SLIRP_EXPORT +int slirp_state_save(Slirp *s, SlirpWriteCb write_cb, void *opaque); + +/* Returns the version of the slirp state, to be saved along the state */ +SLIRP_EXPORT +int slirp_state_version(void); + +/* Load the slirp state through the read_cb. The opaque pointer is passed as + * such to the read_cb. The version should be given as it was obtained from + * slirp_state_version when slirp_state_save was called. */ +SLIRP_EXPORT +int slirp_state_load(Slirp *s, int version_id, SlirpReadCb read_cb, + void *opaque); + +/* Return the version of the slirp implementation */ +SLIRP_EXPORT +const char *slirp_version_string(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* LIBSLIRP_H */ diff --git a/src/net/libslirp/src/libslirp.map b/src/net/libslirp/src/libslirp.map new file mode 100644 index 00000000..3921f8a1 --- /dev/null +++ b/src/net/libslirp/src/libslirp.map @@ -0,0 +1,40 @@ +SLIRP_4.0 { +global: + slirp_add_exec; + slirp_add_guestfwd; + slirp_add_hostfwd; + slirp_cleanup; + slirp_connection_info; + slirp_init; + slirp_input; + slirp_pollfds_fill; + slirp_pollfds_poll; + slirp_remove_hostfwd; + slirp_socket_can_recv; + slirp_socket_recv; + slirp_state_load; + slirp_state_save; + slirp_state_version; + slirp_version_string; +local: + *; +}; + +SLIRP_4.1 { + slirp_new; +} SLIRP_4.0; + +SLIRP_4.2 { + slirp_add_unix; + slirp_remove_guestfwd; +} SLIRP_4.1; + +SLIRP_4.5 { + slirp_add_hostxfwd; + slirp_remove_hostxfwd; + slirp_neighbor_info; +} SLIRP_4.2; + +SLIRP_4.7 { + slirp_handle_timer; +} SLIRP_4.5; diff --git a/src/net/libslirp/src/libslirp.test.map b/src/net/libslirp/src/libslirp.test.map new file mode 100644 index 00000000..773376bb --- /dev/null +++ b/src/net/libslirp/src/libslirp.test.map @@ -0,0 +1,9 @@ +SLIRP_4.0 { +global: + main; +local: + *; +}; + +SLIRP_4.1 { +} SLIRP_4.0; diff --git a/src/net/libslirp/src/main.h b/src/net/libslirp/src/main.h new file mode 100644 index 00000000..ca36277e --- /dev/null +++ b/src/net/libslirp/src/main.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#ifndef SLIRP_MAIN_H +#define SLIRP_MAIN_H + +#include "libslirp.h" + +/* The current guest virtual time */ +extern unsigned curtime; +/* Always equal to INADDR_LOOPBACK, in network order */ +extern struct in_addr loopback_addr; +/* Always equal to IN_CLASSA_NET, in network order */ +extern unsigned long loopback_mask; + +/* Send a packet to the guest */ +int if_encap(Slirp *slirp, struct mbuf *ifm); +/* Send a frame to the guest. Flags are passed to the send() call */ +slirp_ssize_t slirp_send(struct socket *so, const void *buf, size_t len, int flags); + +#endif diff --git a/src/net/libslirp/src/mbuf.c b/src/net/libslirp/src/mbuf.c new file mode 100644 index 00000000..5ccbda31 --- /dev/null +++ b/src/net/libslirp/src/mbuf.c @@ -0,0 +1,291 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski + */ + +/* + * mbuf's in SLiRP are much simpler than the real mbufs in + * FreeBSD. They are fixed size, determined by the MTU, + * so that one whole packet can fit. Mbuf's cannot be + * chained together. If there's more data than the mbuf + * could hold, an external g_malloced buffer is pointed to + * by m_ext (and the data pointers) and M_EXT is set in + * the flags + */ + +#include "slirp.h" + +#define MBUF_THRESH 30 + +/* + * Find a nice value for msize + */ +#define SLIRP_MSIZE(mtu) \ + (offsetof(struct mbuf, m_dat) + IF_MAXLINKHDR + TCPIPHDR_DELTA + (mtu)) + +void m_init(Slirp *slirp) +{ + slirp->m_freelist.qh_link = slirp->m_freelist.qh_rlink = &slirp->m_freelist; + slirp->m_usedlist.qh_link = slirp->m_usedlist.qh_rlink = &slirp->m_usedlist; +} + +static void m_cleanup_list(struct slirp_quehead *list_head, bool pkts) +{ + struct mbuf *m, *next, *next2; + bool last; + + m = (struct mbuf *)list_head->qh_link; + while ((struct slirp_quehead *)m != list_head) { + next = m->m_next; + + last = false; + while (1) { + next2 = m->m_nextpkt; + + if (pkts) { + ifs_remque(m); + last = next2 == m; + } else { + last = true; + } + + if (m->m_flags & M_EXT) { + g_free(m->m_ext); + } + + g_free(m); + + if (last) + break; + m = next2; + }; + + m = next; + } + list_head->qh_link = list_head; + list_head->qh_rlink = list_head; +} + +void m_cleanup(Slirp *slirp) +{ + m_cleanup_list(&slirp->m_usedlist, false); + m_cleanup_list(&slirp->m_freelist, false); + m_cleanup_list(&slirp->if_batchq, true); + m_cleanup_list(&slirp->if_fastq, true); +} + +/* + * Get an mbuf from the free list, if there are none + * allocate one + * + * Because fragmentation can occur if we alloc new mbufs and + * free old mbufs, we mark all mbufs above mbuf_thresh as M_DOFREE, + * which tells m_free to actually g_free() it + */ +struct mbuf *m_get(Slirp *slirp) +{ + register struct mbuf *m; + int flags = 0; + + DEBUG_CALL("m_get"); + + if (MBUF_DEBUG || slirp->m_freelist.qh_link == &slirp->m_freelist) { + m = g_malloc(SLIRP_MSIZE(slirp->if_mtu)); + slirp->mbuf_alloced++; + if (MBUF_DEBUG || slirp->mbuf_alloced > MBUF_THRESH) + flags = M_DOFREE; + m->slirp = slirp; + } else { + m = (struct mbuf *)slirp->m_freelist.qh_link; + slirp_remque(m); + } + + /* Insert it in the used list */ + slirp_insque(m, &slirp->m_usedlist); + m->m_flags = (flags | M_USEDLIST); + + /* Initialise it */ + m->m_size = SLIRP_MSIZE(slirp->if_mtu) - offsetof(struct mbuf, m_dat); + m->m_data = m->m_dat; + m->m_len = 0; + m->m_nextpkt = NULL; + m->m_prevpkt = NULL; + m->resolution_requested = false; + m->expiration_date = (uint64_t)-1; + DEBUG_ARG("m = %p", m); + return m; +} + +void m_free(struct mbuf *m) +{ + DEBUG_CALL("m_free"); + DEBUG_ARG("m = %p", m); + + if (m) { + /* Remove from m_usedlist */ + if (m->m_flags & M_USEDLIST) + slirp_remque(m); + + /* If it's M_EXT, free() it */ + if (m->m_flags & M_EXT) { + g_free(m->m_ext); + m->m_flags &= ~M_EXT; + } + /* + * Either free() it or put it on the free list + */ + if (m->m_flags & M_DOFREE) { + m->slirp->mbuf_alloced--; + g_free(m); + } else if ((m->m_flags & M_FREELIST) == 0) { + slirp_insque(m, &m->slirp->m_freelist); + m->m_flags = M_FREELIST; /* Clobber other flags */ + } + } /* if(m) */ +} + +/* + * Copy data from one mbuf to the end of + * the other.. if result is too big for one mbuf, allocate + * an M_EXT data segment + */ +void m_cat(struct mbuf *m, struct mbuf *n) +{ + /* + * If there's no room, realloc + */ + if (M_FREEROOM(m) < n->m_len) + m_inc(m, m->m_len + n->m_len); + + memcpy(m->m_data + m->m_len, n->m_data, n->m_len); + m->m_len += n->m_len; + + m_free(n); +} + + +/* make m 'size' bytes large from m_data */ +void m_inc(struct mbuf *m, int size) +{ + int gapsize; + + /* some compilers throw up on gotos. This one we can fake. */ + if (M_ROOM(m) >= size) { + return; + } + + if (m->m_flags & M_EXT) { + gapsize = m->m_data - m->m_ext; + m->m_ext = g_realloc(m->m_ext, size + gapsize); + } else { + gapsize = m->m_data - m->m_dat; + m->m_ext = g_malloc(size + gapsize); + memcpy(m->m_ext, m->m_dat, m->m_size); + m->m_flags |= M_EXT; + } + + m->m_data = m->m_ext + gapsize; + m->m_size = size + gapsize; +} + + +void m_adj(struct mbuf *m, int len) +{ + if (m == NULL) + return; + if (len >= 0) { + /* Trim from head */ + m->m_data += len; + m->m_len -= len; + } else { + /* Trim from tail */ + len = -len; + m->m_len -= len; + } +} + + +/* + * Copy len bytes from m, starting off bytes into n + */ +int m_copy(struct mbuf *n, struct mbuf *m, int off, int len) +{ + if (len > M_FREEROOM(n)) + return -1; + + memcpy((n->m_data + n->m_len), (m->m_data + off), len); + n->m_len += len; + return 0; +} + + +struct mbuf *dtom(Slirp *slirp, void *dat) +{ + struct mbuf *m; + + DEBUG_CALL("dtom"); + DEBUG_ARG("dat = %p", dat); + + /* bug corrected for M_EXT buffers */ + for (m = (struct mbuf *)slirp->m_usedlist.qh_link; + (struct slirp_quehead *)m != &slirp->m_usedlist; m = m->m_next) { + if (m->m_flags & M_EXT) { + if ((char *)dat >= m->m_ext && (char *)dat < (m->m_ext + m->m_size)) + return m; + } else { + if ((char *)dat >= m->m_dat && (char *)dat < (m->m_dat + m->m_size)) + return m; + } + } + + DEBUG_ERROR("dtom failed"); + + return (struct mbuf *)0; +} + +struct mbuf *m_dup(Slirp *slirp, struct mbuf *m, + bool copy_header, + size_t header_size) +{ + struct mbuf *n; + int mcopy_result; + + /* The previous mbuf was supposed to have it already, we can check it along + * the way */ + assert(M_ROOMBEFORE(m) >= header_size); + + n = m_get(slirp); + m_inc(n, m->m_len + header_size); + + if (copy_header) { + m->m_len += header_size; + m->m_data -= header_size; + mcopy_result = m_copy(n, m, 0, m->m_len); + n->m_data += header_size; + n->m_len -= header_size; + m->m_len -= header_size; + m->m_data += header_size; + } else { + n->m_data += header_size; + mcopy_result = m_copy(n, m, 0, m->m_len); + } + g_assert(mcopy_result == 0); + + return n; +} + +void *mtod_check(struct mbuf *m, size_t len) +{ + if (m->m_len >= len) { + return m->m_data; + } + + DEBUG_ERROR("mtod failed"); + + return NULL; +} + +void *m_end(struct mbuf *m) +{ + return m->m_data + m->m_len; +} diff --git a/src/net/libslirp/src/mbuf.h b/src/net/libslirp/src/mbuf.h new file mode 100644 index 00000000..ff1ddc96 --- /dev/null +++ b/src/net/libslirp/src/mbuf.h @@ -0,0 +1,225 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)mbuf.h 8.3 (Berkeley) 1/21/94 + * mbuf.h,v 1.9 1994/11/14 13:54:20 bde Exp + */ + +#ifndef MBUF_H +#define MBUF_H + +/* + * Macros for type conversion + * mtod(m,t) - convert mbuf pointer to data pointer of correct type + */ +#define mtod(m, t) ((t)(m)->m_data) + +/* XXX About mbufs for slirp: + * Only one mbuf is ever used in a chain, for each "cell" of data. + * m_nextpkt points to the next packet, if fragmented. + * If the data is too large, the M_EXT is used, and a larger block + * is alloced. Therefore, m_free[m] must check for M_EXT and if set + * free the m_ext. This is inefficient memory-wise, but who cares. + */ + +/* + * mbufs allow to have a gap between the start of the allocated buffer (m_ext if + * M_EXT is set, m_dat otherwise) and the in-use data: + * + * |--gapsize----->|---m_len-------> + * |----------m_size------------------------------> + * |----M_ROOM--------------------> + * |-M_FREEROOM--> + * + * ^ ^ ^ + * m_dat/m_ext m_data end of buffer + */ + +/* + * How much room is in the mbuf, from m_data to the end of the mbuf + */ +#define M_ROOM(m) \ + ((m->m_flags & M_EXT) ? (((m)->m_ext + (m)->m_size) - (m)->m_data) : \ + (((m)->m_dat + (m)->m_size) - (m)->m_data)) + +/* + * How much free room there is + */ +#define M_FREEROOM(m) (M_ROOM(m) - (m)->m_len) + +/* + * How much free room there is before m_data + */ +#define M_ROOMBEFORE(m) \ + (((m)->m_flags & M_EXT) ? (m)->m_data - (m)->m_ext \ + : (m)->m_data - (m)->m_dat) + +struct mbuf { + /* XXX should union some of these! */ + /* header at beginning of each mbuf: */ + struct mbuf *m_next; /* Linked list of mbufs */ + struct mbuf *m_prev; + struct mbuf *m_nextpkt; /* Next packet in queue/record */ + struct mbuf *m_prevpkt; /* Flags aren't used in the output queue */ + int m_flags; /* Misc flags */ + + int m_size; /* Size of mbuf, from m_dat or m_ext */ + struct socket *m_so; + + char *m_data; /* Current location of data */ + int m_len; /* Amount of data in this mbuf, from m_data */ + + Slirp *slirp; + bool resolution_requested; + uint64_t expiration_date; + char *m_ext; + /* start of dynamic buffer area, must be last element */ + char m_dat[]; +}; + +static inline void ifs_remque(struct mbuf *ifm) +{ + ifm->m_prevpkt->m_nextpkt = ifm->m_nextpkt; + ifm->m_nextpkt->m_prevpkt = ifm->m_prevpkt; +} + +#define M_EXT 0x01 /* m_ext points to more (malloced) data */ +#define M_FREELIST 0x02 /* mbuf is on free list */ +#define M_USEDLIST 0x04 /* XXX mbuf is on used list (for dtom()) */ +#define M_DOFREE \ + 0x08 /* when m_free is called on the mbuf, free() \ + * it rather than putting it on the free list */ + +/* Called by slirp_new */ +void m_init(Slirp *); + +/* Called by slirp_cleanup */ +void m_cleanup(Slirp *slirp); + +/* Allocate an mbuf */ +struct mbuf *m_get(Slirp *); + +/* Release an mbuf (put possibly put it in allocation cache */ +void m_free(struct mbuf *); + +/* Catenate the second buffer to the end of the first buffer, and release the second */ +void m_cat(struct mbuf *, struct mbuf *); + +/* Grow the mbuf to the given size */ +void m_inc(struct mbuf *, int); + +/* If len is positive, trim that amount from the head of the mbuf. If it is negative, trim it from the tail of the mbuf */ +void m_adj(struct mbuf *, int len); + +/* Copy len bytes from the first buffer at the given offset, to the end of the second buffer */ +int m_copy(struct mbuf *, struct mbuf *, int off, int len); + +/* + * Duplicate the mbuf + * + * copy_header specifies whether the bytes before m_data should also be copied. + * header_size specifies how many bytes are to be reserved before m_data. + */ +struct mbuf *m_dup(Slirp *slirp, struct mbuf *m, bool copy_header, size_t header_size); + +/* + * Given a pointer into an mbuf, return the mbuf + * XXX This is a kludge, I should eliminate the need for it + * Fortunately, it's not used often + */ +struct mbuf *dtom(Slirp *, void *); + +/* Check that the mbuf contains at least len bytes, and return the data */ +void *mtod_check(struct mbuf *, size_t len); + +/* Return the end of the data of the mbuf */ +void *m_end(struct mbuf *); + +/* Initialize the ifs queue of the mbuf */ +static inline void ifs_init(struct mbuf *ifm) +{ + ifm->m_nextpkt = ifm->m_prevpkt = ifm; +} + +#ifdef SLIRP_DEBUG +# define MBUF_DEBUG 1 +#else +# ifdef HAVE_VALGRIND +# include +# define MBUF_DEBUG RUNNING_ON_VALGRIND +# else +# define MBUF_DEBUG 0 +# endif +#endif + +/* + * When a function is given an mbuf as well as the responsibility to free it, we + * want valgrind etc. to properly identify the new responsible for the + * free. Achieve this by making a new copy. For instance: + * + * f0(void) { + * struct mbuf *m = m_get(slirp); + * [...] + * switch (something) { + * case 1: + * f1(m); + * break; + * case 2: + * f2(m); + * break; + * [...] + * } + * } + * + * f1(struct mbuf *m) { + * M_DUP_DEBUG(m->slirp, m); + * [...] + * m_free(m); // but author of f1 might be forgetting this + * } + * + * f0 transfers the freeing responsibility to f1, f2, etc. Without the + * M_DUP_DEBUG call in f1, valgrind would tell us that it is f0 where the buffer + * was allocated, but it's difficult to know whether a leak is actually in f0, + * or in f1, or in f2, etc. Duplicating the mbuf in M_DUP_DEBUG each time the + * responsibility is transferred allows to immediately know where the leak + * actually is. + */ +#define M_DUP_DEBUG(slirp, m, copy_header, header_size) do { \ + if (MBUF_DEBUG) { \ + struct mbuf *__n; \ + __n = m_dup((slirp), (m), (copy_header), (header_size)); \ + m_free(m); \ + (m) = __n; \ + } else { \ + (void) (slirp); (void) (copy_header); \ + g_assert(M_ROOMBEFORE(m) >= (header_size)); \ + } \ +} while(0) + +#endif diff --git a/src/net/libslirp/src/misc.c b/src/net/libslirp/src/misc.c new file mode 100644 index 00000000..3c258350 --- /dev/null +++ b/src/net/libslirp/src/misc.c @@ -0,0 +1,448 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" +#ifdef G_OS_UNIX +#include +#endif + +void slirp_insque(void *a, void *b) +{ + register struct slirp_quehead *element = (struct slirp_quehead *)a; + register struct slirp_quehead *head = (struct slirp_quehead *)b; + element->qh_link = head->qh_link; + head->qh_link = (struct slirp_quehead *)element; + element->qh_rlink = (struct slirp_quehead *)head; + ((struct slirp_quehead *)(element->qh_link))->qh_rlink = + (struct slirp_quehead *)element; +} + +void slirp_remque(void *a) +{ + register struct slirp_quehead *element = (struct slirp_quehead *)a; + ((struct slirp_quehead *)(element->qh_link))->qh_rlink = element->qh_rlink; + ((struct slirp_quehead *)(element->qh_rlink))->qh_link = element->qh_link; + element->qh_rlink = NULL; +} + +/* TODO: IPv6 */ +struct gfwd_list *add_guestfwd(struct gfwd_list **ex_ptr, SlirpWriteCb write_cb, + void *opaque, struct in_addr addr, int port) +{ + struct gfwd_list *f = g_new0(struct gfwd_list, 1); + + f->write_cb = write_cb; + f->opaque = opaque; + f->ex_fport = port; + f->ex_addr = addr; + f->ex_next = *ex_ptr; + *ex_ptr = f; + + return f; +} + +struct gfwd_list *add_exec(struct gfwd_list **ex_ptr, const char *cmdline, + struct in_addr addr, int port) +{ + struct gfwd_list *f = add_guestfwd(ex_ptr, NULL, NULL, addr, port); + + f->ex_exec = g_strdup(cmdline); + + return f; +} + +struct gfwd_list *add_unix(struct gfwd_list **ex_ptr, const char *unixsock, + struct in_addr addr, int port) +{ + struct gfwd_list *f = add_guestfwd(ex_ptr, NULL, NULL, addr, port); + + f->ex_unix = g_strdup(unixsock); + + return f; +} + +int remove_guestfwd(struct gfwd_list **ex_ptr, struct in_addr addr, int port) +{ + for (; *ex_ptr != NULL; ex_ptr = &((*ex_ptr)->ex_next)) { + struct gfwd_list *f = *ex_ptr; + if (f->ex_addr.s_addr == addr.s_addr && f->ex_fport == port) { + *ex_ptr = f->ex_next; + g_free(f->ex_exec); + g_free(f); + return 0; + } + } + return -1; +} + +static int slirp_socketpair_with_oob(int sv[2]) +{ + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = 0, + .sin_addr.s_addr = htonl(INADDR_LOOPBACK), + }; + socklen_t addrlen = sizeof(addr); + int ret, s; + + sv[1] = -1; + s = slirp_socket(AF_INET, SOCK_STREAM, 0); + if (s < 0 || bind(s, (struct sockaddr *)&addr, addrlen) < 0 || + listen(s, 1) < 0 || + getsockname(s, (struct sockaddr *)&addr, &addrlen) < 0) { + goto err; + } + + sv[1] = slirp_socket(AF_INET, SOCK_STREAM, 0); + if (sv[1] < 0) { + goto err; + } + /* + * This connect won't block because we've already listen()ed on + * the server end (even though we won't accept() the connection + * until later on). + */ + do { + ret = connect(sv[1], (struct sockaddr *)&addr, addrlen); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + goto err; + } + + do { + sv[0] = accept(s, (struct sockaddr *)&addr, &addrlen); + } while (sv[0] < 0 && errno == EINTR); + if (sv[0] < 0) { + goto err; + } + + closesocket(s); + return 0; + +err: + g_critical("slirp_socketpair(): %s", strerror(errno)); + if (s >= 0) { + closesocket(s); + } + if (sv[1] >= 0) { + closesocket(sv[1]); + } + return -1; +} + +static void fork_exec_child_setup(gpointer data) +{ +#ifndef _WIN32 + setsid(); + + /* Unblock all signals and leave our exec()-ee to block what it wants */ + sigset_t ss; + sigemptyset(&ss); + sigprocmask(SIG_SETMASK, &ss, NULL); + + /* POSIX is obnoxious about SIGCHLD specifically across exec() */ + signal(SIGCHLD, SIG_DFL); +#endif +} + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +#if !GLIB_CHECK_VERSION(2, 58, 0) +typedef struct SlirpGSpawnFds { + GSpawnChildSetupFunc child_setup; + gpointer user_data; + gint stdin_fd; + gint stdout_fd; + gint stderr_fd; +} SlirpGSpawnFds; + +static inline void slirp_gspawn_fds_setup(gpointer user_data) +{ + SlirpGSpawnFds *q = (SlirpGSpawnFds *)user_data; + + dup2(q->stdin_fd, 0); + dup2(q->stdout_fd, 1); + dup2(q->stderr_fd, 2); + q->child_setup(q->user_data); +} +#endif + +static inline gboolean +g_spawn_async_with_fds_slirp(const gchar *working_directory, gchar **argv, + gchar **envp, GSpawnFlags flags, + GSpawnChildSetupFunc child_setup, + gpointer user_data, GPid *child_pid, gint stdin_fd, + gint stdout_fd, gint stderr_fd, GError **error) +{ +#if GLIB_CHECK_VERSION(2, 58, 0) + return g_spawn_async_with_fds(working_directory, argv, envp, flags, + child_setup, user_data, child_pid, stdin_fd, + stdout_fd, stderr_fd, error); +#else + SlirpGSpawnFds setup = { + .child_setup = child_setup, + .user_data = user_data, + .stdin_fd = stdin_fd, + .stdout_fd = stdout_fd, + .stderr_fd = stderr_fd, + }; + + return g_spawn_async(working_directory, argv, envp, flags, + slirp_gspawn_fds_setup, &setup, child_pid, error); +#endif +} + +#define g_spawn_async_with_fds(wd, argv, env, f, c, d, p, ifd, ofd, efd, err) \ + g_spawn_async_with_fds_slirp(wd, argv, env, f, c, d, p, ifd, ofd, efd, err) + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +int fork_exec(struct socket *so, const char *ex) +{ + GError *err = NULL; + gint argc = 0; + gchar **argv = NULL; + int opt, sp[2]; + + DEBUG_CALL("fork_exec"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("ex = %p", ex); + + if (slirp_socketpair_with_oob(sp) < 0) { + return 0; + } + + if (!g_shell_parse_argv(ex, &argc, &argv, &err)) { + g_critical("fork_exec invalid command: %s\nerror: %s", ex, err->message); + g_error_free(err); + return 0; + } + + g_spawn_async_with_fds(NULL /* cwd */, argv, NULL /* env */, + G_SPAWN_SEARCH_PATH, fork_exec_child_setup, + NULL /* data */, NULL /* child_pid */, sp[1], sp[1], + sp[1], &err); + g_strfreev(argv); + + if (err) { + g_critical("fork_exec: %s", err->message); + g_error_free(err); + closesocket(sp[0]); + closesocket(sp[1]); + return 0; + } + + so->s = sp[0]; + closesocket(sp[1]); + slirp_socket_set_fast_reuse(so->s); + opt = 1; + setsockopt(so->s, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(int)); + slirp_set_nonblock(so->s); + so->slirp->cb->register_poll_fd(so->s, so->slirp->opaque); + return 1; +} + +int open_unix(struct socket *so, const char *unixpath) +{ +#ifdef G_OS_UNIX + struct sockaddr_un sa; + int s; + + DEBUG_CALL("open_unix"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("unixpath = %s", unixpath); + + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + if (g_strlcpy(sa.sun_path, unixpath, sizeof(sa.sun_path)) >= sizeof(sa.sun_path)) { + g_critical("Bad unix path: %s", unixpath); + return 0; + } + + s = slirp_socket(PF_UNIX, SOCK_STREAM, 0); + if (s < 0) { + g_critical("open_unix(): %s", strerror(errno)); + return 0; + } + + if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) < 0) { + g_critical("open_unix(): %s", strerror(errno)); + closesocket(s); + return 0; + } + + so->s = s; + slirp_set_nonblock(so->s); + so->slirp->cb->register_poll_fd(so->s, so->slirp->opaque); + + return 1; +#else + g_assert_not_reached(); +#endif +} + +char *slirp_connection_info(Slirp *slirp) +{ + GString *str = g_string_new(NULL); + const char *const tcpstates[] = { + [TCPS_CLOSED] = "CLOSED", [TCPS_LISTEN] = "LISTEN", + [TCPS_SYN_SENT] = "SYN_SENT", [TCPS_SYN_RECEIVED] = "SYN_RCVD", + [TCPS_ESTABLISHED] = "ESTABLISHED", [TCPS_CLOSE_WAIT] = "CLOSE_WAIT", + [TCPS_FIN_WAIT_1] = "FIN_WAIT_1", [TCPS_CLOSING] = "CLOSING", + [TCPS_LAST_ACK] = "LAST_ACK", [TCPS_FIN_WAIT_2] = "FIN_WAIT_2", + [TCPS_TIME_WAIT] = "TIME_WAIT", + }; + struct in_addr dst_addr; + struct sockaddr_in src; + socklen_t src_len; + uint16_t dst_port; + struct socket *so; + const char *state; + char addr[INET_ADDRSTRLEN]; + char buf[20]; + + g_string_append_printf(str, + " Protocol[State] FD Source Address Port " + "Dest. Address Port RecvQ SendQ\n"); + + /* TODO: IPv6 */ + + for (so = slirp->tcb.so_next; so != &slirp->tcb; so = so->so_next) { + if (so->so_state & SS_HOSTFWD) { + state = "HOST_FORWARD"; + } else if (so->so_tcpcb) { + state = tcpstates[so->so_tcpcb->t_state]; + } else { + state = "NONE"; + } + if (so->so_state & (SS_HOSTFWD | SS_INCOMING)) { + src_len = sizeof(src); + getsockname(so->s, (struct sockaddr *)&src, &src_len); + dst_addr = so->so_laddr; + dst_port = so->so_lport; + } else { + src.sin_addr = so->so_laddr; + src.sin_port = so->so_lport; + dst_addr = so->so_faddr; + dst_port = so->so_fport; + } + slirp_fmt0(buf, sizeof(buf), " TCP[%s]", state); + g_string_append_printf(str, "%-19s %3d %15s %5d ", buf, so->s, + src.sin_addr.s_addr ? + inet_ntop(AF_INET, &src.sin_addr, addr, sizeof(addr)) : "*", + ntohs(src.sin_port)); + g_string_append_printf(str, "%15s %5d %5d %5d\n", + inet_ntop(AF_INET, &dst_addr, addr, sizeof(addr)), + ntohs(dst_port), so->so_rcv.sb_cc, + so->so_snd.sb_cc); + } + + for (so = slirp->udb.so_next; so != &slirp->udb; so = so->so_next) { + if (so->so_state & SS_HOSTFWD) { + slirp_fmt0(buf, sizeof(buf), " UDP[HOST_FORWARD]"); + src_len = sizeof(src); + getsockname(so->s, (struct sockaddr *)&src, &src_len); + dst_addr = so->so_laddr; + dst_port = so->so_lport; + } else { + slirp_fmt0(buf, sizeof(buf), " UDP[%d sec]", + (so->so_expire - curtime) / 1000); + src.sin_addr = so->so_laddr; + src.sin_port = so->so_lport; + dst_addr = so->so_faddr; + dst_port = so->so_fport; + } + g_string_append_printf(str, "%-19s %3d %15s %5d ", buf, so->s, + src.sin_addr.s_addr ? + inet_ntop(AF_INET, &src.sin_addr, addr, sizeof(addr)) : "*", + ntohs(src.sin_port)); + g_string_append_printf(str, "%15s %5d %5d %5d\n", + inet_ntop(AF_INET, &dst_addr, addr, sizeof(addr)), + ntohs(dst_port), so->so_rcv.sb_cc, + so->so_snd.sb_cc); + } + + for (so = slirp->icmp.so_next; so != &slirp->icmp; so = so->so_next) { + slirp_fmt0(buf, sizeof(buf), " ICMP[%d sec]", + (so->so_expire - curtime) / 1000); + src.sin_addr = so->so_laddr; + dst_addr = so->so_faddr; + g_string_append_printf(str, "%-19s %3d %15s - ", buf, so->s, + src.sin_addr.s_addr ? + inet_ntop(AF_INET, &src.sin_addr, addr, sizeof(addr)) : "*"); + g_string_append_printf(str, "%15s - %5d %5d\n", + inet_ntop(AF_INET, &dst_addr, addr, sizeof(addr)), + so->so_rcv.sb_cc, so->so_snd.sb_cc); + } + + return g_string_free(str, FALSE); +} + +char *slirp_neighbor_info(Slirp *slirp) +{ + GString *str = g_string_new(NULL); + ArpTable *arp_table = &slirp->arp_table; + NdpTable *ndp_table = &slirp->ndp_table; + char ip_addr[INET6_ADDRSTRLEN]; + char eth_addr[ETH_ADDRSTRLEN]; + const char *ip; + + g_string_append_printf(str, " %5s %-17s %s\n", + "Table", "MacAddr", "IP Address"); + + for (int i = 0; i < ARP_TABLE_SIZE; ++i) { + struct in_addr addr; + addr.s_addr = arp_table->table[i].ar_sip; + if (!addr.s_addr) { + continue; + } + ip = inet_ntop(AF_INET, &addr, ip_addr, sizeof(ip_addr)); + g_assert(ip != NULL); + g_string_append_printf(str, " %5s %-17s %s\n", "ARP", + slirp_ether_ntoa(arp_table->table[i].ar_sha, + eth_addr, sizeof(eth_addr)), + ip); + } + + for (int i = 0; i < NDP_TABLE_SIZE; ++i) { + if (in6_zero(&ndp_table->table[i].ip_addr)) { + continue; + } + ip = inet_ntop(AF_INET6, &ndp_table->table[i].ip_addr, ip_addr, + sizeof(ip_addr)); + g_assert(ip != NULL); + g_string_append_printf(str, " %5s %-17s %s\n", "NDP", + slirp_ether_ntoa(ndp_table->table[i].eth_addr, + eth_addr, sizeof(eth_addr)), + ip); + } + + return g_string_free(str, FALSE); +} + +int slirp_bind_outbound(struct socket *so, unsigned short af) +{ + int ret = 0; + struct sockaddr *addr = NULL; + int addr_size = 0; + + if (af == AF_INET && so->slirp->outbound_addr != NULL) { + addr = (struct sockaddr *)so->slirp->outbound_addr; + addr_size = sizeof(struct sockaddr_in); + } else if (af == AF_INET6 && so->slirp->outbound_addr6 != NULL) { + addr = (struct sockaddr *)so->slirp->outbound_addr6; + addr_size = sizeof(struct sockaddr_in6); + } + + if (addr != NULL) { + ret = bind(so->s, addr, addr_size); + } + return ret; +} diff --git a/src/net/libslirp/src/misc.h b/src/net/libslirp/src/misc.h new file mode 100644 index 00000000..39fe367a --- /dev/null +++ b/src/net/libslirp/src/misc.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#ifndef MISC_H +#define MISC_H + +#include "libslirp.h" + +struct gfwd_list { + SlirpWriteCb write_cb; + void *opaque; + struct in_addr ex_addr; /* Server address */ + int ex_fport; /* Port to telnet to */ + char *ex_exec; /* Command line of what to exec */ + char *ex_unix; /* unix socket */ + struct gfwd_list *ex_next; +}; + +#define EMU_NONE 0x0 + +/* TCP emulations */ +#define EMU_CTL 0x1 +#define EMU_FTP 0x2 +#define EMU_KSH 0x3 +#define EMU_IRC 0x4 +#define EMU_REALAUDIO 0x5 +#define EMU_RLOGIN 0x6 +#define EMU_IDENT 0x7 + +#define EMU_NOCONNECT 0x10 /* Don't connect */ + +struct tos_t { + uint16_t lport; + uint16_t fport; + uint8_t tos; + uint8_t emu; +}; + +struct emu_t { + uint16_t lport; + uint16_t fport; + uint8_t tos; + uint8_t emu; + struct emu_t *next; +}; + +struct slirp_quehead { + struct slirp_quehead *qh_link; + struct slirp_quehead *qh_rlink; +}; + +/* Insert element a into queue b */ +void slirp_insque(void *a, void *b); + +/* Remove element a from its queue */ +void slirp_remque(void *a); + +/* Run the given command in the background, and expose its output as a socket */ +int fork_exec(struct socket *so, const char *ex); + +/* Create a Unix socket, and expose it as a socket */ +int open_unix(struct socket *so, const char *unixsock); + +/* Add a guest forward on the given address and port, with guest data being + * forwarded by calling write_cb */ +struct gfwd_list *add_guestfwd(struct gfwd_list **ex_ptr, SlirpWriteCb write_cb, + void *opaque, struct in_addr addr, int port); + +/* Run the given command in the backaground, and send its output to the guest on + * the given address and port */ +struct gfwd_list *add_exec(struct gfwd_list **ex_ptr, const char *cmdline, + struct in_addr addr, int port); + +/* Create a Unix socket, and expose it to the guest on the given address and + * port */ +struct gfwd_list *add_unix(struct gfwd_list **ex_ptr, const char *unixsock, + struct in_addr addr, int port); + +/* Remove the guest forward bound to the given address and port */ +int remove_guestfwd(struct gfwd_list **ex_ptr, struct in_addr addr, int port); + +/* Bind the socket to the outbound address specified in the slirp configuration */ +int slirp_bind_outbound(struct socket *so, unsigned short af); + +#endif diff --git a/src/net/libslirp/src/ncsi-pkt.h b/src/net/libslirp/src/ncsi-pkt.h new file mode 100644 index 00000000..27bedf6a --- /dev/null +++ b/src/net/libslirp/src/ncsi-pkt.h @@ -0,0 +1,527 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright Gavin Shan, IBM Corporation 2016. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef NCSI_PKT_H +#define NCSI_PKT_H + +/* from linux/net/ncsi/ncsi-pkt.h */ +#define __be32 uint32_t +#define __be16 uint16_t + +SLIRP_PACKED_BEGIN +struct ncsi_pkt_hdr { + unsigned char mc_id; /* Management controller ID */ + unsigned char revision; /* NCSI version - 0x01 */ + unsigned char reserved; /* Reserved */ + unsigned char id; /* Packet sequence number */ + unsigned char type; /* Packet type */ + unsigned char channel; /* Network controller ID */ + __be16 length; /* Payload length */ + __be32 reserved1[2]; /* Reserved */ +} SLIRP_PACKED_END; + +SLIRP_PACKED_BEGIN +struct ncsi_cmd_pkt_hdr { + struct ncsi_pkt_hdr common; /* Common NCSI packet header */ +} SLIRP_PACKED_END; + +SLIRP_PACKED_BEGIN +struct ncsi_rsp_pkt_hdr { + struct ncsi_pkt_hdr common; /* Common NCSI packet header */ + __be16 code; /* Response code */ + __be16 reason; /* Response reason */ +} SLIRP_PACKED_END; + +SLIRP_PACKED_BEGIN +struct ncsi_aen_pkt_hdr { + struct ncsi_pkt_hdr common; /* Common NCSI packet header */ + unsigned char reserved2[3]; /* Reserved */ + unsigned char type; /* AEN packet type */ +} SLIRP_PACKED_END; + +/* NCSI common command packet */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 checksum; /* Checksum */ + unsigned char pad[26]; +} SLIRP_PACKED_END; + +SLIRP_PACKED_BEGIN +struct ncsi_rsp_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* Select Package */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_sp_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char reserved[3]; /* Reserved */ + unsigned char hw_arbitration; /* HW arbitration */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* Disable Channel */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_dc_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char reserved[3]; /* Reserved */ + unsigned char ald; /* Allow link down */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* Reset Channel */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_rc_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 reserved; /* Reserved */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* AEN Enable */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_ae_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char reserved[3]; /* Reserved */ + unsigned char mc_id; /* MC ID */ + __be32 mode; /* AEN working mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[18]; +} SLIRP_PACKED_END; + +/* Set Link */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_sl_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 mode; /* Link working mode */ + __be32 oem_mode; /* OEM link mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[18]; +} SLIRP_PACKED_END; + +/* Set VLAN Filter */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_svf_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be16 reserved; /* Reserved */ + __be16 vlan; /* VLAN ID */ + __be16 reserved1; /* Reserved */ + unsigned char index; /* VLAN table index */ + unsigned char enable; /* Enable or disable */ + __be32 checksum; /* Checksum */ + unsigned char pad[14]; +} SLIRP_PACKED_END; + +/* Enable VLAN */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_ev_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char reserved[3]; /* Reserved */ + unsigned char mode; /* VLAN filter mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* Set MAC Address */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_sma_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char mac[6]; /* MAC address */ + unsigned char index; /* MAC table index */ + unsigned char at_e; /* Addr type and operation */ + __be32 checksum; /* Checksum */ + unsigned char pad[18]; +} SLIRP_PACKED_END; + +/* Enable Broadcast Filter */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_ebf_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 mode; /* Filter mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* Enable Global Multicast Filter */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_egmf_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 mode; /* Global MC mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* Set NCSI Flow Control */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_snfc_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char reserved[3]; /* Reserved */ + unsigned char mode; /* Flow control mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* OEM Request Command as per NCSI Specification */ +SLIRP_PACKED_BEGIN +struct ncsi_cmd_oem_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 mfr_id; /* Manufacture ID */ + unsigned char data[]; /* OEM Payload Data */ +} SLIRP_PACKED_END; + +/* OEM Response Packet as per NCSI Specification */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_oem_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Command header */ + __be32 mfr_id; /* Manufacture ID */ + unsigned char data[]; /* Payload data */ +} SLIRP_PACKED_END; + +/* Mellanox Response Data */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_oem_mlx_pkt { + unsigned char cmd_rev; /* Command Revision */ + unsigned char cmd; /* Command ID */ + unsigned char param; /* Parameter */ + unsigned char optional; /* Optional data */ + unsigned char data[]; /* Data */ +} SLIRP_PACKED_END; + +/* Get Link Status */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gls_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 status; /* Link status */ + __be32 other; /* Other indications */ + __be32 oem_status; /* OEM link status */ + __be32 checksum; + unsigned char pad[10]; +} SLIRP_PACKED_END; + +/* Get Version ID */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gvi_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 ncsi_version; /* NCSI version */ + unsigned char reserved[3]; /* Reserved */ + unsigned char alpha2; /* NCSI version */ + unsigned char fw_name[12]; /* f/w name string */ + __be32 fw_version; /* f/w version */ + __be16 pci_ids[4]; /* PCI IDs */ + __be32 mf_id; /* Manufacture ID */ + __be32 checksum; +} SLIRP_PACKED_END; + +/* Get Capabilities */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gc_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 cap; /* Capabilities */ + __be32 bc_cap; /* Broadcast cap */ + __be32 mc_cap; /* Multicast cap */ + __be32 buf_cap; /* Buffering cap */ + __be32 aen_cap; /* AEN cap */ + unsigned char vlan_cnt; /* VLAN filter count */ + unsigned char mixed_cnt; /* Mix filter count */ + unsigned char mc_cnt; /* MC filter count */ + unsigned char uc_cnt; /* UC filter count */ + unsigned char reserved[2]; /* Reserved */ + unsigned char vlan_mode; /* VLAN mode */ + unsigned char channel_cnt; /* Channel count */ + __be32 checksum; /* Checksum */ +} SLIRP_PACKED_END; + +/* Get Parameters */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gp_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + unsigned char mac_cnt; /* Number of MAC addr */ + unsigned char reserved[2]; /* Reserved */ + unsigned char mac_enable; /* MAC addr enable flags */ + unsigned char vlan_cnt; /* VLAN tag count */ + unsigned char reserved1; /* Reserved */ + __be16 vlan_enable; /* VLAN tag enable flags */ + __be32 link_mode; /* Link setting */ + __be32 bc_mode; /* BC filter mode */ + __be32 valid_modes; /* Valid mode parameters */ + unsigned char vlan_mode; /* VLAN mode */ + unsigned char fc_mode; /* Flow control mode */ + unsigned char reserved2[2]; /* Reserved */ + __be32 aen_mode; /* AEN mode */ + unsigned char mac[6]; /* Supported MAC addr */ + __be16 vlan; /* Supported VLAN tags */ + __be32 checksum; /* Checksum */ +} SLIRP_PACKED_END; + +/* Get Controller Packet Statistics */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gcps_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 cnt_hi; /* Counter cleared */ + __be32 cnt_lo; /* Counter cleared */ + __be32 rx_bytes; /* Rx bytes */ + __be32 tx_bytes; /* Tx bytes */ + __be32 rx_uc_pkts; /* Rx UC packets */ + __be32 rx_mc_pkts; /* Rx MC packets */ + __be32 rx_bc_pkts; /* Rx BC packets */ + __be32 tx_uc_pkts; /* Tx UC packets */ + __be32 tx_mc_pkts; /* Tx MC packets */ + __be32 tx_bc_pkts; /* Tx BC packets */ + __be32 fcs_err; /* FCS errors */ + __be32 align_err; /* Alignment errors */ + __be32 false_carrier; /* False carrier detection */ + __be32 runt_pkts; /* Rx runt packets */ + __be32 jabber_pkts; /* Rx jabber packets */ + __be32 rx_pause_xon; /* Rx pause XON frames */ + __be32 rx_pause_xoff; /* Rx XOFF frames */ + __be32 tx_pause_xon; /* Tx XON frames */ + __be32 tx_pause_xoff; /* Tx XOFF frames */ + __be32 tx_s_collision; /* Single collision frames */ + __be32 tx_m_collision; /* Multiple collision frames */ + __be32 l_collision; /* Late collision frames */ + __be32 e_collision; /* Excessive collision frames */ + __be32 rx_ctl_frames; /* Rx control frames */ + __be32 rx_64_frames; /* Rx 64-bytes frames */ + __be32 rx_127_frames; /* Rx 65-127 bytes frames */ + __be32 rx_255_frames; /* Rx 128-255 bytes frames */ + __be32 rx_511_frames; /* Rx 256-511 bytes frames */ + __be32 rx_1023_frames; /* Rx 512-1023 bytes frames */ + __be32 rx_1522_frames; /* Rx 1024-1522 bytes frames */ + __be32 rx_9022_frames; /* Rx 1523-9022 bytes frames */ + __be32 tx_64_frames; /* Tx 64-bytes frames */ + __be32 tx_127_frames; /* Tx 65-127 bytes frames */ + __be32 tx_255_frames; /* Tx 128-255 bytes frames */ + __be32 tx_511_frames; /* Tx 256-511 bytes frames */ + __be32 tx_1023_frames; /* Tx 512-1023 bytes frames */ + __be32 tx_1522_frames; /* Tx 1024-1522 bytes frames */ + __be32 tx_9022_frames; /* Tx 1523-9022 bytes frames */ + __be32 rx_valid_bytes; /* Rx valid bytes */ + __be32 rx_runt_pkts; /* Rx error runt packets */ + __be32 rx_jabber_pkts; /* Rx error jabber packets */ + __be32 checksum; /* Checksum */ +} SLIRP_PACKED_END; + +/* Get NCSI Statistics */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gns_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 rx_cmds; /* Rx NCSI commands */ + __be32 dropped_cmds; /* Dropped commands */ + __be32 cmd_type_errs; /* Command type errors */ + __be32 cmd_csum_errs; /* Command checksum errors */ + __be32 rx_pkts; /* Rx NCSI packets */ + __be32 tx_pkts; /* Tx NCSI packets */ + __be32 tx_aen_pkts; /* Tx AEN packets */ + __be32 checksum; /* Checksum */ +} SLIRP_PACKED_END; + +/* Get NCSI Pass-through Statistics */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gnpts_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 tx_pkts; /* Tx packets */ + __be32 tx_dropped; /* Tx dropped packets */ + __be32 tx_channel_err; /* Tx channel errors */ + __be32 tx_us_err; /* Tx undersize errors */ + __be32 rx_pkts; /* Rx packets */ + __be32 rx_dropped; /* Rx dropped packets */ + __be32 rx_channel_err; /* Rx channel errors */ + __be32 rx_us_err; /* Rx undersize errors */ + __be32 rx_os_err; /* Rx oversize errors */ + __be32 checksum; /* Checksum */ +} SLIRP_PACKED_END; + +/* Get package status */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gps_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 status; /* Hardware arbitration status */ + __be32 checksum; +} SLIRP_PACKED_END; + +/* Get package UUID */ +SLIRP_PACKED_BEGIN +struct ncsi_rsp_gpuuid_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + unsigned char uuid[16]; /* UUID */ + __be32 checksum; +} SLIRP_PACKED_END; + +/* AEN: Link State Change */ +SLIRP_PACKED_BEGIN +struct ncsi_aen_lsc_pkt { + struct ncsi_aen_pkt_hdr aen; /* AEN header */ + __be32 status; /* Link status */ + __be32 oem_status; /* OEM link status */ + __be32 checksum; /* Checksum */ + unsigned char pad[14]; +} SLIRP_PACKED_END; + +/* AEN: Configuration Required */ +SLIRP_PACKED_BEGIN +struct ncsi_aen_cr_pkt { + struct ncsi_aen_pkt_hdr aen; /* AEN header */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +} SLIRP_PACKED_END; + +/* AEN: Host Network Controller Driver Status Change */ +SLIRP_PACKED_BEGIN +struct ncsi_aen_hncdsc_pkt { + struct ncsi_aen_pkt_hdr aen; /* AEN header */ + __be32 status; /* Status */ + __be32 checksum; /* Checksum */ + unsigned char pad[18]; +} SLIRP_PACKED_END; + +/* NCSI packet revision */ +#define NCSI_PKT_REVISION 0x01 + +/* NCSI packet commands */ +#define NCSI_PKT_CMD_CIS 0x00 /* Clear Initial State */ +#define NCSI_PKT_CMD_SP 0x01 /* Select Package */ +#define NCSI_PKT_CMD_DP 0x02 /* Deselect Package */ +#define NCSI_PKT_CMD_EC 0x03 /* Enable Channel */ +#define NCSI_PKT_CMD_DC 0x04 /* Disable Channel */ +#define NCSI_PKT_CMD_RC 0x05 /* Reset Channel */ +#define NCSI_PKT_CMD_ECNT 0x06 /* Enable Channel Network Tx */ +#define NCSI_PKT_CMD_DCNT 0x07 /* Disable Channel Network Tx */ +#define NCSI_PKT_CMD_AE 0x08 /* AEN Enable */ +#define NCSI_PKT_CMD_SL 0x09 /* Set Link */ +#define NCSI_PKT_CMD_GLS 0x0a /* Get Link */ +#define NCSI_PKT_CMD_SVF 0x0b /* Set VLAN Filter */ +#define NCSI_PKT_CMD_EV 0x0c /* Enable VLAN */ +#define NCSI_PKT_CMD_DV 0x0d /* Disable VLAN */ +#define NCSI_PKT_CMD_SMA 0x0e /* Set MAC address */ +#define NCSI_PKT_CMD_EBF 0x10 /* Enable Broadcast Filter */ +#define NCSI_PKT_CMD_DBF 0x11 /* Disable Broadcast Filter */ +#define NCSI_PKT_CMD_EGMF 0x12 /* Enable Global Multicast Filter */ +#define NCSI_PKT_CMD_DGMF 0x13 /* Disable Global Multicast Filter */ +#define NCSI_PKT_CMD_SNFC 0x14 /* Set NCSI Flow Control */ +#define NCSI_PKT_CMD_GVI 0x15 /* Get Version ID */ +#define NCSI_PKT_CMD_GC 0x16 /* Get Capabilities */ +#define NCSI_PKT_CMD_GP 0x17 /* Get Parameters */ +#define NCSI_PKT_CMD_GCPS 0x18 /* Get Controller Packet Statistics */ +#define NCSI_PKT_CMD_GNS 0x19 /* Get NCSI Statistics */ +#define NCSI_PKT_CMD_GNPTS 0x1a /* Get NCSI Pass-throu Statistics */ +#define NCSI_PKT_CMD_GPS 0x1b /* Get package status */ +#define NCSI_PKT_CMD_OEM 0x50 /* OEM */ +#define NCSI_PKT_CMD_PLDM 0x51 /* PLDM request over NCSI over RBT */ +#define NCSI_PKT_CMD_GPUUID 0x52 /* Get package UUID */ + +/* NCSI packet responses */ +#define NCSI_PKT_RSP_CIS (NCSI_PKT_CMD_CIS + 0x80) +#define NCSI_PKT_RSP_SP (NCSI_PKT_CMD_SP + 0x80) +#define NCSI_PKT_RSP_DP (NCSI_PKT_CMD_DP + 0x80) +#define NCSI_PKT_RSP_EC (NCSI_PKT_CMD_EC + 0x80) +#define NCSI_PKT_RSP_DC (NCSI_PKT_CMD_DC + 0x80) +#define NCSI_PKT_RSP_RC (NCSI_PKT_CMD_RC + 0x80) +#define NCSI_PKT_RSP_ECNT (NCSI_PKT_CMD_ECNT + 0x80) +#define NCSI_PKT_RSP_DCNT (NCSI_PKT_CMD_DCNT + 0x80) +#define NCSI_PKT_RSP_AE (NCSI_PKT_CMD_AE + 0x80) +#define NCSI_PKT_RSP_SL (NCSI_PKT_CMD_SL + 0x80) +#define NCSI_PKT_RSP_GLS (NCSI_PKT_CMD_GLS + 0x80) +#define NCSI_PKT_RSP_SVF (NCSI_PKT_CMD_SVF + 0x80) +#define NCSI_PKT_RSP_EV (NCSI_PKT_CMD_EV + 0x80) +#define NCSI_PKT_RSP_DV (NCSI_PKT_CMD_DV + 0x80) +#define NCSI_PKT_RSP_SMA (NCSI_PKT_CMD_SMA + 0x80) +#define NCSI_PKT_RSP_EBF (NCSI_PKT_CMD_EBF + 0x80) +#define NCSI_PKT_RSP_DBF (NCSI_PKT_CMD_DBF + 0x80) +#define NCSI_PKT_RSP_EGMF (NCSI_PKT_CMD_EGMF + 0x80) +#define NCSI_PKT_RSP_DGMF (NCSI_PKT_CMD_DGMF + 0x80) +#define NCSI_PKT_RSP_SNFC (NCSI_PKT_CMD_SNFC + 0x80) +#define NCSI_PKT_RSP_GVI (NCSI_PKT_CMD_GVI + 0x80) +#define NCSI_PKT_RSP_GC (NCSI_PKT_CMD_GC + 0x80) +#define NCSI_PKT_RSP_GP (NCSI_PKT_CMD_GP + 0x80) +#define NCSI_PKT_RSP_GCPS (NCSI_PKT_CMD_GCPS + 0x80) +#define NCSI_PKT_RSP_GNS (NCSI_PKT_CMD_GNS + 0x80) +#define NCSI_PKT_RSP_GNPTS (NCSI_PKT_CMD_GNPTS + 0x80) +#define NCSI_PKT_RSP_GPS (NCSI_PKT_CMD_GPS + 0x80) +#define NCSI_PKT_RSP_OEM (NCSI_PKT_CMD_OEM + 0x80) +#define NCSI_PKT_RSP_PLDM (NCSI_PKT_CMD_PLDM + 0x80) +#define NCSI_PKT_RSP_GPUUID (NCSI_PKT_CMD_GPUUID + 0x80) + +/* NCSI response code/reason */ +#define NCSI_PKT_RSP_C_COMPLETED 0x0000 /* Command Completed */ +#define NCSI_PKT_RSP_C_FAILED 0x0001 /* Command Failed */ +#define NCSI_PKT_RSP_C_UNAVAILABLE 0x0002 /* Command Unavailable */ +#define NCSI_PKT_RSP_C_UNSUPPORTED 0x0003 /* Command Unsupported */ +#define NCSI_PKT_RSP_R_NO_ERROR 0x0000 /* No Error */ +#define NCSI_PKT_RSP_R_INTERFACE 0x0001 /* Interface not ready */ +#define NCSI_PKT_RSP_R_PARAM 0x0002 /* Invalid Parameter */ +#define NCSI_PKT_RSP_R_CHANNEL 0x0003 /* Channel not Ready */ +#define NCSI_PKT_RSP_R_PACKAGE 0x0004 /* Package not Ready */ +#define NCSI_PKT_RSP_R_LENGTH 0x0005 /* Invalid payload length */ +#define NCSI_PKT_RSP_R_UNKNOWN 0x7fff /* Command type unsupported */ + +/* NCSI AEN packet type */ +#define NCSI_PKT_AEN 0xFF /* AEN Packet */ +#define NCSI_PKT_AEN_LSC 0x00 /* Link status change */ +#define NCSI_PKT_AEN_CR 0x01 /* Configuration required */ +#define NCSI_PKT_AEN_HNCDSC 0x02 /* HNC driver status change */ + +/* OEM Vendor Manufacture ID */ +#define NCSI_OEM_MFR_MLX_ID 0x8119 +#define NCSI_OEM_MFR_BCM_ID 0x113d +#define NCSI_OEM_MFR_INTEL_ID 0x157 +/* Intel specific OEM command */ +#define NCSI_OEM_INTEL_CMD_GMA 0x06 /* CMD ID for Get MAC */ +#define NCSI_OEM_INTEL_CMD_KEEP_PHY 0x20 /* CMD ID for Keep PHY up */ +/* Broadcom specific OEM Command */ +#define NCSI_OEM_BCM_CMD_GMA 0x01 /* CMD ID for Get MAC */ +/* Mellanox specific OEM Command */ +#define NCSI_OEM_MLX_CMD_GMA 0x00 /* CMD ID for Get MAC */ +#define NCSI_OEM_MLX_CMD_GMA_PARAM 0x1b /* Parameter for GMA */ +#define NCSI_OEM_MLX_CMD_SMAF 0x01 /* CMD ID for Set MC Affinity */ +#define NCSI_OEM_MLX_CMD_SMAF_PARAM 0x07 /* Parameter for SMAF */ +/* Offset in OEM request */ +#define MLX_SMAF_MAC_ADDR_OFFSET 8 /* Offset for MAC in SMAF */ +#define MLX_SMAF_MED_SUPPORT_OFFSET 14 /* Offset for medium in SMAF */ +/* Mac address offset in OEM response */ +#define BCM_MAC_ADDR_OFFSET 28 +#define MLX_MAC_ADDR_OFFSET 8 +#define INTEL_MAC_ADDR_OFFSET 1 + +/* Status offset in OEM response */ +#define MLX_GMA_STATUS_OFFSET 0 +/* OEM Response payload length */ +#define MLX_GMA_PAYLOAD_LEN 24 + +#endif /* NCSI_PKT_H */ diff --git a/src/net/libslirp/src/ncsi.c b/src/net/libslirp/src/ncsi.c new file mode 100644 index 00000000..846da9a4 --- /dev/null +++ b/src/net/libslirp/src/ncsi.c @@ -0,0 +1,326 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * NC-SI (Network Controller Sideband Interface) "echo" model + * + * Copyright (C) 2016-2018 IBM Corp. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "slirp.h" + +#include "ncsi-pkt.h" + +static uint32_t ncsi_calculate_checksum(uint8_t *data, int len) +{ + uint32_t checksum = 0; + int i; + + /* + * 32-bit unsigned sum of the NC-SI packet header and NC-SI packet + * payload interpreted as a series of 16-bit unsigned integer values. + */ + for (i = 0; i < len; i += 2) { + checksum += (((uint16_t) data[i]) << 8) + data[i+1]; + } + + checksum = (~checksum + 1); + return checksum; +} + +/* Response handler for Mellanox command Get Mac Address */ +static int ncsi_rsp_handler_oem_mlx_gma(Slirp *slirp, + const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh) +{ + uint8_t oob_eth_addr_allocated = 0; + struct ncsi_rsp_oem_pkt *rsp; + int i; + + rsp = (struct ncsi_rsp_oem_pkt *)rnh; + + /* Set the payload length */ + rsp->rsp.common.length = htons(MLX_GMA_PAYLOAD_LEN); + + for (i = 0; i < ETH_ALEN; i++) { + if (slirp->oob_eth_addr[i] != 0x00) { + oob_eth_addr_allocated = 1; + break; + } + } + rsp->data[MLX_GMA_STATUS_OFFSET] = oob_eth_addr_allocated; + + /* Set the allocated management address */ + memcpy(&rsp->data[MLX_MAC_ADDR_OFFSET], slirp->oob_eth_addr, ETH_ALEN); + + return 0; +} + +/* Response handler for Mellanox card */ +static int ncsi_rsp_handler_oem_mlx(Slirp *slirp, const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh) +{ + const struct ncsi_cmd_oem_pkt *cmd; + const struct ncsi_rsp_oem_mlx_pkt *cmd_mlx; + struct ncsi_rsp_oem_pkt *rsp; + struct ncsi_rsp_oem_mlx_pkt *rsp_mlx; + + /* Get the command header */ + cmd = (const struct ncsi_cmd_oem_pkt *)nh; + cmd_mlx = (const struct ncsi_rsp_oem_mlx_pkt *)cmd->data; + + /* Get the response header */ + rsp = (struct ncsi_rsp_oem_pkt *)rnh; + rsp_mlx = (struct ncsi_rsp_oem_mlx_pkt *)rsp->data; + + /* Ensure the OEM response header matches the command's */ + rsp_mlx->cmd_rev = cmd_mlx->cmd_rev; + rsp_mlx->cmd = cmd_mlx->cmd; + rsp_mlx->param = cmd_mlx->param; + rsp_mlx->optional = cmd_mlx->optional; + + if (cmd_mlx->cmd == NCSI_OEM_MLX_CMD_GMA && + cmd_mlx->param == NCSI_OEM_MLX_CMD_GMA_PARAM) + return ncsi_rsp_handler_oem_mlx_gma(slirp, nh, rnh); + + rsp->rsp.common.length = htons(8); + rsp->rsp.code = htons(NCSI_PKT_RSP_C_UNSUPPORTED); + rsp->rsp.reason = htons(NCSI_PKT_RSP_R_UNKNOWN); + return -ENOENT; +} + +static const struct ncsi_rsp_oem_handler { + unsigned int mfr_id; + int (*handler)(Slirp *slirp, const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh); +} ncsi_rsp_oem_handlers[] = { + { NCSI_OEM_MFR_MLX_ID, ncsi_rsp_handler_oem_mlx }, + { NCSI_OEM_MFR_BCM_ID, NULL }, + { NCSI_OEM_MFR_INTEL_ID, NULL }, +}; + +/* Response handler for OEM command */ +static int ncsi_rsp_handler_oem(Slirp *slirp, const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh) +{ + const struct ncsi_rsp_oem_handler *nrh = NULL; + const struct ncsi_cmd_oem_pkt *cmd = (const struct ncsi_cmd_oem_pkt *)nh; + struct ncsi_rsp_oem_pkt *rsp = (struct ncsi_rsp_oem_pkt *)rnh; + uint32_t mfr_id = ntohl(cmd->mfr_id); + int i; + + rsp->mfr_id = cmd->mfr_id; + + if (mfr_id != slirp->mfr_id) { + goto error; + } + + /* Check for manufacturer id and Find the handler */ + for (i = 0; i < G_N_ELEMENTS(ncsi_rsp_oem_handlers); i++) { + if (ncsi_rsp_oem_handlers[i].mfr_id == mfr_id) { + if (ncsi_rsp_oem_handlers[i].handler) + nrh = &ncsi_rsp_oem_handlers[i]; + else + nrh = NULL; + + break; + } + } + + if (!nrh) { + goto error; + } + + /* Process the packet */ + return nrh->handler(slirp, nh, rnh); + +error: + rsp->rsp.common.length = htons(8); + rsp->rsp.code = htons(NCSI_PKT_RSP_C_UNSUPPORTED); + rsp->rsp.reason = htons(NCSI_PKT_RSP_R_UNKNOWN); + return -ENOENT; +} + + +/* Get Version ID */ +static int ncsi_rsp_handler_gvi(Slirp *slirp, const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh) +{ + struct ncsi_rsp_gvi_pkt *rsp = (struct ncsi_rsp_gvi_pkt *)rnh; + + rsp->ncsi_version = htonl(0xF1F0F000); + rsp->mf_id = htonl(slirp->mfr_id); + + return 0; +} + +/* Get Capabilities */ +static int ncsi_rsp_handler_gc(Slirp *slirp, const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh) +{ + struct ncsi_rsp_gc_pkt *rsp = (struct ncsi_rsp_gc_pkt *)rnh; + + rsp->cap = htonl(~0); + rsp->bc_cap = htonl(~0); + rsp->mc_cap = htonl(~0); + rsp->buf_cap = htonl(~0); + rsp->aen_cap = htonl(~0); + rsp->vlan_mode = 0xff; + rsp->uc_cnt = 2; + return 0; +} + +/* Get Link status */ +static int ncsi_rsp_handler_gls(Slirp *slirp, const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh) +{ + struct ncsi_rsp_gls_pkt *rsp = (struct ncsi_rsp_gls_pkt *)rnh; + + rsp->status = htonl(0x1); + return 0; +} + +/* Get Parameters */ +static int ncsi_rsp_handler_gp(Slirp *slirp, const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh) +{ + struct ncsi_rsp_gp_pkt *rsp = (struct ncsi_rsp_gp_pkt *)rnh; + + /* no MAC address filters or VLAN filters on the channel */ + rsp->mac_cnt = 0; + rsp->mac_enable = 0; + rsp->vlan_cnt = 0; + rsp->vlan_enable = 0; + + return 0; +} + +static const struct ncsi_rsp_handler { + unsigned char type; + int payload; + int (*handler)(Slirp *slirp, const struct ncsi_pkt_hdr *nh, + struct ncsi_rsp_pkt_hdr *rnh); +} ncsi_rsp_handlers[] = { { NCSI_PKT_RSP_CIS, 4, NULL }, + { NCSI_PKT_RSP_SP, 4, NULL }, + { NCSI_PKT_RSP_DP, 4, NULL }, + { NCSI_PKT_RSP_EC, 4, NULL }, + { NCSI_PKT_RSP_DC, 4, NULL }, + { NCSI_PKT_RSP_RC, 4, NULL }, + { NCSI_PKT_RSP_ECNT, 4, NULL }, + { NCSI_PKT_RSP_DCNT, 4, NULL }, + { NCSI_PKT_RSP_AE, 4, NULL }, + { NCSI_PKT_RSP_SL, 4, NULL }, + { NCSI_PKT_RSP_GLS, 16, ncsi_rsp_handler_gls }, + { NCSI_PKT_RSP_SVF, 4, NULL }, + { NCSI_PKT_RSP_EV, 4, NULL }, + { NCSI_PKT_RSP_DV, 4, NULL }, + { NCSI_PKT_RSP_SMA, 4, NULL }, + { NCSI_PKT_RSP_EBF, 4, NULL }, + { NCSI_PKT_RSP_DBF, 4, NULL }, + { NCSI_PKT_RSP_EGMF, 4, NULL }, + { NCSI_PKT_RSP_DGMF, 4, NULL }, + { NCSI_PKT_RSP_SNFC, 4, NULL }, + { NCSI_PKT_RSP_GVI, 40, ncsi_rsp_handler_gvi }, + { NCSI_PKT_RSP_GC, 32, ncsi_rsp_handler_gc }, + { NCSI_PKT_RSP_GP, 40, ncsi_rsp_handler_gp }, + { NCSI_PKT_RSP_GCPS, 172, NULL }, + { NCSI_PKT_RSP_GNS, 172, NULL }, + { NCSI_PKT_RSP_GNPTS, 172, NULL }, + { NCSI_PKT_RSP_GPS, 8, NULL }, + { NCSI_PKT_RSP_OEM, 0, ncsi_rsp_handler_oem }, + { NCSI_PKT_RSP_PLDM, 0, NULL }, + { NCSI_PKT_RSP_GPUUID, 20, NULL } }; + +/* + * packet format : ncsi header + payload + checksum + */ +#define NCSI_MAX_PAYLOAD 172 +#define NCSI_MAX_LEN (sizeof(struct ncsi_pkt_hdr) + NCSI_MAX_PAYLOAD + 4) + +void ncsi_input(Slirp *slirp, const uint8_t *pkt, int pkt_len) +{ + const struct ncsi_pkt_hdr *nh = + (const struct ncsi_pkt_hdr *)(pkt + ETH_HLEN); + uint8_t ncsi_reply[2 + ETH_HLEN + NCSI_MAX_LEN]; + struct ethhdr *reh = (struct ethhdr *)(ncsi_reply + 2); + struct ncsi_rsp_pkt_hdr *rnh = + (struct ncsi_rsp_pkt_hdr *)(ncsi_reply + 2 + ETH_HLEN); + const struct ncsi_rsp_handler *handler = NULL; + int i; + int ncsi_rsp_len = sizeof(*nh); + uint32_t checksum; + uint32_t *pchecksum; + + if (pkt_len < ETH_HLEN + sizeof(struct ncsi_pkt_hdr)) { + return; /* packet too short */ + } + + memset(ncsi_reply, 0, sizeof(ncsi_reply)); + + memset(reh->h_dest, 0xff, ETH_ALEN); + memset(reh->h_source, 0xff, ETH_ALEN); + reh->h_proto = htons(ETH_P_NCSI); + + for (i = 0; i < G_N_ELEMENTS(ncsi_rsp_handlers); i++) { + if (ncsi_rsp_handlers[i].type == nh->type + 0x80) { + handler = &ncsi_rsp_handlers[i]; + break; + } + } + + rnh->common.mc_id = nh->mc_id; + rnh->common.revision = NCSI_PKT_REVISION; + rnh->common.id = nh->id; + rnh->common.type = nh->type + 0x80; + rnh->common.channel = nh->channel; + + if (handler) { + rnh->common.length = htons(handler->payload); + rnh->code = htons(NCSI_PKT_RSP_C_COMPLETED); + rnh->reason = htons(NCSI_PKT_RSP_R_NO_ERROR); + + if (handler->handler) { + handler->handler(slirp, nh, rnh); + } + ncsi_rsp_len += ntohs(rnh->common.length); + } else { + rnh->common.length = 0; + rnh->code = htons(NCSI_PKT_RSP_C_UNAVAILABLE); + rnh->reason = htons(NCSI_PKT_RSP_R_UNKNOWN); + } + + /* Add the optional checksum at the end of the frame. */ + checksum = ncsi_calculate_checksum((uint8_t *)rnh, ncsi_rsp_len); + pchecksum = (uint32_t *)((char *)rnh + ncsi_rsp_len); + *pchecksum = htonl(checksum); + ncsi_rsp_len += 4; + + slirp_send_packet_all(slirp, ncsi_reply + 2, ETH_HLEN + ncsi_rsp_len); +} diff --git a/src/net/libslirp/src/ndp_table.c b/src/net/libslirp/src/ndp_table.c new file mode 100644 index 00000000..fdb189d5 --- /dev/null +++ b/src/net/libslirp/src/ndp_table.c @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2013 + * Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne. + */ + +#include "slirp.h" + +void ndp_table_add(Slirp *slirp, struct in6_addr ip_addr, + uint8_t ethaddr[ETH_ALEN]) +{ + char addrstr[INET6_ADDRSTRLEN]; + NdpTable *ndp_table = &slirp->ndp_table; + int i; + char ethaddr_str[ETH_ADDRSTRLEN]; + + inet_ntop(AF_INET6, &(ip_addr), addrstr, INET6_ADDRSTRLEN); + + DEBUG_CALL("ndp_table_add"); + DEBUG_ARG("ip = %s", addrstr); + DEBUG_ARG("hw addr = %s", slirp_ether_ntoa(ethaddr, ethaddr_str, + sizeof(ethaddr_str))); + + if (IN6_IS_ADDR_MULTICAST(&ip_addr) || in6_zero(&ip_addr)) { + /* Do not register multicast or unspecified addresses */ + DEBUG_CALL(" abort: do not register multicast or unspecified address"); + return; + } + + /* Search for an entry */ + for (i = 0; i < NDP_TABLE_SIZE; i++) { + if (in6_equal(&ndp_table->table[i].ip_addr, &ip_addr)) { + DEBUG_CALL(" already in table: update the entry"); + /* Update the entry */ + memcpy(ndp_table->table[i].eth_addr, ethaddr, ETH_ALEN); + return; + } + } + + /* No entry found, create a new one */ + DEBUG_CALL(" create new entry"); + /* Save the first entry, it is the guest. */ + if (in6_zero(&ndp_table->guest_in6_addr)) { + ndp_table->guest_in6_addr = ip_addr; + } + ndp_table->table[ndp_table->next_victim].ip_addr = ip_addr; + memcpy(ndp_table->table[ndp_table->next_victim].eth_addr, ethaddr, + ETH_ALEN); + ndp_table->next_victim = (ndp_table->next_victim + 1) % NDP_TABLE_SIZE; +} + +bool ndp_table_search(Slirp *slirp, struct in6_addr ip_addr, + uint8_t out_ethaddr[ETH_ALEN]) +{ + char addrstr[INET6_ADDRSTRLEN]; + NdpTable *ndp_table = &slirp->ndp_table; + int i; + char ethaddr_str[ETH_ADDRSTRLEN]; + + inet_ntop(AF_INET6, &(ip_addr), addrstr, INET6_ADDRSTRLEN); + + DEBUG_CALL("ndp_table_search"); + DEBUG_ARG("ip = %s", addrstr); + + /* If unspecified address */ + if (in6_zero(&ip_addr)) { + /* return Ethernet broadcast address */ + memset(out_ethaddr, 0xff, ETH_ALEN); + return 1; + } + + /* Multicast address: fec0::abcd:efgh/8 -> 33:33:ab:cd:ef:gh */ + if (IN6_IS_ADDR_MULTICAST(&ip_addr)) { + out_ethaddr[0] = 0x33; + out_ethaddr[1] = 0x33; + out_ethaddr[2] = ip_addr.s6_addr[12]; + out_ethaddr[3] = ip_addr.s6_addr[13]; + out_ethaddr[4] = ip_addr.s6_addr[14]; + out_ethaddr[5] = ip_addr.s6_addr[15]; + DEBUG_ARG("multicast addr = %s", + slirp_ether_ntoa(out_ethaddr, ethaddr_str, + sizeof(ethaddr_str))); + return 1; + } + + for (i = 0; i < NDP_TABLE_SIZE; i++) { + if (in6_equal(&ndp_table->table[i].ip_addr, &ip_addr)) { + memcpy(out_ethaddr, ndp_table->table[i].eth_addr, ETH_ALEN); + DEBUG_ARG("found hw addr = %s", + slirp_ether_ntoa(out_ethaddr, ethaddr_str, + sizeof(ethaddr_str))); + return 1; + } + } + + DEBUG_CALL(" ip not found in table"); + return 0; +} diff --git a/src/net/libslirp/src/sbuf.c b/src/net/libslirp/src/sbuf.c new file mode 100644 index 00000000..6126baad --- /dev/null +++ b/src/net/libslirp/src/sbuf.c @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" + +static void sbappendsb(struct sbuf *sb, struct mbuf *m); + +void sbfree(struct sbuf *sb) +{ + g_free(sb->sb_data); +} + +bool sbdrop(struct sbuf *sb, size_t num) +{ + int limit = sb->sb_datalen / 2; + + g_warn_if_fail(num <= sb->sb_cc); + if (num > sb->sb_cc) + num = sb->sb_cc; + + sb->sb_cc -= num; + sb->sb_rptr += num; + if (sb->sb_rptr >= sb->sb_data + sb->sb_datalen) + sb->sb_rptr -= sb->sb_datalen; + + if (sb->sb_cc < limit && sb->sb_cc + num >= limit) { + return true; + } + + return false; +} + +void sbreserve(struct sbuf *sb, size_t size) +{ + sb->sb_wptr = sb->sb_rptr = sb->sb_data = g_realloc(sb->sb_data, size); + sb->sb_cc = 0; + sb->sb_datalen = size; +} + +void sbappend(struct socket *so, struct mbuf *m) +{ + int ret = 0; + + DEBUG_CALL("sbappend"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("m = %p", m); + DEBUG_ARG("m->m_len = %d", m->m_len); + + /* Shouldn't happen, but... e.g. foreign host closes connection */ + if (m->m_len <= 0) { + m_free(m); + return; + } + + /* + * If there is urgent data, call sosendoob + * if not all was sent, sowrite will take care of the rest + * (The rest of this function is just an optimisation) + */ + if (so->so_urgc) { + sbappendsb(&so->so_rcv, m); + m_free(m); + sosendoob(so); + return; + } + + /* + * We only write if there's nothing in the buffer, + * ottherwise it'll arrive out of order, and hence corrupt + */ + if (!so->so_rcv.sb_cc) + ret = slirp_send(so, m->m_data, m->m_len, 0); + + if (ret <= 0) { + /* + * Nothing was written + * It's possible that the socket has closed, but + * we don't need to check because if it has closed, + * it will be detected in the normal way by soread() + */ + sbappendsb(&so->so_rcv, m); + } else if (ret != m->m_len) { + /* + * Something was written, but not everything.. + * sbappendsb the rest + */ + m->m_len -= ret; + m->m_data += ret; + sbappendsb(&so->so_rcv, m); + } /* else */ + /* Whatever happened, we free the mbuf */ + m_free(m); +} + +/* + * Copy the data from m into sb + * The caller is responsible to make sure there's enough room + */ +static void sbappendsb(struct sbuf *sb, struct mbuf *m) +{ + int len, n, nn; + + len = m->m_len; + + if (sb->sb_wptr < sb->sb_rptr) { + n = sb->sb_rptr - sb->sb_wptr; + if (n > len) + n = len; + memcpy(sb->sb_wptr, m->m_data, n); + } else { + /* Do the right edge first */ + n = sb->sb_data + sb->sb_datalen - sb->sb_wptr; + if (n > len) + n = len; + memcpy(sb->sb_wptr, m->m_data, n); + len -= n; + if (len) { + /* Now the left edge */ + nn = sb->sb_rptr - sb->sb_data; + if (nn > len) + nn = len; + memcpy(sb->sb_data, m->m_data + n, nn); + n += nn; + } + } + + sb->sb_cc += n; + sb->sb_wptr += n; + if (sb->sb_wptr >= sb->sb_data + sb->sb_datalen) + sb->sb_wptr -= sb->sb_datalen; +} + +void sbcopy(struct sbuf *sb, size_t off, size_t len, char *to) +{ + char *from; + + g_assert(len + off <= sb->sb_cc); + + from = sb->sb_rptr + off; + if (from >= sb->sb_data + sb->sb_datalen) + from -= sb->sb_datalen; + + if (from < sb->sb_wptr) { + memcpy(to, from, len); + } else { + /* re-use off */ + off = (sb->sb_data + sb->sb_datalen) - from; + if (off > len) + off = len; + memcpy(to, from, off); + len -= off; + if (len) + memcpy(to + off, sb->sb_data, len); + } +} diff --git a/src/net/libslirp/src/sbuf.h b/src/net/libslirp/src/sbuf.h new file mode 100644 index 00000000..a3eaf496 --- /dev/null +++ b/src/net/libslirp/src/sbuf.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#ifndef SBUF_H +#define SBUF_H + +/* How many bytes are free in the sbuf */ +#define sbspace(sb) ((sb)->sb_datalen - (sb)->sb_cc) + +struct sbuf { + uint32_t sb_cc; /* actual chars in buffer */ + uint32_t sb_datalen; /* Length of data */ + char *sb_wptr; /* write pointer. points to where the next + * bytes should be written in the sbuf */ + char *sb_rptr; /* read pointer. points to where the next + * byte should be read from the sbuf */ + char *sb_data; /* Actual data */ +}; + +/* Release the sbuf */ +void sbfree(struct sbuf *sb); + +/* Drop len bytes from the reading end of the sbuf */ +bool sbdrop(struct sbuf *sb, size_t len); + +/* (re)Allocate sbuf buffer to store size bytes */ +void sbreserve(struct sbuf *sb, size_t size); + +/* + * Try and write() to the socket, whatever doesn't get written + * append to the buffer... for a host with a fast net connection, + * this prevents an unnecessary copy of the data + * (the socket is non-blocking, so we won't hang) + */ +void sbappend(struct socket *sb, struct mbuf *mb); + +/* + * Copy data from sbuf to a normal, straight buffer + * Don't update the sbuf rptr, this will be + * done in sbdrop when the data is acked + */ +void sbcopy(struct sbuf *sb, size_t off, size_t len, char *p); + +#endif diff --git a/src/net/libslirp/src/slirp.c b/src/net/libslirp/src/slirp.c new file mode 100644 index 00000000..fd68f632 --- /dev/null +++ b/src/net/libslirp/src/slirp.c @@ -0,0 +1,1642 @@ +/* SPDX-License-Identifier: MIT */ +/* + * libslirp glue + * + * Copyright (c) 2004-2008 Fabrice Bellard + * + * 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. + */ +#include "slirp.h" + + +#ifndef _WIN32 +#include +#endif + +/* https://gitlab.freedesktop.org/slirp/libslirp/issues/18 */ +#if defined(__NetBSD__) && defined(if_mtu) +#undef if_mtu +#endif + +#if defined(_WIN32) + +#define INITIAL_DNS_ADDR_BUF_SIZE 32 * 1024 +#define REALLOC_RETRIES 5 + +// Broadcast site local DNS resolvers. We do not use these because they are +// highly unlikely to be valid. +// https://www.ietf.org/proceedings/52/I-D/draft-ietf-ipngwg-dns-discovery-03.txt +static const struct in6_addr SITE_LOCAL_DNS_BROADCAST_ADDRS[] = { + { + {{ + 0xfe, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + }} + }, + { + {{ + 0xfe, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 + }} + }, + { + {{ + 0xfe, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + }} + }, +}; + +#endif + +int slirp_debug; + +/* Define to 1 if you want KEEPALIVE timers */ +bool slirp_do_keepalive; + +/* host loopback address */ +struct in_addr loopback_addr; +/* host loopback network mask */ +unsigned long loopback_mask; + +/* emulated hosts use the MAC addr 52:55:IP:IP:IP:IP */ +static const uint8_t special_ethaddr[ETH_ALEN] = { 0x52, 0x55, 0x00, + 0x00, 0x00, 0x00 }; + +unsigned curtime; + +static struct in_addr dns_addr; +static struct in6_addr dns6_addr; +static uint32_t dns6_scope_id; +static unsigned dns_addr_time; +static unsigned dns6_addr_time; + +#define TIMEOUT_FAST 2 /* milliseconds */ +#define TIMEOUT_SLOW 499 /* milliseconds */ +/* for the aging of certain requests like DNS */ +#define TIMEOUT_DEFAULT 1000 /* milliseconds */ + +#if defined(_WIN32) + +int get_dns_addr(struct in_addr *pdns_addr) +{ + FIXED_INFO *FixedInfo = NULL; + ULONG BufLen; + DWORD ret; + IP_ADDR_STRING *pIPAddr; + struct in_addr tmp_addr; + + if (dns_addr.s_addr != 0 && (curtime - dns_addr_time) < TIMEOUT_DEFAULT) { + *pdns_addr = dns_addr; + return 0; + } + + FixedInfo = (FIXED_INFO *)GlobalAlloc(GPTR, sizeof(FIXED_INFO)); + BufLen = sizeof(FIXED_INFO); + + if (ERROR_BUFFER_OVERFLOW == GetNetworkParams(FixedInfo, &BufLen)) { + if (FixedInfo) { + GlobalFree(FixedInfo); + FixedInfo = NULL; + } + FixedInfo = GlobalAlloc(GPTR, BufLen); + } + + if ((ret = GetNetworkParams(FixedInfo, &BufLen)) != ERROR_SUCCESS) { + printf("GetNetworkParams failed. ret = %08x\n", (unsigned)ret); + if (FixedInfo) { + GlobalFree(FixedInfo); + FixedInfo = NULL; + } + return -1; + } + + pIPAddr = &(FixedInfo->DnsServerList); + inet_aton(pIPAddr->IpAddress.String, &tmp_addr); + *pdns_addr = tmp_addr; + dns_addr = tmp_addr; + dns_addr_time = curtime; + if (FixedInfo) { + GlobalFree(FixedInfo); + FixedInfo = NULL; + } + return 0; +} + +static int is_site_local_dns_broadcast(struct in6_addr *address) +{ + int i; + for (i = 0; i < G_N_ELEMENTS(SITE_LOCAL_DNS_BROADCAST_ADDRS); i++) { + if (in6_equal(address, &SITE_LOCAL_DNS_BROADCAST_ADDRS[i])) { + return 1; + } + } + return 0; +} + +static void print_dns_v6_address(struct in6_addr address) +{ + char address_str[INET6_ADDRSTRLEN] = ""; + if (inet_ntop(AF_INET6, &address, address_str, INET6_ADDRSTRLEN) + == NULL) { + DEBUG_ERROR("Failed to stringify IPv6 address for logging."); + return; + } + DEBUG_RAW_CALL("IPv6 DNS server found: %s", address_str); +} + +// Gets the first valid DNS resolver with an IPv6 address. +// Ignores any site local broadcast DNS servers, as these +// are on deprecated addresses and not generally expected +// to work. Further details at: +// https://www.ietf.org/proceedings/52/I-D/draft-ietf-ipngwg-dns-discovery-03.txt +static int get_ipv6_dns_server(struct in6_addr *dns_server_address, uint32_t *scope_id) +{ + PIP_ADAPTER_ADDRESSES addresses = NULL; + PIP_ADAPTER_ADDRESSES address = NULL; + IP_ADAPTER_DNS_SERVER_ADDRESS *dns_server = NULL; + struct sockaddr_in6 *dns_v6_addr = NULL; + + ULONG buf_size = INITIAL_DNS_ADDR_BUF_SIZE; + DWORD res = ERROR_BUFFER_OVERFLOW; + int i; + + for (i = 0; i < REALLOC_RETRIES; i++) { + // If non null, we hit buffer overflow, free it so we can try again. + if (addresses != NULL) { + g_free(addresses); + } + + addresses = g_malloc(buf_size); + res = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, NULL, + addresses, &buf_size); + + if (res != ERROR_BUFFER_OVERFLOW) { + break; + } + } + + if (res != NO_ERROR) { + DEBUG_ERROR("Failed to get IPv6 DNS addresses due to error %lX", res); + goto failure; + } + + address = addresses; + for (address = addresses; address != NULL; address = address->Next) { + for (dns_server = address->FirstDnsServerAddress; + dns_server != NULL; + dns_server = dns_server->Next) { + + if (dns_server->Address.lpSockaddr->sa_family != AF_INET6) { + continue; + } + + dns_v6_addr = (struct sockaddr_in6 *)dns_server->Address.lpSockaddr; + if (is_site_local_dns_broadcast(&dns_v6_addr->sin6_addr) == 0) { + print_dns_v6_address(dns_v6_addr->sin6_addr); + *dns_server_address = dns_v6_addr->sin6_addr; + *scope_id = dns_v6_addr->sin6_scope_id; + + g_free(addresses); + return 0; + } + } + } + + DEBUG_ERROR("No IPv6 DNS servers found.\n"); + +failure: + g_free(addresses); + return -1; +} + +int get_dns6_addr(struct in6_addr *pdns6_addr, uint32_t *scope_id) +{ + if (!in6_zero(&dns6_addr) && (curtime - dns6_addr_time) < TIMEOUT_DEFAULT) { + *pdns6_addr = dns6_addr; + *scope_id = dns6_scope_id; + return 0; + } + + if (get_ipv6_dns_server(pdns6_addr, scope_id) == 0) { + dns6_addr = *pdns6_addr; + dns6_addr_time = curtime; + dns6_scope_id = *scope_id; + return 0; + } + + return -1; +} + +static void winsock_cleanup(void) +{ + WSACleanup(); +} + +#elif defined(__APPLE__) + +#include + +static int get_dns_addr_cached(void *pdns_addr, void *cached_addr, + socklen_t addrlen, unsigned *cached_time) +{ + if (curtime - *cached_time < TIMEOUT_DEFAULT) { + memcpy(pdns_addr, cached_addr, addrlen); + return 0; + } + return 1; +} + +static int get_dns_addr_libresolv(int af, void *pdns_addr, void *cached_addr, + socklen_t addrlen, + uint32_t *scope_id, uint32_t *cached_scope_id, + unsigned *cached_time) +{ + struct __res_state state; + union res_sockaddr_union servers[NI_MAXSERV]; + int count; + int found; + void *addr; + + // we only support IPv4 and IPv4, we assume it's one or the other + assert(af == AF_INET || af == AF_INET6); + + if (res_ninit(&state) != 0) { + return -1; + } + + count = res_getservers(&state, servers, NI_MAXSERV); + found = 0; + DEBUG_MISC("IP address of your DNS(s):"); + for (int i = 0; i < count; i++) { + if (af == servers[i].sin.sin_family) { + found++; + } + if (af == AF_INET) { + addr = &servers[i].sin.sin_addr; + } else { // af == AF_INET6 + addr = &servers[i].sin6.sin6_addr; + } + + // we use the first found entry + if (found == 1) { + memcpy(pdns_addr, addr, addrlen); + memcpy(cached_addr, addr, addrlen); + if (scope_id) { + *scope_id = 0; + } + if (cached_scope_id) { + *cached_scope_id = 0; + } + *cached_time = curtime; + } + + if (found > 3) { + DEBUG_MISC(" (more)"); + break; + } else if (slirp_debug & DBG_MISC) { + char s[INET6_ADDRSTRLEN]; + const char *res = inet_ntop(af, addr, s, sizeof(s)); + if (!res) { + res = " (string conversion error)"; + } + DEBUG_MISC(" %s", res); + } + } + + res_ndestroy(&state); + if (!found) + return -1; + return 0; +} + +int get_dns_addr(struct in_addr *pdns_addr) +{ + if (dns_addr.s_addr != 0) { + int ret; + ret = get_dns_addr_cached(pdns_addr, &dns_addr, sizeof(dns_addr), + &dns_addr_time); + if (ret <= 0) { + return ret; + } + } + return get_dns_addr_libresolv(AF_INET, pdns_addr, &dns_addr, + sizeof(dns_addr), NULL, NULL, &dns_addr_time); +} + +int get_dns6_addr(struct in6_addr *pdns6_addr, uint32_t *scope_id) +{ + if (!in6_zero(&dns6_addr)) { + int ret; + ret = get_dns_addr_cached(pdns6_addr, &dns6_addr, sizeof(dns6_addr), + &dns6_addr_time); + if (ret == 0) { + *scope_id = dns6_scope_id; + } + if (ret <= 0) { + return ret; + } + } + return get_dns_addr_libresolv(AF_INET6, pdns6_addr, &dns6_addr, + sizeof(dns6_addr), + scope_id, &dns6_scope_id, &dns6_addr_time); +} + +#else // !defined(_WIN32) && !defined(__APPLE__) + +#if defined(__HAIKU__) +#define RESOLV_CONF_PATH "/boot/system/settings/network/resolv.conf" +#else +#define RESOLV_CONF_PATH "/etc/resolv.conf" +#endif + +static int get_dns_addr_cached(void *pdns_addr, void *cached_addr, + socklen_t addrlen, struct stat *cached_stat, + unsigned *cached_time) +{ + struct stat old_stat; + if (curtime - *cached_time < TIMEOUT_DEFAULT) { + memcpy(pdns_addr, cached_addr, addrlen); + return 0; + } + old_stat = *cached_stat; + if (stat(RESOLV_CONF_PATH, cached_stat) != 0) { + return -1; + } + if (cached_stat->st_dev == old_stat.st_dev && + cached_stat->st_ino == old_stat.st_ino && + cached_stat->st_size == old_stat.st_size && + cached_stat->st_mtime == old_stat.st_mtime) { + memcpy(pdns_addr, cached_addr, addrlen); + return 0; + } + return 1; +} + +static bool try_and_setdns_server(int af, unsigned found, unsigned if_index, + const char *buff2, void *pdns_addr, void *cached_addr, + socklen_t addrlen, uint32_t *scope_id, uint32_t *cached_scope_id, + unsigned *cached_time) +{ + union { + struct in_addr dns_addr; + struct in6_addr dns6_addr; + } tmp_addr; + + assert(sizeof(tmp_addr) >= addrlen); + + if (!inet_pton(af, buff2, &tmp_addr)) + return false; + + /* If it's the first one, set it to dns_addr */ + if (!found) { + memcpy(pdns_addr, &tmp_addr, addrlen); + memcpy(cached_addr, &tmp_addr, addrlen); + if (scope_id) { + *scope_id = if_index; + } + if (cached_scope_id) { + *cached_scope_id = if_index; + } + *cached_time = curtime; + } + + if (found > 2) { + DEBUG_MISC(" (more)"); + } else if (slirp_debug & DBG_MISC) { + char s[INET6_ADDRSTRLEN]; + const char *res = inet_ntop(af, &tmp_addr, s, sizeof(s)); + if (!res) { + res = " (string conversion error)"; + } + DEBUG_MISC(" %s", res); + } + + return true; +} + +static int get_dns_addr_resolv_conf(int af, void *pdns_addr, void *cached_addr, + socklen_t addrlen, + uint32_t *scope_id, uint32_t *cached_scope_id, + unsigned *cached_time) +{ + char buff[512]; + char buff2[257]; + FILE *f; + int found = 0; + unsigned if_index; + unsigned nameservers = 0; + + f = fopen(RESOLV_CONF_PATH, "r"); + if (!f) + return -1; + + DEBUG_MISC("IP address of your DNS(s):"); + while (fgets(buff, 512, f) != NULL) { + if (sscanf(buff, "nameserver%*[ \t]%256s", buff2) == 1) { + char *c = strchr(buff2, '%'); + if (c) { + if_index = if_nametoindex(c + 1); + *c = '\0'; + } else { + if_index = 0; + } + + nameservers++; + + if (!try_and_setdns_server(af, found, if_index, buff2, pdns_addr, + cached_addr, addrlen, scope_id, + cached_scope_id, cached_time)) + continue; + + if (++found > 3) + break; + } + } + fclose(f); + if (nameservers && !found) + return -1; + if (!nameservers) { + found += try_and_setdns_server(af, found, 0, "127.0.0.1", + pdns_addr, cached_addr, addrlen, scope_id, + cached_scope_id, cached_time); + found += try_and_setdns_server(af, found, 0, "::1", + pdns_addr, cached_addr, addrlen, scope_id, + cached_scope_id, cached_time); + } + + return found ? 0 : -1; +} + +int get_dns_addr(struct in_addr *pdns_addr) +{ + static struct stat dns_addr_stat; + + if (dns_addr.s_addr != 0) { + int ret; + ret = get_dns_addr_cached(pdns_addr, &dns_addr, sizeof(dns_addr), + &dns_addr_stat, &dns_addr_time); + if (ret <= 0) { + return ret; + } + } + return get_dns_addr_resolv_conf(AF_INET, pdns_addr, &dns_addr, + sizeof(dns_addr), + NULL, NULL, &dns_addr_time); +} + +int get_dns6_addr(struct in6_addr *pdns6_addr, uint32_t *scope_id) +{ + static struct stat dns6_addr_stat; + + if (!in6_zero(&dns6_addr)) { + int ret; + ret = get_dns_addr_cached(pdns6_addr, &dns6_addr, sizeof(dns6_addr), + &dns6_addr_stat, &dns6_addr_time); + if (ret == 0) { + *scope_id = dns6_scope_id; + } + if (ret <= 0) { + return ret; + } + } + return get_dns_addr_resolv_conf(AF_INET6, pdns6_addr, &dns6_addr, + sizeof(dns6_addr), + scope_id, &dns6_scope_id, &dns6_addr_time); +} + +#endif + +static void slirp_init_once(void) +{ + static int initialized; + const char *debug; +#ifdef _WIN32 + WSADATA Data; +#endif + + if (initialized) { + return; + } + initialized = 1; + +#ifdef _WIN32 + WSAStartup(MAKEWORD(2, 0), &Data); + atexit(winsock_cleanup); +#endif + + loopback_addr.s_addr = htonl(INADDR_LOOPBACK); + loopback_mask = htonl(IN_CLASSA_NET); + + debug = g_getenv("SLIRP_DEBUG"); + if (debug) { + const GDebugKey keys[] = { + { "call", DBG_CALL }, + { "misc", DBG_MISC }, + { "error", DBG_ERROR }, + { "tftp", DBG_TFTP }, + { "verbose_call", DBG_VERBOSE_CALL }, + }; + slirp_debug = g_parse_debug_string(debug, keys, G_N_ELEMENTS(keys)); + } +} + +static void ra_timer_handler_cb(void *opaque) +{ + Slirp *slirp = opaque; + + ra_timer_handler(slirp, NULL); +} + +void slirp_handle_timer(Slirp *slirp, SlirpTimerId id, void *cb_opaque) +{ + g_return_if_fail(id >= 0 && id < SLIRP_TIMER_NUM); + + switch (id) { + case SLIRP_TIMER_RA: + ra_timer_handler(slirp, cb_opaque); + return; + default: + abort(); + } +} + +void *slirp_timer_new(Slirp *slirp, SlirpTimerId id, void *cb_opaque) +{ + g_return_val_if_fail(id >= 0 && id < SLIRP_TIMER_NUM, NULL); + + if (slirp->cfg_version >= 4 && slirp->cb->timer_new_opaque) { + return slirp->cb->timer_new_opaque(id, cb_opaque, slirp->opaque); + } + + switch (id) { + case SLIRP_TIMER_RA: + g_return_val_if_fail(cb_opaque == NULL, NULL); + return slirp->cb->timer_new(ra_timer_handler_cb, slirp, slirp->opaque); + + default: + abort(); + } +} + +Slirp *slirp_new(const SlirpConfig *cfg, const SlirpCb *callbacks, void *opaque) +{ + Slirp *slirp; + + g_return_val_if_fail(cfg != NULL, NULL); + g_return_val_if_fail(cfg->version >= SLIRP_CONFIG_VERSION_MIN, NULL); + g_return_val_if_fail(cfg->version <= SLIRP_CONFIG_VERSION_MAX, NULL); + g_return_val_if_fail(cfg->if_mtu >= IF_MTU_MIN || cfg->if_mtu == 0, NULL); + g_return_val_if_fail(cfg->if_mtu <= IF_MTU_MAX, NULL); + g_return_val_if_fail(cfg->if_mru >= IF_MRU_MIN || cfg->if_mru == 0, NULL); + g_return_val_if_fail(cfg->if_mru <= IF_MRU_MAX, NULL); + g_return_val_if_fail(!cfg->bootfile || + (strlen(cfg->bootfile) < + G_SIZEOF_MEMBER(struct bootp_t, bp_file)), NULL); + + slirp = g_malloc0(sizeof(Slirp)); + + slirp_init_once(); + + slirp->cfg_version = cfg->version; + slirp->opaque = opaque; + slirp->cb = callbacks; + slirp->grand = g_rand_new(); + slirp->restricted = cfg->restricted; + + slirp->in_enabled = cfg->in_enabled; + slirp->in6_enabled = cfg->in6_enabled; + + if_init(slirp); + ip_init(slirp); + + m_init(slirp); + + slirp->vnetwork_addr = cfg->vnetwork; + slirp->vnetwork_mask = cfg->vnetmask; + slirp->vhost_addr = cfg->vhost; + slirp->vprefix_addr6 = cfg->vprefix_addr6; + slirp->vprefix_len = cfg->vprefix_len; + slirp->vhost_addr6 = cfg->vhost6; + if (cfg->vhostname) { + slirp_pstrcpy(slirp->client_hostname, sizeof(slirp->client_hostname), + cfg->vhostname); + } + slirp->tftp_prefix = g_strdup(cfg->tftp_path); + slirp->bootp_filename = g_strdup(cfg->bootfile); + slirp->vdomainname = g_strdup(cfg->vdomainname); + slirp->vdhcp_startaddr = cfg->vdhcp_start; + slirp->vnameserver_addr = cfg->vnameserver; + slirp->vnameserver_addr6 = cfg->vnameserver6; + slirp->tftp_server_name = g_strdup(cfg->tftp_server_name); + + if (cfg->vdnssearch) { + translate_dnssearch(slirp, cfg->vdnssearch); + } + slirp->if_mtu = cfg->if_mtu == 0 ? IF_MTU_DEFAULT : cfg->if_mtu; + slirp->if_mru = cfg->if_mru == 0 ? IF_MRU_DEFAULT : cfg->if_mru; + slirp->disable_host_loopback = cfg->disable_host_loopback; + slirp->enable_emu = cfg->enable_emu; + + if (cfg->version >= 2) { + slirp->outbound_addr = cfg->outbound_addr; + slirp->outbound_addr6 = cfg->outbound_addr6; + } else { + slirp->outbound_addr = NULL; + slirp->outbound_addr6 = NULL; + } + + if (cfg->version >= 3) { + slirp->disable_dns = cfg->disable_dns; + } else { + slirp->disable_dns = false; + } + + if (cfg->version >= 4) { + slirp->disable_dhcp = cfg->disable_dhcp; + } else { + slirp->disable_dhcp = false; + } + + if (slirp->cfg_version >= 4 && slirp->cb->init_completed) { + slirp->cb->init_completed(slirp, slirp->opaque); + } + + if (cfg->version >= 5) { + slirp->mfr_id = cfg->mfr_id; + memcpy(slirp->oob_eth_addr, cfg->oob_eth_addr, ETH_ALEN); + } else { + slirp->mfr_id = 0; + memset(slirp->oob_eth_addr, 0, ETH_ALEN); + } + + ip6_post_init(slirp); + return slirp; +} + +Slirp *slirp_init(int restricted, bool in_enabled, struct in_addr vnetwork, + struct in_addr vnetmask, struct in_addr vhost, + bool in6_enabled, struct in6_addr vprefix_addr6, + uint8_t vprefix_len, struct in6_addr vhost6, + const char *vhostname, const char *tftp_server_name, + const char *tftp_path, const char *bootfile, + struct in_addr vdhcp_start, struct in_addr vnameserver, + struct in6_addr vnameserver6, const char **vdnssearch, + const char *vdomainname, const SlirpCb *callbacks, + void *opaque) +{ + SlirpConfig cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.version = 1; + cfg.restricted = restricted; + cfg.in_enabled = in_enabled; + cfg.vnetwork = vnetwork; + cfg.vnetmask = vnetmask; + cfg.vhost = vhost; + cfg.in6_enabled = in6_enabled; + cfg.vprefix_addr6 = vprefix_addr6; + cfg.vprefix_len = vprefix_len; + cfg.vhost6 = vhost6; + cfg.vhostname = vhostname; + cfg.tftp_server_name = tftp_server_name; + cfg.tftp_path = tftp_path; + cfg.bootfile = bootfile; + cfg.vdhcp_start = vdhcp_start; + cfg.vnameserver = vnameserver; + cfg.vnameserver6 = vnameserver6; + cfg.vdnssearch = vdnssearch; + cfg.vdomainname = vdomainname; + return slirp_new(&cfg, callbacks, opaque); +} + +void slirp_cleanup(Slirp *slirp) +{ + struct gfwd_list *e, *next; + + for (e = slirp->guestfwd_list; e; e = next) { + next = e->ex_next; + g_free(e->ex_exec); + g_free(e->ex_unix); + g_free(e); + } + + ip_cleanup(slirp); + ip6_cleanup(slirp); + m_cleanup(slirp); + tftp_cleanup(slirp); + + g_rand_free(slirp->grand); + + g_free(slirp->vdnssearch); + g_free(slirp->tftp_prefix); + g_free(slirp->bootp_filename); + g_free(slirp->vdomainname); + g_free(slirp); +} + +#define CONN_CANFSEND(so) \ + (((so)->so_state & (SS_FCANTSENDMORE | SS_ISFCONNECTED)) == SS_ISFCONNECTED) +#define CONN_CANFRCV(so) \ + (((so)->so_state & (SS_FCANTRCVMORE | SS_ISFCONNECTED)) == SS_ISFCONNECTED) + +static void slirp_update_timeout(Slirp *slirp, uint32_t *timeout) +{ + uint32_t t; + + if (*timeout <= TIMEOUT_FAST) { + return; + } + + t = MIN(1000, *timeout); + + /* If we have tcp timeout with slirp, then we will fill @timeout with + * more precise value. + */ + if (slirp->time_fasttimo) { + *timeout = TIMEOUT_FAST; + return; + } + if (slirp->do_slowtimo) { + t = MIN(TIMEOUT_SLOW, t); + } + *timeout = t; +} + +void slirp_pollfds_fill(Slirp *slirp, uint32_t *timeout, + SlirpAddPollCb add_poll, void *opaque) +{ + struct socket *so, *so_next; + + /* + * First, TCP sockets + */ + + /* + * *_slowtimo needs calling if there are IP fragments + * in the fragment queue, or there are TCP connections active + */ + slirp->do_slowtimo = ((slirp->tcb.so_next != &slirp->tcb) || + (&slirp->ipq.ip_link != slirp->ipq.ip_link.next)); + + for (so = slirp->tcb.so_next; so != &slirp->tcb; so = so_next) { + int events = 0; + + so_next = so->so_next; + + so->pollfds_idx = -1; + + /* + * See if we need a tcp_fasttimo + */ + if (slirp->time_fasttimo == 0 && so->so_tcpcb->t_flags & TF_DELACK) { + slirp->time_fasttimo = curtime; /* Flag when want a fasttimo */ + } + + /* + * NOFDREF can include still connecting to local-host, + * newly socreated() sockets etc. Don't want to select these. + */ + if (so->so_state & SS_NOFDREF || so->s == -1) { + continue; + } + + /* + * Set for reading sockets which are accepting + */ + if (so->so_state & SS_FACCEPTCONN) { + so->pollfds_idx = add_poll( + so->s, SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR, opaque); + continue; + } + + /* + * Set for writing sockets which are connecting + */ + if (so->so_state & SS_ISFCONNECTING) { + so->pollfds_idx = + add_poll(so->s, SLIRP_POLL_OUT | SLIRP_POLL_ERR, opaque); + continue; + } + + /* + * Set for writing if we are connected, can send more, and + * we have something to send + */ + if (CONN_CANFSEND(so) && so->so_rcv.sb_cc) { + events |= SLIRP_POLL_OUT | SLIRP_POLL_ERR; + } + + /* + * Set for reading (and urgent data) if we are connected, can + * receive more, and we have room for it. + * + * If sb is already half full, we will wait for the guest to consume it, + * and notify again in sbdrop() when the sb becomes less than half full. + */ + if (CONN_CANFRCV(so) && + (so->so_snd.sb_cc < (so->so_snd.sb_datalen / 2))) { + events |= SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR | + SLIRP_POLL_PRI; + } + + if (events) { + so->pollfds_idx = add_poll(so->s, events, opaque); + } + } + + /* + * UDP sockets + */ + for (so = slirp->udb.so_next; so != &slirp->udb; so = so_next) { + so_next = so->so_next; + + so->pollfds_idx = -1; + + /* + * See if it's timed out + */ + if (so->so_expire) { + if (so->so_expire <= curtime) { + udp_detach(so); + continue; + } else { + slirp->do_slowtimo = true; /* Let socket expire */ + } + } + + /* + * When UDP packets are received from over the + * link, they're sendto()'d straight away, so + * no need for setting for writing + * Limit the number of packets queued by this session + * to 4. Note that even though we try and limit this + * to 4 packets, the session could have more queued + * if the packets needed to be fragmented + * (XXX <= 4 ?) + */ + if ((so->so_state & SS_ISFCONNECTED) && so->so_queued <= 4) { + so->pollfds_idx = add_poll( + so->s, SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR, opaque); + } + } + + /* + * ICMP sockets + */ + for (so = slirp->icmp.so_next; so != &slirp->icmp; so = so_next) { + so_next = so->so_next; + + so->pollfds_idx = -1; + + /* + * See if it's timed out + */ + if (so->so_expire) { + if (so->so_expire <= curtime) { + icmp_detach(so); + continue; + } else { + slirp->do_slowtimo = true; /* Let socket expire */ + } + } + + if (so->so_state & SS_ISFCONNECTED) { + so->pollfds_idx = add_poll( + so->s, SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR, opaque); + } + } + + slirp_update_timeout(slirp, timeout); +} + +void slirp_pollfds_poll(Slirp *slirp, int select_error, + SlirpGetREventsCb get_revents, void *opaque) +{ + struct socket *so, *so_next; + int ret; + + curtime = slirp->cb->clock_get_ns(slirp->opaque) / SCALE_MS; + + /* + * See if anything has timed out + */ + if (slirp->time_fasttimo && + ((curtime - slirp->time_fasttimo) >= TIMEOUT_FAST)) { + tcp_fasttimo(slirp); + slirp->time_fasttimo = 0; + } + if (slirp->do_slowtimo && + ((curtime - slirp->last_slowtimo) >= TIMEOUT_SLOW)) { + ip_slowtimo(slirp); + tcp_slowtimo(slirp); + slirp->last_slowtimo = curtime; + } + + /* + * Check sockets + */ + if (!select_error) { + /* + * Check TCP sockets + */ + for (so = slirp->tcb.so_next; so != &slirp->tcb; so = so_next) { + int revents; + + so_next = so->so_next; + + revents = 0; + if (so->pollfds_idx != -1) { + revents = get_revents(so->pollfds_idx, opaque); + } + + if (so->so_state & SS_NOFDREF || so->s == -1) { + continue; + } + +#ifndef __APPLE__ + /* + * Check for URG data + * This will soread as well, so no need to + * test for SLIRP_POLL_IN below if this succeeds. + * + * This is however disabled on MacOS, which apparently always + * reports data as PRI when it is the last data of the + * connection. We would then report it out of band, which the guest + * would most probably not be ready for. + */ + if (revents & SLIRP_POLL_PRI) { + ret = sorecvoob(so); + if (ret < 0) { + /* Socket error might have resulted in the socket being + * removed, do not try to do anything more with it. */ + continue; + } + } + /* + * Check sockets for reading + */ + else +#endif + if (revents & + (SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR | SLIRP_POLL_PRI)) { + /* + * Check for incoming connections + */ + if (so->so_state & SS_FACCEPTCONN) { + tcp_connect(so); + continue; + } /* else */ + ret = soread(so); + + /* Output it if we read something */ + if (ret > 0) { + tcp_output(sototcpcb(so)); + } + if (ret < 0) { + /* Socket error might have resulted in the socket being + * removed, do not try to do anything more with it. */ + continue; + } + } + + /* + * Check sockets for writing + */ + if (!(so->so_state & SS_NOFDREF) && + (revents & (SLIRP_POLL_OUT | SLIRP_POLL_ERR))) { + /* + * Check for non-blocking, still-connecting sockets + */ + if (so->so_state & SS_ISFCONNECTING) { + /* Connected */ + so->so_state &= ~SS_ISFCONNECTING; + + ret = send(so->s, (const void *)&ret, 0, 0); + if (ret < 0) { + /* XXXXX Must fix, zero bytes is a NOP */ + if (errno == EAGAIN || errno == EWOULDBLOCK || + errno == EINPROGRESS || errno == ENOTCONN) { + continue; + } + + /* else failed */ + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_NOFDREF; + } + /* else so->so_state &= ~SS_ISFCONNECTING; */ + + /* + * Continue tcp_input + */ + tcp_input((struct mbuf *)NULL, sizeof(struct ip), so, + so->so_ffamily); + /* continue; */ + } else { + ret = sowrite(so); + if (ret > 0) { + /* Call tcp_output in case we need to send a window + * update to the guest, otherwise it will be stuck + * until it sends a window probe. */ + tcp_output(sototcpcb(so)); + } + } + } + } + + /* + * Now UDP sockets. + * Incoming packets are sent straight away, they're not buffered. + * Incoming UDP data isn't buffered either. + */ + for (so = slirp->udb.so_next; so != &slirp->udb; so = so_next) { + int revents; + + so_next = so->so_next; + + revents = 0; + if (so->pollfds_idx != -1) { + revents = get_revents(so->pollfds_idx, opaque); + } + + if (so->s != -1 && + (revents & (SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR))) { + sorecvfrom(so); + } + } + + /* + * Check incoming ICMP relies. + */ + for (so = slirp->icmp.so_next; so != &slirp->icmp; so = so_next) { + int revents; + + so_next = so->so_next; + + revents = 0; + if (so->pollfds_idx != -1) { + revents = get_revents(so->pollfds_idx, opaque); + } + + if (so->s != -1 && + (revents & (SLIRP_POLL_IN | SLIRP_POLL_HUP | SLIRP_POLL_ERR))) { + if (so->so_type == IPPROTO_IPV6 || so->so_type == IPPROTO_ICMPV6) + icmp6_receive(so); + else + icmp_receive(so); + } + } + } + + if_start(slirp); +} + +static void arp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len) +{ + const struct slirp_arphdr *ah = + (const struct slirp_arphdr *)(pkt + ETH_HLEN); + uint8_t arp_reply[MAX(2 + ETH_HLEN + sizeof(struct slirp_arphdr), 2 + 64)]; + struct ethhdr *reh = (struct ethhdr *)(arp_reply + 2); + struct slirp_arphdr *rah = (struct slirp_arphdr *)(arp_reply + 2 + ETH_HLEN); + int ar_op; + struct gfwd_list *ex_ptr; + + if (!slirp->in_enabled) { + return; + } + + if (pkt_len < ETH_HLEN + sizeof(struct slirp_arphdr)) { + return; /* packet too short */ + } + + ar_op = ntohs(ah->ar_op); + switch (ar_op) { + case ARPOP_REQUEST: + if (ah->ar_tip == ah->ar_sip) { + /* Gratuitous ARP */ + arp_table_add(slirp, ah->ar_sip, ah->ar_sha); + return; + } + + if ((ah->ar_tip & slirp->vnetwork_mask.s_addr) == + slirp->vnetwork_addr.s_addr) { + if (ah->ar_tip == slirp->vnameserver_addr.s_addr || + ah->ar_tip == slirp->vhost_addr.s_addr) + goto arp_ok; + /* TODO: IPv6 */ + for (ex_ptr = slirp->guestfwd_list; ex_ptr; + ex_ptr = ex_ptr->ex_next) { + if (ex_ptr->ex_addr.s_addr == ah->ar_tip) + goto arp_ok; + } + return; + arp_ok: + memset(arp_reply, 0, sizeof(arp_reply)); + + arp_table_add(slirp, ah->ar_sip, ah->ar_sha); + + /* ARP request for alias/dns mac address */ + memcpy(reh->h_dest, pkt + ETH_ALEN, ETH_ALEN); + memcpy(reh->h_source, special_ethaddr, ETH_ALEN - 4); + memcpy(&reh->h_source[2], &ah->ar_tip, 4); + reh->h_proto = htons(ETH_P_ARP); + + rah->ar_hrd = htons(1); + rah->ar_pro = htons(ETH_P_IP); + rah->ar_hln = ETH_ALEN; + rah->ar_pln = 4; + rah->ar_op = htons(ARPOP_REPLY); + memcpy(rah->ar_sha, reh->h_source, ETH_ALEN); + rah->ar_sip = ah->ar_tip; + memcpy(rah->ar_tha, ah->ar_sha, ETH_ALEN); + rah->ar_tip = ah->ar_sip; + slirp_send_packet_all(slirp, arp_reply + 2, sizeof(arp_reply) - 2); + } + break; + case ARPOP_REPLY: + arp_table_add(slirp, ah->ar_sip, ah->ar_sha); + break; + default: + break; + } +} + +void slirp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len) +{ + struct mbuf *m; + int proto; + + if (pkt_len < ETH_HLEN) + return; + + proto = (((uint16_t)pkt[12]) << 8) + pkt[13]; + switch (proto) { + case ETH_P_ARP: + arp_input(slirp, pkt, pkt_len); + break; + case ETH_P_IP: + case ETH_P_IPV6: + m = m_get(slirp); + if (!m) + return; + /* Note: we add 2 to align the IP header on 8 bytes despite the ethernet + * header, and add the margin for the tcpiphdr overhead */ + if (M_FREEROOM(m) < pkt_len + TCPIPHDR_DELTA + 2) { + m_inc(m, pkt_len + TCPIPHDR_DELTA + 2); + } + m->m_len = pkt_len + TCPIPHDR_DELTA + 2; + memcpy(m->m_data + TCPIPHDR_DELTA + 2, pkt, pkt_len); + + m->m_data += TCPIPHDR_DELTA + 2 + ETH_HLEN; + m->m_len -= TCPIPHDR_DELTA + 2 + ETH_HLEN; + + if (proto == ETH_P_IP) { + ip_input(m); + } else if (proto == ETH_P_IPV6) { + ip6_input(m); + } + break; + + case ETH_P_NCSI: + ncsi_input(slirp, pkt, pkt_len); + break; + + default: + break; + } +} + +/* Prepare the IPv4 packet to be sent to the ethernet device. Returns 1 if no + * packet should be sent, 0 if the packet must be re-queued, 2 if the packet + * is ready to go. + */ +static int if_encap4(Slirp *slirp, struct mbuf *ifm, struct ethhdr *eh, + uint8_t ethaddr[ETH_ALEN]) +{ + const struct ip *iph = (const struct ip *)ifm->m_data; + + if (!arp_table_search(slirp, iph->ip_dst.s_addr, ethaddr)) { + uint8_t arp_req[2 + ETH_HLEN + sizeof(struct slirp_arphdr)]; + struct ethhdr *reh = (struct ethhdr *)(arp_req + 2); + struct slirp_arphdr *rah = (struct slirp_arphdr *)(arp_req + 2 + ETH_HLEN); + + if (!ifm->resolution_requested) { + /* If the client addr is not known, send an ARP request */ + memset(reh->h_dest, 0xff, ETH_ALEN); + memcpy(reh->h_source, special_ethaddr, ETH_ALEN - 4); + memcpy(&reh->h_source[2], &slirp->vhost_addr, 4); + reh->h_proto = htons(ETH_P_ARP); + rah->ar_hrd = htons(1); + rah->ar_pro = htons(ETH_P_IP); + rah->ar_hln = ETH_ALEN; + rah->ar_pln = 4; + rah->ar_op = htons(ARPOP_REQUEST); + + /* source hw addr */ + memcpy(rah->ar_sha, special_ethaddr, ETH_ALEN - 4); + memcpy(&rah->ar_sha[2], &slirp->vhost_addr, 4); + + /* source IP */ + rah->ar_sip = slirp->vhost_addr.s_addr; + + /* target hw addr (none) */ + memset(rah->ar_tha, 0, ETH_ALEN); + + /* target IP */ + rah->ar_tip = iph->ip_dst.s_addr; + slirp->client_ipaddr = iph->ip_dst; + slirp_send_packet_all(slirp, arp_req + 2, sizeof(arp_req) - 2); + ifm->resolution_requested = true; + + /* Expire request and drop outgoing packet after 1 second */ + ifm->expiration_date = + slirp->cb->clock_get_ns(slirp->opaque) + 1000000000ULL; + } + return 0; + } else { + memcpy(eh->h_source, special_ethaddr, ETH_ALEN - 4); + /* XXX: not correct */ + memcpy(&eh->h_source[2], &slirp->vhost_addr, 4); + eh->h_proto = htons(ETH_P_IP); + + /* Send this */ + return 2; + } +} + +/* Prepare the IPv6 packet to be sent to the ethernet device. Returns 1 if no + * packet should be sent, 0 if the packet must be re-queued, 2 if the packet + * is ready to go. + */ +static int if_encap6(Slirp *slirp, struct mbuf *ifm, struct ethhdr *eh, + uint8_t ethaddr[ETH_ALEN]) +{ + const struct ip6 *ip6h = mtod(ifm, const struct ip6 *); + if (!ndp_table_search(slirp, ip6h->ip_dst, ethaddr)) { + if (!ifm->resolution_requested) { + ndp_send_ns(slirp, ip6h->ip_dst); + ifm->resolution_requested = true; + ifm->expiration_date = + slirp->cb->clock_get_ns(slirp->opaque) + 1000000000ULL; + } + return 0; + } else { + eh->h_proto = htons(ETH_P_IPV6); + in6_compute_ethaddr(ip6h->ip_src, eh->h_source); + + /* Send this */ + return 2; + } +} + +/* Output the IP packet to the ethernet device. Returns 0 if the packet must be + * re-queued. + */ +int if_encap(Slirp *slirp, struct mbuf *ifm) +{ + uint8_t buf[IF_MTU_MAX + 100]; + struct ethhdr *eh = (struct ethhdr *)(buf + 2); + uint8_t ethaddr[ETH_ALEN]; + const struct ip *iph = (const struct ip *)ifm->m_data; + int ret; + char ethaddr_str[ETH_ADDRSTRLEN]; + + if (ifm->m_len + ETH_HLEN > sizeof(buf) - 2) { + return 1; + } + + switch (iph->ip_v) { + case IPVERSION: + ret = if_encap4(slirp, ifm, eh, ethaddr); + if (ret < 2) { + return ret; + } + break; + + case IP6VERSION: + ret = if_encap6(slirp, ifm, eh, ethaddr); + if (ret < 2) { + return ret; + } + break; + + default: + g_assert_not_reached(); + } + + memcpy(eh->h_dest, ethaddr, ETH_ALEN); + DEBUG_ARG("src = %s", slirp_ether_ntoa(eh->h_source, ethaddr_str, + sizeof(ethaddr_str))); + DEBUG_ARG("dst = %s", slirp_ether_ntoa(eh->h_dest, ethaddr_str, + sizeof(ethaddr_str))); + memcpy(buf + 2 + sizeof(struct ethhdr), ifm->m_data, ifm->m_len); + slirp_send_packet_all(slirp, buf + 2, ifm->m_len + ETH_HLEN); + return 1; +} + +/* Drop host forwarding rule, return 0 if found. */ +int slirp_remove_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr, + int host_port) +{ + struct socket *so; + struct socket *head = (is_udp ? &slirp->udb : &slirp->tcb); + struct sockaddr_in addr; + int port = htons(host_port); + socklen_t addr_len; + + for (so = head->so_next; so != head; so = so->so_next) { + addr_len = sizeof(addr); + if ((so->so_state & SS_HOSTFWD) && + getsockname(so->s, (struct sockaddr *)&addr, &addr_len) == 0 && + addr_len == sizeof(addr) && + addr.sin_family == AF_INET && + addr.sin_addr.s_addr == host_addr.s_addr && + addr.sin_port == port) { + so->slirp->cb->unregister_poll_fd(so->s, so->slirp->opaque); + closesocket(so->s); + sofree(so); + return 0; + } + } + + return -1; +} + +int slirp_add_hostfwd(Slirp *slirp, int is_udp, struct in_addr host_addr, + int host_port, struct in_addr guest_addr, int guest_port) +{ + if (!guest_addr.s_addr) { + guest_addr = slirp->vdhcp_startaddr; + } + if (is_udp) { + if (!udp_listen(slirp, host_addr.s_addr, htons(host_port), + guest_addr.s_addr, htons(guest_port), SS_HOSTFWD)) + return -1; + } else { + if (!tcp_listen(slirp, host_addr.s_addr, htons(host_port), + guest_addr.s_addr, htons(guest_port), SS_HOSTFWD)) + return -1; + } + return 0; +} + +int slirp_remove_hostxfwd(Slirp *slirp, + const struct sockaddr *haddr, socklen_t haddrlen, + int flags) +{ + struct socket *so; + struct socket *head = (flags & SLIRP_HOSTFWD_UDP ? &slirp->udb : &slirp->tcb); + struct sockaddr_storage addr; + socklen_t addr_len; + + for (so = head->so_next; so != head; so = so->so_next) { + addr_len = sizeof(addr); + if ((so->so_state & SS_HOSTFWD) && + getsockname(so->s, (struct sockaddr *)&addr, &addr_len) == 0 && + sockaddr_equal(&addr, (const struct sockaddr_storage *) haddr)) { + so->slirp->cb->unregister_poll_fd(so->s, so->slirp->opaque); + closesocket(so->s); + sofree(so); + return 0; + } + } + + return -1; +} + +int slirp_add_hostxfwd(Slirp *slirp, + const struct sockaddr *haddr, socklen_t haddrlen, + const struct sockaddr *gaddr, socklen_t gaddrlen, + int flags) +{ + struct sockaddr_in gdhcp_addr; + int fwd_flags = SS_HOSTFWD; + + if (flags & SLIRP_HOSTFWD_V6ONLY) + fwd_flags |= SS_HOSTFWD_V6ONLY; + + if (gaddr->sa_family == AF_INET) { + const struct sockaddr_in *gaddr_in = (const struct sockaddr_in *) gaddr; + + if (gaddrlen < sizeof(struct sockaddr_in)) { + errno = EINVAL; + return -1; + } + + if (!gaddr_in->sin_addr.s_addr) { + gdhcp_addr = *gaddr_in; + gdhcp_addr.sin_addr = slirp->vdhcp_startaddr; + gaddr = (struct sockaddr *) &gdhcp_addr; + gaddrlen = sizeof(gdhcp_addr); + } + } else { + if (gaddrlen < sizeof(struct sockaddr_in6)) { + errno = EINVAL; + return -1; + } + + /* + * Libslirp currently only provides a stateless DHCPv6 server, thus + * we can't translate "addr-any" to the guest here. Instead, we defer + * performing the translation to when it's needed. See + * soassign_guest_addr_if_needed(). + */ + } + + if (flags & SLIRP_HOSTFWD_UDP) { + if (!udpx_listen(slirp, haddr, haddrlen, + gaddr, gaddrlen, + fwd_flags)) + return -1; + } else { + if (!tcpx_listen(slirp, haddr, haddrlen, + gaddr, gaddrlen, + fwd_flags)) + return -1; + } + return 0; +} + +/* TODO: IPv6 */ +static bool check_guestfwd(Slirp *slirp, struct in_addr *guest_addr, + int guest_port) +{ + struct gfwd_list *tmp_ptr; + + if (!guest_addr->s_addr) { + guest_addr->s_addr = slirp->vnetwork_addr.s_addr | + (htonl(0x0204) & ~slirp->vnetwork_mask.s_addr); + } + if ((guest_addr->s_addr & slirp->vnetwork_mask.s_addr) != + slirp->vnetwork_addr.s_addr || + guest_addr->s_addr == slirp->vhost_addr.s_addr || + guest_addr->s_addr == slirp->vnameserver_addr.s_addr) { + return false; + } + + /* check if the port is "bound" */ + for (tmp_ptr = slirp->guestfwd_list; tmp_ptr; tmp_ptr = tmp_ptr->ex_next) { + if (guest_port == tmp_ptr->ex_fport && + guest_addr->s_addr == tmp_ptr->ex_addr.s_addr) + return false; + } + + return true; +} + +int slirp_add_exec(Slirp *slirp, const char *cmdline, + struct in_addr *guest_addr, int guest_port) +{ + if (!check_guestfwd(slirp, guest_addr, guest_port)) { + return -1; + } + + add_exec(&slirp->guestfwd_list, cmdline, *guest_addr, htons(guest_port)); + return 0; +} + +int slirp_add_unix(Slirp *slirp, const char *unixsock, + struct in_addr *guest_addr, int guest_port) +{ +#ifdef G_OS_UNIX + if (!check_guestfwd(slirp, guest_addr, guest_port)) { + return -1; + } + + add_unix(&slirp->guestfwd_list, unixsock, *guest_addr, htons(guest_port)); + return 0; +#else + g_warn_if_reached(); + return -1; +#endif +} + +int slirp_add_guestfwd(Slirp *slirp, SlirpWriteCb write_cb, void *opaque, + struct in_addr *guest_addr, int guest_port) +{ + if (!check_guestfwd(slirp, guest_addr, guest_port)) { + return -1; + } + + add_guestfwd(&slirp->guestfwd_list, write_cb, opaque, *guest_addr, + htons(guest_port)); + return 0; +} + +int slirp_remove_guestfwd(Slirp *slirp, struct in_addr guest_addr, + int guest_port) +{ + return remove_guestfwd(&slirp->guestfwd_list, guest_addr, + htons(guest_port)); +} + +slirp_ssize_t slirp_send(struct socket *so, const void *buf, size_t len, int flags) +{ + if (so->s == -1 && so->guestfwd) { + /* XXX this blocks entire thread. Rewrite to use + * qemu_chr_fe_write and background I/O callbacks */ + so->guestfwd->write_cb(buf, len, so->guestfwd->opaque); + return len; + } + + if (so->s == -1) { + /* + * This should in theory not happen but it is hard to be + * sure because some code paths will end up with so->s == -1 + * on a failure but don't dispose of the struct socket. + * Check specifically, so we don't pass -1 to send(). + */ + errno = EBADF; + return -1; + } + + return send(so->s, buf, len, flags); +} + +struct socket *slirp_find_ctl_socket(Slirp *slirp, struct in_addr guest_addr, + int guest_port) +{ + struct socket *so; + + /* TODO: IPv6 */ + for (so = slirp->tcb.so_next; so != &slirp->tcb; so = so->so_next) { + if (so->so_faddr.s_addr == guest_addr.s_addr && + htons(so->so_fport) == guest_port) { + return so; + } + } + return NULL; +} + +size_t slirp_socket_can_recv(Slirp *slirp, struct in_addr guest_addr, + int guest_port) +{ + struct iovec iov[2]; + struct socket *so; + + so = slirp_find_ctl_socket(slirp, guest_addr, guest_port); + + if (!so || so->so_state & SS_NOFDREF) { + return 0; + } + + if (!CONN_CANFRCV(so) || so->so_snd.sb_cc >= (so->so_snd.sb_datalen / 2)) { + /* If the sb is already half full, we will wait for the guest to consume it, + * and notify again in sbdrop() when the sb becomes less than half full. */ + return 0; + } + + return sopreprbuf(so, iov, NULL); +} + +void slirp_socket_recv(Slirp *slirp, struct in_addr guest_addr, int guest_port, + const uint8_t *buf, int size) +{ + int ret; + struct socket *so = slirp_find_ctl_socket(slirp, guest_addr, guest_port); + + if (!so) + return; + + ret = soreadbuf(so, (const char *)buf, size); + + if (ret > 0) + tcp_output(sototcpcb(so)); +} + +void slirp_send_packet_all(Slirp *slirp, const void *buf, size_t len) +{ + slirp_ssize_t ret; + + if (len < ETH_MINLEN) { + char tmp[ETH_MINLEN]; + memcpy(tmp, buf, len); + memset(tmp + len, 0, ETH_MINLEN - len); + + ret = slirp->cb->send_packet(tmp, ETH_MINLEN, slirp->opaque); + } else { + ret = slirp->cb->send_packet(buf, len, slirp->opaque); + } + + if (ret < 0) { + g_critical("Failed to send packet, ret: %ld", (long)ret); + } else if (ret < len) { + DEBUG_ERROR("send_packet() didn't send all data: %ld < %lu", (long)ret, + (unsigned long)len); + } +} diff --git a/src/net/libslirp/src/slirp.h b/src/net/libslirp/src/slirp.h new file mode 100644 index 00000000..2715353f --- /dev/null +++ b/src/net/libslirp/src/slirp.h @@ -0,0 +1,386 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#ifndef SLIRP_H +#define SLIRP_H + +#ifdef _WIN32 + +/* as defined in sdkddkver.h */ +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 /* Windows 7 */ +#endif +/* reduces the number of implicitly included headers */ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include +#include +#include +#include +#include + +#else +#define O_BINARY 0 +#endif + +#ifndef _WIN32 +#include +#include +#include +#include +#include +#endif + +#ifdef __APPLE__ +#include +#endif + +#include "debug.h" +#include "util.h" + +#include "libslirp.h" +#include "ip.h" +#include "ip6.h" +#include "tcp.h" +#include "tcp_timer.h" +#include "tcp_var.h" +#include "tcpip.h" +#include "udp.h" +#include "ip_icmp.h" +#include "ip6_icmp.h" +#include "mbuf.h" +#include "sbuf.h" +#include "socket.h" +#include "if.h" +#include "main.h" +#include "misc.h" + +#include "bootp.h" +#include "tftp.h" + +#define ARPOP_REQUEST 1 /* ARP request */ +#define ARPOP_REPLY 2 /* ARP reply */ + +struct ethhdr { + unsigned char h_dest[ETH_ALEN]; /* destination eth addr */ + unsigned char h_source[ETH_ALEN]; /* source ether addr */ + unsigned short h_proto; /* packet type ID field */ +}; + +SLIRP_PACKED_BEGIN +struct slirp_arphdr { + unsigned short ar_hrd; /* format of hardware address */ + unsigned short ar_pro; /* format of protocol address */ + unsigned char ar_hln; /* length of hardware address */ + unsigned char ar_pln; /* length of protocol address */ + unsigned short ar_op; /* ARP opcode (command) */ + + /* + * Ethernet looks like this : This bit is variable sized however... + */ + uint8_t ar_sha[ETH_ALEN]; /* sender hardware address */ + uint32_t ar_sip; /* sender IP address */ + uint8_t ar_tha[ETH_ALEN]; /* target hardware address */ + uint32_t ar_tip; /* target IP address */ +} SLIRP_PACKED_END; + +#define ARP_TABLE_SIZE 16 + +typedef struct ArpTable { + struct slirp_arphdr table[ARP_TABLE_SIZE]; + int next_victim; +} ArpTable; + +/* Add a new ARP entry for the given addresses */ +void arp_table_add(Slirp *slirp, uint32_t ip_addr, + const uint8_t ethaddr[ETH_ALEN]); + +/* Look for an ARP entry for the given IP address */ +bool arp_table_search(Slirp *slirp, uint32_t ip_addr, + uint8_t out_ethaddr[ETH_ALEN]); + +struct ndpentry { + uint8_t eth_addr[ETH_ALEN]; /* sender hardware address */ + struct in6_addr ip_addr; /* sender IP address */ +}; + +#define NDP_TABLE_SIZE 16 + +typedef struct NdpTable { + struct ndpentry table[NDP_TABLE_SIZE]; + /* + * The table is a cache with old entries overwritten when the table fills. + * Preserve the first entry: it is the guest, which is needed for lazy + * hostfwd guest address assignment. + */ + struct in6_addr guest_in6_addr; + int next_victim; +} NdpTable; + +/* Add a new NDP entry for the given addresses */ +void ndp_table_add(Slirp *slirp, struct in6_addr ip_addr, + uint8_t ethaddr[ETH_ALEN]); + +/* Look for an NDP entry for the given IPv6 address */ +bool ndp_table_search(Slirp *slirp, struct in6_addr ip_addr, + uint8_t out_ethaddr[ETH_ALEN]); + +/* Slirp configuration, specified by the application */ +struct Slirp { + int cfg_version; + + unsigned time_fasttimo; + unsigned last_slowtimo; + bool do_slowtimo; + + bool in_enabled, in6_enabled; + + /* virtual network configuration */ + struct in_addr vnetwork_addr; + struct in_addr vnetwork_mask; + struct in_addr vhost_addr; + struct in6_addr vprefix_addr6; + uint8_t vprefix_len; + struct in6_addr vhost_addr6; + bool disable_dhcp; /* slirp will not reply to any DHCP requests */ + struct in_addr vdhcp_startaddr; + struct in_addr vnameserver_addr; + struct in6_addr vnameserver_addr6; + + struct in_addr client_ipaddr; + char client_hostname[33]; + + int restricted; + struct gfwd_list *guestfwd_list; + + int if_mtu; + int if_mru; + + bool disable_host_loopback; + + uint32_t mfr_id; + uint8_t oob_eth_addr[ETH_ALEN]; + + /* mbuf states */ + struct slirp_quehead m_freelist; + struct slirp_quehead m_usedlist; + int mbuf_alloced; + + /* if states */ + struct slirp_quehead if_fastq; /* fast queue (for interactive data) */ + struct slirp_quehead if_batchq; /* queue for non-interactive data */ + bool if_start_busy; /* avoid if_start recursion */ + + /* ip states */ + struct ipq ipq; /* ip reass. queue */ + uint16_t ip_id; /* ip packet ctr, for ids */ + + /* bootp/dhcp states */ + BOOTPClient bootp_clients[NB_BOOTP_CLIENTS]; + char *bootp_filename; + size_t vdnssearch_len; + uint8_t *vdnssearch; + char *vdomainname; + + /* tcp states */ + struct socket tcb; + struct socket *tcp_last_so; + tcp_seq tcp_iss; /* tcp initial send seq # */ + uint32_t tcp_now; /* for RFC 1323 timestamps */ + + /* udp states */ + struct socket udb; + struct socket *udp_last_so; + + /* icmp states */ + struct socket icmp; + struct socket *icmp_last_so; + + /* tftp states */ + char *tftp_prefix; + struct tftp_session tftp_sessions[TFTP_SESSIONS_MAX]; + char *tftp_server_name; + + ArpTable arp_table; + NdpTable ndp_table; + + GRand *grand; + void *ra_timer; + + bool enable_emu; + + const SlirpCb *cb; + void *opaque; + + struct sockaddr_in *outbound_addr; + struct sockaddr_in6 *outbound_addr6; + bool disable_dns; /* slirp will not redirect/serve any DNS packet */ +}; + +/* + * Send one packet from each session. + * If there are packets on the fastq, they are sent FIFO, before + * everything else. Then we choose the first packet from each + * batchq session (socket) and send it. + * For example, if there are 3 ftp sessions fighting for bandwidth, + * one packet will be sent from the first session, then one packet + * from the second session, then one packet from the third. + */ +void if_start(Slirp *); + +/* Get the address of the DNS server on the host side */ +int get_dns_addr(struct in_addr *pdns_addr); + +/* Get the IPv6 address of the DNS server on the host side */ +int get_dns6_addr(struct in6_addr *pdns6_addr, uint32_t *scope_id); + +/* ncsi.c */ + +/* Process NCSI packet coming from the guest */ +void ncsi_input(Slirp *slirp, const uint8_t *pkt, int pkt_len); + +#ifndef _WIN32 +#include +#endif + +/* Whether we should send TCP keepalive packets */ +extern bool slirp_do_keepalive; + +#define TCP_MAXIDLE (TCPTV_KEEPCNT * TCPTV_KEEPINTVL) + +/* dnssearch.c */ +/* Translate from vdnssearch in configuration, into Slirp */ +int translate_dnssearch(Slirp *s, const char **names); + +/* cksum.c */ +/* Compute the checksum of the mbuf */ +int cksum(struct mbuf *m, int len); +/* Compute the checksum of the mbuf which contains an IPv6 packet */ +int ip6_cksum(struct mbuf *m); + +/* if.c */ +/* Called from slirp_new */ +void if_init(Slirp *); +/* Queue packet into an output queue (fast or batch), for sending to the guest */ +void if_output(struct socket *, struct mbuf *); + +/* ip_input.c */ +/* Called from slirp_new */ +void ip_init(Slirp *); +/* Called from slirp_cleanup */ +void ip_cleanup(Slirp *); +/* Process IPv4 packet coming from the guest */ +void ip_input(struct mbuf *); +/* + * IP timer processing; + * if a timer expires on a reassembly + * queue, discard it. + */ +void ip_slowtimo(Slirp *); +/* + * Strip out IP options, at higher + * level protocol in the kernel. + */ +void ip_stripoptions(register struct mbuf *); + +/* ip_output.c */ +/* Send IPv4 packet to the guest */ +int ip_output(struct socket *, struct mbuf *); + +/* ip6_input.c */ +/* Called from slirp_new, but after other initialization */ +void ip6_post_init(Slirp *); +/* Called from slirp_cleanup */ +void ip6_cleanup(Slirp *); +/* Process IPv6 packet coming from the guest */ +void ip6_input(struct mbuf *); + +/* ip6_output */ +/* Send IPv6 packet to the guest */ +int ip6_output(struct socket *, struct mbuf *, int fast); + +/* tcp_input.c */ +/* Process TCP datagram coming from the guest */ +void tcp_input(register struct mbuf *, int, struct socket *, unsigned short af); +/* Determine a reasonable value for maxseg size */ +int tcp_mss(register struct tcpcb *, unsigned offer); + +/* tcp_output.c */ +/* Send TCP datagram to the guest */ +int tcp_output(register struct tcpcb *); +/* Start/restart persistence timer */ +void tcp_setpersist(register struct tcpcb *); + +/* tcp_subr.c */ +/* Called from slirp_new */ +void tcp_init(Slirp *); +/* Called from slirp_cleanup */ +void tcp_cleanup(Slirp *); +/* + * Create template to be used to send tcp packets on a connection. + * Call after host entry created, fills + * in a skeletal tcp/ip header, minimizing the amount of work + * necessary when the connection is used. + */ +void tcp_template(struct tcpcb *); +/* + * Send a single message to the TCP at address specified by + * the given TCP/IP header. + */ +void tcp_respond(struct tcpcb *, register struct tcpiphdr *, + register struct mbuf *, tcp_seq, tcp_seq, int, unsigned short); +/* + * Create a new TCP control block, making an + * empty reassembly queue and hooking it to the argument + * protocol control block. + */ +struct tcpcb *tcp_newtcpcb(struct socket *); +/* + * Close a TCP control block: + * discard all space held by the tcp + * discard internet protocol block + * wake up any sleepers + */ +struct tcpcb *tcp_close(register struct tcpcb *); +/* The Internet socket got closed, tell the guest */ +void tcp_sockclosed(struct tcpcb *); +/* + * Connect to a host on the Internet + * Called by tcp_input + */ +int tcp_fconnect(struct socket *, unsigned short af); +/* Accept the connection from the Internet, and connect to the guest */ +void tcp_connect(struct socket *); +/* Attach a TCPCB to a socket */ +void tcp_attach(struct socket *); +/* * Return TOS according to the ports */ +uint8_t tcp_tos(struct socket *); +/* + * We received a packet from the guest. + * + * Emulate programs that try and connect to us + * This includes ftp (the data connection is + * initiated by the server) and IRC (DCC CHAT and + * DCC SEND) for now + */ +int tcp_emu(struct socket *, struct mbuf *); +/* Configure the socket, now that the guest completed accepting the connection */ +int tcp_ctl(struct socket *); +/* + * Drop a TCP connection, reporting + * the specified error. If connection is synchronized, + * then send a RST to peer. + */ +struct tcpcb *tcp_drop(struct tcpcb *tp, int err); + +/* Find the socket for the guest address and port */ +struct socket *slirp_find_ctl_socket(Slirp *slirp, struct in_addr guest_addr, + int guest_port); + +/* Send a frame to the virtual Ethernet board, i.e. call the application send_packet callback */ +void slirp_send_packet_all(Slirp *slirp, const void *buf, size_t len); + +/* Create a new timer, i.e. call the application timer_new callback */ +void *slirp_timer_new(Slirp *slirp, SlirpTimerId id, void *cb_opaque); + +#endif diff --git a/src/net/libslirp/src/socket.c b/src/net/libslirp/src/socket.c new file mode 100644 index 00000000..51a13647 --- /dev/null +++ b/src/net/libslirp/src/socket.c @@ -0,0 +1,1249 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" +#include "ip_icmp.h" +#ifdef __sun__ +#include +#endif +#ifdef __linux__ +#include +#endif + +static void sofcantrcvmore(struct socket *so); +static void sofcantsendmore(struct socket *so); + +struct socket *solookup(struct socket **last, struct socket *head, + struct sockaddr_storage *lhost, + struct sockaddr_storage *fhost) +{ + struct socket *so = *last; + + /* Optimisation */ + if (so != head && sockaddr_equal(&(so->lhost.ss), lhost) && + (!fhost || sockaddr_equal(&so->fhost.ss, fhost))) { + return so; + } + + for (so = head->so_next; so != head; so = so->so_next) { + if (sockaddr_equal(&(so->lhost.ss), lhost) && + (!fhost || sockaddr_equal(&so->fhost.ss, fhost))) { + *last = so; + return so; + } + } + + return (struct socket *)NULL; +} + +/* + * Create a new socket, initialise the fields + * It is the responsibility of the caller to + * slirp_insque() it into the correct linked-list + */ +struct socket *socreate(Slirp *slirp, int type) +{ + struct socket *so = g_new(struct socket, 1); + + memset(so, 0, sizeof(struct socket)); + so->so_type = type; + so->so_state = SS_NOFDREF; + so->s = -1; + so->s_aux = -1; + so->slirp = slirp; + so->pollfds_idx = -1; + + return so; +} + +/* + * Remove references to so from the given message queue. + */ +static void soqfree(struct socket *so, struct slirp_quehead *qh) +{ + struct mbuf *ifq; + + for (ifq = (struct mbuf *)qh->qh_link; (struct slirp_quehead *)ifq != qh; + ifq = ifq->m_next) { + if (ifq->m_so == so) { + struct mbuf *ifm; + ifq->m_so = NULL; + for (ifm = ifq->m_nextpkt; ifm != ifq; ifm = ifm->m_nextpkt) { + ifm->m_so = NULL; + } + } + } +} + +/* + * slirp_remque and free a socket, clobber cache + */ +void sofree(struct socket *so) +{ + Slirp *slirp = so->slirp; + + if (so->s_aux != -1) { + closesocket(so->s_aux); + } + + soqfree(so, &slirp->if_fastq); + soqfree(so, &slirp->if_batchq); + + if (so == slirp->tcp_last_so) { + slirp->tcp_last_so = &slirp->tcb; + } else if (so == slirp->udp_last_so) { + slirp->udp_last_so = &slirp->udb; + } else if (so == slirp->icmp_last_so) { + slirp->icmp_last_so = &slirp->icmp; + } + m_free(so->so_m); + + if (so->so_next && so->so_prev) + slirp_remque(so); /* crashes if so is not in a queue */ + + if (so->so_tcpcb) { + g_free(so->so_tcpcb); + } + g_free(so); +} + +size_t sopreprbuf(struct socket *so, struct iovec *iov, int *np) +{ + int n, lss, total; + struct sbuf *sb = &so->so_snd; + int len = sb->sb_datalen - sb->sb_cc; + int mss = so->so_tcpcb->t_maxseg; + + DEBUG_CALL("sopreprbuf"); + DEBUG_ARG("so = %p", so); + + if (len <= 0) + return 0; + + iov[0].iov_base = sb->sb_wptr; + iov[1].iov_base = NULL; + iov[1].iov_len = 0; + if (sb->sb_wptr < sb->sb_rptr) { + iov[0].iov_len = sb->sb_rptr - sb->sb_wptr; + /* Should never succeed, but... */ + if (iov[0].iov_len > len) + iov[0].iov_len = len; + if (iov[0].iov_len > mss) + iov[0].iov_len -= iov[0].iov_len % mss; + n = 1; + } else { + iov[0].iov_len = (sb->sb_data + sb->sb_datalen) - sb->sb_wptr; + /* Should never succeed, but... */ + if (iov[0].iov_len > len) + iov[0].iov_len = len; + len -= iov[0].iov_len; + if (len) { + iov[1].iov_base = sb->sb_data; + iov[1].iov_len = sb->sb_rptr - sb->sb_data; + if (iov[1].iov_len > len) + iov[1].iov_len = len; + total = iov[0].iov_len + iov[1].iov_len; + if (total > mss) { + lss = total % mss; + if (iov[1].iov_len > lss) { + iov[1].iov_len -= lss; + n = 2; + } else { + lss -= iov[1].iov_len; + iov[0].iov_len -= lss; + n = 1; + } + } else + n = 2; + } else { + if (iov[0].iov_len > mss) + iov[0].iov_len -= iov[0].iov_len % mss; + n = 1; + } + } + if (np) + *np = n; + + return iov[0].iov_len + (n - 1) * iov[1].iov_len; +} + +/* + * Read from so's socket into sb_snd, updating all relevant sbuf fields + * NOTE: This will only be called if it is select()ed for reading, so + * a read() of 0 (or less) means it's disconnected + */ +int soread(struct socket *so) +{ + int n, nn; + size_t buf_len; + struct sbuf *sb = &so->so_snd; + struct iovec iov[2]; + + DEBUG_CALL("soread"); + DEBUG_ARG("so = %p", so); + + /* + * No need to check if there's enough room to read. + * soread wouldn't have been called if there weren't + */ + buf_len = sopreprbuf(so, iov, &n); + assert(buf_len != 0); + + nn = recv(so->s, iov[0].iov_base, iov[0].iov_len, 0); + if (nn <= 0) { + if (nn < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + else { + int err; + socklen_t elen = sizeof err; + struct sockaddr_storage addr; + struct sockaddr *paddr = (struct sockaddr *)&addr; + socklen_t alen = sizeof addr; + + err = errno; + if (nn == 0) { + int shutdown_wr = so->so_state & SS_FCANTSENDMORE; + + if (!shutdown_wr && getpeername(so->s, paddr, &alen) < 0) { + err = errno; + } else { + getsockopt(so->s, SOL_SOCKET, SO_ERROR, &err, &elen); + } + } + + DEBUG_MISC(" --- soread() disconnected, nn = %d, errno = %d-%s", nn, + errno, strerror(errno)); + sofcantrcvmore(so); + + if (err == ECONNABORTED || err == ECONNRESET || err == ECONNREFUSED || + err == ENOTCONN || err == EPIPE) { + tcp_drop(sototcpcb(so), err); + } else { + tcp_sockclosed(sototcpcb(so)); + } + return -1; + } + } + + /* + * If there was no error, try and read the second time round + * We read again if n = 2 (ie, there's another part of the buffer) + * and we read as much as we could in the first read + * We don't test for <= 0 this time, because there legitimately + * might not be any more data (since the socket is non-blocking), + * a close will be detected on next iteration. + * A return of -1 won't (shouldn't) happen, since it didn't happen above + */ + if (n == 2 && nn == iov[0].iov_len) { + int ret; + ret = recv(so->s, iov[1].iov_base, iov[1].iov_len, 0); + if (ret > 0) + nn += ret; + } + + DEBUG_MISC(" ... read nn = %d bytes", nn); + + /* Update fields */ + sb->sb_cc += nn; + sb->sb_wptr += nn; + if (sb->sb_wptr >= (sb->sb_data + sb->sb_datalen)) + sb->sb_wptr -= sb->sb_datalen; + return nn; +} + +int soreadbuf(struct socket *so, const char *buf, int size) +{ + int n, nn, copy = size; + struct sbuf *sb = &so->so_snd; + struct iovec iov[2]; + + DEBUG_CALL("soreadbuf"); + DEBUG_ARG("so = %p", so); + + /* + * No need to check if there's enough room to read. + * soread wouldn't have been called if there weren't + */ + assert(size > 0); + if (sopreprbuf(so, iov, &n) < size) + goto err; + + nn = MIN(iov[0].iov_len, copy); + memcpy(iov[0].iov_base, buf, nn); + + copy -= nn; + buf += nn; + + if (copy == 0) + goto done; + + memcpy(iov[1].iov_base, buf, copy); + +done: + /* Update fields */ + sb->sb_cc += size; + sb->sb_wptr += size; + if (sb->sb_wptr >= (sb->sb_data + sb->sb_datalen)) + sb->sb_wptr -= sb->sb_datalen; + return size; +err: + + sofcantrcvmore(so); + tcp_sockclosed(sototcpcb(so)); + g_critical("soreadbuf buffer too small"); + return -1; +} + +/* + * Get urgent data + * + * When the socket is created, we set it SO_OOBINLINE, + * so when OOB data arrives, we soread() it and everything + * in the send buffer is sent as urgent data + */ +int sorecvoob(struct socket *so) +{ + struct tcpcb *tp = sototcpcb(so); + int ret; + + DEBUG_CALL("sorecvoob"); + DEBUG_ARG("so = %p", so); + + /* + * We take a guess at how much urgent data has arrived. + * In most situations, when urgent data arrives, the next + * read() should get all the urgent data. This guess will + * be wrong however if more data arrives just after the + * urgent data, or the read() doesn't return all the + * urgent data. + */ + ret = soread(so); + if (ret > 0) { + tp->snd_up = tp->snd_una + so->so_snd.sb_cc; + tp->t_force = 1; + tcp_output(tp); + tp->t_force = 0; + } + + return ret; +} + +/* + * Send urgent data + * There's a lot duplicated code here, but... + */ +int sosendoob(struct socket *so) +{ + struct sbuf *sb = &so->so_rcv; + char buff[2048]; /* XXX Shouldn't be sending more oob data than this */ + + int n; + + DEBUG_CALL("sosendoob"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("sb->sb_cc = %d", sb->sb_cc); + + if (so->so_urgc > sizeof(buff)) + so->so_urgc = sizeof(buff); /* XXXX */ + + if (sb->sb_rptr < sb->sb_wptr) { + /* We can send it directly */ + n = slirp_send(so, sb->sb_rptr, so->so_urgc, + (MSG_OOB)); /* |MSG_DONTWAIT)); */ + } else { + /* + * Since there's no sendv or sendtov like writev, + * we must copy all data to a linear buffer then + * send it all + */ + uint32_t urgc = so->so_urgc; /* Amount of room left in buff */ + int len = (sb->sb_data + sb->sb_datalen) - sb->sb_rptr; + if (len > urgc) { + len = urgc; + } + memcpy(buff, sb->sb_rptr, len); + urgc -= len; + if (urgc) { + /* We still have some room for the rest */ + n = sb->sb_wptr - sb->sb_data; + if (n > urgc) { + n = urgc; + } + memcpy((buff + len), sb->sb_data, n); + len += n; + } + n = slirp_send(so, buff, len, (MSG_OOB)); /* |MSG_DONTWAIT)); */ +#ifdef SLIRP_DEBUG + if (n != len) { + DEBUG_ERROR("Didn't send all data urgently XXXXX"); + } +#endif + } + + if (n < 0) { + return n; + } + so->so_urgc -= n; + DEBUG_MISC(" ---2 sent %d bytes urgent data, %d urgent bytes left", n, + so->so_urgc); + + sb->sb_cc -= n; + sb->sb_rptr += n; + if (sb->sb_rptr >= (sb->sb_data + sb->sb_datalen)) + sb->sb_rptr -= sb->sb_datalen; + + return n; +} + +/* + * Write data from so_rcv to so's socket, + * updating all sbuf field as necessary + */ +int sowrite(struct socket *so) +{ + int n, nn; + struct sbuf *sb = &so->so_rcv; + int len = sb->sb_cc; + struct iovec iov[2]; + + DEBUG_CALL("sowrite"); + DEBUG_ARG("so = %p", so); + + if (so->so_urgc) { + uint32_t expected = so->so_urgc; + if (sosendoob(so) < expected) { + /* Treat a short write as a fatal error too, + * rather than continuing on and sending the urgent + * data as if it were non-urgent and leaving the + * so_urgc count wrong. + */ + goto err_disconnected; + } + if (sb->sb_cc == 0) + return 0; + } + + /* + * No need to check if there's something to write, + * sowrite wouldn't have been called otherwise + */ + + iov[0].iov_base = sb->sb_rptr; + iov[1].iov_base = NULL; + iov[1].iov_len = 0; + if (sb->sb_rptr < sb->sb_wptr) { + iov[0].iov_len = sb->sb_wptr - sb->sb_rptr; + /* Should never succeed, but... */ + if (iov[0].iov_len > len) + iov[0].iov_len = len; + n = 1; + } else { + iov[0].iov_len = (sb->sb_data + sb->sb_datalen) - sb->sb_rptr; + if (iov[0].iov_len > len) + iov[0].iov_len = len; + len -= iov[0].iov_len; + if (len) { + iov[1].iov_base = sb->sb_data; + iov[1].iov_len = sb->sb_wptr - sb->sb_data; + if (iov[1].iov_len > len) + iov[1].iov_len = len; + n = 2; + } else + n = 1; + } + /* Check if there's urgent data to send, and if so, send it */ + + nn = slirp_send(so, iov[0].iov_base, iov[0].iov_len, 0); + /* This should never happen, but people tell me it does *shrug* */ + if (nn < 0 && (errno == EAGAIN || errno == EINTR)) + return 0; + + if (nn <= 0) { + goto err_disconnected; + } + + if (n == 2 && nn == iov[0].iov_len) { + int ret; + ret = slirp_send(so, iov[1].iov_base, iov[1].iov_len, 0); + if (ret > 0) + nn += ret; + } + DEBUG_MISC(" ... wrote nn = %d bytes", nn); + + /* Update sbuf */ + sb->sb_cc -= nn; + sb->sb_rptr += nn; + if (sb->sb_rptr >= (sb->sb_data + sb->sb_datalen)) + sb->sb_rptr -= sb->sb_datalen; + + /* + * If in DRAIN mode, and there's no more data, set + * it CANTSENDMORE + */ + if ((so->so_state & SS_FWDRAIN) && sb->sb_cc == 0) + sofcantsendmore(so); + + return nn; + +err_disconnected: + DEBUG_MISC(" --- sowrite disconnected, so->so_state = %x, errno = %d", + so->so_state, errno); + sofcantsendmore(so); + tcp_sockclosed(sototcpcb(so)); + return -1; +} + +/* + * recvfrom() a UDP socket + */ +void sorecvfrom(struct socket *so) +{ + struct sockaddr_storage addr; + struct sockaddr_storage saddr, daddr; + socklen_t addrlen = sizeof(struct sockaddr_storage); + char buff[256]; + +#ifdef __linux__ + ssize_t size; + struct msghdr msg; + struct iovec iov; + char control[1024]; + + /* First look for errors */ + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &saddr; + msg.msg_namelen = sizeof(saddr); + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + iov.iov_base = buff; + iov.iov_len = sizeof(buff); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + size = recvmsg(so->s, &msg, MSG_ERRQUEUE); + if (size >= 0) { + struct cmsghdr *cmsg; + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + + if (cmsg->cmsg_level == IPPROTO_IP && + cmsg->cmsg_type == IP_RECVERR) { + struct sock_extended_err *ee = + (struct sock_extended_err *) CMSG_DATA(cmsg); + + if (ee->ee_origin == SO_EE_ORIGIN_ICMP) { + /* Got an ICMP error, forward it */ + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *) SO_EE_OFFENDER(ee); + icmp_forward_error(so->so_m, ee->ee_type, ee->ee_code, + 0, NULL, &sin->sin_addr); + } + } + else if (cmsg->cmsg_level == IPPROTO_IPV6 && + cmsg->cmsg_type == IPV6_RECVERR) { + struct sock_extended_err *ee = + (struct sock_extended_err *) CMSG_DATA(cmsg); + + if (ee->ee_origin == SO_EE_ORIGIN_ICMP6) { + /* Got an ICMPv6 error, forward it */ + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *) SO_EE_OFFENDER(ee); + icmp6_forward_error(so->so_m, ee->ee_type, ee->ee_code, + &sin6->sin6_addr); + } + } + } + return; + } +#endif + + DEBUG_CALL("sorecvfrom"); + DEBUG_ARG("so = %p", so); + + if (so->so_type == IPPROTO_ICMP) { /* This is a "ping" reply */ + int len; + + len = recvfrom(so->s, buff, 256, 0, (struct sockaddr *)&addr, &addrlen); + /* XXX Check if reply is "correct"? */ + + if (len == -1 || len == 0) { + uint8_t code = ICMP_UNREACH_PORT; + + if (errno == EHOSTUNREACH) + code = ICMP_UNREACH_HOST; + else if (errno == ENETUNREACH) + code = ICMP_UNREACH_NET; + + DEBUG_MISC(" udp icmp rx errno = %d-%s", errno, strerror(errno)); + icmp_send_error(so->so_m, ICMP_UNREACH, code, 0, strerror(errno)); + } else { + icmp_reflect(so->so_m); + so->so_m = NULL; /* Don't m_free() it again! */ + } + /* No need for this socket anymore, udp_detach it */ + udp_detach(so); + } else if (so->so_type == IPPROTO_ICMPV6) { /* This is a "ping" reply */ + int len; + + len = recvfrom(so->s, buff, 256, 0, (struct sockaddr *)&addr, &addrlen); + /* XXX Check if reply is "correct"? */ + + if (len == -1 || len == 0) { + uint8_t code = ICMP6_UNREACH_PORT; + + if (errno == EHOSTUNREACH) + code = ICMP6_UNREACH_ADDRESS; + else if (errno == ENETUNREACH) + code = ICMP6_UNREACH_NO_ROUTE; + + DEBUG_MISC(" udp icmp6 rx errno = %d-%s", errno, strerror(errno)); + icmp6_send_error(so->so_m, ICMP_UNREACH, code); + } else { + icmp6_reflect(so->so_m); + so->so_m = NULL; /* Don't m_free() it again! */ + } + /* No need for this socket anymore, udp_detach it */ + udp_detach(so); + } else { /* A "normal" UDP packet */ + struct mbuf *m; + int len; +#ifdef _WIN32 + unsigned long n; +#else + int n; +#endif + + if (ioctlsocket(so->s, FIONREAD, &n) != 0) { + DEBUG_MISC(" ioctlsocket errno = %d-%s\n", errno, strerror(errno)); + return; + } + + m = m_get(so->slirp); + if (!m) { + return; + } + switch (so->so_ffamily) { + case AF_INET: + m->m_data += IF_MAXLINKHDR + sizeof(struct udpiphdr); + break; + case AF_INET6: + m->m_data += + IF_MAXLINKHDR + sizeof(struct ip6) + sizeof(struct udphdr); + break; + default: + g_assert_not_reached(); + } + + /* + * XXX Shouldn't FIONREAD packets destined for port 53, + * but I don't know the max packet size for DNS lookups + */ + len = M_FREEROOM(m); + /* if (so->so_fport != htons(53)) { */ + + if (n > len) { + n = (m->m_data - m->m_dat) + m->m_len + n + 1; + m_inc(m, n); + len = M_FREEROOM(m); + } + /* } */ + + m->m_len = recvfrom(so->s, m->m_data, len, 0, (struct sockaddr *)&addr, + &addrlen); + DEBUG_MISC(" did recvfrom %d, errno = %d-%s", m->m_len, errno, + strerror(errno)); + if (m->m_len < 0) { + if (errno == ENOTCONN) { + /* + * UDP socket got burnt, e.g. by suspend on iOS. Tear it down + * and let it get re-created if the guest still needs it + */ + udp_detach(so); + } else { + /* Report error as ICMP */ + switch (so->so_lfamily) { + uint8_t code; + case AF_INET: + code = ICMP_UNREACH_PORT; + + if (errno == EHOSTUNREACH) { + code = ICMP_UNREACH_HOST; + } else if (errno == ENETUNREACH) { + code = ICMP_UNREACH_NET; + } + + DEBUG_MISC(" rx error, tx icmp ICMP_UNREACH:%i", code); + icmp_send_error(so->so_m, ICMP_UNREACH, code, 0, + strerror(errno)); + break; + case AF_INET6: + code = ICMP6_UNREACH_PORT; + + if (errno == EHOSTUNREACH) { + code = ICMP6_UNREACH_ADDRESS; + } else if (errno == ENETUNREACH) { + code = ICMP6_UNREACH_NO_ROUTE; + } + + DEBUG_MISC(" rx error, tx icmp6 ICMP_UNREACH:%i", code); + icmp6_send_error(so->so_m, ICMP6_UNREACH, code); + break; + default: + g_assert_not_reached(); + } + m_free(m); + } + } else { + /* + * Hack: domain name lookup will be used the most for UDP, + * and since they'll only be used once there's no need + * for the 4 minute (or whatever) timeout... So we time them + * out much quicker (10 seconds for now...) + */ + if (so->so_expire) { + if (so->so_fport == htons(53)) + so->so_expire = curtime + SO_EXPIREFAST; + else + so->so_expire = curtime + SO_EXPIRE; + } + + /* + * If this packet was destined for CTL_ADDR, + * make it look like that's where it came from + */ + saddr = addr; + sotranslate_in(so, &saddr); + + /* Perform lazy guest IP address resolution if needed. */ + if (so->so_state & SS_HOSTFWD) { + if (soassign_guest_addr_if_needed(so) < 0) { + DEBUG_MISC(" guest address not available yet"); + switch (so->so_lfamily) { + case AF_INET: + icmp_send_error(so->so_m, ICMP_UNREACH, + ICMP_UNREACH_HOST, 0, + "guest address not available yet"); + break; + case AF_INET6: + icmp6_send_error(so->so_m, ICMP6_UNREACH, + ICMP6_UNREACH_ADDRESS); + break; + default: + g_assert_not_reached(); + } + m_free(m); + return; + } + } + daddr = so->lhost.ss; + + switch (so->so_ffamily) { + case AF_INET: + udp_output(so, m, (struct sockaddr_in *)&saddr, + (struct sockaddr_in *)&daddr, so->so_iptos); + break; + case AF_INET6: + udp6_output(so, m, (struct sockaddr_in6 *)&saddr, + (struct sockaddr_in6 *)&daddr); + break; + default: + g_assert_not_reached(); + } + } /* rx error */ + } /* if ping packet */ +} + +/* + * sendto() a socket + */ +int sosendto(struct socket *so, struct mbuf *m) +{ + int ret; + struct sockaddr_storage addr; + + DEBUG_CALL("sosendto"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("m = %p", m); + + addr = so->fhost.ss; + DEBUG_CALL(" sendto()ing)"); + if (sotranslate_out(so, &addr) < 0) { + return -1; + } + + /* Don't care what port we get */ + ret = sendto(so->s, m->m_data, m->m_len, 0, (struct sockaddr *)&addr, + sockaddr_size(&addr)); + if (ret < 0) + return -1; + + /* + * Kill the socket if there's no reply in 4 minutes, + * but only if it's an expirable socket + */ + if (so->so_expire) + so->so_expire = curtime + SO_EXPIRE; + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_ISFCONNECTED; /* So that it gets select()ed */ + return 0; +} + +struct socket *tcpx_listen(Slirp *slirp, + const struct sockaddr *haddr, socklen_t haddrlen, + const struct sockaddr *laddr, socklen_t laddrlen, + int flags) +{ + struct socket *so; + int s, opt = 1; + socklen_t addrlen; + + DEBUG_CALL("tcpx_listen"); + /* AF_INET6 addresses are bigger than AF_INET, so this is big enough. */ + char addrstr[INET6_ADDRSTRLEN]; + char portstr[6]; + int ret; + switch (haddr->sa_family) { + case AF_INET: + case AF_INET6: + ret = getnameinfo(haddr, haddrlen, addrstr, sizeof(addrstr), portstr, sizeof(portstr), NI_NUMERICHOST|NI_NUMERICSERV); + g_assert(ret == 0); + DEBUG_ARG("hfamily = INET"); + DEBUG_ARG("haddr = %s", addrstr); + DEBUG_ARG("hport = %s", portstr); + break; +#ifndef _WIN32 + case AF_UNIX: + DEBUG_ARG("hfamily = UNIX"); + DEBUG_ARG("hpath = %s", ((struct sockaddr_un *) haddr)->sun_path); + break; +#endif + default: + g_assert_not_reached(); + } + switch (laddr->sa_family) { + case AF_INET: + case AF_INET6: + ret = getnameinfo(laddr, laddrlen, addrstr, sizeof(addrstr), portstr, sizeof(portstr), NI_NUMERICHOST|NI_NUMERICSERV); + g_assert(ret == 0); + DEBUG_ARG("laddr = %s", addrstr); + DEBUG_ARG("lport = %s", portstr); + break; + default: + g_assert_not_reached(); + } + DEBUG_ARG("flags = %x", flags); + + /* + * SS_HOSTFWD sockets can be accepted multiple times, so they can't be + * SS_FACCEPTONCE. Also, SS_HOSTFWD connections can be accepted and + * immediately closed if the guest address isn't available yet, which is + * incompatible with the "accept once" concept. Correct code will never + * request both, so disallow their combination by assertion. + */ + g_assert(!((flags & SS_HOSTFWD) && (flags & SS_FACCEPTONCE))); + + so = socreate(slirp, IPPROTO_TCP); + + /* Don't tcp_attach... we don't need so_snd nor so_rcv */ + so->so_tcpcb = tcp_newtcpcb(so); + slirp_insque(so, &slirp->tcb); + + /* + * SS_FACCEPTONCE sockets must time out. + */ + if (flags & SS_FACCEPTONCE) + so->so_tcpcb->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT * 2; + + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= (SS_FACCEPTCONN | flags); + + sockaddr_copy(&so->lhost.sa, sizeof(so->lhost), laddr, laddrlen); + + s = slirp_socket(haddr->sa_family, SOCK_STREAM, 0); + if ((s < 0) || + (haddr->sa_family == AF_INET6 && slirp_socket_set_v6only(s, (flags & SS_HOSTFWD_V6ONLY) != 0) < 0) || + (slirp_socket_set_fast_reuse(s) < 0) || + (bind(s, haddr, haddrlen) < 0) || + (listen(s, 1) < 0)) { + int tmperrno = errno; /* Don't clobber the real reason we failed */ + if (s >= 0) { + closesocket(s); + } + sofree(so); + /* Restore the real errno */ +#ifdef _WIN32 + WSASetLastError(tmperrno); +#else + errno = tmperrno; +#endif + return NULL; + } + setsockopt(s, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(int)); + slirp_socket_set_nodelay(s); + + addrlen = sizeof(so->fhost); + getsockname(s, &so->fhost.sa, &addrlen); + sotranslate_accept(so); + + so->s = s; + return so; +} + +struct socket *tcp_listen(Slirp *slirp, uint32_t haddr, unsigned hport, + uint32_t laddr, unsigned lport, int flags) +{ + struct sockaddr_in hsa, lsa; + + memset(&hsa, 0, sizeof(hsa)); + hsa.sin_family = AF_INET; + hsa.sin_addr.s_addr = haddr; + hsa.sin_port = hport; + + memset(&lsa, 0, sizeof(lsa)); + lsa.sin_family = AF_INET; + lsa.sin_addr.s_addr = laddr; + lsa.sin_port = lport; + + return tcpx_listen(slirp, (const struct sockaddr *) &hsa, sizeof(hsa), (struct sockaddr *) &lsa, sizeof(lsa), flags); +} + +/* + * Various session state calls + * XXX Should be #define's + * The socket state stuff needs work, these often get call 2 or 3 + * times each when only 1 was needed + */ +void soisfconnecting(struct socket *so) +{ + so->so_state &= ~(SS_NOFDREF | SS_ISFCONNECTED | SS_FCANTRCVMORE | + SS_FCANTSENDMORE | SS_FWDRAIN); + so->so_state |= SS_ISFCONNECTING; /* Clobber other states */ +} + +void soisfconnected(struct socket *so) +{ + so->so_state &= ~(SS_ISFCONNECTING | SS_FWDRAIN | SS_NOFDREF); + so->so_state |= SS_ISFCONNECTED; /* Clobber other states */ +} + +static void sofcantrcvmore(struct socket *so) +{ + if ((so->so_state & SS_NOFDREF) == 0) { + shutdown(so->s, 0); + } + so->so_state &= ~(SS_ISFCONNECTING); + if (so->so_state & SS_FCANTSENDMORE) { + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_NOFDREF; /* Don't select it */ + } else { + so->so_state |= SS_FCANTRCVMORE; + } +} + +static void sofcantsendmore(struct socket *so) +{ + if ((so->so_state & SS_NOFDREF) == 0) { + shutdown(so->s, 1); /* send FIN to fhost */ + } + so->so_state &= ~(SS_ISFCONNECTING); + if (so->so_state & SS_FCANTRCVMORE) { + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_NOFDREF; /* as above */ + } else { + so->so_state |= SS_FCANTSENDMORE; + } +} + +void sofwdrain(struct socket *so) +{ + if (so->so_rcv.sb_cc) + so->so_state |= SS_FWDRAIN; + else + sofcantsendmore(so); +} + +static bool sotranslate_out4(Slirp *s, struct socket *so, struct sockaddr_in *sin) +{ + if (!s->disable_dns && so->so_faddr.s_addr == s->vnameserver_addr.s_addr) { + return so->so_fport == htons(53) && get_dns_addr(&sin->sin_addr) >= 0; + } + + if (so->so_faddr.s_addr == s->vhost_addr.s_addr || + so->so_faddr.s_addr == 0xffffffff) { + if (s->disable_host_loopback) { + return false; + } + + sin->sin_addr = loopback_addr; + } + + return true; +} + +static bool sotranslate_out6(Slirp *s, struct socket *so, struct sockaddr_in6 *sin) +{ + if (!s->disable_dns && in6_equal(&so->so_faddr6, &s->vnameserver_addr6)) { + uint32_t scope_id; + if (so->so_fport == htons(53) && get_dns6_addr(&sin->sin6_addr, &scope_id) >= 0) { + sin->sin6_scope_id = scope_id; + return true; + } + return false; + } + + if (in6_equal_net(&so->so_faddr6, &s->vprefix_addr6, s->vprefix_len) || + in6_equal(&so->so_faddr6, &(struct in6_addr)ALLNODES_MULTICAST)) { + if (s->disable_host_loopback) { + return false; + } + + sin->sin6_addr = in6addr_loopback; + } + + return true; +} + + +int sotranslate_out(struct socket *so, struct sockaddr_storage *addr) +{ + bool ok = true; + + switch (addr->ss_family) { + case AF_INET: + ok = sotranslate_out4(so->slirp, so, (struct sockaddr_in *)addr); + break; + case AF_INET6: + ok = sotranslate_out6(so->slirp, so, (struct sockaddr_in6 *)addr); + break; + } + + if (!ok) { + errno = EPERM; + return -1; + } + + return 0; +} + +void sotranslate_in(struct socket *so, struct sockaddr_storage *addr) +{ + Slirp *slirp = so->slirp; + struct sockaddr_in *sin = (struct sockaddr_in *)addr; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr; + + switch (addr->ss_family) { + case AF_INET: + if ((so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) == + slirp->vnetwork_addr.s_addr) { + uint32_t inv_mask = ~slirp->vnetwork_mask.s_addr; + + if ((so->so_faddr.s_addr & inv_mask) == inv_mask) { + sin->sin_addr = slirp->vhost_addr; + } else if (sin->sin_addr.s_addr == loopback_addr.s_addr || + so->so_faddr.s_addr != slirp->vhost_addr.s_addr) { + sin->sin_addr = so->so_faddr; + } + } + break; + + case AF_INET6: + if (in6_equal_net(&so->so_faddr6, &slirp->vprefix_addr6, + slirp->vprefix_len)) { + if (in6_equal(&sin6->sin6_addr, &in6addr_loopback) || + !in6_equal(&so->so_faddr6, &slirp->vhost_addr6)) { + sin6->sin6_addr = so->so_faddr6; + } + } + break; + + default: + break; + } +} + +void sotranslate_accept(struct socket *so) +{ + Slirp *slirp = so->slirp; + + switch (so->so_ffamily) { + case AF_INET: + if (so->so_faddr.s_addr == INADDR_ANY || + (so->so_faddr.s_addr & loopback_mask) == + (loopback_addr.s_addr & loopback_mask)) { + so->so_faddr = slirp->vhost_addr; + } + break; + + case AF_INET6: + if (in6_equal(&so->so_faddr6, &in6addr_any) || + in6_equal(&so->so_faddr6, &in6addr_loopback)) { + so->so_faddr6 = slirp->vhost_addr6; + } + break; + + case AF_UNIX: { + /* Translate Unix socket to random ephemeral source port. We obtain + * this source port by binding to port 0 so that the OS allocates a + * port for us. If this fails, we fall back to choosing a random port + * with a random number generator. */ + int s; + struct sockaddr_in in_addr; + struct sockaddr_in6 in6_addr; + socklen_t in_addr_len; + + if (so->slirp->in_enabled) { + so->so_ffamily = AF_INET; + so->so_faddr = slirp->vhost_addr; + so->so_fport = 0; + + switch (so->so_type) { + case IPPROTO_TCP: + s = slirp_socket(PF_INET, SOCK_STREAM, 0); + break; + case IPPROTO_UDP: + s = slirp_socket(PF_INET, SOCK_DGRAM, 0); + break; + default: + g_assert_not_reached(); + break; + } + if (s < 0) { + g_error("Ephemeral slirp_socket() allocation failed"); + goto unix2inet_cont; + } + memset(&in_addr, 0, sizeof(in_addr)); + in_addr.sin_family = AF_INET; + in_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + in_addr.sin_port = htons(0); + if (bind(s, (struct sockaddr *) &in_addr, sizeof(in_addr))) { + g_error("Ephemeral bind() failed"); + closesocket(s); + goto unix2inet_cont; + } + in_addr_len = sizeof(in_addr); + if (getsockname(s, (struct sockaddr *) &in_addr, &in_addr_len)) { + g_error("Ephemeral getsockname() failed"); + closesocket(s); + goto unix2inet_cont; + } + so->s_aux = s; + so->so_fport = in_addr.sin_port; + +unix2inet_cont: + if (!so->so_fport) { + g_warning("Falling back to random port allocation"); + so->so_fport = htons(g_rand_int_range(slirp->grand, 49152, 65536)); + } + } else if (so->slirp->in6_enabled) { + so->so_ffamily = AF_INET6; + so->so_faddr6 = slirp->vhost_addr6; + so->so_fport6 = 0; + + switch (so->so_type) { + case IPPROTO_TCP: + s = slirp_socket(PF_INET6, SOCK_STREAM, 0); + break; + case IPPROTO_UDP: + s = slirp_socket(PF_INET6, SOCK_DGRAM, 0); + break; + default: + g_assert_not_reached(); + break; + } + if (s < 0) { + g_error("Ephemeral slirp_socket() allocation failed"); + goto unix2inet6_cont; + } + memset(&in6_addr, 0, sizeof(in6_addr)); + in6_addr.sin6_family = AF_INET6; + in6_addr.sin6_addr = in6addr_loopback; + in6_addr.sin6_port = htons(0); + if (bind(s, (struct sockaddr *) &in6_addr, sizeof(in6_addr))) { + g_error("Ephemeral bind() failed"); + closesocket(s); + goto unix2inet6_cont; + } + in_addr_len = sizeof(in6_addr); + if (getsockname(s, (struct sockaddr *) &in6_addr, &in_addr_len)) { + g_error("Ephemeral getsockname() failed"); + closesocket(s); + goto unix2inet6_cont; + } + so->s_aux = s; + so->so_fport6 = in6_addr.sin6_port; + +unix2inet6_cont: + if (!so->so_fport6) { + g_warning("Falling back to random port allocation"); + so->so_fport6 = htons(g_rand_int_range(slirp->grand, 49152, 65536)); + } + } else { + g_assert_not_reached(); + } + break; + } /* case AF_UNIX */ + + default: + break; + } +} + +void sodrop(struct socket *s, int num) +{ + if (sbdrop(&s->so_snd, num)) { + s->slirp->cb->notify(s->slirp->opaque); + } +} + +/* + * Translate "addr-any" in so->lhost to the guest's actual address. + * Returns 0 for success, or -1 if the guest doesn't have an address yet + * with errno set to EHOSTUNREACH. + * + * The guest address is taken from the first entry in the ARP table for IPv4 + * and the first entry in the NDP table for IPv6. + * Note: The IPv4 path isn't exercised yet as all hostfwd "" guest translations + * are handled immediately by using slirp->vdhcp_startaddr. + */ +int soassign_guest_addr_if_needed(struct socket *so) +{ + Slirp *slirp = so->slirp; + /* AF_INET6 addresses are bigger than AF_INET, so this is big enough. */ + char addrstr[INET6_ADDRSTRLEN]; + char portstr[6]; + + g_assert(so->so_state & SS_HOSTFWD); + + switch (so->so_ffamily) { + case AF_INET: + if (so->so_laddr.s_addr == INADDR_ANY) { + g_assert_not_reached(); + } + break; + + case AF_INET6: + if (in6_zero(&so->so_laddr6)) { + int ret; + if (in6_zero(&slirp->ndp_table.guest_in6_addr)) { + errno = EHOSTUNREACH; + return -1; + } + so->so_laddr6 = slirp->ndp_table.guest_in6_addr; + ret = getnameinfo((const struct sockaddr *) &so->lhost.ss, + sizeof(so->lhost.ss), addrstr, sizeof(addrstr), + portstr, sizeof(portstr), + NI_NUMERICHOST|NI_NUMERICSERV); + g_assert(ret == 0); + DEBUG_MISC("%s: new ip = [%s]:%s", __func__, addrstr, portstr); + } + break; + + default: + break; + } + + return 0; +} diff --git a/src/net/libslirp/src/socket.h b/src/net/libslirp/src/socket.h new file mode 100644 index 00000000..27d3b8a6 --- /dev/null +++ b/src/net/libslirp/src/socket.h @@ -0,0 +1,236 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1995 Danny Gasparovski. + */ + +#ifndef SLIRP_SOCKET_H +#define SLIRP_SOCKET_H + +#include + +#ifndef _WIN32 +#include +#endif + +#include "misc.h" +#include "sbuf.h" + +#define SO_EXPIRE 240000 +#define SO_EXPIREFAST 10000 + +/* Helps unify some in/in6 routines. */ +union in4or6_addr { + struct in_addr addr4; + struct in6_addr addr6; +}; +typedef union in4or6_addr in4or6_addr; + +/* + * Our socket structure + */ + +union slirp_sockaddr { + struct sockaddr sa; + struct sockaddr_storage ss; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +}; + +struct socket { + struct socket *so_next, *so_prev; /* For a linked list of sockets */ + + int s; /* The actual socket */ + int s_aux; /* An auxiliary socket for miscellaneous use. Currently used to + * reserve OS ports in UNIX-to-inet translation. */ + struct gfwd_list *guestfwd; + + int pollfds_idx; /* GPollFD GArray index */ + + Slirp *slirp; /* managing slirp instance */ + + /* XXX union these with not-yet-used sbuf params */ + struct mbuf *so_m; /* Pointer to the original SYN packet, + * for non-blocking connect()'s, and + * PING reply's */ + struct tcpiphdr *so_ti; /* Pointer to the original ti within + * so_mconn, for non-blocking connections */ + uint32_t so_urgc; + union slirp_sockaddr fhost; /* Foreign host */ +#define so_faddr fhost.sin.sin_addr +#define so_fport fhost.sin.sin_port +#define so_faddr6 fhost.sin6.sin6_addr +#define so_fport6 fhost.sin6.sin6_port +#define so_ffamily fhost.ss.ss_family + + union slirp_sockaddr lhost; /* Local host */ +#define so_laddr lhost.sin.sin_addr +#define so_lport lhost.sin.sin_port +#define so_laddr6 lhost.sin6.sin6_addr +#define so_lport6 lhost.sin6.sin6_port +#define so_lfamily lhost.ss.ss_family + + uint8_t so_iptos; /* Type of service */ + uint8_t so_emu; /* Is the socket emulated? */ + + uint8_t so_type; /* Protocol of the socket. May be 0 if loading old + * states. */ + int32_t so_state; /* internal state flags SS_*, below */ + + struct tcpcb *so_tcpcb; /* pointer to TCP protocol control block */ + unsigned so_expire; /* When the socket will expire */ + + int so_queued; /* Number of packets queued from this socket */ + int so_nqueued; /* Number of packets queued in a row + * Used to determine when to "downgrade" a session + * from fastq to batchq */ + + struct sbuf so_rcv; /* Receive buffer */ + struct sbuf so_snd; /* Send buffer */ +}; + + +/* + * Socket state bits. (peer means the host on the Internet, + * local host means the host on the other end of the modem) + */ +#define SS_NOFDREF 0x001 /* No fd reference */ + +#define SS_ISFCONNECTING \ + 0x002 /* Socket is connecting to peer (non-blocking connect()'s) */ +#define SS_ISFCONNECTED 0x004 /* Socket is connected to peer */ +#define SS_FCANTRCVMORE \ + 0x008 /* Socket can't receive more from peer (for half-closes) */ +#define SS_FCANTSENDMORE \ + 0x010 /* Socket can't send more to peer (for half-closes) */ +#define SS_FWDRAIN \ + 0x040 /* We received a FIN, drain data and set SS_FCANTSENDMORE */ + +#define SS_CTL 0x080 +#define SS_FACCEPTCONN \ + 0x100 /* Socket is accepting connections from a host on the internet */ +#define SS_FACCEPTONCE \ + 0x200 /* If set, the SS_FACCEPTCONN socket will die after one accept */ + +#define SS_PERSISTENT_MASK 0xf000 /* Unremovable state bits */ +#define SS_HOSTFWD 0x1000 /* Socket describes host->guest forwarding */ +#define SS_INCOMING \ + 0x2000 /* Connection was initiated by a host on the internet */ +#define SS_HOSTFWD_V6ONLY 0x4000 /* Only bind on v6 addresses */ + +/* Check that two addresses are equal */ +static inline int sockaddr_equal(const struct sockaddr_storage *a, + const struct sockaddr_storage *b) +{ + if (a->ss_family != b->ss_family) { + return 0; + } + + switch (a->ss_family) { + case AF_INET: { + const struct sockaddr_in *a4 = (const struct sockaddr_in *)a; + const struct sockaddr_in *b4 = (const struct sockaddr_in *)b; + return a4->sin_addr.s_addr == b4->sin_addr.s_addr && + a4->sin_port == b4->sin_port; + } + case AF_INET6: { + const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)a; + const struct sockaddr_in6 *b6 = (const struct sockaddr_in6 *)b; + return (in6_equal(&a6->sin6_addr, &b6->sin6_addr) && + a6->sin6_port == b6->sin6_port); + } +#ifndef _WIN32 + case AF_UNIX: { + const struct sockaddr_un *aun = (const struct sockaddr_un *)a; + const struct sockaddr_un *bun = (const struct sockaddr_un *)b; + return strncmp(aun->sun_path, bun->sun_path, sizeof(aun->sun_path)) == 0; + } +#endif + default: + g_assert_not_reached(); + } + + return 0; +} + +/* Get the size of an address */ +static inline socklen_t sockaddr_size(const struct sockaddr_storage *a) +{ + switch (a->ss_family) { + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); +#ifndef _WIN32 + case AF_UNIX: + return sizeof(struct sockaddr_un); +#endif + default: + g_assert_not_reached(); + } +} + +/* Copy an address */ +static inline void sockaddr_copy(struct sockaddr *dst, socklen_t dstlen, const struct sockaddr *src, socklen_t srclen) +{ + socklen_t len = sockaddr_size((const struct sockaddr_storage *) src); + g_assert(len <= srclen); + g_assert(len <= dstlen); + memcpy(dst, src, len); +} + +/* Find the socket corresponding to lhost & fhost, trying last as a guess */ +struct socket *solookup(struct socket **last, struct socket *head, + struct sockaddr_storage *lhost, struct sockaddr_storage *fhost); +/* Create a new socket */ +struct socket *socreate(Slirp *, int); +/* Release a socket */ +void sofree(struct socket *); +/* Receive the available data from the Internet socket and queue it on the sb */ +int soread(struct socket *); +/* Receive the available OOB data from the Internet socket and try to send it immediately */ +int sorecvoob(struct socket *); +/* Send OOB data to the Internet socket */ +int sosendoob(struct socket *); +/* Send data to the Internet socket */ +int sowrite(struct socket *); +/* Receive the available data from the Internet UDP socket, and send it to the guest */ +void sorecvfrom(struct socket *); +/* Send data to the Internet UDP socket */ +int sosendto(struct socket *, struct mbuf *); +/* Listen for incoming TCPv4 connections on this haddr+hport */ +struct socket *tcp_listen(Slirp *, uint32_t haddr, unsigned hport, uint32_t laddr, unsigned lport, int flags); +/* + * Listen for incoming TCP connections on this haddr + * On failure errno contains the reason. + */ +struct socket *tcpx_listen(Slirp *slirp, + const struct sockaddr *haddr, socklen_t haddrlen, + const struct sockaddr *laddr, socklen_t laddrlen, + int flags); +/* Note that the socket is connecting */ +void soisfconnecting(register struct socket *); +/* Note that the socket is connected */ +void soisfconnected(register struct socket *); +/* + * Set write drain mode + * Set CANTSENDMORE once all data has been write()n + */ +void sofwdrain(struct socket *); +struct iovec; /* For win32 */ +/* Prepare iov for storing into the sb */ +size_t sopreprbuf(struct socket *so, struct iovec *iov, int *np); +/* Get data from the buffer and queue it on the sb */ +int soreadbuf(struct socket *so, const char *buf, int size); + +/* Translate addr into host addr when it is a virtual address, before sending to the Internet */ +int sotranslate_out(struct socket *, struct sockaddr_storage *); +/* Translate addr into virtual address when it is host, before sending to the guest */ +void sotranslate_in(struct socket *, struct sockaddr_storage *); +/* Translate connections from localhost to the real hostname */ +void sotranslate_accept(struct socket *); +/* Drop num bytes from the reading end of the socket */ +void sodrop(struct socket *, int num); +/* Forwarding a connection to the guest, try to find the guest address to use, fill lhost with it */ +int soassign_guest_addr_if_needed(struct socket *so); + +#endif /* SLIRP_SOCKET_H */ diff --git a/src/net/libslirp/src/state.c b/src/net/libslirp/src/state.c new file mode 100644 index 00000000..f10edf07 --- /dev/null +++ b/src/net/libslirp/src/state.c @@ -0,0 +1,381 @@ +/* SPDX-License-Identifier: MIT */ +/* + * libslirp + * + * Copyright (c) 2004-2008 Fabrice Bellard + * + * 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. + */ +#include "slirp.h" +#include "vmstate.h" +#include "stream.h" + +static int slirp_tcp_post_load(void *opaque, int version) +{ + tcp_template((struct tcpcb *)opaque); + + return 0; +} + +static const VMStateDescription vmstate_slirp_tcp = { + .name = "slirp-tcp", + .version_id = 0, + .post_load = slirp_tcp_post_load, + .fields = (VMStateField[]){ VMSTATE_INT16(t_state, struct tcpcb), + VMSTATE_INT16_ARRAY(t_timer, struct tcpcb, + TCPT_NTIMERS), + VMSTATE_INT16(t_rxtshift, struct tcpcb), + VMSTATE_INT16(t_rxtcur, struct tcpcb), + VMSTATE_INT16(t_dupacks, struct tcpcb), + VMSTATE_UINT16(t_maxseg, struct tcpcb), + VMSTATE_UINT8(t_force, struct tcpcb), + VMSTATE_UINT16(t_flags, struct tcpcb), + VMSTATE_UINT32(snd_una, struct tcpcb), + VMSTATE_UINT32(snd_nxt, struct tcpcb), + VMSTATE_UINT32(snd_up, struct tcpcb), + VMSTATE_UINT32(snd_wl1, struct tcpcb), + VMSTATE_UINT32(snd_wl2, struct tcpcb), + VMSTATE_UINT32(iss, struct tcpcb), + VMSTATE_UINT32(snd_wnd, struct tcpcb), + VMSTATE_UINT32(rcv_wnd, struct tcpcb), + VMSTATE_UINT32(rcv_nxt, struct tcpcb), + VMSTATE_UINT32(rcv_up, struct tcpcb), + VMSTATE_UINT32(irs, struct tcpcb), + VMSTATE_UINT32(rcv_adv, struct tcpcb), + VMSTATE_UINT32(snd_max, struct tcpcb), + VMSTATE_UINT32(snd_cwnd, struct tcpcb), + VMSTATE_UINT32(snd_ssthresh, struct tcpcb), + VMSTATE_INT16(t_idle, struct tcpcb), + VMSTATE_INT16(t_rtt, struct tcpcb), + VMSTATE_UINT32(t_rtseq, struct tcpcb), + VMSTATE_INT16(t_srtt, struct tcpcb), + VMSTATE_INT16(t_rttvar, struct tcpcb), + VMSTATE_UINT16(t_rttmin, struct tcpcb), + VMSTATE_UINT32(max_sndwnd, struct tcpcb), + VMSTATE_UINT8(t_oobflags, struct tcpcb), + VMSTATE_UINT8(t_iobc, struct tcpcb), + VMSTATE_INT16(t_softerror, struct tcpcb), + VMSTATE_UINT8(snd_scale, struct tcpcb), + VMSTATE_UINT8(rcv_scale, struct tcpcb), + VMSTATE_UINT8(request_r_scale, struct tcpcb), + VMSTATE_UINT8(requested_s_scale, struct tcpcb), + VMSTATE_UINT32(ts_recent, struct tcpcb), + VMSTATE_UINT32(ts_recent_age, struct tcpcb), + VMSTATE_UINT32(last_ack_sent, struct tcpcb), + VMSTATE_END_OF_LIST() } +}; + +/* The sbuf has a pair of pointers that are migrated as offsets; + * we calculate the offsets and restore the pointers using + * pre_save/post_load on a tmp structure. + */ +struct sbuf_tmp { + struct sbuf *parent; + uint32_t roff, woff; +}; + +static int sbuf_tmp_pre_save(void *opaque) +{ + struct sbuf_tmp *tmp = opaque; + tmp->woff = tmp->parent->sb_wptr - tmp->parent->sb_data; + tmp->roff = tmp->parent->sb_rptr - tmp->parent->sb_data; + + return 0; +} + +static int sbuf_tmp_post_load(void *opaque, int version) +{ + struct sbuf_tmp *tmp = opaque; + uint32_t requested_len = tmp->parent->sb_datalen; + + /* Allocate the buffer space used by the field after the tmp */ + sbreserve(tmp->parent, tmp->parent->sb_datalen); + + if (tmp->woff >= requested_len || tmp->roff >= requested_len) { + g_critical("invalid sbuf offsets r/w=%u/%u len=%u", tmp->roff, + tmp->woff, requested_len); + return -EINVAL; + } + + tmp->parent->sb_wptr = tmp->parent->sb_data + tmp->woff; + tmp->parent->sb_rptr = tmp->parent->sb_data + tmp->roff; + + return 0; +} + + +static const VMStateDescription vmstate_slirp_sbuf_tmp = { + .name = "slirp-sbuf-tmp", + .post_load = sbuf_tmp_post_load, + .pre_save = sbuf_tmp_pre_save, + .version_id = 0, + .fields = (VMStateField[]){ VMSTATE_UINT32(woff, struct sbuf_tmp), + VMSTATE_UINT32(roff, struct sbuf_tmp), + VMSTATE_END_OF_LIST() } +}; + +static const VMStateDescription vmstate_slirp_sbuf = { + .name = "slirp-sbuf", + .version_id = 0, + .fields = (VMStateField[]){ VMSTATE_UINT32(sb_cc, struct sbuf), + VMSTATE_UINT32(sb_datalen, struct sbuf), + VMSTATE_WITH_TMP(struct sbuf, struct sbuf_tmp, + vmstate_slirp_sbuf_tmp), + VMSTATE_VBUFFER_UINT32(sb_data, struct sbuf, 0, + NULL, sb_datalen), + VMSTATE_END_OF_LIST() } +}; + +static bool slirp_older_than_v4(void *opaque, int version_id) +{ + return version_id < 4; +} + +static bool slirp_family_inet(void *opaque, int version_id) +{ + union slirp_sockaddr *ssa = (union slirp_sockaddr *)opaque; + return ssa->ss.ss_family == AF_INET; +} + +static int slirp_socket_pre_load(void *opaque) +{ + struct socket *so = opaque; + + tcp_attach(so); + /* Older versions don't load these fields */ + so->so_ffamily = AF_INET; + so->so_lfamily = AF_INET; + return 0; +} + +#ifndef _WIN32 +#define VMSTATE_SIN4_ADDR(f, s, t) VMSTATE_UINT32_TEST(f, s, t) +#else +/* Win uses u_long rather than uint32_t - but it's still 32bits long */ +#define VMSTATE_SIN4_ADDR(f, s, t) \ + VMSTATE_SINGLE_TEST(f, s, t, 0, slirp_vmstate_info_uint32, u_long) +#endif + +/* The OS provided ss_family field isn't that portable; it's size + * and type varies (16/8 bit, signed, unsigned) + * and the values it contains aren't fully portable. + */ +typedef struct SS_FamilyTmpStruct { + union slirp_sockaddr *parent; + uint16_t portable_family; +} SS_FamilyTmpStruct; + +#define SS_FAMILY_MIG_IPV4 2 /* Linux, BSD, Win... */ +#define SS_FAMILY_MIG_IPV6 10 /* Linux */ +#define SS_FAMILY_MIG_OTHER 0xffff + +static int ss_family_pre_save(void *opaque) +{ + SS_FamilyTmpStruct *tss = opaque; + + tss->portable_family = SS_FAMILY_MIG_OTHER; + + if (tss->parent->ss.ss_family == AF_INET) { + tss->portable_family = SS_FAMILY_MIG_IPV4; + } else if (tss->parent->ss.ss_family == AF_INET6) { + tss->portable_family = SS_FAMILY_MIG_IPV6; + } + + return 0; +} + +static int ss_family_post_load(void *opaque, int version_id) +{ + SS_FamilyTmpStruct *tss = opaque; + + switch (tss->portable_family) { + case SS_FAMILY_MIG_IPV4: + tss->parent->ss.ss_family = AF_INET; + break; + case SS_FAMILY_MIG_IPV6: + case 23: /* compatibility: AF_INET6 from mingw */ + case 28: /* compatibility: AF_INET6 from FreeBSD sys/socket.h */ + tss->parent->ss.ss_family = AF_INET6; + break; + default: + g_critical("invalid ss_family type %x", tss->portable_family); + return -EINVAL; + } + + return 0; +} + +static const VMStateDescription vmstate_slirp_ss_family = { + .name = "slirp-socket-addr/ss_family", + .pre_save = ss_family_pre_save, + .post_load = ss_family_post_load, + .fields = + (VMStateField[]){ VMSTATE_UINT16(portable_family, SS_FamilyTmpStruct), + VMSTATE_END_OF_LIST() } +}; + +static const VMStateDescription vmstate_slirp_socket_addr = { + .name = "slirp-socket-addr", + .version_id = 4, + .fields = + (VMStateField[]){ + VMSTATE_WITH_TMP(union slirp_sockaddr, SS_FamilyTmpStruct, + vmstate_slirp_ss_family), + VMSTATE_SIN4_ADDR(sin.sin_addr.s_addr, union slirp_sockaddr, + slirp_family_inet), + VMSTATE_UINT16_TEST(sin.sin_port, union slirp_sockaddr, + slirp_family_inet), + +#if 0 + /* Untested: Needs checking by someone with IPv6 test */ + VMSTATE_BUFFER_TEST(sin6.sin6_addr, union slirp_sockaddr, + slirp_family_inet6), + VMSTATE_UINT16_TEST(sin6.sin6_port, union slirp_sockaddr, + slirp_family_inet6), + VMSTATE_UINT32_TEST(sin6.sin6_flowinfo, union slirp_sockaddr, + slirp_family_inet6), + VMSTATE_UINT32_TEST(sin6.sin6_scope_id, union slirp_sockaddr, + slirp_family_inet6), +#endif + + VMSTATE_END_OF_LIST() } +}; + +static const VMStateDescription vmstate_slirp_socket = { + .name = "slirp-socket", + .version_id = 4, + .pre_load = slirp_socket_pre_load, + .fields = + (VMStateField[]){ + VMSTATE_UINT32(so_urgc, struct socket), + /* Pre-v4 versions */ + VMSTATE_SIN4_ADDR(so_faddr.s_addr, struct socket, + slirp_older_than_v4), + VMSTATE_SIN4_ADDR(so_laddr.s_addr, struct socket, + slirp_older_than_v4), + VMSTATE_UINT16_TEST(so_fport, struct socket, slirp_older_than_v4), + VMSTATE_UINT16_TEST(so_lport, struct socket, slirp_older_than_v4), + /* v4 and newer */ + VMSTATE_STRUCT(fhost, struct socket, 4, vmstate_slirp_socket_addr, + union slirp_sockaddr), + VMSTATE_STRUCT(lhost, struct socket, 4, vmstate_slirp_socket_addr, + union slirp_sockaddr), + + VMSTATE_UINT8(so_iptos, struct socket), + VMSTATE_UINT8(so_emu, struct socket), + VMSTATE_UINT8(so_type, struct socket), + VMSTATE_INT32(so_state, struct socket), + VMSTATE_STRUCT(so_rcv, struct socket, 0, vmstate_slirp_sbuf, + struct sbuf), + VMSTATE_STRUCT(so_snd, struct socket, 0, vmstate_slirp_sbuf, + struct sbuf), + VMSTATE_STRUCT_POINTER(so_tcpcb, struct socket, vmstate_slirp_tcp, + struct tcpcb), + VMSTATE_END_OF_LIST() } +}; + +static const VMStateDescription vmstate_slirp_bootp_client = { + .name = "slirp_bootpclient", + .fields = (VMStateField[]){ VMSTATE_UINT16(allocated, BOOTPClient), + VMSTATE_BUFFER(macaddr, BOOTPClient), + VMSTATE_END_OF_LIST() } +}; + +static const VMStateDescription vmstate_slirp = { + .name = "slirp", + .version_id = 4, + .fields = (VMStateField[]){ VMSTATE_UINT16_V(ip_id, Slirp, 2), + VMSTATE_STRUCT_ARRAY( + bootp_clients, Slirp, NB_BOOTP_CLIENTS, 3, + vmstate_slirp_bootp_client, BOOTPClient), + VMSTATE_END_OF_LIST() } +}; + +int slirp_state_save(Slirp *slirp, SlirpWriteCb write_cb, void *opaque) +{ + struct gfwd_list *ex_ptr; + SlirpOStream f = { + .write_cb = write_cb, + .opaque = opaque, + }; + + for (ex_ptr = slirp->guestfwd_list; ex_ptr; ex_ptr = ex_ptr->ex_next) + if (ex_ptr->write_cb) { + struct socket *so; + so = slirp_find_ctl_socket(slirp, ex_ptr->ex_addr, + ntohs(ex_ptr->ex_fport)); + if (!so) { + continue; + } + + slirp_ostream_write_u8(&f, 42); + slirp_vmstate_save_state(&f, &vmstate_slirp_socket, so); + } + slirp_ostream_write_u8(&f, 0); + + slirp_vmstate_save_state(&f, &vmstate_slirp, slirp); + + return 0; +} + + +int slirp_state_load(Slirp *slirp, int version_id, SlirpReadCb read_cb, + void *opaque) +{ + struct gfwd_list *ex_ptr; + SlirpIStream f = { + .read_cb = read_cb, + .opaque = opaque, + }; + + while (slirp_istream_read_u8(&f)) { + int ret; + struct socket *so = socreate(slirp, -1); + + ret = + slirp_vmstate_load_state(&f, &vmstate_slirp_socket, so, version_id); + if (ret < 0) { + return ret; + } + + if ((so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) != + slirp->vnetwork_addr.s_addr) { + return -EINVAL; + } + for (ex_ptr = slirp->guestfwd_list; ex_ptr; ex_ptr = ex_ptr->ex_next) { + if (ex_ptr->write_cb && + so->so_faddr.s_addr == ex_ptr->ex_addr.s_addr && + so->so_fport == ex_ptr->ex_fport) { + break; + } + } + if (!ex_ptr) { + return -EINVAL; + } + + so->guestfwd = ex_ptr; + } + + return slirp_vmstate_load_state(&f, &vmstate_slirp, slirp, version_id); +} + +int slirp_state_version(void) +{ + return 4; +} diff --git a/src/net/libslirp/src/stream.c b/src/net/libslirp/src/stream.c new file mode 100644 index 00000000..6cf326f6 --- /dev/null +++ b/src/net/libslirp/src/stream.c @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: MIT */ +/* + * libslirp io streams + * + * Copyright (c) 2018 Red Hat, Inc. + * + * 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. + */ +#include "stream.h" +#include + +bool slirp_istream_read(SlirpIStream *f, void *buf, size_t size) +{ + return f->read_cb(buf, size, f->opaque) == size; +} + +bool slirp_ostream_write(SlirpOStream *f, const void *buf, size_t size) +{ + return f->write_cb(buf, size, f->opaque) == size; +} + +uint8_t slirp_istream_read_u8(SlirpIStream *f) +{ + uint8_t b; + + if (slirp_istream_read(f, &b, sizeof(b))) { + return b; + } + + return 0; +} + +bool slirp_ostream_write_u8(SlirpOStream *f, uint8_t b) +{ + return slirp_ostream_write(f, &b, sizeof(b)); +} + +uint16_t slirp_istream_read_u16(SlirpIStream *f) +{ + uint16_t b; + + if (slirp_istream_read(f, &b, sizeof(b))) { + return GUINT16_FROM_BE(b); + } + + return 0; +} + +bool slirp_ostream_write_u16(SlirpOStream *f, uint16_t b) +{ + b = GUINT16_TO_BE(b); + return slirp_ostream_write(f, &b, sizeof(b)); +} + +uint32_t slirp_istream_read_u32(SlirpIStream *f) +{ + uint32_t b; + + if (slirp_istream_read(f, &b, sizeof(b))) { + return GUINT32_FROM_BE(b); + } + + return 0; +} + +bool slirp_ostream_write_u32(SlirpOStream *f, uint32_t b) +{ + b = GUINT32_TO_BE(b); + return slirp_ostream_write(f, &b, sizeof(b)); +} + +int16_t slirp_istream_read_i16(SlirpIStream *f) +{ + int16_t b; + + if (slirp_istream_read(f, &b, sizeof(b))) { + return GINT16_FROM_BE(b); + } + + return 0; +} + +bool slirp_ostream_write_i16(SlirpOStream *f, int16_t b) +{ + b = GINT16_TO_BE(b); + return slirp_ostream_write(f, &b, sizeof(b)); +} + +int32_t slirp_istream_read_i32(SlirpIStream *f) +{ + int32_t b; + + if (slirp_istream_read(f, &b, sizeof(b))) { + return GINT32_FROM_BE(b); + } + + return 0; +} + +bool slirp_ostream_write_i32(SlirpOStream *f, int32_t b) +{ + b = GINT32_TO_BE(b); + return slirp_ostream_write(f, &b, sizeof(b)); +} diff --git a/src/net/libslirp/src/stream.h b/src/net/libslirp/src/stream.h new file mode 100644 index 00000000..4cdc2369 --- /dev/null +++ b/src/net/libslirp/src/stream.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#ifndef STREAM_H_ +#define STREAM_H_ + +#include "libslirp.h" + +typedef struct SlirpIStream { + SlirpReadCb read_cb; + void *opaque; +} SlirpIStream; + +typedef struct SlirpOStream { + SlirpWriteCb write_cb; + void *opaque; +} SlirpOStream; + +/* Read exactly size bytes from stream, return 1 if all ok, 0 otherwise */ +bool slirp_istream_read(SlirpIStream *f, void *buf, size_t size); +/* Write exactly size bytes to stream, return 1 if all ok, 0 otherwise */ +bool slirp_ostream_write(SlirpOStream *f, const void *buf, size_t size); + +/* Read exactly one byte from stream, return it, otherwise return 0 */ +uint8_t slirp_istream_read_u8(SlirpIStream *f); +/* Write exactly one byte to stream, return 1 if all ok, 0 otherwise */ +bool slirp_ostream_write_u8(SlirpOStream *f, uint8_t b); + +/* Read exactly two bytes from big-endian stream, return it, otherwise return 0 */ +uint16_t slirp_istream_read_u16(SlirpIStream *f); +/* Write exactly two bytes to big-endian stream, return 1 if all ok, 0 otherwise */ +bool slirp_ostream_write_u16(SlirpOStream *f, uint16_t b); + +/* Read exactly four bytes from big-endian stream, return it, otherwise return 0 */ +uint32_t slirp_istream_read_u32(SlirpIStream *f); +/* Write exactly four bytes to big-endian stream, return 1 if all ok, 0 otherwise */ +bool slirp_ostream_write_u32(SlirpOStream *f, uint32_t b); + +/* Read exactly two bytes from big-endian stream (signed), return it, otherwise return 0 */ +int16_t slirp_istream_read_i16(SlirpIStream *f); +/* Write exactly two bytes to big-endian stream (signed), return 1 if all ok, 0 otherwise */ +bool slirp_ostream_write_i16(SlirpOStream *f, int16_t b); + +/* Read exactly four bytes from big-endian stream (signed), return it, otherwise return 0 */ +int32_t slirp_istream_read_i32(SlirpIStream *f); +/* Write exactly four bytes to big-endian stream (signed), return 1 if all ok, 0 otherwise */ +bool slirp_ostream_write_i32(SlirpOStream *f, int32_t b); + +#endif /* STREAM_H_ */ diff --git a/src/net/libslirp/src/tcp.h b/src/net/libslirp/src/tcp.h new file mode 100644 index 00000000..f678eaea --- /dev/null +++ b/src/net/libslirp/src/tcp.h @@ -0,0 +1,169 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp.h 8.1 (Berkeley) 6/10/93 + * tcp.h,v 1.3 1994/08/21 05:27:34 paul Exp + */ + +#ifndef TCP_H +#define TCP_H + +#include + +typedef uint32_t tcp_seq; + +#define PR_SLOWHZ 2 /* 2 slow timeouts per second (approx) */ +#define PR_FASTHZ 5 /* 5 fast timeouts per second (not important) */ + +#define TCP_SNDSPACE 1024 * 128 +#define TCP_RCVSPACE 1024 * 128 +#define TCP_MAXSEG_MAX 32768 + +/* + * TCP header. + * Per RFC 793, September, 1981. + */ +#define tcphdr slirp_tcphdr +struct tcphdr { + uint16_t th_sport; /* source port */ + uint16_t th_dport; /* destination port */ + tcp_seq th_seq; /* sequence number */ + tcp_seq th_ack; /* acknowledgement number */ +#if (G_BYTE_ORDER == G_BIG_ENDIAN) && !defined(_MSC_VER) + uint8_t th_off : 4, /* data offset */ + th_x2 : 4; /* (unused) */ +#else + uint8_t th_x2 : 4, /* (unused) */ + th_off : 4; /* data offset */ +#endif + uint8_t th_flags; + uint16_t th_win; /* window */ + uint16_t th_sum; /* checksum */ + uint16_t th_urp; /* urgent pointer */ +}; + +#include "tcp_var.h" + +#ifndef TH_FIN +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PUSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 +#endif + +#ifndef TCPOPT_EOL +#define TCPOPT_EOL 0 +#define TCPOPT_NOP 1 +#define TCPOPT_MAXSEG 2 +#define TCPOPT_WINDOW 3 +#define TCPOPT_SACK_PERMITTED 4 /* Experimental */ +#define TCPOPT_SACK 5 /* Experimental */ +#define TCPOPT_TIMESTAMP 8 + +#define TCPOPT_TSTAMP_HDR \ + (TCPOPT_NOP << 24 | TCPOPT_NOP << 16 | TCPOPT_TIMESTAMP << 8 | \ + TCPOLEN_TIMESTAMP) +#endif + +#ifndef TCPOLEN_MAXSEG +#define TCPOLEN_MAXSEG 4 +#define TCPOLEN_WINDOW 3 +#define TCPOLEN_SACK_PERMITTED 2 +#define TCPOLEN_TIMESTAMP 10 +#define TCPOLEN_TSTAMP_APPA (TCPOLEN_TIMESTAMP + 2) /* appendix A */ +#endif + +#undef TCP_MAXWIN +#define TCP_MAXWIN 65535 /* largest value for (unscaled) window */ + +#undef TCP_MAX_WINSHIFT +#define TCP_MAX_WINSHIFT 14 /* maximum window shift */ + +/* + * User-settable options (used with setsockopt). + * + * We don't use the system headers on unix because we have conflicting + * local structures. We can't avoid the system definitions on Windows, + * so we undefine them. + */ +#undef TCP_NODELAY +#define TCP_NODELAY 0x01 /* don't delay send to coalesce packets */ +#undef TCP_MAXSEG + +/* + * TCP FSM state definitions. + * Per RFC793, September, 1981. + */ + +#define TCP_NSTATES 11 + +#define TCPS_CLOSED 0 /* closed */ +#define TCPS_LISTEN 1 /* listening for connection */ +#define TCPS_SYN_SENT 2 /* active, have sent syn */ +#define TCPS_SYN_RECEIVED 3 /* have send and received syn */ +/* states < TCPS_ESTABLISHED are those where connections not established */ +#define TCPS_ESTABLISHED 4 /* established */ +#define TCPS_CLOSE_WAIT 5 /* rcvd fin, waiting for close */ +/* states > TCPS_CLOSE_WAIT are those where user has closed */ +#define TCPS_FIN_WAIT_1 6 /* have closed, sent fin */ +#define TCPS_CLOSING 7 /* closed xchd FIN; await FIN ACK */ +#define TCPS_LAST_ACK 8 /* had fin and close; await FIN ACK */ +/* states > TCPS_CLOSE_WAIT && < TCPS_FIN_WAIT_2 await ACK of FIN */ +#define TCPS_FIN_WAIT_2 9 /* have closed, fin is acked */ +#define TCPS_TIME_WAIT 10 /* in 2*msl quiet wait after close */ + +#define TCPS_HAVERCVDSYN(s) ((s) >= TCPS_SYN_RECEIVED) +#define TCPS_HAVEESTABLISHED(s) ((s) >= TCPS_ESTABLISHED) +#define TCPS_HAVERCVDFIN(s) ((s) >= TCPS_TIME_WAIT) + +/* + * TCP sequence numbers are 32 bit integers operated + * on with modular arithmetic. These macros can be + * used to compare such integers. + */ +#define SEQ_LT(a, b) ((int)((a) - (b)) < 0) +#define SEQ_LEQ(a, b) ((int)((a) - (b)) <= 0) +#define SEQ_GT(a, b) ((int)((a) - (b)) > 0) +#define SEQ_GEQ(a, b) ((int)((a) - (b)) >= 0) + +/* + * Macros to initialize tcp sequence numbers for + * send and receive from initial send and receive + * sequence numbers. + */ +#define tcp_rcvseqinit(tp) (tp)->rcv_adv = (tp)->rcv_nxt = (tp)->irs + 1 + +#define tcp_sendseqinit(tp) \ + (tp)->snd_una = (tp)->snd_nxt = (tp)->snd_max = (tp)->snd_up = (tp)->iss + +#define TCP_ISSINCR (125 * 1024) /* increment for tcp_iss each second */ + +#endif diff --git a/src/net/libslirp/src/tcp_input.c b/src/net/libslirp/src/tcp_input.c new file mode 100644 index 00000000..728c26dd --- /dev/null +++ b/src/net/libslirp/src/tcp_input.c @@ -0,0 +1,1566 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_input.c 8.5 (Berkeley) 4/10/94 + * tcp_input.c,v 1.10 1994/10/13 18:36:32 wollman Exp + */ + +/* + * Changes and additions relating to SLiRP + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" +#include "ip_icmp.h" + +#define TCPREXMTTHRESH 3 + +#define TCP_PAWS_IDLE (24 * 24 * 60 * 60 * PR_SLOWHZ) + +/* for modulo comparisons of timestamps */ +#define TSTMP_LT(a, b) ((int)((a) - (b)) < 0) +#define TSTMP_GEQ(a, b) ((int)((a) - (b)) >= 0) + +/* + * Insert segment ti into reassembly queue of tcp with + * control block tp. Return TH_FIN if reassembly now includes + * a segment with FIN. + * Set DELACK for segments received in order, but ack immediately + * when segments are out of order (so fast retransmit can work). + */ + +static void tcp_dooptions(struct tcpcb *tp, uint8_t *cp, int cnt, + struct tcpiphdr *ti); +static void tcp_xmit_timer(register struct tcpcb *tp, int rtt); + +static int tcp_reass(register struct tcpcb *tp, register struct tcpiphdr *ti, + struct mbuf *m) +{ + register struct tcpiphdr *q; + struct socket *so = tp->t_socket; + int flags; + + /* + * Call with ti==NULL after become established to + * force pre-ESTABLISHED data up to user socket. + */ + if (ti == NULL) + goto present; + + /* + * Find a segment which begins after this one does. + */ + for (q = tcpfrag_list_first(tp); !tcpfrag_list_end(q, tp); + q = tcpiphdr_next(q)) + if (SEQ_GT(q->ti_seq, ti->ti_seq)) + break; + + /* + * If there is a preceding segment, it may provide some of + * our data already. If so, drop the data from the incoming + * segment. If it provides all of our data, drop us. + */ + if (!tcpfrag_list_end(tcpiphdr_prev(q), tp)) { + register int i; + q = tcpiphdr_prev(q); + /* conversion to int (in i) handles seq wraparound */ + i = q->ti_seq + q->ti_len - ti->ti_seq; + if (i > 0) { + if (i >= ti->ti_len) { + m_free(m); + /* + * Try to present any queued data + * at the left window edge to the user. + * This is needed after the 3-WHS + * completes. + */ + goto present; /* ??? */ + } + m_adj(m, i); + ti->ti_len -= i; + ti->ti_seq += i; + } + q = tcpiphdr_next(q); + } + ti->ti_mbuf = m; + + /* + * While we overlap succeeding segments trim them or, + * if they are completely covered, dequeue them. + */ + while (!tcpfrag_list_end(q, tp)) { + register int i = (ti->ti_seq + ti->ti_len) - q->ti_seq; + if (i <= 0) + break; + if (i < q->ti_len) { + q->ti_seq += i; + q->ti_len -= i; + m_adj(q->ti_mbuf, i); + break; + } + q = tcpiphdr_next(q); + m = tcpiphdr_prev(q)->ti_mbuf; + slirp_remque(tcpiphdr2qlink(tcpiphdr_prev(q))); + m_free(m); + } + + /* + * Stick new segment in its place. + */ + slirp_insque(tcpiphdr2qlink(ti), tcpiphdr2qlink(tcpiphdr_prev(q))); + +present: + /* + * Present data to user, advancing rcv_nxt through + * completed sequence space. + */ + if (!TCPS_HAVEESTABLISHED(tp->t_state)) + return (0); + ti = tcpfrag_list_first(tp); + if (tcpfrag_list_end(ti, tp) || ti->ti_seq != tp->rcv_nxt) + return (0); + if (tp->t_state == TCPS_SYN_RECEIVED && ti->ti_len) + return (0); + do { + tp->rcv_nxt += ti->ti_len; + flags = ti->ti_flags & TH_FIN; + slirp_remque(tcpiphdr2qlink(ti)); + m = ti->ti_mbuf; + ti = tcpiphdr_next(ti); + if (so->so_state & SS_FCANTSENDMORE) + m_free(m); + else { + if (so->so_emu) { + if (tcp_emu(so, m)) + sbappend(so, m); + } else + sbappend(so, m); + } + } while (!tcpfrag_list_end(ti, tp) && ti->ti_seq == tp->rcv_nxt); + return (flags); +} + +/* + * TCP input routine, follows pages 65-76 of the + * protocol specification dated September, 1981 very closely. + */ +void tcp_input(struct mbuf *m, int iphlen, struct socket *inso, + unsigned short af) +{ + struct ip save_ip, *ip; + struct ip6 save_ip6, *ip6; + register struct tcpiphdr *ti; + char *optp = NULL; + int optlen = 0; + int len, tlen, off; + register struct tcpcb *tp = NULL; + register int tiflags; + struct socket *so = NULL; + int todrop, acked, ourfinisacked, needoutput = 0; + int iss = 0; + uint32_t tiwin; + int ret; + struct sockaddr_storage lhost, fhost; + struct sockaddr_in *lhost4, *fhost4; + struct sockaddr_in6 *lhost6, *fhost6; + struct gfwd_list *ex_ptr; + Slirp *slirp; + + DEBUG_CALL("tcp_input"); + DEBUG_ARG("m = %p iphlen = %2d inso = %p", m, iphlen, inso); + + memset(&lhost, 0, sizeof(struct sockaddr_storage)); + memset(&fhost, 0, sizeof(struct sockaddr_storage)); + + /* + * If called with m == 0, then we're continuing the connect + */ + if (m == NULL) { + so = inso; + slirp = so->slirp; + + /* Re-set a few variables */ + tp = sototcpcb(so); + m = so->so_m; + so->so_m = NULL; + ti = so->so_ti; + tiwin = ti->ti_win; + tiflags = ti->ti_flags; + + goto cont_conn; + } + slirp = m->slirp; + switch (af) { + case AF_INET: + M_DUP_DEBUG(slirp, m, 0, + sizeof(struct qlink) + sizeof(struct tcpiphdr) - sizeof(struct ip) - sizeof(struct tcphdr)); + break; + case AF_INET6: + M_DUP_DEBUG(slirp, m, 0, + sizeof(struct qlink) + sizeof(struct tcpiphdr) - sizeof(struct ip6) - sizeof(struct tcphdr)); + break; + } + + ip = mtod(m, struct ip *); + ip6 = mtod(m, struct ip6 *); + + switch (af) { + case AF_INET: + if (iphlen > sizeof(struct ip)) { + ip_stripoptions(m); + iphlen = sizeof(struct ip); + } + /* XXX Check if too short */ + + + /* + * Save a copy of the IP header in case we want restore it + * for sending an ICMP error message in response. + */ + save_ip = *ip; + save_ip.ip_len += iphlen; + + /* + * Get IP and TCP header together in first mbuf. + * Note: IP leaves IP header in first mbuf. + */ + m->m_data -= + sizeof(struct tcpiphdr) - sizeof(struct ip) - sizeof(struct tcphdr); + m->m_len += + sizeof(struct tcpiphdr) - sizeof(struct ip) - sizeof(struct tcphdr); + ti = mtod(m, struct tcpiphdr *); + + /* + * Checksum extended TCP header and data. + */ + tlen = ip->ip_len; + tcpiphdr2qlink(ti)->next = tcpiphdr2qlink(ti)->prev = NULL; + memset(&ti->ih_mbuf, 0, sizeof(struct mbuf_ptr)); + memset(&ti->ti, 0, sizeof(ti->ti)); + ti->ti_x0 = 0; + ti->ti_src = save_ip.ip_src; + ti->ti_dst = save_ip.ip_dst; + ti->ti_pr = save_ip.ip_p; + ti->ti_len = htons((uint16_t)tlen); + break; + + case AF_INET6: + /* + * Save a copy of the IP header in case we want restore it + * for sending an ICMP error message in response. + */ + save_ip6 = *ip6; + /* + * Get IP and TCP header together in first mbuf. + * Note: IP leaves IP header in first mbuf. + */ + m->m_data -= sizeof(struct tcpiphdr) - + (sizeof(struct ip6) + sizeof(struct tcphdr)); + m->m_len += sizeof(struct tcpiphdr) - + (sizeof(struct ip6) + sizeof(struct tcphdr)); + ti = mtod(m, struct tcpiphdr *); + + tlen = ip6->ip_pl; + tcpiphdr2qlink(ti)->next = tcpiphdr2qlink(ti)->prev = NULL; + memset(&ti->ih_mbuf, 0, sizeof(struct mbuf_ptr)); + memset(&ti->ti, 0, sizeof(ti->ti)); + ti->ti_x0 = 0; + ti->ti_src6 = save_ip6.ip_src; + ti->ti_dst6 = save_ip6.ip_dst; + ti->ti_nh6 = save_ip6.ip_nh; + ti->ti_len = htons((uint16_t)tlen); + break; + + default: + g_assert_not_reached(); + } + + len = ((sizeof(struct tcpiphdr) - sizeof(struct tcphdr)) + tlen); + if (cksum(m, len)) { + goto drop; + } + + /* + * Check that TCP offset makes sense, + * pull out TCP options and adjust length. XXX + */ + off = ti->ti_off << 2; + if (off < sizeof(struct tcphdr) || off > tlen) { + goto drop; + } + tlen -= off; + ti->ti_len = tlen; + if (off > sizeof(struct tcphdr)) { + optlen = off - sizeof(struct tcphdr); + optp = mtod(m, char *) + sizeof(struct tcpiphdr); + } + tiflags = ti->ti_flags; + + /* + * Convert TCP protocol specific fields to host format. + */ + NTOHL(ti->ti_seq); + NTOHL(ti->ti_ack); + NTOHS(ti->ti_win); + NTOHS(ti->ti_urp); + + /* + * Drop TCP, IP headers and TCP options. + */ + m->m_data += sizeof(struct tcpiphdr) + off - sizeof(struct tcphdr); + m->m_len -= sizeof(struct tcpiphdr) + off - sizeof(struct tcphdr); + + /* + * Locate pcb for segment. + */ +findso: + lhost.ss_family = af; + fhost.ss_family = af; + switch (af) { + case AF_INET: + lhost4 = (struct sockaddr_in *)&lhost; + lhost4->sin_addr = ti->ti_src; + lhost4->sin_port = ti->ti_sport; + fhost4 = (struct sockaddr_in *)&fhost; + fhost4->sin_addr = ti->ti_dst; + fhost4->sin_port = ti->ti_dport; + break; + case AF_INET6: + lhost6 = (struct sockaddr_in6 *)&lhost; + lhost6->sin6_addr = ti->ti_src6; + lhost6->sin6_port = ti->ti_sport; + fhost6 = (struct sockaddr_in6 *)&fhost; + fhost6->sin6_addr = ti->ti_dst6; + fhost6->sin6_port = ti->ti_dport; + break; + default: + g_assert_not_reached(); + } + + so = solookup(&slirp->tcp_last_so, &slirp->tcb, &lhost, &fhost); + + /* + * If the state is CLOSED (i.e., TCB does not exist) then + * all data in the incoming segment is discarded. + * If the TCB exists but is in CLOSED state, it is embryonic, + * but should either do a listen or a connect soon. + * + * state == CLOSED means we've done socreate() but haven't + * attached it to a protocol yet... + * + * XXX If a TCB does not exist, and the TH_SYN flag is + * the only flag set, then create a session, mark it + * as if it was LISTENING, and continue... + */ + if (so == NULL) { + /* TODO: IPv6 */ + if (slirp->restricted) { + /* Any hostfwds will have an existing socket, so we only get here + * for non-hostfwd connections. These should be dropped, unless it + * happens to be a guestfwd. + */ + for (ex_ptr = slirp->guestfwd_list; ex_ptr; + ex_ptr = ex_ptr->ex_next) { + if (ex_ptr->ex_fport == ti->ti_dport && + ti->ti_dst.s_addr == ex_ptr->ex_addr.s_addr) { + break; + } + } + if (!ex_ptr) { + goto dropwithreset; + } + } + + if ((tiflags & (TH_SYN | TH_FIN | TH_RST | TH_URG | TH_ACK)) != TH_SYN) + goto dropwithreset; + + so = socreate(slirp, IPPROTO_TCP); + tcp_attach(so); + + sbreserve(&so->so_snd, TCP_SNDSPACE); + sbreserve(&so->so_rcv, TCP_RCVSPACE); + + so->lhost.ss = lhost; + so->fhost.ss = fhost; + + so->so_iptos = tcp_tos(so); + if (so->so_iptos == 0) { + switch (af) { + case AF_INET: + so->so_iptos = ((struct ip *)ti)->ip_tos; + break; + case AF_INET6: + break; + default: + g_assert_not_reached(); + } + } + + tp = sototcpcb(so); + tp->t_state = TCPS_LISTEN; + } + + /* + * If this is a still-connecting socket, this probably + * a retransmit of the SYN. Whether it's a retransmit SYN + * or something else, we nuke it. + */ + if (so->so_state & SS_ISFCONNECTING) + goto drop; + + tp = sototcpcb(so); + + /* XXX Should never fail */ + if (tp == NULL) + goto dropwithreset; + if (tp->t_state == TCPS_CLOSED) + goto drop; + + tiwin = ti->ti_win; + + /* + * Segment received on connection. + * Reset idle time and keep-alive timer. + */ + tp->t_idle = 0; + if (slirp_do_keepalive) + tp->t_timer[TCPT_KEEP] = TCPTV_KEEPINTVL; + else + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_IDLE; + + /* + * Process options if not in LISTEN state, + * else do it below (after getting remote address). + */ + if (optp && tp->t_state != TCPS_LISTEN) + tcp_dooptions(tp, (uint8_t *)optp, optlen, ti); + + /* + * Header prediction: check for the two common cases + * of a uni-directional data xfer. If the packet has + * no control flags, is in-sequence, the window didn't + * change and we're not retransmitting, it's a + * candidate. If the length is zero and the ack moved + * forward, we're the sender side of the xfer. Just + * free the data acked & wake any higher level process + * that was blocked waiting for space. If the length + * is non-zero and the ack didn't move, we're the + * receiver side. If we're getting packets in-order + * (the reassembly queue is empty), add the data to + * the socket buffer and note that we need a delayed ack. + * + * XXX Some of these tests are not needed + * eg: the tiwin == tp->snd_wnd prevents many more + * predictions.. with no *real* advantage.. + */ + if (tp->t_state == TCPS_ESTABLISHED && + (tiflags & (TH_SYN | TH_FIN | TH_RST | TH_URG | TH_ACK)) == TH_ACK && + ti->ti_seq == tp->rcv_nxt && tiwin && tiwin == tp->snd_wnd && + tp->snd_nxt == tp->snd_max) { + if (ti->ti_len == 0) { + if (SEQ_GT(ti->ti_ack, tp->snd_una) && + SEQ_LEQ(ti->ti_ack, tp->snd_max) && + tp->snd_cwnd >= tp->snd_wnd) { + /* + * this is a pure ack for outstanding data. + */ + if (tp->t_rtt && SEQ_GT(ti->ti_ack, tp->t_rtseq)) + tcp_xmit_timer(tp, tp->t_rtt); + acked = ti->ti_ack - tp->snd_una; + sodrop(so, acked); + tp->snd_una = ti->ti_ack; + m_free(m); + + /* + * If all outstanding data are acked, stop + * retransmit timer, otherwise restart timer + * using current (possibly backed-off) value. + * If process is waiting for space, + * wakeup/selwakeup/signal. If data + * are ready to send, let tcp_output + * decide between more output or persist. + */ + if (tp->snd_una == tp->snd_max) + tp->t_timer[TCPT_REXMT] = 0; + else if (tp->t_timer[TCPT_PERSIST] == 0) + tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + + /* + * This is called because sowwakeup might have + * put data into so_snd. Since we don't so sowwakeup, + * we don't need this.. XXX??? + */ + if (so->so_snd.sb_cc) + tcp_output(tp); + + return; + } + } else if (ti->ti_ack == tp->snd_una && tcpfrag_list_empty(tp) && + ti->ti_len <= sbspace(&so->so_rcv)) { + /* + * this is a pure, in-sequence data packet + * with nothing on the reassembly queue and + * we have enough buffer space to take it. + */ + tp->rcv_nxt += ti->ti_len; + /* + * Add data to socket buffer. + */ + if (so->so_emu) { + if (tcp_emu(so, m)) + sbappend(so, m); + } else + sbappend(so, m); + + /* + * If this is a short packet, then ACK now - with Nagel + * congestion avoidance sender won't send more until + * he gets an ACK. + * + * It is better to not delay acks at all to maximize + * TCP throughput. See RFC 2581. + */ + tp->t_flags |= TF_ACKNOW; + tcp_output(tp); + return; + } + } /* header prediction */ + /* + * Calculate amount of space in receive window, + * and then do TCP input processing. + * Receive window is amount of space in rcv queue, + * but not less than advertised window. + */ + { + int win; + win = sbspace(&so->so_rcv); + if (win < 0) + win = 0; + tp->rcv_wnd = MAX(win, (int)(tp->rcv_adv - tp->rcv_nxt)); + } + + switch (tp->t_state) { + /* + * If the state is LISTEN then ignore segment if it contains an RST. + * If the segment contains an ACK then it is bad and send a RST. + * If it does not contain a SYN then it is not interesting; drop it. + * Don't bother responding if the destination was a broadcast. + * Otherwise initialize tp->rcv_nxt, and tp->irs, select an initial + * tp->iss, and send a segment: + * + * Also initialize tp->snd_nxt to tp->iss+1 and tp->snd_una to tp->iss. + * Fill in remote peer address fields if not previously specified. + * Enter SYN_RECEIVED state, and process any other fields of this + * segment in this state. + */ + case TCPS_LISTEN: { + if (tiflags & TH_RST) + goto drop; + if (tiflags & TH_ACK) + goto dropwithreset; + if ((tiflags & TH_SYN) == 0) + goto drop; + + /* + * This has way too many gotos... + * But a bit of spaghetti code never hurt anybody :) + */ + + /* + * If this is destined for the control address, then flag to + * tcp_ctl once connected, otherwise connect + */ + /* TODO: IPv6 */ + if (af == AF_INET && + (so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) == + slirp->vnetwork_addr.s_addr) { + if (so->so_faddr.s_addr != slirp->vhost_addr.s_addr && + so->so_faddr.s_addr != slirp->vnameserver_addr.s_addr) { + /* May be an add exec */ + for (ex_ptr = slirp->guestfwd_list; ex_ptr; + ex_ptr = ex_ptr->ex_next) { + if (ex_ptr->ex_fport == so->so_fport && + so->so_faddr.s_addr == ex_ptr->ex_addr.s_addr) { + so->so_state |= SS_CTL; + break; + } + } + if (so->so_state & SS_CTL) { + goto cont_input; + } + } + /* CTL_ALIAS: Do nothing, tcp_fconnect will be called on it */ + } + + if (so->so_emu & EMU_NOCONNECT) { + so->so_emu &= ~EMU_NOCONNECT; + goto cont_input; + } + + if ((tcp_fconnect(so, so->so_ffamily) == -1) && (errno != EAGAIN) && + (errno != EINPROGRESS) && (errno != EWOULDBLOCK)) { + uint8_t code; + DEBUG_MISC(" tcp fconnect errno = %d-%s", errno, strerror(errno)); + if (errno == ECONNREFUSED) { + /* ACK the SYN, send RST to refuse the connection */ + tcp_respond(tp, ti, m, ti->ti_seq + 1, (tcp_seq)0, + TH_RST | TH_ACK, af); + } else { + switch (af) { + case AF_INET: + code = ICMP_UNREACH_NET; + if (errno == EHOSTUNREACH) { + code = ICMP_UNREACH_HOST; + } + break; + case AF_INET6: + code = ICMP6_UNREACH_NO_ROUTE; + if (errno == EHOSTUNREACH) { + code = ICMP6_UNREACH_ADDRESS; + } + break; + default: + g_assert_not_reached(); + } + HTONL(ti->ti_seq); /* restore tcp header */ + HTONL(ti->ti_ack); + HTONS(ti->ti_win); + HTONS(ti->ti_urp); + m->m_data -= + sizeof(struct tcpiphdr) + off - sizeof(struct tcphdr); + m->m_len += + sizeof(struct tcpiphdr) + off - sizeof(struct tcphdr); + switch (af) { + case AF_INET: + m->m_data += sizeof(struct tcpiphdr) - sizeof(struct ip) - + sizeof(struct tcphdr); + m->m_len -= sizeof(struct tcpiphdr) - sizeof(struct ip) - + sizeof(struct tcphdr); + *ip = save_ip; + icmp_send_error(m, ICMP_UNREACH, code, 0, strerror(errno)); + break; + case AF_INET6: + m->m_data += sizeof(struct tcpiphdr) - + (sizeof(struct ip6) + sizeof(struct tcphdr)); + m->m_len -= sizeof(struct tcpiphdr) - + (sizeof(struct ip6) + sizeof(struct tcphdr)); + *ip6 = save_ip6; + icmp6_send_error(m, ICMP6_UNREACH, code); + break; + default: + g_assert_not_reached(); + } + } + tcp_close(tp); + m_free(m); + } else { + /* + * Haven't connected yet, save the current mbuf + * and ti, and return + * XXX Some OS's don't tell us whether the connect() + * succeeded or not. So we must time it out. + */ + so->so_m = m; + so->so_ti = ti; + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT; + tp->t_state = TCPS_SYN_RECEIVED; + /* + * Initialize receive sequence numbers now so that we can send a + * valid RST if the remote end rejects our connection. + */ + tp->irs = ti->ti_seq; + tcp_rcvseqinit(tp); + tcp_template(tp); + } + return; + + cont_conn: + /* m==NULL + * Check if the connect succeeded + */ + if (so->so_state & SS_NOFDREF) { + tp = tcp_close(tp); + goto dropwithreset; + } + cont_input: + tcp_template(tp); + + if (optp) + tcp_dooptions(tp, (uint8_t *)optp, optlen, ti); + + if (iss) + tp->iss = iss; + else + tp->iss = slirp->tcp_iss; + slirp->tcp_iss += TCP_ISSINCR / 2; + tp->irs = ti->ti_seq; + tcp_sendseqinit(tp); + tcp_rcvseqinit(tp); + tp->t_flags |= TF_ACKNOW; + tp->t_state = TCPS_SYN_RECEIVED; + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT; + goto trimthenstep6; + } /* case TCPS_LISTEN */ + + /* + * If the state is SYN_SENT: + * if seg contains an ACK, but not for our SYN, drop the input. + * if seg contains a RST, then drop the connection. + * if seg does not contain SYN, then drop it. + * Otherwise this is an acceptable SYN segment + * initialize tp->rcv_nxt and tp->irs + * if seg contains ack then advance tp->snd_una + * if SYN has been acked change to ESTABLISHED else SYN_RCVD state + * arrange for segment to be acked (eventually) + * continue processing rest of data/controls, beginning with URG + */ + case TCPS_SYN_SENT: + if (getenv("SLIRP_FUZZING") && + /* Align seq numbers on what the fuzzing trace says */ + tp->iss == 1 && ti->ti_ack != 0) { + tp->iss = ti->ti_ack - 1; + tp->snd_max = tp->iss + 1; + } + + if ((tiflags & TH_ACK) && + (SEQ_LEQ(ti->ti_ack, tp->iss) || SEQ_GT(ti->ti_ack, tp->snd_max))) + goto dropwithreset; + + if (tiflags & TH_RST) { + if (tiflags & TH_ACK) { + tcp_drop(tp, 0); /* XXX Check t_softerror! */ + } + goto drop; + } + + if ((tiflags & TH_SYN) == 0) + goto drop; + if (tiflags & TH_ACK) { + tp->snd_una = ti->ti_ack; + if (SEQ_LT(tp->snd_nxt, tp->snd_una)) + tp->snd_nxt = tp->snd_una; + } + + tp->t_timer[TCPT_REXMT] = 0; + tp->irs = ti->ti_seq; + tcp_rcvseqinit(tp); + tp->t_flags |= TF_ACKNOW; + if (tiflags & TH_ACK && SEQ_GT(tp->snd_una, tp->iss)) { + soisfconnected(so); + tp->t_state = TCPS_ESTABLISHED; + + tcp_reass(tp, (struct tcpiphdr *)0, (struct mbuf *)0); + /* + * if we didn't have to retransmit the SYN, + * use its rtt as our initial srtt & rtt var. + */ + if (tp->t_rtt) + tcp_xmit_timer(tp, tp->t_rtt); + } else + tp->t_state = TCPS_SYN_RECEIVED; + + trimthenstep6: + /* + * Advance ti->ti_seq to correspond to first data byte. + * If data, trim to stay within window, + * dropping FIN if necessary. + */ + ti->ti_seq++; + if (ti->ti_len > tp->rcv_wnd) { + todrop = ti->ti_len - tp->rcv_wnd; + m_adj(m, -todrop); + ti->ti_len = tp->rcv_wnd; + tiflags &= ~TH_FIN; + } + tp->snd_wl1 = ti->ti_seq - 1; + tp->rcv_up = ti->ti_seq; + goto step6; + } /* switch tp->t_state */ + /* + * States other than LISTEN or SYN_SENT. + * Check that at least some bytes of segment are within + * receive window. If segment begins before rcv_nxt, + * drop leading data (and SYN); if nothing left, just ack. + */ + todrop = tp->rcv_nxt - ti->ti_seq; + if (todrop > 0) { + if (tiflags & TH_SYN) { + tiflags &= ~TH_SYN; + ti->ti_seq++; + if (ti->ti_urp > 1) + ti->ti_urp--; + else + tiflags &= ~TH_URG; + todrop--; + } + /* + * Following if statement from Stevens, vol. 2, p. 960. + */ + if (todrop > ti->ti_len || + (todrop == ti->ti_len && (tiflags & TH_FIN) == 0)) { + /* + * Any valid FIN must be to the left of the window. + * At this point the FIN must be a duplicate or out + * of sequence; drop it. + */ + tiflags &= ~TH_FIN; + + /* + * Send an ACK to resynchronize and drop any data. + * But keep on processing for RST or ACK. + */ + tp->t_flags |= TF_ACKNOW; + todrop = ti->ti_len; + } + m_adj(m, todrop); + ti->ti_seq += todrop; + ti->ti_len -= todrop; + if (ti->ti_urp > todrop) + ti->ti_urp -= todrop; + else { + tiflags &= ~TH_URG; + ti->ti_urp = 0; + } + } + /* + * If new data are received on a connection after the + * user processes are gone, then RST the other end. + */ + if ((so->so_state & SS_NOFDREF) && tp->t_state > TCPS_CLOSE_WAIT && + ti->ti_len) { + tp = tcp_close(tp); + goto dropwithreset; + } + + /* + * If segment ends after window, drop trailing data + * (and PUSH and FIN); if nothing left, just ACK. + */ + todrop = (ti->ti_seq + ti->ti_len) - (tp->rcv_nxt + tp->rcv_wnd); + if (todrop > 0) { + if (todrop >= ti->ti_len) { + /* + * If a new connection request is received + * while in TIME_WAIT, drop the old connection + * and start over if the sequence numbers + * are above the previous ones. + */ + if (tiflags & TH_SYN && tp->t_state == TCPS_TIME_WAIT && + SEQ_GT(ti->ti_seq, tp->rcv_nxt)) { + iss = tp->rcv_nxt + TCP_ISSINCR; + tp = tcp_close(tp); + goto findso; + } + /* + * If window is closed can only take segments at + * window edge, and have to drop data and PUSH from + * incoming segments. Continue processing, but + * remember to ack. Otherwise, drop segment + * and ack. + */ + if (tp->rcv_wnd == 0 && ti->ti_seq == tp->rcv_nxt) { + tp->t_flags |= TF_ACKNOW; + } else { + goto dropafterack; + } + } + m_adj(m, -todrop); + ti->ti_len -= todrop; + tiflags &= ~(TH_PUSH | TH_FIN); + } + + /* + * If the RST bit is set examine the state: + * SYN_RECEIVED STATE: + * If passive open, return to LISTEN state. + * If active open, inform user that connection was refused. + * ESTABLISHED, FIN_WAIT_1, FIN_WAIT2, CLOSE_WAIT STATES: + * Inform user that connection was reset, and close tcb. + * CLOSING, LAST_ACK, TIME_WAIT STATES + * Close the tcb. + */ + if (tiflags & TH_RST) + switch (tp->t_state) { + case TCPS_SYN_RECEIVED: + case TCPS_ESTABLISHED: + case TCPS_FIN_WAIT_1: + case TCPS_FIN_WAIT_2: + case TCPS_CLOSE_WAIT: + tp->t_state = TCPS_CLOSED; + tcp_close(tp); + goto drop; + + case TCPS_CLOSING: + case TCPS_LAST_ACK: + case TCPS_TIME_WAIT: + tcp_close(tp); + goto drop; + } + + /* + * If a SYN is in the window, then this is an + * error and we send an RST and drop the connection. + */ + if (tiflags & TH_SYN) { + tp = tcp_drop(tp, 0); + goto dropwithreset; + } + + /* + * If the ACK bit is off we drop the segment and return. + */ + if ((tiflags & TH_ACK) == 0) + goto drop; + + /* + * Ack processing. + */ + switch (tp->t_state) { + /* + * In SYN_RECEIVED state if the ack ACKs our SYN then enter + * ESTABLISHED state and continue processing, otherwise + * send an RST. una<=ack<=max + */ + case TCPS_SYN_RECEIVED: + if (getenv("SLIRP_FUZZING") && + /* Align seq numbers on what the fuzzing trace says */ + tp->iss == 1 && ti->ti_ack != 0) { + tp->iss = ti->ti_ack - 1; + tp->snd_max = tp->iss + 1; + tp->snd_una = ti->ti_ack; + } + + if (SEQ_GT(tp->snd_una, ti->ti_ack) || SEQ_GT(ti->ti_ack, tp->snd_max)) + goto dropwithreset; + tp->t_state = TCPS_ESTABLISHED; + /* + * The sent SYN is ack'ed with our sequence number +1 + * The first data byte already in the buffer will get + * lost if no correction is made. This is only needed for + * SS_CTL since the buffer is empty otherwise. + * tp->snd_una++; or: + */ + tp->snd_una = ti->ti_ack; + if (so->so_state & SS_CTL) { + /* So tcp_ctl reports the right state */ + ret = tcp_ctl(so); + if (ret == 1) { + soisfconnected(so); + so->so_state &= ~SS_CTL; /* success XXX */ + } else if (ret == 2) { + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_NOFDREF; /* CTL_CMD */ + } else { + needoutput = 1; + tp->t_state = TCPS_FIN_WAIT_1; + } + } else { + soisfconnected(so); + } + + tcp_reass(tp, (struct tcpiphdr *)0, (struct mbuf *)0); + tp->snd_wl1 = ti->ti_seq - 1; + /* Avoid ack processing; snd_una==ti_ack => dup ack */ + goto synrx_to_est; + /* fall into ... */ + + /* + * In ESTABLISHED state: drop duplicate ACKs; ACK out of range + * ACKs. If the ack is in the range + * tp->snd_una < ti->ti_ack <= tp->snd_max + * then advance tp->snd_una to ti->ti_ack and drop + * data from the retransmission queue. If this ACK reflects + * more up to date window information we update our window information. + */ + case TCPS_ESTABLISHED: + case TCPS_FIN_WAIT_1: + case TCPS_FIN_WAIT_2: + case TCPS_CLOSE_WAIT: + case TCPS_CLOSING: + case TCPS_LAST_ACK: + case TCPS_TIME_WAIT: + + if (SEQ_LEQ(ti->ti_ack, tp->snd_una)) { + if (ti->ti_len == 0 && tiwin == tp->snd_wnd) { + DEBUG_MISC(" dup ack m = %p so = %p", m, so); + /* + * If we have outstanding data (other than + * a window probe), this is a completely + * duplicate ack (ie, window info didn't + * change), the ack is the biggest we've + * seen and we've seen exactly our rexmt + * threshold of them, assume a packet + * has been dropped and retransmit it. + * Kludge snd_nxt & the congestion + * window so we send only this one + * packet. + * + * We know we're losing at the current + * window size so do congestion avoidance + * (set ssthresh to half the current window + * and pull our congestion window back to + * the new ssthresh). + * + * Dup acks mean that packets have left the + * network (they're now cached at the receiver) + * so bump cwnd by the amount in the receiver + * to keep a constant cwnd packets in the + * network. + */ + if (tp->t_timer[TCPT_REXMT] == 0 || ti->ti_ack != tp->snd_una) + tp->t_dupacks = 0; + else if (++tp->t_dupacks == TCPREXMTTHRESH) { + tcp_seq onxt = tp->snd_nxt; + unsigned win = + MIN(tp->snd_wnd, tp->snd_cwnd) / 2 / tp->t_maxseg; + + if (win < 2) + win = 2; + tp->snd_ssthresh = win * tp->t_maxseg; + tp->t_timer[TCPT_REXMT] = 0; + tp->t_rtt = 0; + tp->snd_nxt = ti->ti_ack; + tp->snd_cwnd = tp->t_maxseg; + tcp_output(tp); + tp->snd_cwnd = + tp->snd_ssthresh + tp->t_maxseg * tp->t_dupacks; + if (SEQ_GT(onxt, tp->snd_nxt)) + tp->snd_nxt = onxt; + goto drop; + } else if (tp->t_dupacks > TCPREXMTTHRESH) { + tp->snd_cwnd += tp->t_maxseg; + tcp_output(tp); + goto drop; + } + } else + tp->t_dupacks = 0; + break; + } + synrx_to_est: + /* + * If the congestion window was inflated to account + * for the other side's cached packets, retract it. + */ + if (tp->t_dupacks > TCPREXMTTHRESH && tp->snd_cwnd > tp->snd_ssthresh) + tp->snd_cwnd = tp->snd_ssthresh; + tp->t_dupacks = 0; + if (SEQ_GT(ti->ti_ack, tp->snd_max)) { + goto dropafterack; + } + acked = ti->ti_ack - tp->snd_una; + + /* + * If transmit timer is running and timed sequence + * number was acked, update smoothed round trip time. + * Since we now have an rtt measurement, cancel the + * timer backoff (cf., Phil Karn's retransmit alg.). + * Recompute the initial retransmit timer. + */ + if (tp->t_rtt && SEQ_GT(ti->ti_ack, tp->t_rtseq)) + tcp_xmit_timer(tp, tp->t_rtt); + + /* + * If all outstanding data is acked, stop retransmit + * timer and remember to restart (more output or persist). + * If there is more data to be acked, restart retransmit + * timer, using current (possibly backed-off) value. + */ + if (ti->ti_ack == tp->snd_max) { + tp->t_timer[TCPT_REXMT] = 0; + needoutput = 1; + } else if (tp->t_timer[TCPT_PERSIST] == 0) + tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + /* + * When new data is acked, open the congestion window. + * If the window gives us less than ssthresh packets + * in flight, open exponentially (maxseg per packet). + * Otherwise open linearly: maxseg per window + * (maxseg^2 / cwnd per packet). + */ + { + register unsigned cw = tp->snd_cwnd; + register unsigned incr = tp->t_maxseg; + + if (cw > tp->snd_ssthresh) + incr = incr * incr / cw; + tp->snd_cwnd = MIN(cw + incr, TCP_MAXWIN << tp->snd_scale); + } + if (acked > so->so_snd.sb_cc) { + tp->snd_wnd -= so->so_snd.sb_cc; + sodrop(so, (int)so->so_snd.sb_cc); + ourfinisacked = 1; + } else { + sodrop(so, acked); + tp->snd_wnd -= acked; + ourfinisacked = 0; + } + tp->snd_una = ti->ti_ack; + if (SEQ_LT(tp->snd_nxt, tp->snd_una)) + tp->snd_nxt = tp->snd_una; + + switch (tp->t_state) { + /* + * In FIN_WAIT_1 STATE in addition to the processing + * for the ESTABLISHED state if our FIN is now acknowledged + * then enter FIN_WAIT_2. + */ + case TCPS_FIN_WAIT_1: + if (ourfinisacked) { + /* + * If we can't receive any more + * data, then closing user can proceed. + * Starting the timer is contrary to the + * specification, but if we don't get a FIN + * we'll hang forever. + */ + if (so->so_state & SS_FCANTRCVMORE) { + tp->t_timer[TCPT_2MSL] = TCP_MAXIDLE; + } + tp->t_state = TCPS_FIN_WAIT_2; + } + break; + + /* + * In CLOSING STATE in addition to the processing for + * the ESTABLISHED state if the ACK acknowledges our FIN + * then enter the TIME-WAIT state, otherwise ignore + * the segment. + */ + case TCPS_CLOSING: + if (ourfinisacked) { + tp->t_state = TCPS_TIME_WAIT; + tcp_canceltimers(tp); + tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + } + break; + + /* + * In LAST_ACK, we may still be waiting for data to drain + * and/or to be acked, as well as for the ack of our FIN. + * If our FIN is now acknowledged, delete the TCB, + * enter the closed state and return. + */ + case TCPS_LAST_ACK: + if (ourfinisacked) { + tcp_close(tp); + goto drop; + } + break; + + /* + * In TIME_WAIT state the only thing that should arrive + * is a retransmission of the remote FIN. Acknowledge + * it and restart the finack timer. + */ + case TCPS_TIME_WAIT: + tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + goto dropafterack; + } + } /* switch(tp->t_state) */ + +step6: + /* + * Update window information. + * Don't look at window if no ACK: TAC's send garbage on first SYN. + */ + if ((tiflags & TH_ACK) && + (SEQ_LT(tp->snd_wl1, ti->ti_seq) || + (tp->snd_wl1 == ti->ti_seq && + (SEQ_LT(tp->snd_wl2, ti->ti_ack) || + (tp->snd_wl2 == ti->ti_ack && tiwin > tp->snd_wnd))))) { + tp->snd_wnd = tiwin; + tp->snd_wl1 = ti->ti_seq; + tp->snd_wl2 = ti->ti_ack; + if (tp->snd_wnd > tp->max_sndwnd) + tp->max_sndwnd = tp->snd_wnd; + needoutput = 1; + } + + /* + * Process segments with URG. + */ + if ((tiflags & TH_URG) && ti->ti_urp && + TCPS_HAVERCVDFIN(tp->t_state) == 0) { + /* + * This is a kludge, but if we receive and accept + * random urgent pointers, we'll crash in + * soreceive. It's hard to imagine someone + * actually wanting to send this much urgent data. + */ + if (ti->ti_urp + so->so_rcv.sb_cc > so->so_rcv.sb_datalen) { + ti->ti_urp = 0; + tiflags &= ~TH_URG; + goto dodata; + } + /* + * If this segment advances the known urgent pointer, + * then mark the data stream. This should not happen + * in CLOSE_WAIT, CLOSING, LAST_ACK or TIME_WAIT STATES since + * a FIN has been received from the remote side. + * In these states we ignore the URG. + * + * According to RFC961 (Assigned Protocols), + * the urgent pointer points to the last octet + * of urgent data. We continue, however, + * to consider it to indicate the first octet + * of data past the urgent section as the original + * spec states (in one of two places). + */ + if (SEQ_GT(ti->ti_seq + ti->ti_urp, tp->rcv_up)) { + tp->rcv_up = ti->ti_seq + ti->ti_urp; + so->so_urgc = + so->so_rcv.sb_cc + (tp->rcv_up - tp->rcv_nxt); /* -1; */ + tp->rcv_up = ti->ti_seq + ti->ti_urp; + } + } else + /* + * If no out of band data is expected, + * pull receive urgent pointer along + * with the receive window. + */ + if (SEQ_GT(tp->rcv_nxt, tp->rcv_up)) + tp->rcv_up = tp->rcv_nxt; +dodata: + + /* + * If this is a small packet, then ACK now - with Nagel + * congestion avoidance sender won't send more until + * he gets an ACK. + */ + if (ti->ti_len && (unsigned)ti->ti_len <= 5 && + ((struct tcpiphdr_2 *)ti)->first_char == (char)27) { + tp->t_flags |= TF_ACKNOW; + } + + /* + * Process the segment text, merging it into the TCP sequencing queue, + * and arranging for acknowledgment of receipt if necessary. + * This process logically involves adjusting tp->rcv_wnd as data + * is presented to the user (this happens in tcp_usrreq.c, + * case PRU_RCVD). If a FIN has already been received on this + * connection then we just ignore the text. + */ + if ((ti->ti_len || (tiflags & TH_FIN)) && + TCPS_HAVERCVDFIN(tp->t_state) == 0) { + + /* + * segment is the next to be received on an established + * connection, and the queue is empty, avoid linkage into and + * removal from the queue and repetition of various + * conversions from tcp_reass(). + */ + if (ti->ti_seq == tp->rcv_nxt && tcpfrag_list_empty(tp) && + tp->t_state == TCPS_ESTABLISHED) { + tp->t_flags |= TF_DELACK; + tp->rcv_nxt += ti->ti_len; + tiflags = ti->ti_flags & TH_FIN; + if (so->so_emu) { + if (tcp_emu(so, m)) + sbappend(so, m); + } else + sbappend(so, m); + } else { + tiflags = tcp_reass(tp, ti, m); + tp->t_flags |= TF_ACKNOW; + } + } else { + m_free(m); + tiflags &= ~TH_FIN; + } + + /* + * If FIN is received ACK the FIN and let the user know + * that the connection is closing. + */ + if (tiflags & TH_FIN) { + if (TCPS_HAVERCVDFIN(tp->t_state) == 0) { + /* + * If we receive a FIN we can't send more data, + * set it SS_FDRAIN + * Shutdown the socket if there is no rx data in the + * buffer. + * soread() is called on completion of shutdown() and + * will got to TCPS_LAST_ACK, and use tcp_output() + * to send the FIN. + */ + sofwdrain(so); + + tp->t_flags |= TF_ACKNOW; + tp->rcv_nxt++; + } + switch (tp->t_state) { + /* + * In SYN_RECEIVED and ESTABLISHED STATES + * enter the CLOSE_WAIT state. + */ + case TCPS_SYN_RECEIVED: + case TCPS_ESTABLISHED: + if (so->so_emu == EMU_CTL) /* no shutdown on socket */ + tp->t_state = TCPS_LAST_ACK; + else + tp->t_state = TCPS_CLOSE_WAIT; + break; + + /* + * If still in FIN_WAIT_1 STATE FIN has not been acked so + * enter the CLOSING state. + */ + case TCPS_FIN_WAIT_1: + tp->t_state = TCPS_CLOSING; + break; + + /* + * In FIN_WAIT_2 state enter the TIME_WAIT state, + * starting the time-wait timer, turning off the other + * standard timers. + */ + case TCPS_FIN_WAIT_2: + tp->t_state = TCPS_TIME_WAIT; + tcp_canceltimers(tp); + tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + break; + + /* + * In TIME_WAIT state restart the 2 MSL time_wait timer. + */ + case TCPS_TIME_WAIT: + tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; + break; + } + } + + /* + * Return any desired output. + */ + if (needoutput || (tp->t_flags & TF_ACKNOW)) { + tcp_output(tp); + } + return; + +dropafterack: + /* + * Generate an ACK dropping incoming segment if it occupies + * sequence space, where the ACK reflects our state. + */ + if (tiflags & TH_RST) + goto drop; + m_free(m); + tp->t_flags |= TF_ACKNOW; + tcp_output(tp); + return; + +dropwithreset: + /* reuses m if m!=NULL, m_free() unnecessary */ + if (tiflags & TH_ACK) + tcp_respond(tp, ti, m, (tcp_seq)0, ti->ti_ack, TH_RST, af); + else { + if (tiflags & TH_SYN) + ti->ti_len++; + tcp_respond(tp, ti, m, ti->ti_seq + ti->ti_len, (tcp_seq)0, + TH_RST | TH_ACK, af); + } + + return; + +drop: + /* + * Drop space held by incoming segment and return. + */ + m_free(m); +} + +static void tcp_dooptions(struct tcpcb *tp, uint8_t *cp, int cnt, + struct tcpiphdr *ti) +{ + uint16_t mss; + int opt, optlen; + + DEBUG_CALL("tcp_dooptions"); + DEBUG_ARG("tp = %p cnt=%i", tp, cnt); + + for (; cnt > 0; cnt -= optlen, cp += optlen) { + opt = cp[0]; + if (opt == TCPOPT_EOL) + break; + if (opt == TCPOPT_NOP) + optlen = 1; + else { + optlen = cp[1]; + if (optlen <= 0) + break; + } + switch (opt) { + default: + continue; + + case TCPOPT_MAXSEG: + if (optlen != TCPOLEN_MAXSEG) + continue; + if (!(ti->ti_flags & TH_SYN)) + continue; + memcpy((char *)&mss, (char *)cp + 2, sizeof(mss)); + NTOHS(mss); + tcp_mss(tp, mss); /* sets t_maxseg */ + break; + } + } +} + +/* + * Collect new round-trip time estimate + * and update averages and current timeout. + */ + +static void tcp_xmit_timer(register struct tcpcb *tp, int rtt) +{ + register short delta; + + DEBUG_CALL("tcp_xmit_timer"); + DEBUG_ARG("tp = %p", tp); + DEBUG_ARG("rtt = %d", rtt); + + if (tp->t_srtt != 0) { + /* + * srtt is stored as fixed point with 3 bits after the + * binary point (i.e., scaled by 8). The following magic + * is equivalent to the smoothing algorithm in rfc793 with + * an alpha of .875 (srtt = rtt/8 + srtt*7/8 in fixed + * point). Adjust rtt to origin 0. + */ + delta = rtt - 1 - (tp->t_srtt >> TCP_RTT_SHIFT); + if ((tp->t_srtt += delta) <= 0) + tp->t_srtt = 1; + /* + * We accumulate a smoothed rtt variance (actually, a + * smoothed mean difference), then set the retransmit + * timer to smoothed rtt + 4 times the smoothed variance. + * rttvar is stored as fixed point with 2 bits after the + * binary point (scaled by 4). The following is + * equivalent to rfc793 smoothing with an alpha of .75 + * (rttvar = rttvar*3/4 + |delta| / 4). This replaces + * rfc793's wired-in beta. + */ + if (delta < 0) + delta = -delta; + delta -= (tp->t_rttvar >> TCP_RTTVAR_SHIFT); + if ((tp->t_rttvar += delta) <= 0) + tp->t_rttvar = 1; + } else { + /* + * No rtt measurement yet - use the unsmoothed rtt. + * Set the variance to half the rtt (so our first + * retransmit happens at 3*rtt). + */ + tp->t_srtt = rtt << TCP_RTT_SHIFT; + tp->t_rttvar = rtt << (TCP_RTTVAR_SHIFT - 1); + } + tp->t_rtt = 0; + tp->t_rxtshift = 0; + + /* + * the retransmit should happen at rtt + 4 * rttvar. + * Because of the way we do the smoothing, srtt and rttvar + * will each average +1/2 tick of bias. When we compute + * the retransmit timer, we want 1/2 tick of rounding and + * 1 extra tick because of +-1/2 tick uncertainty in the + * firing of the timer. The bias will give us exactly the + * 1.5 tick we need. But, because the bias is + * statistical, we have to test that we don't drop below + * the minimum feasible timer (which is 2 ticks). + */ + TCPT_RANGESET(tp->t_rxtcur, TCP_REXMTVAL(tp), (short)tp->t_rttmin, + TCPTV_REXMTMAX); /* XXX */ + + /* + * We received an ack for a packet that wasn't retransmitted; + * it is probably safe to discard any error indications we've + * received recently. This isn't quite right, but close enough + * for now (a route might have failed after we sent a segment, + * and the return path might not be symmetrical). + */ + tp->t_softerror = 0; +} + +/* + * Determine a reasonable value for maxseg size. + * If the route is known, check route for mtu. + * If none, use an mss that can be handled on the outgoing + * interface without forcing IP to fragment; if bigger than + * an mbuf cluster (MCLBYTES), round down to nearest multiple of MCLBYTES + * to utilize large mbufs. If no route is found, route has no mtu, + * or the destination isn't local, use a default, hopefully conservative + * size (usually 512 or the default IP max size, but no more than the mtu + * of the interface), as we can't discover anything about intervening + * gateways or networks. We also initialize the congestion/slow start + * window to be a single segment if the destination isn't local. + * While looking at the routing entry, we also initialize other path-dependent + * parameters from pre-set or cached values in the routing entry. + */ + +int tcp_mss(struct tcpcb *tp, unsigned offer) +{ + struct socket *so = tp->t_socket; + int mss; + + DEBUG_CALL("tcp_mss"); + DEBUG_ARG("tp = %p", tp); + DEBUG_ARG("offer = %d", offer); + + switch (so->so_ffamily) { + case AF_INET: + mss = MIN(so->slirp->if_mtu, so->slirp->if_mru) - + sizeof(struct tcphdr) - sizeof(struct ip); + break; + case AF_INET6: + mss = MIN(so->slirp->if_mtu, so->slirp->if_mru) - + sizeof(struct tcphdr) - sizeof(struct ip6); + break; + default: + g_assert_not_reached(); + } + + if (offer) + mss = MIN(mss, offer); + mss = MAX(mss, 32); + if (mss < tp->t_maxseg || offer != 0) + tp->t_maxseg = MIN(mss, TCP_MAXSEG_MAX); + + tp->snd_cwnd = mss; + + sbreserve(&so->so_snd, + TCP_SNDSPACE + + ((TCP_SNDSPACE % mss) ? (mss - (TCP_SNDSPACE % mss)) : 0)); + sbreserve(&so->so_rcv, + TCP_RCVSPACE + + ((TCP_RCVSPACE % mss) ? (mss - (TCP_RCVSPACE % mss)) : 0)); + + DEBUG_MISC(" returning mss = %d", mss); + + return mss; +} diff --git a/src/net/libslirp/src/tcp_output.c b/src/net/libslirp/src/tcp_output.c new file mode 100644 index 00000000..77c1204a --- /dev/null +++ b/src/net/libslirp/src/tcp_output.c @@ -0,0 +1,513 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_output.c 8.3 (Berkeley) 12/30/93 + * tcp_output.c,v 1.3 1994/09/15 10:36:55 davidg Exp + */ + +/* + * Changes and additions relating to SLiRP + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" + +static const uint8_t tcp_outflags[TCP_NSTATES] = { + TH_RST | TH_ACK, 0, TH_SYN, TH_SYN | TH_ACK, + TH_ACK, TH_ACK, TH_FIN | TH_ACK, TH_FIN | TH_ACK, + TH_FIN | TH_ACK, TH_ACK, TH_ACK, +}; + + +#undef MAX_TCPOPTLEN +#define MAX_TCPOPTLEN 32 /* max # bytes that go in options */ + +/* + * Tcp output routine: figure out what should be sent and send it. + */ +int tcp_output(struct tcpcb *tp) +{ + register struct socket *so = tp->t_socket; + register long len, win; + int off, flags, error; + register struct mbuf *m; + register struct tcpiphdr *ti, tcpiph_save; + struct ip *ip; + struct ip6 *ip6; + uint8_t opt[MAX_TCPOPTLEN]; + unsigned optlen, hdrlen; + int idle, sendalot; + + DEBUG_CALL("tcp_output"); + DEBUG_ARG("tp = %p", tp); + + /* + * Determine length of data that should be transmitted, + * and flags that will be used. + * If there is some data or critical controls (SYN, RST) + * to send, then transmit; otherwise, investigate further. + */ + idle = (tp->snd_max == tp->snd_una); + if (idle && tp->t_idle >= tp->t_rxtcur) + /* + * We have been idle for "a while" and no acks are + * expected to clock out any data we send -- + * slow start to get ack "clock" running again. + */ + tp->snd_cwnd = tp->t_maxseg; +again: + sendalot = 0; + off = tp->snd_nxt - tp->snd_una; + win = MIN(tp->snd_wnd, tp->snd_cwnd); + + flags = tcp_outflags[tp->t_state]; + + DEBUG_MISC(" --- tcp_output flags = 0x%x", flags); + + /* + * If in persist timeout with window of 0, send 1 byte. + * Otherwise, if window is small but nonzero + * and timer expired, we will send what we can + * and go to transmit state. + */ + if (tp->t_force) { + if (win == 0) { + /* + * If we still have some data to send, then + * clear the FIN bit. Usually this would + * happen below when it realizes that we + * aren't sending all the data. However, + * if we have exactly 1 byte of unset data, + * then it won't clear the FIN bit below, + * and if we are in persist state, we wind + * up sending the packet without recording + * that we sent the FIN bit. + * + * We can't just blindly clear the FIN bit, + * because if we don't have any more data + * to send then the probe will be the FIN + * itself. + */ + if (off < so->so_snd.sb_cc) + flags &= ~TH_FIN; + win = 1; + } else { + tp->t_timer[TCPT_PERSIST] = 0; + tp->t_rxtshift = 0; + } + } + + len = MIN(so->so_snd.sb_cc, win) - off; + + if (len < 0) { + /* + * If FIN has been sent but not acked, + * but we haven't been called to retransmit, + * len will be -1. Otherwise, window shrank + * after we sent into it. If window shrank to 0, + * cancel pending retransmit and pull snd_nxt + * back to (closed) window. We will enter persist + * state below. If the window didn't close completely, + * just wait for an ACK. + */ + len = 0; + if (win == 0) { + tp->t_timer[TCPT_REXMT] = 0; + tp->snd_nxt = tp->snd_una; + } + } + + if (len > tp->t_maxseg) { + len = tp->t_maxseg; + sendalot = 1; + } + if (SEQ_LT(tp->snd_nxt + len, tp->snd_una + so->so_snd.sb_cc)) + flags &= ~TH_FIN; + + win = sbspace(&so->so_rcv); + + /* + * Sender silly window avoidance. If connection is idle + * and can send all data, a maximum segment, + * at least a maximum default-size segment do it, + * or are forced, do it; otherwise don't bother. + * If peer's buffer is tiny, then send + * when window is at least half open. + * If retransmitting (possibly after persist timer forced us + * to send into a small window), then must resend. + */ + if (len) { + if (len == tp->t_maxseg) + goto send; + if ((1 || idle || tp->t_flags & TF_NODELAY) && + len + off >= so->so_snd.sb_cc) + goto send; + if (tp->t_force) + goto send; + if (len >= tp->max_sndwnd / 2 && tp->max_sndwnd > 0) + goto send; + if (SEQ_LT(tp->snd_nxt, tp->snd_max)) + goto send; + } + + /* + * Compare available window to amount of window + * known to peer (as advertised window less + * next expected input). If the difference is at least two + * max size segments, or at least 50% of the maximum possible + * window, then want to send a window update to peer. + */ + if (win > 0) { + /* + * "adv" is the amount we can increase the window, + * taking into account that we are limited by + * TCP_MAXWIN << tp->rcv_scale. + */ + long adv = MIN(win, (long)TCP_MAXWIN << tp->rcv_scale) - + (tp->rcv_adv - tp->rcv_nxt); + + if (adv >= (long)(2 * tp->t_maxseg)) + goto send; + if (2 * adv >= (long)so->so_rcv.sb_datalen) + goto send; + } + + /* + * Send if we owe peer an ACK. + */ + if (tp->t_flags & TF_ACKNOW) + goto send; + if (flags & (TH_SYN | TH_RST)) + goto send; + if (SEQ_GT(tp->snd_up, tp->snd_una)) + goto send; + /* + * If our state indicates that FIN should be sent + * and we have not yet done so, or we're retransmitting the FIN, + * then we need to send. + */ + if (flags & TH_FIN && + ((tp->t_flags & TF_SENTFIN) == 0 || tp->snd_nxt == tp->snd_una)) + goto send; + + /* + * TCP window updates are not reliable, rather a polling protocol + * using ``persist'' packets is used to insure receipt of window + * updates. The three ``states'' for the output side are: + * idle not doing retransmits or persists + * persisting to move a small or zero window + * (re)transmitting and thereby not persisting + * + * tp->t_timer[TCPT_PERSIST] + * is set when we are in persist state. + * tp->t_force + * is set when we are called to send a persist packet. + * tp->t_timer[TCPT_REXMT] + * is set when we are retransmitting + * The output side is idle when both timers are zero. + * + * If send window is too small, there is data to transmit, and no + * retransmit or persist is pending, then go to persist state. + * If nothing happens soon, send when timer expires: + * if window is nonzero, transmit what we can, + * otherwise force out a byte. + */ + if (so->so_snd.sb_cc && tp->t_timer[TCPT_REXMT] == 0 && + tp->t_timer[TCPT_PERSIST] == 0) { + tp->t_rxtshift = 0; + tcp_setpersist(tp); + } + + /* + * No reason to send a segment, just return. + */ + return (0); + +send: + /* + * Before ESTABLISHED, force sending of initial options + * unless TCP set not to do any options. + * NOTE: we assume that the IP/TCP header plus TCP options + * always fit in a single mbuf, leaving room for a maximum + * link header, i.e. + * max_linkhdr + sizeof (struct tcpiphdr) + optlen <= MHLEN + */ + optlen = 0; + hdrlen = sizeof(struct tcpiphdr); + if (flags & TH_SYN) { + tp->snd_nxt = tp->iss; + if ((tp->t_flags & TF_NOOPT) == 0) { + uint16_t mss; + + opt[0] = TCPOPT_MAXSEG; + opt[1] = 4; + mss = htons((uint16_t)tcp_mss(tp, 0)); + memcpy((char *)(opt + 2), (char *)&mss, sizeof(mss)); + optlen = 4; + } + } + + hdrlen += optlen; + + /* + * Adjust data length if insertion of options will + * bump the packet length beyond the t_maxseg length. + */ + if (len > tp->t_maxseg - optlen) { + len = tp->t_maxseg - optlen; + sendalot = 1; + } + + /* + * Grab a header mbuf, attaching a copy of data to + * be transmitted, and initialize the header from + * the template for sends on this connection. + */ + if (len) { + m = m_get(so->slirp); + if (m == NULL) { + error = 1; + goto out; + } + m->m_data += IF_MAXLINKHDR; + m->m_len = hdrlen; + + sbcopy(&so->so_snd, off, (int)len, mtod(m, char *) + hdrlen); + m->m_len += len; + + /* + * If we're sending everything we've got, set PUSH. + * (This will keep happy those implementations which only + * give data to the user when a buffer fills or + * a PUSH comes in.) + */ + if (off + len == so->so_snd.sb_cc) + flags |= TH_PUSH; + } else { + m = m_get(so->slirp); + if (m == NULL) { + error = 1; + goto out; + } + m->m_data += IF_MAXLINKHDR; + m->m_len = hdrlen; + } + + ti = mtod(m, struct tcpiphdr *); + + memcpy((char *)ti, &tp->t_template, sizeof(struct tcpiphdr)); + + /* + * Fill in fields, remembering maximum advertised + * window for use in delaying messages about window sizes. + * If resending a FIN, be sure not to use a new sequence number. + */ + if (flags & TH_FIN && tp->t_flags & TF_SENTFIN && + tp->snd_nxt == tp->snd_max) + tp->snd_nxt--; + /* + * If we are doing retransmissions, then snd_nxt will + * not reflect the first unsent octet. For ACK only + * packets, we do not want the sequence number of the + * retransmitted packet, we want the sequence number + * of the next unsent octet. So, if there is no data + * (and no SYN or FIN), use snd_max instead of snd_nxt + * when filling in ti_seq. But if we are in persist + * state, snd_max might reflect one byte beyond the + * right edge of the window, so use snd_nxt in that + * case, since we know we aren't doing a retransmission. + * (retransmit and persist are mutually exclusive...) + */ + if (len || (flags & (TH_SYN | TH_FIN)) || tp->t_timer[TCPT_PERSIST]) + ti->ti_seq = htonl(tp->snd_nxt); + else + ti->ti_seq = htonl(tp->snd_max); + ti->ti_ack = htonl(tp->rcv_nxt); + if (optlen) { + memcpy((char *)(ti + 1), (char *)opt, optlen); + ti->ti_off = (sizeof(struct tcphdr) + optlen) >> 2; + } + ti->ti_flags = flags; + /* + * Calculate receive window. Don't shrink window, + * but avoid silly window syndrome. + */ + if (win < (long)(so->so_rcv.sb_datalen / 4) && win < (long)tp->t_maxseg) + win = 0; + if (win > (long)TCP_MAXWIN << tp->rcv_scale) + win = (long)TCP_MAXWIN << tp->rcv_scale; + if (win < (long)(tp->rcv_adv - tp->rcv_nxt)) + win = (long)(tp->rcv_adv - tp->rcv_nxt); + ti->ti_win = htons((uint16_t)(win >> tp->rcv_scale)); + + if (SEQ_GT(tp->snd_up, tp->snd_una)) { + ti->ti_urp = htons((uint16_t)(tp->snd_up - ntohl(ti->ti_seq))); + ti->ti_flags |= TH_URG; + } else + /* + * If no urgent pointer to send, then we pull + * the urgent pointer to the left edge of the send window + * so that it doesn't drift into the send window on sequence + * number wraparound. + */ + tp->snd_up = tp->snd_una; /* drag it along */ + + /* + * Put TCP length in extended header, and then + * checksum extended header and data. + */ + if (len + optlen) + ti->ti_len = htons((uint16_t)(sizeof(struct tcphdr) + optlen + len)); + ti->ti_sum = cksum(m, (int)(hdrlen + len)); + + /* + * In transmit state, time the transmission and arrange for + * the retransmit. In persist state, just set snd_max. + */ + if (tp->t_force == 0 || tp->t_timer[TCPT_PERSIST] == 0) { + tcp_seq startseq = tp->snd_nxt; + + /* + * Advance snd_nxt over sequence space of this segment. + */ + if (flags & (TH_SYN | TH_FIN)) { + if (flags & TH_SYN) + tp->snd_nxt++; + if (flags & TH_FIN) { + tp->snd_nxt++; + tp->t_flags |= TF_SENTFIN; + } + } + tp->snd_nxt += len; + if (SEQ_GT(tp->snd_nxt, tp->snd_max)) { + tp->snd_max = tp->snd_nxt; + /* + * Time this transmission if not a retransmission and + * not currently timing anything. + */ + if (tp->t_rtt == 0) { + tp->t_rtt = 1; + tp->t_rtseq = startseq; + } + } + + /* + * Set retransmit timer if not currently set, + * and not doing an ack or a keep-alive probe. + * Initial value for retransmit timer is smoothed + * round-trip time + 2 * round-trip time variance. + * Initialize shift counter which is used for backoff + * of retransmit time. + */ + if (tp->t_timer[TCPT_REXMT] == 0 && tp->snd_nxt != tp->snd_una) { + tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + if (tp->t_timer[TCPT_PERSIST]) { + tp->t_timer[TCPT_PERSIST] = 0; + tp->t_rxtshift = 0; + } + } + } else if (SEQ_GT(tp->snd_nxt + len, tp->snd_max)) + tp->snd_max = tp->snd_nxt + len; + + /* + * Fill in IP length and desired time to live and + * send to IP level. There should be a better way + * to handle ttl and tos; we could keep them in + * the template, but need a way to checksum without them. + */ + m->m_len = hdrlen + len; /* XXX Needed? m_len should be correct */ + tcpiph_save = *mtod(m, struct tcpiphdr *); + + switch (so->so_ffamily) { + case AF_INET: + m->m_data += + sizeof(struct tcpiphdr) - sizeof(struct tcphdr) - sizeof(struct ip); + m->m_len -= + sizeof(struct tcpiphdr) - sizeof(struct tcphdr) - sizeof(struct ip); + ip = mtod(m, struct ip *); + + ip->ip_len = m->m_len; + ip->ip_dst = tcpiph_save.ti_dst; + ip->ip_src = tcpiph_save.ti_src; + ip->ip_p = tcpiph_save.ti_pr; + + ip->ip_ttl = IPDEFTTL; + ip->ip_tos = so->so_iptos; + error = ip_output(so, m); + break; + + case AF_INET6: + m->m_data += sizeof(struct tcpiphdr) - sizeof(struct tcphdr) - + sizeof(struct ip6); + m->m_len -= sizeof(struct tcpiphdr) - sizeof(struct tcphdr) - + sizeof(struct ip6); + ip6 = mtod(m, struct ip6 *); + + ip6->ip_pl = tcpiph_save.ti_len; + ip6->ip_dst = tcpiph_save.ti_dst6; + ip6->ip_src = tcpiph_save.ti_src6; + ip6->ip_nh = tcpiph_save.ti_nh6; + + error = ip6_output(so, m, 0); + break; + + default: + g_assert_not_reached(); + } + + if (error) { + out: + return (error); + } + + /* + * Data sent (as far as we can tell). + * If this advertises a larger window than any other segment, + * then remember the size of the advertised window. + * Any pending ACK has now been sent. + */ + if (win > 0 && SEQ_GT(tp->rcv_nxt + win, tp->rcv_adv)) + tp->rcv_adv = tp->rcv_nxt + win; + tp->last_ack_sent = tp->rcv_nxt; + tp->t_flags &= ~(TF_ACKNOW | TF_DELACK); + if (sendalot) + goto again; + + return (0); +} + +void tcp_setpersist(struct tcpcb *tp) +{ + int t = ((tp->t_srtt >> 2) + tp->t_rttvar) >> 1; + + TCPT_RANGESET(tp->t_timer[TCPT_PERSIST], t * tcp_backoff[tp->t_rxtshift], + TCPTV_PERSMIN, TCPTV_PERSMAX); + if (tp->t_rxtshift < TCP_MAXRXTSHIFT) + tp->t_rxtshift++; +} diff --git a/src/net/libslirp/src/tcp_subr.c b/src/net/libslirp/src/tcp_subr.c new file mode 100644 index 00000000..3cfe2860 --- /dev/null +++ b/src/net/libslirp/src/tcp_subr.c @@ -0,0 +1,986 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_subr.c 8.1 (Berkeley) 6/10/93 + * tcp_subr.c,v 1.5 1994/10/08 22:39:58 phk Exp + */ + +/* + * Changes and additions relating to SLiRP + * Copyright (c) 1995 Danny Gasparovski. + */ + +#include "slirp.h" + +/* patchable/settable parameters for tcp */ +/* Don't do rfc1323 performance enhancements */ +#define TCP_DO_RFC1323 0 + +/* + * Tcp initialization + */ +void tcp_init(Slirp *slirp) +{ + slirp->tcp_iss = 1; /* wrong */ + slirp->tcb.so_next = slirp->tcb.so_prev = &slirp->tcb; + slirp->tcp_last_so = &slirp->tcb; +} + +void tcp_cleanup(Slirp *slirp) +{ + while (slirp->tcb.so_next != &slirp->tcb) { + tcp_close(sototcpcb(slirp->tcb.so_next)); + } +} + +void tcp_template(struct tcpcb *tp) +{ + struct socket *so = tp->t_socket; + register struct tcpiphdr *n = &tp->t_template; + + n->ti_mbuf = NULL; + memset(&n->ti, 0, sizeof(n->ti)); + n->ti_x0 = 0; + switch (so->so_ffamily) { + case AF_INET: + n->ti_pr = IPPROTO_TCP; + n->ti_len = htons(sizeof(struct tcphdr)); + n->ti_src = so->so_faddr; + n->ti_dst = so->so_laddr; + n->ti_sport = so->so_fport; + n->ti_dport = so->so_lport; + break; + + case AF_INET6: + n->ti_nh6 = IPPROTO_TCP; + n->ti_len = htons(sizeof(struct tcphdr)); + n->ti_src6 = so->so_faddr6; + n->ti_dst6 = so->so_laddr6; + n->ti_sport = so->so_fport6; + n->ti_dport = so->so_lport6; + break; + + default: + g_assert_not_reached(); + } + + n->ti_seq = 0; + n->ti_ack = 0; + n->ti_x2 = 0; + n->ti_off = 5; + n->ti_flags = 0; + n->ti_win = 0; + n->ti_sum = 0; + n->ti_urp = 0; +} + +/* + * Send a single message to the TCP at address specified by + * the given TCP/IP header. If m == 0, then we make a copy + * of the tcpiphdr at ti and send directly to the addressed host. + * This is used to force keep alive messages out using the TCP + * template for a connection tp->t_template. If flags are given + * then we send a message back to the TCP which originated the + * segment ti, and discard the mbuf containing it and any other + * attached mbufs. + * + * In any case the ack and sequence number of the transmitted + * segment are as specified by the parameters. + */ +void tcp_respond(struct tcpcb *tp, struct tcpiphdr *ti, struct mbuf *m, + tcp_seq ack, tcp_seq seq, int flags, unsigned short af) +{ + register int tlen; + int win = 0; + + DEBUG_CALL("tcp_respond"); + DEBUG_ARG("tp = %p", tp); + DEBUG_ARG("ti = %p", ti); + DEBUG_ARG("m = %p", m); + DEBUG_ARG("ack = %u", ack); + DEBUG_ARG("seq = %u", seq); + DEBUG_ARG("flags = %x", flags); + + if (tp) + win = sbspace(&tp->t_socket->so_rcv); + if (m == NULL) { + if (!tp || (m = m_get(tp->t_socket->slirp)) == NULL) + return; + tlen = 0; + m->m_data += IF_MAXLINKHDR; + *mtod(m, struct tcpiphdr *) = *ti; + ti = mtod(m, struct tcpiphdr *); + switch (af) { + case AF_INET: + ti->ti.ti_i4.ih_x1 = 0; + break; + case AF_INET6: + ti->ti.ti_i6.ih_x1 = 0; + break; + default: + g_assert_not_reached(); + } + flags = TH_ACK; + } else { + /* + * ti points into m so the next line is just making + * the mbuf point to ti + */ + m->m_data = (char *)ti; + + m->m_len = sizeof(struct tcpiphdr); + tlen = 0; +#define xchg(a, b, type) \ + { \ + type t; \ + t = a; \ + a = b; \ + b = t; \ + } + switch (af) { + case AF_INET: + xchg(ti->ti_dst.s_addr, ti->ti_src.s_addr, uint32_t); + xchg(ti->ti_dport, ti->ti_sport, uint16_t); + break; + case AF_INET6: + xchg(ti->ti_dst6, ti->ti_src6, struct in6_addr); + xchg(ti->ti_dport, ti->ti_sport, uint16_t); + break; + default: + g_assert_not_reached(); + } +#undef xchg + } + ti->ti_len = htons((uint16_t)(sizeof(struct tcphdr) + tlen)); + tlen += sizeof(struct tcpiphdr); + m->m_len = tlen; + + ti->ti_mbuf = NULL; + ti->ti_x0 = 0; + ti->ti_seq = htonl(seq); + ti->ti_ack = htonl(ack); + ti->ti_x2 = 0; + ti->ti_off = sizeof(struct tcphdr) >> 2; + ti->ti_flags = flags; + if (tp) + ti->ti_win = htons((uint16_t)(win >> tp->rcv_scale)); + else + ti->ti_win = htons((uint16_t)win); + ti->ti_urp = 0; + ti->ti_sum = 0; + ti->ti_sum = cksum(m, tlen); + + struct tcpiphdr tcpiph_save = *(mtod(m, struct tcpiphdr *)); + struct ip *ip; + struct ip6 *ip6; + + switch (af) { + case AF_INET: + m->m_data += + sizeof(struct tcpiphdr) - sizeof(struct tcphdr) - sizeof(struct ip); + m->m_len -= + sizeof(struct tcpiphdr) - sizeof(struct tcphdr) - sizeof(struct ip); + ip = mtod(m, struct ip *); + ip->ip_len = m->m_len; + ip->ip_dst = tcpiph_save.ti_dst; + ip->ip_src = tcpiph_save.ti_src; + ip->ip_p = tcpiph_save.ti_pr; + + if (flags & TH_RST) { + ip->ip_ttl = MAXTTL; + } else { + ip->ip_ttl = IPDEFTTL; + } + + ip_output(NULL, m); + break; + + case AF_INET6: + m->m_data += sizeof(struct tcpiphdr) - sizeof(struct tcphdr) - + sizeof(struct ip6); + m->m_len -= sizeof(struct tcpiphdr) - sizeof(struct tcphdr) - + sizeof(struct ip6); + ip6 = mtod(m, struct ip6 *); + ip6->ip_pl = tcpiph_save.ti_len; + ip6->ip_dst = tcpiph_save.ti_dst6; + ip6->ip_src = tcpiph_save.ti_src6; + ip6->ip_nh = tcpiph_save.ti_nh6; + + ip6_output(NULL, m, 0); + break; + + default: + g_assert_not_reached(); + } +} + +struct tcpcb *tcp_newtcpcb(struct socket *so) +{ + register struct tcpcb *tp; + + tp = g_new0(struct tcpcb, 1); + tp->seg_next = tp->seg_prev = (struct tcpiphdr *)tp; + /* + * 40: length of IPv4 header (20) + TCP header (20) + * 60: length of IPv6 header (40) + TCP header (20) + */ + tp->t_maxseg = + MIN(so->slirp->if_mtu - ((so->so_ffamily == AF_INET) ? 40 : 60), + TCP_MAXSEG_MAX); + + tp->t_flags = TCP_DO_RFC1323 ? (TF_REQ_SCALE | TF_REQ_TSTMP) : 0; + tp->t_socket = so; + + /* + * Init srtt to TCPTV_SRTTBASE (0), so we can tell that we have no + * rtt estimate. Set rttvar so that srtt + 2 * rttvar gives + * reasonable initial retransmit time. + */ + tp->t_srtt = TCPTV_SRTTBASE; + tp->t_rttvar = TCPTV_SRTTDFLT << 2; + tp->t_rttmin = TCPTV_MIN; + + TCPT_RANGESET(tp->t_rxtcur, + ((TCPTV_SRTTBASE >> 2) + (TCPTV_SRTTDFLT << 2)) >> 1, + TCPTV_MIN, TCPTV_REXMTMAX); + + tp->snd_cwnd = TCP_MAXWIN << TCP_MAX_WINSHIFT; + tp->snd_ssthresh = TCP_MAXWIN << TCP_MAX_WINSHIFT; + tp->t_state = TCPS_CLOSED; + + so->so_tcpcb = tp; + + return (tp); +} + +struct tcpcb *tcp_drop(struct tcpcb *tp, int err) +{ + DEBUG_CALL("tcp_drop"); + DEBUG_ARG("tp = %p", tp); + DEBUG_ARG("errno = %d", errno); + + if (TCPS_HAVERCVDSYN(tp->t_state)) { + tp->t_state = TCPS_CLOSED; + tcp_output(tp); + } + return (tcp_close(tp)); +} + +struct tcpcb *tcp_close(struct tcpcb *tp) +{ + register struct tcpiphdr *t; + struct socket *so = tp->t_socket; + Slirp *slirp = so->slirp; + register struct mbuf *m; + + DEBUG_CALL("tcp_close"); + DEBUG_ARG("tp = %p", tp); + + /* free the reassembly queue, if any */ + t = tcpfrag_list_first(tp); + while (!tcpfrag_list_end(t, tp)) { + t = tcpiphdr_next(t); + m = tcpiphdr_prev(t)->ti_mbuf; + slirp_remque(tcpiphdr2qlink(tcpiphdr_prev(t))); + m_free(m); + } + g_free(tp); + so->so_tcpcb = NULL; + /* clobber input socket cache if we're closing the cached connection */ + if (so == slirp->tcp_last_so) + slirp->tcp_last_so = &slirp->tcb; + so->slirp->cb->unregister_poll_fd(so->s, so->slirp->opaque); + closesocket(so->s); + sbfree(&so->so_rcv); + sbfree(&so->so_snd); + sofree(so); + return ((struct tcpcb *)0); +} + +/* + * TCP protocol interface to socket abstraction. + */ + +/* + * User issued close, and wish to trail through shutdown states: + * if never received SYN, just forget it. If got a SYN from peer, + * but haven't sent FIN, then go to FIN_WAIT_1 state to send peer a FIN. + * If already got a FIN from peer, then almost done; go to LAST_ACK + * state. In all other cases, have already sent FIN to peer (e.g. + * after PRU_SHUTDOWN), and just have to play tedious game waiting + * for peer to send FIN or not respond to keep-alives, etc. + * We can let the user exit from the close as soon as the FIN is acked. + */ +void tcp_sockclosed(struct tcpcb *tp) +{ + DEBUG_CALL("tcp_sockclosed"); + DEBUG_ARG("tp = %p", tp); + + if (!tp) { + return; + } + + switch (tp->t_state) { + case TCPS_CLOSED: + case TCPS_LISTEN: + case TCPS_SYN_SENT: + tp->t_state = TCPS_CLOSED; + tcp_close(tp); + return; + + case TCPS_SYN_RECEIVED: + case TCPS_ESTABLISHED: + tp->t_state = TCPS_FIN_WAIT_1; + break; + + case TCPS_CLOSE_WAIT: + tp->t_state = TCPS_LAST_ACK; + break; + } + tcp_output(tp); +} + +/* + * Only do a connect, the tcp fields will be set in tcp_input + * return 0 if there's a result of the connect, + * else return -1 means we're still connecting + * The return value is almost always -1 since the socket is + * nonblocking. Connect returns after the SYN is sent, and does + * not wait for ACK+SYN. + */ +int tcp_fconnect(struct socket *so, unsigned short af) +{ + int ret = 0; + + DEBUG_CALL("tcp_fconnect"); + DEBUG_ARG("so = %p", so); + + ret = so->s = slirp_socket(af, SOCK_STREAM, 0); + if (ret >= 0) { + ret = slirp_bind_outbound(so, af); + if (ret < 0) { + // bind failed - close socket + closesocket(so->s); + so->s = -1; + return (ret); + } + } + + if (ret >= 0) { + int opt, s = so->s; + struct sockaddr_storage addr; + + slirp_set_nonblock(s); + so->slirp->cb->register_poll_fd(s, so->slirp->opaque); + slirp_socket_set_fast_reuse(s); + opt = 1; + setsockopt(s, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(opt)); + opt = 1; + setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); + + addr = so->fhost.ss; + DEBUG_CALL(" connect()ing"); + if (sotranslate_out(so, &addr) < 0) { + return -1; + } + + /* We don't care what port we get */ + ret = connect(s, (struct sockaddr *)&addr, sockaddr_size(&addr)); + + /* + * If it's not in progress, it failed, so we just return 0, + * without clearing SS_NOFDREF + */ + soisfconnecting(so); + } + + return (ret); +} + +/* + * We have a problem. The correct thing to do would be + * to first connect to the local-host, and only if the + * connection is accepted, then do an accept() here. + * But, a) we need to know who's trying to connect + * to the socket to be able to SYN the local-host, and + * b) we are already connected to the foreign host by + * the time it gets to accept(), so... We simply accept + * here and SYN the local-host. + */ +void tcp_connect(struct socket *inso) +{ + Slirp *slirp = inso->slirp; + struct socket *so; + struct sockaddr_storage addr; + socklen_t addrlen; + struct tcpcb *tp; + int s, opt, ret; + /* AF_INET6 addresses are bigger than AF_INET, so this is big enough. */ + char addrstr[INET6_ADDRSTRLEN]; + char portstr[6]; + + DEBUG_CALL("tcp_connect"); + DEBUG_ARG("inso = %p", inso); + switch (inso->lhost.ss.ss_family) { + case AF_INET: + addrlen = sizeof(struct sockaddr_in); + break; + case AF_INET6: + addrlen = sizeof(struct sockaddr_in6); + break; + default: + g_assert_not_reached(); + } + ret = getnameinfo((const struct sockaddr *) &inso->lhost.ss, addrlen, addrstr, sizeof(addrstr), portstr, sizeof(portstr), NI_NUMERICHOST|NI_NUMERICSERV); + g_assert(ret == 0); + DEBUG_ARG("ip = [%s]:%s", addrstr, portstr); + DEBUG_ARG("so_state = 0x%x", inso->so_state); + + /* Perform lazy guest IP address resolution if needed. */ + if (inso->so_state & SS_HOSTFWD) { + /* + * We can only reject the connection request by accepting it and + * then immediately closing it. Note that SS_FACCEPTONCE sockets can't + * get here. + */ + if (soassign_guest_addr_if_needed(inso) < 0) { + /* + * Guest address isn't available yet. We could either try to defer + * completing this connection request until the guest address is + * available, or punt. It's easier to punt. Otherwise we need to + * complicate the mechanism by which we're called to defer calling + * us again until the guest address is available. + */ + DEBUG_MISC(" guest address not available yet"); + addrlen = sizeof(addr); + s = accept(inso->s, (struct sockaddr *)&addr, &addrlen); + if (s >= 0) { + close(s); + } + return; + } + } + + /* + * If it's an SS_ACCEPTONCE socket, no need to socreate() + * another socket, just use the accept() socket. + */ + if (inso->so_state & SS_FACCEPTONCE) { + /* FACCEPTONCE already have a tcpcb */ + so = inso; + } else { + so = socreate(slirp, IPPROTO_TCP); + tcp_attach(so); + so->lhost = inso->lhost; + so->so_ffamily = inso->so_ffamily; + } + + tcp_mss(sototcpcb(so), 0); + + addrlen = sizeof(addr); + s = accept(inso->s, (struct sockaddr *)&addr, &addrlen); + if (s < 0) { + tcp_close(sototcpcb(so)); /* This will sofree() as well */ + return; + } + slirp_set_nonblock(s); + so->slirp->cb->register_poll_fd(s, so->slirp->opaque); + slirp_socket_set_fast_reuse(s); + opt = 1; + setsockopt(s, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(int)); + slirp_socket_set_nodelay(s); + + so->fhost.ss = addr; + sotranslate_accept(so); + + /* Close the accept() socket, set right state */ + if (inso->so_state & SS_FACCEPTONCE) { + /* If we only accept once, close the accept() socket */ + so->slirp->cb->unregister_poll_fd(so->s, so->slirp->opaque); + closesocket(so->s); + + /* Don't select it yet, even though we have an FD */ + /* if it's not FACCEPTONCE, it's already NOFDREF */ + so->so_state = SS_NOFDREF; + } + so->s = s; + so->so_state |= SS_INCOMING; + + so->so_iptos = tcp_tos(so); + tp = sototcpcb(so); + + tcp_template(tp); + + tp->t_state = TCPS_SYN_SENT; + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT; + tp->iss = slirp->tcp_iss; + slirp->tcp_iss += TCP_ISSINCR / 2; + tcp_sendseqinit(tp); + tcp_output(tp); +} + +void tcp_attach(struct socket *so) +{ + so->so_tcpcb = tcp_newtcpcb(so); + slirp_insque(so, &so->slirp->tcb); +} + +/* + * Set the socket's type of service field + */ +static const struct tos_t tcptos[] = { + { 0, 20, IPTOS_THROUGHPUT, 0 }, /* ftp data */ + { 21, 21, IPTOS_LOWDELAY, EMU_FTP }, /* ftp control */ + { 0, 23, IPTOS_LOWDELAY, 0 }, /* telnet */ + { 0, 80, IPTOS_THROUGHPUT, 0 }, /* WWW */ + { 0, 513, IPTOS_LOWDELAY, EMU_RLOGIN | EMU_NOCONNECT }, /* rlogin */ + { 0, 544, IPTOS_LOWDELAY, EMU_KSH }, /* kshell */ + { 0, 543, IPTOS_LOWDELAY, 0 }, /* klogin */ + { 0, 6667, IPTOS_THROUGHPUT, EMU_IRC }, /* IRC */ + { 0, 6668, IPTOS_THROUGHPUT, EMU_IRC }, /* IRC undernet */ + { 0, 7070, IPTOS_LOWDELAY, EMU_REALAUDIO }, /* RealAudio control */ + { 0, 113, IPTOS_LOWDELAY, EMU_IDENT }, /* identd protocol */ + { 0, 0, 0, 0 } +}; + +uint8_t tcp_tos(struct socket *so) +{ + int i = 0; + + while (tcptos[i].tos) { + if ((tcptos[i].fport && (ntohs(so->so_fport) == tcptos[i].fport)) || + (tcptos[i].lport && (ntohs(so->so_lport) == tcptos[i].lport))) { + if (so->slirp->enable_emu) + so->so_emu = tcptos[i].emu; + return tcptos[i].tos; + } + i++; + } + return 0; +} + +/* + * NOTE: It's possible to crash SLiRP by sending it + * unstandard strings to emulate... if this is a problem, + * more checks are needed here + * + * XXX Assumes the whole command came in one packet + * XXX If there is more than one command in the packet, the others may + * be truncated. + * XXX If the command is too long, it may be truncated. + * + * XXX Some ftp clients will have their TOS set to + * LOWDELAY and so Nagel will kick in. Because of this, + * we'll get the first letter, followed by the rest, so + * we simply scan for ORT instead of PORT... + * DCC doesn't have this problem because there's other stuff + * in the packet before the DCC command. + * + * Return 1 if the mbuf m is still valid and should be + * sbappend()ed + * + * NOTE: if you return 0 you MUST m_free() the mbuf! + */ +int tcp_emu(struct socket *so, struct mbuf *m) +{ + Slirp *slirp = so->slirp; + unsigned n1, n2, n3, n4, n5, n6; + char buff[257]; + uint32_t laddr; + unsigned lport; + char *bptr; + + DEBUG_CALL("tcp_emu"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("m = %p", m); + + switch (so->so_emu) { + int x, i; + + /* TODO: IPv6 */ + case EMU_IDENT: + /* + * Identification protocol as per rfc-1413 + */ + + { + struct socket *tmpso; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(struct sockaddr_in); + char *eol = g_strstr_len(m->m_data, m->m_len, "\r\n"); + + if (!eol) { + return 1; + } + + *eol = '\0'; + if (sscanf(m->m_data, "%u%*[ ,]%u", &n1, &n2) == 2) { + HTONS(n1); + HTONS(n2); + /* n2 is the one on our host */ + for (tmpso = slirp->tcb.so_next; tmpso != &slirp->tcb; + tmpso = tmpso->so_next) { + if (tmpso->so_laddr.s_addr == so->so_laddr.s_addr && + tmpso->so_lport == n2 && + tmpso->so_faddr.s_addr == so->so_faddr.s_addr && + tmpso->so_fport == n1) { + if (getsockname(tmpso->s, (struct sockaddr *)&addr, + &addrlen) == 0) + n2 = addr.sin_port; + break; + } + } + NTOHS(n1); + NTOHS(n2); + m_inc(m, g_snprintf(NULL, 0, "%d,%d\r\n", n1, n2) + 1); + m->m_len = slirp_fmt(m->m_data, M_ROOM(m), "%d,%d\r\n", n1, n2); + } else { + *eol = '\r'; + } + + return 1; + } + + case EMU_FTP: /* ftp */ + m_inc(m, m->m_len + 1); + *(m->m_data + m->m_len) = 0; /* NUL terminate for strstr */ + if ((bptr = (char *)strstr(m->m_data, "ORT")) != NULL) { + /* + * Need to emulate the PORT command + */ + x = sscanf(bptr, "ORT %u,%u,%u,%u,%u,%u\r\n%256[^\177]", &n1, &n2, + &n3, &n4, &n5, &n6, buff); + if (x < 6) + return 1; + + laddr = htonl((n1 << 24) | (n2 << 16) | (n3 << 8) | (n4)); + lport = htons((n5 << 8) | (n6)); + + if ((so = tcp_listen(slirp, INADDR_ANY, 0, laddr, lport, + SS_FACCEPTONCE)) == NULL) { + return 1; + } + n6 = ntohs(so->so_fport); + + n5 = (n6 >> 8) & 0xff; + n6 &= 0xff; + + laddr = ntohl(so->so_faddr.s_addr); + + n1 = ((laddr >> 24) & 0xff); + n2 = ((laddr >> 16) & 0xff); + n3 = ((laddr >> 8) & 0xff); + n4 = (laddr & 0xff); + + m->m_len = bptr - m->m_data; /* Adjust length */ + m->m_len += slirp_fmt(bptr, M_FREEROOM(m), + "ORT %d,%d,%d,%d,%d,%d\r\n%s", + n1, n2, n3, n4, n5, n6, x == 7 ? buff : ""); + return 1; + } else if ((bptr = (char *)strstr(m->m_data, "27 Entering")) != NULL) { + /* + * Need to emulate the PASV response + */ + x = sscanf( + bptr, + "27 Entering Passive Mode (%u,%u,%u,%u,%u,%u)\r\n%256[^\177]", + &n1, &n2, &n3, &n4, &n5, &n6, buff); + if (x < 6) + return 1; + + laddr = htonl((n1 << 24) | (n2 << 16) | (n3 << 8) | (n4)); + lport = htons((n5 << 8) | (n6)); + + if ((so = tcp_listen(slirp, INADDR_ANY, 0, laddr, lport, + SS_FACCEPTONCE)) == NULL) { + return 1; + } + n6 = ntohs(so->so_fport); + + n5 = (n6 >> 8) & 0xff; + n6 &= 0xff; + + laddr = ntohl(so->so_faddr.s_addr); + + n1 = ((laddr >> 24) & 0xff); + n2 = ((laddr >> 16) & 0xff); + n3 = ((laddr >> 8) & 0xff); + n4 = (laddr & 0xff); + + m->m_len = bptr - m->m_data; /* Adjust length */ + m->m_len += slirp_fmt(bptr, M_FREEROOM(m), + "27 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n%s", + n1, n2, n3, n4, n5, n6, x == 7 ? buff : ""); + return 1; + } + + return 1; + + case EMU_KSH: + /* + * The kshell (Kerberos rsh) and shell services both pass + * a local port port number to carry signals to the server + * and stderr to the client. It is passed at the beginning + * of the connection as a NUL-terminated decimal ASCII string. + */ + so->so_emu = 0; + for (lport = 0, i = 0; i < m->m_len - 1; ++i) { + if (m->m_data[i] < '0' || m->m_data[i] > '9') + return 1; /* invalid number */ + lport *= 10; + lport += m->m_data[i] - '0'; + } + if (m->m_data[m->m_len - 1] == '\0' && lport != 0 && + (so = tcp_listen(slirp, INADDR_ANY, 0, so->so_laddr.s_addr, + htons(lport), SS_FACCEPTONCE)) != NULL) + m->m_len = slirp_fmt0(m->m_data, M_ROOM(m), + "%d", ntohs(so->so_fport)); + return 1; + + case EMU_IRC: + /* + * Need to emulate DCC CHAT, DCC SEND and DCC MOVE + */ + m_inc(m, m->m_len + 1); + *(m->m_data + m->m_len) = 0; /* NULL terminate the string for strstr */ + if ((bptr = (char *)strstr(m->m_data, "DCC")) == NULL) + return 1; + + /* The %256s is for the broken mIRC */ + if (sscanf(bptr, "DCC CHAT %256s %u %u", buff, &laddr, &lport) == 3) { + if ((so = tcp_listen(slirp, INADDR_ANY, 0, htonl(laddr), + htons(lport), SS_FACCEPTONCE)) == NULL) { + return 1; + } + m->m_len = bptr - m->m_data; /* Adjust length */ + m->m_len += slirp_fmt(bptr, M_FREEROOM(m), + "DCC CHAT chat %lu %u%c\n", + (unsigned long)ntohl(so->so_faddr.s_addr), + ntohs(so->so_fport), 1); + } else if (sscanf(bptr, "DCC SEND %256s %u %u %u", buff, &laddr, &lport, + &n1) == 4) { + if ((so = tcp_listen(slirp, INADDR_ANY, 0, htonl(laddr), + htons(lport), SS_FACCEPTONCE)) == NULL) { + return 1; + } + m->m_len = bptr - m->m_data; /* Adjust length */ + m->m_len += slirp_fmt(bptr, M_FREEROOM(m), + "DCC SEND %s %lu %u %u%c\n", buff, + (unsigned long)ntohl(so->so_faddr.s_addr), + ntohs(so->so_fport), n1, 1); + } else if (sscanf(bptr, "DCC MOVE %256s %u %u %u", buff, &laddr, &lport, + &n1) == 4) { + if ((so = tcp_listen(slirp, INADDR_ANY, 0, htonl(laddr), + htons(lport), SS_FACCEPTONCE)) == NULL) { + return 1; + } + m->m_len = bptr - m->m_data; /* Adjust length */ + m->m_len += slirp_fmt(bptr, M_FREEROOM(m), + "DCC MOVE %s %lu %u %u%c\n", buff, + (unsigned long)ntohl(so->so_faddr.s_addr), + ntohs(so->so_fport), n1, 1); + } + return 1; + + case EMU_REALAUDIO: + /* + * RealAudio emulation - JP. We must try to parse the incoming + * data and try to find the two characters that contain the + * port number. Then we redirect an udp port and replace the + * number with the real port we got. + * + * The 1.0 beta versions of the player are not supported + * any more. + * + * A typical packet for player version 1.0 (release version): + * + * 0000:50 4E 41 00 05 + * 0000:00 01 00 02 1B D7 00 00 67 E6 6C DC 63 00 12 50 ........g.l.c..P + * 0010:4E 43 4C 49 45 4E 54 20 31 30 31 20 41 4C 50 48 NCLIENT 101 ALPH + * 0020:41 6C 00 00 52 00 17 72 61 66 69 6C 65 73 2F 76 Al..R..rafiles/v + * 0030:6F 61 2F 65 6E 67 6C 69 73 68 5F 2E 72 61 79 42 oa/english_.rayB + * + * Now the port number 0x1BD7 is found at offset 0x04 of the + * Now the port number 0x1BD7 is found at offset 0x04 of the + * second packet. This time we received five bytes first and + * then the rest. You never know how many bytes you get. + * + * A typical packet for player version 2.0 (beta): + * + * 0000:50 4E 41 00 06 00 02 00 00 00 01 00 02 1B C1 00 PNA............. + * 0010:00 67 75 78 F5 63 00 0A 57 69 6E 32 2E 30 2E 30 .gux.c..Win2.0.0 + * 0020:2E 35 6C 00 00 52 00 1C 72 61 66 69 6C 65 73 2F .5l..R..rafiles/ + * 0030:77 65 62 73 69 74 65 2F 32 30 72 65 6C 65 61 73 website/20releas + * 0040:65 2E 72 61 79 53 00 00 06 36 42 e.rayS...6B + * + * Port number 0x1BC1 is found at offset 0x0d. + * + * This is just a horrible switch statement. Variable ra tells + * us where we're going. + */ + + bptr = m->m_data; + while (bptr < m->m_data + m->m_len) { + uint16_t p; + static int ra = 0; + char ra_tbl[4]; + + ra_tbl[0] = 0x50; + ra_tbl[1] = 0x4e; + ra_tbl[2] = 0x41; + ra_tbl[3] = 0; + + switch (ra) { + case 0: + case 2: + case 3: + if (*bptr++ != ra_tbl[ra]) { + ra = 0; + continue; + } + break; + + case 1: + /* + * We may get 0x50 several times, ignore them + */ + if (*bptr == 0x50) { + ra = 1; + bptr++; + continue; + } else if (*bptr++ != ra_tbl[ra]) { + ra = 0; + continue; + } + break; + + case 4: + /* + * skip version number + */ + bptr++; + break; + + case 5: + if (bptr == m->m_data + m->m_len - 1) + return 1; /* We need two bytes */ + + /* + * The difference between versions 1.0 and + * 2.0 is here. For future versions of + * the player this may need to be modified. + */ + if (*(bptr + 1) == 0x02) + bptr += 8; + else + bptr += 4; + break; + + case 6: + /* This is the field containing the port + * number that RA-player is listening to. + */ + + if (bptr == m->m_data + m->m_len - 1) + return 1; /* We need two bytes */ + + lport = (((uint8_t *)bptr)[0] << 8) + ((uint8_t *)bptr)[1]; + if (lport < 6970) + lport += 256; /* don't know why */ + if (lport < 6970 || lport > 7170) + return 1; /* failed */ + + /* try to get udp port between 6970 - 7170 */ + for (p = 6970; p < 7071; p++) { + if (udp_listen(slirp, INADDR_ANY, htons(p), + so->so_laddr.s_addr, htons(lport), + SS_FACCEPTONCE)) { + break; + } + } + if (p == 7071) + p = 0; + *(uint8_t *)bptr++ = (p >> 8) & 0xff; + *(uint8_t *)bptr = p & 0xff; + ra = 0; + return 1; /* port redirected, we're done */ + break; + + default: + ra = 0; + } + ra++; + } + return 1; + + default: + /* Ooops, not emulated, won't call tcp_emu again */ + so->so_emu = 0; + return 1; + } +} + +/* + * Do misc. config of SLiRP while its running. + * Return 0 if this connections is to be closed, 1 otherwise, + * return 2 if this is a command-line connection + */ +int tcp_ctl(struct socket *so) +{ + Slirp *slirp = so->slirp; + struct sbuf *sb = &so->so_snd; + struct gfwd_list *ex_ptr; + + DEBUG_CALL("tcp_ctl"); + DEBUG_ARG("so = %p", so); + + /* TODO: IPv6 */ + if (so->so_faddr.s_addr != slirp->vhost_addr.s_addr) { + /* Check if it's pty_exec */ + for (ex_ptr = slirp->guestfwd_list; ex_ptr; ex_ptr = ex_ptr->ex_next) { + if (ex_ptr->ex_fport == so->so_fport && + so->so_faddr.s_addr == ex_ptr->ex_addr.s_addr) { + if (ex_ptr->write_cb) { + so->s = -1; + so->guestfwd = ex_ptr; + return 1; + } + DEBUG_MISC(" executing %s", ex_ptr->ex_exec); + if (ex_ptr->ex_unix) + return open_unix(so, ex_ptr->ex_unix); + else + return fork_exec(so, ex_ptr->ex_exec); + } + } + } + sb->sb_cc = slirp_fmt(sb->sb_wptr, sb->sb_datalen - (sb->sb_wptr - sb->sb_data), + "Error: No application configured.\r\n"); + sb->sb_wptr += sb->sb_cc; + return 0; +} diff --git a/src/net/libslirp/src/tcp_timer.c b/src/net/libslirp/src/tcp_timer.c new file mode 100644 index 00000000..aeb610fb --- /dev/null +++ b/src/net/libslirp/src/tcp_timer.c @@ -0,0 +1,283 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_timer.c 8.1 (Berkeley) 6/10/93 + * tcp_timer.c,v 1.2 1994/08/02 07:49:10 davidg Exp + */ + +#include "slirp.h" + +static struct tcpcb *tcp_timers(register struct tcpcb *tp, int timer); + +/* + * Fast timeout routine for processing delayed acks + */ +void tcp_fasttimo(Slirp *slirp) +{ + register struct socket *so; + register struct tcpcb *tp; + + DEBUG_CALL("tcp_fasttimo"); + + so = slirp->tcb.so_next; + if (so) + for (; so != &slirp->tcb; so = so->so_next) + if ((tp = (struct tcpcb *)so->so_tcpcb) && + (tp->t_flags & TF_DELACK)) { + tp->t_flags &= ~TF_DELACK; + tp->t_flags |= TF_ACKNOW; + tcp_output(tp); + } +} + +/* + * Tcp protocol timeout routine called every 500 ms. + * Updates the timers in all active tcb's and + * causes finite state machine actions if timers expire. + */ +void tcp_slowtimo(Slirp *slirp) +{ + register struct socket *ip, *ipnxt; + register struct tcpcb *tp; + register int i; + + DEBUG_CALL("tcp_slowtimo"); + + /* + * Search through tcb's and update active timers. + */ + ip = slirp->tcb.so_next; + if (ip == NULL) { + return; + } + for (; ip != &slirp->tcb; ip = ipnxt) { + ipnxt = ip->so_next; + tp = sototcpcb(ip); + if (tp == NULL) { + continue; + } + for (i = 0; i < TCPT_NTIMERS; i++) { + if (tp->t_timer[i] && --tp->t_timer[i] == 0) { + tcp_timers(tp, i); + if (ipnxt->so_prev != ip) + goto tpgone; + } + } + tp->t_idle++; + if (tp->t_rtt) + tp->t_rtt++; + tpgone:; + } + slirp->tcp_iss += TCP_ISSINCR / PR_SLOWHZ; /* increment iss */ + slirp->tcp_now++; /* for timestamps */ +} + +void tcp_canceltimers(struct tcpcb *tp) +{ + register int i; + + for (i = 0; i < TCPT_NTIMERS; i++) + tp->t_timer[i] = 0; +} + +const int tcp_backoff[TCP_MAXRXTSHIFT + 1] = { 1, 2, 4, 8, 16, 32, 64, + 64, 64, 64, 64, 64, 64 }; + +/* + * TCP timer processing. + */ +static struct tcpcb *tcp_timers(register struct tcpcb *tp, int timer) +{ + register int rexmt; + + DEBUG_CALL("tcp_timers"); + + switch (timer) { + /* + * 2 MSL timeout in shutdown went off. If we're closed but + * still waiting for peer to close and connection has been idle + * too long, or if 2MSL time is up from TIME_WAIT, delete connection + * control block. Otherwise, check again in a bit. + */ + case TCPT_2MSL: + if (tp->t_state != TCPS_TIME_WAIT && tp->t_idle <= TCP_MAXIDLE) + tp->t_timer[TCPT_2MSL] = TCPTV_KEEPINTVL; + else + tp = tcp_close(tp); + break; + + /* + * Retransmission timer went off. Message has not + * been acked within retransmit interval. Back off + * to a longer retransmit interval and retransmit one segment. + */ + case TCPT_REXMT: + + /* + * XXXXX If a packet has timed out, then remove all the queued + * packets for that session. + */ + + if (++tp->t_rxtshift > TCP_MAXRXTSHIFT) { + /* + * This is a hack to suit our terminal server here at the uni of + * canberra since they have trouble with zeroes... It usually lets + * them through unharmed, but under some conditions, it'll eat the + * zeros. If we keep retransmitting it, it'll keep eating the + * zeroes, so we keep retransmitting, and eventually the connection + * dies... (this only happens on incoming data) + * + * So, if we were gonna drop the connection from too many + * retransmits, don't... instead halve the t_maxseg, which might + * break up the NULLs and let them through + * + * *sigh* + */ + + tp->t_maxseg >>= 1; + if (tp->t_maxseg < 32) { + /* + * We tried our best, now the connection must die! + */ + tp->t_rxtshift = TCP_MAXRXTSHIFT; + tp = tcp_drop(tp, tp->t_softerror); + /* tp->t_softerror : ETIMEDOUT); */ /* XXX */ + return (tp); /* XXX */ + } + + /* + * Set rxtshift to 6, which is still at the maximum + * backoff time + */ + tp->t_rxtshift = 6; + } + rexmt = TCP_REXMTVAL(tp) * tcp_backoff[tp->t_rxtshift]; + TCPT_RANGESET(tp->t_rxtcur, rexmt, (short)tp->t_rttmin, + TCPTV_REXMTMAX); /* XXX */ + tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; + /* + * If losing, let the lower level know and try for + * a better route. Also, if we backed off this far, + * our srtt estimate is probably bogus. Clobber it + * so we'll take the next rtt measurement as our srtt; + * move the current srtt into rttvar to keep the current + * retransmit times until then. + */ + if (tp->t_rxtshift > TCP_MAXRXTSHIFT / 4) { + tp->t_rttvar += (tp->t_srtt >> TCP_RTT_SHIFT); + tp->t_srtt = 0; + } + tp->snd_nxt = tp->snd_una; + /* + * If timing a segment in this window, stop the timer. + */ + tp->t_rtt = 0; + /* + * Close the congestion window down to one segment + * (we'll open it by one segment for each ack we get). + * Since we probably have a window's worth of unacked + * data accumulated, this "slow start" keeps us from + * dumping all that data as back-to-back packets (which + * might overwhelm an intermediate gateway). + * + * There are two phases to the opening: Initially we + * open by one mss on each ack. This makes the window + * size increase exponentially with time. If the + * window is larger than the path can handle, this + * exponential growth results in dropped packet(s) + * almost immediately. To get more time between + * drops but still "push" the network to take advantage + * of improving conditions, we switch from exponential + * to linear window opening at some threshold size. + * For a threshold, we use half the current window + * size, truncated to a multiple of the mss. + * + * (the minimum cwnd that will give us exponential + * growth is 2 mss. We don't allow the threshold + * to go below this.) + */ + { + unsigned win = MIN(tp->snd_wnd, tp->snd_cwnd) / 2 / tp->t_maxseg; + if (win < 2) + win = 2; + tp->snd_cwnd = tp->t_maxseg; + tp->snd_ssthresh = win * tp->t_maxseg; + tp->t_dupacks = 0; + } + tcp_output(tp); + break; + + /* + * Persistence timer into zero window. + * Force a byte to be output, if possible. + */ + case TCPT_PERSIST: + tcp_setpersist(tp); + tp->t_force = 1; + tcp_output(tp); + tp->t_force = 0; + break; + + /* + * Keep-alive timer went off; send something + * or drop connection if idle for too long. + */ + case TCPT_KEEP: + if (tp->t_state < TCPS_ESTABLISHED) + goto dropit; + + if (slirp_do_keepalive && tp->t_state <= TCPS_CLOSE_WAIT) { + if (tp->t_idle >= TCPTV_KEEP_IDLE + TCP_MAXIDLE) + goto dropit; + /* + * Send a packet designed to force a response + * if the peer is up and reachable: + * either an ACK if the connection is still alive, + * or an RST if the peer has closed the connection + * due to timeout or reboot. + * Using sequence number tp->snd_una-1 + * causes the transmitted zero-length segment + * to lie outside the receive window; + * by the protocol spec, this requires the + * correspondent TCP to respond. + */ + tcp_respond(tp, &tp->t_template, (struct mbuf *)NULL, tp->rcv_nxt, + tp->snd_una - 1, 0, tp->t_socket->so_ffamily); + tp->t_timer[TCPT_KEEP] = TCPTV_KEEPINTVL; + } else + tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_IDLE; + break; + + dropit: + tp = tcp_drop(tp, 0); + break; + } + + return (tp); +} diff --git a/src/net/libslirp/src/tcp_timer.h b/src/net/libslirp/src/tcp_timer.h new file mode 100644 index 00000000..3a2e9448 --- /dev/null +++ b/src/net/libslirp/src/tcp_timer.h @@ -0,0 +1,133 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_timer.h 8.1 (Berkeley) 6/10/93 + * tcp_timer.h,v 1.4 1994/08/21 05:27:38 paul Exp + */ + +#ifndef TCP_TIMER_H +#define TCP_TIMER_H + +/* + * Definitions of the TCP timers. These timers are counted + * down PR_SLOWHZ times a second. + */ +#define TCPT_NTIMERS 4 + +#define TCPT_REXMT 0 /* retransmit */ +#define TCPT_PERSIST 1 /* retransmit persistence */ +#define TCPT_KEEP 2 /* keep alive */ +#define TCPT_2MSL 3 /* 2*msl quiet time timer */ + +/* + * The TCPT_REXMT timer is used to force retransmissions. + * The TCP has the TCPT_REXMT timer set whenever segments + * have been sent for which ACKs are expected but not yet + * received. If an ACK is received which advances tp->snd_una, + * then the retransmit timer is cleared (if there are no more + * outstanding segments) or reset to the base value (if there + * are more ACKs expected). Whenever the retransmit timer goes off, + * we retransmit one unacknowledged segment, and do a backoff + * on the retransmit timer. + * + * The TCPT_PERSIST timer is used to keep window size information + * flowing even if the window goes shut. If all previous transmissions + * have been acknowledged (so that there are no retransmissions in progress), + * and the window is too small to bother sending anything, then we start + * the TCPT_PERSIST timer. When it expires, if the window is nonzero, + * we go to transmit state. Otherwise, at intervals send a single byte + * into the peer's window to force him to update our window information. + * We do this at most as often as TCPT_PERSMIN time intervals, + * but no more frequently than the current estimate of round-trip + * packet time. The TCPT_PERSIST timer is cleared whenever we receive + * a window update from the peer. + * + * The TCPT_KEEP timer is used to keep connections alive. If an + * connection is idle (no segments received) for TCPTV_KEEP_INIT amount of time, + * but not yet established, then we drop the connection. Once the connection + * is established, if the connection is idle for TCPTV_KEEP_IDLE time + * (and keepalives have been enabled on the socket), we begin to probe + * the connection. We force the peer to send us a segment by sending: + * + * This segment is (deliberately) outside the window, and should elicit + * an ack segment in response from the peer. If, despite the TCPT_KEEP + * initiated segments we cannot elicit a response from a peer in TCPT_MAXIDLE + * amount of time probing, then we drop the connection. + */ + +/* + * Time constants. + */ +#define TCPTV_MSL (5 * PR_SLOWHZ) /* max seg lifetime (hah!) */ + +#define TCPTV_SRTTBASE \ + 0 /* base roundtrip time; \ + if 0, no idea yet */ +#define TCPTV_SRTTDFLT (3 * PR_SLOWHZ) /* assumed RTT if no info */ + +#define TCPTV_PERSMIN (5 * PR_SLOWHZ) /* retransmit persistence */ +#define TCPTV_PERSMAX (60 * PR_SLOWHZ) /* maximum persist interval */ + +#define TCPTV_KEEP_INIT (75 * PR_SLOWHZ) /* initial connect keep alive */ +#define TCPTV_KEEP_IDLE (120 * 60 * PR_SLOWHZ) /* dflt time before probing */ +#define TCPTV_KEEPINTVL (75 * PR_SLOWHZ) /* default probe interval */ +#define TCPTV_KEEPCNT 8 /* max probes before drop */ + +#define TCPTV_MIN (1 * PR_SLOWHZ) /* minimum allowable value */ +#define TCPTV_REXMTMAX (12 * PR_SLOWHZ) /* max allowable REXMT value */ + +#define TCP_LINGERTIME 120 /* linger at most 2 minutes */ + +#define TCP_MAXRXTSHIFT 12 /* maximum retransmits */ + + +/* + * Force a time value to be in a certain range. + */ +#define TCPT_RANGESET(tv, value, tvmin, tvmax) \ + { \ + (tv) = (value); \ + if ((tv) < (tvmin)) \ + (tv) = (tvmin); \ + else if ((tv) > (tvmax)) \ + (tv) = (tvmax); \ + } + +extern const int tcp_backoff[]; + +struct tcpcb; + +/* Process fast time-outs */ +void tcp_fasttimo(Slirp *); +/* Process slow time-outs */ +void tcp_slowtimo(Slirp *); +/* Cancel all timers for TCP tp */ +void tcp_canceltimers(struct tcpcb *); + +#endif diff --git a/src/net/libslirp/src/tcp_var.h b/src/net/libslirp/src/tcp_var.h new file mode 100644 index 00000000..c8da8cbd --- /dev/null +++ b/src/net/libslirp/src/tcp_var.h @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcp_var.h 8.3 (Berkeley) 4/10/94 + * tcp_var.h,v 1.3 1994/08/21 05:27:39 paul Exp + */ + +#ifndef TCP_VAR_H +#define TCP_VAR_H + +#include "tcpip.h" +#include "tcp_timer.h" + +/* + * Tcp control block, one per tcp; fields: + */ +struct tcpcb { + struct tcpiphdr *seg_next; /* sequencing queue */ + struct tcpiphdr *seg_prev; + short t_state; /* state of this connection */ + short t_timer[TCPT_NTIMERS]; /* tcp timers */ + short t_rxtshift; /* log(2) of rexmt exp. backoff */ + short t_rxtcur; /* current retransmit value */ + short t_dupacks; /* consecutive dup acks recd */ + uint16_t t_maxseg; /* maximum segment size */ + uint8_t t_force; /* 1 if forcing out a byte */ + uint16_t t_flags; +#define TF_ACKNOW 0x0001 /* ack peer immediately */ +#define TF_DELACK 0x0002 /* ack, but try to delay it */ +#define TF_NODELAY 0x0004 /* don't delay packets to coalesce */ +#define TF_NOOPT 0x0008 /* don't use tcp options */ +#define TF_SENTFIN 0x0010 /* have sent FIN */ +#define TF_REQ_SCALE 0x0020 /* have/will request window scaling */ +#define TF_RCVD_SCALE 0x0040 /* other side has requested scaling */ +#define TF_REQ_TSTMP 0x0080 /* have/will request timestamps */ +#define TF_RCVD_TSTMP 0x0100 /* a timestamp was received in SYN */ +#define TF_SACK_PERMIT 0x0200 /* other side said I could SACK */ + + struct tcpiphdr t_template; /* static skeletal packet for xmit */ + + struct socket *t_socket; /* back pointer to socket */ + /* + * The following fields are used as in the protocol specification. + * See RFC783, Dec. 1981, page 21. + */ + /* send sequence variables */ + tcp_seq snd_una; /* send unacknowledged */ + tcp_seq snd_nxt; /* send next */ + tcp_seq snd_up; /* send urgent pointer */ + tcp_seq snd_wl1; /* window update seg seq number */ + tcp_seq snd_wl2; /* window update seg ack number */ + tcp_seq iss; /* initial send sequence number */ + uint32_t snd_wnd; /* send window */ + /* receive sequence variables */ + uint32_t rcv_wnd; /* receive window */ + tcp_seq rcv_nxt; /* receive next */ + tcp_seq rcv_up; /* receive urgent pointer */ + tcp_seq irs; /* initial receive sequence number */ + /* + * Additional variables for this implementation. + */ + /* receive variables */ + tcp_seq rcv_adv; /* advertised window */ + /* retransmit variables */ + tcp_seq snd_max; /* highest sequence number sent; + * used to recognize retransmits + */ + /* congestion control (for slow start, source quench, retransmit after loss) + */ + uint32_t snd_cwnd; /* congestion-controlled window */ + uint32_t snd_ssthresh; /* snd_cwnd size threshold for + * for slow start exponential to + * linear switch + */ + /* + * transmit timing stuff. See below for scale of srtt and rttvar. + * "Variance" is actually smoothed difference. + */ + short t_idle; /* inactivity time */ + short t_rtt; /* round trip time */ + tcp_seq t_rtseq; /* sequence number being timed */ + short t_srtt; /* smoothed round-trip time */ + short t_rttvar; /* variance in round-trip time */ + uint16_t t_rttmin; /* minimum rtt allowed */ + uint32_t max_sndwnd; /* largest window peer has offered */ + + /* out-of-band data */ + uint8_t t_oobflags; /* have some */ + uint8_t t_iobc; /* input character */ +#define TCPOOB_HAVEDATA 0x01 +#define TCPOOB_HADDATA 0x02 + short t_softerror; /* possible error not yet reported */ + + /* RFC 1323 variables */ + uint8_t snd_scale; /* window scaling for send window */ + uint8_t rcv_scale; /* window scaling for recv window */ + uint8_t request_r_scale; /* pending window scaling */ + uint8_t requested_s_scale; + uint32_t ts_recent; /* timestamp echo data */ + uint32_t ts_recent_age; /* when last updated */ + tcp_seq last_ack_sent; +}; + +#define sototcpcb(so) ((so)->so_tcpcb) + +/* + * The smoothed round-trip time and estimated variance + * are stored as fixed point numbers scaled by the values below. + * For convenience, these scales are also used in smoothing the average + * (smoothed = (1/scale)sample + ((scale-1)/scale)smoothed). + * With these scales, srtt has 3 bits to the right of the binary point, + * and thus an "ALPHA" of 0.875. rttvar has 2 bits to the right of the + * binary point, and is smoothed with an ALPHA of 0.75. + */ +#define TCP_RTT_SCALE 8 /* multiplier for srtt; 3 bits frac. */ +#define TCP_RTT_SHIFT 3 /* shift for srtt; 3 bits frac. */ +#define TCP_RTTVAR_SCALE 4 /* multiplier for rttvar; 2 bits */ +#define TCP_RTTVAR_SHIFT 2 /* multiplier for rttvar; 2 bits */ + +/* + * The initial retransmission should happen at rtt + 4 * rttvar. + * Because of the way we do the smoothing, srtt and rttvar + * will each average +1/2 tick of bias. When we compute + * the retransmit timer, we want 1/2 tick of rounding and + * 1 extra tick because of +-1/2 tick uncertainty in the + * firing of the timer. The bias will give us exactly the + * 1.5 tick we need. But, because the bias is + * statistical, we have to test that we don't drop below + * the minimum feasible timer (which is 2 ticks). + * This macro assumes that the value of TCP_RTTVAR_SCALE + * is the same as the multiplier for rttvar. + */ +#define TCP_REXMTVAL(tp) (((tp)->t_srtt >> TCP_RTT_SHIFT) + (tp)->t_rttvar) + +#endif diff --git a/src/net/libslirp/src/tcpip.h b/src/net/libslirp/src/tcpip.h new file mode 100644 index 00000000..e9c794bd --- /dev/null +++ b/src/net/libslirp/src/tcpip.h @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tcpip.h 8.1 (Berkeley) 6/10/93 + * tcpip.h,v 1.3 1994/08/21 05:27:40 paul Exp + */ + +#ifndef TCPIP_H +#define TCPIP_H + +/* + * Tcp+ip header, after ip options removed. + */ +struct tcpiphdr { + struct mbuf_ptr ih_mbuf; /* backpointer to mbuf */ + union { + struct { + struct in_addr ih_src; /* source internet address */ + struct in_addr ih_dst; /* destination internet address */ + uint8_t ih_x1; /* (unused) */ + uint8_t ih_pr; /* protocol */ + } ti_i4; + struct { + struct in6_addr ih_src; + struct in6_addr ih_dst; + uint8_t ih_x1; + uint8_t ih_nh; + } ti_i6; + } ti; + uint16_t ti_x0; + uint16_t ti_len; /* protocol length */ + struct tcphdr ti_t; /* tcp header */ +}; +#define ti_mbuf ih_mbuf.mptr +#define ti_pr ti.ti_i4.ih_pr +#define ti_src ti.ti_i4.ih_src +#define ti_dst ti.ti_i4.ih_dst +#define ti_src6 ti.ti_i6.ih_src +#define ti_dst6 ti.ti_i6.ih_dst +#define ti_nh6 ti.ti_i6.ih_nh +#define ti_sport ti_t.th_sport +#define ti_dport ti_t.th_dport +#define ti_seq ti_t.th_seq +#define ti_ack ti_t.th_ack +#define ti_x2 ti_t.th_x2 +#define ti_off ti_t.th_off +#define ti_flags ti_t.th_flags +#define ti_win ti_t.th_win +#define ti_sum ti_t.th_sum +#define ti_urp ti_t.th_urp + +#define tcpiphdr2qlink(T) \ + ((struct qlink *)(((char *)(T)) - sizeof(struct qlink))) +#define qlink2tcpiphdr(Q) \ + ((struct tcpiphdr *)(((char *)(Q)) + sizeof(struct qlink))) +#define tcpiphdr_next(T) qlink2tcpiphdr(tcpiphdr2qlink(T)->next) +#define tcpiphdr_prev(T) qlink2tcpiphdr(tcpiphdr2qlink(T)->prev) +#define tcpfrag_list_first(T) qlink2tcpiphdr((T)->seg_next) +#define tcpfrag_list_end(F, T) (tcpiphdr2qlink(F) == (struct qlink *)(T)) +#define tcpfrag_list_empty(T) ((T)->seg_next == (struct tcpiphdr *)(T)) + +/* This is the difference between the size of a tcpiphdr structure, and the + * size of actual ip+tcp headers, rounded up since we need to align data. */ +#define TCPIPHDR_DELTA \ + (MAX(0, ((int) sizeof(struct qlink) + \ + (int) sizeof(struct tcpiphdr) - (int) sizeof(struct ip) - \ + (int) sizeof(struct tcphdr) + 7) & \ + ~7)) + +/* + * Just a clean way to get to the first byte + * of the packet + */ +struct tcpiphdr_2 { + struct tcpiphdr dummy; + char first_char; +}; + +#endif diff --git a/src/net/libslirp/src/tftp.c b/src/net/libslirp/src/tftp.c new file mode 100644 index 00000000..1b396924 --- /dev/null +++ b/src/net/libslirp/src/tftp.c @@ -0,0 +1,484 @@ +/* SPDX-License-Identifier: MIT */ +/* + * tftp.c - a simple, read-only tftp server for qemu + * + * Copyright (c) 2004 Magnus Damm + * + * 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. + */ + +#include "slirp.h" + +#include +#include +#include + +static inline int tftp_session_in_use(struct tftp_session *spt) +{ + return (spt->slirp != NULL); +} + +static inline void tftp_session_update(struct tftp_session *spt) +{ + spt->timestamp = curtime; +} + +static void tftp_session_terminate(struct tftp_session *spt) +{ + if (spt->fd >= 0) { + close(spt->fd); + spt->fd = -1; + } + g_free(spt->filename); + spt->slirp = NULL; +} + +static int tftp_session_allocate(Slirp *slirp, struct sockaddr_storage *srcsas, + struct tftphdr *hdr) +{ + struct tftp_session *spt; + int k; + + for (k = 0; k < TFTP_SESSIONS_MAX; k++) { + spt = &slirp->tftp_sessions[k]; + + if (!tftp_session_in_use(spt)) + goto found; + + /* sessions time out after 5 inactive seconds */ + if ((int)(curtime - spt->timestamp) > 5000) { + tftp_session_terminate(spt); + goto found; + } + } + + return -1; + +found: + memset(spt, 0, sizeof(*spt)); + memcpy(&spt->client_addr, srcsas, sockaddr_size(srcsas)); + spt->fd = -1; + spt->block_size = 512; + spt->client_port = hdr->udp.uh_sport; + spt->slirp = slirp; + + tftp_session_update(spt); + + return k; +} + +static int tftp_session_find(Slirp *slirp, struct sockaddr_storage *srcsas, + struct tftphdr *hdr) +{ + struct tftp_session *spt; + int k; + + for (k = 0; k < TFTP_SESSIONS_MAX; k++) { + spt = &slirp->tftp_sessions[k]; + + if (tftp_session_in_use(spt)) { + if (sockaddr_equal(&spt->client_addr, srcsas)) { + if (spt->client_port == hdr->udp.uh_sport) { + return k; + } + } + } + } + + return -1; +} + +void tftp_cleanup(Slirp *slirp) +{ + struct tftp_session *spt; + int k; + + for (k = 0; k < TFTP_SESSIONS_MAX; k++) { + spt = &slirp->tftp_sessions[k]; + + if (tftp_session_in_use(spt)) { + tftp_session_terminate(spt); + } + } +} + +static int tftp_read_data(struct tftp_session *spt, uint32_t block_nr, + uint8_t *buf, int len) +{ + int bytes_read = 0; + + if (spt->fd < 0) { + spt->fd = open(spt->filename, O_RDONLY | O_BINARY); + } + + if (spt->fd < 0) { + return -1; + } + + if (len) { + if (lseek(spt->fd, block_nr * spt->block_size, SEEK_SET) == (off_t)-1) { + return -1; + } + + bytes_read = read(spt->fd, buf, len); + } + + return bytes_read; +} + +static struct tftp_t *tftp_prep_mbuf_data(struct tftp_session *spt, + struct mbuf *m) +{ + struct tftp_t *tp; + + memset(m->m_data, 0, m->m_size); + + m->m_data += IF_MAXLINKHDR; + if (spt->client_addr.ss_family == AF_INET6) { + m->m_data += sizeof(struct ip6); + } else { + m->m_data += sizeof(struct ip); + } + tp = (void *)m->m_data; + m->m_data += sizeof(struct udphdr); + + return tp; +} + +static void tftp_udp_output(struct tftp_session *spt, struct mbuf *m, + struct tftphdr *hdr) +{ + if (spt->client_addr.ss_family == AF_INET6) { + struct sockaddr_in6 sa6, da6; + + sa6.sin6_addr = spt->slirp->vhost_addr6; + sa6.sin6_port = hdr->udp.uh_dport; + da6.sin6_addr = ((struct sockaddr_in6 *)&spt->client_addr)->sin6_addr; + da6.sin6_port = spt->client_port; + + udp6_output(NULL, m, &sa6, &da6); + } else { + struct sockaddr_in sa4, da4; + + sa4.sin_addr = spt->slirp->vhost_addr; + sa4.sin_port = hdr->udp.uh_dport; + da4.sin_addr = ((struct sockaddr_in *)&spt->client_addr)->sin_addr; + da4.sin_port = spt->client_port; + + udp_output(NULL, m, &sa4, &da4, IPTOS_LOWDELAY); + } +} + +static int tftp_send_oack(struct tftp_session *spt, const char *keys[], + uint32_t values[], int nb, struct tftp_t *recv_tp) +{ + struct mbuf *m; + struct tftp_t *tp; + int i, n = 0; + + m = m_get(spt->slirp); + + if (!m) + return -1; + + tp = tftp_prep_mbuf_data(spt, m); + + tp->hdr.tp_op = htons(TFTP_OACK); + for (i = 0; i < nb; i++) { + n += slirp_fmt0(tp->x.tp_buf + n, sizeof(tp->x.tp_buf) - n, "%s", keys[i]); + n += slirp_fmt0(tp->x.tp_buf + n, sizeof(tp->x.tp_buf) - n, "%u", values[i]); + } + + m->m_len = G_SIZEOF_MEMBER(struct tftp_t, hdr.tp_op) + n; + tftp_udp_output(spt, m, &recv_tp->hdr); + + return 0; +} + +static void tftp_send_error(struct tftp_session *spt, uint16_t errorcode, + const char *msg, struct tftp_t *recv_tp) +{ + struct mbuf *m; + struct tftp_t *tp; + + DEBUG_TFTP("tftp error msg: %s", msg); + + m = m_get(spt->slirp); + + if (!m) { + goto out; + } + + tp = tftp_prep_mbuf_data(spt, m); + + tp->hdr.tp_op = htons(TFTP_ERROR); + tp->x.tp_error.tp_error_code = htons(errorcode); + slirp_pstrcpy((char *)tp->x.tp_error.tp_msg, sizeof(tp->x.tp_error.tp_msg), + msg); + + m->m_len = sizeof(struct tftp_t) - (TFTP_BLOCKSIZE_MAX + 2) + 3 + + strlen(msg) - sizeof(struct udphdr); + tftp_udp_output(spt, m, &recv_tp->hdr); + +out: + tftp_session_terminate(spt); +} + +static void tftp_send_next_block(struct tftp_session *spt, + struct tftphdr *hdr) +{ + struct mbuf *m; + struct tftp_t *tp; + int nobytes; + + m = m_get(spt->slirp); + + if (!m) { + return; + } + + tp = tftp_prep_mbuf_data(spt, m); + + tp->hdr.tp_op = htons(TFTP_DATA); + tp->x.tp_data.tp_block_nr = htons((spt->block_nr + 1) & 0xffff); + + nobytes = tftp_read_data(spt, spt->block_nr, tp->x.tp_data.tp_buf, + spt->block_size); + + if (nobytes < 0) { + /* send "file not found" error back */ + + tftp_send_error(spt, 1, "File not found", tp); + + m_free(m); + + return; + } + + m->m_len = sizeof(struct tftp_t) - (TFTP_BLOCKSIZE_MAX - nobytes) - + sizeof(struct udphdr); + tftp_udp_output(spt, m, hdr); + + if (nobytes == spt->block_size) { + tftp_session_update(spt); + } else { + tftp_session_terminate(spt); + } + + spt->block_nr++; +} + +static void tftp_handle_rrq(Slirp *slirp, struct sockaddr_storage *srcsas, + struct tftp_t *tp, int pktlen) +{ + struct tftp_session *spt; + int s, k; + size_t prefix_len; + char *req_fname; + const char *option_name[2]; + uint32_t option_value[2]; + int nb_options = 0; + + /* check if a session already exists and if so terminate it */ + s = tftp_session_find(slirp, srcsas, &tp->hdr); + if (s >= 0) { + tftp_session_terminate(&slirp->tftp_sessions[s]); + } + + s = tftp_session_allocate(slirp, srcsas, &tp->hdr); + + if (s < 0) { + return; + } + + spt = &slirp->tftp_sessions[s]; + + /* unspecified prefix means service disabled */ + if (!slirp->tftp_prefix) { + tftp_send_error(spt, 2, "Access violation", tp); + return; + } + + /* skip header fields */ + k = 0; + pktlen -= offsetof(struct tftp_t, x.tp_buf); + + /* prepend tftp_prefix */ + prefix_len = strlen(slirp->tftp_prefix); + spt->filename = g_malloc(prefix_len + TFTP_FILENAME_MAX + 2); + memcpy(spt->filename, slirp->tftp_prefix, prefix_len); + spt->filename[prefix_len] = '/'; + + /* get name */ + req_fname = spt->filename + prefix_len + 1; + + while (1) { + if (k >= TFTP_FILENAME_MAX || k >= pktlen) { + tftp_send_error(spt, 2, "Access violation", tp); + return; + } + req_fname[k] = tp->x.tp_buf[k]; + if (req_fname[k++] == '\0') { + break; + } + } + + DEBUG_TFTP("tftp rrq file: %s", req_fname); + + /* check mode */ + if ((pktlen - k) < 6) { + tftp_send_error(spt, 2, "Access violation", tp); + return; + } + + if (g_ascii_strcasecmp(&tp->x.tp_buf[k], "octet") != 0) { + tftp_send_error(spt, 4, "Unsupported transfer mode", tp); + return; + } + + k += 6; /* skipping octet */ + + /* do sanity checks on the filename */ + if ( +#ifdef G_OS_WIN32 + strstr(req_fname, "..\\") || + req_fname[strlen(req_fname) - 1] == '\\' || +#endif + strstr(req_fname, "../") || + req_fname[strlen(req_fname) - 1] == '/') { + tftp_send_error(spt, 2, "Access violation", tp); + return; + } + + /* check if the file exists */ + if (tftp_read_data(spt, 0, NULL, 0) < 0) { + tftp_send_error(spt, 1, "File not found", tp); + return; + } + + if (tp->x.tp_buf[pktlen - 1] != 0) { + tftp_send_error(spt, 2, "Access violation", tp); + return; + } + + while (k < pktlen && nb_options < G_N_ELEMENTS(option_name)) { + const char *key, *value; + + key = &tp->x.tp_buf[k]; + k += strlen(key) + 1; + + if (k >= pktlen) { + tftp_send_error(spt, 2, "Access violation", tp); + return; + } + + value = &tp->x.tp_buf[k]; + k += strlen(value) + 1; + + if (g_ascii_strcasecmp(key, "tsize") == 0) { + int tsize = atoi(value); + struct stat stat_p; + + if (tsize == 0) { + if (stat(spt->filename, &stat_p) == 0) + tsize = stat_p.st_size; + else { + tftp_send_error(spt, 1, "File not found", tp); + return; + } + } + + option_name[nb_options] = "tsize"; + option_value[nb_options] = tsize; + nb_options++; + } else if (g_ascii_strcasecmp(key, "blksize") == 0) { + int blksize = atoi(value); + + /* Accept blksize up to our maximum size */ + if (blksize > 0) { + spt->block_size = MIN(blksize, TFTP_BLOCKSIZE_MAX); + option_name[nb_options] = "blksize"; + option_value[nb_options] = spt->block_size; + nb_options++; + } + } + } + + if (nb_options > 0) { + assert(nb_options <= G_N_ELEMENTS(option_name)); + tftp_send_oack(spt, option_name, option_value, nb_options, tp); + return; + } + + spt->block_nr = 0; + tftp_send_next_block(spt, &tp->hdr); +} + +static void tftp_handle_ack(Slirp *slirp, struct sockaddr_storage *srcsas, + struct tftphdr *hdr) +{ + int s; + + s = tftp_session_find(slirp, srcsas, hdr); + + if (s < 0) { + return; + } + + tftp_send_next_block(&slirp->tftp_sessions[s], hdr); +} + +static void tftp_handle_error(Slirp *slirp, struct sockaddr_storage *srcsas, + struct tftphdr *hdr) +{ + int s; + + s = tftp_session_find(slirp, srcsas, hdr); + + if (s < 0) { + return; + } + + tftp_session_terminate(&slirp->tftp_sessions[s]); +} + +void tftp_input(struct sockaddr_storage *srcsas, struct mbuf *m) +{ + struct tftphdr *hdr = mtod_check(m, sizeof(struct tftphdr)); + + if (hdr == NULL) { + return; + } + + switch (ntohs(hdr->tp_op)) { + case TFTP_RRQ: + tftp_handle_rrq(m->slirp, srcsas, + mtod(m, struct tftp_t *), + m->m_len); + break; + + case TFTP_ACK: + tftp_handle_ack(m->slirp, srcsas, hdr); + break; + + case TFTP_ERROR: + tftp_handle_error(m->slirp, srcsas, hdr); + break; + } +} diff --git a/src/net/libslirp/src/tftp.h b/src/net/libslirp/src/tftp.h new file mode 100644 index 00000000..263c540a --- /dev/null +++ b/src/net/libslirp/src/tftp.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* tftp defines */ + +#ifndef SLIRP_TFTP_H +#define SLIRP_TFTP_H + +#include "util.h" + +#define TFTP_SESSIONS_MAX 20 + +#define TFTP_SERVER 69 + +#define TFTP_RRQ 1 +#define TFTP_WRQ 2 +#define TFTP_DATA 3 +#define TFTP_ACK 4 +#define TFTP_ERROR 5 +#define TFTP_OACK 6 + +#define TFTP_FILENAME_MAX 512 +#define TFTP_BLOCKSIZE_MAX 1428 + +SLIRP_PACKED_BEGIN +struct tftphdr { + struct udphdr udp; + uint16_t tp_op; +} SLIRP_PACKED_END; + +SLIRP_PACKED_BEGIN +struct tftp_t { + struct tftphdr hdr; + union { + struct { + uint16_t tp_block_nr; + uint8_t tp_buf[TFTP_BLOCKSIZE_MAX]; + } tp_data; + struct { + uint16_t tp_error_code; + uint8_t tp_msg[TFTP_BLOCKSIZE_MAX]; + } tp_error; + char tp_buf[TFTP_BLOCKSIZE_MAX + 2]; + } x; +} SLIRP_PACKED_END; + +struct tftp_session { + Slirp *slirp; + char *filename; + int fd; + uint16_t block_size; + + struct sockaddr_storage client_addr; + uint16_t client_port; + uint32_t block_nr; + + int timestamp; +}; + +/* Process TFTP packet coming from the guest */ +void tftp_input(struct sockaddr_storage *srcsas, struct mbuf *m); + +/* Clear remaining sessions */ +void tftp_cleanup(Slirp *slirp); + +#endif diff --git a/src/net/libslirp/src/udp.c b/src/net/libslirp/src/udp.c new file mode 100644 index 00000000..2965b184 --- /dev/null +++ b/src/net/libslirp/src/udp.c @@ -0,0 +1,427 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)udp_usrreq.c 8.4 (Berkeley) 1/21/94 + * udp_usrreq.c,v 1.4 1994/10/02 17:48:45 phk Exp + */ + +/* + * Changes and additions relating to SLiRP + * Copyright (c) 1995 Danny Gasparovski. + * + * Please read the file COPYRIGHT for the + * terms and conditions of the copyright. + */ + +#include "slirp.h" +#include "ip_icmp.h" + +static uint8_t udp_tos(struct socket *so); + +void udp_init(Slirp *slirp) +{ + slirp->udb.so_next = slirp->udb.so_prev = &slirp->udb; + slirp->udp_last_so = &slirp->udb; +} + +void udp_cleanup(Slirp *slirp) +{ + struct socket *so, *so_next; + + for (so = slirp->udb.so_next; so != &slirp->udb; so = so_next) { + so_next = so->so_next; + udp_detach(so); + } +} + +/* m->m_data points at ip packet header + * m->m_len length ip packet + * ip->ip_len length data (IPDU) + */ +void udp_input(register struct mbuf *m, int iphlen) +{ + Slirp *slirp = m->slirp; + M_DUP_DEBUG(slirp, m, 0, 0); + + register struct ip *ip; + register struct udphdr *uh; + int len; + struct ip save_ip; + struct socket *so; + struct sockaddr_storage lhost; + struct sockaddr_in *lhost4; + int ttl; + + DEBUG_CALL("udp_input"); + DEBUG_ARG("m = %p", m); + DEBUG_ARG("iphlen = %d", iphlen); + + /* + * Strip IP options, if any; should skip this, + * make available to user, and use on returned packets, + * but we don't yet have a way to check the checksum + * with options still present. + */ + if (iphlen > sizeof(struct ip)) { + ip_stripoptions(m); + iphlen = sizeof(struct ip); + } + + /* + * Get IP and UDP header together in first mbuf. + */ + ip = mtod_check(m, iphlen + sizeof(struct udphdr)); + if (ip == NULL) { + goto bad; + } + uh = (struct udphdr *)((char *)ip + iphlen); + + /* + * Make mbuf data length reflect UDP length. + * If not enough data to reflect UDP length, drop. + */ + len = ntohs((uint16_t)uh->uh_ulen); + + if (ip->ip_len != len) { + if (len > ip->ip_len) { + goto bad; + } + m_adj(m, len - ip->ip_len); + ip->ip_len = len; + } + + /* + * Save a copy of the IP header in case we want restore it + * for sending an ICMP error message in response. + */ + save_ip = *ip; + save_ip.ip_len += iphlen; /* tcp_input subtracts this */ + + /* + * Checksum extended UDP header and data. + */ + if (uh->uh_sum) { + memset(&((struct ipovly *)ip)->ih_mbuf, 0, sizeof(struct mbuf_ptr)); + ((struct ipovly *)ip)->ih_x1 = 0; + ((struct ipovly *)ip)->ih_len = uh->uh_ulen; + if (cksum(m, len + sizeof(struct ip))) { + goto bad; + } + } + + lhost.ss_family = AF_INET; + lhost4 = (struct sockaddr_in *)&lhost; + lhost4->sin_addr = ip->ip_src; + lhost4->sin_port = uh->uh_sport; + + /* + * handle DHCP/BOOTP + */ + if (ntohs(uh->uh_dport) == BOOTP_SERVER && + (ip->ip_dst.s_addr == slirp->vhost_addr.s_addr || + ip->ip_dst.s_addr == 0xffffffff)) { + bootp_input(m); + goto bad; + } + + /* + * handle TFTP + */ + if (ntohs(uh->uh_dport) == TFTP_SERVER && + ip->ip_dst.s_addr == slirp->vhost_addr.s_addr) { + m->m_data += iphlen; + m->m_len -= iphlen; + tftp_input(&lhost, m); + m->m_data -= iphlen; + m->m_len += iphlen; + goto bad; + } + + if (slirp->restricted) { + goto bad; + } + + /* + * Locate pcb for datagram. + */ + so = solookup(&slirp->udp_last_so, &slirp->udb, &lhost, NULL); + + if (so == NULL) { + /* + * If there's no socket for this packet, + * create one + */ + so = socreate(slirp, IPPROTO_UDP); + if (udp_attach(so, AF_INET) == -1) { + DEBUG_MISC(" udp_attach errno = %d-%s", errno, strerror(errno)); + sofree(so); + goto bad; + } + + /* + * Setup fields + */ + so->so_lfamily = AF_INET; + so->so_laddr = ip->ip_src; + so->so_lport = uh->uh_sport; + + if ((so->so_iptos = udp_tos(so)) == 0) + so->so_iptos = ip->ip_tos; + + /* + * XXXXX Here, check if it's in udpexec_list, + * and if it is, do the fork_exec() etc. + */ + } + + so->so_ffamily = AF_INET; + so->so_faddr = ip->ip_dst; /* XXX */ + so->so_fport = uh->uh_dport; /* XXX */ + + iphlen += sizeof(struct udphdr); + m->m_len -= iphlen; + m->m_data += iphlen; + + /* + * Check for TTL + */ + ttl = save_ip.ip_ttl-1; + if (ttl <= 0) { + m->m_len += iphlen; + m->m_data -= iphlen; + *ip = save_ip; + DEBUG_MISC("udp ttl exceeded"); + icmp_send_error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, 0, NULL); + goto bad; + } + setsockopt(so->s, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); + + /* + * Now we sendto() the packet. + */ + if (sosendto(so, m) == -1) { + m->m_len += iphlen; + m->m_data -= iphlen; + *ip = save_ip; + DEBUG_MISC("udp tx errno = %d-%s", errno, strerror(errno)); + icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, strerror(errno)); + goto bad; + } + + m_free(so->so_m); /* used for ICMP if error on sorecvfrom */ + + /* restore the orig mbuf packet */ + m->m_len += iphlen; + m->m_data -= iphlen; + *ip = save_ip; + so->so_m = m; /* ICMP backup */ + + return; +bad: + m_free(m); +} + +int udp_output(struct socket *so, struct mbuf *m, struct sockaddr_in *saddr, + struct sockaddr_in *daddr, int iptos) +{ + Slirp *slirp = m->slirp; + char addr[INET_ADDRSTRLEN]; + + M_DUP_DEBUG(slirp, m, 0, sizeof(struct udpiphdr)); + + register struct udpiphdr *ui; + int error = 0; + + DEBUG_CALL("udp_output"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("m = %p", m); + DEBUG_ARG("saddr = %s", inet_ntop(AF_INET, &saddr->sin_addr, addr, sizeof(addr))); + DEBUG_ARG("daddr = %s", inet_ntop(AF_INET, &daddr->sin_addr, addr, sizeof(addr))); + + /* + * Adjust for header + */ + m->m_data -= sizeof(struct udpiphdr); + m->m_len += sizeof(struct udpiphdr); + + /* + * Fill in mbuf with extended UDP header + * and addresses and length put into network format. + */ + ui = mtod(m, struct udpiphdr *); + memset(&ui->ui_i.ih_mbuf, 0, sizeof(struct mbuf_ptr)); + ui->ui_x1 = 0; + ui->ui_pr = IPPROTO_UDP; + ui->ui_len = htons(m->m_len - sizeof(struct ip)); + /* XXXXX Check for from-one-location sockets, or from-any-location sockets + */ + ui->ui_src = saddr->sin_addr; + ui->ui_dst = daddr->sin_addr; + ui->ui_sport = saddr->sin_port; + ui->ui_dport = daddr->sin_port; + ui->ui_ulen = ui->ui_len; + + /* + * Stuff checksum and output datagram. + */ + ui->ui_sum = 0; + if ((ui->ui_sum = cksum(m, m->m_len)) == 0) + ui->ui_sum = 0xffff; + ((struct ip *)ui)->ip_len = m->m_len; + + ((struct ip *)ui)->ip_ttl = IPDEFTTL; + ((struct ip *)ui)->ip_tos = iptos; + + error = ip_output(so, m); + + return (error); +} + +int udp_attach(struct socket *so, unsigned short af) +{ + so->s = slirp_socket(af, SOCK_DGRAM, 0); + if (so->s != -1) { + if (slirp_bind_outbound(so, af) != 0) { + // bind failed - close socket + closesocket(so->s); + so->s = -1; + return -1; + } + +#ifdef __linux__ + { + int opt = 1; + switch (af) { + case AF_INET: + setsockopt(so->s, IPPROTO_IP, IP_RECVERR, &opt, sizeof(opt)); + break; + case AF_INET6: + setsockopt(so->s, IPPROTO_IPV6, IPV6_RECVERR, &opt, sizeof(opt)); + break; + default: + g_assert_not_reached(); + } + } +#endif + + so->so_expire = curtime + SO_EXPIRE; + slirp_insque(so, &so->slirp->udb); + } + so->slirp->cb->register_poll_fd(so->s, so->slirp->opaque); + return (so->s); +} + +void udp_detach(struct socket *so) +{ + so->slirp->cb->unregister_poll_fd(so->s, so->slirp->opaque); + closesocket(so->s); + sofree(so); +} + +static const struct tos_t udptos[] = { { 0, 53, IPTOS_LOWDELAY, 0 }, /* DNS */ + { 0, 0, 0, 0 } }; + +static uint8_t udp_tos(struct socket *so) +{ + int i = 0; + + while (udptos[i].tos) { + if ((udptos[i].fport && ntohs(so->so_fport) == udptos[i].fport) || + (udptos[i].lport && ntohs(so->so_lport) == udptos[i].lport)) { + if (so->slirp->enable_emu) + so->so_emu = udptos[i].emu; + return udptos[i].tos; + } + i++; + } + + return 0; +} + +struct socket *udpx_listen(Slirp *slirp, + const struct sockaddr *haddr, socklen_t haddrlen, + const struct sockaddr *laddr, socklen_t laddrlen, + int flags) +{ + struct socket *so; + socklen_t addrlen; + int save_errno; + + so = socreate(slirp, IPPROTO_UDP); + so->s = slirp_socket(haddr->sa_family, SOCK_DGRAM, 0); + if (so->s < 0) { + save_errno = errno; + sofree(so); + errno = save_errno; + return NULL; + } + if (haddr->sa_family == AF_INET6) + slirp_socket_set_v6only(so->s, (flags & SS_HOSTFWD_V6ONLY) != 0); + so->so_expire = curtime + SO_EXPIRE; + slirp_insque(so, &slirp->udb); + + if (bind(so->s, haddr, haddrlen) < 0) { + save_errno = errno; + udp_detach(so); + errno = save_errno; + return NULL; + } + slirp_socket_set_fast_reuse(so->s); + + addrlen = sizeof(so->fhost); + getsockname(so->s, &so->fhost.sa, &addrlen); + sotranslate_accept(so); + + sockaddr_copy(&so->lhost.sa, sizeof(so->lhost), laddr, laddrlen); + + if (flags != SS_FACCEPTONCE) + so->so_expire = 0; + so->so_state &= SS_PERSISTENT_MASK; + so->so_state |= SS_ISFCONNECTED | flags; + + return so; +} + +struct socket *udp_listen(Slirp *slirp, uint32_t haddr, unsigned hport, + uint32_t laddr, unsigned lport, int flags) +{ + struct sockaddr_in hsa, lsa; + + memset(&hsa, 0, sizeof(hsa)); + hsa.sin_family = AF_INET; + hsa.sin_addr.s_addr = haddr; + hsa.sin_port = hport; + + memset(&lsa, 0, sizeof(lsa)); + lsa.sin_family = AF_INET; + lsa.sin_addr.s_addr = laddr; + lsa.sin_port = lport; + + return udpx_listen(slirp, (const struct sockaddr *) &hsa, sizeof(hsa), (struct sockaddr *) &lsa, sizeof(lsa), flags); +} diff --git a/src/net/libslirp/src/udp.h b/src/net/libslirp/src/udp.h new file mode 100644 index 00000000..b0514f18 --- /dev/null +++ b/src/net/libslirp/src/udp.h @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)udp.h 8.1 (Berkeley) 6/10/93 + * udp.h,v 1.3 1994/08/21 05:27:41 paul Exp + */ + +#ifndef UDP_H +#define UDP_H + +#include "socket.h" + +#define UDP_TTL 0x60 + +/* + * Udp protocol header. + * Per RFC 768, September, 1981. + */ +struct udphdr { + uint16_t uh_sport; /* source port */ + uint16_t uh_dport; /* destination port */ + int16_t uh_ulen; /* udp length */ + uint16_t uh_sum; /* udp checksum */ +}; + +/* + * UDP kernel structures and variables. + */ +struct udpiphdr { + struct ipovly ui_i; /* overlaid ip structure */ + struct udphdr ui_u; /* udp header */ +}; +#define ui_mbuf ui_i.ih_mbuf.mptr +#define ui_x1 ui_i.ih_x1 +#define ui_pr ui_i.ih_pr +#define ui_len ui_i.ih_len +#define ui_src ui_i.ih_src +#define ui_dst ui_i.ih_dst +#define ui_sport ui_u.uh_sport +#define ui_dport ui_u.uh_dport +#define ui_ulen ui_u.uh_ulen +#define ui_sum ui_u.uh_sum + +/* + * Names for UDP sysctl objects + */ +#define UDPCTL_CHECKSUM 1 /* checksum UDP packets */ +#define UDPCTL_MAXID 2 + +struct mbuf; + +/* Called from slirp_init */ +void udp_init(Slirp *); +/* Called from slirp_cleanup */ +void udp_cleanup(Slirp *); +/* Process UDP datagram coming from the guest */ +void udp_input(register struct mbuf *, int); +/* Create a host UDP socket, bound to this socket */ +int udp_attach(struct socket *, unsigned short af); +/* Destroy socket */ +void udp_detach(struct socket *); + +/* Listen for incoming UDP datagrams on this haddr+hport */ +struct socket *udp_listen(Slirp *, uint32_t haddr, unsigned hport, uint32_t laddr, unsigned lport, int flags); +/* Listen for incoming UDP datagrams on this haddr */ +struct socket *udpx_listen(Slirp *, + const struct sockaddr *haddr, socklen_t haddrlen, + const struct sockaddr *laddr, socklen_t laddrlen, + int flags); +/* Send UDP datagram to the guest */ +int udp_output(struct socket *so, struct mbuf *m, struct sockaddr_in *saddr, + struct sockaddr_in *daddr, int iptos); + +/* Process UDPv6 datagram coming from the guest */ +void udp6_input(register struct mbuf *); +/* Send UDPv6 datagram to the guest */ +int udp6_output(struct socket *so, struct mbuf *m, struct sockaddr_in6 *saddr, + struct sockaddr_in6 *daddr); + +#endif diff --git a/src/net/libslirp/src/udp6.c b/src/net/libslirp/src/udp6.c new file mode 100644 index 00000000..effdf77d --- /dev/null +++ b/src/net/libslirp/src/udp6.c @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2013 + * Guillaume Subiron + */ + +#include "slirp.h" +#include "udp.h" +#include "dhcpv6.h" + +void udp6_input(struct mbuf *m) +{ + Slirp *slirp = m->slirp; + M_DUP_DEBUG(slirp, m, 0, 0); + + struct ip6 *ip, save_ip; + struct udphdr *uh; + int iphlen = sizeof(struct ip6); + int len; + struct socket *so; + struct sockaddr_in6 lhost; + int hop_limit; + + DEBUG_CALL("udp6_input"); + DEBUG_ARG("m = %p", m); + + if (slirp->restricted) { + goto bad; + } + + ip = mtod(m, struct ip6 *); + m->m_len -= iphlen; + m->m_data += iphlen; + uh = mtod_check(m, sizeof(struct udphdr)); + if (uh == NULL) { + goto bad; + } + m->m_len += iphlen; + m->m_data -= iphlen; + + if (ip6_cksum(m)) { + goto bad; + } + + len = ntohs((uint16_t)uh->uh_ulen); + + /* + * Make mbuf data length reflect UDP length. + * If not enough data to reflect UDP length, drop. + */ + if (ntohs(ip->ip_pl) != len) { + if (len > ntohs(ip->ip_pl)) { + goto bad; + } + m_adj(m, len - ntohs(ip->ip_pl)); + ip->ip_pl = htons(len); + } + + /* + * Save a copy of the IP header in case we want restore it + * for sending an ICMP error message in response. + */ + save_ip = *ip; + + /* Locate pcb for datagram. */ + lhost.sin6_family = AF_INET6; + lhost.sin6_addr = ip->ip_src; + lhost.sin6_port = uh->uh_sport; + + /* handle DHCPv6 */ + if (ntohs(uh->uh_dport) == DHCPV6_SERVER_PORT && + (in6_equal(&ip->ip_dst, &slirp->vhost_addr6) || + in6_dhcp_multicast(&ip->ip_dst))) { + m->m_data += iphlen; + m->m_len -= iphlen; + dhcpv6_input(&lhost, m); + m->m_data -= iphlen; + m->m_len += iphlen; + goto bad; + } + + /* handle TFTP */ + if (ntohs(uh->uh_dport) == TFTP_SERVER && + !memcmp(ip->ip_dst.s6_addr, slirp->vhost_addr6.s6_addr, 16)) { + m->m_data += iphlen; + m->m_len -= iphlen; + tftp_input((struct sockaddr_storage *)&lhost, m); + m->m_data -= iphlen; + m->m_len += iphlen; + goto bad; + } + + so = solookup(&slirp->udp_last_so, &slirp->udb, + (struct sockaddr_storage *)&lhost, NULL); + + if (so == NULL) { + /* If there's no socket for this packet, create one. */ + so = socreate(slirp, IPPROTO_UDP); + if (udp_attach(so, AF_INET6) == -1) { + DEBUG_MISC(" udp6_attach errno = %d-%s", errno, strerror(errno)); + sofree(so); + goto bad; + } + + /* Setup fields */ + so->so_lfamily = AF_INET6; + so->so_laddr6 = ip->ip_src; + so->so_lport6 = uh->uh_sport; + } + + so->so_ffamily = AF_INET6; + so->so_faddr6 = ip->ip_dst; /* XXX */ + so->so_fport6 = uh->uh_dport; /* XXX */ + + iphlen += sizeof(struct udphdr); + m->m_len -= iphlen; + m->m_data += iphlen; + + /* + * Check for TTL + */ + hop_limit = save_ip.ip_hl-1; + if (hop_limit <= 0) { + m->m_len += iphlen; + m->m_data -= iphlen; + *ip = save_ip; + DEBUG_MISC("udp ttl exceeded"); + icmp6_send_error(m, ICMP6_TIMXCEED, ICMP6_TIMXCEED_INTRANS); + goto bad; + } + setsockopt(so->s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hop_limit, sizeof(hop_limit)); + + /* + * Now we sendto() the packet. + */ + if (sosendto(so, m) == -1) { + m->m_len += iphlen; + m->m_data -= iphlen; + *ip = save_ip; + DEBUG_MISC("udp tx errno = %d-%s", errno, strerror(errno)); + icmp6_send_error(m, ICMP6_UNREACH, ICMP6_UNREACH_NO_ROUTE); + goto bad; + } + + m_free(so->so_m); /* used for ICMP if error on sorecvfrom */ + + /* restore the orig mbuf packet */ + m->m_len += iphlen; + m->m_data -= iphlen; + *ip = save_ip; + so->so_m = m; + + return; +bad: + m_free(m); +} + +int udp6_output(struct socket *so, struct mbuf *m, struct sockaddr_in6 *saddr, + struct sockaddr_in6 *daddr) +{ + Slirp *slirp = m->slirp; + M_DUP_DEBUG(slirp, m, 0, sizeof(struct ip6) + sizeof(struct udphdr)); + + struct ip6 *ip; + struct udphdr *uh; + + DEBUG_CALL("udp6_output"); + DEBUG_ARG("so = %p", so); + DEBUG_ARG("m = %p", m); + + /* adjust for header */ + m->m_data -= sizeof(struct udphdr); + m->m_len += sizeof(struct udphdr); + uh = mtod(m, struct udphdr *); + m->m_data -= sizeof(struct ip6); + m->m_len += sizeof(struct ip6); + ip = mtod(m, struct ip6 *); + + /* Build IP header */ + ip->ip_pl = htons(m->m_len - sizeof(struct ip6)); + ip->ip_nh = IPPROTO_UDP; + ip->ip_src = saddr->sin6_addr; + ip->ip_dst = daddr->sin6_addr; + + /* Build UDP header */ + uh->uh_sport = saddr->sin6_port; + uh->uh_dport = daddr->sin6_port; + uh->uh_ulen = ip->ip_pl; + uh->uh_sum = 0; + uh->uh_sum = ip6_cksum(m); + if (uh->uh_sum == 0) { + uh->uh_sum = 0xffff; + } + + return ip6_output(so, m, 0); +} diff --git a/src/net/libslirp/src/util.c b/src/net/libslirp/src/util.c new file mode 100644 index 00000000..8267f0c5 --- /dev/null +++ b/src/net/libslirp/src/util.c @@ -0,0 +1,441 @@ +/* SPDX-License-Identifier: MIT */ +/* + * util.c (mostly based on QEMU os-win32.c) + * + * Copyright (c) 2003-2008 Fabrice Bellard + * Copyright (c) 2010-2016 Red Hat, Inc. + * + * QEMU library functions for win32 which are shared between QEMU and + * the QEMU tools. + * + * 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. + */ +#include "util.h" + +#include +#include +#include + +#if defined(_WIN32) +int slirp_inet_aton(const char *cp, struct in_addr *ia) +{ + uint32_t addr = inet_addr(cp); + if (addr == 0xffffffff) { + return 0; + } + ia->s_addr = addr; + return 1; +} +#endif + +void slirp_set_nonblock(int fd) +{ +#ifndef _WIN32 + int f; + f = fcntl(fd, F_GETFL); + assert(f != -1); + f = fcntl(fd, F_SETFL, f | O_NONBLOCK); + assert(f != -1); +#else + unsigned long opt = 1; + ioctlsocket(fd, FIONBIO, &opt); +#endif +} + +static void slirp_set_cloexec(int fd) +{ +#ifndef _WIN32 + int f; + f = fcntl(fd, F_GETFD); + assert(f != -1); + f = fcntl(fd, F_SETFD, f | FD_CLOEXEC); + assert(f != -1); +#endif +} + +/* + * Opens a socket with FD_CLOEXEC set + * On failure errno contains the reason. + */ +int slirp_socket(int domain, int type, int protocol) +{ + int ret; + +#ifdef SOCK_CLOEXEC + ret = socket(domain, type | SOCK_CLOEXEC, protocol); + if (ret != -1 || errno != EINVAL) { + return ret; + } +#endif + ret = socket(domain, type, protocol); + if (ret >= 0) { + slirp_set_cloexec(ret); + } + + return ret; +} + +#ifdef _WIN32 +static int socket_error(void) +{ + switch (WSAGetLastError()) { + case 0: + return 0; + case WSAEINTR: + return EINTR; + case WSAEINVAL: + return EINVAL; + case WSA_INVALID_HANDLE: + return EBADF; + case WSA_NOT_ENOUGH_MEMORY: + return ENOMEM; + case WSA_INVALID_PARAMETER: + return EINVAL; + case WSAENAMETOOLONG: + return ENAMETOOLONG; + case WSAENOTEMPTY: + return ENOTEMPTY; + case WSAEWOULDBLOCK: + /* not using EWOULDBLOCK as we don't want code to have + * to check both EWOULDBLOCK and EAGAIN */ + return EAGAIN; + case WSAEINPROGRESS: + return EINPROGRESS; + case WSAEALREADY: + return EALREADY; + case WSAENOTSOCK: + return ENOTSOCK; + case WSAEDESTADDRREQ: + return EDESTADDRREQ; + case WSAEMSGSIZE: + return EMSGSIZE; + case WSAEPROTOTYPE: + return EPROTOTYPE; + case WSAENOPROTOOPT: + return ENOPROTOOPT; + case WSAEPROTONOSUPPORT: + return EPROTONOSUPPORT; + case WSAEOPNOTSUPP: + return EOPNOTSUPP; + case WSAEAFNOSUPPORT: + return EAFNOSUPPORT; + case WSAEADDRINUSE: + return EADDRINUSE; + case WSAEADDRNOTAVAIL: + return EADDRNOTAVAIL; + case WSAENETDOWN: + return ENETDOWN; + case WSAENETUNREACH: + return ENETUNREACH; + case WSAENETRESET: + return ENETRESET; + case WSAECONNABORTED: + return ECONNABORTED; + case WSAECONNRESET: + return ECONNRESET; + case WSAENOBUFS: + return ENOBUFS; + case WSAEISCONN: + return EISCONN; + case WSAENOTCONN: + return ENOTCONN; + case WSAETIMEDOUT: + return ETIMEDOUT; + case WSAECONNREFUSED: + return ECONNREFUSED; + case WSAELOOP: + return ELOOP; + case WSAEHOSTUNREACH: + return EHOSTUNREACH; + default: + return EIO; + } +} + +#undef ioctlsocket +int slirp_ioctlsocket_wrap(int fd, int req, void *val) +{ + int ret; + ret = ioctlsocket(fd, req, val); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef closesocket +int slirp_closesocket_wrap(int fd) +{ + int ret; + ret = closesocket(fd); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef connect +int slirp_connect_wrap(int sockfd, const struct sockaddr *addr, int addrlen) +{ + int ret; + ret = connect(sockfd, addr, addrlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef listen +int slirp_listen_wrap(int sockfd, int backlog) +{ + int ret; + ret = listen(sockfd, backlog); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef bind +int slirp_bind_wrap(int sockfd, const struct sockaddr *addr, int addrlen) +{ + int ret; + ret = bind(sockfd, addr, addrlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef socket +int slirp_socket_wrap(int domain, int type, int protocol) +{ + int ret; + ret = socket(domain, type, protocol); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef accept +int slirp_accept_wrap(int sockfd, struct sockaddr *addr, int *addrlen) +{ + int ret; + ret = accept(sockfd, addr, addrlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef shutdown +int slirp_shutdown_wrap(int sockfd, int how) +{ + int ret; + ret = shutdown(sockfd, how); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef getsockopt +int slirp_getsockopt_wrap(int sockfd, int level, int optname, void *optval, + int *optlen) +{ + int ret; + ret = getsockopt(sockfd, level, optname, optval, optlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef setsockopt +int slirp_setsockopt_wrap(int sockfd, int level, int optname, + const void *optval, int optlen) +{ + int ret; + ret = setsockopt(sockfd, level, optname, optval, optlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef getpeername +int slirp_getpeername_wrap(int sockfd, struct sockaddr *addr, int *addrlen) +{ + int ret; + ret = getpeername(sockfd, addr, addrlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef getsockname +int slirp_getsockname_wrap(int sockfd, struct sockaddr *addr, int *addrlen) +{ + int ret; + ret = getsockname(sockfd, addr, addrlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef send +slirp_ssize_t slirp_send_wrap(int sockfd, const void *buf, size_t len, int flags) +{ + int ret; + ret = send(sockfd, buf, len, flags); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef sendto +slirp_ssize_t slirp_sendto_wrap(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *addr, int addrlen) +{ + int ret; + ret = sendto(sockfd, buf, len, flags, addr, addrlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef recv +slirp_ssize_t slirp_recv_wrap(int sockfd, void *buf, size_t len, int flags) +{ + int ret; + ret = recv(sockfd, buf, len, flags); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} + +#undef recvfrom +slirp_ssize_t slirp_recvfrom_wrap(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *addr, int *addrlen) +{ + int ret; + ret = recvfrom(sockfd, buf, len, flags, addr, addrlen); + if (ret < 0) { + errno = socket_error(); + } + return ret; +} +#endif /* WIN32 */ + +void slirp_pstrcpy(char *buf, int buf_size, const char *str) +{ + int c; + char *q = buf; + + if (buf_size <= 0) + return; + + for (;;) { + c = *str++; + if (c == 0 || q >= buf + buf_size - 1) + break; + *q++ = c; + } + *q = '\0'; +} + +G_GNUC_PRINTF(3, 0) +static int slirp_vsnprintf(char *str, size_t size, + const char *format, va_list args) +{ + int rv = g_vsnprintf(str, size, format, args); + + if (rv < 0) { + g_error("g_vsnprintf() failed: %s", g_strerror(errno)); + } + + return rv; +} + +/* + * A snprintf()-like function that: + * - returns the number of bytes written (excluding optional \0-ending) + * - dies on error + * - warn on truncation + */ +int slirp_fmt(char *str, size_t size, const char *format, ...) +{ + va_list args; + int rv; + + va_start(args, format); + rv = slirp_vsnprintf(str, size, format, args); + va_end(args); + + if (rv >= size) { + g_critical("slirp_fmt() truncation"); + } + + return MIN(rv, size); +} + +/* + * A snprintf()-like function that: + * - always \0-end (unless size == 0) + * - returns the number of bytes actually written, including \0 ending + * - dies on error + * - warn on truncation + */ +int slirp_fmt0(char *str, size_t size, const char *format, ...) +{ + va_list args; + int rv; + + va_start(args, format); + rv = slirp_vsnprintf(str, size, format, args); + va_end(args); + + if (rv >= size) { + g_critical("slirp_fmt0() truncation"); + if (size > 0) + str[size - 1] = '\0'; + rv = size; + } else { + rv += 1; /* include \0 */ + } + + return rv; +} + +const char *slirp_ether_ntoa(const uint8_t *addr, char *out_str, + size_t out_str_size) +{ + assert(out_str_size >= ETH_ADDRSTRLEN); + + slirp_fmt0(out_str, out_str_size, "%02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + + return out_str; +} diff --git a/src/net/libslirp/src/util.h b/src/net/libslirp/src/util.h new file mode 100644 index 00000000..c378cee1 --- /dev/null +++ b/src/net/libslirp/src/util.h @@ -0,0 +1,208 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2003-2008 Fabrice Bellard + * Copyright (c) 2010-2019 Red Hat, Inc. + * + * 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. + */ +#ifndef UTIL_H_ +#define UTIL_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#endif + +#include "libslirp.h" + +#ifdef __GNUC__ +#define SLIRP_PACKED_BEGIN +#if defined(_WIN32) && (defined(__x86_64__) || defined(__i386__)) +#define SLIRP_PACKED_END __attribute__((gcc_struct, packed)) +#else +#define SLIRP_PACKED_END __attribute__((packed)) +#endif +#elif defined(_MSC_VER) +#define SLIRP_PACKED_BEGIN __pragma(pack(push, 1)) +#define SLIRP_PACKED_END __pragma(pack(pop)) +#endif + +#ifndef DIV_ROUND_UP +#define DIV_ROUND_UP(n, d) (((n) + (d)-1) / (d)) +#endif + +#ifndef container_of +#define container_of(ptr, type, member) \ + ((type *) (((char *)(ptr)) - offsetof(type, member))) +#endif + +#ifndef G_SIZEOF_MEMBER +#define G_SIZEOF_MEMBER(type, member) sizeof(((type *)0)->member) +#endif + +#if defined(_WIN32) /* CONFIG_IOVEC */ +#if !defined(IOV_MAX) /* XXX: to avoid duplicate with QEMU osdep.h */ +struct iovec { + void *iov_base; + size_t iov_len; +}; +#endif +#else +#include +#endif + +#define stringify(s) tostring(s) +#define tostring(s) #s + +#define SCALE_MS 1000000 + +#define ETH_ALEN 6 +#define ETH_ADDRSTRLEN 18 /* "xx:xx:xx:xx:xx:xx", with trailing NUL */ +#define ETH_HLEN 14 +#define ETH_MINLEN 60 +#define ETH_P_IP (0x0800) /* Internet Protocol packet */ +#define ETH_P_ARP (0x0806) /* Address Resolution packet */ +#define ETH_P_IPV6 (0x86dd) +#define ETH_P_VLAN (0x8100) +#define ETH_P_DVLAN (0x88a8) +#define ETH_P_NCSI (0x88f8) +#define ETH_P_UNKNOWN (0xffff) + +/* FIXME: remove me when made standalone */ +#ifdef _WIN32 +#undef accept +#undef bind +#undef closesocket +#undef connect +#undef getpeername +#undef getsockname +#undef getsockopt +#undef ioctlsocket +#undef listen +#undef recv +#undef recvfrom +#undef send +#undef sendto +#undef setsockopt +#undef shutdown +#undef socket +#endif + +#ifdef _WIN32 +#define connect slirp_connect_wrap +int slirp_connect_wrap(int fd, const struct sockaddr *addr, int addrlen); +#define listen slirp_listen_wrap +int slirp_listen_wrap(int fd, int backlog); +#define bind slirp_bind_wrap +int slirp_bind_wrap(int fd, const struct sockaddr *addr, int addrlen); +#define socket slirp_socket_wrap +int slirp_socket_wrap(int domain, int type, int protocol); +#define accept slirp_accept_wrap +int slirp_accept_wrap(int fd, struct sockaddr *addr, int *addrlen); +#define shutdown slirp_shutdown_wrap +int slirp_shutdown_wrap(int fd, int how); +#define getpeername slirp_getpeername_wrap +int slirp_getpeername_wrap(int fd, struct sockaddr *addr, int *addrlen); +#define getsockname slirp_getsockname_wrap +int slirp_getsockname_wrap(int fd, struct sockaddr *addr, int *addrlen); +#define send slirp_send_wrap +slirp_ssize_t slirp_send_wrap(int fd, const void *buf, size_t len, int flags); +#define sendto slirp_sendto_wrap +slirp_ssize_t slirp_sendto_wrap(int fd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, int addrlen); +#define recv slirp_recv_wrap +slirp_ssize_t slirp_recv_wrap(int fd, void *buf, size_t len, int flags); +#define recvfrom slirp_recvfrom_wrap +slirp_ssize_t slirp_recvfrom_wrap(int fd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, int *addrlen); +#define closesocket slirp_closesocket_wrap +int slirp_closesocket_wrap(int fd); +#define ioctlsocket slirp_ioctlsocket_wrap +int slirp_ioctlsocket_wrap(int fd, int req, void *val); +#define getsockopt slirp_getsockopt_wrap +int slirp_getsockopt_wrap(int sockfd, int level, int optname, void *optval, + int *optlen); +#define setsockopt slirp_setsockopt_wrap +int slirp_setsockopt_wrap(int sockfd, int level, int optname, + const void *optval, int optlen); +#define inet_aton slirp_inet_aton +int slirp_inet_aton(const char *cp, struct in_addr *ia); +#else +#define closesocket(s) close(s) +#define ioctlsocket(s, r, v) ioctl(s, r, v) +#endif + +int slirp_socket(int domain, int type, int protocol); +void slirp_set_nonblock(int fd); + +static inline int slirp_socket_set_v6only(int fd, int v) +{ + return setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &v, sizeof(v)); +} + +static inline int slirp_socket_set_nodelay(int fd) +{ + int v = 1; + return setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &v, sizeof(v)); +} + +static inline int slirp_socket_set_fast_reuse(int fd) +{ +#ifndef _WIN32 + int v = 1; + return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)); +#else + /* Enabling the reuse of an endpoint that was used by a socket still in + * TIME_WAIT state is usually performed by setting SO_REUSEADDR. On Windows + * fast reuse is the default and SO_REUSEADDR does strange things. So we + * don't have to do anything here. More info can be found at: + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms740621.aspx */ + return 0; +#endif +} + +void slirp_pstrcpy(char *buf, int buf_size, const char *str); + +int slirp_fmt(char *str, size_t size, const char *format, ...) G_GNUC_PRINTF(3, 4); +int slirp_fmt0(char *str, size_t size, const char *format, ...) G_GNUC_PRINTF(3, 4); + +/* + * Pretty print a MAC address into out_str. + * As a convenience returns out_str. + */ +const char *slirp_ether_ntoa(const uint8_t *addr, char *out_str, + size_t out_str_len); + +#endif diff --git a/src/net/libslirp/src/version.c b/src/net/libslirp/src/version.c new file mode 100644 index 00000000..93e0be9c --- /dev/null +++ b/src/net/libslirp/src/version.c @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +#include "libslirp.h" + +const char * +slirp_version_string(void) +{ + return SLIRP_VERSION_STRING; +} diff --git a/src/net/libslirp/src/vmstate.c b/src/net/libslirp/src/vmstate.c new file mode 100644 index 00000000..2c3a727f --- /dev/null +++ b/src/net/libslirp/src/vmstate.c @@ -0,0 +1,445 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * VMState interpreter + * + * Copyright (c) 2009-2018 Red Hat Inc + * + * Authors: + * Juan Quintela + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include + +#include "stream.h" +#include "vmstate.h" + +static int get_nullptr(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field) +{ + if (slirp_istream_read_u8(f) == VMS_NULLPTR_MARKER) { + return 0; + } + g_warning("vmstate: get_nullptr expected VMS_NULLPTR_MARKER"); + return -EINVAL; +} + +static int put_nullptr(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field) + +{ + if (pv == NULL) { + slirp_ostream_write_u8(f, VMS_NULLPTR_MARKER); + return 0; + } + g_warning("vmstate: put_nullptr must be called with pv == NULL"); + return -EINVAL; +} + +const VMStateInfo slirp_vmstate_info_nullptr = { + .name = "uint64", + .get = get_nullptr, + .put = put_nullptr, +}; + +/* 8 bit unsigned int */ + +static int get_uint8(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field) +{ + uint8_t *v = pv; + *v = slirp_istream_read_u8(f); + return 0; +} + +static int put_uint8(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field) +{ + uint8_t *v = pv; + slirp_ostream_write_u8(f, *v); + return 0; +} + +const VMStateInfo slirp_vmstate_info_uint8 = { + .name = "uint8", + .get = get_uint8, + .put = put_uint8, +}; + +/* 16 bit unsigned int */ + +static int get_uint16(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field) +{ + uint16_t *v = pv; + *v = slirp_istream_read_u16(f); + return 0; +} + +static int put_uint16(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field) +{ + uint16_t *v = pv; + slirp_ostream_write_u16(f, *v); + return 0; +} + +const VMStateInfo slirp_vmstate_info_uint16 = { + .name = "uint16", + .get = get_uint16, + .put = put_uint16, +}; + +/* 32 bit unsigned int */ + +static int get_uint32(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field) +{ + uint32_t *v = pv; + *v = slirp_istream_read_u32(f); + return 0; +} + +static int put_uint32(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field) +{ + uint32_t *v = pv; + slirp_ostream_write_u32(f, *v); + return 0; +} + +const VMStateInfo slirp_vmstate_info_uint32 = { + .name = "uint32", + .get = get_uint32, + .put = put_uint32, +}; + +/* 16 bit int */ + +static int get_int16(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field) +{ + int16_t *v = pv; + *v = slirp_istream_read_i16(f); + return 0; +} + +static int put_int16(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field) +{ + int16_t *v = pv; + slirp_ostream_write_i16(f, *v); + return 0; +} + +const VMStateInfo slirp_vmstate_info_int16 = { + .name = "int16", + .get = get_int16, + .put = put_int16, +}; + +/* 32 bit int */ + +static int get_int32(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field) +{ + int32_t *v = pv; + *v = slirp_istream_read_i32(f); + return 0; +} + +static int put_int32(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field) +{ + int32_t *v = pv; + slirp_ostream_write_i32(f, *v); + return 0; +} + +const VMStateInfo slirp_vmstate_info_int32 = { + .name = "int32", + .get = get_int32, + .put = put_int32, +}; + +/* vmstate_info_tmp, see VMSTATE_WITH_TMP, the idea is that we allocate + * a temporary buffer and the pre_load/pre_save methods in the child vmsd + * copy stuff from the parent into the child and do calculations to fill + * in fields that don't really exist in the parent but need to be in the + * stream. + */ +static int get_tmp(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field) +{ + int ret; + const VMStateDescription *vmsd = field->vmsd; + int version_id = field->version_id; + void *tmp = g_malloc(size); + + /* Writes the parent field which is at the start of the tmp */ + *(void **)tmp = pv; + ret = slirp_vmstate_load_state(f, vmsd, tmp, version_id); + g_free(tmp); + return ret; +} + +static int put_tmp(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field) +{ + const VMStateDescription *vmsd = field->vmsd; + void *tmp = g_malloc(size); + int ret; + + /* Writes the parent field which is at the start of the tmp */ + *(void **)tmp = pv; + ret = slirp_vmstate_save_state(f, vmsd, tmp); + g_free(tmp); + + return ret; +} + +const VMStateInfo slirp_vmstate_info_tmp = { + .name = "tmp", + .get = get_tmp, + .put = put_tmp, +}; + +/* uint8_t buffers */ + +static int get_buffer(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field) +{ + slirp_istream_read(f, pv, size); + return 0; +} + +static int put_buffer(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field) +{ + slirp_ostream_write(f, pv, size); + return 0; +} + +const VMStateInfo slirp_vmstate_info_buffer = { + .name = "buffer", + .get = get_buffer, + .put = put_buffer, +}; + +static int vmstate_n_elems(char *opaque, const VMStateField *field) +{ + int n_elems = 1; + + if (field->flags & VMS_ARRAY) { + n_elems = field->num; + } else if (field->flags & VMS_VARRAY_INT32) { + n_elems = *(int32_t *)(opaque + field->num_offset); + } else if (field->flags & VMS_VARRAY_UINT32) { + n_elems = *(uint32_t *)(opaque + field->num_offset); + } else if (field->flags & VMS_VARRAY_UINT16) { + n_elems = *(uint16_t *)(opaque + field->num_offset); + } else if (field->flags & VMS_VARRAY_UINT8) { + n_elems = *(uint8_t *)(opaque + field->num_offset); + } + + if (field->flags & VMS_MULTIPLY_ELEMENTS) { + n_elems *= field->num; + } + + return n_elems; +} + +static int vmstate_size(char *opaque, const VMStateField *field) +{ + int size = field->size; + + if (field->flags & VMS_VBUFFER) { + size = *(int32_t *)(opaque + field->size_offset); + if (field->flags & VMS_MULTIPLY) { + size *= field->size; + } + } + + return size; +} + +static int vmstate_save_state_v(SlirpOStream *f, const VMStateDescription *vmsd, + char *opaque, int version_id) +{ + int ret = 0; + const VMStateField *field = vmsd->fields; + + if (vmsd->pre_save) { + ret = vmsd->pre_save(opaque); + if (ret) { + g_warning("pre-save failed: %s", vmsd->name); + return ret; + } + } + + while (field->name) { + if ((field->field_exists && field->field_exists(opaque, version_id)) || + (!field->field_exists && field->version_id <= version_id)) { + char *first_elem = opaque + field->offset; + int i, n_elems = vmstate_n_elems(opaque, field); + int size = vmstate_size(opaque, field); + + if (field->flags & VMS_POINTER) { + first_elem = *(void **)first_elem; + assert(first_elem || !n_elems || !size); + } + for (i = 0; i < n_elems; i++) { + void *curr_elem = first_elem + size * i; + + if (field->flags & VMS_ARRAY_OF_POINTER) { + assert(curr_elem); + curr_elem = *(void **)curr_elem; + } + if (!curr_elem && size) { + /* if null pointer write placeholder and do not follow */ + assert(field->flags & VMS_ARRAY_OF_POINTER); + ret = slirp_vmstate_info_nullptr.put(f, curr_elem, size, + NULL); + } else if (field->flags & VMS_STRUCT) { + ret = slirp_vmstate_save_state(f, field->vmsd, curr_elem); + } else if (field->flags & VMS_VSTRUCT) { + ret = vmstate_save_state_v(f, field->vmsd, curr_elem, + field->struct_version_id); + } else { + ret = field->info->put(f, curr_elem, size, field); + } + if (ret) { + g_warning("Save of field %s/%s failed", vmsd->name, + field->name); + return ret; + } + } + } else { + if (field->flags & VMS_MUST_EXIST) { + g_warning("Output state validation failed: %s/%s", vmsd->name, + field->name); + assert(!(field->flags & VMS_MUST_EXIST)); + } + } + field++; + } + + return 0; +} + +int slirp_vmstate_save_state(SlirpOStream *f, const VMStateDescription *vmsd, + void *opaque) +{ + return vmstate_save_state_v(f, vmsd, opaque, vmsd->version_id); +} + +static void vmstate_handle_alloc(void *ptr, VMStateField *field, void *opaque) +{ + if (field->flags & VMS_POINTER && field->flags & VMS_ALLOC) { + size_t size = vmstate_size(opaque, field); + size *= vmstate_n_elems(opaque, field); + if (size) { + *(void **)ptr = g_malloc(size); + } + } +} + +int slirp_vmstate_load_state(SlirpIStream *f, const VMStateDescription *vmsd, + void *opaque_, int version_id) +{ + VMStateField *field = vmsd->fields; + int ret = 0; + char *opaque = opaque_; + + if (version_id > vmsd->version_id) { + g_warning("%s: incoming version_id %d is too new " + "for local version_id %d", + vmsd->name, version_id, vmsd->version_id); + return -EINVAL; + } + if (vmsd->pre_load) { + int ret = vmsd->pre_load(opaque); + if (ret) { + return ret; + } + } + while (field->name) { + if ((field->field_exists && field->field_exists(opaque, version_id)) || + (!field->field_exists && field->version_id <= version_id)) { + char *first_elem = opaque + field->offset; + int i, n_elems = vmstate_n_elems(opaque, field); + int size = vmstate_size(opaque, field); + + vmstate_handle_alloc(first_elem, field, opaque); + if (field->flags & VMS_POINTER) { + first_elem = *(void **)first_elem; + assert(first_elem || !n_elems || !size); + } + for (i = 0; i < n_elems; i++) { + void *curr_elem = first_elem + size * i; + + if (field->flags & VMS_ARRAY_OF_POINTER) { + curr_elem = *(void **)curr_elem; + } + if (!curr_elem && size) { + /* if null pointer check placeholder and do not follow */ + assert(field->flags & VMS_ARRAY_OF_POINTER); + ret = slirp_vmstate_info_nullptr.get(f, curr_elem, size, + NULL); + } else if (field->flags & VMS_STRUCT) { + ret = slirp_vmstate_load_state(f, field->vmsd, curr_elem, + field->vmsd->version_id); + } else if (field->flags & VMS_VSTRUCT) { + ret = slirp_vmstate_load_state(f, field->vmsd, curr_elem, + field->struct_version_id); + } else { + ret = field->info->get(f, curr_elem, size, field); + } + if (ret < 0) { + g_warning("Failed to load %s:%s", vmsd->name, field->name); + return ret; + } + } + } else if (field->flags & VMS_MUST_EXIST) { + g_warning("Input validation failed: %s/%s", vmsd->name, + field->name); + return -1; + } + field++; + } + if (vmsd->post_load) { + ret = vmsd->post_load(opaque, version_id); + } + return ret; +} diff --git a/src/net/libslirp/src/vmstate.h b/src/net/libslirp/src/vmstate.h new file mode 100644 index 00000000..cd77c850 --- /dev/null +++ b/src/net/libslirp/src/vmstate.h @@ -0,0 +1,401 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * QEMU migration/snapshot declarations + * + * Copyright (c) 2009-2011 Red Hat, Inc. + * + * Original author: Juan Quintela + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef VMSTATE_H_ +#define VMSTATE_H_ + +#include +#include +#include "slirp.h" +#include "stream.h" + +#define stringify(s) tostring(s) +#define tostring(s) #s + +typedef struct VMStateInfo VMStateInfo; +typedef struct VMStateDescription VMStateDescription; +typedef struct VMStateField VMStateField; + +int slirp_vmstate_save_state(SlirpOStream *f, const VMStateDescription *vmsd, + void *opaque); +int slirp_vmstate_load_state(SlirpIStream *f, const VMStateDescription *vmsd, + void *opaque, int version_id); + +/* VMStateInfo allows customized migration of objects that don't fit in + * any category in VMStateFlags. Additional information is always passed + * into get and put in terms of field and vmdesc parameters. However + * these two parameters should only be used in cases when customized + * handling is needed, such as QTAILQ. For primitive data types such as + * integer, field and vmdesc parameters should be ignored inside get/put. + */ +struct VMStateInfo { + const char *name; + int (*get)(SlirpIStream *f, void *pv, size_t size, + const VMStateField *field); + int (*put)(SlirpOStream *f, void *pv, size_t size, + const VMStateField *field); +}; + +enum VMStateFlags { + /* Ignored */ + VMS_SINGLE = 0x001, + + /* The struct member at opaque + VMStateField.offset is a pointer + * to the actual field (e.g. struct a { uint8_t *b; + * }). Dereference the pointer before using it as basis for + * further pointer arithmetic (see e.g. VMS_ARRAY). Does not + * affect the meaning of VMStateField.num_offset or + * VMStateField.size_offset; see VMS_VARRAY* and VMS_VBUFFER for + * those. */ + VMS_POINTER = 0x002, + + /* The field is an array of fixed size. VMStateField.num contains + * the number of entries in the array. The size of each entry is + * given by VMStateField.size and / or opaque + + * VMStateField.size_offset; see VMS_VBUFFER and + * VMS_MULTIPLY. Each array entry will be processed individually + * (VMStateField.info.get()/put() if VMS_STRUCT is not set, + * recursion into VMStateField.vmsd if VMS_STRUCT is set). May not + * be combined with VMS_VARRAY*. */ + VMS_ARRAY = 0x004, + + /* The field is itself a struct, containing one or more + * fields. Recurse into VMStateField.vmsd. Most useful in + * combination with VMS_ARRAY / VMS_VARRAY*, recursing into each + * array entry. */ + VMS_STRUCT = 0x008, + + /* The field is an array of variable size. The int32_t at opaque + + * VMStateField.num_offset contains the number of entries in the + * array. See the VMS_ARRAY description regarding array handling + * in general. May not be combined with VMS_ARRAY or any other + * VMS_VARRAY*. */ + VMS_VARRAY_INT32 = 0x010, + + /* Ignored */ + VMS_BUFFER = 0x020, + + /* The field is a (fixed-size or variable-size) array of pointers + * (e.g. struct a { uint8_t *b[]; }). Dereference each array entry + * before using it. Note: Does not imply any one of VMS_ARRAY / + * VMS_VARRAY*; these need to be set explicitly. */ + VMS_ARRAY_OF_POINTER = 0x040, + + /* The field is an array of variable size. The uint16_t at opaque + * + VMStateField.num_offset (subject to VMS_MULTIPLY_ELEMENTS) + * contains the number of entries in the array. See the VMS_ARRAY + * description regarding array handling in general. May not be + * combined with VMS_ARRAY or any other VMS_VARRAY*. */ + VMS_VARRAY_UINT16 = 0x080, + + /* The size of the individual entries (a single array entry if + * VMS_ARRAY or any of VMS_VARRAY* are set, or the field itself if + * neither is set) is variable (i.e. not known at compile-time), + * but the same for all entries. Use the int32_t at opaque + + * VMStateField.size_offset (subject to VMS_MULTIPLY) to determine + * the size of each (and every) entry. */ + VMS_VBUFFER = 0x100, + + /* Multiply the entry size given by the int32_t at opaque + + * VMStateField.size_offset (see VMS_VBUFFER description) with + * VMStateField.size to determine the number of bytes to be + * allocated. Only valid in combination with VMS_VBUFFER. */ + VMS_MULTIPLY = 0x200, + + /* The field is an array of variable size. The uint8_t at opaque + + * VMStateField.num_offset (subject to VMS_MULTIPLY_ELEMENTS) + * contains the number of entries in the array. See the VMS_ARRAY + * description regarding array handling in general. May not be + * combined with VMS_ARRAY or any other VMS_VARRAY*. */ + VMS_VARRAY_UINT8 = 0x400, + + /* The field is an array of variable size. The uint32_t at opaque + * + VMStateField.num_offset (subject to VMS_MULTIPLY_ELEMENTS) + * contains the number of entries in the array. See the VMS_ARRAY + * description regarding array handling in general. May not be + * combined with VMS_ARRAY or any other VMS_VARRAY*. */ + VMS_VARRAY_UINT32 = 0x800, + + /* Fail loading the serialised VM state if this field is missing + * from the input. */ + VMS_MUST_EXIST = 0x1000, + + /* When loading serialised VM state, allocate memory for the + * (entire) field. Only valid in combination with + * VMS_POINTER. Note: Not all combinations with other flags are + * currently supported, e.g. VMS_ALLOC|VMS_ARRAY_OF_POINTER won't + * cause the individual entries to be allocated. */ + VMS_ALLOC = 0x2000, + + /* Multiply the number of entries given by the integer at opaque + + * VMStateField.num_offset (see VMS_VARRAY*) with VMStateField.num + * to determine the number of entries in the array. Only valid in + * combination with one of VMS_VARRAY*. */ + VMS_MULTIPLY_ELEMENTS = 0x4000, + + /* A structure field that is like VMS_STRUCT, but uses + * VMStateField.struct_version_id to tell which version of the + * structure we are referencing to use. */ + VMS_VSTRUCT = 0x8000, + + /* Marker for end of list */ + VMS_END = 0x10000 +}; + +struct VMStateField { + const char *name; + size_t offset; + size_t size; + size_t start; + int num; + size_t num_offset; + size_t size_offset; + const VMStateInfo *info; + enum VMStateFlags flags; + const VMStateDescription *vmsd; + int version_id; + int struct_version_id; + bool (*field_exists)(void *opaque, int version_id); +}; + +struct VMStateDescription { + const char *name; + int version_id; + int (*pre_load)(void *opaque); + int (*post_load)(void *opaque, int version_id); + int (*pre_save)(void *opaque); + VMStateField *fields; +}; + + +extern const VMStateInfo slirp_vmstate_info_int16; +extern const VMStateInfo slirp_vmstate_info_int32; +extern const VMStateInfo slirp_vmstate_info_uint8; +extern const VMStateInfo slirp_vmstate_info_uint16; +extern const VMStateInfo slirp_vmstate_info_uint32; + +/** Put this in the stream when migrating a null pointer.*/ +#define VMS_NULLPTR_MARKER (0x30U) /* '0' */ +extern const VMStateInfo slirp_vmstate_info_nullptr; + +extern const VMStateInfo slirp_vmstate_info_buffer; +extern const VMStateInfo slirp_vmstate_info_tmp; + +#ifdef __GNUC__ +#define type_check_array(t1, t2, n) ((t1(*)[n])0 - (t2 *)0) +#define type_check_pointer(t1, t2) ((t1 **)0 - (t2 *)0) +#define typeof_field(type, field) typeof(((type *)0)->field) +#define type_check(t1, t2) ((t1 *)0 - (t2 *)0) +#else +#define type_check_array(t1, t2, n) 0 +#define type_check_pointer(t1, t2) 0 +#define typeof_field(type, field) (((type *)0)->field) +#define type_check(t1, t2) 0 +#endif + +#define vmstate_offset_value(_state, _field, _type) \ + (offsetof(_state, _field) + type_check(_type, typeof_field(_state, _field))) + +#define vmstate_offset_pointer(_state, _field, _type) \ + (offsetof(_state, _field) + \ + type_check_pointer(_type, typeof_field(_state, _field))) + +#define vmstate_offset_array(_state, _field, _type, _num) \ + (offsetof(_state, _field) + \ + type_check_array(_type, typeof_field(_state, _field), _num)) + +#define vmstate_offset_buffer(_state, _field) \ + vmstate_offset_array(_state, _field, uint8_t, \ + sizeof(typeof_field(_state, _field))) + +/* In the macros below, if there is a _version, that means the macro's + * field will be processed only if the version being received is >= + * the _version specified. In general, if you add a new field, you + * would increment the structure's version and put that version + * number into the new field so it would only be processed with the + * new version. + * + * In particular, for VMSTATE_STRUCT() and friends the _version does + * *NOT* pick the version of the sub-structure. It works just as + * specified above. The version of the top-level structure received + * is passed down to all sub-structures. This means that the + * sub-structures must have version that are compatible with all the + * structures that use them. + * + * If you want to specify the version of the sub-structure, use + * VMSTATE_VSTRUCT(), which allows the specific sub-structure version + * to be directly specified. + */ + +#define VMSTATE_SINGLE_TEST(_field, _state, _test, _version, _info, _type) \ + { \ + .name = (stringify(_field)), .version_id = (_version), \ + .field_exists = (_test), .size = sizeof(_type), .info = &(_info), \ + .flags = VMS_SINGLE, \ + .offset = vmstate_offset_value(_state, _field, _type), \ + } + +#define VMSTATE_ARRAY(_field, _state, _num, _version, _info, _type) \ + { \ + .name = (stringify(_field)), .version_id = (_version), .num = (_num), \ + .info = &(_info), .size = sizeof(_type), .flags = VMS_ARRAY, \ + .offset = vmstate_offset_array(_state, _field, _type, _num), \ + } + +#define VMSTATE_STRUCT_TEST(_field, _state, _test, _version, _vmsd, _type) \ + { \ + .name = (stringify(_field)), .version_id = (_version), \ + .field_exists = (_test), .vmsd = &(_vmsd), .size = sizeof(_type), \ + .flags = VMS_STRUCT, \ + .offset = vmstate_offset_value(_state, _field, _type), \ + } + +#define VMSTATE_STRUCT_POINTER_V(_field, _state, _version, _vmsd, _type) \ + { \ + .name = (stringify(_field)), .version_id = (_version), \ + .vmsd = &(_vmsd), .size = sizeof(_type *), \ + .flags = VMS_STRUCT | VMS_POINTER, \ + .offset = vmstate_offset_pointer(_state, _field, _type), \ + } + +#define VMSTATE_STRUCT_ARRAY_TEST(_field, _state, _num, _test, _version, \ + _vmsd, _type) \ + { \ + .name = (stringify(_field)), .num = (_num), .field_exists = (_test), \ + .version_id = (_version), .vmsd = &(_vmsd), .size = sizeof(_type), \ + .flags = VMS_STRUCT | VMS_ARRAY, \ + .offset = vmstate_offset_array(_state, _field, _type, _num), \ + } + +#define VMSTATE_STATIC_BUFFER(_field, _state, _version, _test, _start, _size) \ + { \ + .name = (stringify(_field)), .version_id = (_version), \ + .field_exists = (_test), .size = (_size - _start), \ + .info = &slirp_vmstate_info_buffer, .flags = VMS_BUFFER, \ + .offset = vmstate_offset_buffer(_state, _field) + _start, \ + } + +#define VMSTATE_VBUFFER_UINT32(_field, _state, _version, _test, _field_size) \ + { \ + .name = (stringify(_field)), .version_id = (_version), \ + .field_exists = (_test), \ + .size_offset = vmstate_offset_value(_state, _field_size, uint32_t), \ + .info = &slirp_vmstate_info_buffer, \ + .flags = VMS_VBUFFER | VMS_POINTER, \ + .offset = offsetof(_state, _field), \ + } + +#define QEMU_BUILD_BUG_ON_STRUCT(x) \ + struct { \ + int : (x) ? -1 : 1; \ + } + +#define QEMU_BUILD_BUG_ON_ZERO(x) \ + (sizeof(QEMU_BUILD_BUG_ON_STRUCT(x)) - sizeof(QEMU_BUILD_BUG_ON_STRUCT(x))) + +/* Allocate a temporary of type 'tmp_type', set tmp->parent to _state + * and execute the vmsd on the temporary. Note that we're working with + * the whole of _state here, not a field within it. + * We compile time check that: + * That _tmp_type contains a 'parent' member that's a pointer to the + * '_state' type + * That the pointer is right at the start of _tmp_type. + */ +#define VMSTATE_WITH_TMP(_state, _tmp_type, _vmsd) \ + { \ + .name = "tmp", \ + .size = sizeof(_tmp_type) + \ + QEMU_BUILD_BUG_ON_ZERO(offsetof(_tmp_type, parent) != 0) + \ + type_check_pointer(_state, typeof_field(_tmp_type, parent)), \ + .vmsd = &(_vmsd), .info = &slirp_vmstate_info_tmp, \ + } + +#define VMSTATE_SINGLE(_field, _state, _version, _info, _type) \ + VMSTATE_SINGLE_TEST(_field, _state, NULL, _version, _info, _type) + +#define VMSTATE_STRUCT(_field, _state, _version, _vmsd, _type) \ + VMSTATE_STRUCT_TEST(_field, _state, NULL, _version, _vmsd, _type) + +#define VMSTATE_STRUCT_POINTER(_field, _state, _vmsd, _type) \ + VMSTATE_STRUCT_POINTER_V(_field, _state, 0, _vmsd, _type) + +#define VMSTATE_STRUCT_ARRAY(_field, _state, _num, _version, _vmsd, _type) \ + VMSTATE_STRUCT_ARRAY_TEST(_field, _state, _num, NULL, _version, _vmsd, \ + _type) + +#define VMSTATE_INT16_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, slirp_vmstate_info_int16, int16_t) +#define VMSTATE_INT32_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, slirp_vmstate_info_int32, int32_t) + +#define VMSTATE_UINT8_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, slirp_vmstate_info_uint8, uint8_t) +#define VMSTATE_UINT16_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, slirp_vmstate_info_uint16, uint16_t) +#define VMSTATE_UINT32_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, slirp_vmstate_info_uint32, uint32_t) + +#define VMSTATE_INT16(_f, _s) VMSTATE_INT16_V(_f, _s, 0) +#define VMSTATE_INT32(_f, _s) VMSTATE_INT32_V(_f, _s, 0) + +#define VMSTATE_UINT8(_f, _s) VMSTATE_UINT8_V(_f, _s, 0) +#define VMSTATE_UINT16(_f, _s) VMSTATE_UINT16_V(_f, _s, 0) +#define VMSTATE_UINT32(_f, _s) VMSTATE_UINT32_V(_f, _s, 0) + +#define VMSTATE_UINT16_TEST(_f, _s, _t) \ + VMSTATE_SINGLE_TEST(_f, _s, _t, 0, slirp_vmstate_info_uint16, uint16_t) + +#define VMSTATE_UINT32_TEST(_f, _s, _t) \ + VMSTATE_SINGLE_TEST(_f, _s, _t, 0, slirp_vmstate_info_uint32, uint32_t) + +#define VMSTATE_INT16_ARRAY_V(_f, _s, _n, _v) \ + VMSTATE_ARRAY(_f, _s, _n, _v, slirp_vmstate_info_int16, int16_t) + +#define VMSTATE_INT16_ARRAY(_f, _s, _n) VMSTATE_INT16_ARRAY_V(_f, _s, _n, 0) + +#define VMSTATE_BUFFER_V(_f, _s, _v) \ + VMSTATE_STATIC_BUFFER(_f, _s, _v, NULL, 0, sizeof(typeof_field(_s, _f))) + +#define VMSTATE_BUFFER(_f, _s) VMSTATE_BUFFER_V(_f, _s, 0) + +#define VMSTATE_END_OF_LIST() \ + { \ + .flags = VMS_END, \ + } + +#endif /* VMSTATE_H_ */ diff --git a/src/net/libslirp/test/ncsitest.c b/src/net/libslirp/test/ncsitest.c new file mode 100644 index 00000000..f5ee0b5a --- /dev/null +++ b/src/net/libslirp/test/ncsitest.c @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. (http://www.meta.com) + */ + +/* + * This test verifies slirp responses to NC-SI commands. + */ + +#include +#include +#include + +#include "slirp.h" +#include "ncsi-pkt.h" + +#define NCSI_RESPONSE_CAPACITY 1024 + +static void test_ncsi_get_version_id(Slirp *slirp) +{ + slirp->mfr_id = 0xabcdef01; + + uint8_t command[] = { + /* Destination MAC */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* Source MAC */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* Ethertype */ + 0x88, 0xf8, + /* NC-SI Control packet header */ + 0x00, /* MC ID */ + 0x01, /* Header revision */ + 0x00, /* Reserved */ + 0x01, /* Instance ID */ + 0x15, /* Control Packet Type */ + 0x00, /* Channel ID */ + 0x00, /* Reserved */ + 0x00, /* Payload length */ + 0x00, 0x00, 0x00, 0x00, /* Reserved */ + 0x00, 0x00, 0x00, 0x00, /* Reserved */ + }; + slirp_input(slirp, command, sizeof(command)); + + const struct ncsi_rsp_gvi_pkt *gvi = (const struct ncsi_rsp_gvi_pkt *) ((const char*) slirp->opaque + ETH_HLEN); + + assert(ntohs(gvi->rsp.code) == NCSI_PKT_RSP_C_COMPLETED); + assert(ntohs(gvi->rsp.code) == NCSI_PKT_RSP_R_NO_ERROR); + assert(ntohl(gvi->mf_id) == slirp->mfr_id); + + slirp->mfr_id = 0; +} + +static void test_ncsi_oem_mlx_unsupported_command(Slirp *slirp) +{ + uint8_t command[] = { + /* Destination MAC */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* Source MAC */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* Ethertype */ + 0x88, 0xf8, + /* NC-SI Control packet header */ + 0x00, /* MC ID */ + 0x01, /* Header revision */ + 0x00, /* Reserved */ + 0x01, /* Instance ID */ + 0x50, /* Control Packet Type */ + 0x00, /* Channel ID */ + 0x00, /* Reserved */ + 0x08, /* Payload length */ + 0x00, 0x00, 0x00, 0x00, /* Reserved */ + 0x00, 0x00, 0x00, 0x00, /* Reserved */ + /* NC-SI OEM packet header */ + 0x00, 0x00, 0x81, 0x19, /* Manufacturer ID: Mellanox */ + /* Vendor Data */ + 0xff, /* Command Revision */ + 0xff, /* Command ID */ + 0x00, /* Parameter */ + 0x00, /* Optional data */ + }; + const struct ncsi_rsp_oem_pkt *oem = (const struct ncsi_rsp_oem_pkt *) ((const char*) slirp->opaque + ETH_HLEN); + + slirp->mfr_id = 0x00000000; + slirp_input(slirp, command, sizeof(command)); + + assert(ntohs(oem->rsp.code) == NCSI_PKT_RSP_C_UNSUPPORTED); + assert(ntohs(oem->rsp.reason) == NCSI_PKT_RSP_R_UNKNOWN); + assert(ntohl(oem->mfr_id) == 0x8119); + + slirp->mfr_id = 0x8119; + slirp_input(slirp, command, sizeof(command)); + + assert(ntohs(oem->rsp.code) == NCSI_PKT_RSP_C_UNSUPPORTED); + assert(ntohs(oem->rsp.reason) == NCSI_PKT_RSP_R_UNKNOWN); + assert(ntohl(oem->mfr_id) == 0x8119); +} + +static void test_ncsi_oem_mlx_gma(Slirp *slirp) +{ + uint8_t oob_eth_addr[ETH_ALEN] = {0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe}; + uint8_t command[] = { + /* Destination MAC */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* Source MAC */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* Ethertype */ + 0x88, 0xf8, + /* NC-SI Control packet header */ + 0x00, /* MC ID */ + 0x01, /* Header revision */ + 0x00, /* Reserved */ + 0x01, /* Instance ID */ + 0x50, /* Control Packet Type */ + 0x00, /* Channel ID */ + 0x00, /* Reserved */ + 0x08, /* Payload length */ + 0x00, 0x00, 0x00, 0x00, /* Reserved */ + 0x00, 0x00, 0x00, 0x00, /* Reserved */ + /* NC-SI OEM packet header */ + 0x00, 0x00, 0x81, 0x19, /* Manufacturer ID: Mellanox */ + /* Vendor Data */ + 0x00, /* Command Revision */ + 0x00, /* Command ID */ + 0x1b, /* Parameter */ + 0x00, /* Optional data */ + }; + const struct ncsi_rsp_oem_pkt *oem = (const struct ncsi_rsp_oem_pkt *) ((const char*) slirp->opaque + ETH_HLEN); + + memset(slirp->oob_eth_addr, 0, ETH_ALEN); + slirp->mfr_id = 0x8119; + slirp_input(slirp, command, sizeof(command)); + + assert(ntohs(oem->rsp.code) == NCSI_PKT_RSP_C_COMPLETED); + assert(ntohs(oem->rsp.reason) == NCSI_PKT_RSP_R_NO_ERROR); + assert(ntohl(oem->mfr_id) == slirp->mfr_id); + assert(ntohs(oem->rsp.common.length) == MLX_GMA_PAYLOAD_LEN); + assert(memcmp(slirp->oob_eth_addr, &oem->data[MLX_MAC_ADDR_OFFSET], ETH_ALEN) == 0); + assert(oem->data[MLX_GMA_STATUS_OFFSET] == 0); + + memcpy(slirp->oob_eth_addr, oob_eth_addr, ETH_ALEN); + slirp_input(slirp, command, sizeof(command)); + + assert(ntohs(oem->rsp.code) == NCSI_PKT_RSP_C_COMPLETED); + assert(ntohs(oem->rsp.reason) == NCSI_PKT_RSP_R_NO_ERROR); + assert(ntohl(oem->mfr_id) == slirp->mfr_id); + assert(ntohs(oem->rsp.common.length) == MLX_GMA_PAYLOAD_LEN); + assert(memcmp(oob_eth_addr, &oem->data[MLX_MAC_ADDR_OFFSET], ETH_ALEN) == 0); + assert(oem->data[MLX_GMA_STATUS_OFFSET] == 1); +} + +static slirp_ssize_t send_packet(const void *buf, size_t len, void *opaque) +{ + assert(len <= NCSI_RESPONSE_CAPACITY); + memcpy(opaque, buf, len); + return len; +} + +int main(int argc, char *argv[]) +{ + SlirpConfig config = { + .version = SLIRP_CONFIG_VERSION_MAX, + }; + SlirpCb callbacks = { + .send_packet = send_packet, + }; + Slirp *slirp = NULL; + uint8_t ncsi_response[NCSI_RESPONSE_CAPACITY]; + + slirp = slirp_new(&config, &callbacks, ncsi_response); + + test_ncsi_get_version_id(slirp); + test_ncsi_oem_mlx_unsupported_command(slirp); + test_ncsi_oem_mlx_gma(slirp); + + slirp_cleanup(slirp); +} diff --git a/src/net/libslirp/test/pingtest.c b/src/net/libslirp/test/pingtest.c new file mode 100644 index 00000000..247a5b10 --- /dev/null +++ b/src/net/libslirp/test/pingtest.c @@ -0,0 +1,490 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (c) 2021-2022 Samuel Thibault + */ + +/* + * This simple test configures slirp and tries to ping it + * + * Note: to make this example actually be able to use the outside world, you + * need to either + * - run as root + * - set /proc/sys/net/ipv4/ping_group_range to allow sending ICMP echo requests + * - run a UDP echo server on the target + */ + +#include +#include +#include +#include + +#include "libslirp.h" + +//#define _WIN32 +#ifdef _WIN32 +//#include +#include +static int slirp_inet_aton(const char *cp, struct in_addr *ia) +{ + uint32_t addr = inet_addr(cp); + if (addr == 0xffffffff) { + return 0; + } + ia->s_addr = addr; + return 1; +} +#define inet_aton slirp_inet_aton +#else +#include +#include +#include +#endif + +/* Dumb simulation tick: 100ms */ +#define TICK 100 + +static Slirp *slirp; +static bool done; +static int64_t mytime; + +/* Print a frame for debugging */ +static void print_frame(const uint8_t *data, size_t len) { + int i; + + printf("\ngot packet size %zd:\n", len); + for (i = 0; i < len; i++) { + if (i && i % 16 == 0) + printf("\n"); + printf("%s%02x", i % 16 ? " " : "", data[i]); + } + if (len % 16 != 0) + printf("\n"); + printf("\n"); +} + +/* Classical 16bit checksum */ +static void checksum(uint8_t *data, size_t size, uint8_t *cksum) { + uint32_t sum = 0; + int i; + + cksum[0] = 0; + cksum[1] = 0; + + for (i = 0; i+1 < size; i += 2) + sum += (((uint16_t) data[i]) << 8) + data[i+1]; + if (i < size) /* Odd number of bytes */ + sum += ((uint16_t) data[i]) << 8; + + sum = (sum & 0xffff) + (sum >> 16); + sum = (sum & 0xffff) + (sum >> 16); + sum = ~sum; + + cksum[0] = sum >> 8; + cksum[1] = sum; +} + +/* This is called when receiving a packet from the virtual network, for the + * guest */ +static slirp_ssize_t send_packet(const void *buf, size_t len, void *opaque) { + const uint8_t *data = buf; + + assert(len >= 14); + + if (data[12] == 0x86 && + data[13] == 0xdd) { + /* Ignore IPv6 */ + return len; + } + + print_frame(data, len); + + if (data[12] == 0x08 && + data[13] == 0x06) { + /* ARP */ + /* We expect receiving an ARP request for our address */ + + /* Ethernet address type */ + assert(data[14] == 0x00); + assert(data[15] == 0x01); + + /* IPv4 address type */ + assert(data[16] == 0x08); + assert(data[17] == 0x00); + + /* Ethernet addresses are 6 bytes long */ + assert(data[18] == 0x06); + + /* IPv4 addresses are 4 bytes long */ + assert(data[19] == 0x04); + + /* Opcode: ARP request */ + assert(data[20] == 0x00); + assert(data[21] == 0x01); + + /* Ok, reply! */ + uint8_t myframe[] = { + /*** Ethernet ***/ + /* dst */ + 0x52, 0x55, 0x0a, 0x00, 0x02, 0x02, + /* src */ + 0x52, 0x55, 0x0a, 0x00, 0x02, 0x0e, + /* Type: ARP */ + 0x08, 0x06, + + /* ether, IPv4, */ + 0x00, 0x01, 0x08, 0x00, + /* elen, IPlen */ + 0x06, 0x04, + /* ARP reply */ + 0x00, 0x02, + + /* Our ethernet address */ + 0x52, 0x55, 0x0a, 0x00, 0x02, 0x0e, + /* Our IP address */ + 0x0a, 0x00, 0x02, 0x0e, + + /* Host ethernet address */ + 0x52, 0x55, 0x0a, 0x00, 0x02, 0x02, + /* Host IP address */ + 0x0a, 0x00, 0x02, 0x02, + }; + + slirp_input(slirp, myframe, sizeof(myframe)); + } + + if (data[12] == 0x08 && + data[13] == 0x00) { + /* IPv4 */ + assert(len >= 14 + 20); + + /* We expect receiving the ICMP echo reply for our echo request */ + + /* IPv + hlen */ + assert(data[14] == 0x45); + + /* proto: ICMP */ + assert(data[23] == 0x01); + + /* ICMP */ + assert(len >= 14 + 20 + 8 + 4); + + /* ICMP type: reply */ + assert(data[34] == 0x00); + + /* Check the data */ + assert(data[42] == 0xde); + assert(data[43] == 0xad); + assert(data[44] == 0xbe); + assert(data[45] == 0xef); + + /* Got the answer! */ + printf("got it!\n"); + done = 1; + } + + return len; +} + +static void guest_error(const char *msg, void *opaque) { + printf("guest error %s\n", msg); +} + + +/* + * Dumb timer implementation + */ +static int64_t clock_get_ns(void *opaque) { + return mytime; +} + +struct timer { + SlirpTimerId id; + void *cb_opaque; + int64_t expire; + struct timer *next; +}; + +static struct timer *timer_queue; + +static void *timer_new_opaque(SlirpTimerId id, void *cb_opaque, void *opaque) { + struct timer *new_timer = malloc(sizeof(*new_timer)); + new_timer->id = id; + new_timer->cb_opaque = cb_opaque; + new_timer->next = NULL; + return new_timer; +} + +static void timer_free(void *_timer, void *opaque) { + struct timer *timer = _timer; + struct timer **t; + + for (t = &timer_queue; *t != NULL; *t = (*t)->next) { + if (*t == timer) { + /* Not expired yet, drop it */ + *t = timer->next; + break; + } + } + + free(timer); +} + +static void timer_mod(void *_timer, int64_t expire_time, void *opaque) { + struct timer *timer = _timer; + struct timer **t; + + timer->expire = expire_time * 1000 * 1000; + + for (t = &timer_queue; *t != NULL; *t = (*t)->next) { + if (expire_time < (*t)->expire) + break; + } + + timer->next = *t; + *t = timer; +} + +static void timer_check(Slirp *slirp) { + while (timer_queue && timer_queue->expire <= mytime) + { + struct timer *t = timer_queue; + printf("handling %p at time %lu\n", + t, (unsigned long) timer_queue->expire); + timer_queue = t->next; + slirp_handle_timer(slirp, t->id, t->cb_opaque); + } +} + +static uint32_t timer_timeout(void) { + if (timer_queue) + { + uint32_t timeout = (timer_queue->expire - mytime) / (1000 * 1000); + if (timeout < TICK) + return timeout; + } + + return TICK; +} + + +/* + * Dumb polling implementation + */ +static int npoll; +static void register_poll_fd(int fd, void *opaque) { + /* We might want to prepare for polling on fd */ + npoll++; +} + +static void unregister_poll_fd(int fd, void *opaque) { + /* We might want to clear polling on fd */ + npoll--; +} + +static void notify(void *opaque) { + /* No need for this in single-thread case */ +} + +#ifdef _WIN32 +/* select() variant */ +static fd_set readfds, writefds, exceptfds; +static int maxfd; +static int add_poll_cb(int fd, int events, void *opaque) +{ + if (events & SLIRP_POLL_IN) + FD_SET(fd, &readfds); + if (events & SLIRP_POLL_OUT) + FD_SET(fd, &writefds); + if (events & SLIRP_POLL_PRI) + FD_SET(fd, &exceptfds); + if (maxfd < fd) + maxfd = fd; + return fd; +} + +static int get_revents_cb(int idx, void *opaque) +{ + int event = 0; + if (FD_ISSET(idx, &readfds)) + event |= SLIRP_POLL_IN; + if (FD_ISSET(idx, &writefds)) + event |= SLIRP_POLL_OUT; + if (FD_ISSET(idx, &exceptfds)) + event |= SLIRP_POLL_PRI; + return event; +} + +static void dopoll(uint32_t timeout) { + int err; + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + maxfd = 0; + + slirp_pollfds_fill(slirp, &timeout, add_poll_cb, NULL); + printf("we will use timeout %u\n", (unsigned) timeout); + + struct timeval tv = { + .tv_sec = timeout / 1000, + .tv_usec = (timeout % 1000) * 1000, + }; + err = select(maxfd+1, &readfds, &writefds, &exceptfds, &tv); + + slirp_pollfds_poll(slirp, err < 0, get_revents_cb, NULL); +} +#else +/* poll() variant */ +static struct pollfd *fds; +static int cur_poll; +static int add_poll_cb(int fd, int events, void *opaque) +{ + short poll_events = 0; + + assert(cur_poll < npoll); + fds[cur_poll].fd = fd; + + if (events & SLIRP_POLL_IN) + poll_events |= POLLIN; + if (events & SLIRP_POLL_OUT) + poll_events |= POLLOUT; + if (events & SLIRP_POLL_PRI) + poll_events |= POLLPRI; + fds[cur_poll].events = poll_events; + + return cur_poll++; +} + +static int get_revents_cb(int idx, void *opaque) +{ + return fds[idx].revents; +} + +static void dopoll(uint32_t timeout) { + int err; + fds = malloc(sizeof(*fds) * npoll); + cur_poll = 0; + + slirp_pollfds_fill(slirp, &timeout, add_poll_cb, NULL); + printf("we will use timeout %u\n", (unsigned) timeout); + + err = poll(fds, cur_poll, timeout); + + slirp_pollfds_poll(slirp, err < 0, get_revents_cb, NULL); + + free(fds); +} +#endif + + +static struct SlirpCb callbacks = { + .send_packet = send_packet, + .guest_error = guest_error, + .clock_get_ns = clock_get_ns, + .timer_new_opaque = timer_new_opaque, + .timer_free = timer_free, + .timer_mod = timer_mod, + .register_poll_fd = register_poll_fd, + .unregister_poll_fd = unregister_poll_fd, + .notify = notify, +}; + + +int main(int argc, char *argv[]) { + SlirpConfig config = { + .version = 4, + .restricted = false, + .in_enabled = true, + .vnetwork.s_addr = htonl(0x0a000200), + .vnetmask.s_addr = htonl(0xffffff00), + .vhost.s_addr = htonl(0x0a000202), + .vdhcp_start.s_addr = htonl(0x0a00020f), + .vnameserver.s_addr = htonl(0x0a000203), + .disable_host_loopback = false, + .enable_emu = false, + .disable_dns = false, + }; + uint32_t timeout = 0; + + printf("Slirp version %s\n", slirp_version_string()); + +#if !defined(_WIN32) + inet_pton(AF_INET6, "fec0::", &config.vprefix_addr6); + config.vprefix_len = 64; + config.vhost6 = config.vprefix_addr6; + config.vhost6.s6_addr[15] = 2; + config.vnameserver6 = config.vprefix_addr6; + config.vnameserver6.s6_addr[15] = 2; + config.in6_enabled = true, +#endif + + slirp = slirp_new(&config, &callbacks, NULL); + + /* Send echo request */ + uint8_t myframe[] = { + /*** Ethernet ***/ + /* dst */ + 0x52, 0x55, 0x0a, 0x00, 0x02, 0x02, + /* src */ + 0x52, 0x55, 0x0a, 0x00, 0x02, 0x0e, + /* Type: IPv4 */ + 0x08, 0x00, + + /*** IPv4 ***/ + /* vhl,tos, len */ + 0x45, 0x00, 0x00, 0x20, + /* id, off (DF) */ + 0x68, 0xd7, 0x40, 0x00, + /* ttl,pro, cksum */ + 0x40, 0x01, 0x00, 0x00, + /* src */ + 0x0a, 0x00, 0x02, 0x0e, + /* dst */ + 0x00, 0x00, 0x00, 0x00, + + /*** ICMPv4 ***/ + /* type, code, cksum */ + 0x08, 0x00, 0x00, 0x00, + /* id, seq */ + 0x01, 0xec, 0x00, 0x01, + /* data */ + 0xde, 0xad, 0xbe, 0xef, + }; + + struct in_addr in_addr = { .s_addr = htonl(0x0a000202) }; + if (argc > 1) { + if (inet_aton(argv[1], &in_addr) == 0) { + printf("usage: %s [destination IPv4 address]\n", argv[0]); + exit(EXIT_FAILURE); + } + } + uint32_t addr = ntohl(in_addr.s_addr); + myframe[30] = addr >> 24; + myframe[31] = addr >> 16; + myframe[32] = addr >> 8; + myframe[33] = addr >> 0; + + /* IPv4 header checksum */ + checksum(&myframe[14], 20, &myframe[24]); + /* ICMP header checksum */ + checksum(&myframe[34], 12, &myframe[36]); + + slirp_input(slirp, myframe, sizeof(myframe)); + + /* Wait for echo reply */ + while (!done) { + printf("time %lu\n", (unsigned long) mytime); + + timer_check(slirp); + /* Here we make the virtual time wait like the real time, but we could + * make it wait differently */ + timeout = timer_timeout(); + printf("we wish timeout %u\n", (unsigned) timeout); + + dopoll(timeout); + + /* Fake that the tick elapsed */ + mytime += TICK * 1000 * 1000; + } + + slirp_cleanup(slirp); +} diff --git a/src/frontend/qt_sdl/pcap/bluetooth.h b/src/net/pcap/bluetooth.h similarity index 100% rename from src/frontend/qt_sdl/pcap/bluetooth.h rename to src/net/pcap/bluetooth.h diff --git a/src/frontend/qt_sdl/pcap/bpf.h b/src/net/pcap/bpf.h similarity index 100% rename from src/frontend/qt_sdl/pcap/bpf.h rename to src/net/pcap/bpf.h diff --git a/src/frontend/qt_sdl/pcap/can_socketcan.h b/src/net/pcap/can_socketcan.h similarity index 100% rename from src/frontend/qt_sdl/pcap/can_socketcan.h rename to src/net/pcap/can_socketcan.h diff --git a/src/frontend/qt_sdl/pcap/compiler-tests.h b/src/net/pcap/compiler-tests.h similarity index 100% rename from src/frontend/qt_sdl/pcap/compiler-tests.h rename to src/net/pcap/compiler-tests.h diff --git a/src/frontend/qt_sdl/pcap/dlt.h b/src/net/pcap/dlt.h similarity index 100% rename from src/frontend/qt_sdl/pcap/dlt.h rename to src/net/pcap/dlt.h diff --git a/src/frontend/qt_sdl/pcap/funcattrs.h b/src/net/pcap/funcattrs.h similarity index 100% rename from src/frontend/qt_sdl/pcap/funcattrs.h rename to src/net/pcap/funcattrs.h diff --git a/src/frontend/qt_sdl/pcap/ipnet.h b/src/net/pcap/ipnet.h similarity index 100% rename from src/frontend/qt_sdl/pcap/ipnet.h rename to src/net/pcap/ipnet.h diff --git a/src/frontend/qt_sdl/pcap/namedb.h b/src/net/pcap/namedb.h similarity index 100% rename from src/frontend/qt_sdl/pcap/namedb.h rename to src/net/pcap/namedb.h diff --git a/src/frontend/qt_sdl/pcap/nflog.h b/src/net/pcap/nflog.h similarity index 100% rename from src/frontend/qt_sdl/pcap/nflog.h rename to src/net/pcap/nflog.h diff --git a/src/frontend/qt_sdl/pcap/pcap-inttypes.h b/src/net/pcap/pcap-inttypes.h similarity index 100% rename from src/frontend/qt_sdl/pcap/pcap-inttypes.h rename to src/net/pcap/pcap-inttypes.h diff --git a/src/frontend/qt_sdl/pcap/pcap.h b/src/net/pcap/pcap.h similarity index 100% rename from src/frontend/qt_sdl/pcap/pcap.h rename to src/net/pcap/pcap.h diff --git a/src/frontend/qt_sdl/pcap/sll.h b/src/net/pcap/sll.h similarity index 100% rename from src/frontend/qt_sdl/pcap/sll.h rename to src/net/pcap/sll.h diff --git a/src/frontend/qt_sdl/pcap/usb.h b/src/net/pcap/usb.h similarity index 100% rename from src/frontend/qt_sdl/pcap/usb.h rename to src/net/pcap/usb.h diff --git a/src/frontend/qt_sdl/pcap/vlan.h b/src/net/pcap/vlan.h similarity index 100% rename from src/frontend/qt_sdl/pcap/vlan.h rename to src/net/pcap/vlan.h diff --git a/src/sha1/sha1.c b/src/sha1/sha1.c index c0052b70..c34ace30 100644 --- a/src/sha1/sha1.c +++ b/src/sha1/sha1.c @@ -27,6 +27,9 @@ A million repetitions of "a" #if defined(__sun) #include "solarisfixes.h" #endif +#if defined(__HAIKU__) +#include +#endif #include "sha1.h" #ifndef BYTE_ORDER diff --git a/src/teakra/src/CMakeLists.txt b/src/teakra/src/CMakeLists.txt index b96c500b..30683374 100644 --- a/src/teakra/src/CMakeLists.txt +++ b/src/teakra/src/CMakeLists.txt @@ -32,11 +32,16 @@ add_library(teakra register.h shared_memory.h teakra.cpp - test.h - test_generator.cpp - test_generator.h ) +if (TEAKRA_BUILD_UNIT_TESTS) + target_sources(teakra PUBLIC + test.h + test_generator.cpp + test_generator.h + ) +endif() + create_target_directory_groups(teakra) target_link_libraries(teakra PRIVATE Threads::Threads) diff --git a/src/types.h b/src/types.h index 86a10aa7..e7fccafb 100644 --- a/src/types.h +++ b/src/types.h @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. diff --git a/src/version.h b/src/version.h.in similarity index 59% rename from src/version.h rename to src/version.h.in index 131c610d..9b4cd8ce 100644 --- a/src/version.h +++ b/src/version.h.in @@ -1,5 +1,5 @@ /* - Copyright 2016-2023 melonDS team + Copyright 2016-2024 melonDS team This file is part of melonDS. @@ -19,7 +19,17 @@ #ifndef VERSION_H #define VERSION_H -#define MELONDS_URL "https://melonds.kuribo64.net/" +#define MELONDS_URL "${melonDS_HOMEPAGE_URL}" + +#define MELONDS_VERSION_BASE "${melonDS_VERSION}" +#define MELONDS_VERSION_SUFFIX "${MELONDS_VERSION_SUFFIX}" +#define MELONDS_VERSION MELONDS_VERSION_BASE MELONDS_VERSION_SUFFIX + +#ifdef MELONDS_EMBED_BUILD_INFO +#define MELONDS_GIT_BRANCH "${MELONDS_GIT_BRANCH}" +#define MELONDS_GIT_HASH "${MELONDS_GIT_HASH}" +#define MELONDS_BUILD_PROVIDER "${MELONDS_BUILD_PROVIDER}" +#endif #endif // VERSION_H diff --git a/tools/mac-libs.rb b/tools/mac-libs.rb index e5d4dd57..e609bd8e 100755 --- a/tools/mac-libs.rb +++ b/tools/mac-libs.rb @@ -77,6 +77,19 @@ def expand_load_path(lib, path) return nil end +def detect_framework(lib) + framework = lib.match(/(.*).framework/) + framework = framework.to_s if framework + + if framework + fwname = File.basename(framework) + fwlib = lib.sub(framework + "/", "") + return true, framework, fwname, fwlib + else + return false + end +end + def system_path?(path) path.match(/^\/usr\/lib|^\/System/) != nil end @@ -85,9 +98,10 @@ def system_lib?(lib) system_path? File.dirname(lib) end -def install_name_tool(exec, action, path1, path2 = nil) - args = ["-#{action.to_s}", path1] - args << path2 if path2 != nil +def install_name_tool(exec, *options) + args = options.map do |it| + if it.is_a? Symbol then "-#{it.to_s}" else it end + end Open3.popen3("install_name_tool", *args, exec) do |stdin, stdout, stderr, thread| print stdout.read @@ -99,58 +113,68 @@ def install_name_tool(exec, action, path1, path2 = nil) end def strip(lib) - out, _ = Open3.capture2("strip", "-no_code_signature_warning", "-Sx", lib) + out, _ = Open3.capture2("xcrun", "strip", "-no_code_signature_warning", "-Sx", lib) print out end def fixup_libs(prog, orig_path) throw "fixup_libs: #{prog} doesn't exist" unless File.exist? prog - libs = get_load_libs(prog).map { |it| expand_load_path(orig_path, it) }.select { |it| not system_lib? it[0] } + libs = get_load_libs(prog) + .map { |it| expand_load_path(orig_path, it) } + .select { |it| not system_lib? it[0] } FileUtils.chmod("u+w", prog) strip prog + changes = [] + + isfw, _, fwname, fwlib = detect_framework(prog) + if isfw then + changes += [:id, File.join("@rpath", fwname, fwlib)] + else + changes += [:id, File.join("@rpath", File.basename(prog))] + end + libs.each do |lib| libpath, libtype = lib if File.basename(libpath) == File.basename(prog) if libtype == :absolute - install_name_tool prog, :change, libpath, File.join("@rpath", File.basename(libpath)) + changes += [:change, libpath, File.join("@rpath", File.basename(libpath))] end next end - framework = libpath.match(/(.*).framework/) - framework = framework.to_s if framework - - if framework - fwlib = libpath.sub(framework + "/", "") - fwname = File.basename(framework) + is_framework, fwpath, fwname, fwlib = detect_framework(libpath) + if is_framework unless libtype == :rpath - install_name_tool prog, :change, libpath, File.join("@rpath", fwname, fwlib) + changes += [:change, libpath, File.join("@rpath", fwname, fwlib)] end next if File.exist? File.join(frameworks_dir, fwname) - expath, _ = expand_load_path(orig_path, framework) + expath, _ = expand_load_path(orig_path, fwpath) FileUtils.cp_r(expath, frameworks_dir, preserve: true) FileUtils.chmod_R("u+w", File.join(frameworks_dir, fwname)) fixup_libs File.join(frameworks_dir, fwname, fwlib), libpath else - libname = File.basename(libpath) + reallibpath = File.realpath(libpath) + libname = File.basename(reallibpath) dest = File.join(frameworks_dir, libname) if libtype == :absolute - install_name_tool prog, :change, libpath, File.join("@rpath", libname) + changes += [:change, libpath, File.join("@rpath", libname)] end next if File.exist? dest - expath, _ = expand_load_path(orig_path, libpath) + expath, _ = expand_load_path(orig_path, reallibpath) FileUtils.copy expath, frameworks_dir FileUtils.chmod("u+w", dest) - fixup_libs dest, libpath + fixup_libs dest, reallibpath end end + + install_name_tool(prog, *changes) end if ARGV[0] == "--dmg" @@ -176,14 +200,6 @@ unless File.exist? $bundle and File.exist? File.join($build_dir, "CMakeCache.txt exit 1 end -File.read(File.join($build_dir, "CMakeCache.txt")) - .split("\n") - .find { |it| it.match /Qt(.)_DIR:PATH=(.*)/ } - -qt_major = $1 -qt_dir = $2 -qt_dir = File.absolute_path("#{qt_dir}/../../..") - for lib in get_load_libs(executable) do next if system_lib? lib @@ -196,19 +212,38 @@ for lib in get_load_libs(executable) do $fallback_rpaths << path unless $fallback_rpaths.include? path end -$fallback_rpaths << File.join(qt_dir, "lib") +$qt_major = nil -plugin_paths = [ - File.join(qt_dir, "libexec", "qt#{qt_major}", "plugins"), - File.join(qt_dir, "plugins"), - File.join(qt_dir, "share", "qt", "plugins") -] +qt_dirs = File.read(File.join($build_dir, "CMakeCache.txt")) + .split("\n") + .select { |it| it.match /^Qt([\w]+)_DIR:PATH=.*/ } + .map { |dir| + dir.match /^Qt(5|6).*\=(.*)/ + throw "Inconsistent Qt versions found." if $qt_major != nil && $qt_major != $1 + $qt_major = $1 + File.absolute_path("#{$2}/../../..") + }.uniq -qt_plugins = plugin_paths.find { |file| File.exist? file } -if qt_plugins == nil - puts "Couldn't find Qt plugins, tried looking for:" - plugin_paths.each { |path| puts " - #{path}" } +def locate_plugin(dirs, plugin) + plugin_paths = [ + File.join("plugins", plugin), + File.join("lib", "qt-#{$qt_major}", "plugins", plugin), + File.join("libexec", "qt-#{$qt_major}", "plugins", plugin), + File.join("share", "qt", "plugins", plugin) + ] + + dirs.each do |dir| + plugin_paths.each do |plug| + path = File.join(dir, plug) + return path if File.exists? path + end + end + puts "Couldn't find the required Qt plugin: #{plugin}" + puts "Tried the following prefixes: " + puts dirs.map { |dir| "- #{dir}"}.join("\n") + puts "With the following plugin paths:" + puts plugin_paths.map { |path| "- #{path}"}.join("\n") exit 1 end @@ -217,12 +252,19 @@ fixup_libs(executable, executable) bundle_plugins = File.join($bundle, "Contents", "PlugIns") -want_plugins = ["styles/libqmacstyle.dylib", "platforms/libqcocoa.dylib", "imageformats/libqsvg.dylib"] +want_plugins = [ + "styles/libqmacstyle.dylib", + "platforms/libqcocoa.dylib", + "imageformats/libqsvg.dylib" +] + want_plugins.each do |plug| + pluginpath = locate_plugin(qt_dirs, plug) + destdir = File.join(bundle_plugins, File.dirname(plug)) FileUtils.mkdir_p(destdir) - FileUtils.copy(File.join(qt_plugins, plug), destdir) - fixup_libs File.join(bundle_plugins, plug), File.join(qt_plugins, plug) + FileUtils.copy(pluginpath, destdir) + fixup_libs File.join(bundle_plugins, plug), pluginpath end want_rpath = "@executable_path/../Frameworks" diff --git a/vcpkg.json b/vcpkg.json index a1bd0be5..cd734cce 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,23 +1,72 @@ { + "default-features": ["qt6"], "dependencies": [ "sdl2", + { + "name": "sdl2", + "platform": "linux", + "features": [ "alsa" ] + }, "libarchive", - "libslirp", "zstd", + "enet", { - "name": "qtbase", - "default-features": false, - "features": ["gui", "png", "thread", "widgets", "opengl", "zstd"] + "name": "ecm", + "platform": "linux" }, { - "name": "qtbase", - "host": true, - "default-features": false + "name": "libslirp", + "platform": "linux" + } + ], + "features": { + "qt6": { + "description": "Use Qt 6 for the frontend.", + "dependencies": [ + { + "name": "qtbase", + "default-features": false, + "features": ["gui", "png", "thread", "widgets", "opengl", "zstd", "harfbuzz"] + }, + { + "name": "qtbase", + "platform": "linux", + "default-features": false, + "features": ["dbus", "xcb", "xkb", "xcb-xlib", "freetype", "fontconfig"] + }, + { + "name": "qtbase", + "host": true, + "default-features": false + }, + { + "name": "qtmultimedia", + "default-features": false + }, + { + "name": "qtmultimedia", + "platform": "linux", + "features": ["gstreamer"], + "default-features": false + }, + "qtsvg" + ] }, - { - "name": "qtmultimedia", - "default-features": false - }, - "qtsvg" - ] + "qt5": { + "description": "Use Qt 5 for the frontend.", + "dependencies": [ + { + "name": "qt5-base", + "default-features": false + }, + { + "name": "qt5-base", + "host": true, + "default-features": false + }, + "qt5-multimedia", + "qt5-svg" + ] + } + } }