diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml deleted file mode 100644 index 7e7df583..00000000 --- a/.github/workflows/build-appimage.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: CMake Build (AppImage x86-64) - -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-universal.yml b/.github/workflows/build-macos-universal.yml deleted file mode 100644 index 4416ce7a..00000000 --- a/.github/workflows/build-macos-universal.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: CMake Build (macOS Universal) - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - prepare: - runs-on: [self-hosted, macOS, ARM64] - - steps: - - name: Clean workspace - run: rm -rf ${{runner.workspace}}/build - - - uses: actions/checkout@v3 - - - build-arm64: - needs: prepare - runs-on: [self-hosted, macOS, ARM64] - env: - homebrew_prefix: /opt/homebrew - - steps: - - name: Create build directory - run: mkdir -p ${{runner.workspace}}/build/arm64 - - - name: Configure - working-directory: ${{runner.workspace}}/build/arm64 - run: arch -arm64 ${{env.homebrew_prefix}}/bin/cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PREFIX_PATH="${{env.homebrew_prefix}}/opt/qt@6;${{env.homebrew_prefix}}/opt/libarchive" -DPKG_CONFIG_EXECUTABLE=${{env.homebrew_prefix}}/bin/pkg-config -DMACOS_BUNDLE_LIBS=ON -DUSE_QT6=ON - - - name: Make - working-directory: ${{runner.workspace}}/build/arm64 - run: arch -arm64 make -j$(sysctl -n hw.logicalcpu) - - build-x86_64: - needs: prepare - runs-on: [self-hosted, macOS, ARM64] - env: - homebrew_prefix: /usr/local - - steps: - - name: Create build directory - run: mkdir -p ${{runner.workspace}}/build/x86_64 - - - name: Configure - working-directory: ${{runner.workspace}}/build/x86_64 - run: arch -x86_64 ${{env.homebrew_prefix}}/bin/cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_PREFIX_PATH="${{env.homebrew_prefix}}/opt/qt@6;${{env.homebrew_prefix}}/opt/libarchive" -DPKG_CONFIG_EXECUTABLE=${{env.homebrew_prefix}}/bin/pkg-config -DMACOS_BUNDLE_LIBS=ON -DUSE_QT6=ON - - - name: Make - working-directory: ${{runner.workspace}}/build/x86_64 - run: arch -x86_64 make -j$(sysctl -n hw.logicalcpu) - - universal-binary: - needs: [build-arm64, build-x86_64] - runs-on: [self-hosted, macOS, ARM64] - - steps: - - name: Merge binaries - run: $GITHUB_WORKSPACE/tools/mac-universal.py ${{runner.workspace}}/build/arm64/melonDS.app ${{runner.workspace}}/build/x86_64/melonDS.app ${{runner.workspace}}/build/universal/melonDS.app - - - name: Codesign app - run: codesign -s - --deep -f ${{runner.workspace}}/build/universal/melonDS.app - - - name: Create DMG - run: hdiutil create -fs HFS+ -volname melonDS -srcfolder ${{runner.workspace}}/build/universal/melonDS.app -ov -format UDBZ ${{runner.workspace}}/build/universal/melonDS.dmg - - - uses: actions/upload-artifact@v3 - with: - name: macOS-universal - path: ${{runner.workspace}}/build/universal/melonDS.dmg - diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml new file mode 100644 index 00000000..4178157d --- /dev/null +++ b/.github/workflows/build-macos.yml @@ -0,0 +1,85 @@ +name: macOS + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build-macos: + strategy: + matrix: + arch: [x86_64, arm64] + + name: ${{ matrix.arch }} + 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 python-setuptools + - name: Set up CMake + uses: lukka/get-cmake@latest + - name: Set up vcpkg + uses: lukka/run-vcpkg@v11 + with: + vcpkgGitCommitId: 53bef8994c541b6561884a8395ea35715ece75db + - name: Build + uses: lukka/run-cmake@v10 + with: + configurePreset: release-mac-${{ matrix.arch }} + buildPreset: release-mac-${{ matrix.arch }} + - name: Compress app bundle + shell: bash + run: | + cd build/release-mac-${{ matrix.arch }} + zip -r -y ../../macOS-${{ matrix.arch }}.zip melonDS.app + - name: Upload artifact + uses: actions/upload-artifact@v4 + 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 + with: + name: macOS-x86_64 + path: x86_64 + - name: Download arm64 + uses: actions/download-artifact@v4 + with: + name: macOS-arm64 + path: arm64 + - name: Combine app bundles + shell: bash + run: | + unzip x86_64/*.zip -d x86_64 + unzip arm64/*.zip -d arm64 + lipo {x86_64,arm64}/melonDS.app/Contents/MacOS/melonDS -create -output melonDS + cp -a arm64/melonDS.app melonDS.app + cp melonDS melonDS.app/Contents/MacOS/melonDS + codesign -s - --deep melonDS.app + zip -r -y macOS-universal.zip melonDS.app + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: macOS-universal + path: macOS-universal.zip +# - 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 096bc0b9..00000000 --- a/.github/workflows/build-ubuntu-aarch64.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: CMake Build (Ubuntu aarch64) - -on: - push: - branches: - - master - pull_request: - branches: - - master - -env: - BUILD_TYPE: Release - -jobs: - build: - 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 b6c50e9a..0102d912 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -1,4 +1,4 @@ -name: CMake Build (Ubuntu x86-64) +name: Ubuntu on: push: @@ -9,29 +9,77 @@ on: - master jobs: - build: - - runs-on: ubuntu-20.04 + 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 \ + qt6-{base,base-private,multimedia}-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 + - 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},libarchive,libzstd}-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 + - 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 70b11c05..a4d84a1c 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -1,4 +1,4 @@ -name: CMake Build (Windows x86-64) +name: Windows on: push: @@ -27,7 +27,7 @@ jobs: update: true - name: Install dependencies - run: pacman -Sq --noconfirm git pkgconf mingw-w64-x86_64-{cmake,SDL2,qt5-static,libslirp,libarchive,toolchain} + run: pacman -Sq --noconfirm git pkgconf mingw-w64-x86_64-{cmake,SDL2,qt5-static,libarchive,toolchain} - name: Configure working-directory: ${{runner.workspace}} diff --git a/CMakeLists.txt b/CMakeLists.txt index 598b3bdb..20e37cfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.16) cmake_policy(VERSION 3.15) if (POLICY CMP0076) @@ -8,6 +8,12 @@ endif() set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) +set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_SOURCE_DIR}/cmake/DefaultBuildFlags.cmake") + +option(USE_VCPKG "Use vcpkg for dependency packages" OFF) +if (USE_VCPKG) + include(ConfigureVcpkg) +endif() project(melonDS VERSION 0.9.5 @@ -20,6 +26,8 @@ include(CheckLibraryExists) include(CMakeDependentOption) include(CheckIPOSupported) +include(SetupCCache) + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") set(CMAKE_C_STANDARD 11) @@ -28,8 +36,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") @@ -73,11 +79,6 @@ if (ENABLE_LTO) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) endif() -set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Og") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") -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() @@ -92,13 +93,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 new file mode 100644 index 00000000..e14eda24 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,88 @@ +{ + "version": 6, + "configurePresets": [ + { + "name": "release", + "displayName": "Release", + "description": "Default release build configuration.", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/release" + }, + { + "inherits": "release", + "name": "release-vcpkg", + "displayName": "Release (vcpkg)", + "description": "Release build with packages from vcpkg.", + "cacheVariables": { + "USE_VCPKG": { + "type": "BOOL", + "value": "ON" + } + } + }, + { + "name": "release-mac-x86_64", + "inherits": "release-vcpkg", + "displayName": "macOS release (x86_64)", + "binaryDir": "${sourceDir}/build/release-mac-x86_64", + "cacheVariables": { "CMAKE_OSX_ARCHITECTURES": "x86_64" } + }, + { + "name": "release-mac-arm64", + "inherits": "release-vcpkg", + "displayName": "macOS release (arm64)", + "binaryDir": "${sourceDir}/build/release-mac-arm64", + "cacheVariables": { "CMAKE_OSX_ARCHITECTURES": "arm64" } + } + ], + "buildPresets": [ + { + "name": "release", + "configurePreset": "release" + }, + { + "name": "release-vcpkg", + "configurePreset": "release-vcpkg" + }, + { + "name": "release-mac-x86_64", + "configurePreset": "release-mac-x86_64" + }, + { + "name": "release-mac-arm64", + "configurePreset": "release-mac-arm64" + } + ], + "workflowPresets": [ + { + "name": "release", + "displayName": "Release", + "steps": [ + { "type": "configure", "name": "release" }, + { "type": "build", "name": "release" } + ] + }, + { + "name": "release-vcpkg", + "displayName": "Release (vcpkg)", + "steps": [ + { "type": "configure", "name": "release-vcpkg" }, + { "type": "build", "name": "release-vcpkg" } + ] + }, + { + "name": "release-mac-x86_64", + "steps": [ + { "type": "configure", "name": "release-mac-x86_64" }, + { "type": "build", "name": "release-mac-x86_64" } + ] + }, + { + "name": "release-mac-arm64", + "steps": [ + { "type": "configure", "name": "release-mac-arm64" }, + { "type": "build", "name": "release-mac-arm64" } + ] + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index de494305..f02185ca 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,9 @@
- - - - + + +

DS emulator, sorta @@ -35,9 +34,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 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 libarchive-dev libzstd-dev` + * Arch Linux: `sudo pacman -S base-devel cmake extra-cmake-modules git libpcap sdl2 qt5-base qt5-multimedia libarchive zstd` 3. Download the melonDS repository and prepare: ```bash git clone https://github.com/melonDS-emu/melonDS @@ -64,7 +63,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-tools,libarchive,zstd}` 6. Compile: ```bash cmake -B build @@ -75,7 +74,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,zstd}` 6. Compile: ```bash cmake -B build -DBUILD_STATIC=ON -DCMAKE_PREFIX_PATH=/mingw64/qt5-static @@ -85,7 +84,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 zstd` 3. Download the melonDS repository and prepare: ```zsh git clone https://github.com/melonDS-emu/melonDS diff --git a/cmake/ConfigureVcpkg.cmake b/cmake/ConfigureVcpkg.cmake new file mode 100644 index 00000000..c9f3e92f --- /dev/null +++ b/cmake/ConfigureVcpkg.cmake @@ -0,0 +1,91 @@ +include(FetchContent) + +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) + FetchContent_Declare(vcpkg + GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git" + GIT_TAG 2024.01.12 + SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg") + FetchContent_MakeAvailable(vcpkg) +endif() + +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) + +if (CMAKE_OSX_ARCHITECTURES MATCHES ";") + message(FATAL_ERROR "macOS universal builds are not supported. Build them individually and combine afterwards instead.") +endif() + +if (USE_RECOMMENDED_TRIPLETS) + execute_process( + COMMAND uname -m + OUTPUT_VARIABLE _HOST_PROCESSOR + OUTPUT_STRIP_TRAILING_WHITESPACE) + + set(_CAN_TARGET_AS_HOST OFF) + + if (APPLE) + if (NOT CMAKE_OSX_ARCHITECTURES) + if (_HOST_PROCESSOR STREQUAL arm64) + set(CMAKE_OSX_ARCHITECTURES arm64) + else() + set(CMAKE_OSX_ARCHITECTURES x86_64) + endif() + endif() + + if (CMAKE_OSX_ARCHITECTURES STREQUAL arm64) + set(_WANTED_TRIPLET arm64-osx-11-release) + set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0) + else() + set(_WANTED_TRIPLET x64-osx-1015-release) + set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15) + endif() + elseif(WIN32) + # TODO Windows arm64 if possible + set(_CAN_TARGET_AS_HOST ON) + set(_WANTED_TRIPLET x64-mingw-static) + endif() + + # Don't override it if the user set something else + if (NOT VCPKG_TARGET_TRIPLET) + set(VCPKG_TARGET_TRIPLET "${_WANTED_TRIPLET}") + else() + set(_WANTED_TRIPLET "${VCPKG_TARGET_TRIPLET}") + endif() + + if (APPLE) + if (_HOST_PROCESSOR MATCHES arm64) + if (_WANTED_TRIPLET MATCHES "^arm64-osx-") + set(_CAN_TARGET_AS_HOST ON) + elseif (_WANTED_TRIPLET STREQUAL "x64-osx-1015-release") + # Use the default triplet for when building for arm64 + # because we're probably making a universal build + set(VCPKG_HOST_TRIPLET arm64-osx-11-release) + endif() + else() + if (_WANTED_TRIPLET MATCHES "^x64-osx-") + set(_CAN_TARGET_AS_HOST ON) + elseif (_WANTED_TRIPLET STREQUAL "arm64-osx-11-release") + set(VCPKG_HOST_TRIPLET x64-osx-1015-release) + endif() + endif() + endif() + + # If host and target triplet differ, vcpkg seems to always assume that the host can't run the target's binaries. + # In cases like cross compiling from ARM -> Intel macOS, or target being an older version of the host OS, we *can* do that so the packages built targeting the host are redundant. + if (_CAN_TARGET_AS_HOST AND NOT VCPKG_HOST_TRIPLET) + option(VCPKG_TARGET_AS_HOST "Use the target as host triplet to speed up builds" ON) + else() + option(VCPKG_TARGET_AS_HOST "Use the target as host triplet to speed up builds" OFF) + endif() + + if (VCPKG_TARGET_AS_HOST) + set(VCPKG_HOST_TRIPLET "${VCPKG_TARGET_TRIPLET}" CACHE STRING "Host triplet to use for vcpkg") + endif() +endif() + +set(CMAKE_TOOLCHAIN_FILE "${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake") 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/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/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/arm64-osx-11-release.cmake b/cmake/overlay-triplets/arm64-osx-11-release.cmake new file mode 100644 index 00000000..7c4b43ae --- /dev/null +++ b/cmake/overlay-triplets/arm64-osx-11-release.cmake @@ -0,0 +1,12 @@ +set(VCPKG_TARGET_ARCHITECTURE arm64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) + +set(VCPKG_CMAKE_SYSTEM_NAME Darwin) +set(VCPKG_CMAKE_SYSTEM_VERSION 11.0) +set(VCPKG_OSX_ARCHITECTURES arm64) +set(VCPKG_BUILD_TYPE release) +set(VCPKG_OSX_DEPLOYMENT_TARGET 11.0) + +set(VCPKG_C_FLAGS -mmacosx-version-min=11.0) +set(VCPKG_CXX_FLAGS -mmacosx-version-min=11.0) diff --git a/cmake/overlay-triplets/x64-osx-1015-release.cmake b/cmake/overlay-triplets/x64-osx-1015-release.cmake new file mode 100644 index 00000000..fcb67a7a --- /dev/null +++ b/cmake/overlay-triplets/x64-osx-1015-release.cmake @@ -0,0 +1,12 @@ +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) + +set(VCPKG_CMAKE_SYSTEM_NAME Darwin) +set(VCPKG_CMAKE_SYSTEM_VERSION 10.15) +set(VCPKG_OSX_ARCHITECTURES x86_64) +set(VCPKG_BUILD_TYPE release) +set(VCPKG_OSX_DEPLOYMENT_TARGET 10.15) + +set(VCPKG_C_FLAGS -mmacosx-version-min=10.15) +set(VCPKG_CXX_FLAGS -mmacosx-version-min=10.15) diff --git a/freebios/bios_common.S b/freebios/bios_common.S index 56d349ef..308d67c4 100755 --- a/freebios/bios_common.S +++ b/freebios/bios_common.S @@ -517,6 +517,9 @@ swi_get_crc16: mov const_0x1E, #0x1E adr crc_table_ptr, crc_table + bic crc_value, crc_value, #0xFF000000 + bic crc_value, crc_value, #0x00FF0000 + movs crc_length, crc_length, lsr #1 beq 1f diff --git a/freebios/drastic_bios_arm7.bin b/freebios/drastic_bios_arm7.bin index e586eb9f..adffb53a 100755 Binary files a/freebios/drastic_bios_arm7.bin and b/freebios/drastic_bios_arm7.bin differ diff --git a/freebios/drastic_bios_arm9.bin b/freebios/drastic_bios_arm9.bin index 51a82829..95c330d0 100755 Binary files a/freebios/drastic_bios_arm9.bin and b/freebios/drastic_bios_arm9.bin differ diff --git a/src/AREngine.cpp b/src/AREngine.cpp index 4e13a39a..c7d49fe6 100644 --- a/src/AREngine.cpp +++ b/src/AREngine.cpp @@ -40,16 +40,16 @@ AREngine::AREngine(melonDS::NDS& nds) : NDS(nds) case ((x)+0x08): case ((x)+0x09): case ((x)+0x0A): case ((x)+0x0B): \ case ((x)+0x0C): case ((x)+0x0D): case ((x)+0x0E): case ((x)+0x0F) -void AREngine::RunCheat(ARCode& arcode) +void AREngine::RunCheat(const ARCode& arcode) { - u32* code = &arcode.Code[0]; + const u32* code = &arcode.Code[0]; u32 offset = 0; u32 datareg = 0; u32 cond = 1; u32 condstack = 0; - u32* loopstart = code; + const u32* loopstart = code; u32 loopcount = 0; u32 loopcond = 1; u32 loopcondstack = 0; diff --git a/src/AREngine.h b/src/AREngine.h index 1f2ee186..21044676 100644 --- a/src/AREngine.h +++ b/src/AREngine.h @@ -33,7 +33,7 @@ public: void SetCodeFile(ARCodeFile* file) { CodeFile = file; } void RunCheats(); - void RunCheat(ARCode& arcode); + 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 659d303b..c2f6a6c2 100644 --- a/src/ARM.cpp +++ b/src/ARM.cpp @@ -17,6 +17,7 @@ */ #include +#include #include #include "NDS.h" #include "DSi.h" @@ -106,17 +107,17 @@ const u32 ARM::ConditionTable[16] = 0x0000 // NE }; -ARM::ARM(u32 num, melonDS::NDS& nds) : +ARM::ARM(u32 num, bool jit, std::optional gdb, melonDS::NDS& nds) : #ifdef GDBSTUB_ENABLED - GdbStub(this, Platform::GetConfigInt(num ? Platform::GdbPortARM7 : Platform::GdbPortARM9)), + GdbStub(this, gdb ? (num ? gdb->PortARM7 : gdb->PortARM9) : 0), #endif Num(num), // well uh NDS(nds) { #ifdef GDBSTUB_ENABLED - if (Platform::GetConfigBool(Platform::GdbEnabled) + if (gdb #ifdef JIT_ENABLED - && !Platform::GetConfigBool(Platform::JIT_Enable) + && !jit // TODO: Should we support toggling the GdbStub without destroying the ARM? #endif ) GdbStub.Init(); @@ -129,14 +130,14 @@ ARM::~ARM() // dorp } -ARMv5::ARMv5(melonDS::NDS& nds) : ARM(0, nds) +ARMv5::ARMv5(melonDS::NDS& nds, std::optional gdb, bool jit) : ARM(0, jit, gdb, nds) { DTCM = NDS.JIT.Memory.GetARM9DTCM(); PU_Map = PU_PrivMap; } -ARMv4::ARMv4(melonDS::NDS& nds) : ARM(1, nds) +ARMv4::ARMv4(melonDS::NDS& nds, std::optional gdb, bool jit) : ARM(1, jit, gdb, nds) { // } @@ -187,8 +188,6 @@ void ARM::Reset() #ifdef GDBSTUB_ENABLED IsSingleStep = false; BreakReq = false; - BreakOnStartup = Platform::GetConfigBool( - Num ? Platform::GdbARM7BreakOnStartup : Platform::GdbARM9BreakOnStartup); #endif // zorp @@ -224,7 +223,7 @@ void ARM::DoSavestate(Savestate* file) file->VarArray(R_UND, 3*sizeof(u32)); file->Var32(&CurInstr); #ifdef JIT_ENABLED - if (file->Saving && NDS.EnableJIT) + if (file->Saving && NDS.IsJITEnabled()) { // hack, the JIT doesn't really pipeline // but we still want JIT save states to be diff --git a/src/ARM.h b/src/ARM.h index 4becff02..1e0b71b8 100644 --- a/src/ARM.h +++ b/src/ARM.h @@ -20,9 +20,11 @@ #define ARM_H #include +#include #include "types.h" #include "MemRegion.h" +#include "MemConstants.h" #ifdef GDBSTUB_ENABLED #include "debug/GdbStub.h" @@ -41,10 +43,7 @@ enum RWFlags_ForceUser = (1<<21), }; -const u32 ITCMPhysicalSize = 0x8000; -const u32 DTCMPhysicalSize = 0x4000; - - +struct GDBArgs; class ARMJIT; class GPU; class ARMJIT_Memory; @@ -57,7 +56,7 @@ class ARM #endif { public: - ARM(u32 num, NDS& nds); + ARM(u32 num, bool jit, std::optional gdb, NDS& nds); virtual ~ARM(); // destroy shit virtual void Reset(); @@ -81,7 +80,7 @@ public: virtual void ExecuteJIT() = 0; #endif - bool CheckCondition(u32 code) + bool CheckCondition(u32 code) const { if (code == 0xE) return true; if (ConditionTable[code] & (1 << (CPSR>>28))) return true; @@ -110,7 +109,7 @@ public: if (v) CPSR |= 0x10000000; } - inline bool ModeIs(u32 mode) + inline bool ModeIs(u32 mode) const { u32 cm = CPSR & 0x1f; mode &= 0x1f; @@ -202,6 +201,7 @@ protected: bool IsSingleStep; bool BreakReq; bool BreakOnStartup; + u16 Port; public: int GetCPU() const override { return Num ? 7 : 9; } @@ -225,7 +225,7 @@ protected: class ARMv5 : public ARM { public: - ARMv5(melonDS::NDS& nds); + ARMv5(melonDS::NDS& nds, std::optional gdb, bool jit); ~ARMv5(); void Reset() override; @@ -315,7 +315,7 @@ public: void ICacheInvalidateAll(); void CP15Write(u32 id, u32 val); - u32 CP15Read(u32 id); + u32 CP15Read(u32 id) const; u32 CP15Control; @@ -377,7 +377,7 @@ protected: class ARMv4 : public ARM { public: - ARMv4(melonDS::NDS& nds); + ARMv4(melonDS::NDS& nds, std::optional gdb, bool jit); void FillPipeline() override; diff --git a/src/ARMJIT.cpp b/src/ARMJIT.cpp index b938dfb8..c3fcba26 100644 --- a/src/ARMJIT.cpp +++ b/src/ARMJIT.cpp @@ -63,12 +63,12 @@ const u32 CodeRegionSizes[ARMJIT_Memory::memregions_Count] = 0, ITCMPhysicalSize, 0, - sizeof(NDS::ARM9BIOS), + ARM9BIOSSize, MainRAMMaxSize, SharedWRAMSize, 0, 0x100000, - sizeof(NDS::ARM7BIOS), + ARM7BIOSSize, ARM7WRAMSize, 0, 0, @@ -237,16 +237,6 @@ ARMJIT::~ARMJIT() noexcept void ARMJIT::Reset() noexcept { - MaxBlockSize = Platform::GetConfigInt(Platform::JIT_MaxBlockSize); - LiteralOptimizations = Platform::GetConfigBool(Platform::JIT_LiteralOptimizations); - BranchOptimizations = Platform::GetConfigBool(Platform::JIT_BranchOptimizations); - FastMemory = Platform::GetConfigBool(Platform::JIT_FastMemory); - - if (MaxBlockSize < 1) - MaxBlockSize = 1; - if (MaxBlockSize > 32) - MaxBlockSize = 32; - JitEnableWrite(); ResetBlockCache(); @@ -491,6 +481,56 @@ void ARMJIT::RetireJitBlock(JitBlock* block) noexcept } } +void ARMJIT::SetJITArgs(JITArgs args) noexcept +{ + args.MaxBlockSize = std::clamp(args.MaxBlockSize, 1u, 32u); + + if (MaxBlockSize != args.MaxBlockSize + || LiteralOptimizations != args.LiteralOptimizations + || BranchOptimizations != args.BranchOptimizations + || FastMemory != args.FastMemory) + ResetBlockCache(); + + MaxBlockSize = args.MaxBlockSize; + LiteralOptimizations = args.LiteralOptimizations; + BranchOptimizations = args.BranchOptimizations; + FastMemory = args.FastMemory; +} + +void ARMJIT::SetMaxBlockSize(int size) noexcept +{ + size = std::clamp(size, 1, 32); + + if (size != MaxBlockSize) + ResetBlockCache(); + + MaxBlockSize = size; +} + +void ARMJIT::SetLiteralOptimizations(bool enabled) noexcept +{ + if (LiteralOptimizations != enabled) + ResetBlockCache(); + + LiteralOptimizations = enabled; +} + +void ARMJIT::SetBranchOptimizations(bool enabled) noexcept +{ + if (BranchOptimizations != enabled) + ResetBlockCache(); + + BranchOptimizations = enabled; +} + +void ARMJIT::SetFastMemory(bool enabled) noexcept +{ + if (FastMemory != enabled) + ResetBlockCache(); + + FastMemory = enabled; +} + void ARMJIT::CompileBlock(ARM* cpu) noexcept { bool thumb = cpu->CPSR & 0x20; diff --git a/src/ARMJIT.h b/src/ARMJIT.h index 9e1ca074..7619f234 100644 --- a/src/ARMJIT.h +++ b/src/ARMJIT.h @@ -19,10 +19,15 @@ #ifndef ARMJIT_H #define ARMJIT_H +#include +#include #include #include "types.h" - +#include "MemConstants.h" +#include "Args.h" #include "ARMJIT_Memory.h" + +#ifdef JIT_ENABLED #include "JitBlock.h" #if defined(__APPLE__) && defined(__aarch64__) @@ -30,7 +35,6 @@ #endif #include "ARMJIT_Compiler.h" -#include "MemConstants.h" namespace melonDS { @@ -40,18 +44,25 @@ class JitBlock; class ARMJIT { public: - ARMJIT(melonDS::NDS& nds) noexcept : NDS(nds), Memory(nds), JITCompiler(nds) {}; - ~ARMJIT() noexcept NOOP_IF_NO_JIT; - void InvalidateByAddr(u32) noexcept NOOP_IF_NO_JIT; - void CheckAndInvalidateWVRAM(int) noexcept NOOP_IF_NO_JIT; - void CheckAndInvalidateITCM() noexcept NOOP_IF_NO_JIT; - void Reset() noexcept NOOP_IF_NO_JIT; - void JitEnableWrite() noexcept NOOP_IF_NO_JIT; - void JitEnableExecute() noexcept NOOP_IF_NO_JIT; - void CompileBlock(ARM* cpu) noexcept NOOP_IF_NO_JIT; - void ResetBlockCache() noexcept NOOP_IF_NO_JIT; + ARMJIT(melonDS::NDS& nds, std::optional jit) noexcept : + NDS(nds), + Memory(nds), + JITCompiler(nds), + MaxBlockSize(jit.has_value() ? std::clamp(jit->MaxBlockSize, 1u, 32u) : 32), + LiteralOptimizations(jit.has_value() ? jit->LiteralOptimizations : false), + BranchOptimizations(jit.has_value() ? jit->BranchOptimizations : false), + FastMemory(jit.has_value() ? jit->FastMemory : false) + {} + ~ARMJIT() noexcept; + void InvalidateByAddr(u32) noexcept; + void CheckAndInvalidateWVRAM(int) noexcept; + void CheckAndInvalidateITCM() noexcept; + void Reset() noexcept; + void JitEnableWrite() noexcept; + void JitEnableExecute() noexcept; + void CompileBlock(ARM* cpu) noexcept; + void ResetBlockCache() noexcept; -#ifdef JIT_ENABLED template void CheckAndInvalidate(u32 addr) noexcept { @@ -62,23 +73,31 @@ public: JitBlockEntry LookUpBlock(u32 num, u64* entries, u32 offset, u32 addr) noexcept; bool SetupExecutableRegion(u32 num, u32 blockAddr, u64*& entry, u32& start, u32& size) noexcept; u32 LocaliseCodeAddress(u32 num, u32 addr) const noexcept; -#else - template - void CheckAndInvalidate(u32) noexcept {} -#endif ARMJIT_Memory Memory; +private: int MaxBlockSize {}; bool LiteralOptimizations = false; bool BranchOptimizations = false; bool FastMemory = false; - +public: melonDS::NDS& NDS; TinyVector InvalidLiterals {}; friend class ARMJIT_Memory; void blockSanityCheck(u32 num, u32 blockAddr, JitBlockEntry entry) noexcept; void RetireJitBlock(JitBlock* block) noexcept; + int GetMaxBlockSize() const noexcept { return MaxBlockSize; } + bool LiteralOptimizationsEnabled() const noexcept { return LiteralOptimizations; } + bool BranchOptimizationsEnabled() const noexcept { return BranchOptimizations; } + bool FastMemoryEnabled() const noexcept { return FastMemory; } + + void SetJITArgs(JITArgs args) noexcept; + void SetMaxBlockSize(int size) noexcept; + void SetLiteralOptimizations(bool enabled) noexcept; + void SetBranchOptimizations(bool enabled) noexcept; + void SetFastMemory(bool enabled) noexcept; + Compiler JITCompiler; std::unordered_map JitBlocks9 {}; std::unordered_map JitBlocks7 {}; @@ -162,5 +181,33 @@ public: // Defined in assembly extern "C" void ARM_Dispatch(melonDS::ARM* cpu, melonDS::JitBlockEntry entry); +#else +namespace melonDS +{ +class ARM; + +// This version is a stub; the methods all do nothing, +// but there's still a Memory member. +class ARMJIT +{ +public: + ARMJIT(melonDS::NDS& nds, std::optional) noexcept : Memory(nds) {} + ~ARMJIT() noexcept {} + void InvalidateByAddr(u32) noexcept {} + void CheckAndInvalidateWVRAM(int) noexcept {} + void CheckAndInvalidateITCM() noexcept {} + void Reset() noexcept {} + void JitEnableWrite() noexcept {} + void JitEnableExecute() noexcept {} + void CompileBlock(ARM*) noexcept {} + void ResetBlockCache() noexcept {} + template + void CheckAndInvalidate(u32 addr) noexcept {} + + ARMJIT_Memory Memory; +}; +} +#endif // JIT_ENABLED + +#endif // ARMJIT_H -#endif diff --git a/src/ARMJIT_A64/ARMJIT_Compiler.cpp b/src/ARMJIT_A64/ARMJIT_Compiler.cpp index c306dd84..7981ed67 100644 --- a/src/ARMJIT_A64/ARMJIT_Compiler.cpp +++ b/src/ARMJIT_A64/ARMJIT_Compiler.cpp @@ -276,7 +276,7 @@ Compiler::Compiler(melonDS::NDS& nds) : Arm64Gen::ARM64XEmitter(), NDS(nds) VirtualProtect(pageAligned, alignedSize, PAGE_EXECUTE_READWRITE, &dummy); #elif defined(__APPLE__) pageAligned = (u8*)mmap(NULL, 1024*1024*16, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_JIT,-1, 0); - JitEnableWrite(); + nds.JIT.JitEnableWrite(); #else mprotect(pageAligned, alignedSize, PROT_EXEC | PROT_READ | PROT_WRITE); #endif diff --git a/src/ARMJIT_A64/ARMJIT_Compiler.h b/src/ARMJIT_A64/ARMJIT_Compiler.h index 72dd7bcb..2b0048a9 100644 --- a/src/ARMJIT_A64/ARMJIT_Compiler.h +++ b/src/ARMJIT_A64/ARMJIT_Compiler.h @@ -19,6 +19,8 @@ #ifndef ARMJIT_A64_COMPILER_H #define ARMJIT_A64_COMPILER_H +#if defined(JIT_ENABLED) && defined(__aarch64__) + #include "../ARM.h" #include "../dolphin/Arm64Emitter.h" @@ -67,7 +69,7 @@ struct Op2 bool IsSimpleReg() { return !IsImm && !Reg.ShiftAmount && Reg.ShiftType == Arm64Gen::ST_LSL; } bool ImmFits12Bit() - { return IsImm && (Imm & 0xFFF == Imm); } + { return IsImm && ((Imm & 0xFFF) == Imm); } bool IsZero() { return IsImm && !Imm; } @@ -96,12 +98,8 @@ class Compiler : public Arm64Gen::ARM64XEmitter public: typedef void (Compiler::*CompileFunc)(); -#ifdef JIT_ENABLED explicit Compiler(melonDS::NDS& nds); -#else - explicit Compiler(melonDS::NDS& nds) : XEmitter(), NDS(nds) {} -#endif - ~Compiler(); + ~Compiler() override; void PushRegs(bool saveHiRegs, bool saveRegsToBeChanged, bool allowUnload = true); void PopRegs(bool saveHiRegs, bool saveRegsToBeChanged); @@ -116,7 +114,7 @@ public: bool CanCompile(bool thumb, u16 kind); - bool FlagsNZNeeded() + bool FlagsNZNeeded() const { return CurInstr.SetFlags & 0xC; } @@ -236,7 +234,7 @@ public: return (u8*)entry - GetRXBase(); } - bool IsJITFault(u8* pc); + bool IsJITFault(const u8* pc); u8* RewriteMemAccess(u8* pc); void SwapCodeRegion() @@ -291,3 +289,5 @@ public: } #endif + +#endif diff --git a/src/ARMJIT_A64/ARMJIT_LoadStore.cpp b/src/ARMJIT_A64/ARMJIT_LoadStore.cpp index 22a410ae..e108b7b4 100644 --- a/src/ARMJIT_A64/ARMJIT_LoadStore.cpp +++ b/src/ARMJIT_A64/ARMJIT_LoadStore.cpp @@ -28,7 +28,7 @@ using namespace Arm64Gen; namespace melonDS { -bool Compiler::IsJITFault(u8* pc) +bool Compiler::IsJITFault(const u8* pc) { return (u64)pc >= (u64)GetRXBase() && (u64)pc - (u64)GetRXBase() < (JitMemMainSize + JitMemSecondarySize); } @@ -112,7 +112,7 @@ void Compiler::Comp_MemAccess(int rd, int rn, Op2 offset, int size, int flags) if (size == 16) addressMask = ~1; - if (NDS.JIT.LiteralOptimizations && rn == 15 && rd != 15 && offset.IsImm && !(flags & (memop_Post|memop_Store|memop_Writeback))) + if (NDS.JIT.LiteralOptimizationsEnabled() && rn == 15 && rd != 15 && offset.IsImm && !(flags & (memop_Post|memop_Store|memop_Writeback))) { u32 addr = R15 + offset.Imm * ((flags & memop_SubtractOffset) ? -1 : 1); @@ -147,7 +147,7 @@ void Compiler::Comp_MemAccess(int rd, int rn, Op2 offset, int size, int flags) MOV(W0, rnMapped); } - bool addrIsStatic = NDS.JIT.LiteralOptimizations + bool addrIsStatic = NDS.JIT.LiteralOptimizationsEnabled() && RegCache.IsLiteral(rn) && offset.IsImm && !(flags & (memop_Writeback|memop_Post)); u32 staticAddress; if (addrIsStatic) @@ -189,7 +189,7 @@ void Compiler::Comp_MemAccess(int rd, int rn, Op2 offset, int size, int flags) ? NDS.JIT.Memory.ClassifyAddress9(addrIsStatic ? staticAddress : CurInstr.DataRegion) : NDS.JIT.Memory.ClassifyAddress7(addrIsStatic ? staticAddress : CurInstr.DataRegion); - if (NDS.JIT.FastMemory && ((!Thumb && CurInstr.Cond() != 0xE) || NDS.JIT.Memory.IsFastmemCompatible(expectedTarget))) + if (NDS.JIT.FastMemoryEnabled() && ((!Thumb && CurInstr.Cond() != 0xE) || NDS.JIT.Memory.IsFastmemCompatible(expectedTarget))) { ptrdiff_t memopStart = GetCodeOffset(); LoadStorePatch patch; @@ -453,7 +453,7 @@ void Compiler::T_Comp_LoadPCRel() u32 offset = ((CurInstr.Instr & 0xFF) << 2); u32 addr = (R15 & ~0x2) + offset; - if (!NDS.JIT.LiteralOptimizations || !Comp_MemLoadLiteral(32, false, CurInstr.T_Reg(8), addr)) + if (!NDS.JIT.LiteralOptimizationsEnabled() || !Comp_MemLoadLiteral(32, false, CurInstr.T_Reg(8), addr)) Comp_MemAccess(CurInstr.T_Reg(8), 15, Op2(offset), 32, 0); } @@ -498,7 +498,7 @@ s32 Compiler::Comp_MemAccessBlock(int rn, BitSet16 regs, bool store, bool preinc ? NDS.JIT.Memory.ClassifyAddress9(CurInstr.DataRegion) : NDS.JIT.Memory.ClassifyAddress7(CurInstr.DataRegion); - bool compileFastPath = NDS.JIT.FastMemory + bool compileFastPath = NDS.JIT.FastMemoryEnabled() && store && !usermode && (CurInstr.Cond() < 0xE || NDS.JIT.Memory.IsFastmemCompatible(expectedTarget)); { diff --git a/src/ARMJIT_Compiler.h b/src/ARMJIT_Compiler.h index 4ece834f..ff4f8ff7 100644 --- a/src/ARMJIT_Compiler.h +++ b/src/ARMJIT_Compiler.h @@ -20,10 +20,6 @@ #define ARMJIT_COMPILER_H #ifdef JIT_ENABLED -#define NOOP_IF_NO_JIT -#else -#define NOOP_IF_NO_JIT {} -#endif #if defined(__x86_64__) #include "ARMJIT_x64/ARMJIT_Compiler.h" @@ -34,3 +30,5 @@ #endif #endif + +#endif diff --git a/src/ARMJIT_Internal.h b/src/ARMJIT_Internal.h index 72d40a5f..8429bade 100644 --- a/src/ARMJIT_Internal.h +++ b/src/ARMJIT_Internal.h @@ -85,7 +85,7 @@ typedef void (*InterpreterFunc)(ARM* cpu); extern InterpreterFunc InterpretARM[]; extern InterpreterFunc InterpretTHUMB[]; -inline bool PageContainsCode(AddressRange* range) +inline bool PageContainsCode(const AddressRange* range) { for (int i = 0; i < 8; i++) { diff --git a/src/ARMJIT_Memory.cpp b/src/ARMJIT_Memory.cpp index bf1fb063..c8969aee 100644 --- a/src/ARMJIT_Memory.cpp +++ b/src/ARMJIT_Memory.cpp @@ -473,8 +473,10 @@ void ARMJIT_Memory::RemapDTCM(u32 newBase, u32 newSize) noexcept void ARMJIT_Memory::RemapNWRAM(int num) noexcept { - auto* dsi = dynamic_cast(&NDS); - assert(dsi != nullptr); + if (NDS.ConsoleType == 0) + return; + + auto* dsi = static_cast(&NDS); for (int i = 0; i < Mappings[memregion_SharedWRAM].Length;) { Mapping& mapping = Mappings[memregion_SharedWRAM][i]; @@ -1071,15 +1073,19 @@ int ARMJIT_Memory::ClassifyAddress9(u32 addr) const noexcept } else { - auto& dsi = static_cast(NDS); // ONLY use this if ConsoleType == 1! - if (NDS.ConsoleType == 1 && addr >= 0xFFFF0000 && !(dsi.SCFG_BIOS & (1<<1))) + if (NDS.ConsoleType == 1) { - if ((addr >= 0xFFFF8000) && (dsi.SCFG_BIOS & (1<<0))) - return memregion_Other; + auto& dsi = static_cast(NDS); + if (addr >= 0xFFFF0000 && !(dsi.SCFG_BIOS & (1<<1))) + { + if ((addr >= 0xFFFF8000) && (dsi.SCFG_BIOS & (1<<0))) + return memregion_Other; - return memregion_BIOS9DSi; + return memregion_BIOS9DSi; + } } - else if ((addr & 0xFFFFF000) == 0xFFFF0000) + + if ((addr & 0xFFFFF000) == 0xFFFF0000) { return memregion_BIOS9; } @@ -1091,6 +1097,7 @@ int ARMJIT_Memory::ClassifyAddress9(u32 addr) const noexcept case 0x03000000: if (NDS.ConsoleType == 1) { + auto& dsi = static_cast(NDS); if (addr >= dsi.NWRAMStart[0][0] && addr < dsi.NWRAMEnd[0][0]) return memregion_NewSharedWRAM_A; if (addr >= dsi.NWRAMStart[0][1] && addr < dsi.NWRAMEnd[0][1]) @@ -1116,15 +1123,19 @@ int ARMJIT_Memory::ClassifyAddress9(u32 addr) const noexcept int ARMJIT_Memory::ClassifyAddress7(u32 addr) const noexcept { - auto& dsi = static_cast(NDS); - if (NDS.ConsoleType == 1 && addr < 0x00010000 && !(dsi.SCFG_BIOS & (1<<9))) + if (NDS.ConsoleType == 1) { - if (addr >= 0x00008000 && dsi.SCFG_BIOS & (1<<8)) - return memregion_Other; + auto& dsi = static_cast(NDS); + if (addr < 0x00010000 && !(dsi.SCFG_BIOS & (1<<9))) + { + if (addr >= 0x00008000 && dsi.SCFG_BIOS & (1<<8)) + return memregion_Other; - return memregion_BIOS7DSi; + return memregion_BIOS7DSi; + } } - else if (addr < 0x00004000) + + if (addr < 0x00004000) { return memregion_BIOS7; } @@ -1138,6 +1149,7 @@ int ARMJIT_Memory::ClassifyAddress7(u32 addr) const noexcept case 0x03000000: if (NDS.ConsoleType == 1) { + auto& dsi = static_cast(NDS); if (addr >= dsi.NWRAMStart[1][0] && addr < dsi.NWRAMEnd[1][0]) return memregion_NewSharedWRAM_A; if (addr >= dsi.NWRAMStart[1][1] && addr < dsi.NWRAMEnd[1][1]) diff --git a/src/ARMJIT_Memory.h b/src/ARMJIT_Memory.h index 487005b2..d36f6032 100644 --- a/src/ARMJIT_Memory.h +++ b/src/ARMJIT_Memory.h @@ -20,33 +20,33 @@ #define ARMJIT_MEMORY #include "types.h" -#include "TinyVector.h" - -#include "ARM.h" #include "MemConstants.h" -#if defined(__SWITCH__) -#include -#elif defined(_WIN32) +#ifdef JIT_ENABLED +# include "TinyVector.h" +# include "ARM.h" +# if defined(__SWITCH__) +# include +# elif defined(_WIN32) #include +# else +# include +# include +# include +# include +# include +# endif #else -#include -#include -#include -#include -#include -#endif - -#ifndef JIT_ENABLED -#include -#include "NDS.h" +# include #endif namespace melonDS { +#ifdef JIT_ENABLED namespace Platform { struct DynamicLibrary; } class Compiler; class ARMJIT; +#endif constexpr u32 RoundUp(u32 size) noexcept { diff --git a/src/ARMJIT_RegisterCache.h b/src/ARMJIT_RegisterCache.h index 3cb0f79f..e5f28dd6 100644 --- a/src/ARMJIT_RegisterCache.h +++ b/src/ARMJIT_RegisterCache.h @@ -99,7 +99,7 @@ public: LiteralsLoaded &= ~(1 << reg); } - bool IsLiteral(int reg) + bool IsLiteral(int reg) const { return LiteralsLoaded & (1 << reg); } diff --git a/src/ARMJIT_x64/ARMJIT_Compiler.cpp b/src/ARMJIT_x64/ARMJIT_Compiler.cpp index eec4d7d1..b18837f3 100644 --- a/src/ARMJIT_x64/ARMJIT_Compiler.cpp +++ b/src/ARMJIT_x64/ARMJIT_Compiler.cpp @@ -651,7 +651,7 @@ const Compiler::CompileFunc T_Comp[ARMInstrInfo::tk_Count] = { }; #undef F -bool Compiler::CanCompile(bool thumb, u16 kind) +bool Compiler::CanCompile(bool thumb, u16 kind) const { return (thumb ? T_Comp[kind] : A_Comp[kind]) != NULL; } @@ -667,7 +667,7 @@ void Compiler::Reset() LoadStorePatches.clear(); } -bool Compiler::IsJITFault(u8* addr) +bool Compiler::IsJITFault(const u8* addr) { return (u64)addr >= (u64)ResetStart && (u64)addr < (u64)ResetStart + CodeMemSize; } diff --git a/src/ARMJIT_x64/ARMJIT_Compiler.h b/src/ARMJIT_x64/ARMJIT_Compiler.h index fa6d78a4..941d8924 100644 --- a/src/ARMJIT_x64/ARMJIT_Compiler.h +++ b/src/ARMJIT_x64/ARMJIT_Compiler.h @@ -19,6 +19,8 @@ #ifndef ARMJIT_X64_COMPILER_H #define ARMJIT_X64_COMPILER_H +#if defined(JIT_ENABLED) && defined(__x86_64__) + #include "../dolphin/x64Emitter.h" #include "../ARMJIT_Internal.h" @@ -81,11 +83,7 @@ struct Op2 class Compiler : public Gen::XEmitter { public: -#ifdef JIT_ENABLED explicit Compiler(melonDS::NDS& nds); -#else - explicit Compiler(melonDS::NDS& nds) : XEmitter(), NDS(nds) {} -#endif void Reset(); @@ -94,7 +92,7 @@ public: void LoadReg(int reg, Gen::X64Reg nativeReg); void SaveReg(int reg, Gen::X64Reg nativeReg); - bool CanCompile(bool thumb, u16 kind); + bool CanCompile(bool thumb, u16 kind) const; typedef void (Compiler::*CompileFunc)(); @@ -236,7 +234,7 @@ public: SetCodePtr(FarCode); } - bool IsJITFault(u8* addr); + bool IsJITFault(const u8* addr); u8* RewriteMemAccess(u8* pc); @@ -284,5 +282,6 @@ public: }; } +#endif #endif diff --git a/src/ARMJIT_x64/ARMJIT_LoadStore.cpp b/src/ARMJIT_x64/ARMJIT_LoadStore.cpp index 72a073db..8520bebc 100644 --- a/src/ARMJIT_x64/ARMJIT_LoadStore.cpp +++ b/src/ARMJIT_x64/ARMJIT_LoadStore.cpp @@ -119,7 +119,7 @@ void Compiler::Comp_MemAccess(int rd, int rn, const Op2& op2, int size, int flag if (size == 16) addressMask = ~1; - if (NDS.JIT.LiteralOptimizations && rn == 15 && rd != 15 && op2.IsImm && !(flags & (memop_Post|memop_Store|memop_Writeback))) + if (NDS.JIT.LiteralOptimizationsEnabled() && rn == 15 && rd != 15 && op2.IsImm && !(flags & (memop_Post|memop_Store|memop_Writeback))) { u32 addr = R15 + op2.Imm * ((flags & memop_SubtractOffset) ? -1 : 1); @@ -136,7 +136,7 @@ void Compiler::Comp_MemAccess(int rd, int rn, const Op2& op2, int size, int flag Comp_AddCycles_CDI(); } - bool addrIsStatic = NDS.JIT.LiteralOptimizations + bool addrIsStatic = NDS.JIT.LiteralOptimizationsEnabled() && RegCache.IsLiteral(rn) && op2.IsImm && !(flags & (memop_Writeback|memop_Post)); u32 staticAddress; if (addrIsStatic) @@ -200,7 +200,7 @@ void Compiler::Comp_MemAccess(int rd, int rn, const Op2& op2, int size, int flag ? NDS.JIT.Memory.ClassifyAddress9(CurInstr.DataRegion) : NDS.JIT.Memory.ClassifyAddress7(CurInstr.DataRegion); - if (NDS.JIT.FastMemory && ((!Thumb && CurInstr.Cond() != 0xE) || NDS.JIT.Memory.IsFastmemCompatible(expectedTarget))) + if (NDS.JIT.FastMemoryEnabled() && ((!Thumb && CurInstr.Cond() != 0xE) || NDS.JIT.Memory.IsFastmemCompatible(expectedTarget))) { if (rdMapped.IsImm()) { @@ -431,7 +431,7 @@ s32 Compiler::Comp_MemAccessBlock(int rn, BitSet16 regs, bool store, bool preinc else Comp_AddCycles_CD(); - bool compileFastPath = NDS.JIT.FastMemory + bool compileFastPath = NDS.JIT.FastMemoryEnabled() && !usermode && (CurInstr.Cond() < 0xE || NDS.JIT.Memory.IsFastmemCompatible(expectedTarget)); // we need to make sure that the stack stays aligned to 16 bytes @@ -809,7 +809,7 @@ void Compiler::T_Comp_LoadPCRel() { u32 offset = (CurInstr.Instr & 0xFF) << 2; u32 addr = (R15 & ~0x2) + offset; - if (!NDS.JIT.LiteralOptimizations || !Comp_MemLoadLiteral(32, false, CurInstr.T_Reg(8), addr)) + if (!NDS.JIT.LiteralOptimizationsEnabled() || !Comp_MemLoadLiteral(32, false, CurInstr.T_Reg(8), addr)) Comp_MemAccess(CurInstr.T_Reg(8), 15, Op2(offset), 32, 0); } diff --git a/src/Args.h b/src/Args.h new file mode 100644 index 00000000..0b20bbf0 --- /dev/null +++ b/src/Args.h @@ -0,0 +1,152 @@ +/* + 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 MELONDS_ARGS_H +#define MELONDS_ARGS_H + +#include +#include +#include + +#include "NDSCart.h" +#include "GBACart.h" +#include "types.h" +#include "MemConstants.h" +#include "DSi_NAND.h" +#include "FATStorage.h" +#include "FreeBIOS.h" +#include "GPU3D_Soft.h" +#include "SPI_Firmware.h" +#include "SPU.h" + +namespace melonDS +{ +namespace NDSCart { class CartCommon; } +namespace GBACart { class CartCommon; } + +template +constexpr std::array BrokenBIOS = []() constexpr { + std::array broken {}; + + for (int i = 0; i < 16; i++) + { + broken[i*4+0] = 0xE7; + broken[i*4+1] = 0xFF; + broken[i*4+2] = 0xDE; + broken[i*4+3] = 0xFF; + } + + return broken; +}(); + +/// Arguments that configure the JIT. +/// Ignored in builds that don't have the JIT included. +struct JITArgs +{ + unsigned MaxBlockSize = 32; + bool LiteralOptimizations = true; + bool BranchOptimizations = true; + + /// Ignored in builds that have fast memory excluded + /// (even if the JIT is otherwise available). + /// Enabled by default, but frontends should disable this when debugging + /// so the constants segfaults don't hinder debugging. + bool FastMemory = true; +}; + +using ARM9BIOSImage = std::array; +using ARM7BIOSImage = std::array; +using DSiBIOSImage = std::array; + +struct GDBArgs +{ + u16 PortARM7 = 0; + u16 PortARM9 = 0; + bool ARM7BreakOnStartup = false; + bool ARM9BreakOnStartup = false; +}; + +/// Arguments to pass into the NDS constructor. +/// New fields here should have default values if possible. +struct NDSArgs +{ + /// NDS ROM to install. + /// Defaults to nullptr, which means no cart. + /// Should be populated with the desired save data beforehand, + /// including an SD card if applicable. + std::unique_ptr NDSROM = nullptr; + + /// GBA ROM to install. + /// Defaults to nullptr, which means no cart. + /// Should be populated with the desired save data beforehand. + /// Ignored in DSi mode. + std::unique_ptr GBAROM = nullptr; + + /// NDS ARM9 BIOS to install. + /// Defaults to FreeBIOS, which is not compatible with DSi mode. + 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::unique_ptr ARM7BIOS = std::make_unique(bios_arm7_bin); + + /// Firmware image to install. + /// Defaults to generated NDS firmware. + /// Generated firmware is not compatible with DSi mode. + melonDS::Firmware Firmware {0}; + + /// How the JIT should be configured when initializing. + /// Defaults to enabled, with default settings. + /// To disable the JIT, set this to std::nullopt. + /// Ignored in builds that don't have the JIT included. + std::optional JIT = JITArgs(); + + AudioBitDepth BitDepth = AudioBitDepth::Auto; + AudioInterpolation Interpolation = AudioInterpolation::None; + + /// How the GDB stub should be handled. + /// Defaults to disabled. + /// Ignored in builds that don't have the GDB stub included. + std::optional GDB = std::nullopt; + + /// The 3D renderer to initialize the DS with. + /// Defaults to the software renderer. + /// Can be changed later at any time. + std::unique_ptr Renderer3D = std::make_unique(); +}; + +/// Arguments to pass into the DSi constructor. +/// New fields here should have default values if possible. +/// Contains no virtual methods, so there's no vtable. +struct DSiArgs final : public NDSArgs +{ + 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. + DSi_NAND::NANDImage NANDImage; + + /// SD card to install. + /// Defaults to std::nullopt, which means no SD card. + std::optional DSiSDCard; + + bool FullBIOSBoot = false; +}; +} +#endif //MELONDS_ARGS_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9fe93ae5..50cd7708 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,9 +35,12 @@ add_library(core STATIC GPU2D_Soft.cpp GPU3D.cpp GPU3D_Soft.cpp + GPU3D_Texcache.cpp + GPU3D_Texcache.h melonDLDI.h NDS.cpp NDSCart.cpp + NDSCartR4.cpp Platform.h ROMList.h ROMList.cpp @@ -49,7 +52,8 @@ add_library(core STATIC SPI_Firmware.cpp SPU.cpp types.h - version.h + Utils.cpp + Utils.h Wifi.cpp WifiAP.cpp @@ -76,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) @@ -120,11 +127,26 @@ if (ENABLE_JIT) endif() endif() +set(MELONDS_VERSION_SUFFIX "$ENV{MELONDS_VERSION_SUFFIX}" CACHE STRING "Suffix to add to displayed melonDS version") + +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}") + 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>") 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. + + target_compile_options(core PRIVATE "$<$:-Wno-invalid-offsetof>") + # These warnings are excessive, and are only triggered in the ARMJIT code + # (which is fundamentally non-portable, so this is fine) +endif() + find_library(m MATH_LIBRARY) if (MATH_LIBRARY) @@ -142,11 +164,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 7cea845d..5e5b35ea 100644 --- a/src/CP15.cpp +++ b/src/CP15.cpp @@ -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 @@ -668,7 +672,7 @@ void ARMv5::CP15Write(u32 id, u32 val) Log(LogLevel::Debug, "unknown CP15 write op %03X %08X\n", id, val); } -u32 ARMv5::CP15Read(u32 id) +u32 ARMv5::CP15Read(u32 id) const { //printf("CP15 read op %03X %08X\n", id, NDS::ARM9->R[15]); diff --git a/src/DMA.cpp b/src/DMA.cpp index 717b38fa..0fc6cf05 100644 --- a/src/DMA.cpp +++ b/src/DMA.cpp @@ -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/DSi.cpp b/src/DSi.cpp index f2781e48..306c5d1c 100644 --- a/src/DSi.cpp +++ b/src/DSi.cpp @@ -17,8 +17,10 @@ */ #include +#include #include #include +#include "Args.h" #include "NDS.h" #include "DSi.h" #include "ARM.h" @@ -68,8 +70,8 @@ const u32 NDMAModes[] = 0xFF, // wifi / GBA cart slot (TODO) }; -DSi::DSi() noexcept : - NDS(1), +DSi::DSi(DSiArgs&& args) noexcept : + NDS(std::move(args), 1), NDMAs { DSi_NDMA(0, 0, *this), DSi_NDMA(0, 1, *this), @@ -80,9 +82,11 @@ DSi::DSi() noexcept : DSi_NDMA(1, 2, *this), DSi_NDMA(1, 3, *this), }, + ARM7iBIOS(*args.ARM7iBIOS), + ARM9iBIOS(*args.ARM9iBIOS), DSP(*this), - SDMMC(*this, 0), - SDIO(*this, 1), + SDMMC(*this, std::move(args.NANDImage), std::move(args.DSiSDCard)), + SDIO(*this), I2C(*this), CamModule(*this), AES(*this) @@ -90,7 +94,7 @@ DSi::DSi() noexcept : // Memory is owned by ARMJIT_Memory, don't free it NWRAM_A = JIT.Memory.GetNWRAM_A(); NWRAM_B = JIT.Memory.GetNWRAM_B(); - NWRAM_C = NDS::JIT.Memory.GetNWRAM_C(); + NWRAM_C = JIT.Memory.GetNWRAM_C(); } DSi::~DSi() noexcept @@ -108,6 +112,8 @@ void DSi::Reset() //ARM9.CP15Write(0x100, ARM9.CP15Read(0x100) | 0x00050000); NDS::Reset(); + // The SOUNDBIAS register does nothing on DSi + SPU.SetApplyBias(false); KeyInput &= ~(1 << (16+6)); MapSharedWRAM(3); @@ -118,9 +124,6 @@ void DSi::Reset() CamModule.Reset(); DSP.Reset(); - SDMMC.CloseHandles(); - SDIO.CloseHandles(); - LoadNAND(); SDMMC.Reset(); @@ -128,7 +131,7 @@ void DSi::Reset() AES.Reset(); - if (Platform::GetConfigBool(Platform::DSi_FullBIOSBoot)) + if (FullBIOSBoot) { SCFG_BIOS = 0x0000; } @@ -162,25 +165,23 @@ void DSi::Stop(Platform::StopReason reason) CamModule.Stop(); } -bool DSi::LoadCart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen) +void DSi::SetNDSCart(std::unique_ptr&& cart) { - if (NDS::LoadCart(romdata, romlen, savedata, savelen)) - { - SetCartInserted(true); - return true; - } - - return false; + NDS::SetNDSCart(std::move(cart)); + SetCartInserted(NDSCartSlot.GetCart() != nullptr); } -void DSi::EjectCart() +std::unique_ptr DSi::EjectCart() { - NDS::EjectCart(); + auto oldcart = NDS::EjectCart(); SetCartInserted(false); + + return oldcart; } -void DSi::CamInputFrame(int cam, u32* data, int width, int height, bool rgb) + +void DSi::CamInputFrame(int cam, const u32* data, int width, int height, bool rgb) { switch (cam) { @@ -276,7 +277,7 @@ void DSi::SetCartInserted(bool inserted) SCFG_MC |= 1; } -void DSi::DecryptModcryptArea(u32 offset, u32 size, u8* iv) +void DSi::DecryptModcryptArea(u32 offset, u32 size, const u8* iv) { AES_ctx ctx; u8 key[16]; @@ -509,9 +510,9 @@ void DSi::SetupDirectBoot() ARM9Write32(0x02FFE000+i, tmp); } - if (NANDImage && *NANDImage) + if (DSi_NAND::NANDImage* image = SDMMC.GetNAND(); image && *image) { // If a NAND image is installed, and it's valid... - if (DSi_NAND::NANDMount nand = DSi_NAND::NANDMount(*NANDImage)) + if (DSi_NAND::NANDMount nand = DSi_NAND::NANDMount(*image)) { DSi_NAND::DSiFirmwareSystemSettings userdata {}; nand.ReadUserData(userdata); @@ -531,7 +532,7 @@ void DSi::SetupDirectBoot() } } - Firmware::WifiBoard nwifiver = SPI.GetFirmware()->GetHeader().WifiBoard; + Firmware::WifiBoard nwifiver = SPI.GetFirmware().GetHeader().WifiBoard; ARM9Write8(0x020005E0, static_cast(nwifiver)); // TODO: these should be taken from the wifi firmware in NAND @@ -674,9 +675,6 @@ void DSi::SoftReset() // the DSP most likely gets reset DSP.Reset(); - SDMMC.CloseHandles(); - SDIO.CloseHandles(); - LoadNAND(); SDMMC.Reset(); @@ -684,7 +682,7 @@ void DSi::SoftReset() AES.Reset(); - if (Platform::GetConfigBool(Platform::DSi_FullBIOSBoot)) + if (FullBIOSBoot) { SCFG_BIOS = 0x0000; } @@ -709,21 +707,22 @@ void DSi::SoftReset() bool DSi::LoadNAND() { - if (!NANDImage) + DSi_NAND::NANDImage* image = SDMMC.GetNAND(); + if (!(image && *image)) { Log(LogLevel::Error, "No NAND image loaded\n"); return false; } Log(LogLevel::Info, "Loading DSi NAND\n"); - DSi_NAND::NANDMount nandmount(*NANDImage); + DSi_NAND::NANDMount nandmount(*SDMMC.GetNAND()); if (!nandmount) { Log(LogLevel::Error, "Failed to load DSi NAND\n"); return false; } - FileHandle* nand = NANDImage->GetFile(); + FileHandle* nand = image->GetFile(); // Make sure NWRAM is accessible. // The Bits are set to the startup values in Reset() and we might @@ -745,7 +744,7 @@ bool DSi::LoadNAND() memset(NWRAMMask, 0, sizeof(NWRAMMask)); u32 bootparams[8]; - if (Platform::GetConfigBool(Platform::DSi_FullBIOSBoot)) + if (FullBIOSBoot) { // TODO: figure out default MBK mapping // MBK1..5: disable mappings @@ -879,11 +878,11 @@ bool DSi::LoadNAND() } } - const DSi_NAND::DSiKey& emmccid = NANDImage->GetEMMCID(); + const DSi_NAND::DSiKey& emmccid = image->GetEMMCID(); Log(LogLevel::Debug, "eMMC CID: %08llX%08llX\n", *(const u64*)&emmccid[0], *(const u64*)&emmccid[8]); - Log(LogLevel::Debug, "Console ID: %" PRIx64 "\n", NANDImage->GetConsoleID()); + Log(LogLevel::Debug, "Console ID: %" PRIx64 "\n", image->GetConsoleID()); - if (Platform::GetConfigBool(Platform::DSi_FullBIOSBoot)) + if (FullBIOSBoot) { // point CPUs to boot ROM reset vectors ARM9.JumpTo(0xFFFF0000); @@ -958,21 +957,21 @@ void DSi::StallNDMAs() } -bool DSi::DMAsInMode(u32 cpu, u32 mode) +bool DSi::DMAsInMode(u32 cpu, u32 mode) const { if (NDS::DMAsInMode(cpu, mode)) return true; return NDMAsInMode(cpu, NDMAModes[mode]); } -bool DSi::DMAsRunning(u32 cpu) +bool DSi::DMAsRunning(u32 cpu) const { if (NDS::DMAsRunning(cpu)) return true; return NDMAsRunning(cpu); } -bool DSi::NDMAsInMode(u32 cpu, u32 mode) +bool DSi::NDMAsInMode(u32 cpu, u32 mode) const { cpu <<= 2; if (NDMAs[cpu+0].IsInMode(mode)) return true; @@ -982,7 +981,7 @@ bool DSi::NDMAsInMode(u32 cpu, u32 mode) return false; } -bool DSi::NDMAsRunning(u32 cpu) +bool DSi::NDMAsRunning(u32 cpu) const { cpu <<= 2; if (NDMAs[cpu+0].IsRunning()) return true; @@ -1728,12 +1727,12 @@ bool DSi::ARM9GetMemRegion(u32 addr, bool write, MemRegion* region) return false; } - region->Mem = ARM9BIOS; + region->Mem = &ARM9BIOS[0]; region->Mask = 0xFFF; } else { - region->Mem = ARM9iBIOS; + region->Mem = &ARM9iBIOS[0]; region->Mask = 0xFFFF; } return true; @@ -2678,14 +2677,14 @@ u8 DSi::ARM7IORead8(u32 addr) case 0x04004500: return I2C.ReadData(); case 0x04004501: return I2C.ReadCnt(); - case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() & 0xFF; - case 0x04004D01: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 8) & 0xFF; - case 0x04004D02: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 16) & 0xFF; - case 0x04004D03: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 24) & 0xFF; - case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 32) & 0xFF; - case 0x04004D05: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 40) & 0xFF; - case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 48) & 0xFF; - case 0x04004D07: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() >> 56; + case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() & 0xFF; + case 0x04004D01: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 8) & 0xFF; + case 0x04004D02: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 16) & 0xFF; + case 0x04004D03: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 24) & 0xFF; + case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 32) & 0xFF; + case 0x04004D05: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 40) & 0xFF; + case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 48) & 0xFF; + case 0x04004D07: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() >> 56; case 0x04004D08: return 0; case 0x4004700: return DSP.ReadSNDExCnt() & 0xFF; @@ -2726,10 +2725,10 @@ u16 DSi::ARM7IORead16(u32 addr) CASE_READ16_32BIT(0x0400405C, MBK[1][7]) CASE_READ16_32BIT(0x04004060, MBK[1][8]) - case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() & 0xFFFF; - case 0x04004D02: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 16) & 0xFFFF; - case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 32) & 0xFFFF; - case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() >> 48; + case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() & 0xFFFF; + case 0x04004D02: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 16) & 0xFFFF; + case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 32) & 0xFFFF; + case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() >> 48; case 0x04004D08: return 0; case 0x4004700: return DSP.ReadSNDExCnt(); @@ -2806,8 +2805,8 @@ u32 DSi::ARM7IORead32(u32 addr) case 0x04004400: return AES.ReadCnt(); case 0x0400440C: return AES.ReadOutputFIFO(); - case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() & 0xFFFFFFFF; - case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() >> 32; + case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() & 0xFFFFFFFF; + case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() >> 32; case 0x04004D08: return 0; case 0x4004700: diff --git a/src/DSi.h b/src/DSi.h index a221b5d7..1d010e0f 100644 --- a/src/DSi.h +++ b/src/DSi.h @@ -33,6 +33,7 @@ class DSi_I2CHost; class DSi_CamModule; class DSi_AES; class DSi_DSP; +class DSiArgs; namespace DSi_NAND { @@ -48,9 +49,8 @@ public: u16 SCFG_Clock9; u32 SCFG_EXT[2]; - u8 ARM9iBIOS[0x10000]; - u8 ARM7iBIOS[0x10000]; - std::unique_ptr NANDImage; + std::array ARM9iBIOS; + std::array ARM7iBIOS; DSi_SDHost SDMMC; DSi_SDHost SDIO; @@ -87,8 +87,8 @@ public: void RunNDMAs(u32 cpu); void StallNDMAs(); - bool NDMAsInMode(u32 cpu, u32 mode); - bool NDMAsRunning(u32 cpu); + bool NDMAsInMode(u32 cpu, u32 mode) const; + bool NDMAsRunning(u32 cpu) const; void CheckNDMAs(u32 cpu, u32 mode); void StopNDMAs(u32 cpu, u32 mode); @@ -130,22 +130,32 @@ public: void ARM7IOWrite32(u32 addr, u32 val) override; public: - DSi() noexcept; + DSi(DSiArgs&& args) noexcept; ~DSi() noexcept override; DSi(const DSi&) = delete; DSi& operator=(const DSi&) = delete; DSi(DSi&&) = delete; DSi& operator=(DSi&&) = delete; - bool LoadCart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen) override; - void EjectCart() override; - bool NeedsDirectBoot() override + void SetNDSCart(std::unique_ptr&& cart) override; + std::unique_ptr EjectCart() override; + bool NeedsDirectBoot() const override { // for now, DSi mode requires original BIOS/NAND return false; } - void CamInputFrame(int cam, u32* data, int width, int height, bool rgb) override; - bool DMAsInMode(u32 cpu, u32 mode) override; - bool DMAsRunning(u32 cpu) override; + + [[nodiscard]] const DSi_NAND::NANDImage& GetNAND() const noexcept { return *SDMMC.GetNAND(); } + [[nodiscard]] DSi_NAND::NANDImage& GetNAND() noexcept { return *SDMMC.GetNAND(); } + void SetNAND(DSi_NAND::NANDImage&& nand) noexcept { SDMMC.SetNAND(std::move(nand)); } + u64 GetConsoleID() const noexcept { return SDMMC.GetNAND()->GetConsoleID(); } + + [[nodiscard]] const FATStorage* GetSDCard() const noexcept { return SDMMC.GetSDCard(); } + void SetSDCard(FATStorage&& sdcard) noexcept { SDMMC.SetSDCard(std::move(sdcard)); } + void SetSDCard(std::optional&& sdcard) noexcept { SDMMC.SetSDCard(std::move(sdcard)); } + + void CamInputFrame(int cam, const u32* data, int width, int height, bool rgb) override; + bool DMAsInMode(u32 cpu, u32 mode) const override; + bool DMAsRunning(u32 cpu) const override; void StopDMAs(u32 cpu, u32 mode) override; void CheckDMAs(u32 cpu, u32 mode) override; u16 SCFG_Clock7; @@ -162,10 +172,13 @@ public: u8 GPIO_IE; u8 GPIO_WiFi; + bool GetFullBIOSBoot() const noexcept { return FullBIOSBoot; } + void SetFullBIOSBoot(bool full) noexcept { FullBIOSBoot = full; } private: + bool FullBIOSBoot; void Set_SCFG_Clock9(u16 val); void Set_SCFG_MC(u32 val); - void DecryptModcryptArea(u32 offset, u32 size, u8* iv); + void DecryptModcryptArea(u32 offset, u32 size, const u8* iv); void ApplyNewRAMSize(u32 size); }; diff --git a/src/DSi_AES.cpp b/src/DSi_AES.cpp index 8e04586a..379dea13 100644 --- a/src/DSi_AES.cpp +++ b/src/DSi_AES.cpp @@ -78,7 +78,7 @@ void DSi_AES::Reset() OutputMACDue = false; // initialize keys - u64 consoleid = DSi.NANDImage->GetConsoleID(); + u64 consoleid = DSi.SDMMC.GetNAND()->GetConsoleID(); // slot 0: modcrypt *(u32*)&KeyX[0][0] = 0x746E694E; @@ -235,7 +235,7 @@ void DSi_AES::ProcessBlock_CTR() } -u32 DSi_AES::ReadCnt() +u32 DSi_AES::ReadCnt() const { u32 ret = Cnt; diff --git a/src/DSi_AES.h b/src/DSi_AES.h index 4df82695..d83c870e 100644 --- a/src/DSi_AES.h +++ b/src/DSi_AES.h @@ -54,7 +54,7 @@ public: void Reset(); void DoSavestate(Savestate* file); - u32 ReadCnt(); + u32 ReadCnt() const; void WriteCnt(u32 val); void WriteBlkCnt(u32 val); diff --git a/src/DSi_Camera.cpp b/src/DSi_Camera.cpp index 225bea8b..a1cdbe0a 100644 --- a/src/DSi_Camera.cpp +++ b/src/DSi_Camera.cpp @@ -438,7 +438,7 @@ void DSi_Camera::Stop() Platform::Camera_Stop(Num); } -bool DSi_Camera::IsActivated() +bool DSi_Camera::IsActivated() const { if (StandbyCnt & (1<<14)) return false; // standby if (!(MiscCnt & (1<<9))) return false; // data transfer not enabled @@ -477,7 +477,7 @@ void DSi_Camera::StartTransfer() Platform::Camera_CaptureFrame(Num, FrameBuffer, 640, 480, true); } -bool DSi_Camera::TransferDone() +bool DSi_Camera::TransferDone() const { return TransferY >= FrameHeight; } @@ -590,7 +590,7 @@ void DSi_Camera::Write(u8 val, bool last) else DataPos++; } -u16 DSi_Camera::I2C_ReadReg(u16 addr) +u16 DSi_Camera::I2C_ReadReg(u16 addr) const { switch (addr) { @@ -695,7 +695,7 @@ void DSi_Camera::I2C_WriteReg(u16 addr, u16 val) // TODO: not sure at all what is the accessible range // or if there is any overlap in the address range -u8 DSi_Camera::MCU_Read(u16 addr) +u8 DSi_Camera::MCU_Read(u16 addr) const { addr &= 0x7FFF; @@ -724,7 +724,7 @@ void DSi_Camera::MCU_Write(u16 addr, u8 val) } -void DSi_Camera::InputFrame(u32* data, int width, int height, bool rgb) +void DSi_Camera::InputFrame(const u32* data, int width, int height, bool rgb) { // TODO: double-buffering? diff --git a/src/DSi_Camera.h b/src/DSi_Camera.h index ec409223..363cea43 100644 --- a/src/DSi_Camera.h +++ b/src/DSi_Camera.h @@ -38,10 +38,10 @@ public: void Reset() override; void Stop(); - bool IsActivated(); + bool IsActivated() const; void StartTransfer(); - bool TransferDone(); + bool TransferDone() const; // lengths in words int TransferScanline(u32* buffer, int maxlen); @@ -50,7 +50,7 @@ public: u8 Read(bool last) override; void Write(u8 val, bool last) override; - void InputFrame(u32* data, int width, int height, bool rgb); + void InputFrame(const u32* data, int width, int height, bool rgb); u32 Num; @@ -59,7 +59,7 @@ private: u32 RegAddr; u16 RegData; - u16 I2C_ReadReg(u16 addr); + u16 I2C_ReadReg(u16 addr) const; void I2C_WriteReg(u16 addr, u16 val); u16 PLLDiv; @@ -72,7 +72,7 @@ private: u16 MCUAddr; u8 MCURegs[0x8000]; - u8 MCU_Read(u16 addr); + u8 MCU_Read(u16 addr) const; void MCU_Write(u16 addr, u8 val); u16 FrameWidth, FrameHeight; @@ -91,7 +91,9 @@ public: void Stop(); void DoSavestate(Savestate* file); + const DSi_Camera* GetOuterCamera() const { return Camera0; } DSi_Camera* GetOuterCamera() { return Camera0; } + const DSi_Camera* GetInnerCamera() const { return Camera1; } DSi_Camera* GetInnerCamera() { return Camera1; } void IRQ(u32 param); diff --git a/src/DSi_DSP.cpp b/src/DSi_DSP.cpp index 088943a9..25abd474 100644 --- a/src/DSi_DSP.cpp +++ b/src/DSi_DSP.cpp @@ -34,7 +34,7 @@ const u32 DSi_DSP::DataMemoryOffset = 0x20000; // from Teakra memory_interface.h // NOTE: ^ IS IN DSP WORDS, NOT IN BYTES! -u16 DSi_DSP::GetPSTS() +u16 DSi_DSP::GetPSTS() const { u16 r = DSP_PSTS & (1<<9); // this is the only sticky bit //r &= ~((1<<2)|(1<<7)); // we support instant resets and wrfifo xfers @@ -182,7 +182,7 @@ void DSi_DSP::Reset() SNDExCnt = 0; } -bool DSi_DSP::IsRstReleased() +bool DSi_DSP::IsRstReleased() const { return SCFG_RST; } @@ -193,12 +193,12 @@ void DSi_DSP::SetRstLine(bool release) DSPTimestamp = DSi.ARM9Timestamp; // only start now! } -inline bool DSi_DSP::IsDSPCoreEnabled() +inline bool DSi_DSP::IsDSPCoreEnabled() const { return (DSi.SCFG_Clock9 & (1<<1)) && SCFG_RST && (!(DSP_PCFG & (1<<0))); } -inline bool DSi_DSP::IsDSPIOEnabled() +inline bool DSi_DSP::IsDSPIOEnabled() const { return (DSi.SCFG_Clock9 & (1<<1)) && SCFG_RST; } diff --git a/src/DSi_DSP.h b/src/DSi_DSP.h index a18dabf1..f76b4202 100644 --- a/src/DSi_DSP.h +++ b/src/DSi_DSP.h @@ -41,7 +41,7 @@ public: void DSPCatchUpU32(u32 _); // SCFG_RST bit0 - bool IsRstReleased(); + bool IsRstReleased() const; void SetRstLine(bool release); // DSP_* regs (0x040043xx) (NOTE: checks SCFG_EXT) @@ -54,7 +54,7 @@ public: u32 Read32(u32 addr); void Write32(u32 addr, u32 val); - u16 ReadSNDExCnt() { return SNDExCnt; } + u16 ReadSNDExCnt() const { return SNDExCnt; } void WriteSNDExCnt(u16 val, u16 mask); // NOTE: checks SCFG_CLK9 @@ -93,10 +93,10 @@ private: static const u32 DataMemoryOffset; - u16 GetPSTS(); + u16 GetPSTS() const; - inline bool IsDSPCoreEnabled(); - inline bool IsDSPIOEnabled(); + inline bool IsDSPCoreEnabled() const; + inline bool IsDSPIOEnabled() const; bool DSPCatchUp(); diff --git a/src/DSi_I2C.cpp b/src/DSi_I2C.cpp index d5ea60c6..28f98dc8 100644 --- a/src/DSi_I2C.cpp +++ b/src/DSi_I2C.cpp @@ -117,20 +117,20 @@ void DSi_BPTWL::DoSavestate(Savestate* file) } // TODO: Needs more investigation on the other bits -inline bool DSi_BPTWL::GetIRQMode() +inline bool DSi_BPTWL::GetIRQMode() const { return Registers[0x12] & 0x01; } -u8 DSi_BPTWL::GetBootFlag() { return Registers[0x70]; } +u8 DSi_BPTWL::GetBootFlag() const { return Registers[0x70]; } -bool DSi_BPTWL::GetBatteryCharging() { return Registers[0x20] >> 7; } +bool DSi_BPTWL::GetBatteryCharging() const { return Registers[0x20] >> 7; } void DSi_BPTWL::SetBatteryCharging(bool charging) { Registers[0x20] = (((charging ? 0x8 : 0x0) << 4) | (Registers[0x20] & 0x0F)); } -u8 DSi_BPTWL::GetBatteryLevel() { return Registers[0x20] & 0xF; } +u8 DSi_BPTWL::GetBatteryLevel() const { return Registers[0x20] & 0xF; } void DSi_BPTWL::SetBatteryLevel(u8 batteryLevel) { Registers[0x20] = ((Registers[0x20] & 0xF0) | (batteryLevel & 0x0F)); @@ -143,13 +143,13 @@ void DSi_BPTWL::SetBatteryLevel(u8 batteryLevel) } -u8 DSi_BPTWL::GetVolumeLevel() { return Registers[0x40]; } +u8 DSi_BPTWL::GetVolumeLevel() const { return Registers[0x40]; } void DSi_BPTWL::SetVolumeLevel(u8 volume) { Registers[0x40] = volume & 0x1F; } -u8 DSi_BPTWL::GetBacklightLevel() { return Registers[0x41]; } +u8 DSi_BPTWL::GetBacklightLevel() const { return Registers[0x41]; } void DSi_BPTWL::SetBacklightLevel(u8 backlight) { Registers[0x41] = backlight > 4 ? 4 : backlight; @@ -246,7 +246,7 @@ void DSi_BPTWL::SetVolumeSwitchReleased(u32 key) VolumeSwitchRepeatTime = 0.0; } -inline bool DSi_BPTWL::CheckVolumeSwitchKeysValid() +inline bool DSi_BPTWL::CheckVolumeSwitchKeysValid() const { bool up = VolumeSwitchKeysDown & (1 << volumeKey_Up); bool down = VolumeSwitchKeysDown & (1 << volumeKey_Down); diff --git a/src/DSi_I2C.h b/src/DSi_I2C.h index 51fe78e6..5dfeebd0 100644 --- a/src/DSi_I2C.h +++ b/src/DSi_I2C.h @@ -86,20 +86,20 @@ public: void Reset() override; void DoSavestate(Savestate* file) override; - u8 GetBootFlag(); + u8 GetBootFlag() const; - bool GetBatteryCharging(); + bool GetBatteryCharging() const; void SetBatteryCharging(bool charging); - u8 GetBatteryLevel(); + u8 GetBatteryLevel() const; void SetBatteryLevel(u8 batteryLevel); // 0-31 - u8 GetVolumeLevel(); + u8 GetVolumeLevel() const; void SetVolumeLevel(u8 volume); // 0-4 - u8 GetBacklightLevel(); + u8 GetBacklightLevel() const; void SetBacklightLevel(u8 backlight); void DoHardwareReset(bool direct); @@ -144,10 +144,10 @@ private: u8 Registers[0x100]; u32 CurPos; - bool GetIRQMode(); + bool GetIRQMode() const; void ResetButtonState(); - bool CheckVolumeSwitchKeysValid(); + bool CheckVolumeSwitchKeysValid() const; }; diff --git a/src/DSi_NAND.cpp b/src/DSi_NAND.cpp index 59f582fd..8da02540 100644 --- a/src/DSi_NAND.cpp +++ b/src/DSi_NAND.cpp @@ -122,7 +122,8 @@ NANDImage::NANDImage(NANDImage&& other) noexcept : ConsoleID(other.ConsoleID), FATIV(other.FATIV), FATKey(other.FATKey), - ESKey(other.ESKey) + ESKey(other.ESKey), + Length(other.Length) { other.CurFile = nullptr; } @@ -131,12 +132,16 @@ NANDImage& NANDImage::operator=(NANDImage&& other) noexcept { if (this != &other) { + if (CurFile) + CloseFile(CurFile); + CurFile = other.CurFile; eMMC_CID = other.eMMC_CID; ConsoleID = other.ConsoleID; FATIV = other.FATIV; FATKey = other.FATKey; ESKey = other.ESKey; + Length = other.Length; other.CurFile = nullptr; } @@ -362,7 +367,7 @@ bool NANDImage::ESEncrypt(u8* data, u32 len) const return true; } -bool NANDImage::ESDecrypt(u8* data, u32 len) +bool NANDImage::ESDecrypt(u8* data, u32 len) const { AES_ctx ctx; u8 iv[16]; diff --git a/src/DSi_NAND.h b/src/DSi_NAND.h index 699397bd..104845d5 100644 --- a/src/DSi_NAND.h +++ b/src/DSi_NAND.h @@ -71,7 +71,7 @@ private: u32 ReadFATBlock(u64 addr, u32 len, u8* buf); u32 WriteFATBlock(u64 addr, u32 len, const u8* buf); bool ESEncrypt(u8* data, u32 len) const; - bool ESDecrypt(u8* data, u32 len); + bool ESDecrypt(u8* data, u32 len) const; Platform::FileHandle* CurFile = nullptr; DSiKey eMMC_CID; u64 ConsoleID; diff --git a/src/DSi_NDMA.cpp b/src/DSi_NDMA.cpp index fe1f0ba7..7c77c9ad 100644 --- a/src/DSi_NDMA.cpp +++ b/src/DSi_NDMA.cpp @@ -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 7e87da7b..fb34dbdf 100644 --- a/src/DSi_NDMA.h +++ b/src/DSi_NDMA.h @@ -44,12 +44,12 @@ public: void Run9(); void Run7(); - bool IsInMode(u32 mode) + bool IsInMode(u32 mode) const { return ((mode == StartMode) && (Cnt & 0x80000000)); } - bool IsRunning() { return Running!=0; } + bool IsRunning() const { return Running!=0; } void StartIfNeeded(u32 mode) { diff --git a/src/DSi_NWifi.cpp b/src/DSi_NWifi.cpp index 9e006e2c..a6177dec 100644 --- a/src/DSi_NWifi.cpp +++ b/src/DSi_NWifi.cpp @@ -165,13 +165,13 @@ void DSi_NWifi::Reset() for (int i = 0; i < 9; i++) Mailbox[i].Clear(); - const Firmware* fw = DSi.SPI.GetFirmware(); + const Firmware& fw = DSi.SPI.GetFirmware(); - MacAddress mac = fw->GetHeader().MacAddr; + MacAddress mac = fw.GetHeader().MacAddr; Log(LogLevel::Info, "NWifi MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - Firmware::WifiBoard type = fw->GetHeader().WifiBoard; + Firmware::WifiBoard type = fw.GetHeader().WifiBoard; switch (type) { case Firmware::WifiBoard::W015: // AR6002 diff --git a/src/DSi_SD.cpp b/src/DSi_SD.cpp index 4cbf595d..72fe3756 100644 --- a/src/DSi_SD.cpp +++ b/src/DSi_SD.cpp @@ -18,6 +18,7 @@ #include #include +#include "Args.h" #include "DSi.h" #include "DSi_SD.h" #include "DSi_NAND.h" @@ -26,6 +27,10 @@ namespace melonDS { +using std::holds_alternative; +using std::unique_ptr; +using std::get_if; +using std::get; using namespace Platform; // observed IRQ behavior during transfers @@ -57,36 +62,38 @@ enum }; -DSi_SDHost::DSi_SDHost(melonDS::DSi& dsi, u32 num) : DSi(dsi) +DSi_SDHost::DSi_SDHost(melonDS::DSi& dsi, DSi_NAND::NANDImage&& nand, std::optional&& sdcard) noexcept : DSi(dsi), Num(0) { - Num = num; - - DSi.RegisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer, + DSi.RegisterEventFunc( Event_DSi_SDMMCTransfer, Transfer_TX, MemberEventFunc(DSi_SDHost, FinishTX)); - DSi.RegisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer, + DSi.RegisterEventFunc( Event_DSi_SDMMCTransfer, Transfer_RX, MemberEventFunc(DSi_SDHost, FinishRX)); - Ports[0] = nullptr; + Ports[0] = sdcard ? std::make_unique(DSi, this, std::move(*sdcard)) : nullptr; + sdcard = std::nullopt; // to ensure that sdcard isn't left with a moved-from object + Ports[1] = std::make_unique(DSi, this, std::move(nand)); +} + +// Creates an SDIO host +DSi_SDHost::DSi_SDHost(melonDS::DSi& dsi) noexcept : DSi(dsi), Num(1) +{ + DSi.RegisterEventFunc(Event_DSi_SDIOTransfer , + Transfer_TX, MemberEventFunc(DSi_SDHost, FinishTX)); + DSi.RegisterEventFunc(Event_DSi_SDIOTransfer, + Transfer_RX, MemberEventFunc(DSi_SDHost, FinishRX)); + + Ports[0] = std::make_unique(DSi, this); Ports[1] = nullptr; } DSi_SDHost::~DSi_SDHost() { - if (Ports[0]) delete Ports[0]; - if (Ports[1]) delete Ports[1]; - DSi.UnregisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer, Transfer_TX); DSi.UnregisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer, Transfer_RX); -} -void DSi_SDHost::CloseHandles() -{ - if (Ports[0]) delete Ports[0]; - if (Ports[1]) delete Ports[1]; - Ports[0] = nullptr; - Ports[1] = nullptr; + // unique_ptr's destructor will clean up the ports } void DSi_SDHost::Reset() @@ -129,48 +136,70 @@ void DSi_SDHost::Reset() TXReq = false; - CloseHandles(); + if (Ports[0]) Ports[0]->Reset(); + if (Ports[1]) Ports[1]->Reset(); +} - if (Num == 0) +FATStorage* DSi_SDHost::GetSDCard() noexcept +{ + if (Num != 0) return nullptr; + return static_cast(Ports[0].get())->GetSDCard(); +} + +const FATStorage* DSi_SDHost::GetSDCard() const noexcept +{ + if (Num != 0) return nullptr; + return static_cast(Ports[0].get())->GetSDCard(); +} + +DSi_NAND::NANDImage* DSi_SDHost::GetNAND() noexcept +{ + if (Num != 0) return nullptr; + return static_cast(Ports[1].get())->GetNAND(); +} + +const DSi_NAND::NANDImage* DSi_SDHost::GetNAND() const noexcept +{ + if (Num != 0) return nullptr; + return static_cast(Ports[1].get())->GetNAND(); +} + +void DSi_SDHost::SetSDCard(FATStorage&& sdcard) noexcept +{ + if (Num != 0) return; + + static_cast(Ports[0].get())->SetSDCard(std::move(sdcard)); +} + +void DSi_SDHost::SetSDCard(std::optional&& sdcard) noexcept +{ + if (Num != 0) return; + + if (sdcard) { - DSi_MMCStorage* sd; - DSi_MMCStorage* mmc; - - if (Platform::GetConfigBool(Platform::DSiSD_Enable)) + if (!Ports[0]) { - std::string folderpath; - if (Platform::GetConfigBool(Platform::DSiSD_FolderSync)) - folderpath = Platform::GetConfigString(Platform::DSiSD_FolderPath); - else - folderpath = ""; - - sd = new DSi_MMCStorage(this, - false, - Platform::GetConfigString(Platform::DSiSD_ImagePath), - (u64)Platform::GetConfigInt(Platform::DSiSD_ImageSize) * 1024 * 1024, - Platform::GetConfigBool(Platform::DSiSD_ReadOnly), - folderpath); - u8 sd_cid[16] = {0xBD, 0x12, 0x34, 0x56, 0x78, 0x03, 0x4D, 0x30, 0x30, 0x46, 0x50, 0x41, 0x00, 0x00, 0x15, 0x00}; - sd->SetCID(sd_cid); + Ports[0] = std::make_unique(DSi, this, std::move(*sdcard)); } else - sd = nullptr; - - mmc = new DSi_MMCStorage(this, *DSi.NANDImage); - mmc->SetCID(DSi.NANDImage->GetEMMCID().data()); - - Ports[0] = sd; - Ports[1] = mmc; + { + static_cast(Ports[0].get())->SetSDCard(std::move(*sdcard)); + } } else { - DSi_NWifi* nwifi = new DSi_NWifi(DSi, this); - - Ports[0] = nwifi; + Ports[0] = nullptr; } - if (Ports[0]) Ports[0]->Reset(); - if (Ports[1]) Ports[1]->Reset(); + sdcard = std::nullopt; + // a moved-from optional isn't empty, it contains a moved-from object +} + +void DSi_SDHost::SetNAND(DSi_NAND::NANDImage&& nand) noexcept +{ + if (Num != 0) return; + + static_cast(Ports[1].get())->SetNAND(std::move(nand)); } void DSi_SDHost::DoSavestate(Savestate* file) @@ -261,7 +290,7 @@ void DSi_SDHost::SetCardIRQ() if (!(CardIRQCtl & (1<<0))) return; u16 oldflags = CardIRQStatus & ~CardIRQMask; - DSi_SDDevice* dev = Ports[PortSelect & 0x1]; + DSi_SDDevice* dev = Ports[PortSelect & 0x1].get(); if (dev->IRQ) CardIRQStatus |= (1<<0); else CardIRQStatus &= ~(1<<0); @@ -307,7 +336,7 @@ void DSi_SDHost::FinishRX(u32 param) SetIRQ(24); } -u32 DSi_SDHost::DataRX(u8* data, u32 len) +u32 DSi_SDHost::DataRX(const u8* data, u32 len) { if (len != BlockLen16) { Log(LogLevel::Warn, "!! BAD BLOCKLEN\n"); len = BlockLen16; } @@ -332,7 +361,7 @@ u32 DSi_SDHost::DataRX(u8* data, u32 len) void DSi_SDHost::FinishTX(u32 param) { - DSi_SDDevice* dev = Ports[PortSelect & 0x1]; + DSi_SDDevice* dev = Ports[PortSelect & 0x1].get(); if (BlockCountInternal == 0) { @@ -411,7 +440,7 @@ u32 DSi_SDHost::DataTX(u8* data, u32 len) return len; } -u32 DSi_SDHost::GetTransferrableLen(u32 len) +u32 DSi_SDHost::GetTransferrableLen(u32 len) const { if (len > BlockLen16) len = BlockLen16; // checkme return len; @@ -419,7 +448,7 @@ u32 DSi_SDHost::GetTransferrableLen(u32 len) void DSi_SDHost::CheckRX() { - DSi_SDDevice* dev = Ports[PortSelect & 0x1]; + DSi_SDDevice* dev = Ports[PortSelect & 0x1].get(); CheckSwapFIFO(); @@ -459,7 +488,7 @@ void DSi_SDHost::CheckTX() return; } - DSi_SDDevice* dev = Ports[PortSelect & 0x1]; + DSi_SDDevice* dev = Ports[PortSelect & 0x1].get(); if (dev) dev->ContinueTransfer(); } @@ -550,7 +579,6 @@ u16 DSi_SDHost::ReadFIFO16() return 0; } - DSi_SDDevice* dev = Ports[PortSelect & 0x1]; u16 ret = DataFIFO[f].Read(); if (DataFIFO[f].IsEmpty()) @@ -571,7 +599,6 @@ u32 DSi_SDHost::ReadFIFO32() return 0; } - DSi_SDDevice* dev = Ports[PortSelect & 0x1]; u32 ret = DataFIFO32.Read(); if (DataFIFO32.IsEmpty()) @@ -593,7 +620,7 @@ void DSi_SDHost::Write(u32 addr, u16 val) Command = val; u8 cmd = Command & 0x3F; - DSi_SDDevice* dev = Ports[PortSelect & 0x1]; + DSi_SDDevice* dev = Ports[PortSelect & 0x1].get(); if (dev) { // CHECKME @@ -707,7 +734,6 @@ void DSi_SDHost::Write(u32 addr, u16 val) void DSi_SDHost::WriteFIFO16(u16 val) { - DSi_SDDevice* dev = Ports[PortSelect & 0x1]; u32 f = CurFIFO; if (DataFIFO[f].IsFull()) { @@ -780,34 +806,23 @@ void DSi_SDHost::CheckSwapFIFO() #define MMC_DESC (Internal?"NAND":"SDcard") -DSi_MMCStorage::DSi_MMCStorage(DSi_SDHost* host, DSi_NAND::NANDImage& nand) - : DSi_SDDevice(host), Internal(true), NAND(&nand), SD(nullptr) +DSi_MMCStorage::DSi_MMCStorage(melonDS::DSi& dsi, DSi_SDHost* host, DSi_NAND::NANDImage&& nand) noexcept + : DSi_SDDevice(host), DSi(dsi), Storage(std::move(nand)) { ReadOnly = false; + SetCID(get(Storage).GetEMMCID().data()); } -DSi_MMCStorage::DSi_MMCStorage(DSi_SDHost* host, bool internal, const std::string& filename, u64 size, bool readonly, const std::string& sourcedir) - : DSi_SDDevice(host) +DSi_MMCStorage::DSi_MMCStorage(melonDS::DSi& dsi, DSi_SDHost* host, FATStorage&& sdcard) noexcept + : DSi_SDDevice(host), DSi(dsi), Storage(std::move(sdcard)) { - Internal = internal; - NAND = nullptr; - - SD = new FATStorage(filename, size, readonly, sourcedir); - SD->Open(); - - ReadOnly = readonly; + ReadOnly = get(Storage).IsReadOnly(); + SetCID(DSiSDCardCID); } -DSi_MMCStorage::~DSi_MMCStorage() -{ - if (SD) - { - SD->Close(); - delete SD; - } - - // Do not close the NANDImage, it's not owned by this object -} +// The FATStorage or NANDImage is owned by this object; +// std::variant's destructor will clean it up. +DSi_MMCStorage::~DSi_MMCStorage() = default; void DSi_MMCStorage::Reset() { @@ -836,7 +851,7 @@ void DSi_MMCStorage::Reset() void DSi_MMCStorage::DoSavestate(Savestate* file) { - file->Section(Internal ? "NAND" : "SDCR"); + file->Section(holds_alternative(Storage) ? "NAND" : "SDCR"); file->VarArray(CID, 16); file->VarArray(CSD, 16); @@ -871,7 +886,7 @@ void DSi_MMCStorage::SendCMD(u8 cmd, u32 param) case 1: // SEND_OP_COND // CHECKME!! // also TODO: it's different for the SD card - if (Internal) + if (std::holds_alternative(Storage)) { param &= ~(1<<30); OCR &= 0xBF000000; @@ -895,7 +910,7 @@ void DSi_MMCStorage::SendCMD(u8 cmd, u32 param) return; case 3: // get/set RCA - if (Internal) + if (holds_alternative(Storage)) { RCA = param >> 16; Host->SendResponse(CSR|0x10000, true); // huh?? @@ -930,7 +945,8 @@ void DSi_MMCStorage::SendCMD(u8 cmd, u32 param) case 12: // stop operation SetState(0x04); - if (NAND) FileFlush(NAND->GetFile()); + if (auto* nand = get_if(&Storage)) + FileFlush(nand->GetFile()); RWCommand = 0; Host->SendResponse(CSR, true); return; @@ -1011,7 +1027,7 @@ void DSi_MMCStorage::SendACMD(u8 cmd, u32 param) // DSi boot2 sets this to 0x40100000 (hardcoded) // then has two codepaths depending on whether bit30 did get set // is it settable at all on the MMC? probably not. - if (Internal) param &= ~(1<<30); + if (holds_alternative(Storage)) param &= ~(1<<30); OCR &= 0xBF000000; OCR |= (param & 0x40FFFFFF); Host->SendResponse(OCR, true); @@ -1057,14 +1073,14 @@ u32 DSi_MMCStorage::ReadBlock(u64 addr) len = Host->GetTransferrableLen(len); u8 data[0x200]; - if (SD) + if (auto* sd = std::get_if(&Storage)) { - SD->ReadSectors((u32)(addr >> 9), 1, data); + sd->ReadSectors((u32)(addr >> 9), 1, data); } - else if (NAND) + else if (auto* nand = std::get_if(&Storage)) { - FileSeek(NAND->GetFile(), addr, FileSeekOrigin::Start); - FileRead(&data[addr & 0x1FF], 1, len, NAND->GetFile()); + FileSeek(nand->GetFile(), addr, FileSeekOrigin::Start); + FileRead(&data[addr & 0x1FF], 1, len, nand->GetFile()); } return Host->DataRX(&data[addr & 0x1FF], len); @@ -1078,23 +1094,23 @@ u32 DSi_MMCStorage::WriteBlock(u64 addr) u8 data[0x200]; if (len < 0x200) { - if (SD) + if (auto* sd = get_if(&Storage)) { - SD->ReadSectors((u32)(addr >> 9), 1, data); + sd->ReadSectors((u32)(addr >> 9), 1, data); } } if ((len = Host->DataTX(&data[addr & 0x1FF], len))) { if (!ReadOnly) { - if (SD) + if (auto* sd = get_if(&Storage)) { - SD->WriteSectors((u32)(addr >> 9), 1, data); + sd->WriteSectors((u32)(addr >> 9), 1, data); } - else if (NAND) + else if (auto* nand = get_if(&Storage)) { - FileSeek(NAND->GetFile(), addr, FileSeekOrigin::Start); - FileWrite(&data[addr & 0x1FF], 1, len, NAND->GetFile()); + FileSeek(nand->GetFile(), addr, FileSeekOrigin::Start); + FileWrite(&data[addr & 0x1FF], 1, len, nand->GetFile()); } } } diff --git a/src/DSi_SD.h b/src/DSi_SD.h index 17ba8d30..29620dc5 100644 --- a/src/DSi_SD.h +++ b/src/DSi_SD.h @@ -20,28 +20,30 @@ #define DSI_SD_H #include +#include #include "FIFO.h" #include "FATStorage.h" +#include "DSi_NAND.h" #include "Savestate.h" namespace melonDS { -namespace DSi_NAND -{ - class NANDImage; -} - class DSi_SDDevice; class DSi; +using Nothing = std::monostate; +using DSiStorage = std::variant; class DSi_SDHost { public: - DSi_SDHost(melonDS::DSi& dsi, u32 num); + /// Creates an SDMMC host. + DSi_SDHost(melonDS::DSi& dsi, DSi_NAND::NANDImage&& nand, std::optional&& sdcard = std::nullopt) noexcept; + + /// Creates an SDIO host + explicit DSi_SDHost(melonDS::DSi& dsi) noexcept; ~DSi_SDHost(); - void CloseHandles(); void Reset(); void DoSavestate(Savestate* file); @@ -49,9 +51,9 @@ public: void FinishRX(u32 param); void FinishTX(u32 param); void SendResponse(u32 val, bool last); - u32 DataRX(u8* data, u32 len); + u32 DataRX(const u8* data, u32 len); u32 DataTX(u8* data, u32 len); - u32 GetTransferrableLen(u32 len); + u32 GetTransferrableLen(u32 len) const; void CheckRX(); void CheckTX(); @@ -59,6 +61,15 @@ public: void SetCardIRQ(); + [[nodiscard]] FATStorage* GetSDCard() noexcept; + [[nodiscard]] const FATStorage* GetSDCard() const noexcept; + [[nodiscard]] DSi_NAND::NANDImage* GetNAND() noexcept; + [[nodiscard]] const DSi_NAND::NANDImage* GetNAND() const noexcept; + + void SetSDCard(FATStorage&& sdcard) noexcept; + void SetSDCard(std::optional&& sdcard) noexcept; + void SetNAND(DSi_NAND::NANDImage&& nand) noexcept; + u16 Read(u32 addr); void Write(u32 addr, u16 val); u16 ReadFIFO16(); @@ -96,7 +107,7 @@ private: u32 Param; u16 ResponseBuffer[8]; - DSi_SDDevice* Ports[2]; + std::array, 2> Ports {}; u32 CurFIFO; // FIFO accessible for read/write FIFO DataFIFO[2]; @@ -134,25 +145,53 @@ protected: class DSi_MMCStorage : public DSi_SDDevice { public: - DSi_MMCStorage(DSi_SDHost* host, DSi_NAND::NANDImage& nand); - DSi_MMCStorage(DSi_SDHost* host, bool internal, const std::string& filename, u64 size, bool readonly, const std::string& sourcedir); - ~DSi_MMCStorage(); + DSi_MMCStorage(melonDS::DSi& dsi, DSi_SDHost* host, DSi_NAND::NANDImage&& nand) noexcept; + DSi_MMCStorage(melonDS::DSi& dsi, DSi_SDHost* host, FATStorage&& sdcard) noexcept; + ~DSi_MMCStorage() override; - void Reset(); + [[nodiscard]] FATStorage* GetSDCard() noexcept { return std::get_if(&Storage); } + [[nodiscard]] const FATStorage* GetSDCard() const noexcept { return std::get_if(&Storage); } + [[nodiscard]] DSi_NAND::NANDImage* GetNAND() noexcept { return std::get_if(&Storage); } + [[nodiscard]] const DSi_NAND::NANDImage* GetNAND() const noexcept { return std::get_if(&Storage); } - void DoSavestate(Savestate* file); + void SetNAND(DSi_NAND::NANDImage&& nand) noexcept { Storage = std::move(nand); } + void SetSDCard(FATStorage&& sdcard) noexcept { Storage = std::move(sdcard); } + void SetSDCard(std::optional&& sdcard) noexcept + { + if (sdcard) + { // If we're setting a new SD card... + Storage = std::move(*sdcard); + sdcard = std::nullopt; + } + else + { + Storage = Nothing(); + } + } + + void SetStorage(DSiStorage&& storage) noexcept + { + Storage = std::move(storage); + storage = Nothing(); + // not sure if a moved-from variant is empty or contains a moved-from object; + // better to be safe than sorry + } + + void Reset() override; + + void DoSavestate(Savestate* file) override; void SetCID(const u8* cid) { memcpy(CID, cid, sizeof(CID)); } - void SendCMD(u8 cmd, u32 param); + void SendCMD(u8 cmd, u32 param) override; void SendACMD(u8 cmd, u32 param); - void ContinueTransfer(); + void ContinueTransfer() override; private: - bool Internal; - DSi_NAND::NANDImage* NAND; - FATStorage* SD; + static constexpr u8 DSiSDCardCID[16] = {0xBD, 0x12, 0x34, 0x56, 0x78, 0x03, 0x4D, 0x30, 0x30, 0x46, 0x50, 0x41, 0x00, 0x00, 0x15, 0x00}; + melonDS::DSi& DSi; + DSiStorage Storage; u8 CID[16]; u8 CSD[16]; diff --git a/src/DSi_SPI_TSC.cpp b/src/DSi_SPI_TSC.cpp index 6c7a15c7..d515db9f 100644 --- a/src/DSi_SPI_TSC.cpp +++ b/src/DSi_SPI_TSC.cpp @@ -121,7 +121,7 @@ void DSi_TSC::SetTouchCoords(u16 x, u16 y) } } -void DSi_TSC::MicInputFrame(s16* data, int samples) +void DSi_TSC::MicInputFrame(const s16* data, int samples) { if (TSCMode == 0x00) return TSC::MicInputFrame(data, samples); diff --git a/src/DSi_SPI_TSC.h b/src/DSi_SPI_TSC.h index 47777da5..d1a71063 100644 --- a/src/DSi_SPI_TSC.h +++ b/src/DSi_SPI_TSC.h @@ -40,7 +40,7 @@ public: void SetMode(u8 mode); void SetTouchCoords(u16 x, u16 y) override; - void MicInputFrame(s16* data, int samples) override; + void MicInputFrame(const s16* data, int samples) override; void Write(u8 val) override; void Release() override; diff --git a/src/FATStorage.cpp b/src/FATStorage.cpp index cd0a03c8..f735d4b8 100644 --- a/src/FATStorage.cpp +++ b/src/FATStorage.cpp @@ -29,47 +29,80 @@ namespace melonDS { namespace fs = std::filesystem; using namespace Platform; +using std::string; -FATStorage::FATStorage(const std::string& filename, u64 size, bool readonly, const std::string& sourcedir) +FATStorage::FATStorage(const std::string& filename, u64 size, bool readonly, const std::optional& sourcedir) : + FATStorage(FATStorageArgs { filename, size, readonly, sourcedir }) { - ReadOnly = readonly; - Load(filename, size, sourcedir); +} - File = nullptr; +FATStorage::FATStorage(const FATStorageArgs& args) noexcept : + FATStorage(args.Filename, args.Size, args.ReadOnly, args.SourceDir) +{ +} + +FATStorage::FATStorage(FATStorageArgs&& args) noexcept : + FilePath(std::move(args.Filename)), + FileSize(args.Size), + ReadOnly(args.ReadOnly), + SourceDir(std::move(args.SourceDir)) +{ + Load(FilePath, FileSize, SourceDir); +} + +FATStorage::FATStorage(FATStorage&& other) noexcept +{ + FilePath = std::move(other.FilePath); + IndexPath = std::move(other.IndexPath); + SourceDir = std::move(other.SourceDir); + ReadOnly = other.ReadOnly; + File = other.File; + FileSize = other.FileSize; + DirIndex = std::move(other.DirIndex); + FileIndex = std::move(other.FileIndex); + + other.File = nullptr; +} + +FATStorage& FATStorage::operator=(FATStorage&& other) noexcept +{ + if (this != &other) + { + if (File) + { // Sync this file's contents to the host (if applicable) before closing it + if (!ReadOnly) Save(); + CloseFile(File); + } + + FilePath = std::move(other.FilePath); + IndexPath = std::move(other.IndexPath); + SourceDir = std::move(other.SourceDir); + ReadOnly = other.ReadOnly; + File = other.File; + FileSize = other.FileSize; + DirIndex = std::move(other.DirIndex); + FileIndex = std::move(other.FileIndex); + + other.File = nullptr; + other.SourceDir = std::nullopt; + } + + return *this; } FATStorage::~FATStorage() { if (!ReadOnly) Save(); -} - -bool FATStorage::Open() -{ - File = Platform::OpenLocalFile(FilePath, FileMode::ReadWriteExisting); - if (!File) - { - return false; - } - - return true; -} - -void FATStorage::Close() -{ if (File) CloseFile(File); File = nullptr; } - bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len) { if (!File) return false; - if (FF_File) return false; - FF_File = File; - FF_FileSize = FileSize; - ff_disk_open(FF_ReadStorage, FF_WriteStorage, (LBA_t)(FileSize>>9)); + ff_disk_open(FF_ReadStorage(), FF_WriteStorage(), (LBA_t)(FileSize>>9)); FRESULT res; FATFS fs; @@ -77,8 +110,8 @@ 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(); - FF_File = nullptr; return false; } @@ -90,7 +123,6 @@ bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len) { f_unmount("0:"); ff_disk_close(); - FF_File = nullptr; return false; } @@ -100,34 +132,75 @@ bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len) f_unmount("0:"); ff_disk_close(); - FF_File = nullptr; return nwrite==len; } +u32 FATStorage::ReadFile(const std::string& path, u32 start, u32 len, u8* data) +{ + if (!File) return false; -u32 FATStorage::ReadSectors(u32 start, u32 num, u8* data) + ff_disk_open(FF_ReadStorage(), FF_WriteStorage(), (LBA_t)(FileSize>>9)); + + FRESULT res; + FATFS fs; + + res = f_mount(&fs, "0:", 1); + if (res != FR_OK) + { + f_unmount("0:"); + ff_disk_close(); + return false; + } + + std::string prefixedPath("0:/"); + prefixedPath += path; + FF_FIL file; + res = f_open(&file, prefixedPath.c_str(), FA_READ); + if (res != FR_OK) + { + f_unmount("0:"); + ff_disk_close(); + return false; + } + + u32 nread; + f_lseek(&file, start); + f_read(&file, data, len, &nread); + f_close(&file); + + f_unmount("0:"); + ff_disk_close(); + return nread; +} + +u32 FATStorage::ReadSectors(u32 start, u32 num, u8* data) const { return ReadSectorsInternal(File, FileSize, start, num, data); } -u32 FATStorage::WriteSectors(u32 start, u32 num, u8* data) +u32 FATStorage::WriteSectors(u32 start, u32 num, const u8* data) { if (ReadOnly) return 0; return WriteSectorsInternal(File, FileSize, start, num, data); } - -FileHandle* FATStorage::FF_File; -u64 FATStorage::FF_FileSize; - -UINT FATStorage::FF_ReadStorage(BYTE* buf, LBA_t sector, UINT num) +u64 FATStorage::GetSectorCount() const { - return ReadSectorsInternal(FF_File, FF_FileSize, sector, num, buf); + return FileSize / 0x200; } -UINT FATStorage::FF_WriteStorage(const BYTE* buf, LBA_t sector, UINT num) +ff_disk_read_cb FATStorage::FF_ReadStorage() const noexcept { - return WriteSectorsInternal(FF_File, FF_FileSize, sector, num, buf); + return [this](BYTE* buf, LBA_t sector, UINT num) { + return ReadSectorsInternal(File, FileSize, sector, num, buf); + }; +} + +ff_disk_write_cb FATStorage::FF_WriteStorage() const noexcept +{ + return [this](const BYTE* buf, LBA_t sector, UINT num) { + return WriteSectorsInternal(File, FileSize, sector, num, buf); + }; } @@ -907,7 +980,7 @@ bool FATStorage::ImportDirectory(const std::string& sourcedir) return true; } -u64 FATStorage::GetDirectorySize(fs::path sourcedir) +u64 FATStorage::GetDirectorySize(fs::path sourcedir) const { u64 ret = 0; u32 csize = 0x1000; // this is an estimate @@ -930,19 +1003,15 @@ u64 FATStorage::GetDirectorySize(fs::path sourcedir) return ret; } -bool FATStorage::Load(const std::string& filename, u64 size, const std::string& sourcedir) +bool FATStorage::Load(const std::string& filename, u64 size, const std::optional& sourcedir) { - FilePath = filename; - FileSize = size; - SourceDir = sourcedir; - - bool hasdir = !sourcedir.empty(); - if (hasdir) + bool hasdir = sourcedir && !sourcedir->empty(); + if (sourcedir) { - if (!fs::is_directory(fs::u8path(sourcedir))) + if (!fs::is_directory(fs::u8path(*sourcedir))) { hasdir = false; - SourceDir = ""; + SourceDir = std::nullopt; } } @@ -953,8 +1022,8 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::string& // with a minimum 128MB extra, otherwise size is defaulted to 512MB bool isnew = !Platform::LocalFileExists(filename); - FF_File = Platform::OpenLocalFile(filename, static_cast(FileMode::ReadWrite | FileMode::Preserve)); - if (!FF_File) + File = Platform::OpenLocalFile(filename, static_cast(FileMode::ReadWrite | FileMode::Preserve)); + if (!File) return false; IndexPath = FilePath + ".idx"; @@ -970,7 +1039,7 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::string& if (FileSize == 0) { - FileSize = FileLength(FF_File); + FileSize = FileLength(File); } } @@ -984,8 +1053,7 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::string& } else { - FF_FileSize = FileSize; - ff_disk_open(FF_ReadStorage, FF_WriteStorage, (LBA_t)(FF_FileSize>>9)); + ff_disk_open(FF_ReadStorage(), FF_WriteStorage(), (LBA_t)(FileSize>>9)); res = f_mount(&fs, "0:", 1); if (res != FR_OK) @@ -1005,7 +1073,7 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::string& { if (hasdir) { - FileSize = GetDirectorySize(fs::u8path(sourcedir)); + FileSize = GetDirectorySize(fs::u8path(*sourcedir)); FileSize += 0x8000000ULL; // 128MB leeway // make it a power of two @@ -1021,9 +1089,8 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::string& FileSize = 0x20000000ULL; // 512MB } - FF_FileSize = FileSize; ff_disk_close(); - ff_disk_open(FF_ReadStorage, FF_WriteStorage, (LBA_t)(FF_FileSize>>9)); + ff_disk_open(FF_ReadStorage(), FF_WriteStorage(), (LBA_t)(FileSize>>9)); DirIndex.clear(); FileIndex.clear(); @@ -1054,33 +1121,24 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::string& if (res == FR_OK) { if (hasdir) - ImportDirectory(sourcedir); + ImportDirectory(*sourcedir); } f_unmount("0:"); ff_disk_close(); - CloseFile(FF_File); - FF_File = nullptr; return true; } bool FATStorage::Save() { - if (SourceDir.empty()) - { - return true; + if (!SourceDir) + { // If we're not syncing the SD card image to a host directory... + return true; // Not an error. } - FF_File = Platform::OpenLocalFile(FilePath, FileMode::ReadWriteExisting); - if (!FF_File) - { - return false; - } - - FF_FileSize = FileSize; - ff_disk_open(FF_ReadStorage, FF_WriteStorage, (LBA_t)(FileSize>>9)); + ff_disk_open(FF_ReadStorage(), FF_WriteStorage(), (LBA_t)(FileSize>>9)); FRESULT res; FATFS fs; @@ -1088,21 +1146,18 @@ bool FATStorage::Save() res = f_mount(&fs, "0:", 1); if (res != FR_OK) { + f_unmount("0:"); ff_disk_close(); - CloseFile(FF_File); - FF_File = nullptr; return false; } - ExportChanges(SourceDir); + ExportChanges(*SourceDir); SaveIndex(); f_unmount("0:"); ff_disk_close(); - CloseFile(FF_File); - FF_File = nullptr; return true; } diff --git a/src/FATStorage.h b/src/FATStorage.h index 2bdafad8..00628461 100644 --- a/src/FATStorage.h +++ b/src/FATStorage.h @@ -22,41 +22,63 @@ #include #include #include +#include #include #include "Platform.h" #include "types.h" #include "fatfs/ff.h" +#include "FATIO.h" namespace melonDS { +/// Contains information necessary to load an SD card image. +/// The intended use case is for loading homebrew NDS ROMs; +/// you won't know that a ROM is homebrew until you parse it, +/// so if you load the SD card before the ROM +/// then you might end up discarding it. +struct FATStorageArgs +{ + std::string Filename; + + /// Size of the desired SD card in bytes, or 0 for auto-detect. + u64 Size; + bool ReadOnly; + std::optional SourceDir; +}; + class FATStorage { public: - FATStorage(const std::string& filename, u64 size, bool readonly, const std::string& sourcedir); + FATStorage(const std::string& filename, u64 size, bool readonly, const std::optional& sourcedir = std::nullopt); + explicit FATStorage(const FATStorageArgs& args) noexcept; + explicit FATStorage(FATStorageArgs&& args) noexcept; + FATStorage(FATStorage&& other) noexcept; + FATStorage(const FATStorage& other) = delete; + FATStorage& operator=(const FATStorage& other) = delete; + FATStorage& operator=(FATStorage&& other) noexcept; ~FATStorage(); - bool Open(); - void Close(); - bool InjectFile(const std::string& path, u8* data, u32 len); + u32 ReadFile(const std::string& path, u32 start, u32 len, u8* data); - u32 ReadSectors(u32 start, u32 num, u8* data); - u32 WriteSectors(u32 start, u32 num, u8* data); + u32 ReadSectors(u32 start, u32 num, u8* data) const; + u32 WriteSectors(u32 start, u32 num, const u8* data); + + [[nodiscard]] bool IsReadOnly() const noexcept { return ReadOnly; } + u64 GetSectorCount() const; private: std::string FilePath; std::string IndexPath; - std::string SourceDir; + std::optional SourceDir; bool ReadOnly; Platform::FileHandle* File; u64 FileSize; - static Platform::FileHandle* FF_File; - static u64 FF_FileSize; - static UINT FF_ReadStorage(BYTE* buf, LBA_t sector, UINT num); - static UINT FF_WriteStorage(const BYTE* buf, LBA_t sector, UINT num); + [[nodiscard]] ff_disk_read_cb FF_ReadStorage() const noexcept; + [[nodiscard]] ff_disk_write_cb FF_WriteStorage() const noexcept; static u32 ReadSectorsInternal(Platform::FileHandle* file, u64 filelen, u32 start, u32 num, u8* data); static u32 WriteSectorsInternal(Platform::FileHandle* file, u64 filelen, u32 start, u32 num, const u8* data); @@ -74,9 +96,9 @@ private: void CleanupDirectory(const std::string& sourcedir, const std::string& path, int level); bool ImportFile(const std::string& path, std::filesystem::path in); bool ImportDirectory(const std::string& sourcedir); - u64 GetDirectorySize(std::filesystem::path sourcedir); + u64 GetDirectorySize(std::filesystem::path sourcedir) const; - bool Load(const std::string& filename, u64 size, const std::string& sourcedir); + bool Load(const std::string& filename, u64 size, const std::optional& sourcedir); bool Save(); typedef struct diff --git a/src/FIFO.h b/src/FIFO.h index cbff4ab9..026c2c7f 100644 --- a/src/FIFO.h +++ b/src/FIFO.h @@ -74,12 +74,12 @@ public: return ret; } - T Peek() + T Peek() const { return Entries[ReadPos]; } - T Peek(u32 offset) + T Peek(u32 offset) const { u32 pos = ReadPos + offset; if (pos >= NumEntries) @@ -88,11 +88,11 @@ public: return Entries[pos]; } - u32 Level() { return NumOccupied; } - bool IsEmpty() { return NumOccupied == 0; } - bool IsFull() { return NumOccupied >= NumEntries; } + u32 Level() const { return NumOccupied; } + bool IsEmpty() const { return NumOccupied == 0; } + bool IsFull() const { return NumOccupied >= NumEntries; } - bool CanFit(u32 num) { return ((NumOccupied + num) <= NumEntries); } + bool CanFit(u32 num) const { return ((NumOccupied + num) <= NumEntries); } private: T Entries[NumEntries] = {0}; @@ -164,12 +164,12 @@ public: return ret; } - T Peek() + T Peek() const { return Entries[ReadPos]; } - T Peek(u32 offset) + T Peek(u32 offset) const { u32 pos = ReadPos + offset; if (pos >= NumEntries) @@ -178,11 +178,11 @@ public: return Entries[pos]; } - u32 Level() { return NumOccupied; } - bool IsEmpty() { return NumOccupied == 0; } - bool IsFull() { return NumOccupied >= NumEntries; } + u32 Level() const { return NumOccupied; } + bool IsEmpty() const { return NumOccupied == 0; } + bool IsFull() const { return NumOccupied >= NumEntries; } - bool CanFit(u32 num) { return ((NumOccupied + num) <= NumEntries); } + bool CanFit(u32 num) const { return ((NumOccupied + num) <= NumEntries); } private: u32 NumEntries; diff --git a/src/FreeBIOS.cpp b/src/FreeBIOS.cpp index 7eef18b7..f5d8f366 100644 --- a/src/FreeBIOS.cpp +++ b/src/FreeBIOS.cpp @@ -28,10 +28,10 @@ namespace melonDS { -unsigned char bios_arm7_bin[] = { +std::array bios_arm7_bin = { 0x1c, 0x04, 0x00, 0xea, 0x1c, 0x04, 0x00, 0xea, 0x1c, 0x04, 0x00, 0xea, 0x1a, 0x04, 0x00, 0xea, 0x19, 0x04, 0x00, 0xea, 0x18, 0x04, 0x00, 0xea, - 0xe3, 0x07, 0x00, 0xea, 0x16, 0x04, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, + 0xe5, 0x07, 0x00, 0xea, 0x16, 0x04, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -385,17 +385,17 @@ unsigned char bios_arm7_bin[] = { 0x80, 0x40, 0x04, 0xe2, 0x1f, 0x40, 0x84, 0xe3, 0x02, 0xc0, 0x5e, 0xe5, 0x04, 0xf0, 0x29, 0xe1, 0x00, 0x40, 0x2d, 0xe9, 0x20, 0x00, 0x5c, 0xe3, 0x01, 0xc0, 0xa0, 0xa3, 0x0c, 0xf1, 0x9f, 0xe7, 0x00, 0x00, 0xa0, 0xe1, - 0xd4, 0x1f, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, + 0xdc, 0x1f, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x5c, 0x11, 0x00, 0x00, 0x90, 0x11, 0x00, 0x00, 0x88, 0x11, 0x00, 0x00, 0x4c, 0x11, 0x00, 0x00, 0xbc, 0x11, 0x00, 0x00, 0xcc, 0x11, 0x00, 0x00, 0xe8, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x44, 0x12, 0x00, 0x00, 0xc4, 0x12, 0x00, 0x00, 0x04, 0x13, 0x00, 0x00, 0x50, 0x13, 0x00, 0x00, - 0xe8, 0x13, 0x00, 0x00, 0xf0, 0x13, 0x00, 0x00, 0x84, 0x14, 0x00, 0x00, - 0x00, 0x15, 0x00, 0x00, 0xac, 0x15, 0x00, 0x00, 0xb0, 0x15, 0x00, 0x00, - 0xb0, 0x15, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, - 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x88, 0x16, 0x00, 0x00, - 0x9c, 0x1c, 0x00, 0x00, 0xc4, 0x1f, 0x00, 0x00, 0x84, 0x1f, 0x00, 0x00, - 0xa0, 0x1f, 0x00, 0x00, 0x00, 0x40, 0xbd, 0xe8, 0xd3, 0x40, 0xa0, 0xe3, + 0xf0, 0x13, 0x00, 0x00, 0xf8, 0x13, 0x00, 0x00, 0x8c, 0x14, 0x00, 0x00, + 0x08, 0x15, 0x00, 0x00, 0xb4, 0x15, 0x00, 0x00, 0xb8, 0x15, 0x00, 0x00, + 0xb8, 0x15, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, + 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x90, 0x16, 0x00, 0x00, + 0xa4, 0x1c, 0x00, 0x00, 0xcc, 0x1f, 0x00, 0x00, 0x8c, 0x1f, 0x00, 0x00, + 0xa8, 0x1f, 0x00, 0x00, 0x00, 0x40, 0xbd, 0xe8, 0xd3, 0x40, 0xa0, 0xe3, 0x04, 0xf0, 0x29, 0xe1, 0x10, 0x00, 0xbd, 0xe8, 0x04, 0xf0, 0x69, 0xe1, 0x10, 0x50, 0xbd, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0xa0, 0xe3, 0x80, 0x20, 0xa0, 0xe3, 0x01, 0x23, 0xc0, 0xe5, @@ -442,205 +442,206 @@ unsigned char bios_arm7_bin[] = { 0x01, 0xf0, 0x00, 0x3c, 0x00, 0x28, 0x01, 0xe4, 0x01, 0xa0, 0x00, 0x6c, 0x00, 0x78, 0x01, 0xb4, 0x00, 0x50, 0x01, 0x9c, 0x01, 0x88, 0x00, 0x44, 0x20, 0x00, 0x2d, 0xe9, 0x1e, 0x40, 0xa0, 0xe3, 0x30, 0xe0, 0x4f, 0xe2, - 0xa2, 0x20, 0xb0, 0xe1, 0x1e, 0x00, 0x00, 0x0a, 0xb2, 0x30, 0xd1, 0xe0, + 0xff, 0x04, 0xc0, 0xe3, 0xff, 0x08, 0xc0, 0xe3, 0xa2, 0x20, 0xb0, 0xe1, + 0x1e, 0x00, 0x00, 0x0a, 0xb2, 0x30, 0xd1, 0xe0, 0x80, 0x50, 0x04, 0xe0, + 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, + 0x83, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, - 0x0c, 0x00, 0x20, 0xe0, 0x83, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, + 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x51, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, - 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x51, 0x04, 0xe0, + 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x53, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, - 0xa3, 0x53, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, - 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, - 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x55, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, - 0x0c, 0x00, 0x20, 0xe0, 0x01, 0x20, 0x52, 0xe2, 0xe0, 0xff, 0xff, 0x1a, - 0x20, 0x00, 0xbd, 0xe8, 0x50, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe3, - 0x4e, 0xff, 0xff, 0xea, 0xe0, 0x03, 0x2d, 0xe9, 0xb0, 0x30, 0xd2, 0xe1, - 0x02, 0xc0, 0xd2, 0xe5, 0x03, 0xe0, 0xd2, 0xe5, 0x04, 0x40, 0x92, 0xe5, - 0xa4, 0x2f, 0xa0, 0xe1, 0x02, 0x41, 0xc4, 0xe3, 0x01, 0x50, 0xa0, 0xe3, - 0x15, 0x5c, 0xa0, 0xe1, 0x01, 0x50, 0x45, 0xe2, 0x01, 0x60, 0xa0, 0xe3, - 0x00, 0x70, 0xa0, 0xe3, 0x00, 0x80, 0xa0, 0xe3, 0x01, 0x00, 0x5c, 0xe3, - 0x83, 0x31, 0xa0, 0x01, 0x02, 0x00, 0x5c, 0xe3, 0x03, 0x31, 0xa0, 0x01, - 0x04, 0x00, 0x5c, 0xe3, 0x83, 0x30, 0xa0, 0x01, 0x01, 0x00, 0x56, 0xe3, - 0x01, 0x60, 0xd0, 0x04, 0x01, 0x6c, 0x86, 0x03, 0x05, 0x90, 0x06, 0xe0, - 0x36, 0x6c, 0xa0, 0xe1, 0x00, 0x00, 0x59, 0xe3, 0x01, 0x00, 0x12, 0x03, - 0x04, 0x90, 0x89, 0x10, 0x19, 0x78, 0x87, 0xe1, 0x0e, 0x80, 0x88, 0xe0, - 0x20, 0x00, 0x58, 0xe3, 0x04, 0x70, 0x81, 0x04, 0x00, 0x70, 0xa0, 0x03, - 0x00, 0x80, 0xa0, 0x03, 0x01, 0x30, 0x53, 0xe2, 0xef, 0xff, 0xff, 0x1a, - 0xe0, 0x03, 0xbd, 0xe8, 0x29, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, - 0x23, 0x24, 0xb0, 0xe1, 0x26, 0xff, 0xff, 0x0a, 0x01, 0x30, 0xd0, 0xe4, - 0x02, 0x35, 0x83, 0xe3, 0x80, 0x00, 0x13, 0xe3, 0x10, 0x00, 0x00, 0x0a, - 0x01, 0x40, 0xd0, 0xe4, 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, - 0x24, 0xe6, 0xa0, 0xe1, 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, - 0x0c, 0xc0, 0x41, 0xe0, 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x40, 0xdc, 0xe4, - 0x01, 0x20, 0x52, 0xe2, 0x01, 0x40, 0xc1, 0xe4, 0x16, 0xff, 0xff, 0x0a, - 0x01, 0xe0, 0x5e, 0xe2, 0xf9, 0xff, 0xff, 0x1a, 0x83, 0x30, 0xb0, 0xe1, - 0xed, 0xff, 0xff, 0x5a, 0xea, 0xff, 0xff, 0xea, 0x01, 0x40, 0xd0, 0xe4, - 0x01, 0x20, 0x52, 0xe2, 0x01, 0x40, 0xc1, 0xe4, 0x0d, 0xff, 0xff, 0x0a, - 0x83, 0x30, 0xb0, 0xe1, 0xe6, 0xff, 0xff, 0x5a, 0xe3, 0xff, 0xff, 0xea, - 0x04, 0x30, 0x90, 0xe4, 0x23, 0x24, 0xb0, 0xe1, 0x07, 0xff, 0xff, 0x0a, - 0x20, 0x00, 0x2d, 0xe9, 0x00, 0x50, 0xa0, 0xe3, 0x01, 0x30, 0xd0, 0xe4, - 0x02, 0x35, 0x83, 0xe3, 0x80, 0x00, 0x13, 0xe3, 0x15, 0x00, 0x00, 0x0a, - 0x01, 0x40, 0xd0, 0xe4, 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, - 0x24, 0xe6, 0xa0, 0xe1, 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, - 0x0c, 0xc0, 0x41, 0xe0, 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x00, 0x11, 0xe3, - 0x01, 0x50, 0xdc, 0x04, 0x01, 0x40, 0xdc, 0x14, 0x04, 0x44, 0x85, 0x11, - 0xb1, 0x40, 0x41, 0x11, 0x01, 0x10, 0x81, 0xe2, 0x01, 0x20, 0x52, 0xe2, - 0x20, 0x00, 0xbd, 0x08, 0xf0, 0xfe, 0xff, 0x0a, 0x01, 0xe0, 0x5e, 0xe2, - 0xf4, 0xff, 0xff, 0x1a, 0x83, 0x30, 0xb0, 0xe1, 0xe8, 0xff, 0xff, 0x5a, - 0xe5, 0xff, 0xff, 0xea, 0x01, 0x00, 0x11, 0xe3, 0x01, 0x50, 0xd0, 0x04, - 0x01, 0x40, 0xd0, 0x14, 0x04, 0x44, 0x85, 0x11, 0xb1, 0x40, 0x41, 0x11, + 0xa3, 0x55, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, + 0x01, 0x20, 0x52, 0xe2, 0xe0, 0xff, 0xff, 0x1a, 0x20, 0x00, 0xbd, 0xe8, + 0x4e, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe3, 0x4c, 0xff, 0xff, 0xea, + 0xe0, 0x03, 0x2d, 0xe9, 0xb0, 0x30, 0xd2, 0xe1, 0x02, 0xc0, 0xd2, 0xe5, + 0x03, 0xe0, 0xd2, 0xe5, 0x04, 0x40, 0x92, 0xe5, 0xa4, 0x2f, 0xa0, 0xe1, + 0x02, 0x41, 0xc4, 0xe3, 0x01, 0x50, 0xa0, 0xe3, 0x15, 0x5c, 0xa0, 0xe1, + 0x01, 0x50, 0x45, 0xe2, 0x01, 0x60, 0xa0, 0xe3, 0x00, 0x70, 0xa0, 0xe3, + 0x00, 0x80, 0xa0, 0xe3, 0x01, 0x00, 0x5c, 0xe3, 0x83, 0x31, 0xa0, 0x01, + 0x02, 0x00, 0x5c, 0xe3, 0x03, 0x31, 0xa0, 0x01, 0x04, 0x00, 0x5c, 0xe3, + 0x83, 0x30, 0xa0, 0x01, 0x01, 0x00, 0x56, 0xe3, 0x01, 0x60, 0xd0, 0x04, + 0x01, 0x6c, 0x86, 0x03, 0x05, 0x90, 0x06, 0xe0, 0x36, 0x6c, 0xa0, 0xe1, + 0x00, 0x00, 0x59, 0xe3, 0x01, 0x00, 0x12, 0x03, 0x04, 0x90, 0x89, 0x10, + 0x19, 0x78, 0x87, 0xe1, 0x0e, 0x80, 0x88, 0xe0, 0x20, 0x00, 0x58, 0xe3, + 0x04, 0x70, 0x81, 0x04, 0x00, 0x70, 0xa0, 0x03, 0x00, 0x80, 0xa0, 0x03, + 0x01, 0x30, 0x53, 0xe2, 0xef, 0xff, 0xff, 0x1a, 0xe0, 0x03, 0xbd, 0xe8, + 0x27, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, 0x23, 0x24, 0xb0, 0xe1, + 0x24, 0xff, 0xff, 0x0a, 0x01, 0x30, 0xd0, 0xe4, 0x02, 0x35, 0x83, 0xe3, + 0x80, 0x00, 0x13, 0xe3, 0x10, 0x00, 0x00, 0x0a, 0x01, 0x40, 0xd0, 0xe4, + 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, 0x24, 0xe6, 0xa0, 0xe1, + 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, 0x0c, 0xc0, 0x41, 0xe0, + 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x40, 0xdc, 0xe4, 0x01, 0x20, 0x52, 0xe2, + 0x01, 0x40, 0xc1, 0xe4, 0x14, 0xff, 0xff, 0x0a, 0x01, 0xe0, 0x5e, 0xe2, + 0xf9, 0xff, 0xff, 0x1a, 0x83, 0x30, 0xb0, 0xe1, 0xed, 0xff, 0xff, 0x5a, + 0xea, 0xff, 0xff, 0xea, 0x01, 0x40, 0xd0, 0xe4, 0x01, 0x20, 0x52, 0xe2, + 0x01, 0x40, 0xc1, 0xe4, 0x0b, 0xff, 0xff, 0x0a, 0x83, 0x30, 0xb0, 0xe1, + 0xe6, 0xff, 0xff, 0x5a, 0xe3, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, + 0x23, 0x24, 0xb0, 0xe1, 0x05, 0xff, 0xff, 0x0a, 0x20, 0x00, 0x2d, 0xe9, + 0x00, 0x50, 0xa0, 0xe3, 0x01, 0x30, 0xd0, 0xe4, 0x02, 0x35, 0x83, 0xe3, + 0x80, 0x00, 0x13, 0xe3, 0x15, 0x00, 0x00, 0x0a, 0x01, 0x40, 0xd0, 0xe4, + 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, 0x24, 0xe6, 0xa0, 0xe1, + 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, 0x0c, 0xc0, 0x41, 0xe0, + 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x00, 0x11, 0xe3, 0x01, 0x50, 0xdc, 0x04, + 0x01, 0x40, 0xdc, 0x14, 0x04, 0x44, 0x85, 0x11, 0xb1, 0x40, 0x41, 0x11, 0x01, 0x10, 0x81, 0xe2, 0x01, 0x20, 0x52, 0xe2, 0x20, 0x00, 0xbd, 0x08, - 0xe2, 0xfe, 0xff, 0x0a, 0x83, 0x30, 0xb0, 0xe1, 0xdc, 0xff, 0xff, 0x5a, - 0xd9, 0xff, 0xff, 0xea, 0xde, 0xfe, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, - 0x23, 0x24, 0xa0, 0xe1, 0x01, 0x30, 0xd0, 0xe4, 0x80, 0x00, 0x13, 0xe3, - 0x7f, 0x30, 0x03, 0xe2, 0x07, 0x00, 0x00, 0x0a, 0x01, 0xc0, 0xd0, 0xe4, - 0x03, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xc1, 0xe4, 0x01, 0x20, 0x52, 0xe2, - 0xd3, 0xfe, 0xff, 0x0a, 0x01, 0x30, 0x53, 0xe2, 0xfa, 0xff, 0xff, 0x1a, - 0xf3, 0xff, 0xff, 0xea, 0x01, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xd0, 0xe4, - 0x01, 0x20, 0x52, 0xe2, 0x01, 0xc0, 0xc1, 0xe4, 0xcb, 0xfe, 0xff, 0x0a, - 0x01, 0x30, 0x53, 0xe2, 0xf9, 0xff, 0xff, 0x1a, 0xeb, 0xff, 0xff, 0xea, - 0x00, 0x00, 0x24, 0x03, 0x48, 0x06, 0x6a, 0x09, 0x8c, 0x0c, 0xab, 0x0f, - 0xc8, 0x12, 0xe2, 0x15, 0xf9, 0x18, 0x0b, 0x1c, 0x1a, 0x1f, 0x23, 0x22, - 0x28, 0x25, 0x26, 0x28, 0x1f, 0x2b, 0x11, 0x2e, 0xfb, 0x30, 0xdf, 0x33, - 0xba, 0x36, 0x8c, 0x39, 0x56, 0x3c, 0x17, 0x3f, 0xce, 0x41, 0x7a, 0x44, - 0x1c, 0x47, 0xb4, 0x49, 0x3f, 0x4c, 0xbf, 0x4e, 0x33, 0x51, 0x9b, 0x53, - 0xf5, 0x55, 0x42, 0x58, 0x82, 0x5a, 0xb3, 0x5c, 0xd7, 0x5e, 0xeb, 0x60, - 0xf1, 0x62, 0xe8, 0x64, 0xcf, 0x66, 0xa6, 0x68, 0x6d, 0x6a, 0x23, 0x6c, - 0xc9, 0x6d, 0x5e, 0x6f, 0xe2, 0x70, 0x54, 0x72, 0xb5, 0x73, 0x04, 0x75, - 0x41, 0x76, 0x6b, 0x77, 0x84, 0x78, 0x89, 0x79, 0x7c, 0x7a, 0x5c, 0x7b, - 0x29, 0x7c, 0xe3, 0x7c, 0x89, 0x7d, 0x1d, 0x7e, 0x9c, 0x7e, 0x09, 0x7f, - 0x61, 0x7f, 0xa6, 0x7f, 0xd8, 0x7f, 0xf5, 0x7f, 0x00, 0x00, 0x80, 0xe0, - 0x8c, 0x10, 0x4f, 0xe2, 0xb0, 0x00, 0x91, 0xe1, 0xa4, 0xfe, 0xff, 0xea, - 0x00, 0x00, 0x3b, 0x00, 0x76, 0x00, 0xb2, 0x00, 0xed, 0x00, 0x28, 0x01, - 0x64, 0x01, 0x9f, 0x01, 0xdb, 0x01, 0x17, 0x02, 0x52, 0x02, 0x8e, 0x02, - 0xca, 0x02, 0x05, 0x03, 0x41, 0x03, 0x7d, 0x03, 0xb9, 0x03, 0xf5, 0x03, - 0x31, 0x04, 0x6e, 0x04, 0xaa, 0x04, 0xe6, 0x04, 0x22, 0x05, 0x5f, 0x05, - 0x9b, 0x05, 0xd8, 0x05, 0x14, 0x06, 0x51, 0x06, 0x8d, 0x06, 0xca, 0x06, - 0x07, 0x07, 0x43, 0x07, 0x80, 0x07, 0xbd, 0x07, 0xfa, 0x07, 0x37, 0x08, - 0x74, 0x08, 0xb1, 0x08, 0xef, 0x08, 0x2c, 0x09, 0x69, 0x09, 0xa7, 0x09, - 0xe4, 0x09, 0x21, 0x0a, 0x5f, 0x0a, 0x9c, 0x0a, 0xda, 0x0a, 0x18, 0x0b, - 0x56, 0x0b, 0x93, 0x0b, 0xd1, 0x0b, 0x0f, 0x0c, 0x4d, 0x0c, 0x8b, 0x0c, - 0xc9, 0x0c, 0x07, 0x0d, 0x45, 0x0d, 0x84, 0x0d, 0xc2, 0x0d, 0x00, 0x0e, - 0x3f, 0x0e, 0x7d, 0x0e, 0xbc, 0x0e, 0xfa, 0x0e, 0x39, 0x0f, 0x78, 0x0f, - 0xb6, 0x0f, 0xf5, 0x0f, 0x34, 0x10, 0x73, 0x10, 0xb2, 0x10, 0xf1, 0x10, - 0x30, 0x11, 0x6f, 0x11, 0xae, 0x11, 0xee, 0x11, 0x2d, 0x12, 0x6c, 0x12, - 0xac, 0x12, 0xeb, 0x12, 0x2b, 0x13, 0x6b, 0x13, 0xaa, 0x13, 0xea, 0x13, - 0x2a, 0x14, 0x6a, 0x14, 0xa9, 0x14, 0xe9, 0x14, 0x29, 0x15, 0x69, 0x15, - 0xaa, 0x15, 0xea, 0x15, 0x2a, 0x16, 0x6a, 0x16, 0xab, 0x16, 0xeb, 0x16, - 0x2c, 0x17, 0x6c, 0x17, 0xad, 0x17, 0xed, 0x17, 0x2e, 0x18, 0x6f, 0x18, - 0xb0, 0x18, 0xf0, 0x18, 0x31, 0x19, 0x72, 0x19, 0xb3, 0x19, 0xf5, 0x19, - 0x36, 0x1a, 0x77, 0x1a, 0xb8, 0x1a, 0xfa, 0x1a, 0x3b, 0x1b, 0x7d, 0x1b, - 0xbe, 0x1b, 0x00, 0x1c, 0x41, 0x1c, 0x83, 0x1c, 0xc5, 0x1c, 0x07, 0x1d, - 0x48, 0x1d, 0x8a, 0x1d, 0xcc, 0x1d, 0x0e, 0x1e, 0x51, 0x1e, 0x93, 0x1e, - 0xd5, 0x1e, 0x17, 0x1f, 0x5a, 0x1f, 0x9c, 0x1f, 0xdf, 0x1f, 0x21, 0x20, - 0x64, 0x20, 0xa6, 0x20, 0xe9, 0x20, 0x2c, 0x21, 0x6f, 0x21, 0xb2, 0x21, - 0xf5, 0x21, 0x38, 0x22, 0x7b, 0x22, 0xbe, 0x22, 0x01, 0x23, 0x44, 0x23, - 0x88, 0x23, 0xcb, 0x23, 0x0e, 0x24, 0x52, 0x24, 0x96, 0x24, 0xd9, 0x24, - 0x1d, 0x25, 0x61, 0x25, 0xa4, 0x25, 0xe8, 0x25, 0x2c, 0x26, 0x70, 0x26, - 0xb4, 0x26, 0xf8, 0x26, 0x3d, 0x27, 0x81, 0x27, 0xc5, 0x27, 0x0a, 0x28, - 0x4e, 0x28, 0x92, 0x28, 0xd7, 0x28, 0x1c, 0x29, 0x60, 0x29, 0xa5, 0x29, - 0xea, 0x29, 0x2f, 0x2a, 0x74, 0x2a, 0xb9, 0x2a, 0xfe, 0x2a, 0x43, 0x2b, - 0x88, 0x2b, 0xcd, 0x2b, 0x13, 0x2c, 0x58, 0x2c, 0x9d, 0x2c, 0xe3, 0x2c, - 0x28, 0x2d, 0x6e, 0x2d, 0xb4, 0x2d, 0xf9, 0x2d, 0x3f, 0x2e, 0x85, 0x2e, - 0xcb, 0x2e, 0x11, 0x2f, 0x57, 0x2f, 0x9d, 0x2f, 0xe3, 0x2f, 0x2a, 0x30, - 0x70, 0x30, 0xb6, 0x30, 0xfd, 0x30, 0x43, 0x31, 0x8a, 0x31, 0xd0, 0x31, - 0x17, 0x32, 0x5e, 0x32, 0xa5, 0x32, 0xec, 0x32, 0x32, 0x33, 0x79, 0x33, - 0xc1, 0x33, 0x08, 0x34, 0x4f, 0x34, 0x96, 0x34, 0xdd, 0x34, 0x25, 0x35, - 0x6c, 0x35, 0xb4, 0x35, 0xfb, 0x35, 0x43, 0x36, 0x8b, 0x36, 0xd3, 0x36, - 0x1a, 0x37, 0x62, 0x37, 0xaa, 0x37, 0xf2, 0x37, 0x3a, 0x38, 0x83, 0x38, - 0xcb, 0x38, 0x13, 0x39, 0x5c, 0x39, 0xa4, 0x39, 0xed, 0x39, 0x35, 0x3a, - 0x7e, 0x3a, 0xc6, 0x3a, 0x0f, 0x3b, 0x58, 0x3b, 0xa1, 0x3b, 0xea, 0x3b, - 0x33, 0x3c, 0x7c, 0x3c, 0xc5, 0x3c, 0x0e, 0x3d, 0x58, 0x3d, 0xa1, 0x3d, - 0xea, 0x3d, 0x34, 0x3e, 0x7d, 0x3e, 0xc7, 0x3e, 0x11, 0x3f, 0x5a, 0x3f, - 0xa4, 0x3f, 0xee, 0x3f, 0x38, 0x40, 0x82, 0x40, 0xcc, 0x40, 0x16, 0x41, - 0x61, 0x41, 0xab, 0x41, 0xf5, 0x41, 0x40, 0x42, 0x8a, 0x42, 0xd5, 0x42, - 0x1f, 0x43, 0x6a, 0x43, 0xb5, 0x43, 0x00, 0x44, 0x4b, 0x44, 0x95, 0x44, - 0xe1, 0x44, 0x2c, 0x45, 0x77, 0x45, 0xc2, 0x45, 0x0d, 0x46, 0x59, 0x46, - 0xa4, 0x46, 0xf0, 0x46, 0x3b, 0x47, 0x87, 0x47, 0xd3, 0x47, 0x1e, 0x48, - 0x6a, 0x48, 0xb6, 0x48, 0x02, 0x49, 0x4e, 0x49, 0x9a, 0x49, 0xe6, 0x49, - 0x33, 0x4a, 0x7f, 0x4a, 0xcb, 0x4a, 0x18, 0x4b, 0x64, 0x4b, 0xb1, 0x4b, - 0xfe, 0x4b, 0x4a, 0x4c, 0x97, 0x4c, 0xe4, 0x4c, 0x31, 0x4d, 0x7e, 0x4d, - 0xcb, 0x4d, 0x18, 0x4e, 0x66, 0x4e, 0xb3, 0x4e, 0x00, 0x4f, 0x4e, 0x4f, - 0x9b, 0x4f, 0xe9, 0x4f, 0x36, 0x50, 0x84, 0x50, 0xd2, 0x50, 0x20, 0x51, - 0x6e, 0x51, 0xbc, 0x51, 0x0a, 0x52, 0x58, 0x52, 0xa6, 0x52, 0xf4, 0x52, - 0x43, 0x53, 0x91, 0x53, 0xe0, 0x53, 0x2e, 0x54, 0x7d, 0x54, 0xcc, 0x54, - 0x1a, 0x55, 0x69, 0x55, 0xb8, 0x55, 0x07, 0x56, 0x56, 0x56, 0xa5, 0x56, - 0xf4, 0x56, 0x44, 0x57, 0x93, 0x57, 0xe2, 0x57, 0x32, 0x58, 0x82, 0x58, - 0xd1, 0x58, 0x21, 0x59, 0x71, 0x59, 0xc1, 0x59, 0x10, 0x5a, 0x60, 0x5a, - 0xb0, 0x5a, 0x01, 0x5b, 0x51, 0x5b, 0xa1, 0x5b, 0xf1, 0x5b, 0x42, 0x5c, - 0x92, 0x5c, 0xe3, 0x5c, 0x34, 0x5d, 0x84, 0x5d, 0xd5, 0x5d, 0x26, 0x5e, - 0x77, 0x5e, 0xc8, 0x5e, 0x19, 0x5f, 0x6a, 0x5f, 0xbb, 0x5f, 0x0d, 0x60, - 0x5e, 0x60, 0xb0, 0x60, 0x01, 0x61, 0x53, 0x61, 0xa4, 0x61, 0xf6, 0x61, - 0x48, 0x62, 0x9a, 0x62, 0xec, 0x62, 0x3e, 0x63, 0x90, 0x63, 0xe2, 0x63, - 0x34, 0x64, 0x87, 0x64, 0xd9, 0x64, 0x2c, 0x65, 0x7e, 0x65, 0xd1, 0x65, - 0x24, 0x66, 0x76, 0x66, 0xc9, 0x66, 0x1c, 0x67, 0x6f, 0x67, 0xc2, 0x67, - 0x15, 0x68, 0x69, 0x68, 0xbc, 0x68, 0x0f, 0x69, 0x63, 0x69, 0xb6, 0x69, - 0x0a, 0x6a, 0x5e, 0x6a, 0xb1, 0x6a, 0x05, 0x6b, 0x59, 0x6b, 0xad, 0x6b, - 0x01, 0x6c, 0x55, 0x6c, 0xaa, 0x6c, 0xfe, 0x6c, 0x52, 0x6d, 0xa7, 0x6d, - 0xfb, 0x6d, 0x50, 0x6e, 0xa4, 0x6e, 0xf9, 0x6e, 0x4e, 0x6f, 0xa3, 0x6f, - 0xf8, 0x6f, 0x4d, 0x70, 0xa2, 0x70, 0xf7, 0x70, 0x4d, 0x71, 0xa2, 0x71, - 0xf7, 0x71, 0x4d, 0x72, 0xa2, 0x72, 0xf8, 0x72, 0x4e, 0x73, 0xa4, 0x73, - 0xfa, 0x73, 0x50, 0x74, 0xa6, 0x74, 0xfc, 0x74, 0x52, 0x75, 0xa8, 0x75, - 0xff, 0x75, 0x55, 0x76, 0xac, 0x76, 0x02, 0x77, 0x59, 0x77, 0xb0, 0x77, - 0x07, 0x78, 0x5e, 0x78, 0xb4, 0x78, 0x0c, 0x79, 0x63, 0x79, 0xba, 0x79, - 0x11, 0x7a, 0x69, 0x7a, 0xc0, 0x7a, 0x18, 0x7b, 0x6f, 0x7b, 0xc7, 0x7b, - 0x1f, 0x7c, 0x77, 0x7c, 0xcf, 0x7c, 0x27, 0x7d, 0x7f, 0x7d, 0xd7, 0x7d, - 0x2f, 0x7e, 0x88, 0x7e, 0xe0, 0x7e, 0x38, 0x7f, 0x91, 0x7f, 0xea, 0x7f, - 0x42, 0x80, 0x9b, 0x80, 0xf4, 0x80, 0x4d, 0x81, 0xa6, 0x81, 0xff, 0x81, - 0x59, 0x82, 0xb2, 0x82, 0x0b, 0x83, 0x65, 0x83, 0xbe, 0x83, 0x18, 0x84, - 0x72, 0x84, 0xcb, 0x84, 0x25, 0x85, 0x7f, 0x85, 0xd9, 0x85, 0x33, 0x86, - 0x8e, 0x86, 0xe8, 0x86, 0x42, 0x87, 0x9d, 0x87, 0xf7, 0x87, 0x52, 0x88, - 0xac, 0x88, 0x07, 0x89, 0x62, 0x89, 0xbd, 0x89, 0x18, 0x8a, 0x73, 0x8a, - 0xce, 0x8a, 0x2a, 0x8b, 0x85, 0x8b, 0xe0, 0x8b, 0x3c, 0x8c, 0x97, 0x8c, - 0xf3, 0x8c, 0x4f, 0x8d, 0xab, 0x8d, 0x07, 0x8e, 0x63, 0x8e, 0xbf, 0x8e, - 0x1b, 0x8f, 0x77, 0x8f, 0xd4, 0x8f, 0x30, 0x90, 0x8c, 0x90, 0xe9, 0x90, - 0x46, 0x91, 0xa2, 0x91, 0xff, 0x91, 0x5c, 0x92, 0xb9, 0x92, 0x16, 0x93, - 0x73, 0x93, 0xd1, 0x93, 0x2e, 0x94, 0x8c, 0x94, 0xe9, 0x94, 0x47, 0x95, - 0xa4, 0x95, 0x02, 0x96, 0x60, 0x96, 0xbe, 0x96, 0x1c, 0x97, 0x7a, 0x97, - 0xd8, 0x97, 0x36, 0x98, 0x95, 0x98, 0xf3, 0x98, 0x52, 0x99, 0xb0, 0x99, - 0x0f, 0x9a, 0x6e, 0x9a, 0xcd, 0x9a, 0x2c, 0x9b, 0x8b, 0x9b, 0xea, 0x9b, - 0x49, 0x9c, 0xa8, 0x9c, 0x08, 0x9d, 0x67, 0x9d, 0xc7, 0x9d, 0x26, 0x9e, - 0x86, 0x9e, 0xe6, 0x9e, 0x46, 0x9f, 0xa6, 0x9f, 0x06, 0xa0, 0x66, 0xa0, - 0xc6, 0xa0, 0x27, 0xa1, 0x87, 0xa1, 0xe8, 0xa1, 0x48, 0xa2, 0xa9, 0xa2, - 0x0a, 0xa3, 0x6b, 0xa3, 0xcc, 0xa3, 0x2d, 0xa4, 0x8e, 0xa4, 0xef, 0xa4, - 0x50, 0xa5, 0xb2, 0xa5, 0x13, 0xa6, 0x75, 0xa6, 0xd6, 0xa6, 0x38, 0xa7, - 0x9a, 0xa7, 0xfc, 0xa7, 0x5e, 0xa8, 0xc0, 0xa8, 0x22, 0xa9, 0x84, 0xa9, - 0xe7, 0xa9, 0x49, 0xaa, 0xac, 0xaa, 0x0e, 0xab, 0x71, 0xab, 0xd4, 0xab, - 0x37, 0xac, 0x9a, 0xac, 0xfd, 0xac, 0x60, 0xad, 0xc3, 0xad, 0x27, 0xae, - 0x8a, 0xae, 0xed, 0xae, 0x51, 0xaf, 0xb5, 0xaf, 0x19, 0xb0, 0x7c, 0xb0, - 0xe0, 0xb0, 0x45, 0xb1, 0xa9, 0xb1, 0x0d, 0xb2, 0x71, 0xb2, 0xd6, 0xb2, - 0x3a, 0xb3, 0x9f, 0xb3, 0x03, 0xb4, 0x68, 0xb4, 0xcd, 0xb4, 0x32, 0xb5, - 0x97, 0xb5, 0xfc, 0xb5, 0x62, 0xb6, 0xc7, 0xb6, 0x2c, 0xb7, 0x92, 0xb7, - 0xf7, 0xb7, 0x5d, 0xb8, 0xc3, 0xb8, 0x29, 0xb9, 0x8f, 0xb9, 0xf5, 0xb9, - 0x5b, 0xba, 0xc1, 0xba, 0x28, 0xbb, 0x8e, 0xbb, 0xf5, 0xbb, 0x5b, 0xbc, - 0xc2, 0xbc, 0x29, 0xbd, 0x90, 0xbd, 0xf7, 0xbd, 0x5e, 0xbe, 0xc5, 0xbe, - 0x2c, 0xbf, 0x94, 0xbf, 0xfb, 0xbf, 0x63, 0xc0, 0xca, 0xc0, 0x32, 0xc1, - 0x9a, 0xc1, 0x02, 0xc2, 0x6a, 0xc2, 0xd2, 0xc2, 0x3a, 0xc3, 0xa2, 0xc3, - 0x0b, 0xc4, 0x73, 0xc4, 0xdc, 0xc4, 0x44, 0xc5, 0xad, 0xc5, 0x16, 0xc6, - 0x7f, 0xc6, 0xe8, 0xc6, 0x51, 0xc7, 0xbb, 0xc7, 0x24, 0xc8, 0x8d, 0xc8, - 0xf7, 0xc8, 0x60, 0xc9, 0xca, 0xc9, 0x34, 0xca, 0x9e, 0xca, 0x08, 0xcb, - 0x72, 0xcb, 0xdc, 0xcb, 0x47, 0xcc, 0xb1, 0xcc, 0x1b, 0xcd, 0x86, 0xcd, - 0xf1, 0xcd, 0x5b, 0xce, 0xc6, 0xce, 0x31, 0xcf, 0x9c, 0xcf, 0x08, 0xd0, - 0x73, 0xd0, 0xde, 0xd0, 0x4a, 0xd1, 0xb5, 0xd1, 0x21, 0xd2, 0x8d, 0xd2, - 0xf8, 0xd2, 0x64, 0xd3, 0xd0, 0xd3, 0x3d, 0xd4, 0xa9, 0xd4, 0x15, 0xd5, - 0x82, 0xd5, 0xee, 0xd5, 0x5b, 0xd6, 0xc7, 0xd6, 0x34, 0xd7, 0xa1, 0xd7, - 0x0e, 0xd8, 0x7b, 0xd8, 0xe9, 0xd8, 0x56, 0xd9, 0xc3, 0xd9, 0x31, 0xda, - 0x9e, 0xda, 0x0c, 0xdb, 0x7a, 0xdb, 0xe8, 0xdb, 0x56, 0xdc, 0xc4, 0xdc, - 0x32, 0xdd, 0xa0, 0xdd, 0x0f, 0xde, 0x7d, 0xde, 0xec, 0xde, 0x5b, 0xdf, - 0xc9, 0xdf, 0x38, 0xe0, 0xa7, 0xe0, 0x16, 0xe1, 0x86, 0xe1, 0xf5, 0xe1, - 0x64, 0xe2, 0xd4, 0xe2, 0x43, 0xe3, 0xb3, 0xe3, 0x23, 0xe4, 0x93, 0xe4, - 0x03, 0xe5, 0x73, 0xe5, 0xe3, 0xe5, 0x54, 0xe6, 0xc4, 0xe6, 0x35, 0xe7, - 0xa5, 0xe7, 0x16, 0xe8, 0x87, 0xe8, 0xf8, 0xe8, 0x69, 0xe9, 0xda, 0xe9, - 0x4b, 0xea, 0xbc, 0xea, 0x2e, 0xeb, 0x9f, 0xeb, 0x11, 0xec, 0x83, 0xec, - 0xf5, 0xec, 0x66, 0xed, 0xd9, 0xed, 0x4b, 0xee, 0xbd, 0xee, 0x2f, 0xef, - 0xa2, 0xef, 0x14, 0xf0, 0x87, 0xf0, 0xfa, 0xf0, 0x6d, 0xf1, 0xe0, 0xf1, - 0x53, 0xf2, 0xc6, 0xf2, 0x39, 0xf3, 0xad, 0xf3, 0x20, 0xf4, 0x94, 0xf4, - 0x07, 0xf5, 0x7b, 0xf5, 0xef, 0xf5, 0x63, 0xf6, 0xd7, 0xf6, 0x4c, 0xf7, - 0xc0, 0xf7, 0x34, 0xf8, 0xa9, 0xf8, 0x1e, 0xf9, 0x92, 0xf9, 0x07, 0xfa, - 0x7c, 0xfa, 0xf1, 0xfa, 0x66, 0xfb, 0xdc, 0xfb, 0x51, 0xfc, 0xc7, 0xfc, - 0x3c, 0xfd, 0xb2, 0xfd, 0x28, 0xfe, 0x9e, 0xfe, 0x14, 0xff, 0x8a, 0xff, - 0x98, 0x16, 0x00, 0x00, 0x00, 0x00, 0x80, 0xe0, 0x10, 0x10, 0x1f, 0xe5, - 0xb0, 0x00, 0x91, 0xe1, 0x1f, 0xfd, 0xff, 0xea, 0x00, 0x01, 0x01, 0x01, + 0xee, 0xfe, 0xff, 0x0a, 0x01, 0xe0, 0x5e, 0xe2, 0xf4, 0xff, 0xff, 0x1a, + 0x83, 0x30, 0xb0, 0xe1, 0xe8, 0xff, 0xff, 0x5a, 0xe5, 0xff, 0xff, 0xea, + 0x01, 0x00, 0x11, 0xe3, 0x01, 0x50, 0xd0, 0x04, 0x01, 0x40, 0xd0, 0x14, + 0x04, 0x44, 0x85, 0x11, 0xb1, 0x40, 0x41, 0x11, 0x01, 0x10, 0x81, 0xe2, + 0x01, 0x20, 0x52, 0xe2, 0x20, 0x00, 0xbd, 0x08, 0xe0, 0xfe, 0xff, 0x0a, + 0x83, 0x30, 0xb0, 0xe1, 0xdc, 0xff, 0xff, 0x5a, 0xd9, 0xff, 0xff, 0xea, + 0xdc, 0xfe, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, 0x23, 0x24, 0xa0, 0xe1, + 0x01, 0x30, 0xd0, 0xe4, 0x80, 0x00, 0x13, 0xe3, 0x7f, 0x30, 0x03, 0xe2, + 0x07, 0x00, 0x00, 0x0a, 0x01, 0xc0, 0xd0, 0xe4, 0x03, 0x30, 0x83, 0xe2, + 0x01, 0xc0, 0xc1, 0xe4, 0x01, 0x20, 0x52, 0xe2, 0xd1, 0xfe, 0xff, 0x0a, + 0x01, 0x30, 0x53, 0xe2, 0xfa, 0xff, 0xff, 0x1a, 0xf3, 0xff, 0xff, 0xea, + 0x01, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xd0, 0xe4, 0x01, 0x20, 0x52, 0xe2, + 0x01, 0xc0, 0xc1, 0xe4, 0xc9, 0xfe, 0xff, 0x0a, 0x01, 0x30, 0x53, 0xe2, + 0xf9, 0xff, 0xff, 0x1a, 0xeb, 0xff, 0xff, 0xea, 0x00, 0x00, 0x24, 0x03, + 0x48, 0x06, 0x6a, 0x09, 0x8c, 0x0c, 0xab, 0x0f, 0xc8, 0x12, 0xe2, 0x15, + 0xf9, 0x18, 0x0b, 0x1c, 0x1a, 0x1f, 0x23, 0x22, 0x28, 0x25, 0x26, 0x28, + 0x1f, 0x2b, 0x11, 0x2e, 0xfb, 0x30, 0xdf, 0x33, 0xba, 0x36, 0x8c, 0x39, + 0x56, 0x3c, 0x17, 0x3f, 0xce, 0x41, 0x7a, 0x44, 0x1c, 0x47, 0xb4, 0x49, + 0x3f, 0x4c, 0xbf, 0x4e, 0x33, 0x51, 0x9b, 0x53, 0xf5, 0x55, 0x42, 0x58, + 0x82, 0x5a, 0xb3, 0x5c, 0xd7, 0x5e, 0xeb, 0x60, 0xf1, 0x62, 0xe8, 0x64, + 0xcf, 0x66, 0xa6, 0x68, 0x6d, 0x6a, 0x23, 0x6c, 0xc9, 0x6d, 0x5e, 0x6f, + 0xe2, 0x70, 0x54, 0x72, 0xb5, 0x73, 0x04, 0x75, 0x41, 0x76, 0x6b, 0x77, + 0x84, 0x78, 0x89, 0x79, 0x7c, 0x7a, 0x5c, 0x7b, 0x29, 0x7c, 0xe3, 0x7c, + 0x89, 0x7d, 0x1d, 0x7e, 0x9c, 0x7e, 0x09, 0x7f, 0x61, 0x7f, 0xa6, 0x7f, + 0xd8, 0x7f, 0xf5, 0x7f, 0x00, 0x00, 0x80, 0xe0, 0x8c, 0x10, 0x4f, 0xe2, + 0xb0, 0x00, 0x91, 0xe1, 0xa2, 0xfe, 0xff, 0xea, 0x00, 0x00, 0x3b, 0x00, + 0x76, 0x00, 0xb2, 0x00, 0xed, 0x00, 0x28, 0x01, 0x64, 0x01, 0x9f, 0x01, + 0xdb, 0x01, 0x17, 0x02, 0x52, 0x02, 0x8e, 0x02, 0xca, 0x02, 0x05, 0x03, + 0x41, 0x03, 0x7d, 0x03, 0xb9, 0x03, 0xf5, 0x03, 0x31, 0x04, 0x6e, 0x04, + 0xaa, 0x04, 0xe6, 0x04, 0x22, 0x05, 0x5f, 0x05, 0x9b, 0x05, 0xd8, 0x05, + 0x14, 0x06, 0x51, 0x06, 0x8d, 0x06, 0xca, 0x06, 0x07, 0x07, 0x43, 0x07, + 0x80, 0x07, 0xbd, 0x07, 0xfa, 0x07, 0x37, 0x08, 0x74, 0x08, 0xb1, 0x08, + 0xef, 0x08, 0x2c, 0x09, 0x69, 0x09, 0xa7, 0x09, 0xe4, 0x09, 0x21, 0x0a, + 0x5f, 0x0a, 0x9c, 0x0a, 0xda, 0x0a, 0x18, 0x0b, 0x56, 0x0b, 0x93, 0x0b, + 0xd1, 0x0b, 0x0f, 0x0c, 0x4d, 0x0c, 0x8b, 0x0c, 0xc9, 0x0c, 0x07, 0x0d, + 0x45, 0x0d, 0x84, 0x0d, 0xc2, 0x0d, 0x00, 0x0e, 0x3f, 0x0e, 0x7d, 0x0e, + 0xbc, 0x0e, 0xfa, 0x0e, 0x39, 0x0f, 0x78, 0x0f, 0xb6, 0x0f, 0xf5, 0x0f, + 0x34, 0x10, 0x73, 0x10, 0xb2, 0x10, 0xf1, 0x10, 0x30, 0x11, 0x6f, 0x11, + 0xae, 0x11, 0xee, 0x11, 0x2d, 0x12, 0x6c, 0x12, 0xac, 0x12, 0xeb, 0x12, + 0x2b, 0x13, 0x6b, 0x13, 0xaa, 0x13, 0xea, 0x13, 0x2a, 0x14, 0x6a, 0x14, + 0xa9, 0x14, 0xe9, 0x14, 0x29, 0x15, 0x69, 0x15, 0xaa, 0x15, 0xea, 0x15, + 0x2a, 0x16, 0x6a, 0x16, 0xab, 0x16, 0xeb, 0x16, 0x2c, 0x17, 0x6c, 0x17, + 0xad, 0x17, 0xed, 0x17, 0x2e, 0x18, 0x6f, 0x18, 0xb0, 0x18, 0xf0, 0x18, + 0x31, 0x19, 0x72, 0x19, 0xb3, 0x19, 0xf5, 0x19, 0x36, 0x1a, 0x77, 0x1a, + 0xb8, 0x1a, 0xfa, 0x1a, 0x3b, 0x1b, 0x7d, 0x1b, 0xbe, 0x1b, 0x00, 0x1c, + 0x41, 0x1c, 0x83, 0x1c, 0xc5, 0x1c, 0x07, 0x1d, 0x48, 0x1d, 0x8a, 0x1d, + 0xcc, 0x1d, 0x0e, 0x1e, 0x51, 0x1e, 0x93, 0x1e, 0xd5, 0x1e, 0x17, 0x1f, + 0x5a, 0x1f, 0x9c, 0x1f, 0xdf, 0x1f, 0x21, 0x20, 0x64, 0x20, 0xa6, 0x20, + 0xe9, 0x20, 0x2c, 0x21, 0x6f, 0x21, 0xb2, 0x21, 0xf5, 0x21, 0x38, 0x22, + 0x7b, 0x22, 0xbe, 0x22, 0x01, 0x23, 0x44, 0x23, 0x88, 0x23, 0xcb, 0x23, + 0x0e, 0x24, 0x52, 0x24, 0x96, 0x24, 0xd9, 0x24, 0x1d, 0x25, 0x61, 0x25, + 0xa4, 0x25, 0xe8, 0x25, 0x2c, 0x26, 0x70, 0x26, 0xb4, 0x26, 0xf8, 0x26, + 0x3d, 0x27, 0x81, 0x27, 0xc5, 0x27, 0x0a, 0x28, 0x4e, 0x28, 0x92, 0x28, + 0xd7, 0x28, 0x1c, 0x29, 0x60, 0x29, 0xa5, 0x29, 0xea, 0x29, 0x2f, 0x2a, + 0x74, 0x2a, 0xb9, 0x2a, 0xfe, 0x2a, 0x43, 0x2b, 0x88, 0x2b, 0xcd, 0x2b, + 0x13, 0x2c, 0x58, 0x2c, 0x9d, 0x2c, 0xe3, 0x2c, 0x28, 0x2d, 0x6e, 0x2d, + 0xb4, 0x2d, 0xf9, 0x2d, 0x3f, 0x2e, 0x85, 0x2e, 0xcb, 0x2e, 0x11, 0x2f, + 0x57, 0x2f, 0x9d, 0x2f, 0xe3, 0x2f, 0x2a, 0x30, 0x70, 0x30, 0xb6, 0x30, + 0xfd, 0x30, 0x43, 0x31, 0x8a, 0x31, 0xd0, 0x31, 0x17, 0x32, 0x5e, 0x32, + 0xa5, 0x32, 0xec, 0x32, 0x32, 0x33, 0x79, 0x33, 0xc1, 0x33, 0x08, 0x34, + 0x4f, 0x34, 0x96, 0x34, 0xdd, 0x34, 0x25, 0x35, 0x6c, 0x35, 0xb4, 0x35, + 0xfb, 0x35, 0x43, 0x36, 0x8b, 0x36, 0xd3, 0x36, 0x1a, 0x37, 0x62, 0x37, + 0xaa, 0x37, 0xf2, 0x37, 0x3a, 0x38, 0x83, 0x38, 0xcb, 0x38, 0x13, 0x39, + 0x5c, 0x39, 0xa4, 0x39, 0xed, 0x39, 0x35, 0x3a, 0x7e, 0x3a, 0xc6, 0x3a, + 0x0f, 0x3b, 0x58, 0x3b, 0xa1, 0x3b, 0xea, 0x3b, 0x33, 0x3c, 0x7c, 0x3c, + 0xc5, 0x3c, 0x0e, 0x3d, 0x58, 0x3d, 0xa1, 0x3d, 0xea, 0x3d, 0x34, 0x3e, + 0x7d, 0x3e, 0xc7, 0x3e, 0x11, 0x3f, 0x5a, 0x3f, 0xa4, 0x3f, 0xee, 0x3f, + 0x38, 0x40, 0x82, 0x40, 0xcc, 0x40, 0x16, 0x41, 0x61, 0x41, 0xab, 0x41, + 0xf5, 0x41, 0x40, 0x42, 0x8a, 0x42, 0xd5, 0x42, 0x1f, 0x43, 0x6a, 0x43, + 0xb5, 0x43, 0x00, 0x44, 0x4b, 0x44, 0x95, 0x44, 0xe1, 0x44, 0x2c, 0x45, + 0x77, 0x45, 0xc2, 0x45, 0x0d, 0x46, 0x59, 0x46, 0xa4, 0x46, 0xf0, 0x46, + 0x3b, 0x47, 0x87, 0x47, 0xd3, 0x47, 0x1e, 0x48, 0x6a, 0x48, 0xb6, 0x48, + 0x02, 0x49, 0x4e, 0x49, 0x9a, 0x49, 0xe6, 0x49, 0x33, 0x4a, 0x7f, 0x4a, + 0xcb, 0x4a, 0x18, 0x4b, 0x64, 0x4b, 0xb1, 0x4b, 0xfe, 0x4b, 0x4a, 0x4c, + 0x97, 0x4c, 0xe4, 0x4c, 0x31, 0x4d, 0x7e, 0x4d, 0xcb, 0x4d, 0x18, 0x4e, + 0x66, 0x4e, 0xb3, 0x4e, 0x00, 0x4f, 0x4e, 0x4f, 0x9b, 0x4f, 0xe9, 0x4f, + 0x36, 0x50, 0x84, 0x50, 0xd2, 0x50, 0x20, 0x51, 0x6e, 0x51, 0xbc, 0x51, + 0x0a, 0x52, 0x58, 0x52, 0xa6, 0x52, 0xf4, 0x52, 0x43, 0x53, 0x91, 0x53, + 0xe0, 0x53, 0x2e, 0x54, 0x7d, 0x54, 0xcc, 0x54, 0x1a, 0x55, 0x69, 0x55, + 0xb8, 0x55, 0x07, 0x56, 0x56, 0x56, 0xa5, 0x56, 0xf4, 0x56, 0x44, 0x57, + 0x93, 0x57, 0xe2, 0x57, 0x32, 0x58, 0x82, 0x58, 0xd1, 0x58, 0x21, 0x59, + 0x71, 0x59, 0xc1, 0x59, 0x10, 0x5a, 0x60, 0x5a, 0xb0, 0x5a, 0x01, 0x5b, + 0x51, 0x5b, 0xa1, 0x5b, 0xf1, 0x5b, 0x42, 0x5c, 0x92, 0x5c, 0xe3, 0x5c, + 0x34, 0x5d, 0x84, 0x5d, 0xd5, 0x5d, 0x26, 0x5e, 0x77, 0x5e, 0xc8, 0x5e, + 0x19, 0x5f, 0x6a, 0x5f, 0xbb, 0x5f, 0x0d, 0x60, 0x5e, 0x60, 0xb0, 0x60, + 0x01, 0x61, 0x53, 0x61, 0xa4, 0x61, 0xf6, 0x61, 0x48, 0x62, 0x9a, 0x62, + 0xec, 0x62, 0x3e, 0x63, 0x90, 0x63, 0xe2, 0x63, 0x34, 0x64, 0x87, 0x64, + 0xd9, 0x64, 0x2c, 0x65, 0x7e, 0x65, 0xd1, 0x65, 0x24, 0x66, 0x76, 0x66, + 0xc9, 0x66, 0x1c, 0x67, 0x6f, 0x67, 0xc2, 0x67, 0x15, 0x68, 0x69, 0x68, + 0xbc, 0x68, 0x0f, 0x69, 0x63, 0x69, 0xb6, 0x69, 0x0a, 0x6a, 0x5e, 0x6a, + 0xb1, 0x6a, 0x05, 0x6b, 0x59, 0x6b, 0xad, 0x6b, 0x01, 0x6c, 0x55, 0x6c, + 0xaa, 0x6c, 0xfe, 0x6c, 0x52, 0x6d, 0xa7, 0x6d, 0xfb, 0x6d, 0x50, 0x6e, + 0xa4, 0x6e, 0xf9, 0x6e, 0x4e, 0x6f, 0xa3, 0x6f, 0xf8, 0x6f, 0x4d, 0x70, + 0xa2, 0x70, 0xf7, 0x70, 0x4d, 0x71, 0xa2, 0x71, 0xf7, 0x71, 0x4d, 0x72, + 0xa2, 0x72, 0xf8, 0x72, 0x4e, 0x73, 0xa4, 0x73, 0xfa, 0x73, 0x50, 0x74, + 0xa6, 0x74, 0xfc, 0x74, 0x52, 0x75, 0xa8, 0x75, 0xff, 0x75, 0x55, 0x76, + 0xac, 0x76, 0x02, 0x77, 0x59, 0x77, 0xb0, 0x77, 0x07, 0x78, 0x5e, 0x78, + 0xb4, 0x78, 0x0c, 0x79, 0x63, 0x79, 0xba, 0x79, 0x11, 0x7a, 0x69, 0x7a, + 0xc0, 0x7a, 0x18, 0x7b, 0x6f, 0x7b, 0xc7, 0x7b, 0x1f, 0x7c, 0x77, 0x7c, + 0xcf, 0x7c, 0x27, 0x7d, 0x7f, 0x7d, 0xd7, 0x7d, 0x2f, 0x7e, 0x88, 0x7e, + 0xe0, 0x7e, 0x38, 0x7f, 0x91, 0x7f, 0xea, 0x7f, 0x42, 0x80, 0x9b, 0x80, + 0xf4, 0x80, 0x4d, 0x81, 0xa6, 0x81, 0xff, 0x81, 0x59, 0x82, 0xb2, 0x82, + 0x0b, 0x83, 0x65, 0x83, 0xbe, 0x83, 0x18, 0x84, 0x72, 0x84, 0xcb, 0x84, + 0x25, 0x85, 0x7f, 0x85, 0xd9, 0x85, 0x33, 0x86, 0x8e, 0x86, 0xe8, 0x86, + 0x42, 0x87, 0x9d, 0x87, 0xf7, 0x87, 0x52, 0x88, 0xac, 0x88, 0x07, 0x89, + 0x62, 0x89, 0xbd, 0x89, 0x18, 0x8a, 0x73, 0x8a, 0xce, 0x8a, 0x2a, 0x8b, + 0x85, 0x8b, 0xe0, 0x8b, 0x3c, 0x8c, 0x97, 0x8c, 0xf3, 0x8c, 0x4f, 0x8d, + 0xab, 0x8d, 0x07, 0x8e, 0x63, 0x8e, 0xbf, 0x8e, 0x1b, 0x8f, 0x77, 0x8f, + 0xd4, 0x8f, 0x30, 0x90, 0x8c, 0x90, 0xe9, 0x90, 0x46, 0x91, 0xa2, 0x91, + 0xff, 0x91, 0x5c, 0x92, 0xb9, 0x92, 0x16, 0x93, 0x73, 0x93, 0xd1, 0x93, + 0x2e, 0x94, 0x8c, 0x94, 0xe9, 0x94, 0x47, 0x95, 0xa4, 0x95, 0x02, 0x96, + 0x60, 0x96, 0xbe, 0x96, 0x1c, 0x97, 0x7a, 0x97, 0xd8, 0x97, 0x36, 0x98, + 0x95, 0x98, 0xf3, 0x98, 0x52, 0x99, 0xb0, 0x99, 0x0f, 0x9a, 0x6e, 0x9a, + 0xcd, 0x9a, 0x2c, 0x9b, 0x8b, 0x9b, 0xea, 0x9b, 0x49, 0x9c, 0xa8, 0x9c, + 0x08, 0x9d, 0x67, 0x9d, 0xc7, 0x9d, 0x26, 0x9e, 0x86, 0x9e, 0xe6, 0x9e, + 0x46, 0x9f, 0xa6, 0x9f, 0x06, 0xa0, 0x66, 0xa0, 0xc6, 0xa0, 0x27, 0xa1, + 0x87, 0xa1, 0xe8, 0xa1, 0x48, 0xa2, 0xa9, 0xa2, 0x0a, 0xa3, 0x6b, 0xa3, + 0xcc, 0xa3, 0x2d, 0xa4, 0x8e, 0xa4, 0xef, 0xa4, 0x50, 0xa5, 0xb2, 0xa5, + 0x13, 0xa6, 0x75, 0xa6, 0xd6, 0xa6, 0x38, 0xa7, 0x9a, 0xa7, 0xfc, 0xa7, + 0x5e, 0xa8, 0xc0, 0xa8, 0x22, 0xa9, 0x84, 0xa9, 0xe7, 0xa9, 0x49, 0xaa, + 0xac, 0xaa, 0x0e, 0xab, 0x71, 0xab, 0xd4, 0xab, 0x37, 0xac, 0x9a, 0xac, + 0xfd, 0xac, 0x60, 0xad, 0xc3, 0xad, 0x27, 0xae, 0x8a, 0xae, 0xed, 0xae, + 0x51, 0xaf, 0xb5, 0xaf, 0x19, 0xb0, 0x7c, 0xb0, 0xe0, 0xb0, 0x45, 0xb1, + 0xa9, 0xb1, 0x0d, 0xb2, 0x71, 0xb2, 0xd6, 0xb2, 0x3a, 0xb3, 0x9f, 0xb3, + 0x03, 0xb4, 0x68, 0xb4, 0xcd, 0xb4, 0x32, 0xb5, 0x97, 0xb5, 0xfc, 0xb5, + 0x62, 0xb6, 0xc7, 0xb6, 0x2c, 0xb7, 0x92, 0xb7, 0xf7, 0xb7, 0x5d, 0xb8, + 0xc3, 0xb8, 0x29, 0xb9, 0x8f, 0xb9, 0xf5, 0xb9, 0x5b, 0xba, 0xc1, 0xba, + 0x28, 0xbb, 0x8e, 0xbb, 0xf5, 0xbb, 0x5b, 0xbc, 0xc2, 0xbc, 0x29, 0xbd, + 0x90, 0xbd, 0xf7, 0xbd, 0x5e, 0xbe, 0xc5, 0xbe, 0x2c, 0xbf, 0x94, 0xbf, + 0xfb, 0xbf, 0x63, 0xc0, 0xca, 0xc0, 0x32, 0xc1, 0x9a, 0xc1, 0x02, 0xc2, + 0x6a, 0xc2, 0xd2, 0xc2, 0x3a, 0xc3, 0xa2, 0xc3, 0x0b, 0xc4, 0x73, 0xc4, + 0xdc, 0xc4, 0x44, 0xc5, 0xad, 0xc5, 0x16, 0xc6, 0x7f, 0xc6, 0xe8, 0xc6, + 0x51, 0xc7, 0xbb, 0xc7, 0x24, 0xc8, 0x8d, 0xc8, 0xf7, 0xc8, 0x60, 0xc9, + 0xca, 0xc9, 0x34, 0xca, 0x9e, 0xca, 0x08, 0xcb, 0x72, 0xcb, 0xdc, 0xcb, + 0x47, 0xcc, 0xb1, 0xcc, 0x1b, 0xcd, 0x86, 0xcd, 0xf1, 0xcd, 0x5b, 0xce, + 0xc6, 0xce, 0x31, 0xcf, 0x9c, 0xcf, 0x08, 0xd0, 0x73, 0xd0, 0xde, 0xd0, + 0x4a, 0xd1, 0xb5, 0xd1, 0x21, 0xd2, 0x8d, 0xd2, 0xf8, 0xd2, 0x64, 0xd3, + 0xd0, 0xd3, 0x3d, 0xd4, 0xa9, 0xd4, 0x15, 0xd5, 0x82, 0xd5, 0xee, 0xd5, + 0x5b, 0xd6, 0xc7, 0xd6, 0x34, 0xd7, 0xa1, 0xd7, 0x0e, 0xd8, 0x7b, 0xd8, + 0xe9, 0xd8, 0x56, 0xd9, 0xc3, 0xd9, 0x31, 0xda, 0x9e, 0xda, 0x0c, 0xdb, + 0x7a, 0xdb, 0xe8, 0xdb, 0x56, 0xdc, 0xc4, 0xdc, 0x32, 0xdd, 0xa0, 0xdd, + 0x0f, 0xde, 0x7d, 0xde, 0xec, 0xde, 0x5b, 0xdf, 0xc9, 0xdf, 0x38, 0xe0, + 0xa7, 0xe0, 0x16, 0xe1, 0x86, 0xe1, 0xf5, 0xe1, 0x64, 0xe2, 0xd4, 0xe2, + 0x43, 0xe3, 0xb3, 0xe3, 0x23, 0xe4, 0x93, 0xe4, 0x03, 0xe5, 0x73, 0xe5, + 0xe3, 0xe5, 0x54, 0xe6, 0xc4, 0xe6, 0x35, 0xe7, 0xa5, 0xe7, 0x16, 0xe8, + 0x87, 0xe8, 0xf8, 0xe8, 0x69, 0xe9, 0xda, 0xe9, 0x4b, 0xea, 0xbc, 0xea, + 0x2e, 0xeb, 0x9f, 0xeb, 0x11, 0xec, 0x83, 0xec, 0xf5, 0xec, 0x66, 0xed, + 0xd9, 0xed, 0x4b, 0xee, 0xbd, 0xee, 0x2f, 0xef, 0xa2, 0xef, 0x14, 0xf0, + 0x87, 0xf0, 0xfa, 0xf0, 0x6d, 0xf1, 0xe0, 0xf1, 0x53, 0xf2, 0xc6, 0xf2, + 0x39, 0xf3, 0xad, 0xf3, 0x20, 0xf4, 0x94, 0xf4, 0x07, 0xf5, 0x7b, 0xf5, + 0xef, 0xf5, 0x63, 0xf6, 0xd7, 0xf6, 0x4c, 0xf7, 0xc0, 0xf7, 0x34, 0xf8, + 0xa9, 0xf8, 0x1e, 0xf9, 0x92, 0xf9, 0x07, 0xfa, 0x7c, 0xfa, 0xf1, 0xfa, + 0x66, 0xfb, 0xdc, 0xfb, 0x51, 0xfc, 0xc7, 0xfc, 0x3c, 0xfd, 0xb2, 0xfd, + 0x28, 0xfe, 0x9e, 0xfe, 0x14, 0xff, 0x8a, 0xff, 0xa0, 0x16, 0x00, 0x00, + 0x00, 0x00, 0x80, 0xe0, 0x10, 0x10, 0x1f, 0xe5, 0xb0, 0x00, 0x91, 0xe1, + 0x1d, 0xfd, 0xff, 0xea, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, @@ -648,905 +649,66 @@ unsigned char bios_arm7_bin[] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, + 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, - 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, - 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, - 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, - 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, - 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, - 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, - 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, - 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x10, 0x10, 0x10, 0x10, 0x10, - 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, - 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, - 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x19, - 0x19, 0x19, 0x19, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1c, 0x1c, 0x1c, - 0x1d, 0x1d, 0x1d, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x20, 0x20, 0x20, - 0x21, 0x21, 0x22, 0x22, 0x22, 0x23, 0x23, 0x24, 0x24, 0x24, 0x25, 0x25, - 0x26, 0x26, 0x27, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x2a, 0x2a, 0x2b, - 0x2b, 0x2c, 0x2c, 0x2d, 0x2d, 0x2e, 0x2e, 0x2f, 0x2f, 0x30, 0x31, 0x31, - 0x32, 0x32, 0x33, 0x33, 0x34, 0x35, 0x35, 0x36, 0x36, 0x37, 0x38, 0x38, - 0x39, 0x3a, 0x3a, 0x3b, 0x3c, 0x3c, 0x3d, 0x3e, 0x3f, 0x3f, 0x40, 0x41, - 0x42, 0x42, 0x43, 0x44, 0x45, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4a, - 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x52, 0x53, 0x54, 0x55, - 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, - 0x63, 0x64, 0x65, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6d, 0x6e, 0x6f, 0x71, - 0x72, 0x73, 0x75, 0x76, 0x77, 0x79, 0x7a, 0x7b, 0x7d, 0x7e, 0x7f, 0x20, - 0x21, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23, 0x23, 0x24, 0x24, 0x25, 0x25, - 0x26, 0x26, 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x2a, 0x2a, 0x2b, - 0x2b, 0x2c, 0x2c, 0x2d, 0x2d, 0x2e, 0x2e, 0x2f, 0x2f, 0x30, 0x30, 0x31, - 0x31, 0x32, 0x33, 0x33, 0x34, 0x34, 0x35, 0x36, 0x36, 0x37, 0x37, 0x38, - 0x39, 0x39, 0x3a, 0x3b, 0x3b, 0x3c, 0x3d, 0x3e, 0x3e, 0x3f, 0x40, 0x40, - 0x41, 0x42, 0x43, 0x43, 0x44, 0x45, 0x46, 0x47, 0x47, 0x48, 0x49, 0x4a, - 0x4b, 0x4c, 0x4d, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, - 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x62, - 0x63, 0x64, 0x65, 0x66, 0x67, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6f, 0x70, - 0x71, 0x73, 0x74, 0x75, 0x77, 0x78, 0x79, 0x7b, 0x7c, 0x7e, 0x7e, 0x40, - 0x41, 0x42, 0x43, 0x43, 0x44, 0x45, 0x46, 0x47, 0x47, 0x48, 0x49, 0x4a, - 0x4b, 0x4c, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, - 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, - 0x62, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6b, 0x6c, 0x6d, 0x6e, 0x70, - 0x71, 0x72, 0x74, 0x75, 0x76, 0x78, 0x79, 0x7b, 0x7c, 0x7d, 0x7e, 0x40, - 0x41, 0x42, 0x42, 0x43, 0x44, 0x45, 0x46, 0x46, 0x47, 0x48, 0x49, 0x4a, - 0x4b, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, - 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, - 0x62, 0x63, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6c, 0x6d, 0x6e, 0x6f, - 0x71, 0x72, 0x73, 0x75, 0x76, 0x77, 0x79, 0x7a, 0x7c, 0x7d, 0x7e, 0x7f, - 0x00, 0x00, 0x00, 0x00, 0x2e, 0x00, 0xa0, 0xe3, 0x3c, 0x10, 0xa0, 0xe3, - 0xff, 0x20, 0xa0, 0xe3, 0x0a, 0x0c, 0x80, 0xe3, 0x0b, 0x1b, 0x81, 0xe3, - 0x05, 0x2c, 0x82, 0xe3, 0x62, 0xfc, 0xff, 0xea, 0x01, 0x13, 0xa0, 0xe3, - 0x01, 0x03, 0xc1, 0xe5, 0x5f, 0xfc, 0xff, 0xea, 0x0f, 0x50, 0x2d, 0xe9, - 0x01, 0x03, 0xa0, 0xe3, 0x0f, 0xe0, 0xa0, 0xe1, 0x04, 0xf0, 0x10, 0xe5, - 0x0f, 0x50, 0xbd, 0xe8, 0x04, 0xf0, 0x5e, 0xe2, 0x32, 0x1e, 0x4f, 0xe2, - 0x00, 0x00, 0xd1, 0xe7, 0x56, 0xfc, 0xff, 0xea, 0xdc, 0xff, 0x80, 0x03, - 0x01, 0x03, 0xa0, 0xe3, 0x80, 0x10, 0xa0, 0xe3, 0x00, 0x20, 0xa0, 0xe3, - 0x04, 0x20, 0x20, 0xe5, 0x01, 0x10, 0x51, 0xe2, 0xfc, 0xff, 0xff, 0x1a, - 0x24, 0x10, 0x1f, 0xe5, 0xd3, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, - 0x01, 0xd0, 0xa0, 0xe1, 0x02, 0xe0, 0xa0, 0xe1, 0x02, 0xf0, 0x6f, 0xe1, - 0xd2, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, 0x2c, 0xd0, 0x41, 0xe2, - 0x02, 0xe0, 0xa0, 0xe1, 0x02, 0xf0, 0x6f, 0xe1, 0x5f, 0x30, 0xa0, 0xe3, - 0x03, 0xf0, 0x2f, 0xe1, 0xdc, 0xd0, 0x41, 0xe2, 0xff, 0x1f, 0x90, 0xe8, - 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 -}; - -unsigned char bios_arm9_bin[] = { - 0x3e, 0x00, 0x00, 0xea, 0x3e, 0x00, 0x00, 0xea, 0x3e, 0x00, 0x00, 0xea, - 0x3c, 0x00, 0x00, 0xea, 0x3b, 0x00, 0x00, 0xea, 0x3a, 0x00, 0x00, 0xea, - 0xad, 0x01, 0x00, 0xea, 0x38, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea, - 0x10, 0x50, 0x2d, 0xe9, 0x00, 0x40, 0x4f, 0xe1, 0x10, 0x00, 0x2d, 0xe9, - 0x80, 0x40, 0x04, 0xe2, 0x1f, 0x40, 0x84, 0xe3, 0x02, 0xc0, 0x5e, 0xe5, - 0x04, 0xf0, 0x29, 0xe1, 0x00, 0x40, 0x2d, 0xe9, 0x20, 0x00, 0x5c, 0xe3, - 0x01, 0xc0, 0xa0, 0xa3, 0x0c, 0xf1, 0x9f, 0xe7, 0x00, 0x00, 0xa0, 0xe1, - 0xf8, 0x06, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, - 0xe0, 0x01, 0xff, 0xff, 0x20, 0x02, 0xff, 0xff, 0x18, 0x02, 0xff, 0xff, - 0xd4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, - 0x4c, 0x02, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xa8, 0x02, 0xff, 0xff, - 0x28, 0x03, 0xff, 0xff, 0x68, 0x03, 0xff, 0xff, 0xb4, 0x03, 0xff, 0xff, - 0x4c, 0x04, 0xff, 0xff, 0x54, 0x04, 0xff, 0xff, 0xe8, 0x04, 0xff, 0xff, - 0x64, 0x05, 0xff, 0xff, 0x10, 0x06, 0xff, 0xff, 0x14, 0x06, 0xff, 0xff, - 0x14, 0x06, 0xff, 0xff, 0x6c, 0x06, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, - 0x98, 0x06, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, - 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, - 0xc8, 0x06, 0xff, 0xff, 0x00, 0x40, 0xbd, 0xe8, 0xd3, 0x40, 0xa0, 0xe3, - 0x04, 0xf0, 0x29, 0xe1, 0x10, 0x00, 0xbd, 0xe8, 0x04, 0xf0, 0x69, 0xe1, - 0x10, 0x50, 0xbd, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xa0, 0xe3, 0x90, 0x0f, 0x07, 0xee, 0xf4, 0xff, 0xff, 0xea, - 0x01, 0x00, 0x50, 0xe2, 0xfd, 0xff, 0xff, 0xca, 0xf1, 0xff, 0xff, 0xea, - 0x11, 0x2f, 0x19, 0xee, 0xff, 0x20, 0xc2, 0xe3, 0x01, 0x29, 0x82, 0xe2, - 0x08, 0x00, 0x12, 0xe5, 0x08, 0x32, 0x83, 0xe5, 0x00, 0x00, 0x11, 0xe1, - 0x01, 0x00, 0xc0, 0xe1, 0x08, 0x00, 0x02, 0xe5, 0x01, 0xc0, 0xa0, 0xe3, - 0x08, 0xc2, 0x83, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, 0x01, 0x00, 0xa0, 0xe3, - 0x01, 0x10, 0xa0, 0xe3, 0x01, 0x33, 0xa0, 0xe3, 0x00, 0x00, 0x50, 0xe3, - 0xef, 0xff, 0xff, 0x1b, 0x00, 0x00, 0xa0, 0xe3, 0x90, 0x0f, 0x07, 0xee, - 0xec, 0xff, 0xff, 0xeb, 0xfb, 0xff, 0xff, 0x0a, 0xdc, 0xff, 0xff, 0xea, - 0xe9, 0xff, 0xff, 0xeb, 0xda, 0xff, 0xff, 0x1a, 0xf7, 0xff, 0xff, 0xea, - 0x02, 0xc1, 0x10, 0xe2, 0x00, 0x00, 0x60, 0x42, 0x02, 0x31, 0x11, 0xe2, - 0x00, 0x10, 0x61, 0x42, 0x03, 0xc0, 0x2c, 0xe0, 0x00, 0x20, 0xa0, 0xe3, - 0x01, 0x30, 0xa0, 0xe3, 0x00, 0x00, 0x51, 0xe1, 0x81, 0x10, 0xa0, 0x91, - 0x83, 0x30, 0xa0, 0x91, 0xfb, 0xff, 0xff, 0x9a, 0x01, 0x00, 0x50, 0xe1, - 0x01, 0x00, 0x40, 0x20, 0x03, 0x20, 0x82, 0x21, 0xa3, 0x30, 0xb0, 0xe1, - 0xa1, 0x10, 0xa0, 0x31, 0xf9, 0xff, 0xff, 0x3a, 0x00, 0x10, 0xa0, 0xe1, - 0x02, 0x30, 0xa0, 0xe1, 0x02, 0x00, 0xa0, 0xe1, 0x02, 0x01, 0x1c, 0xe3, - 0x00, 0x00, 0x60, 0x42, 0xc2, 0xff, 0xff, 0xea, 0xff, 0xc4, 0xc2, 0xe3, - 0x0e, 0xc6, 0xcc, 0xe3, 0x01, 0x03, 0x12, 0xe3, 0x0d, 0x00, 0x00, 0x1a, - 0x01, 0x04, 0x12, 0xe3, 0x01, 0x00, 0xc0, 0xe3, 0x01, 0x10, 0xc1, 0xe3, - 0x04, 0x00, 0x00, 0x1a, 0xb2, 0x30, 0xd0, 0xe0, 0x01, 0xc0, 0x5c, 0xe2, - 0xb2, 0x30, 0xc1, 0xe0, 0xfb, 0xff, 0xff, 0x1a, 0xb5, 0xff, 0xff, 0xea, - 0xb0, 0x30, 0xd0, 0xe1, 0xb2, 0x30, 0xc1, 0xe0, 0x01, 0xc0, 0x5c, 0xe2, - 0xfc, 0xff, 0xff, 0x1a, 0xb0, 0xff, 0xff, 0xea, 0x01, 0x04, 0x12, 0xe3, - 0x03, 0x00, 0xc0, 0xe3, 0x03, 0x10, 0xc1, 0xe3, 0x04, 0x00, 0x00, 0x1a, - 0x04, 0x30, 0x90, 0xe4, 0x01, 0xc0, 0x5c, 0xe2, 0x04, 0x30, 0x81, 0xe4, - 0xfb, 0xff, 0xff, 0x1a, 0xa7, 0xff, 0xff, 0xea, 0x00, 0x30, 0x90, 0xe5, - 0x04, 0x30, 0x81, 0xe4, 0x01, 0xc0, 0x5c, 0xe2, 0xfc, 0xff, 0xff, 0x1a, - 0xa2, 0xff, 0xff, 0xea, 0xff, 0xc4, 0xc2, 0xe3, 0x0e, 0xc6, 0xcc, 0xe3, - 0x01, 0x04, 0x12, 0xe3, 0x03, 0x00, 0xc0, 0xe3, 0x03, 0x10, 0xc1, 0xe3, - 0x04, 0x00, 0x00, 0x1a, 0x04, 0x30, 0x90, 0xe4, 0x01, 0xc0, 0x5c, 0xe2, - 0x04, 0x30, 0x81, 0xe4, 0xfb, 0xff, 0xff, 0x1a, 0x97, 0xff, 0xff, 0xea, - 0x00, 0x30, 0x90, 0xe5, 0x04, 0x30, 0x81, 0xe4, 0x01, 0xc0, 0x5c, 0xe2, - 0xfc, 0xff, 0xff, 0x1a, 0x92, 0xff, 0xff, 0xea, 0x01, 0x11, 0xa0, 0xe3, - 0x00, 0x20, 0xa0, 0xe3, 0x02, 0x30, 0x81, 0xe1, 0x03, 0x00, 0x50, 0xe1, - 0x03, 0x00, 0x40, 0xa0, 0xa2, 0x20, 0xa0, 0xe1, 0x01, 0x20, 0x82, 0xa1, - 0x21, 0x11, 0xb0, 0xe1, 0xf8, 0xff, 0xff, 0x1a, 0x02, 0x00, 0xa0, 0xe1, - 0x87, 0xff, 0xff, 0xea, 0x00, 0x00, 0x01, 0xcc, 0x01, 0xd8, 0x00, 0x14, - 0x01, 0xf0, 0x00, 0x3c, 0x00, 0x28, 0x01, 0xe4, 0x01, 0xa0, 0x00, 0x6c, - 0x00, 0x78, 0x01, 0xb4, 0x00, 0x50, 0x01, 0x9c, 0x01, 0x88, 0x00, 0x44, - 0x20, 0x00, 0x2d, 0xe9, 0x1e, 0x40, 0xa0, 0xe3, 0x30, 0xe0, 0x4f, 0xe2, - 0xa2, 0x20, 0xb0, 0xe1, 0x1e, 0x00, 0x00, 0x0a, 0xb2, 0x30, 0xd1, 0xe0, - 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, - 0x0c, 0x00, 0x20, 0xe0, 0x83, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, - 0x0c, 0x00, 0x20, 0xe0, 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, - 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x51, 0x04, 0xe0, - 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0x80, 0x50, 0x04, 0xe0, - 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, - 0xa3, 0x53, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, - 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, - 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x55, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, - 0x0c, 0x00, 0x20, 0xe0, 0x01, 0x20, 0x52, 0xe2, 0xe0, 0xff, 0xff, 0x1a, - 0x20, 0x00, 0xbd, 0xe8, 0x59, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe3, - 0x57, 0xff, 0xff, 0xea, 0xe0, 0x03, 0x2d, 0xe9, 0xb0, 0x30, 0xd2, 0xe1, - 0x02, 0xc0, 0xd2, 0xe5, 0x03, 0xe0, 0xd2, 0xe5, 0x04, 0x40, 0x92, 0xe5, - 0xa4, 0x2f, 0xa0, 0xe1, 0x02, 0x41, 0xc4, 0xe3, 0x01, 0x50, 0xa0, 0xe3, - 0x15, 0x5c, 0xa0, 0xe1, 0x01, 0x50, 0x45, 0xe2, 0x01, 0x60, 0xa0, 0xe3, - 0x00, 0x70, 0xa0, 0xe3, 0x00, 0x80, 0xa0, 0xe3, 0x01, 0x00, 0x5c, 0xe3, - 0x83, 0x31, 0xa0, 0x01, 0x02, 0x00, 0x5c, 0xe3, 0x03, 0x31, 0xa0, 0x01, - 0x04, 0x00, 0x5c, 0xe3, 0x83, 0x30, 0xa0, 0x01, 0x01, 0x00, 0x56, 0xe3, - 0x01, 0x60, 0xd0, 0x04, 0x01, 0x6c, 0x86, 0x03, 0x05, 0x90, 0x06, 0xe0, - 0x36, 0x6c, 0xa0, 0xe1, 0x00, 0x00, 0x59, 0xe3, 0x01, 0x00, 0x12, 0x03, - 0x04, 0x90, 0x89, 0x10, 0x19, 0x78, 0x87, 0xe1, 0x0e, 0x80, 0x88, 0xe0, - 0x20, 0x00, 0x58, 0xe3, 0x04, 0x70, 0x81, 0x04, 0x00, 0x70, 0xa0, 0x03, - 0x00, 0x80, 0xa0, 0x03, 0x01, 0x30, 0x53, 0xe2, 0xef, 0xff, 0xff, 0x1a, - 0xe0, 0x03, 0xbd, 0xe8, 0x32, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, - 0x23, 0x24, 0xb0, 0xe1, 0x2f, 0xff, 0xff, 0x0a, 0x01, 0x30, 0xd0, 0xe4, - 0x02, 0x35, 0x83, 0xe3, 0x80, 0x00, 0x13, 0xe3, 0x10, 0x00, 0x00, 0x0a, - 0x01, 0x40, 0xd0, 0xe4, 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, - 0x24, 0xe6, 0xa0, 0xe1, 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, - 0x0c, 0xc0, 0x41, 0xe0, 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x40, 0xdc, 0xe4, - 0x01, 0x20, 0x52, 0xe2, 0x01, 0x40, 0xc1, 0xe4, 0x1f, 0xff, 0xff, 0x0a, - 0x01, 0xe0, 0x5e, 0xe2, 0xf9, 0xff, 0xff, 0x1a, 0x83, 0x30, 0xb0, 0xe1, - 0xed, 0xff, 0xff, 0x5a, 0xea, 0xff, 0xff, 0xea, 0x01, 0x40, 0xd0, 0xe4, - 0x01, 0x20, 0x52, 0xe2, 0x01, 0x40, 0xc1, 0xe4, 0x16, 0xff, 0xff, 0x0a, - 0x83, 0x30, 0xb0, 0xe1, 0xe6, 0xff, 0xff, 0x5a, 0xe3, 0xff, 0xff, 0xea, - 0x04, 0x30, 0x90, 0xe4, 0x23, 0x24, 0xb0, 0xe1, 0x10, 0xff, 0xff, 0x0a, - 0x20, 0x00, 0x2d, 0xe9, 0x00, 0x50, 0xa0, 0xe3, 0x01, 0x30, 0xd0, 0xe4, - 0x02, 0x35, 0x83, 0xe3, 0x80, 0x00, 0x13, 0xe3, 0x15, 0x00, 0x00, 0x0a, - 0x01, 0x40, 0xd0, 0xe4, 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, - 0x24, 0xe6, 0xa0, 0xe1, 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, - 0x0c, 0xc0, 0x41, 0xe0, 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x00, 0x11, 0xe3, - 0x01, 0x50, 0xdc, 0x04, 0x01, 0x40, 0xdc, 0x14, 0x04, 0x44, 0x85, 0x11, - 0xb1, 0x40, 0x41, 0x11, 0x01, 0x10, 0x81, 0xe2, 0x01, 0x20, 0x52, 0xe2, - 0x20, 0x00, 0xbd, 0x08, 0xf9, 0xfe, 0xff, 0x0a, 0x01, 0xe0, 0x5e, 0xe2, - 0xf4, 0xff, 0xff, 0x1a, 0x83, 0x30, 0xb0, 0xe1, 0xe8, 0xff, 0xff, 0x5a, - 0xe5, 0xff, 0xff, 0xea, 0x01, 0x00, 0x11, 0xe3, 0x01, 0x50, 0xd0, 0x04, - 0x01, 0x40, 0xd0, 0x14, 0x04, 0x44, 0x85, 0x11, 0xb1, 0x40, 0x41, 0x11, - 0x01, 0x10, 0x81, 0xe2, 0x01, 0x20, 0x52, 0xe2, 0x20, 0x00, 0xbd, 0x08, - 0xeb, 0xfe, 0xff, 0x0a, 0x83, 0x30, 0xb0, 0xe1, 0xdc, 0xff, 0xff, 0x5a, - 0xd9, 0xff, 0xff, 0xea, 0xe7, 0xfe, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, - 0x23, 0x24, 0xa0, 0xe1, 0x01, 0x30, 0xd0, 0xe4, 0x80, 0x00, 0x13, 0xe3, - 0x7f, 0x30, 0x03, 0xe2, 0x07, 0x00, 0x00, 0x0a, 0x01, 0xc0, 0xd0, 0xe4, - 0x03, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xc1, 0xe4, 0x01, 0x20, 0x52, 0xe2, - 0xdc, 0xfe, 0xff, 0x0a, 0x01, 0x30, 0x53, 0xe2, 0xfa, 0xff, 0xff, 0x1a, - 0xf3, 0xff, 0xff, 0xea, 0x01, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xd0, 0xe4, - 0x01, 0x20, 0x52, 0xe2, 0x01, 0xc0, 0xc1, 0xe4, 0xd4, 0xfe, 0xff, 0x0a, - 0x01, 0x30, 0x53, 0xe2, 0xf9, 0xff, 0xff, 0x1a, 0xeb, 0xff, 0xff, 0xea, - 0x04, 0x30, 0x90, 0xe4, 0x01, 0xc0, 0xd0, 0xe4, 0x23, 0x24, 0xa0, 0xe1, - 0x01, 0xc0, 0xc1, 0xe4, 0x01, 0x20, 0x42, 0xe2, 0x01, 0x30, 0xd0, 0xe4, - 0x01, 0x20, 0x52, 0xe2, 0x03, 0xc0, 0x8c, 0xe0, 0x01, 0xc0, 0xc1, 0xe4, - 0xfa, 0xff, 0xff, 0x1a, 0xc6, 0xfe, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, - 0xb2, 0xc0, 0xd0, 0xe0, 0x23, 0x24, 0xa0, 0xe1, 0xb2, 0xc0, 0xc1, 0xe0, - 0x01, 0x20, 0xc2, 0xe3, 0x02, 0x20, 0x42, 0xe2, 0xb2, 0x30, 0xd0, 0xe0, - 0x02, 0x20, 0x52, 0xe2, 0x03, 0xc0, 0x8c, 0xe0, 0xb2, 0xc0, 0xc1, 0xe0, - 0xfa, 0xff, 0xff, 0x1a, 0xba, 0xfe, 0xff, 0xea, 0x01, 0x13, 0xa0, 0xe3, - 0x00, 0x03, 0xc1, 0xe5, 0xb7, 0xfe, 0xff, 0xea, 0x0f, 0x50, 0x2d, 0xe9, - 0x11, 0x0f, 0x19, 0xee, 0xff, 0x00, 0xc0, 0xe3, 0x01, 0x09, 0x80, 0xe2, + 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, + 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x1a, + 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1c, 0x1c, 0x1c, 0x1d, 0x1d, 0x1d, 0x1e, + 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x20, 0x20, 0x20, 0x21, 0x21, 0x22, 0x22, + 0x22, 0x23, 0x23, 0x24, 0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x27, 0x27, + 0x27, 0x28, 0x28, 0x29, 0x29, 0x2a, 0x2a, 0x2b, 0x2b, 0x2c, 0x2c, 0x2d, + 0x2d, 0x2e, 0x2e, 0x2f, 0x2f, 0x30, 0x31, 0x31, 0x32, 0x32, 0x33, 0x33, + 0x34, 0x35, 0x35, 0x36, 0x36, 0x37, 0x38, 0x38, 0x39, 0x3a, 0x3a, 0x3b, + 0x3c, 0x3c, 0x3d, 0x3e, 0x3f, 0x3f, 0x40, 0x41, 0x42, 0x42, 0x43, 0x44, + 0x45, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, + 0x4f, 0x50, 0x51, 0x52, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x5b, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6d, 0x6e, 0x6f, 0x71, 0x72, 0x73, 0x75, 0x76, + 0x77, 0x79, 0x7a, 0x7b, 0x7d, 0x7e, 0x7f, 0x20, 0x21, 0x21, 0x21, 0x22, + 0x22, 0x23, 0x23, 0x23, 0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x26, 0x27, + 0x27, 0x28, 0x28, 0x29, 0x29, 0x2a, 0x2a, 0x2b, 0x2b, 0x2c, 0x2c, 0x2d, + 0x2d, 0x2e, 0x2e, 0x2f, 0x2f, 0x30, 0x30, 0x31, 0x31, 0x32, 0x33, 0x33, + 0x34, 0x34, 0x35, 0x36, 0x36, 0x37, 0x37, 0x38, 0x39, 0x39, 0x3a, 0x3b, + 0x3b, 0x3c, 0x3d, 0x3e, 0x3e, 0x3f, 0x40, 0x40, 0x41, 0x42, 0x43, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4d, + 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x67, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6f, 0x70, 0x71, 0x73, 0x74, 0x75, + 0x77, 0x78, 0x79, 0x7b, 0x7c, 0x7e, 0x7e, 0x40, 0x41, 0x42, 0x43, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4c, 0x4d, + 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x64, 0x65, 0x66, + 0x67, 0x68, 0x69, 0x6b, 0x6c, 0x6d, 0x6e, 0x70, 0x71, 0x72, 0x74, 0x75, + 0x76, 0x78, 0x79, 0x7b, 0x7c, 0x7d, 0x7e, 0x40, 0x41, 0x42, 0x42, 0x43, + 0x44, 0x45, 0x46, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4b, 0x4c, 0x4d, + 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x65, 0x66, + 0x67, 0x68, 0x69, 0x6a, 0x6c, 0x6d, 0x6e, 0x6f, 0x71, 0x72, 0x73, 0x75, + 0x76, 0x77, 0x79, 0x7a, 0x7c, 0x7d, 0x7e, 0x7f, 0x00, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0xa0, 0xe3, 0x3c, 0x10, 0xa0, 0xe3, 0xff, 0x20, 0xa0, 0xe3, + 0x0a, 0x0c, 0x80, 0xe3, 0x0b, 0x1b, 0x81, 0xe3, 0x05, 0x2c, 0x82, 0xe3, + 0x60, 0xfc, 0xff, 0xea, 0x01, 0x13, 0xa0, 0xe3, 0x01, 0x03, 0xc1, 0xe5, + 0x5d, 0xfc, 0xff, 0xea, 0x0f, 0x50, 0x2d, 0xe9, 0x01, 0x03, 0xa0, 0xe3, 0x0f, 0xe0, 0xa0, 0xe1, 0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8, - 0x04, 0xf0, 0x5e, 0xe2, 0xdc, 0xff, 0x80, 0x03, 0x01, 0x03, 0xa0, 0xe3, + 0x04, 0xf0, 0x5e, 0xe2, 0x32, 0x1e, 0x4f, 0xe2, 0x00, 0x00, 0xd1, 0xe7, + 0x54, 0xfc, 0xff, 0xea, 0xdc, 0xff, 0x80, 0x03, 0x01, 0x03, 0xa0, 0xe3, 0x80, 0x10, 0xa0, 0xe3, 0x00, 0x20, 0xa0, 0xe3, 0x04, 0x20, 0x20, 0xe5, 0x01, 0x10, 0x51, 0xe2, 0xfc, 0xff, 0xff, 0x1a, 0x24, 0x10, 0x1f, 0xe5, 0xd3, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, 0x01, 0xd0, 0xa0, 0xe1, @@ -1739,6 +901,844 @@ unsigned char bios_arm9_bin[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +std::array bios_arm9_bin = { + 0x3e, 0x00, 0x00, 0xea, 0x3e, 0x00, 0x00, 0xea, 0x3e, 0x00, 0x00, 0xea, + 0x3c, 0x00, 0x00, 0xea, 0x3b, 0x00, 0x00, 0xea, 0x3a, 0x00, 0x00, 0xea, + 0xaf, 0x01, 0x00, 0xea, 0x38, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea, + 0x10, 0x50, 0x2d, 0xe9, 0x00, 0x40, 0x4f, 0xe1, 0x10, 0x00, 0x2d, 0xe9, + 0x80, 0x40, 0x04, 0xe2, 0x1f, 0x40, 0x84, 0xe3, 0x02, 0xc0, 0x5e, 0xe5, + 0x04, 0xf0, 0x29, 0xe1, 0x00, 0x40, 0x2d, 0xe9, 0x20, 0x00, 0x5c, 0xe3, + 0x01, 0xc0, 0xa0, 0xa3, 0x0c, 0xf1, 0x9f, 0xe7, 0x00, 0x00, 0xa0, 0xe1, + 0x00, 0x07, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, + 0xe0, 0x01, 0xff, 0xff, 0x20, 0x02, 0xff, 0xff, 0x18, 0x02, 0xff, 0xff, + 0xd4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, + 0x4c, 0x02, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xa8, 0x02, 0xff, 0xff, + 0x28, 0x03, 0xff, 0xff, 0x68, 0x03, 0xff, 0xff, 0xb4, 0x03, 0xff, 0xff, + 0x54, 0x04, 0xff, 0xff, 0x5c, 0x04, 0xff, 0xff, 0xf0, 0x04, 0xff, 0xff, + 0x6c, 0x05, 0xff, 0xff, 0x18, 0x06, 0xff, 0xff, 0x1c, 0x06, 0xff, 0xff, + 0x1c, 0x06, 0xff, 0xff, 0x74, 0x06, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, + 0xa0, 0x06, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, + 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, + 0xd0, 0x06, 0xff, 0xff, 0x00, 0x40, 0xbd, 0xe8, 0xd3, 0x40, 0xa0, 0xe3, + 0x04, 0xf0, 0x29, 0xe1, 0x10, 0x00, 0xbd, 0xe8, 0x04, 0xf0, 0x69, 0xe1, + 0x10, 0x50, 0xbd, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xa0, 0xe3, 0x90, 0x0f, 0x07, 0xee, 0xf4, 0xff, 0xff, 0xea, + 0x01, 0x00, 0x50, 0xe2, 0xfd, 0xff, 0xff, 0xca, 0xf1, 0xff, 0xff, 0xea, + 0x11, 0x2f, 0x19, 0xee, 0xff, 0x20, 0xc2, 0xe3, 0x01, 0x29, 0x82, 0xe2, + 0x08, 0x00, 0x12, 0xe5, 0x08, 0x32, 0x83, 0xe5, 0x00, 0x00, 0x11, 0xe1, + 0x01, 0x00, 0xc0, 0xe1, 0x08, 0x00, 0x02, 0xe5, 0x01, 0xc0, 0xa0, 0xe3, + 0x08, 0xc2, 0x83, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, 0x01, 0x00, 0xa0, 0xe3, + 0x01, 0x10, 0xa0, 0xe3, 0x01, 0x33, 0xa0, 0xe3, 0x00, 0x00, 0x50, 0xe3, + 0xef, 0xff, 0xff, 0x1b, 0x00, 0x00, 0xa0, 0xe3, 0x90, 0x0f, 0x07, 0xee, + 0xec, 0xff, 0xff, 0xeb, 0xfb, 0xff, 0xff, 0x0a, 0xdc, 0xff, 0xff, 0xea, + 0xe9, 0xff, 0xff, 0xeb, 0xda, 0xff, 0xff, 0x1a, 0xf7, 0xff, 0xff, 0xea, + 0x02, 0xc1, 0x10, 0xe2, 0x00, 0x00, 0x60, 0x42, 0x02, 0x31, 0x11, 0xe2, + 0x00, 0x10, 0x61, 0x42, 0x03, 0xc0, 0x2c, 0xe0, 0x00, 0x20, 0xa0, 0xe3, + 0x01, 0x30, 0xa0, 0xe3, 0x00, 0x00, 0x51, 0xe1, 0x81, 0x10, 0xa0, 0x91, + 0x83, 0x30, 0xa0, 0x91, 0xfb, 0xff, 0xff, 0x9a, 0x01, 0x00, 0x50, 0xe1, + 0x01, 0x00, 0x40, 0x20, 0x03, 0x20, 0x82, 0x21, 0xa3, 0x30, 0xb0, 0xe1, + 0xa1, 0x10, 0xa0, 0x31, 0xf9, 0xff, 0xff, 0x3a, 0x00, 0x10, 0xa0, 0xe1, + 0x02, 0x30, 0xa0, 0xe1, 0x02, 0x00, 0xa0, 0xe1, 0x02, 0x01, 0x1c, 0xe3, + 0x00, 0x00, 0x60, 0x42, 0xc2, 0xff, 0xff, 0xea, 0xff, 0xc4, 0xc2, 0xe3, + 0x0e, 0xc6, 0xcc, 0xe3, 0x01, 0x03, 0x12, 0xe3, 0x0d, 0x00, 0x00, 0x1a, + 0x01, 0x04, 0x12, 0xe3, 0x01, 0x00, 0xc0, 0xe3, 0x01, 0x10, 0xc1, 0xe3, + 0x04, 0x00, 0x00, 0x1a, 0xb2, 0x30, 0xd0, 0xe0, 0x01, 0xc0, 0x5c, 0xe2, + 0xb2, 0x30, 0xc1, 0xe0, 0xfb, 0xff, 0xff, 0x1a, 0xb5, 0xff, 0xff, 0xea, + 0xb0, 0x30, 0xd0, 0xe1, 0xb2, 0x30, 0xc1, 0xe0, 0x01, 0xc0, 0x5c, 0xe2, + 0xfc, 0xff, 0xff, 0x1a, 0xb0, 0xff, 0xff, 0xea, 0x01, 0x04, 0x12, 0xe3, + 0x03, 0x00, 0xc0, 0xe3, 0x03, 0x10, 0xc1, 0xe3, 0x04, 0x00, 0x00, 0x1a, + 0x04, 0x30, 0x90, 0xe4, 0x01, 0xc0, 0x5c, 0xe2, 0x04, 0x30, 0x81, 0xe4, + 0xfb, 0xff, 0xff, 0x1a, 0xa7, 0xff, 0xff, 0xea, 0x00, 0x30, 0x90, 0xe5, + 0x04, 0x30, 0x81, 0xe4, 0x01, 0xc0, 0x5c, 0xe2, 0xfc, 0xff, 0xff, 0x1a, + 0xa2, 0xff, 0xff, 0xea, 0xff, 0xc4, 0xc2, 0xe3, 0x0e, 0xc6, 0xcc, 0xe3, + 0x01, 0x04, 0x12, 0xe3, 0x03, 0x00, 0xc0, 0xe3, 0x03, 0x10, 0xc1, 0xe3, + 0x04, 0x00, 0x00, 0x1a, 0x04, 0x30, 0x90, 0xe4, 0x01, 0xc0, 0x5c, 0xe2, + 0x04, 0x30, 0x81, 0xe4, 0xfb, 0xff, 0xff, 0x1a, 0x97, 0xff, 0xff, 0xea, + 0x00, 0x30, 0x90, 0xe5, 0x04, 0x30, 0x81, 0xe4, 0x01, 0xc0, 0x5c, 0xe2, + 0xfc, 0xff, 0xff, 0x1a, 0x92, 0xff, 0xff, 0xea, 0x01, 0x11, 0xa0, 0xe3, + 0x00, 0x20, 0xa0, 0xe3, 0x02, 0x30, 0x81, 0xe1, 0x03, 0x00, 0x50, 0xe1, + 0x03, 0x00, 0x40, 0xa0, 0xa2, 0x20, 0xa0, 0xe1, 0x01, 0x20, 0x82, 0xa1, + 0x21, 0x11, 0xb0, 0xe1, 0xf8, 0xff, 0xff, 0x1a, 0x02, 0x00, 0xa0, 0xe1, + 0x87, 0xff, 0xff, 0xea, 0x00, 0x00, 0x01, 0xcc, 0x01, 0xd8, 0x00, 0x14, + 0x01, 0xf0, 0x00, 0x3c, 0x00, 0x28, 0x01, 0xe4, 0x01, 0xa0, 0x00, 0x6c, + 0x00, 0x78, 0x01, 0xb4, 0x00, 0x50, 0x01, 0x9c, 0x01, 0x88, 0x00, 0x44, + 0x20, 0x00, 0x2d, 0xe9, 0x1e, 0x40, 0xa0, 0xe3, 0x30, 0xe0, 0x4f, 0xe2, + 0xff, 0x04, 0xc0, 0xe3, 0xff, 0x08, 0xc0, 0xe3, 0xa2, 0x20, 0xb0, 0xe1, + 0x1e, 0x00, 0x00, 0x0a, 0xb2, 0x30, 0xd1, 0xe0, 0x80, 0x50, 0x04, 0xe0, + 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, + 0x83, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, + 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, + 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x51, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, + 0x0c, 0x00, 0x20, 0xe0, 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, + 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x53, 0x04, 0xe0, + 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0x80, 0x50, 0x04, 0xe0, + 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, + 0xa3, 0x55, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, + 0x01, 0x20, 0x52, 0xe2, 0xe0, 0xff, 0xff, 0x1a, 0x20, 0x00, 0xbd, 0xe8, + 0x57, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe3, 0x55, 0xff, 0xff, 0xea, + 0xe0, 0x03, 0x2d, 0xe9, 0xb0, 0x30, 0xd2, 0xe1, 0x02, 0xc0, 0xd2, 0xe5, + 0x03, 0xe0, 0xd2, 0xe5, 0x04, 0x40, 0x92, 0xe5, 0xa4, 0x2f, 0xa0, 0xe1, + 0x02, 0x41, 0xc4, 0xe3, 0x01, 0x50, 0xa0, 0xe3, 0x15, 0x5c, 0xa0, 0xe1, + 0x01, 0x50, 0x45, 0xe2, 0x01, 0x60, 0xa0, 0xe3, 0x00, 0x70, 0xa0, 0xe3, + 0x00, 0x80, 0xa0, 0xe3, 0x01, 0x00, 0x5c, 0xe3, 0x83, 0x31, 0xa0, 0x01, + 0x02, 0x00, 0x5c, 0xe3, 0x03, 0x31, 0xa0, 0x01, 0x04, 0x00, 0x5c, 0xe3, + 0x83, 0x30, 0xa0, 0x01, 0x01, 0x00, 0x56, 0xe3, 0x01, 0x60, 0xd0, 0x04, + 0x01, 0x6c, 0x86, 0x03, 0x05, 0x90, 0x06, 0xe0, 0x36, 0x6c, 0xa0, 0xe1, + 0x00, 0x00, 0x59, 0xe3, 0x01, 0x00, 0x12, 0x03, 0x04, 0x90, 0x89, 0x10, + 0x19, 0x78, 0x87, 0xe1, 0x0e, 0x80, 0x88, 0xe0, 0x20, 0x00, 0x58, 0xe3, + 0x04, 0x70, 0x81, 0x04, 0x00, 0x70, 0xa0, 0x03, 0x00, 0x80, 0xa0, 0x03, + 0x01, 0x30, 0x53, 0xe2, 0xef, 0xff, 0xff, 0x1a, 0xe0, 0x03, 0xbd, 0xe8, + 0x30, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, 0x23, 0x24, 0xb0, 0xe1, + 0x2d, 0xff, 0xff, 0x0a, 0x01, 0x30, 0xd0, 0xe4, 0x02, 0x35, 0x83, 0xe3, + 0x80, 0x00, 0x13, 0xe3, 0x10, 0x00, 0x00, 0x0a, 0x01, 0x40, 0xd0, 0xe4, + 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, 0x24, 0xe6, 0xa0, 0xe1, + 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, 0x0c, 0xc0, 0x41, 0xe0, + 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x40, 0xdc, 0xe4, 0x01, 0x20, 0x52, 0xe2, + 0x01, 0x40, 0xc1, 0xe4, 0x1d, 0xff, 0xff, 0x0a, 0x01, 0xe0, 0x5e, 0xe2, + 0xf9, 0xff, 0xff, 0x1a, 0x83, 0x30, 0xb0, 0xe1, 0xed, 0xff, 0xff, 0x5a, + 0xea, 0xff, 0xff, 0xea, 0x01, 0x40, 0xd0, 0xe4, 0x01, 0x20, 0x52, 0xe2, + 0x01, 0x40, 0xc1, 0xe4, 0x14, 0xff, 0xff, 0x0a, 0x83, 0x30, 0xb0, 0xe1, + 0xe6, 0xff, 0xff, 0x5a, 0xe3, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, + 0x23, 0x24, 0xb0, 0xe1, 0x0e, 0xff, 0xff, 0x0a, 0x20, 0x00, 0x2d, 0xe9, + 0x00, 0x50, 0xa0, 0xe3, 0x01, 0x30, 0xd0, 0xe4, 0x02, 0x35, 0x83, 0xe3, + 0x80, 0x00, 0x13, 0xe3, 0x15, 0x00, 0x00, 0x0a, 0x01, 0x40, 0xd0, 0xe4, + 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, 0x24, 0xe6, 0xa0, 0xe1, + 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, 0x0c, 0xc0, 0x41, 0xe0, + 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x00, 0x11, 0xe3, 0x01, 0x50, 0xdc, 0x04, + 0x01, 0x40, 0xdc, 0x14, 0x04, 0x44, 0x85, 0x11, 0xb1, 0x40, 0x41, 0x11, + 0x01, 0x10, 0x81, 0xe2, 0x01, 0x20, 0x52, 0xe2, 0x20, 0x00, 0xbd, 0x08, + 0xf7, 0xfe, 0xff, 0x0a, 0x01, 0xe0, 0x5e, 0xe2, 0xf4, 0xff, 0xff, 0x1a, + 0x83, 0x30, 0xb0, 0xe1, 0xe8, 0xff, 0xff, 0x5a, 0xe5, 0xff, 0xff, 0xea, + 0x01, 0x00, 0x11, 0xe3, 0x01, 0x50, 0xd0, 0x04, 0x01, 0x40, 0xd0, 0x14, + 0x04, 0x44, 0x85, 0x11, 0xb1, 0x40, 0x41, 0x11, 0x01, 0x10, 0x81, 0xe2, + 0x01, 0x20, 0x52, 0xe2, 0x20, 0x00, 0xbd, 0x08, 0xe9, 0xfe, 0xff, 0x0a, + 0x83, 0x30, 0xb0, 0xe1, 0xdc, 0xff, 0xff, 0x5a, 0xd9, 0xff, 0xff, 0xea, + 0xe5, 0xfe, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, 0x23, 0x24, 0xa0, 0xe1, + 0x01, 0x30, 0xd0, 0xe4, 0x80, 0x00, 0x13, 0xe3, 0x7f, 0x30, 0x03, 0xe2, + 0x07, 0x00, 0x00, 0x0a, 0x01, 0xc0, 0xd0, 0xe4, 0x03, 0x30, 0x83, 0xe2, + 0x01, 0xc0, 0xc1, 0xe4, 0x01, 0x20, 0x52, 0xe2, 0xda, 0xfe, 0xff, 0x0a, + 0x01, 0x30, 0x53, 0xe2, 0xfa, 0xff, 0xff, 0x1a, 0xf3, 0xff, 0xff, 0xea, + 0x01, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xd0, 0xe4, 0x01, 0x20, 0x52, 0xe2, + 0x01, 0xc0, 0xc1, 0xe4, 0xd2, 0xfe, 0xff, 0x0a, 0x01, 0x30, 0x53, 0xe2, + 0xf9, 0xff, 0xff, 0x1a, 0xeb, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, + 0x01, 0xc0, 0xd0, 0xe4, 0x23, 0x24, 0xa0, 0xe1, 0x01, 0xc0, 0xc1, 0xe4, + 0x01, 0x20, 0x42, 0xe2, 0x01, 0x30, 0xd0, 0xe4, 0x01, 0x20, 0x52, 0xe2, + 0x03, 0xc0, 0x8c, 0xe0, 0x01, 0xc0, 0xc1, 0xe4, 0xfa, 0xff, 0xff, 0x1a, + 0xc4, 0xfe, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, 0xb2, 0xc0, 0xd0, 0xe0, + 0x23, 0x24, 0xa0, 0xe1, 0xb2, 0xc0, 0xc1, 0xe0, 0x01, 0x20, 0xc2, 0xe3, + 0x02, 0x20, 0x42, 0xe2, 0xb2, 0x30, 0xd0, 0xe0, 0x02, 0x20, 0x52, 0xe2, + 0x03, 0xc0, 0x8c, 0xe0, 0xb2, 0xc0, 0xc1, 0xe0, 0xfa, 0xff, 0xff, 0x1a, + 0xb8, 0xfe, 0xff, 0xea, 0x01, 0x13, 0xa0, 0xe3, 0x00, 0x03, 0xc1, 0xe5, + 0xb5, 0xfe, 0xff, 0xea, 0x0f, 0x50, 0x2d, 0xe9, 0x11, 0x0f, 0x19, 0xee, + 0xff, 0x00, 0xc0, 0xe3, 0x01, 0x09, 0x80, 0xe2, 0x0f, 0xe0, 0xa0, 0xe1, + 0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8, 0x04, 0xf0, 0x5e, 0xe2, + 0xdc, 0xff, 0x80, 0x03, 0x01, 0x03, 0xa0, 0xe3, 0x80, 0x10, 0xa0, 0xe3, + 0x00, 0x20, 0xa0, 0xe3, 0x04, 0x20, 0x20, 0xe5, 0x01, 0x10, 0x51, 0xe2, + 0xfc, 0xff, 0xff, 0x1a, 0x24, 0x10, 0x1f, 0xe5, 0xd3, 0x30, 0xa0, 0xe3, + 0x03, 0xf0, 0x2f, 0xe1, 0x01, 0xd0, 0xa0, 0xe1, 0x02, 0xe0, 0xa0, 0xe1, + 0x02, 0xf0, 0x6f, 0xe1, 0xd2, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, + 0x2c, 0xd0, 0x41, 0xe2, 0x02, 0xe0, 0xa0, 0xe1, 0x02, 0xf0, 0x6f, 0xe1, + 0x5f, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, 0xdc, 0xd0, 0x41, 0xe2, + 0xff, 0x1f, 0x90, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; } \ No newline at end of file diff --git a/src/FreeBIOS.h b/src/FreeBIOS.h index ce90725d..a60c1f88 100644 --- a/src/FreeBIOS.h +++ b/src/FreeBIOS.h @@ -28,10 +28,13 @@ #ifndef FREEBIOS_H #define FREEBIOS_H +#include +#include "MemConstants.h" + namespace melonDS { -extern unsigned char bios_arm7_bin[16384]; -extern unsigned char bios_arm9_bin[4096]; +extern std::array bios_arm7_bin; +extern std::array bios_arm9_bin; } #endif // FREEBIOS_H diff --git a/src/GBACart.cpp b/src/GBACart.cpp index f5e320f9..1be50e75 100644 --- a/src/GBACart.cpp +++ b/src/GBACart.cpp @@ -1,903 +1,878 @@ -/* - 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 "NDS.h" -#include "GBACart.h" -#include "CRC32.h" -#include "Platform.h" - -namespace melonDS -{ -using Platform::Log; -using Platform::LogLevel; - -namespace GBACart -{ - -const char SOLAR_SENSOR_GAMECODES[10][5] = -{ - "U3IJ", // Bokura no Taiyou - Taiyou Action RPG (Japan) - "U3IE", // Boktai - The Sun Is in Your Hand (USA) - "U3IP", // Boktai - The Sun Is in Your Hand (Europe) - "U32J", // Zoku Bokura no Taiyou - Taiyou Shounen Django (Japan) - "U32E", // Boktai 2 - Solar Boy Django (USA) - "U32P", // Boktai 2 - Solar Boy Django (Europe) - "U33J", // Shin Bokura no Taiyou - Gyakushuu no Sabata (Japan) - "A3IJ" // Boktai - The Sun Is in Your Hand (USA) (Sample) -}; - -CartCommon::CartCommon() -{ -} - -CartCommon::~CartCommon() -{ -} - -void CartCommon::Reset() -{ -} - -void CartCommon::DoSavestate(Savestate* file) -{ - file->Section("GBCS"); -} - -void CartCommon::SetupSave(u32 type) -{ -} - -void CartCommon::LoadSave(const u8* savedata, u32 savelen) -{ -} - -int CartCommon::SetInput(int num, bool pressed) -{ - return -1; -} - -u16 CartCommon::ROMRead(u32 addr) const -{ - return 0; -} - -void CartCommon::ROMWrite(u32 addr, u16 val) -{ -} - -u8 CartCommon::SRAMRead(u32 addr) -{ - return 0; -} - -void CartCommon::SRAMWrite(u32 addr, u8 val) -{ -} - -u8* CartCommon::GetSaveMemory() const -{ - return nullptr; -} - -u32 CartCommon::GetSaveMemoryLength() const -{ - return 0; -} - -CartGame::CartGame(u8* rom, u32 len) : CartCommon() -{ - ROM = rom; - ROMLength = len; - - SRAM = nullptr; - SRAMLength = 0; - SRAMType = S_NULL; - SRAMFlashState = {}; -} - -CartGame::~CartGame() -{ - if (SRAM) delete[] SRAM; - delete[] ROM; -} - -u32 CartGame::Checksum() const -{ - u32 crc = CRC32(ROM, 0xC0, 0); - - // TODO: hash more contents? - - return crc; -} - -void CartGame::Reset() -{ - memset(&GPIO, 0, sizeof(GPIO)); -} - -void CartGame::DoSavestate(Savestate* file) -{ - CartCommon::DoSavestate(file); - - file->Var16(&GPIO.control); - file->Var16(&GPIO.data); - file->Var16(&GPIO.direction); - - u32 oldlen = SRAMLength; - - file->Var32(&SRAMLength); - - if (SRAMLength != oldlen) - { - // reallocate save memory - if (oldlen) delete[] SRAM; - SRAM = nullptr; - if (SRAMLength) SRAM = new u8[SRAMLength]; - } - if (SRAMLength) - { - // fill save memory if data is present - file->VarArray(SRAM, SRAMLength); - } - else - { - // no save data, clear the current state - SRAMType = SaveType::S_NULL; - SRAM = nullptr; - return; - } - - // persist some extra state info - file->Var8(&SRAMFlashState.bank); - file->Var8(&SRAMFlashState.cmd); - file->Var8(&SRAMFlashState.device); - file->Var8(&SRAMFlashState.manufacturer); - file->Var8(&SRAMFlashState.state); - - file->Var8((u8*)&SRAMType); - - if ((!file->Saving) && SRAM) - Platform::WriteGBASave(SRAM, SRAMLength, 0, SRAMLength); -} - -void CartGame::SetupSave(u32 type) -{ - if (SRAM) delete[] SRAM; - SRAM = nullptr; - - // TODO: have type be determined from some list, like in NDSCart - // and not this gross hack!! - SRAMLength = type; - - if (SRAMLength) - { - SRAM = new u8[SRAMLength]; - memset(SRAM, 0xFF, SRAMLength); - } - - switch (SRAMLength) - { - case 512: - SRAMType = S_EEPROM4K; - break; - case 8192: - SRAMType = S_EEPROM64K; - break; - case 32768: - SRAMType = S_SRAM256K; - break; - case 65536: - SRAMType = S_FLASH512K; - break; - case 128*1024: - SRAMType = S_FLASH1M; - break; - case 0: - SRAMType = S_NULL; - break; - default: - Log(LogLevel::Warn, "!! BAD GBA SAVE LENGTH %d\n", SRAMLength); - } - - if (SRAMType == S_FLASH512K) - { - // Panasonic 64K chip - SRAMFlashState.device = 0x1B; - SRAMFlashState.manufacturer = 0x32; - } - else if (SRAMType == S_FLASH1M) - { - // Sanyo 128K chip - SRAMFlashState.device = 0x13; - SRAMFlashState.manufacturer = 0x62; - } -} - -void CartGame::LoadSave(const u8* savedata, u32 savelen) -{ - if (!SRAM) return; - - u32 len = std::min(savelen, SRAMLength); - memcpy(SRAM, savedata, len); - Platform::WriteGBASave(savedata, len, 0, len); -} - -u16 CartGame::ROMRead(u32 addr) const -{ - addr &= 0x01FFFFFF; - - if (addr >= 0xC4 && addr < 0xCA) - { - if (GPIO.control & 0x1) - { - switch (addr) - { - case 0xC4: return GPIO.data; - case 0xC6: return GPIO.direction; - case 0xC8: return GPIO.control; - } - } - else - return 0; - } - - // CHECKME: does ROM mirror? - if (addr < ROMLength) - return *(u16*)&ROM[addr]; - - return 0; -} - -void CartGame::ROMWrite(u32 addr, u16 val) -{ - addr &= 0x01FFFFFF; - - switch (addr) - { - case 0xC4: - GPIO.data &= ~GPIO.direction; - GPIO.data |= val & GPIO.direction; - ProcessGPIO(); - break; - - case 0xC6: - GPIO.direction = val; - break; - - case 0xC8: - GPIO.control = val; - break; - - default: - Log(LogLevel::Warn, "Unknown GBA GPIO write 0x%02X @ 0x%04X\n", val, addr); - break; - } -} - -u8 CartGame::SRAMRead(u32 addr) -{ - addr &= 0xFFFF; - - switch (SRAMType) - { - case S_EEPROM4K: - case S_EEPROM64K: - return SRAMRead_EEPROM(addr); - - case S_FLASH512K: - case S_FLASH1M: - return SRAMRead_FLASH(addr); - - case S_SRAM256K: - return SRAMRead_SRAM(addr); - default: - break; - } - - return 0xFF; -} - -void CartGame::SRAMWrite(u32 addr, u8 val) -{ - addr &= 0xFFFF; - - switch (SRAMType) - { - case S_EEPROM4K: - case S_EEPROM64K: - return SRAMWrite_EEPROM(addr, val); - - case S_FLASH512K: - case S_FLASH1M: - return SRAMWrite_FLASH(addr, val); - - case S_SRAM256K: - return SRAMWrite_SRAM(addr, val); - default: - break; - } -} - -u8* CartGame::GetSaveMemory() const -{ - return SRAM; -} - -u32 CartGame::GetSaveMemoryLength() const -{ - return SRAMLength; -} - -void CartGame::ProcessGPIO() -{ -} - -u8 CartGame::SRAMRead_EEPROM(u32 addr) -{ - return 0; -} - -void CartGame::SRAMWrite_EEPROM(u32 addr, u8 val) -{ - // TODO: could be used in homebrew? -} - -// mostly ported from DeSmuME -u8 CartGame::SRAMRead_FLASH(u32 addr) -{ - if (SRAMFlashState.cmd == 0) // no cmd - { - return *(u8*)&SRAM[addr + 0x10000 * SRAMFlashState.bank]; - } - - switch (SRAMFlashState.cmd) - { - case 0x90: // chip ID - if (addr == 0x0000) return SRAMFlashState.manufacturer; - if (addr == 0x0001) return SRAMFlashState.device; - break; - case 0xF0: // terminate command (TODO: break if non-Macronix chip and not at the end of an ID call?) - SRAMFlashState.state = 0; - SRAMFlashState.cmd = 0; - break; - case 0xA0: // write command - break; // ignore here, handled in Write_Flash() - case 0xB0: // bank switching (128K only) - break; // ignore here, handled in Write_Flash() - default: - Log(LogLevel::Warn, "GBACart_SRAM::Read_Flash: unknown command 0x%02X @ 0x%04X\n", SRAMFlashState.cmd, addr); - break; - } - - return 0xFF; -} - -// mostly ported from DeSmuME -void CartGame::SRAMWrite_FLASH(u32 addr, u8 val) -{ - switch (SRAMFlashState.state) - { - case 0x00: - if (addr == 0x5555) - { - if (val == 0xF0) - { - // reset - SRAMFlashState.state = 0; - SRAMFlashState.cmd = 0; - return; - } - else if (val == 0xAA) - { - SRAMFlashState.state = 1; - return; - } - } - if (addr == 0x0000) - { - if (SRAMFlashState.cmd == 0xB0) - { - // bank switching - SRAMFlashState.bank = val; - SRAMFlashState.cmd = 0; - return; - } - } - break; - case 0x01: - if (addr == 0x2AAA && val == 0x55) - { - SRAMFlashState.state = 2; - return; - } - SRAMFlashState.state = 0; - break; - case 0x02: - if (addr == 0x5555) - { - // send command - switch (val) - { - case 0x80: // erase - SRAMFlashState.state = 0x80; - break; - case 0x90: // chip ID - SRAMFlashState.state = 0x90; - break; - case 0xA0: // write - SRAMFlashState.state = 0; - break; - default: - SRAMFlashState.state = 0; - break; - } - - SRAMFlashState.cmd = val; - return; - } - SRAMFlashState.state = 0; - break; - // erase - case 0x80: - if (addr == 0x5555 && val == 0xAA) - { - SRAMFlashState.state = 0x81; - return; - } - SRAMFlashState.state = 0; - break; - case 0x81: - if (addr == 0x2AAA && val == 0x55) - { - SRAMFlashState.state = 0x82; - return; - } - SRAMFlashState.state = 0; - break; - case 0x82: - if (val == 0x30) - { - u32 start_addr = addr + 0x10000 * SRAMFlashState.bank; - memset((u8*)&SRAM[start_addr], 0xFF, 0x1000); - - Platform::WriteGBASave(SRAM, SRAMLength, start_addr, 0x1000); - } - SRAMFlashState.state = 0; - SRAMFlashState.cmd = 0; - return; - // chip ID - case 0x90: - if (addr == 0x5555 && val == 0xAA) - { - SRAMFlashState.state = 0x91; - return; - } - SRAMFlashState.state = 0; - break; - case 0x91: - if (addr == 0x2AAA && val == 0x55) - { - SRAMFlashState.state = 0x92; - return; - } - SRAMFlashState.state = 0; - break; - case 0x92: - SRAMFlashState.state = 0; - SRAMFlashState.cmd = 0; - return; - default: - break; - } - - if (SRAMFlashState.cmd == 0xA0) // write - { - SRAMWrite_SRAM(addr + 0x10000 * SRAMFlashState.bank, val); - SRAMFlashState.state = 0; - SRAMFlashState.cmd = 0; - return; - } - - Log(LogLevel::Debug, "GBACart_SRAM::Write_Flash: unknown write 0x%02X @ 0x%04X (state: 0x%02X)\n", - val, addr, SRAMFlashState.state); -} - -u8 CartGame::SRAMRead_SRAM(u32 addr) -{ - if (addr >= SRAMLength) return 0xFF; - - return SRAM[addr]; -} - -void CartGame::SRAMWrite_SRAM(u32 addr, u8 val) -{ - if (addr >= SRAMLength) return; - - u8 prev = *(u8*)&SRAM[addr]; - if (prev != val) - { - *(u8*)&SRAM[addr] = val; - - // TODO: optimize this!! - Platform::WriteGBASave(SRAM, SRAMLength, addr, 1); - } -} - - -const int CartGameSolarSensor::kLuxLevels[11] = {0, 5, 11, 18, 27, 42, 62, 84, 109, 139, 183}; - -CartGameSolarSensor::CartGameSolarSensor(u8* rom, u32 len) : CartGame(rom, len) -{ -} - -CartGameSolarSensor::~CartGameSolarSensor() -{ -} - -void CartGameSolarSensor::Reset() -{ - LightEdge = false; - LightCounter = 0; - LightSample = 0xFF; - LightLevel = 0; -} - -void CartGameSolarSensor::DoSavestate(Savestate* file) -{ - CartGame::DoSavestate(file); - - file->Var8((u8*)&LightEdge); - file->Var8(&LightCounter); - file->Var8(&LightSample); - file->Var8(&LightLevel); -} - -int CartGameSolarSensor::SetInput(int num, bool pressed) -{ - if (!pressed) return -1; - - if (num == Input_SolarSensorDown) - { - if (LightLevel > 0) - LightLevel--; - - return LightLevel; - } - else if (num == Input_SolarSensorUp) - { - if (LightLevel < 10) - LightLevel++; - - return LightLevel; - } - - return -1; -} - -void CartGameSolarSensor::ProcessGPIO() -{ - if (GPIO.data & 4) return; // Boktai chip select - if (GPIO.data & 2) // Reset - { - u8 prev = LightSample; - LightCounter = 0; - LightSample = (0xFF - (0x16 + kLuxLevels[LightLevel])); - Log(LogLevel::Debug, "Solar sensor reset (sample: 0x%02X -> 0x%02X)\n", prev, LightSample); - } - if (GPIO.data & 1 && LightEdge) LightCounter++; - - LightEdge = !(GPIO.data & 1); - - bool sendBit = LightCounter >= LightSample; - if (GPIO.control & 1) - { - GPIO.data = (GPIO.data & GPIO.direction) | ((sendBit << 3) & ~GPIO.direction & 0xF); - } -} - - -CartRAMExpansion::CartRAMExpansion() : CartCommon() -{ -} - -CartRAMExpansion::~CartRAMExpansion() -{ -} - -void CartRAMExpansion::Reset() -{ - memset(RAM, 0xFF, sizeof(RAM)); - RAMEnable = 1; -} - -void CartRAMExpansion::DoSavestate(Savestate* file) -{ - CartCommon::DoSavestate(file); - - file->VarArray(RAM, sizeof(RAM)); - file->Var16(&RAMEnable); -} - -u16 CartRAMExpansion::ROMRead(u32 addr) const -{ - addr &= 0x01FFFFFF; - - if (addr < 0x01000000) - { - switch (addr) - { - case 0xB0: return 0xFFFF; - case 0xB2: return 0x0000; - case 0xB4: return 0x2400; - case 0xB6: return 0x2424; - case 0xB8: return 0xFFFF; - case 0xBA: return 0xFFFF; - case 0xBC: return 0xFFFF; - case 0xBE: return 0x7FFF; - - case 0x1FFFC: return 0xFFFF; - case 0x1FFFE: return 0x7FFF; - - case 0x240000: return RAMEnable; - case 0x240002: return 0x0000; - } - - return 0xFFFF; - } - else if (addr < 0x01800000) - { - if (!RAMEnable) return 0xFFFF; - - return *(u16*)&RAM[addr & 0x7FFFFF]; - } - - return 0xFFFF; -} - -void CartRAMExpansion::ROMWrite(u32 addr, u16 val) -{ - addr &= 0x01FFFFFF; - - if (addr < 0x01000000) - { - switch (addr) - { - case 0x240000: - RAMEnable = val & 0x0001; - return; - } - } - else if (addr < 0x01800000) - { - if (!RAMEnable) return; - - *(u16*)&RAM[addr & 0x7FFFFF] = val; - } -} - -void GBACartSlot::Reset() noexcept -{ - if (Cart) Cart->Reset(); -} - -void GBACartSlot::DoSavestate(Savestate* file) noexcept -{ - file->Section("GBAC"); // Game Boy Advance Cartridge - - // little state here - // no need to save OpenBusDecay, it will be set later - - u32 carttype = 0; - u32 cartchk = 0; - if (Cart) - { - carttype = Cart->Type(); - cartchk = Cart->Checksum(); - } - - if (file->Saving) - { - file->Var32(&carttype); - file->Var32(&cartchk); - } - else - { - u32 savetype; - file->Var32(&savetype); - if (savetype != carttype) return; - - u32 savechk; - file->Var32(&savechk); - if (savechk != cartchk) return; - } - - if (Cart) Cart->DoSavestate(file); -} - - -std::unique_ptr ParseROM(const u8* romdata, u32 romlen) -{ - if (romdata == nullptr) - { - Log(LogLevel::Error, "GBACart: romdata is null\n"); - return nullptr; - } - - if (romlen == 0) - { - Log(LogLevel::Error, "GBACart: romlen is zero\n"); - return nullptr; - } - - u32 cartromsize = 0x200; - while (cartromsize < romlen) - cartromsize <<= 1; - - u8* cartrom = nullptr; - try - { - cartrom = new u8[cartromsize]; - } - catch (const std::bad_alloc& e) - { - Log(LogLevel::Error, "GBACart: failed to allocate memory for ROM (%d bytes)\n", cartromsize); - - return nullptr; - } - - memset(cartrom, 0, cartromsize); - memcpy(cartrom, romdata, romlen); - - char gamecode[5] = { '\0' }; - memcpy(&gamecode, cartrom + 0xAC, 4); - - bool solarsensor = false; - for (const char* i : SOLAR_SENSOR_GAMECODES) - { - if (strcmp(gamecode, i) == 0) - solarsensor = true; - } - - if (solarsensor) - { - Log(LogLevel::Info, "GBA solar sensor support detected!\n"); - } - - std::unique_ptr cart; - if (solarsensor) - cart = std::make_unique(cartrom, cartromsize); - else - cart = std::make_unique(cartrom, cartromsize); - - cart->Reset(); - - // TODO: setup cart save here! from a list or something - - // save - //printf("GBA save file: %s\n", sram); - - // TODO: have a list of sorts like in NDSCart? to determine the savemem type - //if (Cart) Cart->LoadSave(sram, 0); - - return cart; -} - -bool GBACartSlot::InsertROM(std::unique_ptr&& cart) noexcept -{ - if (!cart) { - Log(LogLevel::Error, "Failed to insert invalid GBA cart; existing cart (if any) was not ejected.\n"); - return false; - } - - if (Cart != nullptr) - EjectCart(); - - Cart = std::move(cart); - - const u8* cartrom = Cart->GetROM(); - - if (cartrom) - { - char gamecode[5] = { '\0' }; - memcpy(&gamecode, Cart->GetROM() + 0xAC, 4); - Log(LogLevel::Info, "Inserted GBA cart with game code: %s\n", gamecode); - } - else - { - Log(LogLevel::Info, "Inserted GBA cart with no game code (it's probably an accessory)\n"); - } - - return true; -} - -bool GBACartSlot::LoadROM(const u8* romdata, u32 romlen) noexcept -{ - std::unique_ptr data = ParseROM(romdata, romlen); - - return InsertROM(std::move(data)); -} - -void GBACartSlot::LoadSave(const u8* savedata, u32 savelen) noexcept -{ - if (Cart) - { - // gross hack - Cart->SetupSave(savelen); - - Cart->LoadSave(savedata, savelen); - } -} - -void GBACartSlot::LoadAddon(int type) noexcept -{ - switch (type) - { - case GBAAddon_RAMExpansion: - Cart = std::make_unique(); - break; - - default: - Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type); - return; - } -} - -void GBACartSlot::EjectCart() noexcept -{ - Cart = nullptr; -} - - -int GBACartSlot::SetInput(int num, bool pressed) noexcept -{ - if (Cart) return Cart->SetInput(num, pressed); - - return -1; -} - - -u16 GBACartSlot::ROMRead(u32 addr) const noexcept -{ - if (Cart) return Cart->ROMRead(addr); - - return ((addr >> 1) & 0xFFFF) | OpenBusDecay; -} - -void GBACartSlot::ROMWrite(u32 addr, u16 val) noexcept -{ - if (Cart) Cart->ROMWrite(addr, val); -} - -u8 GBACartSlot::SRAMRead(u32 addr) noexcept -{ - if (Cart) return Cart->SRAMRead(addr); - - return 0xFF; -} - -void GBACartSlot::SRAMWrite(u32 addr, u8 val) noexcept -{ - if (Cart) Cart->SRAMWrite(addr, val); -} - -} - +/* + 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 "NDS.h" +#include "GBACart.h" +#include "CRC32.h" +#include "Platform.h" +#include "Utils.h" + +namespace melonDS +{ +using Platform::Log; +using Platform::LogLevel; + +namespace GBACart +{ + +const char SOLAR_SENSOR_GAMECODES[10][5] = +{ + "U3IJ", // Bokura no Taiyou - Taiyou Action RPG (Japan) + "U3IE", // Boktai - The Sun Is in Your Hand (USA) + "U3IP", // Boktai - The Sun Is in Your Hand (Europe) + "U32J", // Zoku Bokura no Taiyou - Taiyou Shounen Django (Japan) + "U32E", // Boktai 2 - Solar Boy Django (USA) + "U32P", // Boktai 2 - Solar Boy Django (Europe) + "U33J", // Shin Bokura no Taiyou - Gyakushuu no Sabata (Japan) + "A3IJ" // Boktai - The Sun Is in Your Hand (USA) (Sample) +}; + +CartCommon::CartCommon(GBACart::CartType type) : CartType(type) +{ +} + +void CartCommon::Reset() +{ +} + +void CartCommon::DoSavestate(Savestate* file) +{ + file->Section("GBCS"); +} + +void CartCommon::SetSaveMemory(const u8* savedata, u32 savelen) +{ +} + +int CartCommon::SetInput(int num, bool pressed) +{ + return -1; +} + +u16 CartCommon::ROMRead(u32 addr) const +{ + return 0; +} + +void CartCommon::ROMWrite(u32 addr, u16 val) +{ +} + +u8 CartCommon::SRAMRead(u32 addr) +{ + return 0; +} + +void CartCommon::SRAMWrite(u32 addr, u8 val) +{ +} + +u8* CartCommon::GetSaveMemory() const +{ + return nullptr; +} + +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(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen, GBACart::CartType type) : + CartCommon(type), + ROM(std::move(rom)), + ROMLength(len), + SRAM(std::move(sram)), + SRAMLength(sramlen) +{ + if (SRAM && SRAMLength) + { + SetupSave(sramlen); + } +} + +CartGame::~CartGame() = default; +// unique_ptr cleans up the allocated memory + +u32 CartGame::Checksum() const +{ + u32 crc = CRC32(ROM.get(), 0xC0, 0); + + // TODO: hash more contents? + + return crc; +} + +void CartGame::Reset() +{ + memset(&GPIO, 0, sizeof(GPIO)); +} + +void CartGame::DoSavestate(Savestate* file) +{ + CartCommon::DoSavestate(file); + + file->Var16(&GPIO.control); + file->Var16(&GPIO.data); + file->Var16(&GPIO.direction); + + u32 oldlen = SRAMLength; + + file->Var32(&SRAMLength); + + if (SRAMLength != oldlen) + { + // reallocate save memory + SRAM = SRAMLength ? std::make_unique(SRAMLength) : nullptr; + } + if (SRAMLength) + { + // fill save memory if data is present + file->VarArray(SRAM.get(), SRAMLength); + } + else + { + // no save data, clear the current state + SRAMType = SaveType::S_NULL; + SRAM = nullptr; + return; + } + + // persist some extra state info + file->Var8(&SRAMFlashState.bank); + file->Var8(&SRAMFlashState.cmd); + file->Var8(&SRAMFlashState.device); + file->Var8(&SRAMFlashState.manufacturer); + file->Var8(&SRAMFlashState.state); + + file->Var8((u8*)&SRAMType); + + if ((!file->Saving) && SRAM) + Platform::WriteGBASave(SRAM.get(), SRAMLength, 0, SRAMLength); +} + +void CartGame::SetupSave(u32 type) +{ + // TODO: have type be determined from some list, like in NDSCart + // and not this gross hack!! + SRAMLength = type; + switch (SRAMLength) + { + case 512: + SRAMType = S_EEPROM4K; + break; + case 8192: + SRAMType = S_EEPROM64K; + break; + case 32768: + SRAMType = S_SRAM256K; + break; + case 65536: + SRAMType = S_FLASH512K; + break; + case 128*1024: + case (128*1024 + 0x10): // .sav file with appended real time clock data (ex: emulator mGBA) + SRAMType = S_FLASH1M; + break; + case 0: + SRAMType = S_NULL; + break; + default: + Log(LogLevel::Warn, "!! BAD GBA SAVE LENGTH %d\n", SRAMLength); + } + + if (SRAMType == S_FLASH512K) + { + // Panasonic 64K chip + SRAMFlashState.device = 0x1B; + SRAMFlashState.manufacturer = 0x32; + } + else if (SRAMType == S_FLASH1M) + { + // Sanyo 128K chip + SRAMFlashState.device = 0x13; + SRAMFlashState.manufacturer = 0x62; + } +} + +void CartGame::SetSaveMemory(const u8* savedata, u32 savelen) +{ + SetupSave(savelen); + + u32 len = std::min(savelen, SRAMLength); + memcpy(SRAM.get(), savedata, len); + Platform::WriteGBASave(savedata, len, 0, len); +} + +u16 CartGame::ROMRead(u32 addr) const +{ + addr &= 0x01FFFFFF; + + if (addr >= 0xC4 && addr < 0xCA) + { + if (GPIO.control & 0x1) + { + switch (addr) + { + case 0xC4: return GPIO.data; + case 0xC6: return GPIO.direction; + case 0xC8: return GPIO.control; + } + } + else + return 0; + } + + // CHECKME: does ROM mirror? + if (addr < ROMLength) + return *(u16*)&ROM[addr]; + + return 0; +} + +void CartGame::ROMWrite(u32 addr, u16 val) +{ + addr &= 0x01FFFFFF; + + switch (addr) + { + case 0xC4: + GPIO.data &= ~GPIO.direction; + GPIO.data |= val & GPIO.direction; + ProcessGPIO(); + break; + + case 0xC6: + GPIO.direction = val; + break; + + case 0xC8: + GPIO.control = val; + break; + + default: + Log(LogLevel::Warn, "Unknown GBA GPIO write 0x%02X @ 0x%04X\n", val, addr); + break; + } +} + +u8 CartGame::SRAMRead(u32 addr) +{ + addr &= 0xFFFF; + + switch (SRAMType) + { + case S_EEPROM4K: + case S_EEPROM64K: + return SRAMRead_EEPROM(addr); + + case S_FLASH512K: + case S_FLASH1M: + return SRAMRead_FLASH(addr); + + case S_SRAM256K: + return SRAMRead_SRAM(addr); + default: + break; + } + + return 0xFF; +} + +void CartGame::SRAMWrite(u32 addr, u8 val) +{ + addr &= 0xFFFF; + + switch (SRAMType) + { + case S_EEPROM4K: + case S_EEPROM64K: + return SRAMWrite_EEPROM(addr, val); + + case S_FLASH512K: + case S_FLASH1M: + return SRAMWrite_FLASH(addr, val); + + case S_SRAM256K: + return SRAMWrite_SRAM(addr, val); + default: + break; + } +} + +u8* CartGame::GetSaveMemory() const +{ + return SRAM.get(); +} + +u32 CartGame::GetSaveMemoryLength() const +{ + return SRAMLength; +} + +void CartGame::ProcessGPIO() +{ +} + +u8 CartGame::SRAMRead_EEPROM(u32 addr) +{ + return 0; +} + +void CartGame::SRAMWrite_EEPROM(u32 addr, u8 val) +{ + // TODO: could be used in homebrew? +} + +// mostly ported from DeSmuME +u8 CartGame::SRAMRead_FLASH(u32 addr) +{ + if (SRAMFlashState.cmd == 0) // no cmd + { + return *(u8*)&SRAM[addr + 0x10000 * SRAMFlashState.bank]; + } + + switch (SRAMFlashState.cmd) + { + case 0x90: // chip ID + if (addr == 0x0000) return SRAMFlashState.manufacturer; + if (addr == 0x0001) return SRAMFlashState.device; + break; + case 0xF0: // terminate command (TODO: break if non-Macronix chip and not at the end of an ID call?) + SRAMFlashState.state = 0; + SRAMFlashState.cmd = 0; + break; + case 0xA0: // write command + break; // ignore here, handled in Write_Flash() + case 0xB0: // bank switching (128K only) + break; // ignore here, handled in Write_Flash() + default: + Log(LogLevel::Warn, "GBACart_SRAM::Read_Flash: unknown command 0x%02X @ 0x%04X\n", SRAMFlashState.cmd, addr); + break; + } + + return 0xFF; +} + +// mostly ported from DeSmuME +void CartGame::SRAMWrite_FLASH(u32 addr, u8 val) +{ + switch (SRAMFlashState.state) + { + case 0x00: + if (addr == 0x5555) + { + if (val == 0xF0) + { + // reset + SRAMFlashState.state = 0; + SRAMFlashState.cmd = 0; + return; + } + else if (val == 0xAA) + { + SRAMFlashState.state = 1; + return; + } + } + if (addr == 0x0000) + { + if (SRAMFlashState.cmd == 0xB0) + { + // bank switching + SRAMFlashState.bank = val; + SRAMFlashState.cmd = 0; + return; + } + } + break; + case 0x01: + if (addr == 0x2AAA && val == 0x55) + { + SRAMFlashState.state = 2; + return; + } + SRAMFlashState.state = 0; + break; + case 0x02: + if (addr == 0x5555) + { + // send command + switch (val) + { + case 0x80: // erase + SRAMFlashState.state = 0x80; + break; + case 0x90: // chip ID + SRAMFlashState.state = 0x90; + break; + case 0xA0: // write + SRAMFlashState.state = 0; + break; + default: + SRAMFlashState.state = 0; + break; + } + + SRAMFlashState.cmd = val; + return; + } + SRAMFlashState.state = 0; + break; + // erase + case 0x80: + if (addr == 0x5555 && val == 0xAA) + { + SRAMFlashState.state = 0x81; + return; + } + SRAMFlashState.state = 0; + break; + case 0x81: + if (addr == 0x2AAA && val == 0x55) + { + SRAMFlashState.state = 0x82; + return; + } + SRAMFlashState.state = 0; + break; + case 0x82: + if (val == 0x30) + { + u32 start_addr = addr + 0x10000 * SRAMFlashState.bank; + memset((u8*)&SRAM[start_addr], 0xFF, 0x1000); + + Platform::WriteGBASave(SRAM.get(), SRAMLength, start_addr, 0x1000); + } + SRAMFlashState.state = 0; + SRAMFlashState.cmd = 0; + return; + // chip ID + case 0x90: + if (addr == 0x5555 && val == 0xAA) + { + SRAMFlashState.state = 0x91; + return; + } + SRAMFlashState.state = 0; + break; + case 0x91: + if (addr == 0x2AAA && val == 0x55) + { + SRAMFlashState.state = 0x92; + return; + } + SRAMFlashState.state = 0; + break; + case 0x92: + SRAMFlashState.state = 0; + SRAMFlashState.cmd = 0; + return; + default: + break; + } + + if (SRAMFlashState.cmd == 0xA0) // write + { + SRAMWrite_SRAM(addr + 0x10000 * SRAMFlashState.bank, val); + SRAMFlashState.state = 0; + SRAMFlashState.cmd = 0; + return; + } + + Log(LogLevel::Debug, "GBACart_SRAM::Write_Flash: unknown write 0x%02X @ 0x%04X (state: 0x%02X)\n", + val, addr, SRAMFlashState.state); +} + +u8 CartGame::SRAMRead_SRAM(u32 addr) +{ + if (addr >= SRAMLength) return 0xFF; + + return SRAM[addr]; +} + +void CartGame::SRAMWrite_SRAM(u32 addr, u8 val) +{ + if (addr >= SRAMLength) return; + + u8 prev = *(u8*)&SRAM[addr]; + if (prev != val) + { + *(u8*)&SRAM[addr] = val; + + // TODO: optimize this!! + Platform::WriteGBASave(SRAM.get(), SRAMLength, addr, 1); + } +} + + +CartGameSolarSensor::CartGameSolarSensor(const u8* rom, u32 len, const u8* sram, u32 sramlen) : + CartGameSolarSensor(CopyToUnique(rom, len), len, CopyToUnique(sram, sramlen), sramlen) +{ +} + +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) +{ +} + +const int CartGameSolarSensor::kLuxLevels[11] = {0, 5, 11, 18, 27, 42, 62, 84, 109, 139, 183}; + +void CartGameSolarSensor::Reset() +{ + CartGame::Reset(); + LightEdge = false; + LightCounter = 0; + LightSample = 0xFF; + LightLevel = 0; +} + +void CartGameSolarSensor::DoSavestate(Savestate* file) +{ + CartGame::DoSavestate(file); + + file->Var8((u8*)&LightEdge); + file->Var8(&LightCounter); + file->Var8(&LightSample); + file->Var8(&LightLevel); +} + +int CartGameSolarSensor::SetInput(int num, bool pressed) +{ + if (!pressed) return -1; + + if (num == Input_SolarSensorDown) + { + if (LightLevel > 0) + LightLevel--; + + return LightLevel; + } + else if (num == Input_SolarSensorUp) + { + if (LightLevel < 10) + LightLevel++; + + return LightLevel; + } + + return -1; +} + +void CartGameSolarSensor::ProcessGPIO() +{ + if (GPIO.data & 4) return; // Boktai chip select + if (GPIO.data & 2) // Reset + { + u8 prev = LightSample; + LightCounter = 0; + LightSample = (0xFF - (0x16 + kLuxLevels[LightLevel])); + Log(LogLevel::Debug, "Solar sensor reset (sample: 0x%02X -> 0x%02X)\n", prev, LightSample); + } + if (GPIO.data & 1 && LightEdge) LightCounter++; + + LightEdge = !(GPIO.data & 1); + + bool sendBit = LightCounter >= LightSample; + if (GPIO.control & 1) + { + GPIO.data = (GPIO.data & GPIO.direction) | ((sendBit << 3) & ~GPIO.direction & 0xF); + } +} + + +CartRAMExpansion::CartRAMExpansion() : CartCommon(RAMExpansion) +{ +} + +CartRAMExpansion::~CartRAMExpansion() = default; + +void CartRAMExpansion::Reset() +{ + memset(RAM, 0xFF, sizeof(RAM)); + RAMEnable = 1; +} + +void CartRAMExpansion::DoSavestate(Savestate* file) +{ + CartCommon::DoSavestate(file); + + file->VarArray(RAM, sizeof(RAM)); + file->Var16(&RAMEnable); +} + +u16 CartRAMExpansion::ROMRead(u32 addr) const +{ + addr &= 0x01FFFFFF; + + if (addr < 0x01000000) + { + switch (addr) + { + case 0xB0: return 0xFFFF; + case 0xB2: return 0x0000; + case 0xB4: return 0x2400; + case 0xB6: return 0x2424; + case 0xB8: return 0xFFFF; + case 0xBA: return 0xFFFF; + case 0xBC: return 0xFFFF; + case 0xBE: return 0x7FFF; + + case 0x1FFFC: return 0xFFFF; + case 0x1FFFE: return 0x7FFF; + + case 0x240000: return RAMEnable; + case 0x240002: return 0x0000; + } + + return 0xFFFF; + } + else if (addr < 0x01800000) + { + if (!RAMEnable) return 0xFFFF; + + return *(u16*)&RAM[addr & 0x7FFFFF]; + } + + return 0xFFFF; +} + +void CartRAMExpansion::ROMWrite(u32 addr, u16 val) +{ + addr &= 0x01FFFFFF; + + if (addr < 0x01000000) + { + switch (addr) + { + case 0x240000: + RAMEnable = val & 0x0001; + return; + } + } + else if (addr < 0x01800000) + { + if (!RAMEnable) return; + + *(u16*)&RAM[addr & 0x7FFFFF] = val; + } +} + +GBACartSlot::GBACartSlot(std::unique_ptr&& cart) noexcept : Cart(std::move(cart)) +{ +} + +void GBACartSlot::Reset() noexcept +{ + if (Cart) Cart->Reset(); +} + +void GBACartSlot::DoSavestate(Savestate* file) noexcept +{ + file->Section("GBAC"); // Game Boy Advance Cartridge + + // little state here + // no need to save OpenBusDecay, it will be set later + + u32 carttype = 0; + u32 cartchk = 0; + if (Cart) + { + carttype = Cart->Type(); + cartchk = Cart->Checksum(); + } + + if (file->Saving) + { + file->Var32(&carttype); + file->Var32(&cartchk); + } + else + { + u32 savetype; + file->Var32(&savetype); + if (savetype != carttype) return; + + u32 savechk; + file->Var32(&savechk); + if (savechk != cartchk) return; + } + + if (Cart) Cart->DoSavestate(file); +} + +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen) +{ + return ParseROM(std::move(romdata), romlen, nullptr, 0); +} + +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, const u8* sramdata, u32 sramlen) +{ + auto [romcopy, romcopylen] = PadToPowerOf2(romdata, romlen); + + return ParseROM(std::move(romcopy), romcopylen, CopyToUnique(sramdata, sramlen), sramlen); +} + +std::unique_ptr ParseROM(const u8* romdata, u32 romlen) +{ + return ParseROM(romdata, romlen, nullptr, 0); +} + +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::unique_ptr&& sramdata, u32 sramlen) +{ + if (romdata == nullptr) + { + Log(LogLevel::Error, "GBACart: romdata is null\n"); + return nullptr; + } + + if (romlen == 0) + { + Log(LogLevel::Error, "GBACart: romlen is zero\n"); + return nullptr; + } + + auto [cartrom, cartromsize] = PadToPowerOf2(std::move(romdata), romlen); + + char gamecode[5] = { '\0' }; + memcpy(&gamecode, cartrom.get() + 0xAC, 4); + + bool solarsensor = false; + for (const char* i : SOLAR_SENSOR_GAMECODES) + { + if (strcmp(gamecode, i) == 0) + solarsensor = true; + } + + if (solarsensor) + { + Log(LogLevel::Info, "GBA solar sensor support detected!\n"); + } + + std::unique_ptr cart; + if (solarsensor) + cart = std::make_unique(std::move(cartrom), cartromsize, std::move(sramdata), sramlen); + else + cart = std::make_unique(std::move(cartrom), cartromsize, std::move(sramdata), sramlen); + + cart->Reset(); + + // save + //printf("GBA save file: %s\n", sram); + + // TODO: have a list of sorts like in NDSCart? to determine the savemem type + //if (Cart) Cart->LoadSave(sram, 0); + + return cart; +} + +void GBACartSlot::SetCart(std::unique_ptr&& cart) noexcept +{ + Cart = std::move(cart); + + if (!Cart) + { + Log(LogLevel::Info, "Ejected GBA cart"); + return; + } + + const u8* cartrom = Cart->GetROM(); + + if (cartrom) + { + char gamecode[5] = { '\0' }; + memcpy(&gamecode, Cart->GetROM() + 0xAC, 4); + Log(LogLevel::Info, "Inserted GBA cart with game code: %s\n", gamecode); + } + else + { + Log(LogLevel::Info, "Inserted GBA cart with no game code (it's probably an accessory)\n"); + } +} + +void GBACartSlot::SetSaveMemory(const u8* savedata, u32 savelen) noexcept +{ + if (Cart) + { + Cart->SetSaveMemory(savedata, savelen); + } +} + +void GBACartSlot::LoadAddon(int type) noexcept +{ + switch (type) + { + case GBAAddon_RAMExpansion: + Cart = std::make_unique(); + break; + + default: + Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type); + return; + } +} + +std::unique_ptr GBACartSlot::EjectCart() noexcept +{ + return std::move(Cart); + // Cart will be nullptr after this function returns, due to the move +} + + +int GBACartSlot::SetInput(int num, bool pressed) noexcept +{ + if (Cart) return Cart->SetInput(num, pressed); + + return -1; +} + + +u16 GBACartSlot::ROMRead(u32 addr) const noexcept +{ + if (Cart) return Cart->ROMRead(addr); + + return ((addr >> 1) & 0xFFFF) | OpenBusDecay; +} + +void GBACartSlot::ROMWrite(u32 addr, u16 val) noexcept +{ + if (Cart) Cart->ROMWrite(addr, val); +} + +u8 GBACartSlot::SRAMRead(u32 addr) noexcept +{ + if (Cart) return Cart->SRAMRead(addr); + + return 0xFF; +} + +void GBACartSlot::SRAMWrite(u32 addr, u8 val) noexcept +{ + if (Cart) Cart->SRAMWrite(addr, val); +} + +} + } \ No newline at end of file diff --git a/src/GBACart.h b/src/GBACart.h index a5570897..493bf6b8 100644 --- a/src/GBACart.h +++ b/src/GBACart.h @@ -1,257 +1,289 @@ -/* - 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 GBACART_H -#define GBACART_H - -#include -#include "types.h" -#include "Savestate.h" - -namespace melonDS::GBACart -{ - -enum CartType -{ - Default = 0x001, - Game = 0x101, - GameSolarSensor = 0x102, - RAMExpansion = 0x201, -}; - -// CartCommon -- base code shared by all cart types -class CartCommon -{ -public: - CartCommon(); - virtual ~CartCommon(); - - virtual u32 Type() const = 0; - virtual u32 Checksum() const { return 0; } - - virtual void Reset(); - - virtual void DoSavestate(Savestate* file); - - virtual void SetupSave(u32 type); - virtual void LoadSave(const u8* savedata, u32 savelen); - - virtual int SetInput(int num, bool pressed); - - virtual u16 ROMRead(u32 addr) const; - virtual void ROMWrite(u32 addr, u16 val); - - virtual u8 SRAMRead(u32 addr); - virtual void SRAMWrite(u32 addr, u8 val); - - [[nodiscard]] virtual const u8* GetROM() const { return nullptr; } - [[nodiscard]] virtual u32 GetROMLength() const { return 0; } - - virtual u8* GetSaveMemory() const; - virtual u32 GetSaveMemoryLength() const; -}; - -// CartGame -- regular retail game cart (ROM, SRAM) -class CartGame : public CartCommon -{ -public: - CartGame(u8* rom, u32 len); - virtual ~CartGame() override; - - virtual u32 Type() const override { return CartType::Game; } - virtual u32 Checksum() const override; - - virtual void Reset() override; - - virtual void DoSavestate(Savestate* file) override; - - virtual void SetupSave(u32 type) override; - virtual void LoadSave(const u8* savedata, u32 savelen) override; - - virtual u16 ROMRead(u32 addr) const override; - virtual void ROMWrite(u32 addr, u16 val) override; - - virtual u8 SRAMRead(u32 addr) override; - virtual void SRAMWrite(u32 addr, u8 val) override; - - [[nodiscard]] const u8* GetROM() const override { return ROM; } - [[nodiscard]] u32 GetROMLength() const override { return ROMLength; } - - virtual u8* GetSaveMemory() const override; - virtual u32 GetSaveMemoryLength() const override; -protected: - virtual void ProcessGPIO(); - - u8 SRAMRead_EEPROM(u32 addr); - void SRAMWrite_EEPROM(u32 addr, u8 val); - u8 SRAMRead_FLASH(u32 addr); - void SRAMWrite_FLASH(u32 addr, u8 val); - u8 SRAMRead_SRAM(u32 addr); - void SRAMWrite_SRAM(u32 addr, u8 val); - - u8* ROM; - u32 ROMLength; - - struct - { - u16 data; - u16 direction; - u16 control; - - } GPIO; - - enum SaveType - { - S_NULL, - S_EEPROM4K, - S_EEPROM64K, - S_SRAM256K, - S_FLASH512K, - S_FLASH1M - }; - - // from DeSmuME - struct - { - u8 state; - u8 cmd; - u8 device; - u8 manufacturer; - u8 bank; - - } SRAMFlashState; - - u8* SRAM; - u32 SRAMLength; - SaveType SRAMType; -}; - -// CartGameSolarSensor -- Boktai game cart -class CartGameSolarSensor : public CartGame -{ -public: - CartGameSolarSensor(u8* rom, u32 len); - virtual ~CartGameSolarSensor() override; - - virtual u32 Type() const override { return CartType::GameSolarSensor; } - - virtual void Reset() override; - - virtual void DoSavestate(Savestate* file) override; - - virtual int SetInput(int num, bool pressed) override; - -private: - virtual void ProcessGPIO() override; - - static const int kLuxLevels[11]; - - bool LightEdge; - u8 LightCounter; - u8 LightSample; - u8 LightLevel; -}; - -// CartRAMExpansion -- RAM expansion cart (DS browser, ...) -class CartRAMExpansion : public CartCommon -{ -public: - CartRAMExpansion(); - ~CartRAMExpansion() override; - - virtual u32 Type() const override { return CartType::RAMExpansion; } - - void Reset() override; - - void DoSavestate(Savestate* file) override; - - u16 ROMRead(u32 addr) const override; - void ROMWrite(u32 addr, u16 val) override; - -private: - u8 RAM[0x800000]; - u16 RAMEnable; -}; - -// possible inputs for GBA carts that might accept user input -enum -{ - Input_SolarSensorDown = 0, - Input_SolarSensorUp, -}; - -class GBACartSlot -{ -public: - GBACartSlot() noexcept = default; - ~GBACartSlot() noexcept = default; - void Reset() noexcept; - void DoSavestate(Savestate* file) noexcept; - /// Applies the GBACartData to the emulator state and unloads an existing ROM if any. - /// Upon successful insertion, \c cart will be nullptr and the global GBACart state - /// (\c CartROM, CartInserted, etc.) will be updated. - bool InsertROM(std::unique_ptr&& cart) noexcept; - bool LoadROM(const u8* romdata, u32 romlen) noexcept; - void LoadSave(const u8* savedata, u32 savelen) noexcept; - - void LoadAddon(int type) noexcept; - - void EjectCart() noexcept; - - // TODO: make more flexible, support nonbinary inputs - int SetInput(int num, bool pressed) noexcept; - - void SetOpenBusDecay(u16 val) noexcept { OpenBusDecay = val; } - - u16 ROMRead(u32 addr) const noexcept; - void ROMWrite(u32 addr, u16 val) noexcept; - - u8 SRAMRead(u32 addr) noexcept; - void SRAMWrite(u32 addr, u8 val) noexcept; - - /// This function is intended to allow frontends to save and load SRAM - /// without using melonDS APIs. - /// Modifying the emulated SRAM for any other reason is strongly discouraged. - /// The returned pointer may be invalidated if the emulator is reset, - /// or when a new game is loaded. - /// Consequently, don't store the returned pointer for any longer than necessary. - /// @returns Pointer to this cart's SRAM if a cart is loaded and supports SRAM, otherwise \c nullptr. - [[nodiscard]] u8* GetSaveMemory() noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; } - [[nodiscard]] const u8* GetSaveMemory() const noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; } - - /// @returns The length of the buffer returned by ::GetSaveMemory() - /// if a cart is loaded and supports SRAM, otherwise zero. - [[nodiscard]] u32 GetSaveMemoryLength() const noexcept { return Cart ? Cart->GetSaveMemoryLength() : 0; } -private: - std::unique_ptr Cart = nullptr; - u16 OpenBusDecay = 0; -}; - -/// Parses the given ROM data and constructs a \c GBACart::CartCommon subclass -/// that can be inserted into the emulator or used to extract information about the cart beforehand. -/// @param romdata The ROM data to parse. -/// The returned cartridge will contain a copy of this data, -/// so the caller may deallocate \c romdata after this function returns. -/// @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); - -} - -#endif // GBACART_H +/* + 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 GBACART_H +#define GBACART_H + +#include +#include "types.h" +#include "Savestate.h" + +namespace melonDS::GBACart +{ + +enum CartType +{ + Default = 0x001, + Game = 0x101, + GameSolarSensor = 0x102, + RAMExpansion = 0x201, +}; + +// CartCommon -- base code shared by all cart types +class CartCommon +{ +public: + virtual ~CartCommon() = default; + + [[nodiscard]] u32 Type() const { return CartType; } + virtual u32 Checksum() const { return 0; } + + virtual void Reset(); + + virtual void DoSavestate(Savestate* file); + + virtual int SetInput(int num, bool pressed); + + virtual u16 ROMRead(u32 addr) const; + virtual void ROMWrite(u32 addr, u16 val); + + virtual u8 SRAMRead(u32 addr); + virtual void SRAMWrite(u32 addr, u8 val); + + [[nodiscard]] virtual const u8* GetROM() const { return nullptr; } + [[nodiscard]] virtual u32 GetROMLength() const { return 0; } + + virtual u8* GetSaveMemory() const; + virtual u32 GetSaveMemoryLength() const; + virtual void SetSaveMemory(const u8* savedata, u32 savelen); +protected: + CartCommon(GBACart::CartType type); + friend class GBACartSlot; +private: + GBACart::CartType CartType; +}; + +// CartGame -- regular retail game cart (ROM, SRAM) +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() override; + + u32 Checksum() const override; + + void Reset() override; + + void DoSavestate(Savestate* file) override; + + u16 ROMRead(u32 addr) const override; + void ROMWrite(u32 addr, u16 val) override; + + u8 SRAMRead(u32 addr) override; + void SRAMWrite(u32 addr, u8 val) override; + + [[nodiscard]] const u8* GetROM() const override { return ROM.get(); } + [[nodiscard]] u32 GetROMLength() const override { return ROMLength; } + + u8* GetSaveMemory() const override; + u32 GetSaveMemoryLength() const override; + void SetSaveMemory(const u8* savedata, u32 savelen) override; +protected: + virtual void ProcessGPIO(); + + u8 SRAMRead_EEPROM(u32 addr); + void SRAMWrite_EEPROM(u32 addr, u8 val); + u8 SRAMRead_FLASH(u32 addr); + void SRAMWrite_FLASH(u32 addr, u8 val); + u8 SRAMRead_SRAM(u32 addr); + void SRAMWrite_SRAM(u32 addr, u8 val); + + std::unique_ptr ROM; + u32 ROMLength; + + struct + { + u16 data; + u16 direction; + u16 control; + + } GPIO {}; + + enum SaveType + { + S_NULL, + S_EEPROM4K, + S_EEPROM64K, + S_SRAM256K, + S_FLASH512K, + S_FLASH1M + }; + + // from DeSmuME + struct + { + u8 state; + u8 cmd; + u8 device; + u8 manufacturer; + u8 bank; + + } SRAMFlashState {}; + + std::unique_ptr SRAM = nullptr; + u32 SRAMLength = 0; + SaveType SRAMType = S_NULL; +private: + void SetupSave(u32 type); +}; + +// CartGameSolarSensor -- Boktai game cart +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); + + void Reset() override; + + void DoSavestate(Savestate* file) override; + + int SetInput(int num, bool pressed) override; + +protected: + void ProcessGPIO() override; + +private: + static const int kLuxLevels[11]; + + bool LightEdge = false; + u8 LightCounter = 0; + u8 LightSample = 0; + u8 LightLevel = 0; +}; + +// CartRAMExpansion -- RAM expansion cart (DS browser, ...) +class CartRAMExpansion : public CartCommon +{ +public: + CartRAMExpansion(); + ~CartRAMExpansion() override; + + void Reset() override; + + void DoSavestate(Savestate* file) override; + + u16 ROMRead(u32 addr) const override; + void ROMWrite(u32 addr, u16 val) override; + +private: + u8 RAM[0x800000] {}; + u16 RAMEnable = 0; +}; + +// possible inputs for GBA carts that might accept user input +enum +{ + Input_SolarSensorDown = 0, + Input_SolarSensorUp, +}; + +class GBACartSlot +{ +public: + GBACartSlot(std::unique_ptr&& cart = nullptr) noexcept; + ~GBACartSlot() noexcept = default; + void Reset() noexcept; + void DoSavestate(Savestate* file) noexcept; + + /// Ejects the cart in the GBA slot (if any) + /// and inserts the given one. + /// + /// To insert a cart that does not require ROM data + /// (such as the RAM expansion pack), + /// create it manually with std::make_unique and pass it here. + /// + /// @param cart Movable \c unique_ptr to the GBA cart object. + /// May be \c nullptr, in which case the cart slot remains empty. + /// @post \c cart is \c nullptr and the underlying object + /// is moved into the cart slot. + void SetCart(std::unique_ptr&& cart) noexcept; + [[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); } + [[nodiscard]] const CartCommon* GetCart() const noexcept { return Cart.get(); } + + void LoadAddon(int type) noexcept; + + /// @return The cart that was in the cart slot if any, + /// or \c nullptr if the cart slot was empty. + std::unique_ptr EjectCart() noexcept; + + // TODO: make more flexible, support nonbinary inputs + int SetInput(int num, bool pressed) noexcept; + + void SetOpenBusDecay(u16 val) noexcept { OpenBusDecay = val; } + + u16 ROMRead(u32 addr) const noexcept; + void ROMWrite(u32 addr, u16 val) noexcept; + + u8 SRAMRead(u32 addr) noexcept; + void SRAMWrite(u32 addr, u8 val) noexcept; + + /// This function is intended to allow frontends to save and load SRAM + /// without using melonDS APIs. + /// Modifying the emulated SRAM for any other reason is strongly discouraged. + /// The returned pointer may be invalidated if the emulator is reset, + /// or when a new game is loaded. + /// Consequently, don't store the returned pointer for any longer than necessary. + /// @returns Pointer to this cart's SRAM if a cart is loaded and supports SRAM, otherwise \c nullptr. + [[nodiscard]] u8* GetSaveMemory() noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; } + [[nodiscard]] const u8* GetSaveMemory() const noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; } + + /// Sets the loaded cart's SRAM. + /// Does nothing if no cart is inserted + /// or the inserted cart doesn't support SRAM. + /// + /// @param savedata Buffer containing the raw contents of the SRAM. + /// The contents of this buffer are copied into the cart slot, + /// so the caller may dispose of it after this method returns. + /// @param savelen The length of the buffer in \c savedata, in bytes. + void SetSaveMemory(const u8* savedata, u32 savelen) noexcept; + + /// @returns The length of the buffer returned by ::GetSaveMemory() + /// if a cart is loaded and supports SRAM, otherwise zero. + [[nodiscard]] u32 GetSaveMemoryLength() const noexcept { return Cart ? Cart->GetSaveMemoryLength() : 0; } +private: + std::unique_ptr Cart = nullptr; + u16 OpenBusDecay = 0; +}; + +/// Parses the given ROM data and constructs a \c GBACart::CartCommon subclass +/// that can be inserted into the emulator or used to extract information about the cart beforehand. +/// @param romdata The ROM data to parse. +/// The returned cartridge will contain a copy of this data, +/// so the caller may deallocate \c romdata after this function returns. +/// @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); + +/// @param romdata The ROM data to parse. Will be moved-from. +/// @param romlen Length of romdata in bytes. +/// @param sramdata The save data to add to the cart. +/// May be \c nullptr, in which case the cart will have no save data. +/// @param sramlen Length of sramdata in bytes. +/// 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); + +} + +#endif // GBACART_H diff --git a/src/GPU.cpp b/src/GPU.cpp index c80e3119..07a6654e 100644 --- a/src/GPU.cpp +++ b/src/GPU.cpp @@ -23,7 +23,7 @@ #include "ARMJIT.h" #include "GPU2D_Soft.h" -#include "GPU3D_Soft.h" +#include "GPU3D.h" namespace melonDS { @@ -67,7 +67,7 @@ GPU::GPU(melonDS::NDS& nds, std::unique_ptr&& renderer3d, std::uniqu NDS(nds), GPU2D_A(0, *this), GPU2D_B(1, *this), - GPU3D(nds, renderer3d ? std::move(renderer3d) : std::make_unique(*this)), + GPU3D(nds, renderer3d ? std::move(renderer3d) : std::make_unique()), GPU2D_Renderer(renderer2d ? std::move(renderer2d) : std::make_unique(*this)) { NDS.RegisterEventFunc(Event_LCD, LCD_StartHBlank, MemberEventFunc(GPU, StartHBlank)); @@ -75,7 +75,7 @@ GPU::GPU(melonDS::NDS& nds, std::unique_ptr&& renderer3d, std::uniqu NDS.RegisterEventFunc(Event_LCD, LCD_FinishFrame, MemberEventFunc(GPU, FinishFrame)); NDS.RegisterEventFunc(Event_DisplayFIFO, 0, MemberEventFunc(GPU, DisplayFIFO)); - FrontBuffer = 0; + InitFramebuffers(); } GPU::~GPU() noexcept @@ -209,7 +209,7 @@ void GPU::Stop() noexcept memset(Framebuffer[1][0].get(), 0, fbsize*4); memset(Framebuffer[1][1].get(), 0, fbsize*4); - GPU3D.Stop(); + GPU3D.Stop(*this); } void GPU::DoSavestate(Savestate* file) noexcept @@ -275,7 +275,8 @@ void GPU::DoSavestate(Savestate* file) noexcept GPU2D_B.DoSavestate(file); GPU3D.DoSavestate(file); - ResetVRAMCache(); + if (!file->Saving) + ResetVRAMCache(); } void GPU::AssignFramebuffers() noexcept @@ -294,10 +295,15 @@ void GPU::AssignFramebuffers() noexcept void GPU::SetRenderer3D(std::unique_ptr&& renderer) noexcept { if (renderer == nullptr) - GPU3D.SetCurrentRenderer(std::make_unique(*this)); + GPU3D.SetCurrentRenderer(std::make_unique()); else GPU3D.SetCurrentRenderer(std::move(renderer)); + InitFramebuffers(); +} + +void GPU::InitFramebuffers() noexcept +{ int fbsize; if (GPU3D.IsRendererAccelerated()) fbsize = (256*3 + 1) * 192; @@ -894,7 +900,7 @@ void GPU::StartHBlank(u32 line) noexcept } else if (VCount == 215) { - GPU3D.VCount215(); + GPU3D.VCount215(*this); } else if (VCount == 262) { @@ -920,7 +926,7 @@ void GPU::FinishFrame(u32 lines) noexcept if (GPU3D.AbortFrame) { - GPU3D.RestartFrame(); + GPU3D.RestartFrame(*this); GPU3D.AbortFrame = false; } } @@ -1013,7 +1019,7 @@ void GPU::StartScanline(u32 line) noexcept // texture memory anyway and only update it before the start //of the next frame. // So we can give the rasteriser a bit more headroom - GPU3D.VCount144(); + GPU3D.VCount144(*this); // VBlank DispStat[0] |= (1<<0); @@ -1033,7 +1039,7 @@ void GPU::StartScanline(u32 line) noexcept // Need a better way to identify the openGL renderer in particular if (GPU3D.IsRendererAccelerated()) - GPU3D.Blit(); + GPU3D.Blit(*this); } } @@ -1063,7 +1069,7 @@ void GPU::SetVCount(u16 val) noexcept } template -NonStupidBitField VRAMTrackingSet::DeriveState(u32* currentMappings, GPU& gpu) +NonStupidBitField VRAMTrackingSet::DeriveState(const u32* currentMappings, GPU& gpu) { NonStupidBitField result; u16 banksToBeZeroed = 0; @@ -1126,12 +1132,12 @@ NonStupidBitField VRAMTrackingSet VRAMTrackingSet<32*1024, 8*1024>::DeriveState(u32*, GPU& gpu); -template NonStupidBitField<8*1024/VRAMDirtyGranularity> VRAMTrackingSet<8*1024, 8*1024>::DeriveState(u32*, GPU& gpu); -template NonStupidBitField<512*1024/VRAMDirtyGranularity> VRAMTrackingSet<512*1024, 128*1024>::DeriveState(u32*, GPU& gpu); -template NonStupidBitField<128*1024/VRAMDirtyGranularity> VRAMTrackingSet<128*1024, 16*1024>::DeriveState(u32*, GPU& gpu); -template NonStupidBitField<256*1024/VRAMDirtyGranularity> VRAMTrackingSet<256*1024, 16*1024>::DeriveState(u32*, GPU& gpu); -template NonStupidBitField<512*1024/VRAMDirtyGranularity> VRAMTrackingSet<512*1024, 16*1024>::DeriveState(u32*, GPU& gpu); +template NonStupidBitField<32*1024/VRAMDirtyGranularity> VRAMTrackingSet<32*1024, 8*1024>::DeriveState(const u32*, GPU& gpu); +template NonStupidBitField<8*1024/VRAMDirtyGranularity> VRAMTrackingSet<8*1024, 8*1024>::DeriveState(const u32*, GPU& gpu); +template NonStupidBitField<512*1024/VRAMDirtyGranularity> VRAMTrackingSet<512*1024, 128*1024>::DeriveState(const u32*, GPU& gpu); +template NonStupidBitField<128*1024/VRAMDirtyGranularity> VRAMTrackingSet<128*1024, 16*1024>::DeriveState(const u32*, GPU& gpu); +template NonStupidBitField<256*1024/VRAMDirtyGranularity> VRAMTrackingSet<256*1024, 16*1024>::DeriveState(const u32*, GPU& gpu); +template NonStupidBitField<512*1024/VRAMDirtyGranularity> VRAMTrackingSet<512*1024, 16*1024>::DeriveState(const u32*, GPU& gpu); diff --git a/src/GPU.h b/src/GPU.h index 21a05d58..780d5e01 100644 --- a/src/GPU.h +++ b/src/GPU.h @@ -49,7 +49,7 @@ struct VRAMTrackingSet Mapping[i] = 0x8000; } } - NonStupidBitField DeriveState(u32* currentMappings, GPU& gpu); + NonStupidBitField DeriveState(const u32* currentMappings, GPU& gpu); }; class GPU @@ -536,18 +536,18 @@ public: u8 VRAMCNT[9] {}; u8 VRAMSTAT = 0; - u8 Palette[2*1024] {}; - u8 OAM[2*1024] {}; + alignas(u64) u8 Palette[2*1024] {}; + alignas(u64) u8 OAM[2*1024] {}; - u8 VRAM_A[128*1024] {}; - u8 VRAM_B[128*1024] {}; - u8 VRAM_C[128*1024] {}; - u8 VRAM_D[128*1024] {}; - u8 VRAM_E[ 64*1024] {}; - u8 VRAM_F[ 16*1024] {}; - u8 VRAM_G[ 16*1024] {}; - u8 VRAM_H[ 32*1024] {}; - u8 VRAM_I[ 16*1024] {}; + alignas(u64) u8 VRAM_A[128*1024] {}; + alignas(u64) u8 VRAM_B[128*1024] {}; + alignas(u64) u8 VRAM_C[128*1024] {}; + alignas(u64) u8 VRAM_D[128*1024] {}; + alignas(u64) u8 VRAM_E[ 64*1024] {}; + alignas(u64) u8 VRAM_F[ 16*1024] {}; + alignas(u64) u8 VRAM_G[ 16*1024] {}; + alignas(u64) u8 VRAM_H[ 32*1024] {}; + alignas(u64) u8 VRAM_I[ 16*1024] {}; u8* const VRAM[9] = {VRAM_A, VRAM_B, VRAM_C, VRAM_D, VRAM_E, VRAM_F, VRAM_G, VRAM_H, VRAM_I}; u32 const VRAMMask[9] = {0x1FFFF, 0x1FFFF, 0x1FFFF, 0x1FFFF, 0xFFFF, 0x3FFF, 0x3FFF, 0x7FFF, 0x3FFF}; @@ -596,17 +596,18 @@ public: u8 VRAMFlat_AOBJ[256*1024] {}; u8 VRAMFlat_BOBJ[128*1024] {}; - u8 VRAMFlat_ABGExtPal[32*1024] {}; - u8 VRAMFlat_BBGExtPal[32*1024] {}; + alignas(u16) u8 VRAMFlat_ABGExtPal[32*1024] {}; + alignas(u16) u8 VRAMFlat_BBGExtPal[32*1024] {}; - u8 VRAMFlat_AOBJExtPal[8*1024] {}; - u8 VRAMFlat_BOBJExtPal[8*1024] {}; + alignas(u16) u8 VRAMFlat_AOBJExtPal[8*1024] {}; + alignas(u16) u8 VRAMFlat_BOBJExtPal[8*1024] {}; - u8 VRAMFlat_Texture[512*1024] {}; - u8 VRAMFlat_TexPal[128*1024] {}; + alignas(u64) u8 VRAMFlat_Texture[512*1024] {}; + alignas(u64) u8 VRAMFlat_TexPal[128*1024] {}; private: void ResetVRAMCache() noexcept; void AssignFramebuffers() noexcept; + void InitFramebuffers() noexcept; template T ReadVRAM_ABGExtPal(u32 addr) const noexcept { diff --git a/src/GPU2D.cpp b/src/GPU2D.cpp index e7fb9a29..be6a5987 100644 --- a/src/GPU2D.cpp +++ b/src/GPU2D.cpp @@ -20,6 +20,7 @@ #include #include "NDS.h" #include "GPU.h" +#include "GPU3D.h" namespace melonDS { @@ -648,7 +649,7 @@ void Unit::CheckWindows(u32 line) else if (line == Win1Coords[2]) Win1Active |= 0x1; } -void Unit::CalculateWindowMask(u32 line, u8* windowMask, u8* objWindow) +void Unit::CalculateWindowMask(u32 line, u8* windowMask, const u8* objWindow) { for (u32 i = 0; i < 256; i++) windowMask[i] = WinCnt[2]; // window outside @@ -694,7 +695,7 @@ void Unit::CalculateWindowMask(u32 line, u8* windowMask, u8* objWindow) } } -void Unit::GetBGVRAM(u8*& data, u32& mask) +void Unit::GetBGVRAM(u8*& data, u32& mask) const { if (Num == 0) { @@ -708,7 +709,7 @@ void Unit::GetBGVRAM(u8*& data, u32& mask) } } -void Unit::GetOBJVRAM(u8*& data, u32& mask) +void Unit::GetOBJVRAM(u8*& data, u32& mask) const { if (Num == 0) { diff --git a/src/GPU2D.h b/src/GPU2D.h index 7367d07a..e87167cb 100644 --- a/src/GPU2D.h +++ b/src/GPU2D.h @@ -52,7 +52,7 @@ public: void Write16(u32 addr, u16 val); void Write32(u32 addr, u32 val); - bool UsesFIFO() + bool UsesFIFO() const { if (((DispCnt >> 16) & 0x3) == 3) return true; @@ -72,11 +72,11 @@ public: u16* GetBGExtPal(u32 slot, u32 pal); u16* GetOBJExtPal(); - void GetBGVRAM(u8*& data, u32& mask); - void GetOBJVRAM(u8*& data, u32& mask); + void GetBGVRAM(u8*& data, u32& mask) const; + void GetOBJVRAM(u8*& data, u32& mask) const; void UpdateMosaicCounters(u32 line); - void CalculateWindowMask(u32 line, u8* windowMask, u8* objWindow); + void CalculateWindowMask(u32 line, u8* windowMask, const u8* objWindow); u32 Num; bool Enabled; diff --git a/src/GPU2D_Soft.cpp b/src/GPU2D_Soft.cpp index 6d0252c3..26f9a875 100644 --- a/src/GPU2D_Soft.cpp +++ b/src/GPU2D_Soft.cpp @@ -18,7 +18,7 @@ #include "GPU2D_Soft.h" #include "GPU.h" -#include "GPU3D_OpenGL.h" +#include "GPU3D.h" namespace melonDS { @@ -30,7 +30,7 @@ SoftRenderer::SoftRenderer(melonDS::GPU& gpu) // mosaic table is initialized at compile-time } -u32 SoftRenderer::ColorComposite(int i, u32 val1, u32 val2) +u32 SoftRenderer::ColorComposite(int i, u32 val1, u32 val2) const { u32 coloreffect = 0; u32 eva, evb; diff --git a/src/GPU2D_Soft.h b/src/GPU2D_Soft.h index ca242a51..befb67f6 100644 --- a/src/GPU2D_Soft.h +++ b/src/GPU2D_Soft.h @@ -117,7 +117,7 @@ private: return rb | g | 0xFF000000; } - u32 ColorComposite(int i, u32 val1, u32 val2); + u32 ColorComposite(int i, u32 val1, u32 val2) const; template void DrawScanlineBGMode(u32 line); void DrawScanlineBGMode6(u32 line); diff --git a/src/GPU3D.cpp b/src/GPU3D.cpp index 049ae589..44561dfa 100644 --- a/src/GPU3D.cpp +++ b/src/GPU3D.cpp @@ -24,6 +24,7 @@ #include "FIFO.h" #include "GPU3D_Soft.h" #include "Platform.h" +#include "GPU3D.h" namespace melonDS { @@ -142,10 +143,29 @@ void MatrixLoadIdentity(s32* m); GPU3D::GPU3D(melonDS::NDS& nds, std::unique_ptr&& renderer) noexcept : NDS(nds), - CurrentRenderer(renderer ? std::move(renderer) : std::make_unique(nds.GPU)) + CurrentRenderer(renderer ? std::move(renderer) : std::make_unique()) { } +void Vertex::DoSavestate(Savestate* file) noexcept +{ + file->VarArray(Position, sizeof(Position)); + file->VarArray(Color, sizeof(Color)); + file->VarArray(TexCoords, sizeof(TexCoords)); + + file->Bool32(&Clipped); + + file->VarArray(FinalPosition, sizeof(FinalPosition)); + file->VarArray(FinalColor, sizeof(FinalColor)); + file->VarArray(HiresPosition, sizeof(HiresPosition)); +} + +void GPU3D::SetCurrentRenderer(std::unique_ptr&& renderer) noexcept +{ + CurrentRenderer = std::move(renderer); + CurrentRenderer->Reset(NDS.GPU); +} + void GPU3D::ResetRenderingState() noexcept { RenderNumPolygons = 0; @@ -172,17 +192,6 @@ void GPU3D::Reset() noexcept CmdStallQueue.Clear(); - NumCommands = 0; - CurCommand = 0; - ParamCount = 0; - TotalParams = 0; - - NumPushPopCommands = 0; - NumTestCommands = 0; - - DispCnt = 0; - AlphaRef = 0; - ZeroDotWLimit = 0; // CHECKME GXStat = 0; @@ -190,7 +199,6 @@ void GPU3D::Reset() noexcept memset(ExecParams, 0, 32*4); ExecParamCount = 0; - Timestamp = 0; CycleCount = 0; VertexPipeline = 0; NormalPipeline = 0; @@ -198,6 +206,8 @@ void GPU3D::Reset() noexcept VertexSlotCounter = 0; VertexSlotsFree = 1; + NumPushPopCommands = 0; + NumTestCommands = 0; MatrixMode = 0; @@ -215,41 +225,98 @@ void GPU3D::Reset() noexcept memset(PosMatrixStack, 0, 31 * 16*4); memset(VecMatrixStack, 0, 31 * 16*4); memset(TexMatrixStack, 0, 16*4); + ProjMatrixStackPointer = 0; PosMatrixStackPointer = 0; TexMatrixStackPointer = 0; + NumCommands = 0; + CurCommand = 0; + ParamCount = 0; + TotalParams = 0; + + GeometryEnabled = false; + RenderingEnabled = false; + + DispCnt = 0; + AlphaRefVal = 0; + AlphaRef = 0; + + memset(ToonTable, 0, sizeof(ToonTable)); + memset(EdgeTable, 0, sizeof(EdgeTable)); + + // TODO: confirm initial polyid/color/fog values + FogOffset = 0; + FogColor = 0; + memset(FogDensityTable, 0, sizeof(FogDensityTable)); + + ClearAttr1 = 0x3F000000; + ClearAttr2 = 0x00007FFF; + + ResetRenderingState(); + + AbortFrame = false; + + Timestamp = 0; + + PolygonMode = 0; + memset(CurVertex, 0, sizeof(CurVertex)); + memset(VertexColor, 0, sizeof(VertexColor)); + memset(TexCoords, 0, sizeof(TexCoords)); + memset(RawTexCoords, 0, sizeof(RawTexCoords)); + memset(Normal, 0, sizeof(Normal)); + + memset(LightDirection, 0, sizeof(LightDirection)); + memset(LightColor, 0, sizeof(LightColor)); + memset(MatDiffuse, 0, sizeof(MatDiffuse)); + memset(MatAmbient, 0, sizeof(MatAmbient)); + memset(MatSpecular, 0, sizeof(MatSpecular)); + memset(MatEmission, 0, sizeof(MatSpecular)); + + UseShininessTable = false; + memset(ShininessTable, 0, sizeof(ShininessTable)); + + PolygonAttr = 0; + CurPolygonAttr = 0; + + TexParam = 0; + TexPalette = 0; + memset(PosTestResult, 0, 4*4); memset(VecTestResult, 0, 2*3); + memset(TempVertexBuffer, 0, sizeof(TempVertexBuffer)); VertexNum = 0; VertexNumInPoly = 0; + NumConsecutivePolygons = 0; + LastStripPolygon = nullptr; + NumOpaquePolygons = 0; - CurRAMBank = 0; CurVertexRAM = &VertexRAM[0]; CurPolygonRAM = &PolygonRAM[0]; NumVertices = 0; NumPolygons = 0; - NumOpaquePolygons = 0; - - // TODO: confirm initial polyid/color/fog values - ClearAttr1 = 0x3F000000; - ClearAttr2 = 0x00007FFF; + CurRAMBank = 0; FlushRequest = 0; FlushAttributes = 0; - ResetRenderingState(); - RenderXPos = 0; - AbortFrame = false; + if (CurrentRenderer) + CurrentRenderer->Reset(NDS.GPU); } void GPU3D::DoSavestate(Savestate* file) noexcept { file->Section("GP3D"); + SoftRenderer* softRenderer = dynamic_cast(CurrentRenderer.get()); + if (softRenderer && softRenderer->IsThreaded()) + { + softRenderer->SetupRenderThread(NDS.GPU); + } + CmdFIFO.DoSavestate(file); CmdPIPE.DoSavestate(file); @@ -325,33 +392,21 @@ void GPU3D::DoSavestate(Savestate* file) noexcept file->Var32(&VertexNumInPoly); file->Var32(&NumConsecutivePolygons); - for (int i = 0; i < 4; i++) + for (Vertex& vtx : TempVertexBuffer) { - Vertex* vtx = &TempVertexBuffer[i]; - - file->VarArray(vtx->Position, sizeof(s32)*4); - file->VarArray(vtx->Color, sizeof(s32)*3); - file->VarArray(vtx->TexCoords, sizeof(s16)*2); - - file->Bool32(&vtx->Clipped); - - file->VarArray(vtx->FinalPosition, sizeof(s32)*2); - file->VarArray(vtx->FinalColor, sizeof(s32)*3); + vtx.DoSavestate(file); } if (file->Saving) { - u32 id; - if (LastStripPolygon) id = (u32)((LastStripPolygon - (&PolygonRAM[0])) / sizeof(Polygon)); - else id = -1; - file->Var32(&id); + u32 index = LastStripPolygon ? (u32)(LastStripPolygon - &PolygonRAM[0]) : UINT32_MAX; + file->Var32(&index); } else { - u32 id; - file->Var32(&id); - if (id == 0xFFFFFFFF) LastStripPolygon = NULL; - else LastStripPolygon = &PolygonRAM[id]; + u32 index = UINT32_MAX; + file->Var32(&index); + LastStripPolygon = (index == UINT32_MAX) ? nullptr : &PolygonRAM[index]; } file->Var32(&CurRAMBank); @@ -362,18 +417,9 @@ void GPU3D::DoSavestate(Savestate* file) noexcept file->Var32(&FlushRequest); file->Var32(&FlushAttributes); - for (int i = 0; i < 6144*2; i++) + for (Vertex& vtx : VertexRAM) { - Vertex* vtx = &VertexRAM[i]; - - file->VarArray(vtx->Position, sizeof(s32)*4); - file->VarArray(vtx->Color, sizeof(s32)*3); - file->VarArray(vtx->TexCoords, sizeof(s16)*2); - - file->Bool32(&vtx->Clipped); - - file->VarArray(vtx->FinalPosition, sizeof(s32)*2); - file->VarArray(vtx->FinalColor, sizeof(s32)*3); + vtx.DoSavestate(file); } for(int i = 0; i < 2048*2; i++) @@ -387,20 +433,17 @@ void GPU3D::DoSavestate(Savestate* file) noexcept for (int j = 0; j < 10; j++) { Vertex* ptr = poly->Vertices[j]; - u32 id; - if (ptr) id = (u32)((ptr - (&VertexRAM[0])) / sizeof(Vertex)); - else id = -1; - file->Var32(&id); + u32 index = ptr ? (u32)(ptr - &VertexRAM[0]) : UINT32_MAX; + file->Var32(&index); } } else { for (int j = 0; j < 10; j++) { - u32 id = -1; - file->Var32(&id); - if (id == 0xFFFFFFFF) poly->Vertices[j] = NULL; - else poly->Vertices[j] = &VertexRAM[id]; + u32 index = UINT32_MAX; + file->Var32(&index); + poly->Vertices[j] = index == UINT32_MAX ? nullptr : &VertexRAM[index]; } } @@ -448,7 +491,6 @@ void GPU3D::DoSavestate(Savestate* file) noexcept } } - // probably not worth storing the vblank-latched Renderxxxxxx variables CmdStallQueue.DoSavestate(file); file->Var32((u32*)&VertexPipeline); @@ -464,10 +506,27 @@ void GPU3D::DoSavestate(Savestate* file) noexcept CurVertexRAM = &VertexRAM[CurRAMBank ? 6144 : 0]; CurPolygonRAM = &PolygonRAM[CurRAMBank ? 2048 : 0]; + } - // better safe than sorry, I guess - // might cause a blank frame but atleast it won't shit itself - RenderNumPolygons = 0; + file->Var32(&RenderNumPolygons); + if (file->Saving) + { + for (const Polygon* p : RenderPolygonRAM) + { + u32 index = p ? (p - &PolygonRAM[0]) : UINT32_MAX; + + file->Var32(&index); + } + } + else + { + for (int i = 0; i < RenderPolygonRAM.size(); ++i) + { + u32 index = UINT32_MAX; + file->Var32(&index); + + RenderPolygonRAM[i] = index == UINT32_MAX ? nullptr : &PolygonRAM[index]; + } } file->VarArray(CurVertex, sizeof(s16)*3); @@ -487,6 +546,18 @@ void GPU3D::DoSavestate(Savestate* file) noexcept file->VarArray(ShininessTable, 128*sizeof(u8)); file->Bool32(&AbortFrame); + file->Bool32(&GeometryEnabled); + file->Bool32(&RenderingEnabled); + file->Var32(&PolygonMode); + file->Var32(&PolygonAttr); + file->Var32(&CurPolygonAttr); + file->Var32(&TexParam); + file->Var32(&TexPalette); + RenderFrameIdentical = false; + if (softRenderer && softRenderer->IsThreaded()) + { + softRenderer->EnableRenderThread(); + } } @@ -1455,7 +1526,7 @@ void GPU3D::CalculateLighting() noexcept } -void GPU3D::BoxTest(u32* params) noexcept +void GPU3D::BoxTest(const u32* params) noexcept { Vertex cube[8]; Vertex face[10]; @@ -1588,7 +1659,7 @@ void GPU3D::VecTest(u32 param) noexcept -void GPU3D::CmdFIFOWrite(CmdFIFOEntry& entry) noexcept +void GPU3D::CmdFIFOWrite(const CmdFIFOEntry& entry) noexcept { if (CmdFIFO.IsEmpty() && !CmdPIPE.IsFull()) { @@ -2329,20 +2400,20 @@ void GPU3D::CheckFIFODMA() noexcept NDS.CheckDMAs(0, 0x07); } -void GPU3D::VCount144() noexcept +void GPU3D::VCount144(GPU& gpu) noexcept { - CurrentRenderer->VCount144(); + CurrentRenderer->VCount144(gpu); } -void GPU3D::RestartFrame() noexcept +void GPU3D::RestartFrame(GPU& gpu) noexcept { - CurrentRenderer->RestartFrame(); + CurrentRenderer->RestartFrame(gpu); } -void GPU3D::Stop() noexcept +void GPU3D::Stop(const GPU& gpu) noexcept { if (CurrentRenderer) - CurrentRenderer->Stop(); + CurrentRenderer->Stop(gpu); } @@ -2435,9 +2506,9 @@ void GPU3D::VBlank() noexcept } } -void GPU3D::VCount215() noexcept +void GPU3D::VCount215(GPU& gpu) noexcept { - CurrentRenderer->RenderFrame(); + CurrentRenderer->RenderFrame(gpu); } void GPU3D::SetRenderXPos(u16 xpos) noexcept @@ -2897,10 +2968,10 @@ void GPU3D::Write32(u32 addr, u32 val) noexcept Log(LogLevel::Debug, "unknown GPU3D write32 %08X %08X\n", addr, val); } -void GPU3D::Blit() noexcept +void GPU3D::Blit(const GPU& gpu) noexcept { if (CurrentRenderer) - CurrentRenderer->Blit(); + CurrentRenderer->Blit(gpu); } Renderer3D::Renderer3D(bool Accelerated) diff --git a/src/GPU3D.h b/src/GPU3D.h index 8e743fac..f5446f34 100644 --- a/src/GPU3D.h +++ b/src/GPU3D.h @@ -47,6 +47,7 @@ struct Vertex // TODO maybe: hi-res color? (that survives clipping) s32 HiresPosition[2]; + void DoSavestate(Savestate* file) noexcept; }; struct Polygon @@ -78,6 +79,7 @@ struct Polygon u32 SortKey; + void DoSavestate(Savestate* file) noexcept; }; class Renderer3D; @@ -101,12 +103,12 @@ public: void CheckFIFOIRQ() noexcept; void CheckFIFODMA() noexcept; - void VCount144() noexcept; + void VCount144(GPU& gpu) noexcept; void VBlank() noexcept; - void VCount215() noexcept; + void VCount215(GPU& gpu) noexcept; - void RestartFrame() noexcept; - void Stop() noexcept; + void RestartFrame(GPU& gpu) noexcept; + void Stop(const GPU& gpu) noexcept; void SetRenderXPos(u16 xpos) noexcept; [[nodiscard]] u16 GetRenderXPos() const noexcept { return RenderXPos; } @@ -117,7 +119,7 @@ public: [[nodiscard]] bool IsRendererAccelerated() const noexcept; [[nodiscard]] Renderer3D& GetCurrentRenderer() noexcept { return *CurrentRenderer; } [[nodiscard]] const Renderer3D& GetCurrentRenderer() const noexcept { return *CurrentRenderer; } - void SetCurrentRenderer(std::unique_ptr&& renderer) noexcept { CurrentRenderer = std::move(renderer); } + void SetCurrentRenderer(std::unique_ptr&& renderer) noexcept; u8 Read8(u32 addr) noexcept; u16 Read16(u32 addr) noexcept; @@ -125,7 +127,7 @@ public: void Write8(u32 addr, u8 val) noexcept; void Write16(u32 addr, u16 val) noexcept; void Write32(u32 addr, u32 val) noexcept; - void Blit() noexcept; + void Blit(const GPU& gpu) noexcept; private: melonDS::NDS& NDS; typedef union @@ -147,10 +149,10 @@ private: void SubmitPolygon() noexcept; void SubmitVertex() noexcept; void CalculateLighting() noexcept; - void BoxTest(u32* params) noexcept; + void BoxTest(const u32* params) noexcept; void PosTest() noexcept; void VecTest(u32 param) noexcept; - void CmdFIFOWrite(CmdFIFOEntry& entry) noexcept; + void CmdFIFOWrite(const CmdFIFOEntry& entry) noexcept; CmdFIFOEntry CmdFIFORead() noexcept; void FinishWork(s32 cycles) noexcept; void VertexPipelineSubmitCmd() noexcept @@ -254,6 +256,7 @@ public: u32 ClearAttr1 = 0; u32 ClearAttr2 = 0; + u32 RenderDispCnt = 0; u8 RenderAlphaRef = 0; @@ -268,7 +271,7 @@ public: u32 RenderClearAttr1 = 0; u32 RenderClearAttr2 = 0; - bool RenderFrameIdentical = false; + bool RenderFrameIdentical = false; // not part of the hardware state, don't serialize bool AbortFrame = false; @@ -322,7 +325,7 @@ public: u32 FlushRequest = 0; u32 FlushAttributes = 0; - u32 ScrolledLine[256]; + u32 ScrolledLine[256]; // not part of the hardware state, don't serialize }; class Renderer3D @@ -333,20 +336,27 @@ public: Renderer3D(const Renderer3D&) = delete; Renderer3D& operator=(const Renderer3D&) = delete; - virtual void Reset() = 0; + virtual void Reset(GPU& gpu) = 0; // This "Accelerated" flag currently communicates if the framebuffer should // be allocated differently and other little misc handlers. Ideally there // are more detailed "traits" that we can ask of the Renderer3D type const bool Accelerated; - virtual void VCount144() {}; - virtual void Stop() {} - virtual void RenderFrame() = 0; - virtual void RestartFrame() {}; + virtual void VCount144(GPU& gpu) {}; + virtual void Stop(const GPU& gpu) {} + virtual void RenderFrame(GPU& gpu) = 0; + virtual void RestartFrame(GPU& gpu) {}; virtual u32* GetLine(int line) = 0; - virtual void Blit() {}; + 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..da255950 --- /dev/null +++ b/src/GPU3D_Compute.cpp @@ -0,0 +1,1137 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include "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..7544c09e --- /dev/null +++ b/src/GPU3D_Compute.h @@ -0,0 +1,242 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef 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() { 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..572f9ad6 --- /dev/null +++ b/src/GPU3D_Compute_shaders.h @@ -0,0 +1,1665 @@ +/* + Copyright 2016-2022 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef 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 bee04305..9088f078 100644 --- a/src/GPU3D_OpenGL.cpp +++ b/src/GPU3D_OpenGL.cpp @@ -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]; - sprintf(shadername, "RenderShader%02X", flags); + 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; } @@ -97,9 +85,8 @@ void SetupDefaultTexParams(GLuint tex) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } -GLRenderer::GLRenderer(GLCompositor&& compositor, melonDS::GPU& gpu) noexcept : +GLRenderer::GLRenderer(GLCompositor&& compositor) noexcept : Renderer3D(true), - GPU(gpu), CurGLCompositor(std::move(compositor)) { // GLRenderer::New() will be used to actually initialize the renderer; @@ -107,7 +94,7 @@ GLRenderer::GLRenderer(GLCompositor&& compositor, melonDS::GPU& gpu) noexcept : // so we can just let the destructor clean up a half-initialized renderer. } -std::unique_ptr GLRenderer::New(melonDS::GPU& gpu) noexcept +std::unique_ptr GLRenderer::New() noexcept { assert(glEnable != nullptr); @@ -117,7 +104,7 @@ std::unique_ptr GLRenderer::New(melonDS::GPU& gpu) noexcept // Will be returned if the initialization succeeds, // or cleaned up via RAII if it fails. - std::unique_ptr result = std::unique_ptr(new GLRenderer(std::move(*compositor), gpu)); + std::unique_ptr result = std::unique_ptr(new GLRenderer(std::move(*compositor))); compositor = std::nullopt; glEnable(GL_DEPTH_TEST); @@ -126,21 +113,17 @@ std::unique_ptr GLRenderer::New(melonDS::GPU& gpu) 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)); @@ -168,42 +151,35 @@ std::unique_ptr GLRenderer::New(melonDS::GPU& gpu) 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); @@ -256,29 +232,26 @@ std::unique_ptr GLRenderer::New(melonDS::GPU& gpu) 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); // 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); @@ -316,8 +289,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); @@ -328,12 +305,12 @@ 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]); } } -void GLRenderer::Reset() +void GLRenderer::Reset(GPU& gpu) { // This is where the compositor's Reset() method would be called, // except there's no such method right now. @@ -362,40 +339,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); @@ -406,7 +368,7 @@ void GLRenderer::SetRenderSettings(bool betterpolygons, int scale) noexcept } -void GLRenderer::SetupPolygon(GLRenderer::RendererPolygon* rp, Polygon* polygon) +void GLRenderer::SetupPolygon(GLRenderer::RendererPolygon* rp, Polygon* polygon) const { rp->PolyData = polygon; @@ -452,7 +414,7 @@ void GLRenderer::SetupPolygon(GLRenderer::RendererPolygon* rp, Polygon* polygon) } } -u32* GLRenderer::SetupVertex(Polygon* poly, int vid, Vertex* vtx, u32 vtxattr, u32* vptr) +u32* GLRenderer::SetupVertex(const Polygon* poly, int vid, const Vertex* vtx, u32 vtxattr, u32* vptr) const { u32 z = poly->FinalZ[vid]; u32 w = poly->FinalW[vid]; @@ -735,18 +697,18 @@ void GLRenderer::BuildPolygons(GLRenderer::RendererPolygon* polygons, int npolys NumEdgeIndices = eidx - EdgeIndicesOffset; } -int GLRenderer::RenderSinglePolygon(int i) +int GLRenderer::RenderSinglePolygon(int i) const { - RendererPolygon* rp = &PolygonList[i]; + const RendererPolygon* rp = &PolygonList[i]; glDrawElements(rp->PrimType, rp->NumIndices, GL_UNSIGNED_SHORT, (void*)(uintptr_t)(rp->IndicesOffset * 2)); return 1; } -int GLRenderer::RenderPolygonBatch(int i) +int GLRenderer::RenderPolygonBatch(int i) const { - RendererPolygon* rp = &PolygonList[i]; + const RendererPolygon* rp = &PolygonList[i]; GLuint primtype = rp->PrimType; u32 key = rp->RenderKey; int numpolys = 0; @@ -754,7 +716,7 @@ int GLRenderer::RenderPolygonBatch(int i) for (int iend = i; iend < NumFinalPolys; iend++) { - RendererPolygon* cur_rp = &PolygonList[iend]; + const RendererPolygon* cur_rp = &PolygonList[iend]; if (cur_rp->PrimType != primtype) break; if (cur_rp->RenderKey != key) break; @@ -766,16 +728,16 @@ int GLRenderer::RenderPolygonBatch(int i) return numpolys; } -int GLRenderer::RenderPolygonEdgeBatch(int i) +int GLRenderer::RenderPolygonEdgeBatch(int i) const { - RendererPolygon* rp = &PolygonList[i]; + const RendererPolygon* rp = &PolygonList[i]; u32 key = rp->RenderKey; int numpolys = 0; u32 numindices = 0; for (int iend = i; iend < NumFinalPolys; iend++) { - RendererPolygon* cur_rp = &PolygonList[iend]; + const RendererPolygon* cur_rp = &PolygonList[iend]; if (cur_rp->RenderKey != key) break; numpolys++; @@ -786,14 +748,14 @@ int GLRenderer::RenderPolygonEdgeBatch(int i) return numpolys; } -void GLRenderer::RenderSceneChunk(int y, int h) +void GLRenderer::RenderSceneChunk(const GPU3D& gpu3d, int y, int h) { u32 flags = 0; - if (GPU.GPU3D.RenderPolygonRAM[0]->WBuffer) flags |= RenderFlag_WBuffer; + if (gpu3d.RenderPolygonRAM[0]->WBuffer) flags |= RenderFlag_WBuffer; if (h != 192) glScissor(0, y<PolyData->IsShadow) { // shadow against clear-plane will only pass if its polyID matches that of the clear plane - u32 clrpolyid = (GPU.GPU3D.RenderClearAttr1 >> 24) & 0x3F; + u32 clrpolyid = (gpu3d.RenderClearAttr1 >> 24) & 0x3F; if (polyid != clrpolyid) { i++; continue; } glEnable(GL_BLEND); @@ -1089,7 +1051,7 @@ void GLRenderer::RenderSceneChunk(int y, int h) } } - if (GPU.GPU3D.RenderDispCnt & 0x00A0) // fog/edge enabled + if (gpu3d.RenderDispCnt & 0x00A0) // fog/edge enabled { glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glColorMaski(1, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); @@ -1104,38 +1066,38 @@ void GLRenderer::RenderSceneChunk(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); - if (GPU.GPU3D.RenderDispCnt & (1<<5)) + if (gpu3d.RenderDispCnt & (1<<5)) { // 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); glDrawArrays(GL_TRIANGLES, 0, 2*3); } - if (GPU.GPU3D.RenderDispCnt & (1<<7)) + if (gpu3d.RenderDispCnt & (1<<7)) { // fog - glUseProgram(FinalPassFogShader[2]); + glUseProgram(FinalPassFogShader); - if (GPU.GPU3D.RenderDispCnt & (1<<6)) + if (gpu3d.RenderDispCnt & (1<<6)) glBlendFuncSeparate(GL_ZERO, GL_ONE, GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_ALPHA); else glBlendFuncSeparate(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_ALPHA); { - u32 c = GPU.GPU3D.RenderFogColor; + u32 c = gpu3d.RenderFogColor; u32 r = c & 0x1F; u32 g = (c >> 5) & 0x1F; u32 b = (c >> 10) & 0x1F; @@ -1150,20 +1112,20 @@ void GLRenderer::RenderSceneChunk(int y, int h) } -void GLRenderer::RenderFrame() +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; - ShaderConfig.uDispCnt = GPU.GPU3D.RenderDispCnt; + ShaderConfig.uDispCnt = gpu.GPU3D.RenderDispCnt; for (int i = 0; i < 32; i++) { - u16 c = GPU.GPU3D.RenderToonTable[i]; + u16 c = gpu.GPU3D.RenderToonTable[i]; u32 r = c & 0x1F; u32 g = (c >> 5) & 0x1F; u32 b = (c >> 10) & 0x1F; @@ -1175,7 +1137,7 @@ void GLRenderer::RenderFrame() for (int i = 0; i < 8; i++) { - u16 c = GPU.GPU3D.RenderEdgeTable[i]; + u16 c = gpu.GPU3D.RenderEdgeTable[i]; u32 r = c & 0x1F; u32 g = (c >> 5) & 0x1F; u32 b = (c >> 10) & 0x1F; @@ -1186,7 +1148,7 @@ void GLRenderer::RenderFrame() } { - u32 c = GPU.GPU3D.RenderFogColor; + u32 c = gpu.GPU3D.RenderFogColor; u32 r = c & 0x1F; u32 g = (c >> 5) & 0x1F; u32 b = (c >> 10) & 0x1F; @@ -1200,12 +1162,12 @@ void GLRenderer::RenderFrame() for (int i = 0; i < 34; i++) { - u8 d = GPU.GPU3D.RenderFogDensityTable[i]; + u8 d = gpu.GPU3D.RenderFogDensityTable[i]; ShaderConfig.uFogDensity[i][0] = (float)d / 127.0; } - ShaderConfig.uFogOffset = GPU.GPU3D.RenderFogOffset; - ShaderConfig.uFogShift = GPU.GPU3D.RenderFogShift; + ShaderConfig.uFogOffset = gpu.GPU3D.RenderFogOffset; + ShaderConfig.uFogShift = gpu.GPU3D.RenderFogShift; glBindBuffer(GL_UNIFORM_BUFFER, ShaderConfigUBO); void* unibuf = glMapBuffer(GL_UNIFORM_BUFFER, GL_WRITE_ONLY); @@ -1218,13 +1180,13 @@ void GLRenderer::RenderFrame() glBindTexture(GL_TEXTURE_2D, TexMemID); for (int i = 0; i < 4; i++) { - u32 mask = GPU.VRAMMap_Texture[i]; + u32 mask = gpu.VRAMMap_Texture[i]; u8* vram; if (!mask) continue; - else if (mask & (1<<0)) vram = GPU.VRAM_A; - else if (mask & (1<<1)) vram = GPU.VRAM_B; - else if (mask & (1<<2)) vram = GPU.VRAM_C; - else if (mask & (1<<3)) vram = GPU.VRAM_D; + else if (mask & (1<<0)) vram = gpu.VRAM_A; + else if (mask & (1<<1)) vram = gpu.VRAM_B; + else if (mask & (1<<2)) vram = gpu.VRAM_C; + else if (mask & (1<<3)) vram = gpu.VRAM_D; glTexSubImage2D(GL_TEXTURE_2D, 0, 0, i*128, 1024, 128, GL_RED_INTEGER, GL_UNSIGNED_BYTE, vram); } @@ -1234,12 +1196,12 @@ void GLRenderer::RenderFrame() for (int i = 0; i < 6; i++) { // 6 x 16K chunks - u32 mask = GPU.VRAMMap_TexPal[i]; + u32 mask = gpu.VRAMMap_TexPal[i]; u8* vram; if (!mask) continue; - else if (mask & (1<<4)) vram = &GPU.VRAM_E[(i&3)*0x4000]; - else if (mask & (1<<5)) vram = GPU.VRAM_F; - else if (mask & (1<<6)) vram = GPU.VRAM_G; + else if (mask & (1<<4)) vram = &gpu.VRAM_E[(i&3)*0x4000]; + else if (mask & (1<<5)) vram = gpu.VRAM_F; + else if (mask & (1<<6)) vram = gpu.VRAM_G; glTexSubImage2D(GL_TEXTURE_2D, 0, 0, i*8, 1024, 8, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, vram); } @@ -1261,16 +1223,16 @@ void GLRenderer::RenderFrame() // 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; - u32 g = (GPU.GPU3D.RenderClearAttr1 >> 5) & 0x1F; - u32 b = (GPU.GPU3D.RenderClearAttr1 >> 10) & 0x1F; - u32 fog = (GPU.GPU3D.RenderClearAttr1 >> 15) & 0x1; - u32 a = (GPU.GPU3D.RenderClearAttr1 >> 16) & 0x1F; - u32 polyid = (GPU.GPU3D.RenderClearAttr1 >> 24) & 0x3F; - u32 z = ((GPU.GPU3D.RenderClearAttr2 & 0x7FFF) * 0x200) + 0x1FF; + u32 r = gpu.GPU3D.RenderClearAttr1 & 0x1F; + u32 g = (gpu.GPU3D.RenderClearAttr1 >> 5) & 0x1F; + u32 b = (gpu.GPU3D.RenderClearAttr1 >> 10) & 0x1F; + u32 fog = (gpu.GPU3D.RenderClearAttr1 >> 15) & 0x1; + u32 a = (gpu.GPU3D.RenderClearAttr1 >> 16) & 0x1F; + u32 polyid = (gpu.GPU3D.RenderClearAttr1 >> 24) & 0x3F; + u32 z = ((gpu.GPU3D.RenderClearAttr2 & 0x7FFF) * 0x200) + 0x1FF; glStencilFunc(GL_ALWAYS, 0xFF, 0xFF); glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); @@ -1289,20 +1251,20 @@ void GLRenderer::RenderFrame() glDrawArrays(GL_TRIANGLES, 0, 2*3); } - if (GPU.GPU3D.RenderNumPolygons) + if (gpu.GPU3D.RenderNumPolygons) { // render shit here u32 flags = 0; - if (GPU.GPU3D.RenderPolygonRAM[0]->WBuffer) flags |= RenderFlag_WBuffer; + if (gpu.GPU3D.RenderPolygonRAM[0]->WBuffer) flags |= RenderFlag_WBuffer; int npolys = 0; int firsttrans = -1; - for (u32 i = 0; i < GPU.GPU3D.RenderNumPolygons; i++) + for (u32 i = 0; i < gpu.GPU3D.RenderNumPolygons; i++) { - if (GPU.GPU3D.RenderPolygonRAM[i]->Degenerate) continue; + if (gpu.GPU3D.RenderPolygonRAM[i]->Degenerate) continue; - SetupPolygon(&PolygonList[npolys], GPU.GPU3D.RenderPolygonRAM[i]); - if (firsttrans < 0 && GPU.GPU3D.RenderPolygonRAM[i]->Translucent) + SetupPolygon(&PolygonList[npolys], gpu.GPU3D.RenderPolygonRAM[i]); + if (firsttrans < 0 && gpu.GPU3D.RenderPolygonRAM[i]->Translucent) firsttrans = npolys; npolys++; @@ -1319,35 +1281,36 @@ void GLRenderer::RenderFrame() glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, NumIndices * 2, IndexBuffer); glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, EdgeIndicesOffset * 2, NumEdgeIndices * 2, IndexBuffer + EdgeIndicesOffset); - RenderSceneChunk(0, 192); + RenderSceneChunk(gpu.GPU3D, 0, 192); } - - FrontBuffer = FrontBuffer ? 0 : 1; } -void GLRenderer::Stop() +void GLRenderer::Stop(const GPU& gpu) { - CurGLCompositor.Stop(GPU); + CurGLCompositor.Stop(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); } -void GLRenderer::Blit() +void GLRenderer::Blit(const GPU& gpu) { - CurGLCompositor.RenderFrame(GPU, *this); + CurGLCompositor.RenderFrame(gpu, *this); +} + +void GLRenderer::BindOutputTexture(int buffer) +{ + CurGLCompositor.BindOutputTexture(buffer); } u32* GLRenderer::GetLine(int line) @@ -1356,6 +1319,7 @@ u32* GLRenderer::GetLine(int line) 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); @@ -1375,7 +1339,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 63ee8de2..dcab6e87 100644 --- a/src/GPU3D_OpenGL.h +++ b/src/GPU3D_OpenGL.h @@ -31,7 +31,7 @@ class GLRenderer : public Renderer3D { public: ~GLRenderer() override; - void Reset() override; + void Reset(GPU& gpu) override; void SetRenderSettings(bool betterpolygons, int scale) noexcept; void SetBetterPolygons(bool betterpolygons) noexcept; @@ -39,22 +39,21 @@ public: [[nodiscard]] bool GetBetterPolygons() const noexcept { return BetterPolygons; } [[nodiscard]] int GetScaleFactor() const noexcept { return ScaleFactor; } - void VCount144() override {}; - void RenderFrame() override; - void Stop() override; + void VCount144(GPU& gpu) override {}; + void RenderFrame(GPU& gpu) override; + void Stop(const GPU& gpu) override; u32* GetLine(int line) override; - void SetupAccelFrame(); + void SetupAccelFrame() override; void PrepareCaptureFrame() override; - void Blit() 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(melonDS::GPU& gpu) noexcept; + static std::unique_ptr New() noexcept; private: // Used by New() - GLRenderer(GLCompositor&& compositor, GPU& gpu) noexcept; + GLRenderer(GLCompositor&& compositor) noexcept; // GL version requirements // * texelFetch: 3.0 (GLSL 1.30) (3.2/1.50 for MS) @@ -74,19 +73,18 @@ private: u32 RenderKey; }; - melonDS::GPU& GPU; 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); - u32* SetupVertex(Polygon* poly, int vid, Vertex* vtx, u32 vtxattr, u32* vptr); + void SetupPolygon(RendererPolygon* rp, Polygon* polygon) const; + u32* SetupVertex(const Polygon* poly, int vid, const Vertex* vtx, u32 vtxattr, u32* vptr) const; void BuildPolygons(RendererPolygon* polygons, int npolys); - int RenderSinglePolygon(int i); - int RenderPolygonBatch(int i); - int RenderPolygonEdgeBatch(int i); - void RenderSceneChunk(int y, int h); + int RenderSinglePolygon(int i) const; + int RenderPolygonBatch(int i) const; + int RenderPolygonEdgeBatch(int i) const; + void RenderSceneChunk(const GPU3D& gpu3d, int y, int h); enum { @@ -97,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 @@ -156,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_Soft.cpp b/src/GPU3D_Soft.cpp index 03c6265e..a8da14cd 100644 --- a/src/GPU3D_Soft.cpp +++ b/src/GPU3D_Soft.cpp @@ -34,35 +34,52 @@ void SoftRenderer::StopRenderThread() { if (RenderThreadRunning.load(std::memory_order_relaxed)) { + // Tell the render thread to stop drawing new frames, and finish up the current one. RenderThreadRunning = false; + Platform::Semaphore_Post(Sema_RenderStart); + Platform::Thread_Wait(RenderThread); Platform::Thread_Free(RenderThread); RenderThread = nullptr; } } -void SoftRenderer::SetupRenderThread() +void SoftRenderer::SetupRenderThread(GPU& gpu) { if (Threaded) { if (!RenderThreadRunning.load(std::memory_order_relaxed)) - { - RenderThreadRunning = true; - RenderThread = Platform::Thread_Create(std::bind(&SoftRenderer::RenderThreadFunc, this)); + { // If the render thread isn't already running... + RenderThreadRunning = true; // "Time for work, render thread!" + RenderThread = Platform::Thread_Create([this, &gpu]() { + RenderThreadFunc(gpu); + }); } - // otherwise more than one frame can be queued up at once + // "Be on standby, but don't start rendering until I tell you to!" Platform::Semaphore_Reset(Sema_RenderStart); + // "Oh, sorry, were you already in the middle of a frame from the last iteration?" if (RenderThreadRendering) + // "Tell me when you're done, I'll wait here." Platform::Semaphore_Wait(Sema_RenderDone); - Platform::Semaphore_Reset(Sema_RenderDone); - Platform::Semaphore_Reset(Sema_RenderStart); - Platform::Semaphore_Reset(Sema_ScanlineCount); + // "All good? Okay, let me give you your training." + // "(Maybe you're still the same thread, but I have to tell you this stuff anyway.)" - Platform::Semaphore_Post(Sema_RenderStart); + // "This is the signal you'll send when you're done with a frame." + // "I'll listen for it when I need to show something to the frontend." + Platform::Semaphore_Reset(Sema_RenderDone); + + // "This is the signal I'll send when I want you to start rendering." + // "Don't do anything until you get the message." + Platform::Semaphore_Reset(Sema_RenderStart); + + // "This is the signal you'll send every time you finish drawing a line." + // "I might need some of your scanlines before you finish the whole buffer," + // "so let me know as soon as you're done with each one." + Platform::Semaphore_Reset(Sema_ScanlineCount); } else { @@ -70,9 +87,16 @@ void SoftRenderer::SetupRenderThread() } } +void SoftRenderer::EnableRenderThread() +{ + if (Threaded && Sema_RenderStart) + { + Platform::Semaphore_Post(Sema_RenderStart); + } +} -SoftRenderer::SoftRenderer(melonDS::GPU& gpu, bool threaded) noexcept - : Renderer3D(false), GPU(gpu), Threaded(threaded) +SoftRenderer::SoftRenderer() noexcept + : Renderer3D(false) { Sema_RenderStart = Platform::Semaphore_Create(); Sema_RenderDone = Platform::Semaphore_Create(); @@ -92,7 +116,7 @@ SoftRenderer::~SoftRenderer() Platform::Semaphore_Free(Sema_ScanlineCount); } -void SoftRenderer::Reset() +void SoftRenderer::Reset(GPU& gpu) { memset(ColorBuffer, 0, BufferSize * 2 * 4); memset(DepthBuffer, 0, BufferSize * 2 * 4); @@ -100,19 +124,21 @@ void SoftRenderer::Reset() PrevIsShadowMask = false; - SetupRenderThread(); + SetupRenderThread(gpu); + EnableRenderThread(); } -void SoftRenderer::SetThreaded(bool threaded) noexcept +void SoftRenderer::SetThreaded(bool threaded, GPU& gpu) noexcept { if (Threaded != threaded) { Threaded = threaded; - SetupRenderThread(); + SetupRenderThread(gpu); + EnableRenderThread(); } } -void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* color, u8* alpha) +void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s, s16 t, u16* color, u8* alpha) const { u32 vramaddr = (texparam & 0xFFFF) << 3; @@ -167,10 +193,10 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co case 1: // A3I5 { vramaddr += ((t * width) + s); - u8 pixel = ReadVRAM_Texture(vramaddr); + u8 pixel = ReadVRAM_Texture(vramaddr, gpu); texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + ((pixel&0x1F)<<1)); + *color = ReadVRAM_TexPal(texpal + ((pixel&0x1F)<<1), gpu); *alpha = ((pixel >> 3) & 0x1C) + (pixel >> 6); } break; @@ -178,12 +204,12 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co case 2: // 4-color { vramaddr += (((t * width) + s) >> 2); - u8 pixel = ReadVRAM_Texture(vramaddr); + u8 pixel = ReadVRAM_Texture(vramaddr, gpu); pixel >>= ((s & 0x3) << 1); pixel &= 0x3; texpal <<= 3; - *color = ReadVRAM_TexPal(texpal + (pixel<<1)); + *color = ReadVRAM_TexPal(texpal + (pixel<<1), gpu); *alpha = (pixel==0) ? alpha0 : 31; } break; @@ -191,12 +217,12 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co case 3: // 16-color { vramaddr += (((t * width) + s) >> 1); - u8 pixel = ReadVRAM_Texture(vramaddr); + u8 pixel = ReadVRAM_Texture(vramaddr, gpu); if (s & 0x1) pixel >>= 4; else pixel &= 0xF; texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + (pixel<<1)); + *color = ReadVRAM_TexPal(texpal + (pixel<<1), gpu); *alpha = (pixel==0) ? alpha0 : 31; } break; @@ -204,10 +230,10 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co case 4: // 256-color { vramaddr += ((t * width) + s); - u8 pixel = ReadVRAM_Texture(vramaddr); + u8 pixel = ReadVRAM_Texture(vramaddr, gpu); texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + (pixel<<1)); + *color = ReadVRAM_TexPal(texpal + (pixel<<1), gpu); *alpha = (pixel==0) ? alpha0 : 31; } break; @@ -221,30 +247,30 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co if (vramaddr >= 0x40000) slot1addr += 0x10000; - u8 val = ReadVRAM_Texture(vramaddr); + u8 val = ReadVRAM_Texture(vramaddr, gpu); val >>= (2 * (s & 0x3)); - u16 palinfo = ReadVRAM_Texture(slot1addr); + u16 palinfo = ReadVRAM_Texture(slot1addr, gpu); u32 paloffset = (palinfo & 0x3FFF) << 2; texpal <<= 4; switch (val & 0x3) { case 0: - *color = ReadVRAM_TexPal(texpal + paloffset); + *color = ReadVRAM_TexPal(texpal + paloffset, gpu); *alpha = 31; break; case 1: - *color = ReadVRAM_TexPal(texpal + paloffset + 2); + *color = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); *alpha = 31; break; case 2: if ((palinfo >> 14) == 1) { - u16 color0 = ReadVRAM_TexPal(texpal + paloffset); - u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2); + u16 color0 = ReadVRAM_TexPal(texpal + paloffset, gpu); + u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); u32 r0 = color0 & 0x001F; u32 g0 = color0 & 0x03E0; @@ -261,8 +287,8 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co } else if ((palinfo >> 14) == 3) { - u16 color0 = ReadVRAM_TexPal(texpal + paloffset); - u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2); + u16 color0 = ReadVRAM_TexPal(texpal + paloffset, gpu); + u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); u32 r0 = color0 & 0x001F; u32 g0 = color0 & 0x03E0; @@ -278,20 +304,20 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co *color = r | g | b; } else - *color = ReadVRAM_TexPal(texpal + paloffset + 4); + *color = ReadVRAM_TexPal(texpal + paloffset + 4, gpu); *alpha = 31; break; case 3: if ((palinfo >> 14) == 2) { - *color = ReadVRAM_TexPal(texpal + paloffset + 6); + *color = ReadVRAM_TexPal(texpal + paloffset + 6, gpu); *alpha = 31; } else if ((palinfo >> 14) == 3) { - u16 color0 = ReadVRAM_TexPal(texpal + paloffset); - u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2); + u16 color0 = ReadVRAM_TexPal(texpal + paloffset, gpu); + u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); u32 r0 = color0 & 0x001F; u32 g0 = color0 & 0x03E0; @@ -320,10 +346,10 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co case 6: // A5I3 { vramaddr += ((t * width) + s); - u8 pixel = ReadVRAM_Texture(vramaddr); + u8 pixel = ReadVRAM_Texture(vramaddr, gpu); texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + ((pixel&0x7)<<1)); + *color = ReadVRAM_TexPal(texpal + ((pixel&0x7)<<1), gpu); *alpha = (pixel >> 3); } break; @@ -331,7 +357,7 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co case 7: // direct color { vramaddr += (((t * width) + s) << 1); - *color = ReadVRAM_Texture(vramaddr); + *color = ReadVRAM_Texture(vramaddr, gpu); *alpha = (*color & 0x8000) ? 31 : 0; } break; @@ -388,7 +414,7 @@ bool DepthTest_LessThan_FrontFacing(s32 dstz, s32 z, u32 dstattr) return false; } -u32 SoftRenderer::AlphaBlend(u32 srccolor, u32 dstcolor, u32 alpha) noexcept +u32 SoftRenderer::AlphaBlend(const GPU3D& gpu3d, u32 srccolor, u32 dstcolor, u32 alpha) const noexcept { u32 dstalpha = dstcolor >> 24; @@ -399,7 +425,7 @@ u32 SoftRenderer::AlphaBlend(u32 srccolor, u32 dstcolor, u32 alpha) noexcept u32 srcG = (srccolor >> 8) & 0x3F; u32 srcB = (srccolor >> 16) & 0x3F; - if (GPU.GPU3D.RenderDispCnt & (1<<3)) + if (gpu3d.RenderDispCnt & (1<<3)) { u32 dstR = dstcolor & 0x3F; u32 dstG = (dstcolor >> 8) & 0x3F; @@ -418,7 +444,7 @@ u32 SoftRenderer::AlphaBlend(u32 srccolor, u32 dstcolor, u32 alpha) noexcept return srcR | (srcG << 8) | (srcB << 16) | (dstalpha << 24); } -u32 SoftRenderer::RenderPixel(Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 t) +u32 SoftRenderer::RenderPixel(const GPU& gpu, const Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 t) const { u8 r, g, b, a; @@ -428,7 +454,7 @@ u32 SoftRenderer::RenderPixel(Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 if (blendmode == 2) { - if (GPU.GPU3D.RenderDispCnt & (1<<1)) + if (gpu.GPU3D.RenderDispCnt & (1<<1)) { // highlight mode: color is calculated normally // except all vertex color components are set @@ -442,7 +468,7 @@ u32 SoftRenderer::RenderPixel(Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 { // toon mode: vertex color is replaced by toon color - u16 tooncolor = GPU.GPU3D.RenderToonTable[vr >> 1]; + u16 tooncolor = gpu.GPU3D.RenderToonTable[vr >> 1]; vr = (tooncolor << 1) & 0x3E; if (vr) vr++; vg = (tooncolor >> 4) & 0x3E; if (vg) vg++; @@ -450,12 +476,12 @@ u32 SoftRenderer::RenderPixel(Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 } } - if ((GPU.GPU3D.RenderDispCnt & (1<<0)) && (((polygon->TexParam >> 26) & 0x7) != 0)) + if ((gpu.GPU3D.RenderDispCnt & (1<<0)) && (((polygon->TexParam >> 26) & 0x7) != 0)) { u8 tr, tg, tb; u16 tcolor; u8 talpha; - TextureLookup(polygon->TexParam, polygon->TexPalette, s, t, &tcolor, &talpha); + TextureLookup(gpu, polygon->TexParam, polygon->TexPalette, s, t, &tcolor, &talpha); tr = (tcolor << 1) & 0x3E; if (tr) tr++; tg = (tcolor >> 4) & 0x3E; if (tg) tg++; @@ -503,9 +529,9 @@ u32 SoftRenderer::RenderPixel(Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 a = polyalpha; } - if ((blendmode == 2) && (GPU.GPU3D.RenderDispCnt & (1<<1))) + if ((blendmode == 2) && (gpu.GPU3D.RenderDispCnt & (1<<1))) { - u16 tooncolor = GPU.GPU3D.RenderToonTable[vr >> 1]; + u16 tooncolor = gpu.GPU3D.RenderToonTable[vr >> 1]; vr = (tooncolor << 1) & 0x3E; if (vr) vr++; vg = (tooncolor >> 4) & 0x3E; if (vg) vg++; @@ -526,7 +552,7 @@ u32 SoftRenderer::RenderPixel(Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 return r | (g << 8) | (b << 16) | (a << 24); } -void SoftRenderer::PlotTranslucentPixel(u32 pixeladdr, u32 color, u32 z, u32 polyattr, u32 shadow) +void SoftRenderer::PlotTranslucentPixel(const GPU3D& gpu3d, u32 pixeladdr, u32 color, u32 z, u32 polyattr, u32 shadow) { u32 dstattr = AttrBuffer[pixeladdr]; u32 attr = (polyattr & 0xE0F0) | ((polyattr >> 8) & 0xFF0000) | (1<<22) | (dstattr & 0xFF001F0F); @@ -556,7 +582,7 @@ void SoftRenderer::PlotTranslucentPixel(u32 pixeladdr, u32 color, u32 z, u32 pol if (!(dstattr & (1<<15))) attr &= ~(1<<15); - color = AlphaBlend(color, ColorBuffer[pixeladdr], color>>24); + color = AlphaBlend(gpu3d, color, ColorBuffer[pixeladdr], color>>24); if (z != -1) DepthBuffer[pixeladdr] = z; @@ -565,7 +591,7 @@ void SoftRenderer::PlotTranslucentPixel(u32 pixeladdr, u32 color, u32 z, u32 pol AttrBuffer[pixeladdr] = attr; } -void SoftRenderer::SetupPolygonLeftEdge(SoftRenderer::RendererPolygon* rp, s32 y) +void SoftRenderer::SetupPolygonLeftEdge(SoftRenderer::RendererPolygon* rp, s32 y) const { Polygon* polygon = rp->PolyData; @@ -592,7 +618,7 @@ void SoftRenderer::SetupPolygonLeftEdge(SoftRenderer::RendererPolygon* rp, s32 y polygon->FinalW[rp->CurVL], polygon->FinalW[rp->NextVL], y); } -void SoftRenderer::SetupPolygonRightEdge(SoftRenderer::RendererPolygon* rp, s32 y) +void SoftRenderer::SetupPolygonRightEdge(SoftRenderer::RendererPolygon* rp, s32 y) const { Polygon* polygon = rp->PolyData; @@ -619,7 +645,7 @@ void SoftRenderer::SetupPolygonRightEdge(SoftRenderer::RendererPolygon* rp, s32 polygon->FinalW[rp->CurVR], polygon->FinalW[rp->NextVR], y); } -void SoftRenderer::SetupPolygon(SoftRenderer::RendererPolygon* rp, Polygon* polygon) +void SoftRenderer::SetupPolygon(SoftRenderer::RendererPolygon* rp, Polygon* polygon) const { u32 nverts = polygon->NumVertices; @@ -672,7 +698,7 @@ void SoftRenderer::SetupPolygon(SoftRenderer::RendererPolygon* rp, Polygon* poly } } -void SoftRenderer::RenderShadowMaskScanline(RendererPolygon* rp, s32 y) +void SoftRenderer::RenderShadowMaskScanline(const GPU3D& gpu3d, RendererPolygon* rp, s32 y) { Polygon* polygon = rp->PolyData; @@ -749,7 +775,7 @@ void SoftRenderer::RenderShadowMaskScanline(RendererPolygon* rp, s32 y) std::swap(zl, zr); // CHECKME: edge fill rules for swapped opaque shadow mask polygons - if ((GPU.GPU3D.RenderDispCnt & ((1<<4)|(1<<5))) || ((polyalpha < 31) && (GPU.GPU3D.RenderDispCnt & (1<<3))) || wireframe) + if ((gpu3d.RenderDispCnt & ((1<<4)|(1<<5))) || ((polyalpha < 31) && (gpu3d.RenderDispCnt & (1<<3))) || wireframe) { l_filledge = true; r_filledge = true; @@ -777,7 +803,7 @@ void SoftRenderer::RenderShadowMaskScanline(RendererPolygon* rp, s32 y) rp->SlopeR.EdgeParams(&r_edgelen, &r_edgecov); // CHECKME: edge fill rules for unswapped opaque shadow mask polygons - if ((GPU.GPU3D.RenderDispCnt & ((1<<4)|(1<<5))) || ((polyalpha < 31) && (GPU.GPU3D.RenderDispCnt & (1<<3))) || wireframe) + if ((gpu3d.RenderDispCnt & ((1<<4)|(1<<5))) || ((polyalpha < 31) && (gpu3d.RenderDispCnt & (1<<3))) || wireframe) { l_filledge = true; r_filledge = true; @@ -798,7 +824,7 @@ void SoftRenderer::RenderShadowMaskScanline(RendererPolygon* rp, s32 y) // similarly, we can perform alpha test early (checkme) if (wireframe) polyalpha = 31; - if (polyalpha <= GPU.GPU3D.RenderAlphaRef) return; + if (polyalpha <= gpu3d.RenderAlphaRef) return; // in wireframe mode, there are special rules for equal Z (TODO) @@ -900,7 +926,7 @@ void SoftRenderer::RenderShadowMaskScanline(RendererPolygon* rp, s32 y) rp->XR = rp->SlopeR.Step(); } -void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) +void SoftRenderer::RenderPolygonScanline(const GPU& gpu, RendererPolygon* rp, s32 y) { Polygon* polygon = rp->PolyData; @@ -984,7 +1010,7 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) // edges are always filled if antialiasing/edgemarking are enabled, // if the pixels are translucent and alpha blending is enabled, or if the polygon is wireframe // checkme: do swapped line polygons exist? - if ((GPU.GPU3D.RenderDispCnt & ((1<<4)|(1<<5))) || ((polyalpha < 31) && (GPU.GPU3D.RenderDispCnt & (1<<3))) || wireframe) + if ((gpu.GPU3D.RenderDispCnt & ((1<<4)|(1<<5))) || ((polyalpha < 31) && (gpu.GPU3D.RenderDispCnt & (1<<3))) || wireframe) { l_filledge = true; r_filledge = true; @@ -1019,7 +1045,7 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) // * edges are filled if both sides are identical and fully overlapping // edges are always filled if antialiasing/edgemarking are enabled, // if the pixels are translucent and alpha blending is enabled, or if the polygon is wireframe - if ((GPU.GPU3D.RenderDispCnt & ((1<<4)|(1<<5))) || ((polyalpha < 31) && (GPU.GPU3D.RenderDispCnt & (1<<3))) || wireframe) + if ((gpu.GPU3D.RenderDispCnt & ((1<<4)|(1<<5))) || ((polyalpha < 31) && (gpu.GPU3D.RenderDispCnt & (1<<3))) || wireframe) { l_filledge = true; r_filledge = true; @@ -1118,17 +1144,17 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) s16 s = interpX.Interpolate(sl, sr); s16 t = interpX.Interpolate(tl, tr); - u32 color = RenderPixel(polygon, vr>>3, vg>>3, vb>>3, s, t); + u32 color = RenderPixel(gpu, polygon, vr>>3, vg>>3, vb>>3, s, t); u8 alpha = color >> 24; // alpha test - if (alpha <= GPU.GPU3D.RenderAlphaRef) continue; + if (alpha <= gpu.GPU3D.RenderAlphaRef) continue; if (alpha == 31) { u32 attr = polyattr | edge; - if (GPU.GPU3D.RenderDispCnt & (1<<4)) + if (gpu.GPU3D.RenderDispCnt & (1<<4)) { // anti-aliasing: all edges are rendered @@ -1158,11 +1184,11 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) else { if (!(polygon->Attr & (1<<11))) z = -1; - PlotTranslucentPixel(pixeladdr, color, z, polyattr, polygon->IsShadow); + PlotTranslucentPixel(gpu.GPU3D, pixeladdr, color, z, polyattr, polygon->IsShadow); // blend with bottom pixel too, if needed if ((dstattr & 0xF) && (pixeladdr < BufferSize)) - PlotTranslucentPixel(pixeladdr+BufferSize, color, z, polyattr, polygon->IsShadow); + PlotTranslucentPixel(gpu.GPU3D, pixeladdr+BufferSize, color, z, polyattr, polygon->IsShadow); } } @@ -1214,17 +1240,17 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) s16 s = interpX.Interpolate(sl, sr); s16 t = interpX.Interpolate(tl, tr); - u32 color = RenderPixel(polygon, vr>>3, vg>>3, vb>>3, s, t); + u32 color = RenderPixel(gpu, polygon, vr>>3, vg>>3, vb>>3, s, t); u8 alpha = color >> 24; // alpha test - if (alpha <= GPU.GPU3D.RenderAlphaRef) continue; + if (alpha <= gpu.GPU3D.RenderAlphaRef) continue; if (alpha == 31) { u32 attr = polyattr | edge; - if ((GPU.GPU3D.RenderDispCnt & (1<<4)) && (attr & 0xF)) + if ((gpu.GPU3D.RenderDispCnt & (1<<4)) && (attr & 0xF)) { // anti-aliasing: all edges are rendered @@ -1247,11 +1273,11 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) else { if (!(polygon->Attr & (1<<11))) z = -1; - PlotTranslucentPixel(pixeladdr, color, z, polyattr, polygon->IsShadow); + PlotTranslucentPixel(gpu.GPU3D, pixeladdr, color, z, polyattr, polygon->IsShadow); // blend with bottom pixel too, if needed if ((dstattr & 0xF) && (pixeladdr < BufferSize)) - PlotTranslucentPixel(pixeladdr+BufferSize, color, z, polyattr, polygon->IsShadow); + PlotTranslucentPixel(gpu.GPU3D, pixeladdr+BufferSize, color, z, polyattr, polygon->IsShadow); } } @@ -1306,17 +1332,17 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) s16 s = interpX.Interpolate(sl, sr); s16 t = interpX.Interpolate(tl, tr); - u32 color = RenderPixel(polygon, vr>>3, vg>>3, vb>>3, s, t); + u32 color = RenderPixel(gpu, polygon, vr>>3, vg>>3, vb>>3, s, t); u8 alpha = color >> 24; // alpha test - if (alpha <= GPU.GPU3D.RenderAlphaRef) continue; + if (alpha <= gpu.GPU3D.RenderAlphaRef) continue; if (alpha == 31) { u32 attr = polyattr | edge; - if (GPU.GPU3D.RenderDispCnt & (1<<4)) + if (gpu.GPU3D.RenderDispCnt & (1<<4)) { // anti-aliasing: all edges are rendered @@ -1346,11 +1372,11 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) else { if (!(polygon->Attr & (1<<11))) z = -1; - PlotTranslucentPixel(pixeladdr, color, z, polyattr, polygon->IsShadow); + PlotTranslucentPixel(gpu.GPU3D, pixeladdr, color, z, polyattr, polygon->IsShadow); // blend with bottom pixel too, if needed if ((dstattr & 0xF) && (pixeladdr < BufferSize)) - PlotTranslucentPixel(pixeladdr+BufferSize, color, z, polyattr, polygon->IsShadow); + PlotTranslucentPixel(gpu.GPU3D, pixeladdr+BufferSize, color, z, polyattr, polygon->IsShadow); } } @@ -1358,7 +1384,7 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) rp->XR = rp->SlopeR.Step(); } -void SoftRenderer::RenderScanline(s32 y, int npolys) +void SoftRenderer::RenderScanline(const GPU& gpu, s32 y, int npolys) { for (int i = 0; i < npolys; i++) { @@ -1368,19 +1394,19 @@ void SoftRenderer::RenderScanline(s32 y, int npolys) if (y >= polygon->YTop && (y < polygon->YBottom || (y == polygon->YTop && polygon->YBottom == polygon->YTop))) { if (polygon->IsShadowMask) - RenderShadowMaskScanline(rp, y); + RenderShadowMaskScanline(gpu.GPU3D, rp, y); else - RenderPolygonScanline(rp, y); + RenderPolygonScanline(gpu, rp, y); } } } -u32 SoftRenderer::CalculateFogDensity(u32 pixeladdr) +u32 SoftRenderer::CalculateFogDensity(const GPU3D& gpu3d, u32 pixeladdr) const { u32 z = DepthBuffer[pixeladdr]; u32 densityid, densityfrac; - if (z < GPU.GPU3D.RenderFogOffset) + if (z < gpu3d.RenderFogOffset) { densityid = 0; densityfrac = 0; @@ -1392,8 +1418,8 @@ u32 SoftRenderer::CalculateFogDensity(u32 pixeladdr) // on hardware, the final value can overflow the 32-bit range with a shift big enough, // causing fog to 'wrap around' and accidentally apply to larger Z ranges - z -= GPU.GPU3D.RenderFogOffset; - z = (z >> 2) << GPU.GPU3D.RenderFogShift; + z -= gpu3d.RenderFogOffset; + z = (z >> 2) << gpu3d.RenderFogShift; densityid = z >> 17; if (densityid >= 32) @@ -1407,20 +1433,20 @@ u32 SoftRenderer::CalculateFogDensity(u32 pixeladdr) // checkme (may be too precise?) u32 density = - ((GPU.GPU3D.RenderFogDensityTable[densityid] * (0x20000-densityfrac)) + - (GPU.GPU3D.RenderFogDensityTable[densityid+1] * densityfrac)) >> 17; + ((gpu3d.RenderFogDensityTable[densityid] * (0x20000-densityfrac)) + + (gpu3d.RenderFogDensityTable[densityid+1] * densityfrac)) >> 17; if (density >= 127) density = 128; return density; } -void SoftRenderer::ScanlineFinalPass(s32 y) +void SoftRenderer::ScanlineFinalPass(const GPU3D& gpu3d, s32 y) { // to consider: // clearing all polygon fog flags if the master flag isn't set? // merging all final pass loops into one? - if (GPU.GPU3D.RenderDispCnt & (1<<5)) + if (gpu3d.RenderDispCnt & (1<<5)) { // edge marking // only applied to topmost pixels @@ -1440,7 +1466,7 @@ void SoftRenderer::ScanlineFinalPass(s32 y) ((polyid != (AttrBuffer[pixeladdr-ScanlineWidth] >> 24)) && (z < DepthBuffer[pixeladdr-ScanlineWidth])) || ((polyid != (AttrBuffer[pixeladdr+ScanlineWidth] >> 24)) && (z < DepthBuffer[pixeladdr+ScanlineWidth]))) { - u16 edgecolor = GPU.GPU3D.RenderEdgeTable[polyid >> 3]; + u16 edgecolor = gpu3d.RenderEdgeTable[polyid >> 3]; u32 edgeR = (edgecolor << 1) & 0x3E; if (edgeR) edgeR++; u32 edgeG = (edgecolor >> 4) & 0x3E; if (edgeG) edgeG++; u32 edgeB = (edgecolor >> 9) & 0x3E; if (edgeB) edgeB++; @@ -1453,7 +1479,7 @@ void SoftRenderer::ScanlineFinalPass(s32 y) } } - if (GPU.GPU3D.RenderDispCnt & (1<<7)) + if (gpu3d.RenderDispCnt & (1<<7)) { // fog @@ -1466,12 +1492,12 @@ void SoftRenderer::ScanlineFinalPass(s32 y) // TODO: check the 'fog alpha glitch with small Z' GBAtek talks about - bool fogcolor = !(GPU.GPU3D.RenderDispCnt & (1<<6)); + bool fogcolor = !(gpu3d.RenderDispCnt & (1<<6)); - 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; + u32 fogR = (gpu3d.RenderFogColor << 1) & 0x3E; if (fogR) fogR++; + u32 fogG = (gpu3d.RenderFogColor >> 4) & 0x3E; if (fogG) fogG++; + u32 fogB = (gpu3d.RenderFogColor >> 9) & 0x3E; if (fogB) fogB++; + u32 fogA = (gpu3d.RenderFogColor >> 16) & 0x1F; for (int x = 0; x < 256; x++) { @@ -1481,7 +1507,7 @@ void SoftRenderer::ScanlineFinalPass(s32 y) u32 attr = AttrBuffer[pixeladdr]; if (attr & (1<<15)) { - density = CalculateFogDensity(pixeladdr); + density = CalculateFogDensity(gpu3d, pixeladdr); srccolor = ColorBuffer[pixeladdr]; srcR = srccolor & 0x3F; @@ -1510,7 +1536,7 @@ void SoftRenderer::ScanlineFinalPass(s32 y) attr = AttrBuffer[pixeladdr]; if (!(attr & (1<<15))) continue; - density = CalculateFogDensity(pixeladdr); + density = CalculateFogDensity(gpu3d, pixeladdr); srccolor = ColorBuffer[pixeladdr]; srcR = srccolor & 0x3F; @@ -1531,7 +1557,7 @@ void SoftRenderer::ScanlineFinalPass(s32 y) } } - if (GPU.GPU3D.RenderDispCnt & (1<<4)) + if (gpu3d.RenderDispCnt & (1<<4)) { // anti-aliasing @@ -1584,10 +1610,10 @@ void SoftRenderer::ScanlineFinalPass(s32 y) } } -void SoftRenderer::ClearBuffers() +void SoftRenderer::ClearBuffers(const GPU& gpu) { - u32 clearz = ((GPU.GPU3D.RenderClearAttr2 & 0x7FFF) * 0x200) + 0x1FF; - u32 polyid = GPU.GPU3D.RenderClearAttr1 & 0x3F000000; // this sets the opaque polygonID + u32 clearz = ((gpu.GPU3D.RenderClearAttr2 & 0x7FFF) * 0x200) + 0x1FF; + u32 polyid = gpu.GPU3D.RenderClearAttr1 & 0x3F000000; // this sets the opaque polygonID // fill screen borders for edge marking @@ -1617,17 +1643,17 @@ void SoftRenderer::ClearBuffers() // clear the screen - if (GPU.GPU3D.RenderDispCnt & (1<<14)) + if (gpu.GPU3D.RenderDispCnt & (1<<14)) { - u8 xoff = (GPU.GPU3D.RenderClearAttr2 >> 16) & 0xFF; - u8 yoff = (GPU.GPU3D.RenderClearAttr2 >> 24) & 0xFF; + u8 xoff = (gpu.GPU3D.RenderClearAttr2 >> 16) & 0xFF; + u8 yoff = (gpu.GPU3D.RenderClearAttr2 >> 24) & 0xFF; for (int y = 0; y < ScanlineWidth*192; y+=ScanlineWidth) { for (int x = 0; x < 256; x++) { - u16 val2 = ReadVRAM_Texture(0x40000 + (yoff << 9) + (xoff << 1)); - u16 val3 = ReadVRAM_Texture(0x60000 + (yoff << 9) + (xoff << 1)); + u16 val2 = ReadVRAM_Texture(0x40000 + (yoff << 9) + (xoff << 1), gpu); + u16 val3 = ReadVRAM_Texture(0x60000 + (yoff << 9) + (xoff << 1), gpu); // TODO: confirm color conversion u32 r = (val2 << 1) & 0x3E; if (r) r++; @@ -1652,13 +1678,13 @@ void SoftRenderer::ClearBuffers() else { // TODO: confirm color conversion - 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; + 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; u32 color = r | (g << 8) | (b << 16) | (a << 24); - polyid |= (GPU.GPU3D.RenderClearAttr1 & 0x8000); + polyid |= (gpu.GPU3D.RenderClearAttr1 & 0x8000); for (int y = 0; y < ScanlineWidth*192; y+=ScanlineWidth) { @@ -1673,7 +1699,7 @@ void SoftRenderer::ClearBuffers() } } -void SoftRenderer::RenderPolygons(bool threaded, Polygon** polygons, int npolys) +void SoftRenderer::RenderPolygons(const GPU& gpu, bool threaded, Polygon** polygons, int npolys) { int j = 0; for (int i = 0; i < npolys; i++) @@ -1682,74 +1708,88 @@ void SoftRenderer::RenderPolygons(bool threaded, Polygon** polygons, int npolys) SetupPolygon(&PolygonList[j++], polygons[i]); } - RenderScanline(0, j); + RenderScanline(gpu, 0, j); for (s32 y = 1; y < 192; y++) { - RenderScanline(y, j); - ScanlineFinalPass(y-1); + RenderScanline(gpu, y, j); + ScanlineFinalPass(gpu.GPU3D, y-1); if (threaded) + // Notify the main thread that we're done with a scanline. Platform::Semaphore_Post(Sema_ScanlineCount); } - ScanlineFinalPass(191); + ScanlineFinalPass(gpu.GPU3D, 191); if (threaded) + // If this renderer is threaded, notify the main thread that we're done with the frame. Platform::Semaphore_Post(Sema_ScanlineCount); } -void SoftRenderer::VCount144() +void SoftRenderer::VCount144(GPU& gpu) { - if (RenderThreadRunning.load(std::memory_order_relaxed) && !GPU.GPU3D.AbortFrame) + if (RenderThreadRunning.load(std::memory_order_relaxed) && !gpu.GPU3D.AbortFrame) Platform::Semaphore_Wait(Sema_RenderDone); } -void SoftRenderer::RenderFrame() +void SoftRenderer::RenderFrame(GPU& gpu) { - auto textureDirty = GPU.VRAMDirty_Texture.DeriveState(GPU.VRAMMap_Texture, GPU); - auto texPalDirty = GPU.VRAMDirty_TexPal.DeriveState(GPU.VRAMMap_TexPal, 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); + bool textureChanged = gpu.MakeVRAMFlat_TextureCoherent(textureDirty); + bool texPalChanged = gpu.MakeVRAMFlat_TexPalCoherent(texPalDirty); - FrameIdentical = !(textureChanged || texPalChanged) && GPU.GPU3D.RenderFrameIdentical; + FrameIdentical = !(textureChanged || texPalChanged) && gpu.GPU3D.RenderFrameIdentical; if (RenderThreadRunning.load(std::memory_order_relaxed)) { + // "Render thread, you're up! Get moving." Platform::Semaphore_Post(Sema_RenderStart); } else if (!FrameIdentical) { - ClearBuffers(); - RenderPolygons(false, &GPU.GPU3D.RenderPolygonRAM[0], GPU.GPU3D.RenderNumPolygons); + ClearBuffers(gpu); + RenderPolygons(gpu, false, &gpu.GPU3D.RenderPolygonRAM[0], gpu.GPU3D.RenderNumPolygons); } } -void SoftRenderer::RestartFrame() +void SoftRenderer::RestartFrame(GPU& gpu) { - SetupRenderThread(); + SetupRenderThread(gpu); + EnableRenderThread(); } -void SoftRenderer::RenderThreadFunc() +void SoftRenderer::RenderThreadFunc(GPU& gpu) { for (;;) { + // Wait for a notice from the main thread to start rendering (or to stop entirely). Platform::Semaphore_Wait(Sema_RenderStart); if (!RenderThreadRunning) return; + // Protect the GPU state from the main thread. + // Some melonDS frontends (though not ours) + // will repeatedly save or load states; + // if they do so while the render thread is busy here, + // the ensuing race conditions may cause a crash + // (since some of the GPU state includes pointers). RenderThreadRendering = true; if (FrameIdentical) - { + { // If no rendering is needed, just say we're done. Platform::Semaphore_Post(Sema_ScanlineCount, 192); } else { - ClearBuffers(); - RenderPolygons(true, &GPU.GPU3D.RenderPolygonRAM[0], GPU.GPU3D.RenderNumPolygons); + ClearBuffers(gpu); + RenderPolygons(gpu, true, &gpu.GPU3D.RenderPolygonRAM[0], gpu.GPU3D.RenderNumPolygons); } + // Tell the main thread that we're done rendering + // and that it's safe to access the GPU state again. Platform::Semaphore_Post(Sema_RenderDone); + RenderThreadRendering = false; } } @@ -1759,6 +1799,9 @@ u32* SoftRenderer::GetLine(int line) if (RenderThreadRunning.load(std::memory_order_relaxed)) { if (line < 192) + // We need a scanline, so let's wait for the render thread to finish it. + // (both threads process scanlines from top-to-bottom, + // so we don't need to wait for a specific row) Platform::Semaphore_Wait(Sema_ScanlineCount); } diff --git a/src/GPU3D_Soft.h b/src/GPU3D_Soft.h index 72ab8816..16f2556c 100644 --- a/src/GPU3D_Soft.h +++ b/src/GPU3D_Soft.h @@ -29,21 +29,23 @@ namespace melonDS class SoftRenderer : public Renderer3D { public: - SoftRenderer(melonDS::GPU& gpu, bool threaded = false) noexcept; + SoftRenderer() noexcept; ~SoftRenderer() override; - void Reset() override; + void Reset(GPU& gpu) override; - void SetThreaded(bool threaded) noexcept; + void SetThreaded(bool threaded, GPU& gpu) noexcept; [[nodiscard]] bool IsThreaded() const noexcept { return Threaded; } - void VCount144() override; - void RenderFrame() override; - void RestartFrame() override; + void VCount144(GPU& gpu) override; + void RenderFrame(GPU& gpu) override; + void RestartFrame(GPU& gpu) override; u32* GetLine(int line) override; - void SetupRenderThread(); + void SetupRenderThread(GPU& gpu); + void EnableRenderThread(); void StopRenderThread(); private: + friend void GPU3D::DoSavestate(Savestate* file) noexcept; // Notes on the interpolator: // // This is a theory on how the DS hardware interpolates values. It matches hardware output @@ -178,7 +180,7 @@ private: { // Z-buffering: linear interpolation // still doesn't quite match hardware... - s32 base, disp, factor; + s32 base = 0, disp = 0, factor = 0; if (z0 < z1) { @@ -357,7 +359,7 @@ private: constexpr s32 XVal() const { - s32 ret; + s32 ret = 0; if (Negative) ret = x0 - (dx >> 18); else ret = x0 + (dx >> 18); @@ -453,16 +455,16 @@ private: }; template - inline T ReadVRAM_Texture(u32 addr) + inline T ReadVRAM_Texture(u32 addr, const GPU& gpu) const { - return *(T*)&GPU.VRAMFlat_Texture[addr & 0x7FFFF]; + return *(T*)&gpu.VRAMFlat_Texture[addr & 0x7FFFF]; } template - inline T ReadVRAM_TexPal(u32 addr) + inline T ReadVRAM_TexPal(u32 addr, const GPU& gpu) const { - return *(T*)&GPU.VRAMFlat_TexPal[addr & 0x1FFFF]; + return *(T*)&gpu.VRAMFlat_TexPal[addr & 0x1FFFF]; } - u32 AlphaBlend(u32 srccolor, u32 dstcolor, u32 alpha) noexcept; + u32 AlphaBlend(const GPU3D& gpu3d, u32 srccolor, u32 dstcolor, u32 alpha) const noexcept; struct RendererPolygon { @@ -476,23 +478,22 @@ private: }; - melonDS::GPU& GPU; RendererPolygon PolygonList[2048]; - void TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* color, u8* alpha); - u32 RenderPixel(Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 t); - void PlotTranslucentPixel(u32 pixeladdr, u32 color, u32 z, u32 polyattr, u32 shadow); - void SetupPolygonLeftEdge(RendererPolygon* rp, s32 y); - void SetupPolygonRightEdge(RendererPolygon* rp, s32 y); - void SetupPolygon(RendererPolygon* rp, Polygon* polygon); - void RenderShadowMaskScanline(RendererPolygon* rp, s32 y); - void RenderPolygonScanline(RendererPolygon* rp, s32 y); - void RenderScanline(s32 y, int npolys); - u32 CalculateFogDensity(u32 pixeladdr); - void ScanlineFinalPass(s32 y); - void ClearBuffers(); - void RenderPolygons(bool threaded, Polygon** polygons, int npolys); + void TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s, s16 t, u16* color, u8* alpha) const; + u32 RenderPixel(const GPU& gpu, const Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 t) const; + void PlotTranslucentPixel(const GPU3D& gpu3d, u32 pixeladdr, u32 color, u32 z, u32 polyattr, u32 shadow); + void SetupPolygonLeftEdge(RendererPolygon* rp, s32 y) const; + void SetupPolygonRightEdge(RendererPolygon* rp, s32 y) const; + void SetupPolygon(RendererPolygon* rp, Polygon* polygon) const; + void RenderShadowMaskScanline(const GPU3D& gpu3d, RendererPolygon* rp, s32 y); + void RenderPolygonScanline(const GPU& gpu, RendererPolygon* rp, s32 y); + void RenderScanline(const GPU& gpu, s32 y, int npolys); + u32 CalculateFogDensity(const GPU3D& gpu3d, u32 pixeladdr) const; + void ScanlineFinalPass(const GPU3D& gpu3d, s32 y); + void ClearBuffers(const GPU& gpu); + void RenderPolygons(const GPU& gpu, bool threaded, Polygon** polygons, int npolys); - void RenderThreadFunc(); + void RenderThreadFunc(GPU& gpu); // buffer dimensions are 258x194 to add a offscreen 1px border // which simplifies edge marking tests @@ -527,12 +528,19 @@ private: // threading - bool Threaded; + bool Threaded = false; Platform::Thread* RenderThread; std::atomic_bool RenderThreadRunning; std::atomic_bool RenderThreadRendering; + + // Used by the main thread to tell the render thread to start rendering a frame Platform::Semaphore* Sema_RenderStart; + + // Used by the render thread to tell the main thread that it's done rendering a frame Platform::Semaphore* Sema_RenderDone; + + // Used to allow the main thread to read some scanlines + // before (the 3D portion of) the entire frame is rasterized. Platform::Semaphore* Sema_ScanlineCount; }; } diff --git a/src/GPU3D_Texcache.cpp b/src/GPU3D_Texcache.cpp new file mode 100644 index 00000000..196009e6 --- /dev/null +++ b/src/GPU3D_Texcache.cpp @@ -0,0 +1,269 @@ +#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, u8* texData) +{ + for (u32 i = 0; i < width*height; i++) + { + u16 value = *(u16*)&texData[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, u8* texData); + +template +void ConvertCompressedTexture(u32 width, u32 height, u32* output, u8* texData, u8* texAuxData, u16* palData) +{ + // 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 = ((u32*)texData)[x + y * (width / 4)]; + u16 auxData = ((u16*)texAuxData)[x + y * (width / 4)]; + + u32 paletteOffset = auxData & 0x3FFF; + u16 color0 = palData[paletteOffset*2] | 0x8000; + u16 color1 = palData[paletteOffset*2+1] | 0x8000; + u16 color2, color3; + + switch ((auxData >> 14) & 0x3) + { + case 0: + color2 = palData[paletteOffset*2+2] | 0x8000; + 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: + color2 = palData[paletteOffset*2+2] | 0x8000; + color3 = palData[paletteOffset*2+3] | 0x8000; + 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++) + { + u16 color = (packed >> 16 * (data >> 2 * (i + j * 4))) & 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*, u8*, u8*, u16*); + +template +void ConvertAXIYTexture(u32 width, u32 height, u32* output, u8* texData, u16* palData) +{ + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + u8 val = texData[x + y * width]; + + u32 idx = val & ((1 << Y) - 1); + + u16 color = palData[idx]; + 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*, u8*, u16*); +template void ConvertAXIYTexture(u32, u32, u32*, u8*, u16*); + +template +void ConvertNColorsTexture(u32 width, u32 height, u32* output, u8* texData, u16* palData, bool color0Transparent) +{ + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width / (8 / colorBits); x++) + { + u8 val = texData[x + y * (width / (8 / colorBits))]; + + for (int i = 0; i < 8 / colorBits; i++) + { + u32 index = (val >> (i * colorBits)) & ((1 << colorBits) - 1); + u16 color = palData[index]; + + 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 * (8 / colorBits) + y * width + i] = res; + } + } + } +} + +template void ConvertNColorsTexture(u32, u32, u32*, u8*, u16*, bool); +template void ConvertNColorsTexture(u32, u32, u32*, u8*, u16*, bool); +template void ConvertNColorsTexture(u32, u32, u32*, u8*, u16*, bool); + +} \ No newline at end of file diff --git a/src/GPU3D_Texcache.h b/src/GPU3D_Texcache.h new file mode 100644 index 00000000..214c6254 --- /dev/null +++ b/src/GPU3D_Texcache.h @@ -0,0 +1,310 @@ +#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, u8* texData); +template +void ConvertCompressedTexture(u32 width, u32 height, u32* output, u8* texData, u8* texAuxData, u16* palData); +template +void ConvertAXIYTexture(u32 width, u32 height, u32* output, u8* texData, u16* palData); +template +void ConvertNColorsTexture(u32 width, u32 height, u32* output, u8* texData, u16* palData, bool color0Transparent); + +template +class Texcache +{ +public: + Texcache(const TexLoaderT& texloader) + : TexLoader(texloader) // probably better if this would be a move constructor??? + {} + + 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++) + { + u32 startBit = entry.TextureRAMStart[i] / VRAMDirtyGranularity; + u32 bitsCount = ((entry.TextureRAMStart[i] + entry.TextureRAMSize[i] + 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) & textureDirty.Data[j]) + { + u64 newTexHash = XXH3_64bits(&gpu.VRAMFlat_Texture[entry.TextureRAMStart[i]], entry.TextureRAMSize[i]); + + if (newTexHash != entry.TextureHash[i]) + goto invalidate; + } + } + } + } + + if (texPalChanged && entry.TexPalSize > 0) + { + u32 startBit = entry.TexPalStart / VRAMDirtyGranularity; + u32 bitsCount = ((entry.TexPalStart + entry.TexPalSize + 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) & texPalDirty.Data[j]) + { + u64 newPalHash = XXH3_64bits(&gpu.VRAMFlat_TexPal[entry.TexPalStart], entry.TexPalSize); + if (newPalHash != entry.TexPalHash) + 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, &gpu.VRAMFlat_Texture[addr]); + } + else if (fmt == 5) + { + u8* texData = &gpu.VRAMFlat_Texture[addr]; + u32 slot1addr = 0x20000 + ((addr & 0x1FFFC) >> 1); + if (addr >= 0x40000) + slot1addr += 0x10000; + u8* texAuxData = &gpu.VRAMFlat_Texture[slot1addr]; + + u16* palData = (u16*)(gpu.VRAMFlat_TexPal + palBase*16); + + 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, texData, texAuxData, palData); + } + 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; + + u8* texData = &gpu.VRAMFlat_Texture[addr]; + u16* palData = (u16*)(gpu.VRAMFlat_TexPal + palAddr); + + //assert(entry.TexPalStart+entry.TexPalSize <= 128*1024*1024); + + bool color0Transparent = texParam & (1 << 29); + + switch (fmt) + { + case 1: ConvertAXIYTexture(width, height, DecodingBuffer, texData, palData); break; + case 6: ConvertAXIYTexture(width, height, DecodingBuffer, texData, palData); break; + case 2: ConvertNColorsTexture(width, height, DecodingBuffer, texData, palData, color0Transparent); break; + case 3: ConvertNColorsTexture(width, height, DecodingBuffer, texData, palData, color0Transparent); break; + case 4: ConvertNColorsTexture(width, height, DecodingBuffer, texData, palData, color0Transparent); break; + } + } + + for (int i = 0; i < 2; i++) + { + if (entry.TextureRAMSize[i]) + entry.TextureHash[i] = XXH3_64bits(&gpu.VRAMFlat_Texture[entry.TextureRAMStart[i]], entry.TextureRAMSize[i]); + } + if (entry.TexPalSize) + entry.TexPalHash = XXH3_64bits(&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..6084405b 100644 --- a/src/GPU_OpenGL.cpp +++ b/src/GPU_OpenGL.cpp @@ -36,32 +36,27 @@ 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"); + Comp3DXPosLoc = glGetUniformLocation(CompShader, "u3DXPos"); - 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 +131,7 @@ GLCompositor::~GLCompositor() glDeleteVertexArrays(1, &CompVertexArrayID); glDeleteBuffers(1, &CompVertexBufferID); - OpenGL::DeleteShaderProgram(CompShader.data()); + glDeleteProgram(CompShader); } @@ -174,7 +169,7 @@ GLCompositor& GLCompositor::operator=(GLCompositor&& other) noexcept CompVertices = other.CompVertices; // Clean up these resources before overwriting them - OpenGL::DeleteShaderProgram(CompShader.data()); + glDeleteProgram(CompShader); CompShader = other.CompShader; glDeleteBuffers(1, &CompVertexBufferID); @@ -244,11 +239,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,7 +255,7 @@ 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 @@ -269,12 +264,12 @@ void GLCompositor::RenderFrame(const GPU& gpu, GLRenderer& renderer) noexcept 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..e9f4b173 100644 --- a/src/GPU_OpenGL.h +++ b/src/GPU_OpenGL.h @@ -28,6 +28,7 @@ namespace melonDS class GPU; struct RenderSettings; class GLRenderer; +class Renderer3D; class GLCompositor { public: @@ -42,14 +43,14 @@ 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; diff --git a/src/JitBlock.h b/src/JitBlock.h index 6a187b27..9b31d6d7 100644 --- a/src/JitBlock.h +++ b/src/JitBlock.h @@ -46,12 +46,12 @@ public: JitBlockEntry EntryPoint; - u32* AddressRanges() - { return &Data[0]; } - u32* AddressMasks() - { return &Data[NumAddresses]; } - u32* Literals() - { return &Data[NumAddresses * 2]; } + const u32* AddressRanges() const { return &Data[0]; } + u32* AddressRanges() { return &Data[0]; } + const u32* AddressMasks() const { return &Data[NumAddresses]; } + u32* AddressMasks() { return &Data[NumAddresses]; } + const u32* Literals() const { return &Data[NumAddresses * 2]; } + u32* Literals() { return &Data[NumAddresses * 2]; } private: TinyVector Data; diff --git a/src/MemConstants.h b/src/MemConstants.h index ab80faba..e9aa6b2b 100644 --- a/src/MemConstants.h +++ b/src/MemConstants.h @@ -30,6 +30,10 @@ constexpr u32 NWRAMSize = 0x40000; constexpr u32 ARM9BIOSSize = 0x1000; constexpr u32 ARM7BIOSSize = 0x4000; constexpr u32 DSiBIOSSize = 0x10000; +constexpr u32 ITCMPhysicalSize = 0x8000; +constexpr u32 DTCMPhysicalSize = 0x4000; +constexpr u32 ARM7BIOSCRC32 = 0x1280f0d5; +constexpr u32 ARM9BIOSCRC32 = 0x2ab23573; } #endif // MELONDS_MEMCONSTANTS_H \ No newline at end of file diff --git a/src/NDS.cpp b/src/NDS.cpp index 3aa4a52a..94a24029 100644 --- a/src/NDS.cpp +++ b/src/NDS.cpp @@ -34,6 +34,8 @@ #include "AREngine.h" #include "Platform.h" #include "FreeBIOS.h" +#include "Args.h" +#include "version.h" #include "DSi.h" #include "DSi_SPI_TSC.h" @@ -74,19 +76,39 @@ const s32 kIterationCycleMargin = 8; NDS* NDS::Current = nullptr; -NDS::NDS(int type) noexcept : +NDS::NDS() noexcept : + NDS( + NDSArgs { + nullptr, + nullptr, + std::make_unique(bios_arm9_bin), + std::make_unique(bios_arm7_bin), + Firmware(0), + } + ) +{ +} + +NDS::NDS(NDSArgs&& args, int type) noexcept : ConsoleType(type), - JIT(*this), - SPU(*this), - GPU(*this), - SPI(*this), + ARM7BIOS(*args.ARM7BIOS), + ARM9BIOS(*args.ARM9BIOS), + ARM7BIOSNative(CRC32(ARM7BIOS.data(), ARM7BIOS.size()) == ARM7BIOSCRC32), + ARM9BIOSNative(CRC32(ARM9BIOS.data(), ARM9BIOS.size()) == ARM9BIOSCRC32), + JIT(*this, args.JIT), + SPU(*this, args.BitDepth, args.Interpolation), + GPU(*this, std::move(args.Renderer3D)), + SPI(*this, std::move(args.Firmware)), RTC(*this), Wifi(*this), - NDSCartSlot(*this), - GBACartSlot(), + NDSCartSlot(*this, std::move(args.NDSROM)), + GBACartSlot(type == 1 ? nullptr : std::move(args.GBAROM)), AREngine(*this), - ARM9(*this), - ARM7(*this), + ARM9(*this, args.GDB, args.JIT.has_value()), + ARM7(*this, args.GDB, args.JIT.has_value()), +#ifdef JIT_ENABLED + EnableJIT(args.JIT.has_value()), +#endif DMAs { DMA(0, 0, *this), DMA(0, 1, *this), @@ -187,6 +209,22 @@ void NDS::SetARM7RegionTimings(u32 addrstart, u32 addrend, u32 region, int buswi } } +#ifdef JIT_ENABLED +void NDS::SetJITArgs(std::optional args) noexcept +{ + if (args) + { // If we want to turn the JIT on... + JIT.SetJITArgs(*args); + } + else if (args.has_value() != EnableJIT) + { // Else if we want to turn the JIT off, and it wasn't already off... + JIT.ResetBlockCache(); + } + + EnableJIT = args.has_value(); +} +#endif + void NDS::InitTimings() { // TODO, eventually: @@ -224,7 +262,7 @@ void NDS::InitTimings() // handled later: GBA slot, wifi } -bool NDS::NeedsDirectBoot() +bool NDS::NeedsDirectBoot() const { if (ConsoleType == 1) { @@ -233,12 +271,12 @@ bool NDS::NeedsDirectBoot() } else { - // internal BIOS does not support direct boot - if (!Platform::GetConfigBool(Platform::ExternalBIOSEnable)) + // DSi/3DS firmwares aren't bootable, neither is the generated firmware + if (!SPI.GetFirmware().IsBootable()) return true; - // DSi/3DS firmwares aren't bootable - if (!SPI.GetFirmware()->IsBootable()) + // FreeBIOS requires direct boot (it can't boot firmware) + if (!IsLoadedARM9BIOSKnownNative() || !IsLoadedARM7BIOSKnownNative()) return true; return false; @@ -252,6 +290,13 @@ void NDS::SetupDirectBoot() const u8* cartrom = NDSCartSlot.GetCart()->GetROM(); MapSharedWRAM(3); + // Copy the Nintendo logo from the NDS ROM header to the ARM9 BIOS if using FreeBIOS + // Games need this for DS<->GBA comm to work + if (!IsLoadedARM9BIOSKnownNative()) + { + memcpy(ARM9BIOS.data() + 0x20, header.NintendoLogo, 0x9C); + } + // setup main RAM data for (u32 i = 0; i < 0x170; i+=4) @@ -378,10 +423,6 @@ void NDS::Reset() Platform::FileHandle* f; u32 i; -#ifdef JIT_ENABLED - EnableJIT = Platform::GetConfigBool(Platform::JIT_Enable); -#endif - RunningGame = false; LastSysClockCycles = 0; @@ -489,28 +530,6 @@ void NDS::Reset() SPI.Reset(); RTC.Reset(); Wifi.Reset(); - - // TODO: move the SOUNDBIAS/degrade logic to SPU? - - // The SOUNDBIAS register does nothing on DSi - SPU.SetApplyBias(ConsoleType == 0); - - bool degradeAudio = true; - - if (ConsoleType == 1) - { - //DSi::Reset(); - KeyInput &= ~(1 << (16+6)); - degradeAudio = false; - } - - int bitDepth = Platform::GetConfigInt(Platform::AudioBitDepth); - if (bitDepth == 1) // Always 10-bit - degradeAudio = true; - else if (bitDepth == 2) // Always 16-bit - degradeAudio = false; - - SPU.SetDegrade10Bit(degradeAudio); } void NDS::Start() @@ -695,57 +714,38 @@ bool NDS::DoSavestate(Savestate* file) SPU.SetPowerCnt(PowerControl7 & 0x0001); Wifi.SetPowerCnt(PowerControl7 & 0x0002); - } #ifdef JIT_ENABLED - if (!file->Saving) - { - JIT.ResetBlockCache(); - JIT.Memory.Reset(); - } + JIT.Reset(); #endif + } file->Finish(); return true; } -bool NDS::LoadCart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen) +void NDS::SetNDSCart(std::unique_ptr&& cart) { - if (!NDSCartSlot.LoadROM(romdata, romlen)) - return false; - - if (savedata && savelen) - NDSCartSlot.LoadSave(savedata, savelen); - - return true; + NDSCartSlot.SetCart(std::move(cart)); + // The existing cart will always be ejected; + // if cart is null, then that's equivalent to ejecting a cart + // without inserting a new one. } -void NDS::LoadSave(const u8* savedata, u32 savelen) +void NDS::SetNDSSave(const u8* savedata, u32 savelen) { if (savedata && savelen) - NDSCartSlot.LoadSave(savedata, savelen); + NDSCartSlot.SetSaveMemory(savedata, savelen); } -void NDS::EjectCart() +void NDS::SetGBASave(const u8* savedata, u32 savelen) { - NDSCartSlot.EjectCart(); -} + if (ConsoleType == 0 && savedata && savelen) + { + GBACartSlot.SetSaveMemory(savedata, savelen); + } -bool NDS::CartInserted() -{ - return NDSCartSlot.GetCart() != nullptr; -} - -bool NDS::LoadGBACart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen) -{ - if (!GBACartSlot.LoadROM(romdata, romlen)) - return false; - - if (savedata && savelen) - GBACartSlot.LoadSave(savedata, savelen); - - return true; } void NDS::LoadGBAAddon(int type) @@ -753,24 +753,21 @@ void NDS::LoadGBAAddon(int type) GBACartSlot.LoadAddon(type); } -void NDS::EjectGBACart() -{ - GBACartSlot.EjectCart(); -} - void NDS::LoadBIOS() { Reset(); } -bool NDS::IsLoadedARM9BIOSBuiltIn() +void NDS::SetARM7BIOS(const std::array& bios) noexcept { - return memcmp(ARM9BIOS, bios_arm9_bin, sizeof(NDS::ARM9BIOS)) == 0; + ARM7BIOS = bios; + ARM7BIOSNative = CRC32(ARM7BIOS.data(), ARM7BIOS.size()) == ARM7BIOSCRC32; } -bool NDS::IsLoadedARM7BIOSBuiltIn() +void NDS::SetARM9BIOS(const std::array& bios) noexcept { - return memcmp(ARM7BIOS, bios_arm7_bin, sizeof(NDS::ARM7BIOS)) == 0; + ARM9BIOS = bios; + ARM9BIOSNative = CRC32(ARM9BIOS.data(), ARM9BIOS.size()) == ARM9BIOSCRC32; } u64 NDS::NextTarget() @@ -1169,7 +1166,7 @@ void NDS::SetKeyMask(u32 mask) CheckKeyIRQ(1, oldkey, KeyInput); } -bool NDS::IsLidClosed() +bool NDS::IsLidClosed() const { if (KeyInput & (1<<23)) return true; return false; @@ -1339,7 +1336,7 @@ void NDS::SetIRQ(u32 cpu, u32 irq) { CPUStop &= ~CPUStop_Sleep; CPUStop |= CPUStop_Wakeup; - GPU.GPU3D.RestartFrame(); + GPU.GPU3D.RestartFrame(GPU); } } } @@ -1362,7 +1359,7 @@ void NDS::ClearIRQ2(u32 irq) UpdateIRQ(1); } -bool NDS::HaltInterrupted(u32 cpu) +bool NDS::HaltInterrupted(u32 cpu) const { if (cpu == 0) { @@ -1433,7 +1430,7 @@ void NDS::EnterSleepMode() ARM7.Halt(2); } -u32 NDS::GetPC(u32 cpu) +u32 NDS::GetPC(u32 cpu) const { return cpu ? ARM7.R[15] : ARM9.R[15]; } @@ -1497,40 +1494,40 @@ void NDS::NocashPrint(u32 ncpu, u32 addr) if (cmd[0] == 'r') { - if (!strcmp(cmd, "r0")) sprintf(subs, "%08X", cpu->R[0]); - else if (!strcmp(cmd, "r1")) sprintf(subs, "%08X", cpu->R[1]); - else if (!strcmp(cmd, "r2")) sprintf(subs, "%08X", cpu->R[2]); - else if (!strcmp(cmd, "r3")) sprintf(subs, "%08X", cpu->R[3]); - else if (!strcmp(cmd, "r4")) sprintf(subs, "%08X", cpu->R[4]); - else if (!strcmp(cmd, "r5")) sprintf(subs, "%08X", cpu->R[5]); - else if (!strcmp(cmd, "r6")) sprintf(subs, "%08X", cpu->R[6]); - else if (!strcmp(cmd, "r7")) sprintf(subs, "%08X", cpu->R[7]); - else if (!strcmp(cmd, "r8")) sprintf(subs, "%08X", cpu->R[8]); - else if (!strcmp(cmd, "r9")) sprintf(subs, "%08X", cpu->R[9]); - else if (!strcmp(cmd, "r10")) sprintf(subs, "%08X", cpu->R[10]); - else if (!strcmp(cmd, "r11")) sprintf(subs, "%08X", cpu->R[11]); - else if (!strcmp(cmd, "r12")) sprintf(subs, "%08X", cpu->R[12]); - else if (!strcmp(cmd, "r13")) sprintf(subs, "%08X", cpu->R[13]); - else if (!strcmp(cmd, "r14")) sprintf(subs, "%08X", cpu->R[14]); - else if (!strcmp(cmd, "r15")) sprintf(subs, "%08X", cpu->R[15]); + if (!strcmp(cmd, "r0")) snprintf(subs, sizeof(subs), "%08X", cpu->R[0]); + else if (!strcmp(cmd, "r1")) snprintf(subs, sizeof(subs), "%08X", cpu->R[1]); + else if (!strcmp(cmd, "r2")) snprintf(subs, sizeof(subs), "%08X", cpu->R[2]); + else if (!strcmp(cmd, "r3")) snprintf(subs, sizeof(subs), "%08X", cpu->R[3]); + else if (!strcmp(cmd, "r4")) snprintf(subs, sizeof(subs), "%08X", cpu->R[4]); + else if (!strcmp(cmd, "r5")) snprintf(subs, sizeof(subs), "%08X", cpu->R[5]); + else if (!strcmp(cmd, "r6")) snprintf(subs, sizeof(subs), "%08X", cpu->R[6]); + else if (!strcmp(cmd, "r7")) snprintf(subs, sizeof(subs), "%08X", cpu->R[7]); + else if (!strcmp(cmd, "r8")) snprintf(subs, sizeof(subs), "%08X", cpu->R[8]); + else if (!strcmp(cmd, "r9")) snprintf(subs, sizeof(subs), "%08X", cpu->R[9]); + else if (!strcmp(cmd, "r10")) snprintf(subs, sizeof(subs), "%08X", cpu->R[10]); + else if (!strcmp(cmd, "r11")) snprintf(subs, sizeof(subs), "%08X", cpu->R[11]); + else if (!strcmp(cmd, "r12")) snprintf(subs, sizeof(subs), "%08X", cpu->R[12]); + else if (!strcmp(cmd, "r13")) snprintf(subs, sizeof(subs), "%08X", cpu->R[13]); + else if (!strcmp(cmd, "r14")) snprintf(subs, sizeof(subs), "%08X", cpu->R[14]); + else if (!strcmp(cmd, "r15")) snprintf(subs, sizeof(subs), "%08X", cpu->R[15]); } else { - if (!strcmp(cmd, "sp")) sprintf(subs, "%08X", cpu->R[13]); - else if (!strcmp(cmd, "lr")) sprintf(subs, "%08X", cpu->R[14]); - else if (!strcmp(cmd, "pc")) sprintf(subs, "%08X", cpu->R[15]); - else if (!strcmp(cmd, "frame")) sprintf(subs, "%u", NumFrames); - else if (!strcmp(cmd, "scanline")) sprintf(subs, "%u", GPU.VCount); - else if (!strcmp(cmd, "totalclks")) sprintf(subs, "%" PRIu64, GetSysClockCycles(0)); - else if (!strcmp(cmd, "lastclks")) sprintf(subs, "%" PRIu64, GetSysClockCycles(1)); + if (!strcmp(cmd, "sp")) snprintf(subs, sizeof(subs), "%08X", cpu->R[13]); + else if (!strcmp(cmd, "lr")) snprintf(subs, sizeof(subs), "%08X", cpu->R[14]); + else if (!strcmp(cmd, "pc")) snprintf(subs, sizeof(subs), "%08X", cpu->R[15]); + else if (!strcmp(cmd, "frame")) snprintf(subs, sizeof(subs), "%u", NumFrames); + else if (!strcmp(cmd, "scanline")) snprintf(subs, sizeof(subs), "%u", GPU.VCount); + else if (!strcmp(cmd, "totalclks")) snprintf(subs, sizeof(subs), "%" PRIu64, GetSysClockCycles(0)); + else if (!strcmp(cmd, "lastclks")) snprintf(subs, sizeof(subs), "%" PRIu64, GetSysClockCycles(1)); else if (!strcmp(cmd, "zeroclks")) { - sprintf(subs, "%s", ""); + snprintf(subs, sizeof(subs), "%s", ""); GetSysClockCycles(1); } } - int slen = strlen(subs); + int slen = strnlen(subs, sizeof(subs)); if ((ptr+slen) > 1023) slen = 1023-ptr; strncpy(&output[ptr], subs, slen); ptr += slen; @@ -1661,7 +1658,7 @@ void NDS::TimerStart(u32 id, u16 cnt) -bool NDS::DMAsInMode(u32 cpu, u32 mode) +bool NDS::DMAsInMode(u32 cpu, u32 mode) const { cpu <<= 2; if (DMAs[cpu+0].IsInMode(mode)) return true; @@ -1672,7 +1669,7 @@ bool NDS::DMAsInMode(u32 cpu, u32 mode) return false; } -bool NDS::DMAsRunning(u32 cpu) +bool NDS::DMAsRunning(u32 cpu) const { cpu <<= 2; if (DMAs[cpu+0].IsRunning()) return true; @@ -1849,7 +1846,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) { @@ -2252,7 +2249,7 @@ bool NDS::ARM9GetMemRegion(u32 addr, bool write, MemRegion* region) if ((addr & 0xFFFFF000) == 0xFFFF0000 && !write) { - region->Mem = ARM9BIOS; + region->Mem = &ARM9BIOS[0]; region->Mask = 0xFFF; return true; } @@ -2700,7 +2697,7 @@ bool NDS::ARM7GetMemRegion(u32 addr, bool write, MemRegion* region) { if (ARM7.R[15] < 0x4000 && (addr >= ARM7BIOSProt || ARM7.R[15] < ARM7BIOSProt)) { - region->Mem = ARM7BIOS; + region->Mem = &ARM7BIOS[0]; region->Mask = 0x3FFF; return true; } @@ -2732,11 +2729,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); @@ -2817,7 +2840,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]); } @@ -2888,6 +2911,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) | @@ -3151,6 +3183,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; @@ -3280,6 +3329,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))) { @@ -3596,11 +3654,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); @@ -3701,6 +3785,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) | @@ -3888,6 +3981,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; @@ -3993,6 +4103,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)) { @@ -4216,4 +4335,4 @@ void NDS::ARM7IOWrite32(u32 addr, u32 val) Log(LogLevel::Debug, "unknown ARM7 IO write32 %08X %08X %08X\n", addr, val, ARM7.R[15]); } -} \ No newline at end of file +} diff --git a/src/NDS.h b/src/NDS.h index 0979b2fa..f9df2d69 100644 --- a/src/NDS.h +++ b/src/NDS.h @@ -21,7 +21,7 @@ #include #include -#include +#include #include #include "Platform.h" @@ -36,7 +36,12 @@ #include "AREngine.h" #include "GPU.h" #include "ARMJIT.h" +#include "MemRegion.h" +#include "ARMJIT_Memory.h" +#include "ARM.h" +#include "CRC32.h" #include "DMA.h" +#include "FreeBIOS.h" // when touching the main loop/timing code, pls test a lot of shit // with this enabled, to make sure it doesn't desync @@ -44,7 +49,8 @@ namespace melonDS { - +struct NDSArgs; +class Firmware; enum { Event_LCD = 0, @@ -217,11 +223,12 @@ class ARMJIT; class NDS { -public: - +private: #ifdef JIT_ENABLED bool EnableJIT; #endif + +public: // TODO: Encapsulate the rest of these members int ConsoleType; int CurCPU; @@ -252,11 +259,17 @@ public: u16 PowerControl9; u16 ExMemCnt[2]; - u8 ROMSeed0[2*8]; - u8 ROMSeed1[2*8]; + alignas(u32) u8 ROMSeed0[2*8]; + alignas(u32) u8 ROMSeed1[2*8]; - u8 ARM9BIOS[0x1000]; - u8 ARM7BIOS[0x4000]; +protected: + // These BIOS arrays should be declared *before* the component objects (JIT, SPI, etc.) + // so that they're initialized before the component objects' constructors run. + std::array ARM9BIOS; + std::array ARM7BIOS; + bool ARM9BIOSNative; + bool ARM7BIOSNative; +public: // TODO: Encapsulate the rest of these members u16 ARM7BIOSProt; u8* MainRAM; @@ -303,25 +316,70 @@ public: void SetARM9RegionTimings(u32 addrstart, u32 addrend, u32 region, int buswidth, int nonseq, int seq); void SetARM7RegionTimings(u32 addrstart, u32 addrend, u32 region, int buswidth, int nonseq, int seq); - // 0=DS 1=DSi - void SetConsoleType(int type); - void LoadBIOS(); - bool IsLoadedARM9BIOSBuiltIn(); - bool IsLoadedARM7BIOSBuiltIn(); - virtual bool LoadCart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen); - void LoadSave(const u8* savedata, u32 savelen); - virtual void EjectCart(); - bool CartInserted(); + /// @return \c true if the loaded ARM9 BIOS image is a known dump + /// of a native DS-compatible ARM9 BIOS. + [[nodiscard]] bool IsLoadedARM9BIOSKnownNative() const noexcept { return ARM9BIOSNative; } + [[nodiscard]] const std::array& GetARM9BIOS() const noexcept { return ARM9BIOS; } + void SetARM9BIOS(const std::array& bios) noexcept; - virtual bool NeedsDirectBoot(); + [[nodiscard]] const std::array& GetARM7BIOS() const noexcept { return ARM7BIOS; } + void SetARM7BIOS(const std::array& bios) noexcept; + + /// @return \c true if the loaded ARM7 BIOS image is a known dump + /// of a native DS-compatible ARM9 BIOS. + [[nodiscard]] bool IsLoadedARM7BIOSKnownNative() const noexcept { return ARM7BIOSNative; } + + [[nodiscard]] NDSCart::CartCommon* GetNDSCart() { return NDSCartSlot.GetCart(); } + [[nodiscard]] const NDSCart::CartCommon* GetNDSCart() const { return NDSCartSlot.GetCart(); } + virtual void SetNDSCart(std::unique_ptr&& cart); + [[nodiscard]] bool CartInserted() const noexcept { return NDSCartSlot.GetCart() != nullptr; } + virtual std::unique_ptr EjectCart() { return NDSCartSlot.EjectCart(); } + + [[nodiscard]] u8* GetNDSSave() { return NDSCartSlot.GetSaveMemory(); } + [[nodiscard]] const u8* GetNDSSave() const { return NDSCartSlot.GetSaveMemory(); } + [[nodiscard]] u32 GetNDSSaveLength() const { return NDSCartSlot.GetSaveMemoryLength(); } + void SetNDSSave(const u8* savedata, u32 savelen); + + const Firmware& GetFirmware() const { return SPI.GetFirmwareMem()->GetFirmware(); } + Firmware& GetFirmware() { return SPI.GetFirmwareMem()->GetFirmware(); } + void SetFirmware(Firmware&& firmware) { SPI.GetFirmwareMem()->SetFirmware(std::move(firmware)); } + + const Renderer3D& GetRenderer3D() const noexcept { return GPU.GetRenderer3D(); } + Renderer3D& GetRenderer3D() noexcept { return GPU.GetRenderer3D(); } + void SetRenderer3D(std::unique_ptr&& renderer) noexcept + { + if (renderer != nullptr) + GPU.SetRenderer3D(std::move(renderer)); + } + + virtual bool NeedsDirectBoot() const; void SetupDirectBoot(const std::string& romname); virtual void SetupDirectBoot(); - bool LoadGBACart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen); + [[nodiscard]] GBACart::CartCommon* GetGBACart() { return (ConsoleType == 1) ? nullptr : GBACartSlot.GetCart(); } + [[nodiscard]] const GBACart::CartCommon* GetGBACart() const { return (ConsoleType == 1) ? nullptr : GBACartSlot.GetCart(); } + + /// Inserts a GBA cart into the emulated console's Slot-2. + /// + /// @param cart The GBA cart, most likely (but not necessarily) returned from GBACart::ParseROM. + /// To insert an accessory that doesn't use a ROM image + /// (e.g. the Expansion Pak), create it manually and pass it here. + /// If \c nullptr, the existing cart is ejected. + /// If this is a DSi, this method does nothing. + /// + /// @post \c cart is \c nullptr and this NDS takes ownership + /// of the cart object it held, if any. + void SetGBACart(std::unique_ptr&& cart) { if (ConsoleType == 0) GBACartSlot.SetCart(std::move(cart)); } + + u8* GetGBASave() { return GBACartSlot.GetSaveMemory(); } + const u8* GetGBASave() const { return GBACartSlot.GetSaveMemory(); } + u32 GetGBASaveLength() const { return GBACartSlot.GetSaveMemoryLength(); } + void SetGBASave(const u8* savedata, u32 savelen); + void LoadGBAAddon(int type); - void EjectGBACart(); + std::unique_ptr EjectGBACart() { return GBACartSlot.EjectCart(); } u32 RunFrame(); @@ -332,10 +390,10 @@ public: void SetKeyMask(u32 mask); - bool IsLidClosed(); + bool IsLidClosed() const; void SetLidClosed(bool closed); - virtual void CamInputFrame(int cam, u32* data, int width, int height, bool rgb) {} + virtual void CamInputFrame(int cam, const u32* data, int width, int height, bool rgb) {} void MicInputFrame(s16* data, int samples); void RegisterEventFunc(u32 id, u32 funcid, EventFunc func); @@ -354,20 +412,20 @@ public: void ClearIRQ(u32 cpu, u32 irq); void SetIRQ2(u32 irq); void ClearIRQ2(u32 irq); - bool HaltInterrupted(u32 cpu); + bool HaltInterrupted(u32 cpu) const; void StopCPU(u32 cpu, u32 mask); void ResumeCPU(u32 cpu, u32 mask); void GXFIFOStall(); void GXFIFOUnstall(); - u32 GetPC(u32 cpu); + u32 GetPC(u32 cpu) const; u64 GetSysClockCycles(int num); void NocashPrint(u32 cpu, u32 addr); void MonitorARM9Jump(u32 addr); - virtual bool DMAsInMode(u32 cpu, u32 mode); - virtual bool DMAsRunning(u32 cpu); + virtual bool DMAsInMode(u32 cpu, u32 mode) const; + virtual bool DMAsRunning(u32 cpu) const; virtual void CheckDMAs(u32 cpu, u32 mode); virtual void StopDMAs(u32 cpu, u32 mode); @@ -405,6 +463,14 @@ public: virtual void ARM7IOWrite16(u32 addr, u16 val); virtual void ARM7IOWrite32(u32 addr, u32 val); +#ifdef JIT_ENABLED + [[nodiscard]] bool IsJITEnabled() const noexcept { return EnableJIT; } + void SetJITArgs(std::optional args) noexcept; +#else + [[nodiscard]] bool IsJITEnabled() const noexcept { return false; } + void SetJITArgs(std::optional args) noexcept {} +#endif + private: void InitTimings(); u32 SchedListMask; @@ -423,12 +489,12 @@ private: FIFO IPCFIFO9; // FIFO in which the ARM9 writes FIFO IPCFIFO7; u16 DivCnt; - u32 DivNumerator[2]; - u32 DivDenominator[2]; - u32 DivQuotient[2]; - u32 DivRemainder[2]; + alignas(u64) u32 DivNumerator[2]; + alignas(u64) u32 DivDenominator[2]; + alignas(u64) u32 DivQuotient[2]; + alignas(u64) u32 DivRemainder[2]; u16 SqrtCnt; - u32 SqrtVal[2]; + alignas(u64) u32 SqrtVal[2]; u32 SqrtRes; u16 KeyCnt[2]; bool Running; @@ -456,7 +522,8 @@ private: template u32 RunFrame(); public: - NDS() noexcept : NDS(0) {} + NDS(NDSArgs&& args) noexcept : NDS(std::move(args), 0) {} + NDS() noexcept; virtual ~NDS() noexcept; NDS(const NDS&) = delete; NDS& operator=(const NDS&) = delete; @@ -465,7 +532,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(int type) noexcept; + explicit NDS(NDSArgs&& args, int type) noexcept; virtual void DoSavestateExtra(Savestate* file) {} }; diff --git a/src/NDSCart.cpp b/src/NDSCart.cpp index 95306fc5..a64d8a27 100644 --- a/src/NDSCart.cpp +++ b/src/NDSCart.cpp @@ -20,12 +20,12 @@ #include "NDS.h" #include "DSi.h" #include "NDSCart.h" -#include "ARM.h" #include "CRC32.h" #include "Platform.h" #include "ROMList.h" #include "melonDLDI.h" -#include "xxhash/xxhash.h" +#include "FATStorage.h" +#include "Utils.h" namespace melonDS { @@ -43,12 +43,12 @@ enum // SRAM TODO: emulate write delays??? -u32 ByteSwap(u32 val) +constexpr u32 ByteSwap(u32 val) { return (val >> 24) | ((val >> 8) & 0xFF00) | ((val << 8) & 0xFF0000) | (val << 24); } -void NDSCartSlot::Key1_Encrypt(u32* data) noexcept +void NDSCartSlot::Key1_Encrypt(u32* data) const noexcept { u32 y = data[0]; u32 x = data[1]; @@ -69,7 +69,7 @@ void NDSCartSlot::Key1_Encrypt(u32* data) noexcept data[1] = y ^ Key1_KeyBuf[0x11]; } -void NDSCartSlot::Key1_Decrypt(u32* data) noexcept +void NDSCartSlot::Key1_Decrypt(u32* data) const noexcept { u32 y = data[0]; u32 x = data[1]; @@ -109,9 +109,9 @@ void NDSCartSlot::Key1_ApplyKeycode(u32* keycode, u32 mod) noexcept } } -void NDSCartSlot::Key1_LoadKeyBuf(bool dsi, bool externalBios, u8 *bios, u32 biosLength) noexcept +void NDSCartSlot::Key1_LoadKeyBuf(bool dsi, const u8 *bios, u32 biosLength) noexcept { - if (externalBios) + if (NDS.IsLoadedARM7BIOSKnownNative()) { u32 expected_bios_length = dsi ? 0x10000 : 0x4000; if (biosLength != expected_bios_length) @@ -136,9 +136,9 @@ void NDSCartSlot::Key1_LoadKeyBuf(bool dsi, bool externalBios, u8 *bios, u32 bio } } -void NDSCartSlot::Key1_InitKeycode(bool dsi, u32 idcode, u32 level, u32 mod, u8 *bios, u32 biosLength) noexcept +void NDSCartSlot::Key1_InitKeycode(bool dsi, u32 idcode, u32 level, u32 mod, const u8 *bios, u32 biosLength) noexcept { - Key1_LoadKeyBuf(dsi, Platform::GetConfigBool(Platform::ExternalBIOSEnable), bios, biosLength); + Key1_LoadKeyBuf(dsi, bios, biosLength); u32 keycode[3] = {idcode, idcode>>1, idcode<<1}; if (level >= 1) Key1_ApplyKeycode(keycode, mod); @@ -152,7 +152,7 @@ void NDSCartSlot::Key1_InitKeycode(bool dsi, u32 idcode, u32 level, u32 mod, u8 } -void NDSCartSlot::Key2_Encrypt(u8* data, u32 len) noexcept +void NDSCartSlot::Key2_Encrypt(const u8* data, u32 len) noexcept { for (u32 i = 0; i < len; i++) { @@ -173,27 +173,29 @@ void NDSCartSlot::Key2_Encrypt(u8* data, u32 len) noexcept } -CartCommon::CartCommon(u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams) +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) { - ROM = rom; - ROMLength = len; - ChipID = chipid; - ROMParams = romparams; +} - memcpy(&Header, rom, sizeof(Header)); +CartCommon::CartCommon(std::unique_ptr&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, melonDS::NDSCart::CartType type) : + ROM(std::move(rom)), + ROMLength(len), + ChipID(chipid), + ROMParams(romparams), + CartType(type) +{ + memcpy(&Header, ROM.get(), sizeof(Header)); IsDSi = Header.IsDSi() && !badDSiDump; DSiBase = Header.DSiRegionStart << 19; } -CartCommon::~CartCommon() -{ - delete[] ROM; -} +CartCommon::~CartCommon() = default; u32 CartCommon::Checksum() const { const NDSHeader& header = GetHeader(); - u32 crc = CRC32(ROM, 0x40); + u32 crc = CRC32(ROM.get(), 0x40); crc = CRC32(&ROM[header.ARM9ROMOffset], header.ARM9Size, crc); crc = CRC32(&ROM[header.ARM7ROMOffset], header.ARM7Size, crc); @@ -230,15 +232,7 @@ void CartCommon::DoSavestate(Savestate* file) file->Bool32(&DSiMode); } -void CartCommon::SetupSave(u32 type) -{ -} - -void CartCommon::LoadSave(const u8* savedata, u32 savelen) -{ -} - -int CartCommon::ROMCommandStart(NDS& nds, NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) +int CartCommon::ROMCommandStart(NDS& nds, NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) { if (CmdEncMode == 0) { @@ -267,7 +261,7 @@ int CartCommon::ROMCommandStart(NDS& nds, NDSCartSlot& cartslot, u8* cmd, u8* da case 0x3C: CmdEncMode = 1; - cartslot.Key1_InitKeycode(false, *(u32*)&ROM[0xC], 2, 2, nds.ARM7BIOS, sizeof(NDS::ARM7BIOS)); + cartslot.Key1_InitKeycode(false, *(u32*)&ROM[0xC], 2, 2, nds.GetARM7BIOS().data(), ARM7BIOSSize); DSiMode = false; return 0; @@ -276,7 +270,7 @@ int CartCommon::ROMCommandStart(NDS& nds, NDSCartSlot& cartslot, u8* cmd, u8* da { auto& dsi = static_cast(nds); CmdEncMode = 1; - cartslot.Key1_InitKeycode(true, *(u32*)&ROM[0xC], 1, 2, dsi.ARM7iBIOS, sizeof(DSi::ARM7iBIOS)); + cartslot.Key1_InitKeycode(true, *(u32*)&ROM[0xC], 1, 2, &dsi.ARM7iBIOS[0], sizeof(DSi::ARM7iBIOS)); DSiMode = true; } return 0; @@ -351,7 +345,7 @@ int CartCommon::ROMCommandStart(NDS& nds, NDSCartSlot& cartslot, u8* cmd, u8* da return 0; } -void CartCommon::ROMCommandFinish(u8* cmd, u8* data, u32 len) +void CartCommon::ROMCommandFinish(const u8* cmd, u8* data, u32 len) { } @@ -360,23 +354,13 @@ u8 CartCommon::SPIWrite(u8 val, u32 pos, bool last) return 0xFF; } -u8 *CartCommon::GetSaveMemory() const -{ - return nullptr; -} - -u32 CartCommon::GetSaveMemoryLength() const -{ - return 0; -} - -void CartCommon::ReadROM(u32 addr, u32 len, u8* data, u32 offset) +void CartCommon::ReadROM(u32 addr, u32 len, u8* data, u32 offset) const { if (addr >= ROMLength) return; if ((addr+len) > ROMLength) len = ROMLength - addr; - memcpy(data+offset, ROM+addr, len); + memcpy(data+offset, ROM.get()+addr, len); } const NDSBanner* CartCommon::Banner() const @@ -385,22 +369,68 @@ const NDSBanner* CartCommon::Banner() const size_t bannersize = header.IsDSi() ? 0x23C0 : 0xA40; if (header.BannerOffset >= 0x200 && header.BannerOffset < (ROMLength - bannersize)) { - return reinterpret_cast(ROM + header.BannerOffset); + return reinterpret_cast(ROM.get() + header.BannerOffset); } return nullptr; } -CartRetail::CartRetail(u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams) : CartCommon(rom, len, chipid, badDSiDump, romparams) +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) { - SRAM = nullptr; } -CartRetail::~CartRetail() +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) { - if (SRAM) delete[] SRAM; + u32 savememtype = ROMParams.SaveMemType <= 10 ? ROMParams.SaveMemType : 0; + constexpr int sramlengths[] = + { + 0, + 512, + 8192, 65536, 128*1024, + 256*1024, 512*1024, 1024*1024, + 8192*1024, 16384*1024, 65536*1024 + }; + SRAMLength = sramlengths[savememtype]; + + if (SRAMLength) + { // If this cart should have any save data... + if (sram && sramlen == SRAMLength) + { // If we were given save data that already has the correct length... + SRAM = std::move(sram); + } + else + { // Copy in what we can, truncate the rest. + SRAM = std::make_unique(SRAMLength); + memset(SRAM.get(), 0xFF, SRAMLength); + + if (sram) + { // If we have anything to copy, that is. + memcpy(SRAM.get(), sram.get(), std::min(sramlen, SRAMLength)); + } + } + } + + switch (savememtype) + { + case 1: SRAMType = 1; break; // EEPROM, small + case 2: + case 3: + case 4: SRAMType = 2; break; // EEPROM, regular + case 5: + case 6: + case 7: SRAMType = 3; break; // FLASH + case 8: + case 9: + case 10: SRAMType = 4; break; // NAND + default: SRAMType = 0; break; // ...whatever else + } } +CartRetail::~CartRetail() = default; +// std::unique_ptr cleans up the SRAM and ROM + void CartRetail::Reset() { CartCommon::Reset(); @@ -425,13 +455,11 @@ void CartRetail::DoSavestate(Savestate* file) Log(LogLevel::Warn, "savestate: VERY BAD!!!! SRAM LENGTH DIFFERENT. %d -> %d\n", oldlen, SRAMLength); Log(LogLevel::Warn, "oh well. loading it anyway. adsfgdsf\n"); - if (oldlen) delete[] SRAM; - SRAM = nullptr; - if (SRAMLength) SRAM = new u8[SRAMLength]; + SRAM = SRAMLength ? std::make_unique(SRAMLength) : nullptr; } if (SRAMLength) { - file->VarArray(SRAM, SRAMLength); + file->VarArray(SRAM.get(), SRAMLength); } // SPI status shito @@ -441,57 +469,19 @@ void CartRetail::DoSavestate(Savestate* file) file->Var8(&SRAMStatus); if ((!file->Saving) && SRAM) - Platform::WriteNDSSave(SRAM, SRAMLength, 0, SRAMLength); + Platform::WriteNDSSave(SRAM.get(), SRAMLength, 0, SRAMLength); } -void CartRetail::SetupSave(u32 type) -{ - if (SRAM) delete[] SRAM; - SRAM = nullptr; - - if (type > 10) type = 0; - int sramlen[] = - { - 0, - 512, - 8192, 65536, 128*1024, - 256*1024, 512*1024, 1024*1024, - 8192*1024, 16384*1024, 65536*1024 - }; - SRAMLength = sramlen[type]; - - if (SRAMLength) - { - SRAM = new u8[SRAMLength]; - memset(SRAM, 0xFF, SRAMLength); - } - - switch (type) - { - case 1: SRAMType = 1; break; // EEPROM, small - case 2: - case 3: - case 4: SRAMType = 2; break; // EEPROM, regular - case 5: - case 6: - case 7: SRAMType = 3; break; // FLASH - case 8: - case 9: - case 10: SRAMType = 4; break; // NAND - default: SRAMType = 0; break; // ...whatever else - } -} - -void CartRetail::LoadSave(const u8* savedata, u32 savelen) +void CartRetail::SetSaveMemory(const u8* savedata, u32 savelen) { if (!SRAM) return; u32 len = std::min(savelen, SRAMLength); - memcpy(SRAM, savedata, len); + memcpy(SRAM.get(), savedata, len); Platform::WriteNDSSave(savedata, len, 0, len); } -int CartRetail::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) +int CartRetail::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) { if (CmdEncMode != 2) return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len); @@ -551,17 +541,7 @@ u8 CartRetail::SPIWrite(u8 val, u32 pos, bool last) } } -u8 *CartRetail::GetSaveMemory() const -{ - return SRAM; -} - -u32 CartRetail::GetSaveMemoryLength() const -{ - return SRAMLength; -} - -void CartRetail::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) +void CartRetail::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) const { addr &= (ROMLength-1); @@ -578,7 +558,7 @@ void CartRetail::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) addr = 0x8000 + (addr & 0x1FF); } - memcpy(data+offset, ROM+addr, len); + memcpy(data+offset, ROM.get()+addr, len); } u8 CartRetail::SRAMWrite_EEPROMTiny(u8 val, u32 pos, bool last) @@ -613,7 +593,7 @@ u8 CartRetail::SRAMWrite_EEPROMTiny(u8 val, u32 pos, bool last) if (last) { SRAMStatus &= ~(1<<1); - Platform::WriteNDSSave(SRAM, SRAMLength, + Platform::WriteNDSSave(SRAM.get(), SRAMLength, (SRAMFirstAddr + ((SRAMCmd==0x0A)?0x100:0)) & 0x1FF, SRAMAddr-SRAMFirstAddr); } return 0; @@ -677,7 +657,7 @@ u8 CartRetail::SRAMWrite_EEPROM(u8 val, u32 pos, bool last) if (last) { SRAMStatus &= ~(1<<1); - Platform::WriteNDSSave(SRAM, SRAMLength, + Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); } return 0; @@ -734,7 +714,7 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) if (last) { SRAMStatus &= ~(1<<1); - Platform::WriteNDSSave(SRAM, SRAMLength, + Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); } return 0; @@ -771,7 +751,7 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) if (last) { SRAMStatus &= ~(1<<1); - Platform::WriteNDSSave(SRAM, SRAMLength, + Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); } return 0; @@ -817,7 +797,7 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) if (last) { SRAMStatus &= ~(1<<1); - Platform::WriteNDSSave(SRAM, SRAMLength, + Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); } return 0; @@ -840,7 +820,7 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) if (last) { SRAMStatus &= ~(1<<1); - Platform::WriteNDSSave(SRAM, SRAMLength, + Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); } return 0; @@ -852,15 +832,19 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) } } - -CartRetailNAND::CartRetailNAND(u8* rom, u32 len, u32 chipid, ROMListEntry romparams) : CartRetail(rom, len, chipid, false, romparams) +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() +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) { + BuildSRAMID(); } +CartRetailNAND::~CartRetailNAND() = default; + void CartRetailNAND::Reset() { CartRetail::Reset(); @@ -889,13 +873,13 @@ void CartRetailNAND::DoSavestate(Savestate* file) BuildSRAMID(); } -void CartRetailNAND::LoadSave(const u8* savedata, u32 savelen) +void CartRetailNAND::SetSaveMemory(const u8* savedata, u32 savelen) { - CartRetail::LoadSave(savedata, savelen); + CartRetail::SetSaveMemory(savedata, savelen); BuildSRAMID(); } -int CartRetailNAND::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) +int CartRetailNAND::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) { if (CmdEncMode != 2) return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len); @@ -924,7 +908,7 @@ int CartRetailNAND::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8 if (SRAMLength && SRAMAddr < (SRAMBase+SRAMLength-0x20000)) { memcpy(&SRAM[SRAMAddr - SRAMBase], SRAMWriteBuffer, 0x800); - Platform::WriteNDSSave(SRAM, SRAMLength, SRAMAddr - SRAMBase, 0x800); + Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMAddr - SRAMBase, 0x800); } SRAMAddr = 0; @@ -1031,7 +1015,7 @@ int CartRetailNAND::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8 } } -void CartRetailNAND::ROMCommandFinish(u8* cmd, u8* data, u32 len) +void CartRetailNAND::ROMCommandFinish(const u8* cmd, u8* data, u32 len) { if (CmdEncMode != 2) return CartCommon::ROMCommandFinish(cmd, data, len); @@ -1080,15 +1064,28 @@ void CartRetailNAND::BuildSRAMID() } -CartRetailIR::CartRetailIR(u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams) : CartRetail(rom, len, chipid, badDSiDump, romparams) +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) { - IRVersion = irversion; } -CartRetailIR::~CartRetailIR() +CartRetailIR::CartRetailIR( + std::unique_ptr&& rom, + u32 len, + u32 chipid, + u32 irversion, + bool badDSiDump, + ROMListEntry romparams, + std::unique_ptr&& sram, + u32 sramlen +) : + CartRetail(std::move(rom), len, chipid, badDSiDump, romparams, std::move(sram), sramlen, CartType::RetailIR), + IRVersion(irversion) { } +CartRetailIR::~CartRetailIR() = default; + void CartRetailIR::Reset() { CartRetail::Reset(); @@ -1125,25 +1122,18 @@ 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(u8* rom, u32 len, u32 chipid, ROMListEntry romparams) : CartRetail(rom, len, chipid, false, romparams) +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) { Log(LogLevel::Info,"POKETYPE CART\n"); } -CartRetailBT::~CartRetailBT() -{ -} - -void CartRetailBT::Reset() -{ - CartRetail::Reset(); -} - -void CartRetailBT::DoSavestate(Savestate* file) -{ - CartRetail::DoSavestate(file); -} +CartRetailBT::~CartRetailBT() = default; u8 CartRetailBT::SPIWrite(u8 val, u32 pos, bool last) { @@ -1160,149 +1150,23 @@ u8 CartRetailBT::SPIWrite(u8 val, u32 pos, bool last) } -CartHomebrew::CartHomebrew(u8* rom, u32 len, u32 chipid, ROMListEntry romparams) : CartCommon(rom, len, chipid, false, romparams) +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(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard) : + CartCommon(std::move(rom), len, chipid, false, romparams, CartType::Homebrew), + SD(std::move(sdcard)) { - SD = nullptr; + sdcard = std::nullopt; + // std::move on optionals usually results in an optional with a moved-from object } -CartHomebrew::~CartHomebrew() -{ - if (SD) - { - SD->Close(); - delete SD; - } -} +CartSD::~CartSD() = default; +// The SD card is destroyed by the optional's destructor -void CartHomebrew::Reset() -{ - CartCommon::Reset(); - ReadOnly = Platform::GetConfigBool(Platform::DLDI_ReadOnly); - - if (SD) - { - SD->Close(); - delete SD; - } - - if (Platform::GetConfigBool(Platform::DLDI_Enable)) - { - std::string folderpath; - if (Platform::GetConfigBool(Platform::DLDI_FolderSync)) - folderpath = Platform::GetConfigString(Platform::DLDI_FolderPath); - else - folderpath = ""; - - ApplyDLDIPatch(melonDLDI, sizeof(melonDLDI), ReadOnly); - SD = new FATStorage(Platform::GetConfigString(Platform::DLDI_ImagePath), - (u64)Platform::GetConfigInt(Platform::DLDI_ImageSize) * 1024 * 1024, - ReadOnly, - folderpath); - SD->Open(); - } - else - SD = nullptr; -} - -void CartHomebrew::SetupDirectBoot(const std::string& romname, NDS& nds) -{ - CartCommon::SetupDirectBoot(romname, nds); - - if (SD) - { - // add the ROM to the SD volume - - if (!SD->InjectFile(romname, ROM, ROMLength)) - return; - - // setup argv command line - - char argv[512] = {0}; - int argvlen; - - strncpy(argv, "fat:/", 511); - strncat(argv, romname.c_str(), 511); - argvlen = strlen(argv); - - const NDSHeader& header = GetHeader(); - - u32 argvbase = header.ARM9RAMAddress + header.ARM9Size; - argvbase = (argvbase + 0xF) & ~0xF; - - for (u32 i = 0; i <= argvlen; i+=4) - nds.ARM9Write32(argvbase+i, *(u32*)&argv[i]); - - nds.ARM9Write32(0x02FFFE70, 0x5F617267); - nds.ARM9Write32(0x02FFFE74, argvbase); - nds.ARM9Write32(0x02FFFE78, argvlen+1); - // The DSi version of ARM9Write32 will be called if nds is really a DSi - } -} - -void CartHomebrew::DoSavestate(Savestate* file) -{ - CartCommon::DoSavestate(file); -} - -int CartHomebrew::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) -{ - if (CmdEncMode != 2) return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len); - - switch (cmd[0]) - { - case 0xB7: - { - u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; - memset(data, 0, len); - - if (((addr + len - 1) >> 12) != (addr >> 12)) - { - u32 len1 = 0x1000 - (addr & 0xFFF); - ReadROM_B7(addr, len1, data, 0); - ReadROM_B7(addr+len1, len-len1, data, len1); - } - else - ReadROM_B7(addr, len, data, 0); - } - return 0; - - case 0xC0: // SD read - { - u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; - if (SD) SD->ReadSectors(sector, len>>9, data); - } - return 0; - - case 0xC1: // SD write - return 1; - - default: - return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len); - } -} - -void CartHomebrew::ROMCommandFinish(u8* cmd, u8* data, u32 len) -{ - if (CmdEncMode != 2) return CartCommon::ROMCommandFinish(cmd, data, len); - - // TODO: delayed SD writing? like we have for SRAM - - switch (cmd[0]) - { - case 0xC1: - { - u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; - if (SD && (!ReadOnly)) SD->WriteSectors(sector, len>>9, data); - } - break; - - default: - return CartCommon::ROMCommandFinish(cmd, data, len); - } -} - -void CartHomebrew::ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly) +void CartSD::ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly) const { if (patch[0x0D] > binary[dldioffset+0x0F]) { @@ -1403,7 +1267,7 @@ void CartHomebrew::ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, Log(LogLevel::Debug, "applied DLDI patch at %08X\n", dldioffset); } -void CartHomebrew::ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly) +void CartSD::ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly) { if (*(u32*)&patch[0] != 0xBF8DA5ED || *(u32*)&patch[4] != 0x69684320 || @@ -1433,23 +1297,134 @@ void CartHomebrew::ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly) } } -void CartHomebrew::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) +void CartSD::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) const { // TODO: how strict should this be for homebrew? addr &= (ROMLength-1); - memcpy(data+offset, ROM+addr, len); + 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(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard) : + CartSD(std::move(rom), len, chipid, romparams, std::move(sdcard)) +{} -NDSCartSlot::NDSCartSlot(melonDS::NDS& nds) noexcept : NDS(nds) +CartHomebrew::~CartHomebrew() = default; + +void CartHomebrew::Reset() +{ + CartSD::Reset(); + + if (SD) + ApplyDLDIPatch(melonDLDI, sizeof(melonDLDI), SD->IsReadOnly()); +} + +void CartHomebrew::SetupDirectBoot(const std::string& romname, NDS& nds) +{ + CartCommon::SetupDirectBoot(romname, nds); + + if (SD) + { + // add the ROM to the SD volume + + if (!SD->InjectFile(romname, ROM.get(), ROMLength)) + return; + + // setup argv command line + + char argv[512] = {0}; + int argvlen; + + strncpy(argv, "fat:/", 511); + strncat(argv, romname.c_str(), 511); + argvlen = strlen(argv); + + const NDSHeader& header = GetHeader(); + + u32 argvbase = header.ARM9RAMAddress + header.ARM9Size; + argvbase = (argvbase + 0xF) & ~0xF; + + for (u32 i = 0; i <= argvlen; i+=4) + nds.ARM9Write32(argvbase+i, *(u32*)&argv[i]); + + nds.ARM9Write32(0x02FFFE70, 0x5F617267); + nds.ARM9Write32(0x02FFFE74, argvbase); + nds.ARM9Write32(0x02FFFE78, argvlen+1); + // The DSi version of ARM9Write32 will be called if nds is really a DSi + } +} + +int CartHomebrew::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) +{ + if (CmdEncMode != 2) return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len); + + switch (cmd[0]) + { + case 0xB7: + { + u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + memset(data, 0, len); + + if (((addr + len - 1) >> 12) != (addr >> 12)) + { + u32 len1 = 0x1000 - (addr & 0xFFF); + ReadROM_B7(addr, len1, data, 0); + ReadROM_B7(addr+len1, len-len1, data, len1); + } + else + ReadROM_B7(addr, len, data, 0); + } + return 0; + + case 0xC0: // SD read + { + u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + if (SD) SD->ReadSectors(sector, len>>9, data); + } + return 0; + + case 0xC1: // SD write + return 1; + + default: + return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len); + } +} + +void CartHomebrew::ROMCommandFinish(const u8* cmd, u8* data, u32 len) +{ + if (CmdEncMode != 2) return CartCommon::ROMCommandFinish(cmd, data, len); + + // TODO: delayed SD writing? like we have for SRAM + + switch (cmd[0]) + { + case 0xC1: + { + u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + if (SD && !SD->IsReadOnly()) SD->WriteSectors(sector, len>>9, data); + } + break; + + default: + return CartCommon::ROMCommandFinish(cmd, data, len); + } +} + +NDSCartSlot::NDSCartSlot(melonDS::NDS& nds, std::unique_ptr&& rom) noexcept : NDS(nds) { NDS.RegisterEventFunc(Event_ROMTransfer, ROMTransfer_PrepareData, MemberEventFunc(NDSCartSlot, ROMPrepareData)); NDS.RegisterEventFunc(Event_ROMTransfer, ROMTransfer_End, MemberEventFunc(NDSCartSlot, ROMEndTransfer)); NDS.RegisterEventFunc(Event_ROMSPITransfer, 0, MemberEventFunc(NDSCartSlot, SPITransferDone)); // All fields are default-constructed because they're listed as such in the class declaration + + if (rom) + SetCart(std::move(rom)); } NDSCartSlot::~NDSCartSlot() noexcept @@ -1569,16 +1544,13 @@ void NDSCartSlot::DecryptSecureArea(u8* out) noexcept memcpy(out, &cartrom[arm9base], 0x800); - Key1_InitKeycode(false, gamecode, 2, 2, NDS.ARM7BIOS, sizeof(NDS::ARM7BIOS)); + Key1_InitKeycode(false, gamecode, 2, 2, NDS.GetARM7BIOS().data(), ARM7BIOSSize); Key1_Decrypt((u32*)&out[0]); - Key1_InitKeycode(false, gamecode, 3, 2, NDS.ARM7BIOS, sizeof(NDS::ARM7BIOS)); + Key1_InitKeycode(false, gamecode, 3, 2, NDS.GetARM7BIOS().data(), ARM7BIOSSize); for (u32 i = 0; i < 0x800; i += 8) Key1_Decrypt((u32*)&out[i]); - XXH64_hash_t hash = XXH64(out, 0x800, 0); - Log(LogLevel::Debug, "Secure area post-decryption xxh64 hash: %zx\n", hash); - if (!strncmp((const char*)out, "encryObj", 8)) { Log(LogLevel::Info, "Secure area decryption OK\n"); @@ -1593,7 +1565,12 @@ void NDSCartSlot::DecryptSecureArea(u8* out) noexcept } } -std::unique_ptr ParseROM(const u8* romdata, u32 romlen) +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, std::optional&& args) +{ + return ParseROM(CopyToUnique(romdata, romlen), romlen, std::move(args)); +} + +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::optional&& args) { if (romdata == nullptr) { @@ -1607,28 +1584,10 @@ std::unique_ptr ParseROM(const u8* romdata, u32 romlen) return nullptr; } - u32 cartromsize = 0x200; - while (cartromsize < romlen) - cartromsize <<= 1; // ROM size must be a power of 2 - - u8* cartrom = nullptr; - try - { - cartrom = new u8[cartromsize]; - } - catch (const std::bad_alloc& e) - { - Log(LogLevel::Error, "NDSCart: failed to allocate memory for ROM (%d bytes)\n", cartromsize); - - return nullptr; - } - - // copy romdata into cartrom then zero out the remaining space - memcpy(cartrom, romdata, romlen); - memset(cartrom + romlen, 0, cartromsize - romlen); + auto [cartrom, cartromsize] = PadToPowerOf2(std::move(romdata), romlen); NDSHeader header {}; - memcpy(&header, cartrom, sizeof(header)); + memcpy(&header, cartrom.get(), sizeof(header)); bool dsi = header.IsDSi(); bool badDSiDump = false; @@ -1640,6 +1599,7 @@ std::unique_ptr ParseROM(const u8* romdata, u32 romlen) dsi = false; } + const char *gametitle = header.GameTitle; u32 gamecode = header.GameCodeAsU32(); u32 arm9base = header.ARM9ROMOffset; @@ -1694,30 +1654,33 @@ std::unique_ptr ParseROM(const u8* romdata, u32 romlen) } std::unique_ptr cart; + std::unique_ptr sram = args ? std::move(args->SRAM) : nullptr; + u32 sramlen = args ? args->SRAMLength : 0; if (homebrew) - cart = std::make_unique(cartrom, cartromsize, cartid, romparams); + { + 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)); + } + 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)); + } else if (cartid & 0x08000000) - cart = std::make_unique(cartrom, cartromsize, cartid, romparams); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen); else if (irversion != 0) - cart = std::make_unique(cartrom, cartromsize, cartid, irversion, badDSiDump, romparams); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, irversion, badDSiDump, romparams, std::move(sram), sramlen); else if ((gamecode & 0xFFFFFF) == 0x505A55) // UZPx - cart = std::make_unique(cartrom, cartromsize, cartid, romparams); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen); else - cart = std::make_unique(cartrom, cartromsize, cartid, badDSiDump, romparams); - - if (romparams.SaveMemType > 0) - cart->SetupSave(romparams.SaveMemType); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, badDSiDump, romparams, std::move(sram), sramlen); + args = std::nullopt; return cart; } -bool NDSCartSlot::InsertROM(std::unique_ptr&& cart) noexcept +void NDSCartSlot::SetCart(std::unique_ptr&& cart) noexcept { - if (!cart) { - Log(LogLevel::Error, "Failed to insert invalid cart; existing cart (if any) was not ejected.\n"); - return false; - } - if (Cart) EjectCart(); @@ -1725,6 +1688,10 @@ bool NDSCartSlot::InsertROM(std::unique_ptr&& cart) noexcept // and cloning polymorphic objects without knowing the underlying type is annoying. Cart = std::move(cart); + if (!Cart) + // If we're ejecting an existing cart without inserting a new one... + return; + Cart->Reset(); const NDSHeader& header = Cart->GetHeader(); @@ -1739,11 +1706,11 @@ bool NDSCartSlot::InsertROM(std::unique_ptr&& cart) noexcept strncpy((char*)&cartrom[header.ARM9ROMOffset], "encryObj", 8); - Key1_InitKeycode(false, romparams.GameCode, 3, 2, NDS.ARM7BIOS, sizeof(NDS::ARM7BIOS)); + Key1_InitKeycode(false, romparams.GameCode, 3, 2, NDS.GetARM7BIOS().data(), ARM7BIOSSize); for (u32 i = 0; i < 0x800; i += 8) Key1_Encrypt((u32*)&cartrom[header.ARM9ROMOffset + i]); - Key1_InitKeycode(false, romparams.GameCode, 2, 2, NDS.ARM7BIOS, sizeof(NDS::ARM7BIOS)); + Key1_InitKeycode(false, romparams.GameCode, 2, 2, NDS.GetARM7BIOS().data(), ARM7BIOSSize); Key1_Encrypt((u32*)&cartrom[header.ARM9ROMOffset]); Log(LogLevel::Debug, "Re-encrypted cart secure area\n"); @@ -1757,21 +1724,12 @@ bool NDSCartSlot::InsertROM(std::unique_ptr&& cart) noexcept Log(LogLevel::Info, "Inserted cart with game code: %.4s\n", header.GameCode); Log(LogLevel::Info, "Inserted cart with ID: %08X\n", Cart->ID()); Log(LogLevel::Info, "ROM entry: %08X %08X\n", romparams.ROMSize, romparams.SaveMemType); - - return true; } -bool NDSCartSlot::LoadROM(const u8* romdata, u32 romlen) noexcept -{ - std::unique_ptr cart = ParseROM(romdata, romlen); - - return InsertROM(std::move(cart)); -} - -void NDSCartSlot::LoadSave(const u8* savedata, u32 savelen) noexcept +void NDSCartSlot::SetSaveMemory(const u8* savedata, u32 savelen) noexcept { if (Cart) - Cart->LoadSave(savedata, savelen); + Cart->SetSaveMemory(savedata, savelen); } void NDSCartSlot::SetupDirectBoot(const std::string& romname) noexcept @@ -1780,15 +1738,15 @@ void NDSCartSlot::SetupDirectBoot(const std::string& romname) noexcept Cart->SetupDirectBoot(romname, NDS); } -void NDSCartSlot::EjectCart() noexcept +std::unique_ptr NDSCartSlot::EjectCart() noexcept { - if (!Cart) return; + if (!Cart) return nullptr; // ejecting the cart triggers the gamecard IRQ NDS.SetIRQ(0, IRQ_CartIREQMC); NDS.SetIRQ(1, IRQ_CartIREQMC); - Cart = nullptr; + return std::move(Cart); // CHECKME: does an eject imply anything for the ROM/SPI transfer registers? } diff --git a/src/NDSCart.h b/src/NDSCart.h index 24f9f9ee..2f6a3be5 100644 --- a/src/NDSCart.h +++ b/src/NDSCart.h @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include "types.h" #include "Savestate.h" @@ -45,18 +45,42 @@ enum CartType RetailIR = 0x103, RetailBT = 0x104, Homebrew = 0x201, + UnlicensedR4 = 0x301 }; class NDSCartSlot; +/// Arguments used to create and populate an NDS cart of unknown type. +/// Different carts take different subsets of these arguments, +/// but we won't know which ones to use +/// until we parse the header at runtime. +struct NDSCartArgs +{ + /// The arguments used to load a homebrew SD card image. + /// If \c nullopt, then the cart will not have an SD card. + /// Ignored for retail ROMs. + std::optional SDCard = std::nullopt; + + /// Save RAM to load into the cartridge. + /// If \c nullptr, then the cart's SRAM buffer will be empty. + /// Ignored for homebrew ROMs. + std::unique_ptr SRAM = nullptr; + + /// The length of the buffer in SRAM. + /// If 0, then the cart's SRAM buffer will be empty. + /// Ignored for homebrew ROMs. + u32 SRAMLength = 0; +}; + // CartCommon -- base code shared by all cart types class CartCommon { public: - CartCommon(u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams); + 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); virtual ~CartCommon(); - virtual u32 Type() const = 0; + [[nodiscard]] u32 Type() const { return CartType; }; [[nodiscard]] u32 Checksum() const; virtual void Reset(); @@ -64,16 +88,16 @@ public: virtual void DoSavestate(Savestate* file); - virtual void SetupSave(u32 type); - virtual void LoadSave(const u8* savedata, u32 savelen); - virtual int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len); - virtual void ROMCommandFinish(u8* cmd, u8* data, u32 len); + virtual int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len); + virtual void ROMCommandFinish(const u8* cmd, u8* data, u32 len); virtual u8 SPIWrite(u8 val, u32 pos, bool last); - virtual u8* GetSaveMemory() const; - virtual u32 GetSaveMemoryLength() const; + virtual u8* GetSaveMemory() { return nullptr; } + virtual const u8* GetSaveMemory() const { return nullptr; } + virtual u32 GetSaveMemoryLength() const { return 0; } + virtual void SetSaveMemory(const u8* savedata, u32 savelen) {}; [[nodiscard]] const NDSHeader& GetHeader() const { return Header; } [[nodiscard]] NDSHeader& GetHeader() { return Header; } @@ -82,105 +106,120 @@ public: [[nodiscard]] const NDSBanner* Banner() const; [[nodiscard]] const ROMListEntry& GetROMParams() const { return ROMParams; }; [[nodiscard]] u32 ID() const { return ChipID; } - [[nodiscard]] const u8* GetROM() const { return ROM; } + [[nodiscard]] const u8* GetROM() const { return ROM.get(); } [[nodiscard]] u32 GetROMLength() const { return ROMLength; } protected: - void ReadROM(u32 addr, u32 len, u8* data, u32 offset); + void ReadROM(u32 addr, u32 len, u8* data, u32 offset) const; - u8* ROM; - u32 ROMLength; - u32 ChipID; - bool IsDSi; - bool DSiMode; - u32 DSiBase; + std::unique_ptr ROM = nullptr; + u32 ROMLength = 0; + u32 ChipID = 0; + bool IsDSi = false; + bool DSiMode = false; + u32 DSiBase = 0; - u32 CmdEncMode; - u32 DataEncMode; + u32 CmdEncMode = 0; + u32 DataEncMode = 0; // Kept separate from the ROM data so we can decrypt the modcrypt area // without touching the overall ROM data - NDSHeader Header; - ROMListEntry ROMParams; + NDSHeader Header {}; + ROMListEntry ROMParams {}; + const melonDS::NDSCart::CartType CartType = Default; }; // CartRetail -- regular retail cart (ROM, SPI SRAM) class CartRetail : public CartCommon { public: - CartRetail(u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams); - virtual ~CartRetail() override; + CartRetail( + const u8* rom, + u32 len, + u32 chipid, + bool badDSiDump, + ROMListEntry romparams, + std::unique_ptr&& sram, + u32 sramlen, + melonDS::NDSCart::CartType type = CartType::Retail + ); + CartRetail( + std::unique_ptr&& rom, + u32 len, u32 chipid, + bool badDSiDump, + ROMListEntry romparams, + std::unique_ptr&& sram, + u32 sramlen, + melonDS::NDSCart::CartType type = CartType::Retail + ); + ~CartRetail() override; - virtual u32 Type() const override { return CartType::Retail; } + void Reset() override; - virtual void Reset() override; + void DoSavestate(Savestate* file) override; - virtual void DoSavestate(Savestate* file) override; + void SetSaveMemory(const u8* savedata, u32 savelen) override; - virtual void SetupSave(u32 type) override; - virtual void LoadSave(const u8* savedata, u32 savelen) override; + int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) override; - virtual int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) override; + u8 SPIWrite(u8 val, u32 pos, bool last) override; - virtual u8 SPIWrite(u8 val, u32 pos, bool last) override; - - virtual u8* GetSaveMemory() const override; - virtual u32 GetSaveMemoryLength() const override; + u8* GetSaveMemory() override { return SRAM.get(); } + const u8* GetSaveMemory() const override { return SRAM.get(); } + u32 GetSaveMemoryLength() const override { return SRAMLength; } protected: - void ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset); + void ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) const; u8 SRAMWrite_EEPROMTiny(u8 val, u32 pos, bool last); u8 SRAMWrite_EEPROM(u8 val, u32 pos, bool last); u8 SRAMWrite_FLASH(u8 val, u32 pos, bool last); - u8* SRAM; - u32 SRAMLength; - u32 SRAMType; + std::unique_ptr SRAM = nullptr; + u32 SRAMLength = 0; + u32 SRAMType = 0; - u8 SRAMCmd; - u32 SRAMAddr; - u32 SRAMFirstAddr; - u8 SRAMStatus; + u8 SRAMCmd = 0; + u32 SRAMAddr = 0; + u32 SRAMFirstAddr = 0; + u8 SRAMStatus = 0; }; // CartRetailNAND -- retail cart with NAND SRAM (WarioWare DIY, Jam with the Band, ...) class CartRetailNAND : public CartRetail { public: - CartRetailNAND(u8* rom, u32 len, u32 chipid, ROMListEntry romparams); + 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() override; - virtual u32 Type() const override { return CartType::RetailNAND; } - void Reset() override; void DoSavestate(Savestate* file) override; - void LoadSave(const u8* savedata, u32 savelen) override; + void SetSaveMemory(const u8* savedata, u32 savelen) override; - int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) override; - void ROMCommandFinish(u8* cmd, u8* data, u32 len) override; + int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) override; + void ROMCommandFinish(const u8* cmd, u8* data, u32 len) override; u8 SPIWrite(u8 val, u32 pos, bool last) override; private: void BuildSRAMID(); - u32 SRAMBase; - u32 SRAMWindow; + u32 SRAMBase = 0; + u32 SRAMWindow = 0; - u8 SRAMWriteBuffer[0x800]; - u32 SRAMWritePos; + u8 SRAMWriteBuffer[0x800] {}; + u32 SRAMWritePos = 0; }; // CartRetailIR -- SPI IR device and SRAM class CartRetailIR : public CartRetail { public: - CartRetailIR(u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams); + 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() override; - virtual u32 Type() const override { return CartType::RetailIR; } - void Reset() override; void DoSavestate(Savestate* file) override; @@ -188,56 +227,135 @@ public: u8 SPIWrite(u8 val, u32 pos, bool last) override; private: - u32 IRVersion; - u8 IRCmd; + u32 IRVersion = 0; + u8 IRCmd = 0; }; // CartRetailBT - Pok�mon Typing Adventure (SPI BT controller) class CartRetailBT : public CartRetail { public: - CartRetailBT(u8* rom, u32 len, u32 chipid, ROMListEntry romparams); + 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() override; - virtual u32 Type() const override { return CartType::RetailBT; } - - void Reset() override; - - void DoSavestate(Savestate* file) override; - u8 SPIWrite(u8 val, u32 pos, bool last) override; }; -// CartHomebrew -- homebrew 'cart' (no SRAM, DLDI) -class CartHomebrew : public CartCommon +// CartSD -- any 'cart' with an SD card slot +class CartSD : public CartCommon { public: - CartHomebrew(u8* rom, u32 len, u32 chipid, ROMListEntry romparams); - ~CartHomebrew() override; + 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() override; - virtual u32 Type() const override { return CartType::Homebrew; } + [[nodiscard]] const std::optional& GetSDCard() const noexcept { return SD; } + void SetSDCard(FATStorage&& sdcard) noexcept { SD = std::move(sdcard); } + void SetSDCard(std::optional&& sdcard) noexcept + { + SD = std::move(sdcard); + sdcard = std::nullopt; + // moving from an optional doesn't set it to nullopt, + // it just leaves behind an optional with a moved-from value + } + + void SetSDCard(std::optional&& args) noexcept + { + // Close the open SD card (if any) so that its contents are flushed to disk. + // Also, if args refers to the same image file that SD is currently using, + // this will ensure that we don't have two open read-write handles + // to the same file. + SD = std::nullopt; + + if (args) + SD = FATStorage(std::move(*args)); + + args = std::nullopt; + // moving from an optional doesn't set it to nullopt, + // it just leaves behind an optional with a moved-from value + } + +protected: + void ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly) const; + void ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly); + void ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) const; + + std::optional SD {}; +}; + +// CartHomebrew -- homebrew 'cart' (no SRAM, DLDI) +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() override; void Reset() override; void SetupDirectBoot(const std::string& romname, NDS& nds) override; + int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) override; + void ROMCommandFinish(const u8* cmd, u8* data, u32 len) override; +}; + +// CartR4 -- unlicensed R4 'cart' (NDSCartR4.cpp) +enum CartR4Type +{ + /* non-SDHC carts */ + CartR4TypeM3Simply = 0, + CartR4TypeR4 = 1, + /* SDHC carts */ + CartR4TypeAce3DS = 2 +}; + +enum CartR4Language +{ + CartR4LanguageJapanese = (7 << 3) | 1, + CartR4LanguageEnglish = (7 << 3) | 2, + CartR4LanguageFrench = (2 << 3) | 2, + CartR4LanguageKorean = (4 << 3) | 2, + CartR4LanguageSimplifiedChinese = (6 << 3) | 3, + CartR4LanguageTraditionalChinese = (7 << 3) | 3 +}; + +class CartR4 : public CartSD +{ +public: + CartR4(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage, + std::optional&& sdcard = std::nullopt); + ~CartR4() override; + + void Reset() override; + void DoSavestate(Savestate* file) override; - int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) override; - void ROMCommandFinish(u8* cmd, u8* data, u32 len) override; + int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) override; + void ROMCommandFinish(const u8* cmd, u8* data, u32 len) override; private: - void ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly); - void ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly); - void ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset); + inline u32 GetAdjustedSector(u32 sector) const + { + return R4CartType >= CartR4TypeAce3DS ? sector : sector >> 9; + } - FATStorage* SD; - bool ReadOnly; + u16 GetEncryptionKey(u16 sector); + void ReadSDToBuffer(u32 sector, bool rom); + u64 SDFATEntrySectorGet(u32 entry, u32 addr); + + s32 EncryptionKey; + u32 FATEntryOffset[2]; + u8 Buffer[512]; + u8 InitStatus; + CartR4Type R4CartType; + CartR4Language CartLanguage; + bool BufferInitialized; }; class NDSCartSlot { public: - NDSCartSlot(melonDS::NDS& nds) noexcept; + explicit NDSCartSlot(melonDS::NDS& nds, std::unique_ptr&& rom = nullptr) noexcept; ~NDSCartSlot() noexcept; void Reset() noexcept; void ResetCart() noexcept; @@ -252,25 +370,16 @@ public: /// If the provided cart is not valid, /// then the currently-loaded ROM will not be ejected. /// - /// @param cart Movable reference to the cart. - /// @returns \c true if the cart was successfully loaded, - /// \c false otherwise. + /// @param cart Movable reference to the cart, + /// or \c nullptr to eject the cart. /// @post If the cart was successfully loaded, /// then \c cart will be \c nullptr /// and \c Cart will contain the object that \c cart previously pointed to. /// Otherwise, \c cart and \c Cart will be both be unchanged. - bool InsertROM(std::unique_ptr&& cart) noexcept; + void SetCart(std::unique_ptr&& cart) noexcept; + [[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); } + [[nodiscard]] const CartCommon* GetCart() const noexcept { return Cart.get(); } - /// Parses a ROM image and loads it into the emulator. - /// This function is equivalent to calling ::ParseROM() and ::InsertROM() in sequence. - /// @param romdata Pointer to the ROM image. - /// The cart emulator maintains its own copy of this data, - /// so the caller is free to discard this data after calling this function. - /// @param romlen The length of the ROM image, in bytes. - /// @returns \c true if the ROM image was successfully loaded, - /// \c false if not. - bool LoadROM(const u8* romdata, u32 romlen) noexcept; - void LoadSave(const u8* savedata, u32 savelen) noexcept; void SetupDirectBoot(const std::string& romname) noexcept; /// This function is intended to allow frontends to save and load SRAM @@ -282,11 +391,15 @@ public: /// @returns Pointer to this cart's SRAM if a cart is loaded and supports SRAM, otherwise \c nullptr. [[nodiscard]] const u8* GetSaveMemory() const noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; } [[nodiscard]] u8* GetSaveMemory() noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; } + void SetSaveMemory(const u8* savedata, u32 savelen) noexcept; /// @returns The length of the buffer returned by ::GetSaveMemory() /// if a cart is loaded and supports SRAM, otherwise zero. [[nodiscard]] u32 GetSaveMemoryLength() const noexcept { return Cart ? Cart->GetSaveMemoryLength() : 0; } - void EjectCart() noexcept; + + /// @return The cart that was in the slot before it was ejected, + /// or \c nullptr if the slot was already empty. + std::unique_ptr EjectCart() noexcept; u32 ReadROMData() noexcept; void WriteROMData(u32 val) noexcept; void WriteSPICnt(u16 val) noexcept; @@ -294,9 +407,6 @@ public: [[nodiscard]] u8 ReadSPIData() const noexcept; void WriteSPIData(u8 val) noexcept; - [[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); } - [[nodiscard]] const CartCommon* GetCart() const noexcept { return Cart.get(); } - [[nodiscard]] u8 GetROMCommand(u8 index) const noexcept { return ROMCommand[index]; } void SetROMCommand(u8 index, u8 val) noexcept { ROMCommand[index] = val; } @@ -306,34 +416,34 @@ public: private: friend class CartCommon; melonDS::NDS& NDS; - u16 SPICnt {}; - u32 ROMCnt {}; + u16 SPICnt = 0; + u32 ROMCnt = 0; std::array ROMCommand {}; - u8 SPIData; - u32 SPIDataPos; - bool SPIHold; + u8 SPIData = 0; + u32 SPIDataPos = 0; + bool SPIHold = false; - u32 ROMData; + u32 ROMData = 0; - std::array TransferData; - u32 TransferPos; - u32 TransferLen; - u32 TransferDir; - std::array TransferCmd; + std::array TransferData {}; + u32 TransferPos = 0; + u32 TransferLen = 0; + u32 TransferDir = 0; + std::array TransferCmd {}; - std::unique_ptr Cart; + std::unique_ptr Cart = nullptr; - std::array Key1_KeyBuf; + std::array Key1_KeyBuf {}; - u64 Key2_X; - u64 Key2_Y; + u64 Key2_X = 0; + u64 Key2_Y = 0; - void Key1_Encrypt(u32* data) noexcept; - void Key1_Decrypt(u32* data) noexcept; + void Key1_Encrypt(u32* data) const noexcept; + void Key1_Decrypt(u32* data) const noexcept; void Key1_ApplyKeycode(u32* keycode, u32 mod) noexcept; - void Key1_LoadKeyBuf(bool dsi, bool externalBios, u8 *bios, u32 biosLength) noexcept; - void Key1_InitKeycode(bool dsi, u32 idcode, u32 level, u32 mod, u8 *bios, u32 biosLength) noexcept; - void Key2_Encrypt(u8* data, u32 len) noexcept; + void Key1_LoadKeyBuf(bool dsi, const u8 *bios, u32 biosLength) noexcept; + void Key1_InitKeycode(bool dsi, u32 idcode, u32 level, u32 mod, const u8 *bios, u32 biosLength) noexcept; + void Key2_Encrypt(const u8* data, u32 len) noexcept; void ROMEndTransfer(u32 param) noexcept; void ROMPrepareData(u32 param) noexcept; void AdvanceROMTransfer() noexcept; @@ -346,9 +456,13 @@ private: /// The returned cartridge will contain a copy of this data, /// so the caller may deallocate \c romdata after this function returns. /// @param romlen The length of the ROM data in bytes. +/// @param sdcard The arguments to use for initializing the SD card. +/// Ignored if the parsed ROM is not homebrew. +/// 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::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); } #endif diff --git a/src/NDSCartR4.cpp b/src/NDSCartR4.cpp new file mode 100644 index 00000000..8497f556 --- /dev/null +++ b/src/NDSCartR4.cpp @@ -0,0 +1,371 @@ +/* + 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 "NDS.h" +#include "DSi.h" +#include "NDSCart.h" +#include "Platform.h" + +namespace melonDS +{ +using Platform::Log; +using Platform::LogLevel; + +namespace NDSCart +{ + +/* + Original algorithm discovered by yasu, 2007 + + http://hp.vector.co.jp/authors/VA013928/ + http://www.usay.jp/ + http://www.yasu.nu/ +*/ +static void DecryptR4Sector(u8* dest, u8* src, u16 key1) +{ + for (int i = 0; i < 512; i++) + { + // Derive key2 from key1. + u8 key2 = ((key1 >> 7) & 0x80) + | ((key1 >> 6) & 0x60) + | ((key1 >> 5) & 0x10) + | ((key1 >> 4) & 0x0C) + | (key1 & 0x03); + + // Decrypt byte. + dest[i] = src[i] ^ key2; + + // Derive next key1 from key2. + u16 tmp = ((src[i] << 8) ^ key1); + u16 tmpXor = 0; + for (int ii = 0; ii < 16; ii++) + tmpXor ^= (tmp >> ii); + + u16 newKey1 = 0; + newKey1 |= ((tmpXor & 0x80) | (tmp & 0x7C)) << 8; + newKey1 |= ((tmp ^ (tmpXor >> 14)) << 8) & 0x0300; + newKey1 |= (((tmp >> 1) ^ tmp) >> 6) & 0xFC; + newKey1 |= ((tmp ^ (tmpXor >> 1)) >> 8) & 0x03; + + key1 = newKey1; + } +} + +CartR4::CartR4(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage, + std::optional&& sdcard) + : CartSD(std::move(rom), len, chipid, romparams, std::move(sdcard)) +{ + InitStatus = 0; + R4CartType = ctype; + CartLanguage = clanguage; +} + +CartR4::~CartR4() +{ +} + +void CartR4::Reset() +{ + CartSD::Reset(); + + BufferInitialized = false; + EncryptionKey = -1; + FATEntryOffset[0] = 0xFFFFFFFF; + FATEntryOffset[1] = 0xFFFFFFFF; + + if (!SD) + InitStatus = 1; + else + { + u8 buffer[512]; + if (!SD->ReadFile("_DS_MENU.DAT", 0, 512, buffer)) + InitStatus = 3; + else + InitStatus = 4; + } +} + +void CartR4::DoSavestate(Savestate* file) +{ + CartCommon::DoSavestate(file); + + file->Var32(&FATEntryOffset[0]); + file->Var32(&FATEntryOffset[1]); + file->VarArray(Buffer, 512); +} + +// FIXME: Ace3DS/clone behavior is only partially verified. +int CartR4::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) +{ + if (CmdEncMode != 2) + return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len); + + switch (cmd[0]) + { + case 0xB0: /* Get card information */ + { + u32 info = 0x75A00000 | (((R4CartType >= 1 ? 4 : 0) | CartLanguage) << 3) | InitStatus; + for (u32 pos = 0; pos < len; pos += 4) + *(u32*)&data[pos] = info; + return 0; + } + case 0xB4: /* FAT entry */ + { + u8 entryBuffer[512]; + u32 sector = ((cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]) & (~0x1F); + // set FAT entry offset to the starting cluster, to gain a bit of speed + SD->ReadSectors(sector >> 9, 1, entryBuffer); + u16 fileEntryOffset = sector & 0x1FF; + u32 clusterStart = (entryBuffer[fileEntryOffset + 27] << 8) + | entryBuffer[fileEntryOffset + 26] + | (entryBuffer[fileEntryOffset + 21] << 24) + | (entryBuffer[fileEntryOffset + 20] << 16); + FATEntryOffset[cmd[4] & 0x01] = clusterStart; + for (u32 pos = 0; pos < len; pos += 4) + *(u32*)&data[pos] = 0; + return 0; + } + case 0xB8: /* ? Get chip ID ? */ + { + for (u32 pos = 0; pos < len; pos += 4) + *(u32*)&data[pos] = ChipID; + return 0; + } + case 0xB2: /* Save read request */ + case 0xB6: /* ROM read request */ + { + u32 sector = ((cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]); + ReadSDToBuffer(sector, cmd[0] == 0xB6); + for (u32 pos = 0; pos < len; pos += 4) + *(u32*)&data[pos] = 0; + return 0; + } + case 0xB9: /* SD read request */ + { + u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + if (SD) + SD->ReadSectors(GetAdjustedSector(sector), 1, Buffer); + for (u32 pos = 0; pos < len; pos += 4) + *(u32*)&data[pos] = 0; + return 0; + } + case 0xBB: /* SD write start */ + case 0xBD: /* Save write start */ + return 1; + case 0xBC: /* SD write status */ + case 0xBE: /* Save write status */ + { + if (R4CartType == CartR4TypeAce3DS && cmd[0] == 0xBC) + { + uint8_t checksum = 0; + for (int i = 0; i < 7; i++) + checksum ^= cmd[i]; + if (checksum != cmd[7]) + Log(LogLevel::Warn, "R4: invalid 0xBC command checksum (%d != %d)", cmd[7], checksum); + } + for (u32 pos = 0; pos < len; pos += 4) + *(u32*)&data[pos] = 0; + return 0; + } + case 0xB7: /* ROM read data */ + { + /* If the buffer has not been initialized yet, emulate ROM. */ + /* TODO: When does the R4 do this exactly? */ + if (!BufferInitialized) + { + u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + memcpy(data, &ROM[addr & (ROMLength-1)], len); + return 0; + } + /* Otherwise, fall through. */ + } + case 0xB3: /* Save read data */ + case 0xBA: /* SD read data */ + { + // TODO: Do these use separate buffers? + for (u32 pos = 0; pos < len; pos++) + data[pos] = Buffer[pos & 0x1FF]; + return 0; + } + case 0xBF: /* ROM read decrypted data */ + { + // TODO: Is decryption done using the sector from 0xBF or 0xB6? + u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + if (len >= 512) + DecryptR4Sector(data, Buffer, GetEncryptionKey(sector >> 9)); + return 0; + } + default: + Log(LogLevel::Warn, "R4: unknown command %02X %02X %02X %02X %02X %02X %02X %02X (%d)\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], len); + for (u32 pos = 0; pos < len; pos += 4) + *(u32*)&data[pos] = 0; + return 0; + } +} + +void CartR4::ROMCommandFinish(const u8* cmd, u8* data, u32 len) +{ + if (CmdEncMode != 2) return CartCommon::ROMCommandFinish(cmd, data, len); + + switch (cmd[0]) + { + case 0xBB: /* SD write start */ + { + u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + + // The official R4 firmware sends a superfluous write to card + // (sector 0, byte 1) on boot. TODO: Is this correct? + if (GetAdjustedSector(sector) != sector && (sector & 0x1FF)) break; + + if (SD && !SD->IsReadOnly()) + SD->WriteSectors(GetAdjustedSector(sector), 1, data); + break; + } + case 0xBD: /* Save write start */ + { + u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + + if (sector & 0x1FF) break; + + if (SD && !SD->IsReadOnly()) + SD->WriteSectors( + SDFATEntrySectorGet(FATEntryOffset[1], sector) >> 9, + 1, data + ); + break; + } + } +} + +u16 CartR4::GetEncryptionKey(u16 sector) +{ + if (EncryptionKey == -1) + { + u8 encryptedBuffer[512]; + u8 decryptedBuffer[512]; + SD->ReadFile("_DS_MENU.DAT", 0, 512, encryptedBuffer); + for (u32 key = 0; key < 0x10000; key++) + { + DecryptR4Sector(decryptedBuffer, encryptedBuffer, key); + if (decryptedBuffer[12] == '#' && decryptedBuffer[13] == '#' + && decryptedBuffer[14] == '#' && decryptedBuffer[15] == '#') + { + EncryptionKey = key; + break; + } + } + + if (EncryptionKey != -1) + { + Log(LogLevel::Warn, "R4: found cartridge key = %04X\n", EncryptionKey); + } + else + { + EncryptionKey = -2; + Log(LogLevel::Warn, "R4: could not find cartridge key\n"); + } + } + return EncryptionKey ^ sector; +} + +void CartR4::ReadSDToBuffer(u32 sector, bool rom) +{ + if (SD) + { + if (rom && FATEntryOffset[0] == 0xFFFFFFFF) + { + // On first boot, read from _DS_MENU.DAT. + SD->ReadFile("_DS_MENU.DAT", sector & ~0x1FF, 512, Buffer); + } + else + { + // Default mode. + SD->ReadSectors( + SDFATEntrySectorGet(FATEntryOffset[rom ? 0 : 1], sector) >> 9, + 1, Buffer + ); + } + BufferInitialized = true; + } +} + +u64 CartR4::SDFATEntrySectorGet(u32 entry, u32 addr) +{ + u8 buffer[512]; + u32 bufferSector = 0xFFFFFFFF; + + // Parse FAT header. + SD->ReadSectors(0, 1, buffer); + u16 bytesPerSector = (buffer[12] << 8) | buffer[11]; + u8 sectorsPerCluster = buffer[13]; + u16 firstFatSector = (buffer[15] << 8) | buffer[14]; + u8 fatTableCount = buffer[16]; + u32 clustersTotal = SD->GetSectorCount() / sectorsPerCluster; + bool isFat32 = clustersTotal >= 65526; + + u32 fatTableSize = (buffer[23] << 8) | buffer[22]; + if (fatTableSize == 0 && isFat32) + fatTableSize = (buffer[39] << 24) | (buffer[38] << 16) | (buffer[37] << 8) | buffer[36]; + u32 bytesPerCluster = bytesPerSector * sectorsPerCluster; + u32 rootDirSectors = 0; + if (!isFat32) { + u32 rootDirEntries = (buffer[18] << 8) | buffer[17]; + rootDirSectors = ((rootDirEntries * 32) + (bytesPerSector - 1)) / bytesPerSector; + } + u32 firstDataSector = firstFatSector + fatTableCount * fatTableSize + rootDirSectors; + + // Parse file entry (done when processing command 0xB4). + u32 clusterStart = entry; + + // Parse cluster table. + u32 currentCluster = clusterStart; + while (true) + { + currentCluster &= isFat32 ? 0x0FFFFFFF : 0xFFFF; + if (addr < bytesPerCluster) + { + // Read from this cluster. + return (u64) (firstDataSector + ((currentCluster - 2) * sectorsPerCluster)) * bytesPerSector + addr; + } + else if (currentCluster >= 2 && currentCluster <= (isFat32 ? 0x0FFFFFF6 : 0xFFF6)) + { + // Follow into next cluster. + u32 nextClusterOffset = firstFatSector * bytesPerSector + currentCluster * (isFat32 ? 4 : 2); + u32 nextClusterTableSector = nextClusterOffset >> 9; + if (bufferSector != nextClusterTableSector) + { + SD->ReadSectors(nextClusterTableSector, 1, buffer); + bufferSector = nextClusterTableSector; + } + nextClusterOffset &= 0x1FF; + currentCluster = (buffer[nextClusterOffset + 1] << 8) | buffer[nextClusterOffset]; + if (isFat32) + currentCluster |= (buffer[nextClusterOffset + 3] << 24) | (buffer[nextClusterOffset + 2] << 16); + addr -= bytesPerCluster; + } + else + { + // End of cluster table. + return 0; + } + } +} + +} +} diff --git a/src/NDS_Header.h b/src/NDS_Header.h index de75f7c7..77a5baca 100644 --- a/src/NDS_Header.h +++ b/src/NDS_Header.h @@ -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 eb5e1f2b..a3cc4b2e 100644 --- a/src/NonStupidBitfield.h +++ b/src/NonStupidBitfield.h @@ -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 { @@ -42,7 +69,7 @@ struct NonStupidBitField NonStupidBitField& BitField; u32 Idx; - operator bool() + operator bool() const { return BitField.Data[Idx >> 6] & (1ULL << (Idx & 0x3F)); } @@ -62,13 +89,13 @@ struct NonStupidBitField u32 BitIdx; u64 RemainingBits; - u32 operator*() { return DataIdx * 64 + BitIdx; } + u32 operator*() const { return DataIdx * 64 + BitIdx; } - bool operator==(const Iterator& other) + bool operator==(const Iterator& other) const { return other.DataIdx == DataIdx; } - bool operator!=(const Iterator& other) + bool operator!=(const Iterator& other) const { return other.DataIdx != DataIdx; } @@ -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..a7d000ce 100644 --- a/src/OpenGLSupport.cpp +++ b/src/OpenGLSupport.cpp @@ -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 %d\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..f8c44300 100644 --- a/src/OpenGLSupport.h +++ b/src/OpenGLSupport.h @@ -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 f8b0453c..425c712c 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -92,56 +92,6 @@ int InstanceID(); */ std::string InstanceFileSuffix(); -// configuration values - -enum ConfigEntry -{ -#ifdef JIT_ENABLED - JIT_Enable, - JIT_MaxBlockSize, - JIT_LiteralOptimizations, - JIT_BranchOptimizations, - JIT_FastMemory, -#endif - - ExternalBIOSEnable, - - DLDI_Enable, - DLDI_ImagePath, - DLDI_ImageSize, - DLDI_ReadOnly, - DLDI_FolderSync, - DLDI_FolderPath, - - DSiSD_Enable, - DSiSD_ImagePath, - DSiSD_ImageSize, - DSiSD_ReadOnly, - DSiSD_FolderSync, - DSiSD_FolderPath, - - Firm_MAC, - - WifiSettingsPath, - - AudioBitDepth, - - DSi_FullBIOSBoot, - -#ifdef GDBSTUB_ENABLED - GdbEnabled, - GdbPortARM7, - GdbPortARM9, - GdbARM7BreakOnStartup, - GdbARM9BreakOnStartup, -#endif -}; - -int GetConfigInt(ConfigEntry entry); -bool GetConfigBool(ConfigEntry entry); -std::string GetConfigString(ConfigEntry entry); -bool GetConfigArray(ConfigEntry entry, void* data); - /** * Denotes how a file will be opened and accessed. * Flags may or may not correspond to the operating system's file API. @@ -186,6 +136,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. @@ -251,6 +206,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. diff --git a/src/RTC.cpp b/src/RTC.cpp index b5e497a2..d8219df1 100644 --- a/src/RTC.cpp +++ b/src/RTC.cpp @@ -86,17 +86,17 @@ void RTC::DoSavestate(Savestate* file) } -u8 RTC::BCD(u8 val) +u8 RTC::BCD(u8 val) const { return (val % 10) | ((val / 10) << 4); } -u8 RTC::FromBCD(u8 val) +u8 RTC::FromBCD(u8 val) const { return (val & 0xF) + ((val >> 4) * 10); } -u8 RTC::BCDIncrement(u8 val) +u8 RTC::BCDIncrement(u8 val) const { val++; if ((val & 0x0F) >= 0x0A) @@ -106,7 +106,7 @@ u8 RTC::BCDIncrement(u8 val) return val; } -u8 RTC::BCDSanitize(u8 val, u8 vmin, u8 vmax) +u8 RTC::BCDSanitize(u8 val, u8 vmin, u8 vmax) const { if (val < vmin || val > vmax) val = vmin; @@ -119,12 +119,12 @@ u8 RTC::BCDSanitize(u8 val, u8 vmin, u8 vmax) } -void RTC::GetState(StateData& state) +void RTC::GetState(StateData& state) const { memcpy(&state, &State, sizeof(State)); } -void RTC::SetState(StateData& state) +void RTC::SetState(const StateData& state) { memcpy(&State, &state, sizeof(State)); @@ -134,7 +134,7 @@ void RTC::SetState(StateData& state) WriteDateTime(i+1, State.DateTime[i]); } -void RTC::GetDateTime(int& year, int& month, int& day, int& hour, int& minute, int& second) +void RTC::GetDateTime(int& year, int& month, int& day, int& hour, int& minute, int& second) const { year = FromBCD(State.DateTime[0]); year += 2000; @@ -374,7 +374,7 @@ void RTC::ProcessIRQ(int type) // 0=minute carry 1=periodic 2=status reg write } -u8 RTC::DaysInMonth() +u8 RTC::DaysInMonth() const { u8 numdays; diff --git a/src/RTC.h b/src/RTC.h index 0caf5ee5..1477e0eb 100644 --- a/src/RTC.h +++ b/src/RTC.h @@ -55,9 +55,9 @@ public: void DoSavestate(Savestate* file); - void GetState(StateData& state); - void SetState(StateData& state); - void GetDateTime(int& year, int& month, int& day, int& hour, int& minute, int& second); + void GetState(StateData& state) const; + void SetState(const StateData& state); + void GetDateTime(int& year, int& month, int& day, int& hour, int& minute, int& second) const; void SetDateTime(int year, int month, int day, int hour, int minute, int second); void ClockTimer(u32 param); @@ -87,16 +87,16 @@ private: void ResetState(); void ScheduleTimer(bool first); - u8 BCD(u8 val); - u8 FromBCD(u8 val); - u8 BCDIncrement(u8 val); - u8 BCDSanitize(u8 val, u8 vmin, u8 vmax); + u8 BCD(u8 val) const; + u8 FromBCD(u8 val) const; + u8 BCDIncrement(u8 val) const; + u8 BCDSanitize(u8 val, u8 vmin, u8 vmax) const; void SetIRQ(u8 irq); void ClearIRQ(u8 irq); void ProcessIRQ(int type); - u8 DaysInMonth(); + u8 DaysInMonth() const; void CountYear(); void CountMonth(); void CheckEndOfMonth(); diff --git a/src/SPI.cpp b/src/SPI.cpp index b3e5b4e1..2aa915c6 100644 --- a/src/SPI.cpp +++ b/src/SPI.cpp @@ -57,33 +57,24 @@ u16 CRC16(const u8* data, u32 len, u32 start) -bool FirmwareMem::VerifyCRC16(u32 start, u32 offset, u32 len, u32 crcoffset) +bool FirmwareMem::VerifyCRC16(u32 start, u32 offset, u32 len, u32 crcoffset) const { - u16 crc_stored = *(u16*)&Firmware->Buffer()[crcoffset]; - u16 crc_calced = CRC16(&Firmware->Buffer()[offset], len, start); + u16 crc_stored = *(u16*)&FirmwareData.Buffer()[crcoffset]; + u16 crc_calced = CRC16(&FirmwareData.Buffer()[offset], len, start); return (crc_stored == crc_calced); } -FirmwareMem::FirmwareMem(melonDS::NDS& nds) : SPIDevice(nds) +FirmwareMem::FirmwareMem(melonDS::NDS& nds, melonDS::Firmware&& firmware) : SPIDevice(nds), FirmwareData(std::move(firmware)) { } -FirmwareMem::~FirmwareMem() -{ - RemoveFirmware(); -} +FirmwareMem::~FirmwareMem() = default; void FirmwareMem::Reset() { - if (!Firmware) - { - Log(LogLevel::Warn, "SPI firmware: no firmware loaded! Using default\n"); - Firmware = std::make_unique(NDS.ConsoleType); - } - // fix touchscreen coords - for (auto& u : Firmware->GetUserData()) + for (auto& u : FirmwareData.GetUserData()) { u.TouchCalibrationADC1[0] = 0; u.TouchCalibrationADC1[1] = 0; @@ -95,17 +86,17 @@ void FirmwareMem::Reset() u.TouchCalibrationPixel2[1] = 191; } - Firmware->UpdateChecksums(); + FirmwareData.UpdateChecksums(); // disable autoboot //Firmware[userdata+0x64] &= 0xBF; - MacAddress mac = Firmware->GetHeader().MacAddr; + MacAddress mac = FirmwareData.GetHeader().MacAddr; Log(LogLevel::Info, "MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); // verify shit - u32 mask = Firmware->Mask(); - Log(LogLevel::Debug, "FW: WIFI CRC16 = %s\n", VerifyCRC16(0x0000, 0x2C, *(u16*)&Firmware->Buffer()[0x2C], 0x2A)?"GOOD":"BAD"); + u32 mask = FirmwareData.Mask(); + Log(LogLevel::Debug, "FW: WIFI CRC16 = %s\n", VerifyCRC16(0x0000, 0x2C, *(u16*)&FirmwareData.Buffer()[0x2C], 0x2A)?"GOOD":"BAD"); Log(LogLevel::Debug, "FW: AP1 CRC16 = %s\n", VerifyCRC16(0x0000, 0x7FA00&mask, 0xFE, 0x7FAFE&mask)?"GOOD":"BAD"); Log(LogLevel::Debug, "FW: AP2 CRC16 = %s\n", VerifyCRC16(0x0000, 0x7FB00&mask, 0xFE, 0x7FBFE&mask)?"GOOD":"BAD"); Log(LogLevel::Debug, "FW: AP3 CRC16 = %s\n", VerifyCRC16(0x0000, 0x7FC00&mask, 0xFE, 0x7FCFE&mask)?"GOOD":"BAD"); @@ -136,8 +127,8 @@ void FirmwareMem::DoSavestate(Savestate* file) void FirmwareMem::SetupDirectBoot() { - const auto& header = Firmware->GetHeader(); - const auto& userdata = Firmware->GetEffectiveUserData(); + const auto& header = FirmwareData.GetHeader(); + const auto& userdata = FirmwareData.GetEffectiveUserData(); if (NDS.ConsoleType == 1) { // The ARMWrite methods are virtual, they'll delegate to DSi if necessary @@ -163,58 +154,9 @@ void FirmwareMem::SetupDirectBoot() } } -const class Firmware* FirmwareMem::GetFirmware() +bool FirmwareMem::IsLoadedFirmwareBuiltIn() const { - return Firmware.get(); -} - -bool FirmwareMem::IsLoadedFirmwareBuiltIn() -{ - return Firmware->GetHeader().Identifier == GENERATED_FIRMWARE_IDENTIFIER; -} - -bool FirmwareMem::InstallFirmware(class Firmware&& firmware) -{ - if (!firmware.Buffer()) - { - Log(LogLevel::Error, "SPI firmware: firmware buffer is null!\n"); - return false; - } - - Firmware = std::make_unique(std::move(firmware)); - - FirmwareIdentifier id = Firmware->GetHeader().Identifier; - Log(LogLevel::Debug, "Installed firmware (Identifier: %c%c%c%c)\n", id[0], id[1], id[2], id[3]); - - return true; -} - -bool FirmwareMem::InstallFirmware(std::unique_ptr&& firmware) -{ - if (!firmware) - { - Log(LogLevel::Error, "SPI firmware: firmware is null!\n"); - return false; - } - - if (!firmware->Buffer()) - { - Log(LogLevel::Error, "SPI firmware: firmware buffer is null!\n"); - return false; - } - - Firmware = std::move(firmware); - - FirmwareIdentifier id = Firmware->GetHeader().Identifier; - Log(LogLevel::Debug, "Installed firmware (Identifier: %c%c%c%c)\n", id[0], id[1], id[2], id[3]); - - return true; -} - -void FirmwareMem::RemoveFirmware() -{ - Firmware.reset(); - Log(LogLevel::Debug, "Removed installed firmware (if any)\n"); + return FirmwareData.GetHeader().Identifier == GENERATED_FIRMWARE_IDENTIFIER; } void FirmwareMem::Write(u8 val) @@ -256,7 +198,7 @@ void FirmwareMem::Write(u8 val) } else { - Data = Firmware->Buffer()[Addr & Firmware->Mask()]; + Data = FirmwareData.Buffer()[Addr & FirmwareData.Mask()]; Addr++; } @@ -279,7 +221,7 @@ void FirmwareMem::Write(u8 val) } else { - Firmware->Buffer()[Addr & Firmware->Mask()] = val; + FirmwareData.Buffer()[Addr & FirmwareData.Mask()] = val; Data = val; Addr++; } @@ -314,11 +256,11 @@ void FirmwareMem::Release() { // If the SPI firmware chip just finished a write... // We only notify the frontend of changes to the Wi-fi/userdata settings region // (although it might still decide to flush the whole thing) - u32 wifioffset = Firmware->GetWifiAccessPointOffset(); + u32 wifioffset = FirmwareData.GetWifiAccessPointOffset(); // Request that the start of the Wi-fi/userdata settings region // through the end of the firmware blob be flushed to disk - Platform::WriteFirmware(*Firmware, wifioffset, Firmware->Length() - wifioffset); + Platform::WriteFirmware(FirmwareData, wifioffset, FirmwareData.Length() - wifioffset); } SPIDevice::Release(); @@ -366,7 +308,7 @@ void PowerMan::DoSavestate(Savestate* file) file->VarArray(RegMasks, 8); // is that needed?? } -bool PowerMan::GetBatteryLevelOkay() { return !Registers[1]; } +bool PowerMan::GetBatteryLevelOkay() const { return !Registers[1]; } void PowerMan::SetBatteryLevelOkay(bool okay) { Registers[1] = okay ? 0x00 : 0x01; } void PowerMan::Write(u8 val) @@ -462,7 +404,7 @@ void TSC::SetTouchCoords(u16 x, u16 y) NDS.KeyInput &= ~(1 << (16+6)); } -void TSC::MicInputFrame(s16* data, int samples) +void TSC::MicInputFrame(const s16* data, int samples) { if (!data) { @@ -530,11 +472,11 @@ void TSC::Write(u8 val) -SPIHost::SPIHost(melonDS::NDS& nds) : NDS(nds) +SPIHost::SPIHost(melonDS::NDS& nds, Firmware&& firmware) : NDS(nds) { NDS.RegisterEventFunc(Event_SPITransfer, 0, MemberEventFunc(SPIHost, TransferDone)); - Devices[SPIDevice_FirmwareMem] = new FirmwareMem(NDS); + Devices[SPIDevice_FirmwareMem] = new FirmwareMem(NDS, std::move(firmware)); Devices[SPIDevice_PowerMan] = new PowerMan(NDS); if (NDS.ConsoleType == 1) @@ -607,7 +549,7 @@ void SPIHost::TransferDone(u32 param) NDS.SetIRQ(1, IRQ_SPI); } -u8 SPIHost::ReadData() +u8 SPIHost::ReadData() const { if (!(Cnt & (1<<15))) return 0; if (Cnt & (1<<7)) return 0; // checkme diff --git a/src/SPI.h b/src/SPI.h index f27f0c3e..7ed889a4 100644 --- a/src/SPI.h +++ b/src/SPI.h @@ -51,7 +51,7 @@ public: virtual void Reset() = 0; virtual void DoSavestate(Savestate* file) = 0; - virtual u8 Read() { return Data; } + virtual u8 Read() const { return Data; } virtual void Write(u8 val) = 0; virtual void Release() { Hold = false; DataPos = 0; } @@ -66,31 +66,30 @@ protected: class FirmwareMem : public SPIDevice { public: - FirmwareMem(melonDS::NDS& nds); + FirmwareMem(melonDS::NDS& nds, melonDS::Firmware&& firmware); ~FirmwareMem() override; void Reset() override; void DoSavestate(Savestate* file) override; void SetupDirectBoot(); - const class Firmware* GetFirmware(); - bool IsLoadedFirmwareBuiltIn(); - bool InstallFirmware(class Firmware&& firmware); - bool InstallFirmware(std::unique_ptr&& firmware); - void RemoveFirmware(); + Firmware& GetFirmware() noexcept { return FirmwareData; } + [[nodiscard]] const Firmware& GetFirmware() const noexcept { return FirmwareData; } + void SetFirmware(Firmware&& firmware) { FirmwareData = std::move(firmware); } + bool IsLoadedFirmwareBuiltIn() const; void Write(u8 val) override; void Release() override; private: - std::unique_ptr Firmware; + Firmware FirmwareData; u8 CurCmd; u8 StatusReg; u32 Addr; - bool VerifyCRC16(u32 start, u32 offset, u32 len, u32 crcoffset); + bool VerifyCRC16(u32 start, u32 offset, u32 len, u32 crcoffset) const; }; class PowerMan : public SPIDevice @@ -101,7 +100,7 @@ public: void Reset() override; void DoSavestate(Savestate* file) override; - bool GetBatteryLevelOkay(); + bool GetBatteryLevelOkay() const; void SetBatteryLevelOkay(bool okay); void Write(u8 val) override; @@ -122,7 +121,7 @@ public: virtual void DoSavestate(Savestate* file) override; virtual void SetTouchCoords(u16 x, u16 y); - virtual void MicInputFrame(s16* data, int samples); + virtual void MicInputFrame(const s16* data, int samples); virtual void Write(u8 val) override; @@ -141,21 +140,26 @@ protected: class SPIHost { public: - SPIHost(melonDS::NDS& nds); + SPIHost(melonDS::NDS& nds, Firmware&& firmware); ~SPIHost(); void Reset(); void DoSavestate(Savestate* file); FirmwareMem* GetFirmwareMem() { return (FirmwareMem*)Devices[SPIDevice_FirmwareMem]; } + const FirmwareMem* GetFirmwareMem() const { return (FirmwareMem*)Devices[SPIDevice_FirmwareMem]; } PowerMan* GetPowerMan() { return (PowerMan*)Devices[SPIDevice_PowerMan]; } + const PowerMan* GetPowerMan() const { return (PowerMan*)Devices[SPIDevice_PowerMan]; } TSC* GetTSC() { return (TSC*)Devices[SPIDevice_TSC]; } + const TSC* GetTSC() const { return (TSC*)Devices[SPIDevice_TSC]; } - const Firmware* GetFirmware() { return GetFirmwareMem()->GetFirmware(); } + const Firmware& GetFirmware() const { return GetFirmwareMem()->GetFirmware(); } + Firmware& GetFirmware() { return GetFirmwareMem()->GetFirmware(); } + void SetFirmware(Firmware&& firmware) { GetFirmwareMem()->SetFirmware(std::move(firmware)); } - u16 ReadCnt() { return Cnt; } + u16 ReadCnt() const { return Cnt; } void WriteCnt(u16 val); - u8 ReadData(); + u8 ReadData() const; void WriteData(u8 val); void TransferDone(u32 param); diff --git a/src/SPU.cpp b/src/SPU.cpp index 00db3691..69c0b9de 100644 --- a/src/SPU.cpp +++ b/src/SPU.cpp @@ -65,23 +65,145 @@ const s16 SPUChannel::PSGTable[8][8] = {-0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF} }; -s16 SPUChannel::InterpCos[0x100]; -s16 SPUChannel::InterpCubic[0x100][4]; -bool SPUChannel::InterpInited = false; +template +constexpr T ipow(T num, unsigned int pow) +{ + T product = 1; + for (int i = 0; i < pow; ++i) + { + product *= num; + } + return product; +} -SPU::SPU(melonDS::NDS& nds) : NDS(nds) +template +constexpr T factorial(T num) +{ + T product = 1; + for (T i = 1; i <= num; ++i) + { + product *= i; + } + + return product; +} + +// We can't use std::cos in constexpr functions until C++26, +// so we need to compute the cosine ourselves with the Taylor series. +// Code adapted from https://prosepoetrycode.potterpcs.net/2015/07/a-simple-constexpr-power-function-c/ +template +constexpr double cosine (double theta) +{ + return (ipow(-1, Iterations) * ipow(theta, 2 * Iterations)) / + static_cast(factorial(2ull * Iterations)) + + cosine(theta); +} + +template <> +constexpr double cosine<0> (double theta) +{ + return 1.0; +} + +// generate interpolation tables +// values are 1:1:14 fixed-point +constexpr std::array InterpCos = []() constexpr { + std::array interp {}; + + for (int i = 0; i < 0x100; i++) + { + float ratio = (i * M_PI) / 255.0f; + ratio = 1.0f - cosine(ratio); + + interp[i] = (s16)(ratio * 0x2000); + } + + return interp; +}(); + +constexpr array2d InterpCubic = []() constexpr { + array2d interp {}; + + for (int i = 0; i < 0x100; i++) + { + s32 i1 = i << 6; + s32 i2 = (i * i) >> 2; + s32 i3 = (i * i * i) >> 10; + + interp[i][0] = -i3 + 2*i2 - i1; + interp[i][1] = i3 - 2*i2 + 0x4000; + interp[i][2] = -i3 + i2 + i1; + interp[i][3] = i3 - i2; + } + + 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 { + SPUChannel(0, nds, interpolation), + SPUChannel(1, nds, interpolation), + SPUChannel(2, nds, interpolation), + SPUChannel(3, nds, interpolation), + SPUChannel(4, nds, interpolation), + SPUChannel(5, nds, interpolation), + SPUChannel(6, nds, interpolation), + SPUChannel(7, nds, interpolation), + SPUChannel(8, nds, interpolation), + SPUChannel(9, nds, interpolation), + SPUChannel(10, nds, interpolation), + SPUChannel(11, nds, interpolation), + SPUChannel(12, nds, interpolation), + SPUChannel(13, nds, interpolation), + SPUChannel(14, nds, interpolation), + SPUChannel(15, nds, interpolation), + }, + Capture { + SPUCaptureUnit(0, nds), + SPUCaptureUnit(1, nds), + }, + AudioLock(Platform::Mutex_Create()), + Degrade10Bit(bitdepth == AudioBitDepth::_10Bit || (nds.ConsoleType == 1 && bitdepth == AudioBitDepth::Auto)) { NDS.RegisterEventFunc(Event_SPU, 0, MemberEventFunc(SPU, Mix)); - for (int i = 0; i < 16; i++) - Channels[i] = new SPUChannel(i, NDS); - - Capture[0] = new SPUCaptureUnit(0, NDS); - Capture[1] = new SPUCaptureUnit(1, NDS); - - AudioLock = Platform::Mutex_Create(); - ApplyBias = true; Degrade10Bit = false; @@ -90,50 +212,10 @@ SPU::SPU(melonDS::NDS& nds) : NDS(nds) OutputBackbufferWritePosition = 0; OutputFrontBufferReadPosition = 0; OutputFrontBufferWritePosition = 0; - - if (!SPUChannel::InterpInited) - { - // generate interpolation tables - // values are 1:1:14 fixed-point - - float m_pi = std::acos(-1.0f); - for (int i = 0; i < 0x100; i++) - { - float ratio = (i * m_pi) / 255.0f; - ratio = 1.0f - std::cos(ratio); - - SPUChannel::InterpCos[i] = (s16)(ratio * 0x2000); - } - - for (int i = 0; i < 0x100; i++) - { - s32 i1 = i << 6; - s32 i2 = (i * i) >> 2; - s32 i3 = (i * i * i) >> 10; - - SPUChannel::InterpCubic[i][0] = -i3 + 2*i2 - i1; - SPUChannel::InterpCubic[i][1] = i3 - 2*i2 + 0x4000; - SPUChannel::InterpCubic[i][2] = -i3 + i2 + i1; - SPUChannel::InterpCubic[i][3] = i3 - i2; - } - - SPUChannel::InterpInited = true; - } } SPU::~SPU() { - for (int i = 0; i < 16; i++) - { - delete Channels[i]; - Channels[i] = nullptr; - } - - delete Capture[0]; - delete Capture[1]; - Capture[0] = nullptr; - Capture[1] = nullptr; - Platform::Mutex_Free(AudioLock); AudioLock = nullptr; @@ -149,10 +231,10 @@ void SPU::Reset() Bias = 0; for (int i = 0; i < 16; i++) - Channels[i]->Reset(); + Channels[i].Reset(); - Capture[0]->Reset(); - Capture[1]->Reset(); + Capture[0].Reset(); + Capture[1].Reset(); NDS.ScheduleEvent(Event_SPU, false, 1024, 0, 0); } @@ -176,11 +258,11 @@ void SPU::DoSavestate(Savestate* file) file->Var8(&MasterVolume); file->Var16(&Bias); - for (int i = 0; i < 16; i++) - Channels[i]->DoSavestate(file); + for (SPUChannel& channel : Channels) + channel.DoSavestate(file); - Capture[0]->DoSavestate(file); - Capture[1]->DoSavestate(file); + for (SPUCaptureUnit& capture : Capture) + capture.DoSavestate(file); } @@ -190,10 +272,10 @@ void SPU::SetPowerCnt(u32 val) } -void SPU::SetInterpolation(int type) +void SPU::SetInterpolation(AudioInterpolation type) { - for (int i = 0; i < 16; i++) - Channels[i]->InterpType = type; + for (SPUChannel& channel : Channels) + channel.InterpType = type; } void SPU::SetBias(u16 bias) @@ -211,15 +293,26 @@ void SPU::SetDegrade10Bit(bool enable) Degrade10Bit = enable; } - -SPUChannel::SPUChannel(u32 num, melonDS::NDS& nds) : NDS(nds) +void SPU::SetDegrade10Bit(AudioBitDepth depth) { - Num = num; - - InterpType = 0; + switch (depth) + { + case AudioBitDepth::Auto: + Degrade10Bit = (NDS.ConsoleType == 0); + break; + case AudioBitDepth::_10Bit: + Degrade10Bit = true; + break; + case AudioBitDepth::_16Bit: + Degrade10Bit = false; + break; + } } -SPUChannel::~SPUChannel() +SPUChannel::SPUChannel(u32 num, melonDS::NDS& nds, AudioInterpolation interpolation) : + NDS(nds), + Num(num), + InterpType(interpolation) { } @@ -520,7 +613,7 @@ s32 SPUChannel::Run() // for optional interpolation: save previous samples // the interpolated audio will be delayed by a couple samples, // but it's easier to deal with this way - if ((type < 3) && (InterpType != 0)) + if ((type < 3) && (InterpType != AudioInterpolation::None)) { PrevSample[2] = PrevSample[1]; PrevSample[1] = PrevSample[0]; @@ -540,29 +633,44 @@ s32 SPUChannel::Run() s32 val = (s32)CurSample; // interpolation (emulation improvement, not a hardware feature) - if ((type < 3) && (InterpType != 0)) + if ((type < 3) && (InterpType != AudioInterpolation::None)) { s32 samplepos = ((Timer - TimerReload) * 0x100) / (0x10000 - TimerReload); if (samplepos > 0xFF) samplepos = 0xFF; switch (InterpType) { - case 1: // linear + case AudioInterpolation::Linear: val = ((val * samplepos) + (PrevSample[0] * (0xFF-samplepos))) >> 8; break; - case 2: // cosine + case AudioInterpolation::Cosine: val = ((val * InterpCos[samplepos]) + (PrevSample[0] * InterpCos[0xFF-samplepos])) >> 14; break; - case 3: // cubic + case AudioInterpolation::Cubic: val = ((PrevSample[2] * InterpCubic[samplepos][0]) + (PrevSample[1] * InterpCubic[samplepos][1]) + (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; } } @@ -579,11 +687,6 @@ void SPUChannel::PanOutput(s32 in, s32& left, s32& right) SPUCaptureUnit::SPUCaptureUnit(u32 num, melonDS::NDS& nds) : NDS(nds), Num(num) -{ - Num = num; -} - -SPUCaptureUnit::~SPUCaptureUnit() { } @@ -713,21 +816,21 @@ void SPU::Mix(u32 dummy) if ((Cnt & (1<<15)) && (!dummy)) { - s32 ch0 = Channels[0]->DoRun(); - s32 ch1 = Channels[1]->DoRun(); - s32 ch2 = Channels[2]->DoRun(); - s32 ch3 = Channels[3]->DoRun(); + s32 ch0 = Channels[0].DoRun(); + s32 ch1 = Channels[1].DoRun(); + s32 ch2 = Channels[2].DoRun(); + s32 ch3 = Channels[3].DoRun(); // TODO: addition from capture registers - Channels[0]->PanOutput(ch0, left, right); - Channels[2]->PanOutput(ch2, left, right); + Channels[0].PanOutput(ch0, left, right); + Channels[2].PanOutput(ch2, left, right); - if (!(Cnt & (1<<12))) Channels[1]->PanOutput(ch1, left, right); - if (!(Cnt & (1<<13))) Channels[3]->PanOutput(ch3, left, right); + if (!(Cnt & (1<<12))) Channels[1].PanOutput(ch1, left, right); + if (!(Cnt & (1<<13))) Channels[3].PanOutput(ch3, left, right); for (int i = 4; i < 16; i++) { - SPUChannel* chan = Channels[i]; + SPUChannel* chan = &Channels[i]; s32 channel = chan->DoRun(); chan->PanOutput(channel, left, right); @@ -736,7 +839,7 @@ void SPU::Mix(u32 dummy) // sound capture // TODO: other sound capture sources, along with their bugs - if (Capture[0]->Cnt & (1<<7)) + if (Capture[0].Cnt & (1<<7)) { s32 val = left; @@ -744,10 +847,10 @@ void SPU::Mix(u32 dummy) if (val < -0x8000) val = -0x8000; else if (val > 0x7FFF) val = 0x7FFF; - Capture[0]->Run(val); + Capture[0].Run(val); } - if (Capture[1]->Cnt & (1<<7)) + if (Capture[1].Cnt & (1<<7)) { s32 val = right; @@ -755,7 +858,7 @@ void SPU::Mix(u32 dummy) if (val < -0x8000) val = -0x8000; else if (val > 0x7FFF) val = 0x7FFF; - Capture[1]->Run(val); + Capture[1].Run(val); } // final output @@ -767,20 +870,20 @@ void SPU::Mix(u32 dummy) break; case 0x0100: // channel 1 { - s32 pan = 128 - Channels[1]->Pan; + s32 pan = 128 - Channels[1].Pan; leftoutput = ((s64)ch1 * pan) >> 10; } break; case 0x0200: // channel 3 { - s32 pan = 128 - Channels[3]->Pan; + s32 pan = 128 - Channels[3].Pan; leftoutput = ((s64)ch3 * pan) >> 10; } break; case 0x0300: // channel 1+3 { - s32 pan1 = 128 - Channels[1]->Pan; - s32 pan3 = 128 - Channels[3]->Pan; + s32 pan1 = 128 - Channels[1].Pan; + s32 pan3 = 128 - Channels[3].Pan; leftoutput = (((s64)ch1 * pan1) >> 10) + (((s64)ch3 * pan3) >> 10); } break; @@ -793,20 +896,20 @@ void SPU::Mix(u32 dummy) break; case 0x0400: // channel 1 { - s32 pan = Channels[1]->Pan; + s32 pan = Channels[1].Pan; rightoutput = ((s64)ch1 * pan) >> 10; } break; case 0x0800: // channel 3 { - s32 pan = Channels[3]->Pan; + s32 pan = Channels[3].Pan; rightoutput = ((s64)ch3 * pan) >> 10; } break; case 0x0C00: // channel 1+3 { - s32 pan1 = Channels[1]->Pan; - s32 pan3 = Channels[3]->Pan; + s32 pan1 = Channels[1].Pan; + s32 pan3 = Channels[3].Pan; rightoutput = (((s64)ch1 * pan1) >> 10) + (((s64)ch3 * pan3) >> 10); } break; @@ -903,7 +1006,7 @@ void SPU::InitOutput() Platform::Mutex_Unlock(AudioLock); } -int SPU::GetOutputSize() +int SPU::GetOutputSize() const { Platform::Mutex_Lock(AudioLock); @@ -982,7 +1085,7 @@ u8 SPU::Read8(u32 addr) { if (addr < 0x04000500) { - SPUChannel* chan = Channels[(addr >> 4) & 0xF]; + SPUChannel* chan = &Channels[(addr >> 4) & 0xF]; switch (addr & 0xF) { @@ -999,8 +1102,8 @@ u8 SPU::Read8(u32 addr) case 0x04000500: return Cnt & 0x7F; case 0x04000501: return Cnt >> 8; - case 0x04000508: return Capture[0]->Cnt; - case 0x04000509: return Capture[1]->Cnt; + case 0x04000508: return Capture[0].Cnt; + case 0x04000509: return Capture[1].Cnt; } } @@ -1012,7 +1115,7 @@ u16 SPU::Read16(u32 addr) { if (addr < 0x04000500) { - SPUChannel* chan = Channels[(addr >> 4) & 0xF]; + SPUChannel* chan = &Channels[(addr >> 4) & 0xF]; switch (addr & 0xF) { @@ -1027,7 +1130,7 @@ u16 SPU::Read16(u32 addr) case 0x04000500: return Cnt; case 0x04000504: return Bias; - case 0x04000508: return Capture[0]->Cnt | (Capture[1]->Cnt << 8); + case 0x04000508: return Capture[0].Cnt | (Capture[1].Cnt << 8); } } @@ -1039,7 +1142,7 @@ u32 SPU::Read32(u32 addr) { if (addr < 0x04000500) { - SPUChannel* chan = Channels[(addr >> 4) & 0xF]; + SPUChannel* chan = &Channels[(addr >> 4) & 0xF]; switch (addr & 0xF) { @@ -1053,10 +1156,10 @@ u32 SPU::Read32(u32 addr) case 0x04000500: return Cnt; case 0x04000504: return Bias; - case 0x04000508: return Capture[0]->Cnt | (Capture[1]->Cnt << 8); + case 0x04000508: return Capture[0].Cnt | (Capture[1].Cnt << 8); - case 0x04000510: return Capture[0]->DstAddr; - case 0x04000518: return Capture[1]->DstAddr; + case 0x04000510: return Capture[0].DstAddr; + case 0x04000518: return Capture[1].DstAddr; } } @@ -1068,7 +1171,7 @@ void SPU::Write8(u32 addr, u8 val) { if (addr < 0x04000500) { - SPUChannel* chan = Channels[(addr >> 4) & 0xF]; + SPUChannel* chan = &Channels[(addr >> 4) & 0xF]; switch (addr & 0xF) { @@ -1092,11 +1195,11 @@ void SPU::Write8(u32 addr, u8 val) return; case 0x04000508: - Capture[0]->SetCnt(val); + Capture[0].SetCnt(val); if (val & 0x03) Log(LogLevel::Warn, "!! UNSUPPORTED SPU CAPTURE MODE %02X\n", val); return; case 0x04000509: - Capture[1]->SetCnt(val); + Capture[1].SetCnt(val); if (val & 0x03) Log(LogLevel::Warn, "!! UNSUPPORTED SPU CAPTURE MODE %02X\n", val); return; } @@ -1109,7 +1212,7 @@ void SPU::Write16(u32 addr, u16 val) { if (addr < 0x04000500) { - SPUChannel* chan = Channels[(addr >> 4) & 0xF]; + SPUChannel* chan = &Channels[(addr >> 4) & 0xF]; switch (addr & 0xF) { @@ -1117,8 +1220,8 @@ void SPU::Write16(u32 addr, u16 val) case 0x2: chan->SetCnt((chan->Cnt & 0x0000FFFF) | (val << 16)); return; case 0x8: chan->SetTimerReload(val); - if ((addr & 0xF0) == 0x10) Capture[0]->SetTimerReload(val); - else if ((addr & 0xF0) == 0x30) Capture[1]->SetTimerReload(val); + if ((addr & 0xF0) == 0x10) Capture[0].SetTimerReload(val); + else if ((addr & 0xF0) == 0x30) Capture[1].SetTimerReload(val); return; case 0xA: chan->SetLoopPos(val); return; @@ -1141,13 +1244,13 @@ void SPU::Write16(u32 addr, u16 val) return; case 0x04000508: - Capture[0]->SetCnt(val & 0xFF); - Capture[1]->SetCnt(val >> 8); + Capture[0].SetCnt(val & 0xFF); + Capture[1].SetCnt(val >> 8); if (val & 0x0303) Log(LogLevel::Warn, "!! UNSUPPORTED SPU CAPTURE MODE %04X\n", val); return; - case 0x04000514: Capture[0]->SetLength(val); return; - case 0x0400051C: Capture[1]->SetLength(val); return; + case 0x04000514: Capture[0].SetLength(val); return; + case 0x0400051C: Capture[1].SetLength(val); return; } } @@ -1158,7 +1261,7 @@ void SPU::Write32(u32 addr, u32 val) { if (addr < 0x04000500) { - SPUChannel* chan = Channels[(addr >> 4) & 0xF]; + SPUChannel* chan = &Channels[(addr >> 4) & 0xF]; switch (addr & 0xF) { @@ -1168,8 +1271,8 @@ void SPU::Write32(u32 addr, u32 val) chan->SetLoopPos(val >> 16); val &= 0xFFFF; chan->SetTimerReload(val); - if ((addr & 0xF0) == 0x10) Capture[0]->SetTimerReload(val); - else if ((addr & 0xF0) == 0x30) Capture[1]->SetTimerReload(val); + if ((addr & 0xF0) == 0x10) Capture[0].SetTimerReload(val); + else if ((addr & 0xF0) == 0x30) Capture[1].SetTimerReload(val); return; case 0xC: chan->SetLength(val); return; } @@ -1189,17 +1292,17 @@ void SPU::Write32(u32 addr, u32 val) return; case 0x04000508: - Capture[0]->SetCnt(val & 0xFF); - Capture[1]->SetCnt(val >> 8); + Capture[0].SetCnt(val & 0xFF); + Capture[1].SetCnt(val >> 8); if (val & 0x0303) Log(LogLevel::Warn, "!! UNSUPPORTED SPU CAPTURE MODE %04X\n", val); return; - case 0x04000510: Capture[0]->SetDstAddr(val); return; - case 0x04000514: Capture[0]->SetLength(val & 0xFFFF); return; - case 0x04000518: Capture[1]->SetDstAddr(val); return; - case 0x0400051C: Capture[1]->SetLength(val & 0xFFFF); return; + case 0x04000510: Capture[0].SetDstAddr(val); return; + case 0x04000514: Capture[0].SetLength(val & 0xFFFF); return; + case 0x04000518: Capture[1].SetDstAddr(val); return; + case 0x0400051C: Capture[1].SetLength(val & 0xFFFF); return; } } } -} \ No newline at end of file +} diff --git a/src/SPU.h b/src/SPU.h index bf0c658b..6e3d1aae 100644 --- a/src/SPU.h +++ b/src/SPU.h @@ -27,11 +27,26 @@ namespace melonDS class NDS; class SPU; +enum class AudioBitDepth +{ + Auto, + _10Bit, + _16Bit, +}; + +enum class AudioInterpolation +{ + None, + Linear, + Cosine, + Cubic, + SNESGaussian +}; + class SPUChannel { public: - SPUChannel(u32 num, melonDS::NDS& nds); - ~SPUChannel(); + SPUChannel(u32 num, melonDS::NDS& nds, AudioInterpolation interpolation); void Reset(); void DoSavestate(Savestate* file); @@ -39,44 +54,40 @@ public: static const u16 ADPCMTable[89]; static const s16 PSGTable[8][8]; - static s16 InterpCos[0x100]; - static s16 InterpCubic[0x100][4]; - static bool InterpInited; - // audio interpolation is an improvement upon the original hardware // (which performs no interpolation) - int InterpType; + AudioInterpolation InterpType = AudioInterpolation::None; - u32 Num; + const u32 Num; - u32 Cnt; - u32 SrcAddr; - u16 TimerReload; - u32 LoopPos; - u32 Length; + u32 Cnt = 0; + u32 SrcAddr = 0; + u16 TimerReload = 0; + u32 LoopPos = 0; + u32 Length = 0; - u8 Volume; - u8 VolumeShift; - u8 Pan; + u8 Volume = 0; + u8 VolumeShift = 0; + u8 Pan = 0; - bool KeyOn; - u32 Timer; - s32 Pos; - s16 PrevSample[3]; - s16 CurSample; - u16 NoiseVal; + bool KeyOn = false; + u32 Timer = 0; + s32 Pos = 0; + s16 PrevSample[3] {}; + s16 CurSample = 0; + u16 NoiseVal = 0; - s32 ADPCMVal; - s32 ADPCMIndex; - s32 ADPCMValLoop; - s32 ADPCMIndexLoop; - u8 ADPCMCurByte; + s32 ADPCMVal = 0; + s32 ADPCMIndex = 0; + s32 ADPCMValLoop = 0; + s32 ADPCMIndexLoop = 0; + u8 ADPCMCurByte = 0; - u32 FIFO[8]; - u32 FIFOReadPos; - u32 FIFOWritePos; - u32 FIFOReadOffset; - u32 FIFOLevel; + u32 FIFO[8] {}; + u32 FIFOReadPos = 0; + u32 FIFOWritePos = 0; + u32 FIFOReadOffset = 0; + u32 FIFOLevel = 0; void FIFO_BufferData(); template T FIFO_ReadData(); @@ -150,25 +161,24 @@ class SPUCaptureUnit { public: SPUCaptureUnit(u32 num, melonDS::NDS&); - ~SPUCaptureUnit(); void Reset(); void DoSavestate(Savestate* file); - u32 Num; + const u32 Num; - u8 Cnt; - u32 DstAddr; - u16 TimerReload; - u32 Length; + u8 Cnt = 0; + u32 DstAddr = 0; + u16 TimerReload = 0; + u32 Length = 0; - u32 Timer; - s32 Pos; + u32 Timer = 0; + s32 Pos = 0; - u32 FIFO[4]; - u32 FIFOReadPos; - u32 FIFOWritePos; - u32 FIFOWriteOffset; - u32 FIFOLevel; + u32 FIFO[4] {}; + u32 FIFOReadPos = 0; + u32 FIFOWritePos = 0; + u32 FIFOWriteOffset = 0; + u32 FIFOLevel = 0; void FIFO_FlushData(); template void FIFO_WriteData(T val); @@ -206,7 +216,7 @@ private: class SPU { public: - SPU(melonDS::NDS& nds); + explicit SPU(melonDS::NDS& nds, AudioBitDepth bitdepth, AudioInterpolation interpolation); ~SPU(); void Reset(); void DoSavestate(Savestate* file); @@ -216,10 +226,11 @@ public: void SetPowerCnt(u32 val); // 0=none 1=linear 2=cosine 3=cubic - void SetInterpolation(int type); + void SetInterpolation(AudioInterpolation type); void SetBias(u16 bias); void SetDegrade10Bit(bool enable); + void SetDegrade10Bit(AudioBitDepth depth); void SetApplyBias(bool enable); void Mix(u32 dummy); @@ -227,7 +238,7 @@ public: void TrimOutput(); void DrainOutput(); void InitOutput(); - int GetOutputSize(); + int GetOutputSize() const; void Sync(bool wait); int ReadOutput(s16* data, int samples); void TransferOutput(); @@ -242,23 +253,23 @@ public: private: static const u32 OutputBufferSize = 2*2048; melonDS::NDS& NDS; - s16 OutputBackbuffer[2 * OutputBufferSize]; - u32 OutputBackbufferWritePosition; + s16 OutputBackbuffer[2 * OutputBufferSize] {}; + u32 OutputBackbufferWritePosition = 0; - s16 OutputFrontBuffer[2 * OutputBufferSize]; - u32 OutputFrontBufferWritePosition; - u32 OutputFrontBufferReadPosition; + s16 OutputFrontBuffer[2 * OutputBufferSize] {}; + u32 OutputFrontBufferWritePosition = 0; + u32 OutputFrontBufferReadPosition = 0; Platform::Mutex* AudioLock; - u16 Cnt; - u8 MasterVolume; - u16 Bias; - bool ApplyBias; - bool Degrade10Bit; + u16 Cnt = 0; + u8 MasterVolume = 0; + u16 Bias = 0; + bool ApplyBias = true; + bool Degrade10Bit = false; - SPUChannel* Channels[16]; - SPUCaptureUnit* Capture[2]; + std::array Channels; + std::array Capture; }; } diff --git a/src/Savestate.h b/src/Savestate.h index 236aa643..2e1400a0 100644 --- a/src/Savestate.h +++ b/src/Savestate.h @@ -24,7 +24,7 @@ #include #include "types.h" -#define SAVESTATE_MAJOR 11 +#define SAVESTATE_MAJOR 12 #define SAVESTATE_MINOR 1 namespace melonDS diff --git a/src/TinyVector.h b/src/TinyVector.h index 1904f2ad..5a30ff65 100644 --- a/src/TinyVector.h +++ b/src/TinyVector.h @@ -97,7 +97,7 @@ struct __attribute__((packed)) TinyVector Data[i] = Data[i + 1];*/ } - int Find(T needle) + int Find(T needle) const { for (int i = 0; i < Length; i++) { @@ -125,6 +125,12 @@ struct __attribute__((packed)) TinyVector assert(index >= 0 && index < Length); return Data[index]; } + + const T& operator[](int index) const + { + assert(index >= 0 && index < Length); + return Data[index]; + } }; } diff --git a/src/Utils.cpp b/src/Utils.cpp new file mode 100644 index 00000000..698cf9bd --- /dev/null +++ b/src/Utils.cpp @@ -0,0 +1,66 @@ +/* + 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 "Utils.h" + +#include + +namespace melonDS +{ +std::pair, u32> PadToPowerOf2(std::unique_ptr&& data, u32 len) noexcept +{ + if (data == nullptr || len == 0) + return {nullptr, 0}; + + if ((len & (len - 1)) == 0) + return {std::move(data), len}; + + u32 newlen = 1; + while (newlen < len) + newlen <<= 1; + + auto newdata = std::make_unique(newlen); + memcpy(newdata.get(), data.get(), len); + data = nullptr; + return {std::move(newdata), newlen}; +} + +std::pair, u32> PadToPowerOf2(const u8* data, u32 len) noexcept +{ + if (len == 0) + return {nullptr, 0}; + + u32 newlen = 1; + while (newlen < len) + newlen <<= 1; + + auto newdata = std::make_unique(newlen); + memcpy(newdata.get(), data, len); + return {std::move(newdata), newlen}; +} + +std::unique_ptr CopyToUnique(const u8* data, u32 len) noexcept +{ + if (data == nullptr || len == 0) + return nullptr; + + auto newdata = std::make_unique(len); + memcpy(newdata.get(), data, len); + return newdata; +} +} \ No newline at end of file diff --git a/src/Utils.h b/src/Utils.h new file mode 100644 index 00000000..63be217b --- /dev/null +++ b/src/Utils.h @@ -0,0 +1,43 @@ +/* + 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 MELONDS_UTILS_H +#define MELONDS_UTILS_H + +#include +#include "types.h" +#include + +namespace melonDS +{ +/// Ensures that the given array is a power of 2 in length. +/// +/// @return If \c len is a power of 2, returns \c data and \c len unchanged +/// without copying anything. +/// If \c data is \c nullptr, returns {nullptr, 0}. +/// Otherwise, return a copy of \c data with zero-padding to the next power of 2. +/// @post \c data is \c nullptr, even if it doesn't need to be copied. +std::pair, u32> PadToPowerOf2(std::unique_ptr&& data, u32 len) noexcept; + +std::pair, u32> PadToPowerOf2(const u8* data, u32 len) noexcept; + +std::unique_ptr CopyToUnique(const u8* data, u32 len) noexcept; + +} + +#endif // MELONDS_UTILS_H diff --git a/src/Wifi.cpp b/src/Wifi.cpp index 50275236..d8f440b4 100644 --- a/src/Wifi.cpp +++ b/src/Wifi.cpp @@ -78,12 +78,12 @@ const u8 Wifi::MPAckMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x03}; // * TX errors (if applicable) -bool MACEqual(u8* a, const u8* b) +bool MACEqual(const u8* a, const u8* b) { return (*(u32*)&a[0] == *(u32*)&b[0]) && (*(u16*)&a[4] == *(u16*)&b[4]); } -bool MACIsBroadcast(u8* a) +bool MACIsBroadcast(const u8* a) { return (*(u32*)&a[0] == 0xFFFFFFFF) && (*(u16*)&a[4] == 0xFFFF); } @@ -158,12 +158,46 @@ void Wifi::Reset() } #undef BBREG_FIXED - const Firmware* fw = NDS.SPI.GetFirmware(); + 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 +216,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 +245,6 @@ void Wifi::Reset() CmdCounter = 0; USUntilPowerOn = 0; - ForcePowerOn = false; IsMP = false; IsMPClient = false; @@ -244,6 +279,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 +322,6 @@ void Wifi::DoSavestate(Savestate* file) file->Var16(&MPLastSeqno); file->Var32((u32*)&USUntilPowerOn); - file->Bool32(&ForcePowerOn); file->Bool32(&IsMP); file->Bool32(&IsMPClient); @@ -351,33 +387,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<Addr + 0x4]; if (cnt < 0xFF) cnt++; @@ -477,7 +612,7 @@ void Wifi::ReportMPReplyErrors(u16 clientfail) } } -void Wifi::TXSendFrame(TXSlot* slot, int num) +void Wifi::TXSendFrame(const TXSlot* slot, int num) { u32 noseqno = 0; @@ -521,6 +656,9 @@ void Wifi::TXSendFrame(TXSlot* slot, int num) if (noseqno == 2) *(u16*)&TXBuffer[0xC] |= (1<<11); + if (CurChannel == 0) return; + TXBuffer[9] = CurChannel; + switch (num) { case 0: @@ -600,6 +738,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 +819,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); @@ -767,6 +911,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; @@ -1110,8 +1257,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); } @@ -1380,7 +1530,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 +1571,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 +1583,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 (;;) @@ -1468,6 +1618,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]; @@ -1528,7 +1696,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 +1737,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 +1774,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 +1829,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 +1864,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 +1924,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 +1937,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 +1973,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 +1992,9 @@ void Wifi::RFTransfer_Type3() { u32 data = IOPORT(W_RFData1) & 0xFF; RFRegs[id] = data; + + if (id == RFChannelIndex[0] || id == RFChannelIndex[1]) + ChangeChannel(); } } @@ -1819,6 +2018,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 +2099,6 @@ u16 Wifi::Read(u32 addr) } } - //printf("WIFI: read %08X\n", addr); return IOPORT(addr&0xFFF); } @@ -1923,28 +2122,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 +2177,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 +2223,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 +2438,7 @@ void Wifi::Write(u32 addr, u16 val) // read-only ports case 0x000: + case 0x034: case 0x044: case 0x054: case 0x098: @@ -2258,12 +2466,12 @@ void Wifi::Write(u32 addr, u16 val) } -u8* Wifi::GetMAC() +const u8* Wifi::GetMAC() const { return (u8*)&IOPORT(W_MACAddr0); } -u8* Wifi::GetBSSID() +const u8* Wifi::GetBSSID() const { return (u8*)&IOPORT(W_BSSID0); } diff --git a/src/Wifi.h b/src/Wifi.h index 76fa1463..2e0465a6 100644 --- a/src/Wifi.h +++ b/src/Wifi.h @@ -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, @@ -169,8 +170,8 @@ public: u16 Read(u32 addr); void Write(u32 addr, u16 val); - u8* GetMAC(); - u8* GetBSSID(); + const u8* GetMAC() const; + const u8* GetBSSID() const; private: melonDS::NDS& NDS; @@ -206,6 +207,10 @@ private: u8 RFVersion; u32 RFRegs[0x40]; + u32 RFChannelIndex[2]; + u32 RFChannelData[14][2]; + int CurChannel; + struct TXSlot { bool Valid; @@ -240,7 +245,6 @@ private: bool LANInited; int USUntilPowerOn; - bool ForcePowerOn; // MULTIPLAYER SYNC APPARATUS bool IsMP; @@ -253,20 +257,22 @@ 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(); - int PreambleLen(int rate); - u32 NumClients(u16 bitmask); - void IncrementTXCount(TXSlot* slot); + void UpdatePowerStatus(int power); + + int PreambleLen(int rate) const; + u32 NumClients(u16 bitmask) const; + void IncrementTXCount(const TXSlot* slot); void ReportMPReplyErrors(u16 clientfail); - void TXSendFrame(TXSlot* slot, int num); + void TXSendFrame(const TXSlot* slot, int num); void StartTX_LocN(int nslot, int loc); void StartTX_Cmd(); void StartTX_Beacon(); @@ -284,6 +290,8 @@ private: void MSTimer(); + void ChangeChannel(); + void RFTransfer_Type2(); void RFTransfer_Type3(); }; diff --git a/src/WifiAP.cpp b/src/WifiAP.cpp index efc34a5c..855dc244 100644 --- a/src/WifiAP.cpp +++ b/src/WifiAP.cpp @@ -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); @@ -66,8 +67,8 @@ const u8 WifiAP::APMac[6] = {0x00, 0xF0, 0x77, 0x77, 0x77, 0x77}; #define PALIGN_4(p, base) while (PLEN(p,base) & 0x3) *p++ = 0xFF; -bool MACEqual(u8* a, const u8* b); -bool MACIsBroadcast(u8* a); +bool MACEqual(const u8* a, const u8* b); +bool MACIsBroadcast(const u8* a); WifiAP::WifiAP(Wifi* client) : Client(client) @@ -107,7 +108,7 @@ void WifiAP::MSTimer() } -int WifiAP::HandleManagementFrame(u8* data, int len) +int WifiAP::HandleManagementFrame(const u8* data, int len) { // TODO: perfect this // noting that frames sent pre-auth/assoc don't have a proper BSSID @@ -174,7 +175,7 @@ int WifiAP::HandleManagementFrame(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); @@ -258,8 +259,11 @@ int WifiAP::HandleManagementFrame(u8* data, int len) } -int WifiAP::SendPacket(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]; @@ -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); diff --git a/src/WifiAP.h b/src/WifiAP.h index 5d966687..a9e80c3b 100644 --- a/src/WifiAP.h +++ b/src/WifiAP.h @@ -34,11 +34,12 @@ public: static const char* APName; static const u8 APMac[6]; + static const u8 APChannel; void MSTimer(); // packet format: 12-byte TX header + original 802.11 frame - int SendPacket(u8* data, int len); + int SendPacket(const u8* data, int len); int RecvPacket(u8* data); private: @@ -60,7 +61,7 @@ private: // 0=disconnected 1=authenticated 2=associated int ClientStatus; - int HandleManagementFrame(u8* data, int len); + int HandleManagementFrame(const u8* data, int len); }; } diff --git a/src/debug/GdbProto.cpp b/src/debug/GdbProto.cpp index ebdf3be5..bd7e6e65 100644 --- a/src/debug/GdbProto.cpp +++ b/src/debug/GdbProto.cpp @@ -1,6 +1,6 @@ #ifdef _WIN32 -#include +#include #include #include #endif diff --git a/src/debug/GdbStub.cpp b/src/debug/GdbStub.cpp index 099f27f1..53101cec 100644 --- a/src/debug/GdbStub.cpp +++ b/src/debug/GdbStub.cpp @@ -1,6 +1,6 @@ #ifdef _WIN32 -#include +#include #include #include #endif diff --git a/src/debug/GdbStub.h b/src/debug/GdbStub.h index ace07bf6..99b88158 100644 --- a/src/debug/GdbStub.h +++ b/src/debug/GdbStub.h @@ -3,6 +3,7 @@ #define GDBSTUB_H_ #include +#include #include #include diff --git a/src/fatfs/ff.c b/src/fatfs/ff.c index 9d212949..385da84e 100644 --- a/src/fatfs/ff.c +++ b/src/fatfs/ff.c @@ -728,13 +728,13 @@ static int dbc_2nd (BYTE c) #if FF_USE_LFN -/* Get a Unicode code point from the TCHAR string in defined API encodeing */ +/* Get a Unicode code point from the FF_TCHAR string in defined API encodeing */ static DWORD tchar2uni ( /* Returns a character in UTF-16 encoding (>=0x10000 on surrogate pair, 0xFFFFFFFF on decode error) */ - const TCHAR** str /* Pointer to pointer to TCHAR string in configured encoding */ + const FF_TCHAR** str /* Pointer to pointer to FF_TCHAR string in configured encoding */ ) { DWORD uc; - const TCHAR *p = *str; + const FF_TCHAR *p = *str; #if FF_LFN_UNICODE == 1 /* UTF-16 input */ WCHAR wc; @@ -771,7 +771,7 @@ static DWORD tchar2uni ( /* Returns a character in UTF-16 encoding (>=0x10000 on } #elif FF_LFN_UNICODE == 3 /* UTF-32 input */ - uc = (TCHAR)*p++; /* Get a unit */ + uc = (FF_TCHAR)*p++; /* Get a unit */ if (uc >= 0x110000 || IsSurrogate(uc)) return 0xFFFFFFFF; /* Wrong code? */ if (uc >= 0x010000) uc = 0xD800DC00 | ((uc - 0x10000) << 6 & 0x3FF0000) | (uc & 0x3FF); /* Make a surrogate pair if needed */ @@ -800,7 +800,7 @@ static DWORD tchar2uni ( /* Returns a character in UTF-16 encoding (>=0x10000 on /* Store a Unicode char in defined API encoding */ static UINT put_utf ( /* Returns number of encoding units written (0:buffer overflow or wrong encoding) */ DWORD chr, /* UTF-16 encoded character (Surrogate pair if >=0x10000) */ - TCHAR* buf, /* Output buffer */ + FF_TCHAR* buf, /* Output buffer */ UINT szb /* Size of the buffer */ ) { @@ -824,20 +824,20 @@ static UINT put_utf ( /* Returns number of encoding units written (0:buffer over if (chr < 0x80) { /* Single byte code? */ if (szb < 1) return 0; /* Buffer overflow? */ - *buf = (TCHAR)chr; + *buf = (FF_TCHAR)chr; return 1; } if (chr < 0x800) { /* 2-byte sequence? */ if (szb < 2) return 0; /* Buffer overflow? */ - *buf++ = (TCHAR)(0xC0 | (chr >> 6 & 0x1F)); - *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F)); + *buf++ = (FF_TCHAR)(0xC0 | (chr >> 6 & 0x1F)); + *buf++ = (FF_TCHAR)(0x80 | (chr >> 0 & 0x3F)); return 2; } if (chr < 0x10000) { /* 3-byte sequence? */ if (szb < 3 || IsSurrogate(chr)) return 0; /* Buffer overflow or wrong code? */ - *buf++ = (TCHAR)(0xE0 | (chr >> 12 & 0x0F)); - *buf++ = (TCHAR)(0x80 | (chr >> 6 & 0x3F)); - *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F)); + *buf++ = (FF_TCHAR)(0xE0 | (chr >> 12 & 0x0F)); + *buf++ = (FF_TCHAR)(0x80 | (chr >> 6 & 0x3F)); + *buf++ = (FF_TCHAR)(0x80 | (chr >> 0 & 0x3F)); return 3; } /* 4-byte sequence */ @@ -846,10 +846,10 @@ static UINT put_utf ( /* Returns number of encoding units written (0:buffer over chr = (chr & 0xFFFF) - 0xDC00; /* Get low 10 bits */ if (hc >= 0x100000 || chr >= 0x400) return 0; /* Wrong surrogate? */ chr = (hc | chr) + 0x10000; - *buf++ = (TCHAR)(0xF0 | (chr >> 18 & 0x07)); - *buf++ = (TCHAR)(0x80 | (chr >> 12 & 0x3F)); - *buf++ = (TCHAR)(0x80 | (chr >> 6 & 0x3F)); - *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F)); + *buf++ = (FF_TCHAR)(0xF0 | (chr >> 18 & 0x07)); + *buf++ = (FF_TCHAR)(0x80 | (chr >> 12 & 0x3F)); + *buf++ = (FF_TCHAR)(0x80 | (chr >> 6 & 0x3F)); + *buf++ = (FF_TCHAR)(0x80 | (chr >> 0 & 0x3F)); return 4; #elif FF_LFN_UNICODE == 3 /* UTF-32 output */ @@ -862,7 +862,7 @@ static UINT put_utf ( /* Returns number of encoding units written (0:buffer over if (hc >= 0x100000 || chr >= 0x400) return 0; /* Wrong surrogate? */ chr = (hc | chr) + 0x10000; } - *buf++ = (TCHAR)chr; + *buf++ = (FF_TCHAR)chr; return 1; #else /* ANSI/OEM output */ @@ -872,11 +872,11 @@ static UINT put_utf ( /* Returns number of encoding units written (0:buffer over if (wc >= 0x100) { /* Is this a DBC? */ if (szb < 2) return 0; *buf++ = (char)(wc >> 8); /* Store DBC 1st byte */ - *buf++ = (TCHAR)wc; /* Store DBC 2nd byte */ + *buf++ = (FF_TCHAR)wc; /* Store DBC 2nd byte */ return 2; } if (wc == 0 || szb < 1) return 0; /* Invalid char or buffer overflow? */ - *buf++ = (TCHAR)wc; /* Store the character */ + *buf++ = (FF_TCHAR)wc; /* Store the character */ return 1; #endif } @@ -2595,7 +2595,7 @@ static void get_fileinfo ( FATFS *fs = dp->obj.fs; UINT nw; #else - TCHAR c; + FF_TCHAR c; #endif @@ -2668,7 +2668,7 @@ static void get_fileinfo ( if (nw == 0) { di = 0; break; } /* Buffer overflow? */ di += nw; #else /* ANSI/OEM output */ - fno->altname[di++] = (TCHAR)wc; /* Store it without any conversion */ + fno->altname[di++] = (FF_TCHAR)wc; /* Store it without any conversion */ #endif } fno->altname[di] = 0; /* Terminate the SFN (null string means SFN is invalid) */ @@ -2681,7 +2681,7 @@ static void get_fileinfo ( wc = (WCHAR)fno->altname[si]; if (wc == '.') lcf = NS_EXT; if (IsUpper(wc) && (dp->dir[DIR_NTres] & lcf)) wc += 0x20; - fno->fname[di] = (TCHAR)wc; + fno->fname[di] = (FF_TCHAR)wc; } } fno->fname[di] = 0; /* Terminate the LFN */ @@ -2691,7 +2691,7 @@ static void get_fileinfo ( #else /* Non-LFN configuration */ si = di = 0; while (si < 11) { /* Copy name body and extension */ - c = (TCHAR)dp->dir[si++]; + c = (FF_TCHAR)dp->dir[si++]; if (c == ' ') continue; /* Skip padding spaces */ if (c == RDDEM) c = DDEM; /* Restore replaced DDEM character */ if (si == 9) fno->fname[di++] = '.';/* Insert a . if extension is exist */ @@ -2719,7 +2719,7 @@ static void get_fileinfo ( static DWORD get_achar ( /* Get a character and advance ptr */ - const TCHAR** ptr /* Pointer to pointer to the ANSI/OEM or Unicode string */ + const FF_TCHAR** ptr /* Pointer to pointer to the ANSI/OEM or Unicode string */ ) { DWORD chr; @@ -2750,13 +2750,13 @@ static DWORD get_achar ( /* Get a character and advance ptr */ static int pattern_match ( /* 0:mismatched, 1:matched */ - const TCHAR* pat, /* Matching pattern */ - const TCHAR* nam, /* String to be tested */ + const FF_TCHAR* pat, /* Matching pattern */ + const FF_TCHAR* nam, /* String to be tested */ UINT skip, /* Number of pre-skip chars (number of ?s, b8:infinite (* specified)) */ UINT recur /* Recursion count */ ) { - const TCHAR *pptr, *nptr; + const FF_TCHAR *pptr, *nptr; DWORD pchr, nchr; UINT sk; @@ -2800,7 +2800,7 @@ static int pattern_match ( /* 0:mismatched, 1:matched */ static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not create */ FF_DIR* dp, /* Pointer to the directory object */ - const TCHAR** path /* Pointer to pointer to the segment in the path string */ + const FF_TCHAR** path /* Pointer to pointer to the segment in the path string */ ) { #if FF_USE_LFN /* LFN configuration */ @@ -2808,7 +2808,7 @@ static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not cr WCHAR wc, *lfn; DWORD uc; UINT i, ni, si, di; - const TCHAR *p; + const FF_TCHAR *p; /* Create LFN into LFN working buffer */ @@ -3002,7 +3002,7 @@ static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not cr static FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */ FF_DIR* dp, /* Directory object to return last directory and found object */ - const TCHAR* path /* Full-path string to find a file or directory */ + const FF_TCHAR* path /* Full-path string to find a file or directory */ ) { FRESULT res; @@ -3088,11 +3088,11 @@ static FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */ /*-----------------------------------------------------------------------*/ static int get_ldnumber ( /* Returns logical drive number (-1:invalid drive number or null pointer) */ - const TCHAR** path /* Pointer to pointer to the path name */ + const FF_TCHAR** path /* Pointer to pointer to the path name */ ) { - const TCHAR *tp, *tt; - TCHAR tc; + const FF_TCHAR *tp, *tt; + FF_TCHAR tc; int i; int vol = -1; #if FF_STR_VOLUME_ID /* Find string volume ID */ @@ -3118,7 +3118,7 @@ static int get_ldnumber ( /* Returns logical drive number (-1:invalid drive numb c = *sp++; tc = *tp++; if (IsLower(c)) c -= 0x20; if (IsLower(tc)) tc -= 0x20; - } while (c && (TCHAR)c == tc); + } while (c && (FF_TCHAR)c == tc); } while ((c || tp != tt) && ++i < FF_VOLUMES); /* Repeat for each id until pattern match */ } #endif @@ -3138,7 +3138,7 @@ static int get_ldnumber ( /* Returns logical drive number (-1:invalid drive numb c = *sp++; tc = *(++tt); if (IsLower(c)) c -= 0x20; if (IsLower(tc)) tc -= 0x20; - } while (c && (TCHAR)c == tc); + } while (c && (FF_TCHAR)c == tc); } while ((c || (tc != '/' && !IsTerminator(tc))) && ++i < FF_VOLUMES); /* Repeat for each ID until pattern match */ if (i < FF_VOLUMES) { /* If a volume ID is found, get the drive number and strip it */ vol = i; /* Drive number */ @@ -3330,7 +3330,7 @@ static UINT find_volume ( /* Returns BS status found in the hosting drive */ /*-----------------------------------------------------------------------*/ static FRESULT mount_volume ( /* FR_OK(0): successful, !=0: an error occurred */ - const TCHAR** path, /* Pointer to pointer to the path name (drive number) */ + const FF_TCHAR** path, /* Pointer to pointer to the path name (drive number) */ FATFS** rfs, /* Pointer to pointer to the found filesystem object */ BYTE mode /* !=0: Check write protection for write access */ ) @@ -3604,14 +3604,14 @@ static FRESULT validate ( /* Returns FR_OK or FR_INVALID_OBJECT */ FRESULT f_mount ( FATFS* fs, /* Pointer to the filesystem object to be registered (NULL:unmount)*/ - const TCHAR* path, /* Logical drive number to be mounted/unmounted */ + const FF_TCHAR* path, /* Logical drive number to be mounted/unmounted */ BYTE opt /* Mount option: 0=Do not mount (delayed mount), 1=Mount immediately */ ) { FATFS *cfs; int vol; FRESULT res; - const TCHAR *rp = path; + const FF_TCHAR *rp = path; /* Get logical drive number */ @@ -3652,7 +3652,7 @@ FRESULT f_mount ( FRESULT f_open ( FF_FIL* fp, /* Pointer to the blank file object */ - const TCHAR* path, /* Pointer to the file name */ + const FF_TCHAR* path, /* Pointer to the file name */ BYTE mode /* Access mode and open mode flags */ ) { @@ -4186,7 +4186,7 @@ FRESULT f_close ( /*-----------------------------------------------------------------------*/ FRESULT f_chdrive ( - const TCHAR* path /* Drive number to set */ + const FF_TCHAR* path /* Drive number to set */ ) { int vol; @@ -4203,7 +4203,7 @@ FRESULT f_chdrive ( FRESULT f_chdir ( - const TCHAR* path /* Pointer to the directory path */ + const FF_TCHAR* path /* Pointer to the directory path */ ) { #if FF_STR_VOLUME_ID == 2 @@ -4265,8 +4265,8 @@ FRESULT f_chdir ( #if FF_FS_RPATH >= 2 FRESULT f_getcwd ( - TCHAR* buff, /* Pointer to the directory path */ - UINT len /* Size of buff in unit of TCHAR */ + FF_TCHAR* buff, /* Pointer to the directory path */ + UINT len /* Size of buff in unit of FF_TCHAR */ ) { FRESULT res; @@ -4274,7 +4274,7 @@ FRESULT f_getcwd ( FATFS *fs; UINT i, n; DWORD ccl; - TCHAR *tp = buff; + FF_TCHAR *tp = buff; #if FF_VOLUMES >= 2 UINT vl; #if FF_STR_VOLUME_ID @@ -4287,7 +4287,7 @@ FRESULT f_getcwd ( /* Get logical drive */ buff[0] = 0; /* Set null string to get current volume */ - res = mount_volume((const TCHAR**)&buff, &fs, 0); /* Get current volume */ + res = mount_volume((const FF_TCHAR**)&buff, &fs, 0); /* Get current volume */ if (res == FR_OK) { dj.obj.fs = fs; INIT_NAMBUF(fs); @@ -4328,15 +4328,15 @@ FRESULT f_getcwd ( #if FF_STR_VOLUME_ID >= 1 /* String volume ID */ for (n = 0, vp = (const char*)VolumeStr[CurrVol]; vp[n]; n++) ; if (i >= n + 2) { - if (FF_STR_VOLUME_ID == 2) *tp++ = (TCHAR)'/'; - for (vl = 0; vl < n; *tp++ = (TCHAR)vp[vl], vl++) ; - if (FF_STR_VOLUME_ID == 1) *tp++ = (TCHAR)':'; + if (FF_STR_VOLUME_ID == 2) *tp++ = (FF_TCHAR)'/'; + for (vl = 0; vl < n; *tp++ = (FF_TCHAR)vp[vl], vl++) ; + if (FF_STR_VOLUME_ID == 1) *tp++ = (FF_TCHAR)':'; vl++; } #else /* Numeric volume ID */ if (i >= 3) { - *tp++ = (TCHAR)'0' + CurrVol; - *tp++ = (TCHAR)':'; + *tp++ = (FF_TCHAR)'0' + CurrVol; + *tp++ = (FF_TCHAR)':'; vl = 2; } #endif @@ -4530,7 +4530,7 @@ FRESULT f_lseek ( FRESULT f_opendir ( FF_DIR* dp, /* Pointer to directory object to create */ - const TCHAR* path /* Pointer to the directory path */ + const FF_TCHAR* path /* Pointer to the directory path */ ) { FRESULT res; @@ -4688,8 +4688,8 @@ FRESULT f_findnext ( FRESULT f_findfirst ( FF_DIR* dp, /* Pointer to the blank directory object */ FF_FILINFO* fno, /* Pointer to the file information structure */ - const TCHAR* path, /* Pointer to the directory to open */ - const TCHAR* pattern /* Pointer to the matching pattern */ + const FF_TCHAR* path, /* Pointer to the directory to open */ + const FF_TCHAR* pattern /* Pointer to the matching pattern */ ) { FRESULT res; @@ -4713,7 +4713,7 @@ FRESULT f_findfirst ( /*-----------------------------------------------------------------------*/ FRESULT f_stat ( - const TCHAR* path, /* Pointer to the file path */ + const FF_TCHAR* path, /* Pointer to the file path */ FF_FILINFO* fno /* Pointer to file information to return */ ) { @@ -4748,7 +4748,7 @@ FRESULT f_stat ( /*-----------------------------------------------------------------------*/ FRESULT f_getfree ( - const TCHAR* path, /* Logical drive number */ + const FF_TCHAR* path, /* Logical drive number */ DWORD* nclst, /* Pointer to a variable to return number of free clusters */ FATFS** fatfs /* Pointer to return pointer to corresponding filesystem object */ ) @@ -4890,7 +4890,7 @@ FRESULT f_truncate ( /*-----------------------------------------------------------------------*/ FRESULT f_unlink ( - const TCHAR* path /* Pointer to the file or directory path */ + const FF_TCHAR* path /* Pointer to the file or directory path */ ) { FRESULT res; @@ -4984,7 +4984,7 @@ FRESULT f_unlink ( /*-----------------------------------------------------------------------*/ FRESULT f_mkdir ( - const TCHAR* path /* Pointer to the directory path */ + const FF_TCHAR* path /* Pointer to the directory path */ ) { FRESULT res; @@ -5068,8 +5068,8 @@ FRESULT f_mkdir ( /*-----------------------------------------------------------------------*/ FRESULT f_rename ( - const TCHAR* path_old, /* Pointer to the object name to be renamed */ - const TCHAR* path_new /* Pointer to the new name */ + const FF_TCHAR* path_old, /* Pointer to the object name to be renamed */ + const FF_TCHAR* path_new /* Pointer to the new name */ ) { FRESULT res; @@ -5178,7 +5178,7 @@ FRESULT f_rename ( /*-----------------------------------------------------------------------*/ FRESULT f_chmod ( - const TCHAR* path, /* Pointer to the file path */ + const FF_TCHAR* path, /* Pointer to the file path */ BYTE attr, /* Attribute bits */ BYTE mask /* Attribute mask to change */ ) @@ -5225,7 +5225,7 @@ FRESULT f_chmod ( /*-----------------------------------------------------------------------*/ FRESULT f_utime ( - const TCHAR* path, /* Pointer to the file/directory name */ + const FF_TCHAR* path, /* Pointer to the file/directory name */ const FF_FILINFO* fno /* Pointer to the timestamp to be set */ ) { @@ -5272,8 +5272,8 @@ FRESULT f_utime ( /*-----------------------------------------------------------------------*/ FRESULT f_getlabel ( - const TCHAR* path, /* Logical drive number */ - TCHAR* label, /* Buffer to store the volume label */ + const FF_TCHAR* path, /* Logical drive number */ + FF_TCHAR* label, /* Buffer to store the volume label */ DWORD* vsn /* Variable to store the volume serial number */ ) { @@ -5322,7 +5322,7 @@ FRESULT f_getlabel ( if (wc == 0) { di = 0; break; } /* Invalid char in current code page? */ di += put_utf(wc, &label[di], 4); /* Store it in Unicode */ #else /* ANSI/OEM output */ - label[di++] = (TCHAR)wc; + label[di++] = (FF_TCHAR)wc; #endif } do { /* Truncate trailing spaces */ @@ -5369,7 +5369,7 @@ FRESULT f_getlabel ( /*-----------------------------------------------------------------------*/ FRESULT f_setlabel ( - const TCHAR* label /* Volume label to set with heading logical drive number */ + const FF_TCHAR* label /* Volume label to set with heading logical drive number */ ) { FRESULT res; @@ -5800,7 +5800,7 @@ static FRESULT create_partition ( FRESULT f_mkfs ( - const TCHAR* path, /* Logical drive number */ + const FF_TCHAR* path, /* Logical drive number */ const FF_MKFS_PARM* opt, /* Format options */ void* work, /* Pointer to working buffer (null: use heap memory) */ UINT len /* Size of working buffer [byte] */ @@ -6335,14 +6335,14 @@ FRESULT f_fdisk ( /* Get a String from the File */ /*-----------------------------------------------------------------------*/ -TCHAR* f_gets ( - TCHAR* buff, /* Pointer to the buffer to store read string */ +FF_TCHAR* f_gets ( + FF_TCHAR* buff, /* Pointer to the buffer to store read string */ int len, /* Size of string buffer (items) */ FF_FIL* fp /* Pointer to the file object */ ) { int nc = 0; - TCHAR *p = buff; + FF_TCHAR *p = buff; BYTE s[4]; UINT rc; DWORD dc; @@ -6407,32 +6407,32 @@ TCHAR* f_gets ( if (FF_USE_STRFUNC == 2 && dc == '\r') continue; /* Strip \r off if needed */ #if FF_LFN_UNICODE == 1 || FF_LFN_UNICODE == 3 /* Output it in UTF-16/32 encoding */ if (FF_LFN_UNICODE == 1 && dc >= 0x10000) { /* Out of BMP at UTF-16? */ - *p++ = (TCHAR)(0xD800 | ((dc >> 10) - 0x40)); nc++; /* Make and output high surrogate */ + *p++ = (FF_TCHAR)(0xD800 | ((dc >> 10) - 0x40)); nc++; /* Make and output high surrogate */ dc = 0xDC00 | (dc & 0x3FF); /* Make low surrogate */ } - *p++ = (TCHAR)dc; nc++; + *p++ = (FF_TCHAR)dc; nc++; if (dc == '\n') break; /* End of line? */ #elif FF_LFN_UNICODE == 2 /* Output it in UTF-8 encoding */ if (dc < 0x80) { /* Single byte? */ - *p++ = (TCHAR)dc; + *p++ = (FF_TCHAR)dc; nc++; if (dc == '\n') break; /* End of line? */ } else { if (dc < 0x800) { /* 2-byte sequence? */ - *p++ = (TCHAR)(0xC0 | (dc >> 6 & 0x1F)); - *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F)); + *p++ = (FF_TCHAR)(0xC0 | (dc >> 6 & 0x1F)); + *p++ = (FF_TCHAR)(0x80 | (dc >> 0 & 0x3F)); nc += 2; } else { if (dc < 0x10000) { /* 3-byte sequence? */ - *p++ = (TCHAR)(0xE0 | (dc >> 12 & 0x0F)); - *p++ = (TCHAR)(0x80 | (dc >> 6 & 0x3F)); - *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F)); + *p++ = (FF_TCHAR)(0xE0 | (dc >> 12 & 0x0F)); + *p++ = (FF_TCHAR)(0x80 | (dc >> 6 & 0x3F)); + *p++ = (FF_TCHAR)(0x80 | (dc >> 0 & 0x3F)); nc += 3; } else { /* 4-byte sequence? */ - *p++ = (TCHAR)(0xF0 | (dc >> 18 & 0x07)); - *p++ = (TCHAR)(0x80 | (dc >> 12 & 0x3F)); - *p++ = (TCHAR)(0x80 | (dc >> 6 & 0x3F)); - *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F)); + *p++ = (FF_TCHAR)(0xF0 | (dc >> 18 & 0x07)); + *p++ = (FF_TCHAR)(0x80 | (dc >> 12 & 0x3F)); + *p++ = (FF_TCHAR)(0x80 | (dc >> 6 & 0x3F)); + *p++ = (FF_TCHAR)(0x80 | (dc >> 0 & 0x3F)); nc += 4; } } @@ -6447,7 +6447,7 @@ TCHAR* f_gets ( if (rc != 1) break; /* EOF? */ dc = s[0]; if (FF_USE_STRFUNC == 2 && dc == '\r') continue; - *p++ = (TCHAR)dc; nc++; + *p++ = (FF_TCHAR)dc; nc++; if (dc == '\n') break; } #endif @@ -6485,7 +6485,7 @@ typedef struct { /* Buffered file write with code conversion */ -static void putc_bfd (putbuff* pb, TCHAR c) +static void putc_bfd (putbuff* pb, FF_TCHAR c) { UINT n; int i, nc; @@ -6493,7 +6493,7 @@ static void putc_bfd (putbuff* pb, TCHAR c) WCHAR hs, wc; #if FF_LFN_UNICODE == 2 DWORD dc; - const TCHAR *tp; + const FF_TCHAR *tp; #endif #endif @@ -6535,7 +6535,7 @@ static void putc_bfd (putbuff* pb, TCHAR c) return; } } - tp = (const TCHAR*)pb->bs; + tp = (const FF_TCHAR*)pb->bs; dc = tchar2uni(&tp); /* UTF-8 ==> UTF-16 */ if (dc == 0xFFFFFFFF) return; /* Wrong code? */ wc = (WCHAR)dc; @@ -6638,7 +6638,7 @@ static void putc_init (putbuff* pb, FF_FIL* fp) int f_putc ( - TCHAR c, /* A character to be output */ + FF_TCHAR c, /* A character to be output */ FF_FIL* fp /* Pointer to the file object */ ) { @@ -6658,7 +6658,7 @@ int f_putc ( /*-----------------------------------------------------------------------*/ int f_puts ( - const TCHAR* str, /* Pointer to the string to be output */ + const FF_TCHAR* str, /* Pointer to the string to be output */ FF_FIL* fp /* Pointer to the file object */ ) { @@ -6727,7 +6727,7 @@ static void ftoa ( char* buf, /* Buffer to output the floating point string */ double val, /* Value to output */ int prec, /* Number of fractional digits */ - TCHAR fmt /* Notation */ + FF_TCHAR fmt /* Notation */ ) { int d; @@ -6800,7 +6800,7 @@ static void ftoa ( int f_printf ( FF_FIL* fp, /* Pointer to the file object */ - const TCHAR* fmt, /* Pointer to the format string */ + const FF_TCHAR* fmt, /* Pointer to the format string */ ... /* Optional arguments... */ ) { @@ -6813,8 +6813,8 @@ int f_printf ( #else DWORD v; #endif - TCHAR tc, pad, *tp; - TCHAR nul = 0; + FF_TCHAR tc, pad, *tp; + FF_TCHAR nul = 0; char d, str[SZ_NUM_BUF]; @@ -6879,10 +6879,10 @@ int f_printf ( case 'X': /* Unsigned hexdecimal (upper case) */ r = 16; break; case 'c': /* Character */ - putc_bfd(&pb, (TCHAR)va_arg(arp, int)); + putc_bfd(&pb, (FF_TCHAR)va_arg(arp, int)); continue; case 's': /* String */ - tp = va_arg(arp, TCHAR*); /* Get a pointer argument */ + tp = va_arg(arp, FF_TCHAR*); /* Get a pointer argument */ if (!tp) tp = &nul; /* Null ptr generates a null string */ for (j = 0; tp[j]; j++) ; /* j = tcslen(tp) */ if (prec >= 0 && j > (UINT)prec) j = prec; /* Limited length of string body */ @@ -6937,7 +6937,7 @@ int f_printf ( if (f & 1) str[i++] = '-'; /* Sign */ /* Write it */ for (j = i; !(f & 2) && j < w; j++) putc_bfd(&pb, pad); /* Left pads */ - do putc_bfd(&pb, (TCHAR)str[--i]); while (i); /* Body */ + do putc_bfd(&pb, (FF_TCHAR)str[--i]); while (i); /* Body */ while (j++ < w) putc_bfd(&pb, ' '); /* Right pads */ } diff --git a/src/fatfs/ff.h b/src/fatfs/ff.h index c2832be1..1662d836 100644 --- a/src/fatfs/ff.h +++ b/src/fatfs/ff.h @@ -85,24 +85,24 @@ typedef DWORD LBA_t; -/* Type of path name strings on FatFs API (TCHAR) */ +/* Type of path name strings on FatFs API (FF_TCHAR) */ #if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */ -typedef WCHAR TCHAR; +typedef WCHAR FF_TCHAR; #define _T(x) L ## x #define _TEXT(x) L ## x #elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */ -typedef char TCHAR; +typedef char FF_TCHAR; #define _T(x) u8 ## x #define _TEXT(x) u8 ## x #elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */ -typedef DWORD TCHAR; +typedef DWORD FF_TCHAR; #define _T(x) U ## x #define _TEXT(x) U ## x #elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3) #error Wrong FF_LFN_UNICODE setting #else /* ANSI/OEM code in SBCS/DBCS */ -typedef char TCHAR; +typedef char FF_TCHAR; #define _T(x) x #define _TEXT(x) x #endif @@ -236,7 +236,7 @@ typedef struct { DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */ #endif #if FF_USE_FIND - const TCHAR* pat; /* Pointer to the name matching pattern */ + const FF_TCHAR* pat; /* Pointer to the name matching pattern */ #endif } FF_DIR; @@ -250,10 +250,10 @@ typedef struct { WORD ftime; /* Modified time */ BYTE fattrib; /* File attribute */ #if FF_USE_LFN - TCHAR altname[FF_SFN_BUF + 1];/* Altenative file name */ - TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */ + FF_TCHAR altname[FF_SFN_BUF + 1];/* Altenative file name */ + FF_TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */ #else - TCHAR fname[12 + 1]; /* File name */ + FF_TCHAR fname[12 + 1]; /* File name */ #endif } FF_FILINFO; @@ -301,40 +301,40 @@ typedef enum { /*--------------------------------------------------------------*/ /* FatFs module application interface */ -FRESULT f_open (FF_FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */ +FRESULT f_open (FF_FIL* fp, const FF_TCHAR* path, BYTE mode); /* Open or create a file */ FRESULT f_close (FF_FIL* fp); /* Close an open file object */ FRESULT f_read (FF_FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */ FRESULT f_write (FF_FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */ FRESULT f_lseek (FF_FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */ FRESULT f_truncate (FF_FIL* fp); /* Truncate the file */ FRESULT f_sync (FF_FIL* fp); /* Flush cached data of the writing file */ -FRESULT f_opendir (FF_DIR* dp, const TCHAR* path); /* Open a directory */ +FRESULT f_opendir (FF_DIR* dp, const FF_TCHAR* path); /* Open a directory */ FRESULT f_closedir (FF_DIR* dp); /* Close an open directory */ FRESULT f_readdir (FF_DIR* dp, FF_FILINFO* fno); /* Read a directory item */ -FRESULT f_findfirst (FF_DIR* dp, FF_FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */ +FRESULT f_findfirst (FF_DIR* dp, FF_FILINFO* fno, const FF_TCHAR* path, const FF_TCHAR* pattern); /* Find first file */ FRESULT f_findnext (FF_DIR* dp, FF_FILINFO* fno); /* Find next file */ -FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */ -FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */ -FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */ -FRESULT f_stat (const TCHAR* path, FF_FILINFO* fno); /* Get file status */ -FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */ -FRESULT f_utime (const TCHAR* path, const FF_FILINFO* fno); /* Change timestamp of a file/dir */ -FRESULT f_chdir (const TCHAR* path); /* Change current directory */ -FRESULT f_chdrive (const TCHAR* path); /* Change current drive */ -FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */ -FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */ -FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */ -FRESULT f_setlabel (const TCHAR* label); /* Set volume label */ +FRESULT f_mkdir (const FF_TCHAR* path); /* Create a sub directory */ +FRESULT f_unlink (const FF_TCHAR* path); /* Delete an existing file or directory */ +FRESULT f_rename (const FF_TCHAR* path_old, const FF_TCHAR* path_new); /* Rename/Move a file or directory */ +FRESULT f_stat (const FF_TCHAR* path, FF_FILINFO* fno); /* Get file status */ +FRESULT f_chmod (const FF_TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */ +FRESULT f_utime (const FF_TCHAR* path, const FF_FILINFO* fno); /* Change timestamp of a file/dir */ +FRESULT f_chdir (const FF_TCHAR* path); /* Change current directory */ +FRESULT f_chdrive (const FF_TCHAR* path); /* Change current drive */ +FRESULT f_getcwd (FF_TCHAR* buff, UINT len); /* Get current directory */ +FRESULT f_getfree (const FF_TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */ +FRESULT f_getlabel (const FF_TCHAR* path, FF_TCHAR* label, DWORD* vsn); /* Get volume label */ +FRESULT f_setlabel (const FF_TCHAR* label); /* Set volume label */ FRESULT f_forward (FF_FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */ FRESULT f_expand (FF_FIL* fp, FSIZE_t fsz, BYTE opt); /* Allocate a contiguous block to the file */ -FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */ -FRESULT f_mkfs (const TCHAR* path, const FF_MKFS_PARM* opt, void* work, UINT len); /* Create a FAT volume */ +FRESULT f_mount (FATFS* fs, const FF_TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */ +FRESULT f_mkfs (const FF_TCHAR* path, const FF_MKFS_PARM* opt, void* work, UINT len); /* Create a FAT volume */ FRESULT f_fdisk (BYTE pdrv, const LBA_t ptbl[], void* work); /* Divide a physical drive into some partitions */ FRESULT f_setcp (WORD cp); /* Set current code page */ -int f_putc (TCHAR c, FF_FIL* fp); /* Put a character to the file */ -int f_puts (const TCHAR* str, FF_FIL* cp); /* Put a string to the file */ -int f_printf (FF_FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */ -TCHAR* f_gets (TCHAR* buff, int len, FF_FIL* fp); /* Get a string from the file */ +int f_putc (FF_TCHAR c, FF_FIL* fp); /* Put a character to the file */ +int f_puts (const FF_TCHAR* str, FF_FIL* cp); /* Put a string to the file */ +int f_printf (FF_FIL* fp, const FF_TCHAR* str, ...); /* Put a formatted string to the file */ +FF_TCHAR* f_gets (FF_TCHAR* buff, int len, FF_FIL* fp); /* Get a string from the file */ #define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize)) #define f_error(fp) ((fp)->err) diff --git a/src/fatfs/ffsystem.c b/src/fatfs/ffsystem.c index 63fedf65..ebde84a5 100644 --- a/src/fatfs/ffsystem.c +++ b/src/fatfs/ffsystem.c @@ -110,7 +110,11 @@ DWORD get_fattime(void) time_t timestamp = time(NULL); struct tm timedata; +#if defined(_MSC_VER) + localtime_s(&timedata, ×tamp); +#else localtime_r(×tamp, &timedata); +#endif DWORD ret; ret = (timedata.tm_sec >> 1); diff --git a/src/frontend/Util_Audio.cpp b/src/frontend/Util_Audio.cpp index 02b3026e..25e04db3 100644 --- a/src/frontend/Util_Audio.cpp +++ b/src/frontend/Util_Audio.cpp @@ -63,21 +63,28 @@ int AudioOut_GetNumSamples(int outlen) 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; + double factor = (double) inlen / (double) outlen; + double inpos = -(factor / 2); + double vol = (double) volume / 256.f; - for (int i = 0; i < outlen; i++) + for (int i = 0; i < outlen * 2; i += 2) { - outbuf[i*2 ] = (inbuf[res_pos*2 ] * volume) >> 8; - outbuf[i*2+1] = (inbuf[res_pos*2+1] * volume) >> 8; + double intpart_d; + double frac = modf(inpos, &intpart_d); + int intpart = (int) intpart_d; - res_timer += res_incr; - while (res_timer >= 1.0) - { - res_timer -= 1.0; - res_pos++; - } + double l1 = inbuf[ intpart * 2]; + double l2 = inbuf[(intpart * 2) + 2]; + double r1 = inbuf[(intpart * 2) + 1]; + double r2 = inbuf[(intpart * 2) + 3]; + + double ldiff = l2 - l1; + double rdiff = r2 - r1; + + outbuf[i] = (s16) round((l1 + ldiff * frac) * vol); + outbuf[i+1] = (s16) round((r1 + rdiff * frac) * vol); + + inpos += factor; } } 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/libslirp/.clang-format b/src/frontend/libslirp/.clang-format new file mode 100644 index 00000000..17fb49fe --- /dev/null +++ b/src/frontend/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/frontend/libslirp/.gitignore b/src/frontend/libslirp/.gitignore new file mode 100644 index 00000000..bd362c29 --- /dev/null +++ b/src/frontend/libslirp/.gitignore @@ -0,0 +1,11 @@ +*.[aod] +*.gcda +*.gcno +*.gcov +*.lib +*.obj +/build/ +/TAGS +/cscope* +/src/libslirp-version.h +/tags diff --git a/src/frontend/libslirp/.gitlab-ci.yml b/src/frontend/libslirp/.gitlab-ci.yml new file mode 100644 index 00000000..7c4584eb --- /dev/null +++ b/src/frontend/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/frontend/libslirp/.gitpublish b/src/frontend/libslirp/.gitpublish new file mode 100644 index 00000000..7b852951 --- /dev/null +++ b/src/frontend/libslirp/.gitpublish @@ -0,0 +1,3 @@ +[gitpublishprofile "default"] +base = master +to = slirp@lists.freedesktop.org diff --git a/src/frontend/libslirp/CHANGELOG.md b/src/frontend/libslirp/CHANGELOG.md new file mode 100644 index 00000000..4684bce5 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/CMakeLists.txt b/src/frontend/libslirp/CMakeLists.txt new file mode 100644 index 00000000..cb009a6c --- /dev/null +++ b/src/frontend/libslirp/CMakeLists.txt @@ -0,0 +1,65 @@ +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 PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/glib") +target_include_directories(slirp PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src") +target_include_directories(slirp 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) +endif() diff --git a/src/frontend/libslirp/COPYRIGHT b/src/frontend/libslirp/COPYRIGHT new file mode 100644 index 00000000..ed49512d --- /dev/null +++ b/src/frontend/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/frontend/libslirp/README.md b/src/frontend/libslirp/README.md new file mode 100644 index 00000000..9f9c1b14 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_arp/arp.pcap b/src/frontend/libslirp/fuzzing/IN_arp/arp.pcap new file mode 100644 index 00000000..f920e213 Binary files /dev/null and b/src/frontend/libslirp/fuzzing/IN_arp/arp.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_dhcp/dhcp.pkt b/src/frontend/libslirp/fuzzing/IN_dhcp/dhcp.pkt new file mode 100644 index 00000000..92000377 Binary files /dev/null and b/src/frontend/libslirp/fuzzing/IN_dhcp/dhcp.pkt differ diff --git a/src/frontend/libslirp/fuzzing/IN_dhcp/dhcp_capture.pcap b/src/frontend/libslirp/fuzzing/IN_dhcp/dhcp_capture.pcap new file mode 100644 index 00000000..99ae0a83 Binary files /dev/null and b/src/frontend/libslirp/fuzzing/IN_dhcp/dhcp_capture.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_icmp/icmp_capture.pcap b/src/frontend/libslirp/fuzzing/IN_icmp/icmp_capture.pcap new file mode 100644 index 00000000..6a0f0b83 Binary files /dev/null and b/src/frontend/libslirp/fuzzing/IN_icmp/icmp_capture.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_icmp/ping_10-0-2-2.pcap b/src/frontend/libslirp/fuzzing/IN_icmp/ping_10-0-2-2.pcap new file mode 100644 index 00000000..69b60fbc Binary files /dev/null and b/src/frontend/libslirp/fuzzing/IN_icmp/ping_10-0-2-2.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_icmp6/icmp_capture.pcap b/src/frontend/libslirp/fuzzing/IN_icmp6/icmp_capture.pcap new file mode 100644 index 00000000..365bed10 Binary files /dev/null and b/src/frontend/libslirp/fuzzing/IN_icmp6/icmp_capture.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_icmp6/ndp.pcap b/src/frontend/libslirp/fuzzing/IN_icmp6/ndp.pcap new file mode 120000 index 00000000..74443f1e --- /dev/null +++ b/src/frontend/libslirp/fuzzing/IN_icmp6/ndp.pcap @@ -0,0 +1 @@ +../IN_ndp/ndp.pcap \ No newline at end of file diff --git a/src/frontend/libslirp/fuzzing/IN_icmp6/ping_10-0-2-2.pcap b/src/frontend/libslirp/fuzzing/IN_icmp6/ping_10-0-2-2.pcap new file mode 100644 index 00000000..87f63899 Binary files /dev/null and b/src/frontend/libslirp/fuzzing/IN_icmp6/ping_10-0-2-2.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_ip-header/DNS_freedesktop_1-1-1-1.pcap b/src/frontend/libslirp/fuzzing/IN_ip-header/DNS_freedesktop_1-1-1-1.pcap new file mode 120000 index 00000000..09b9ce1c --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_ip-header/dhcp.pkt b/src/frontend/libslirp/fuzzing/IN_ip-header/dhcp.pkt new file mode 120000 index 00000000..b1a9e045 --- /dev/null +++ b/src/frontend/libslirp/fuzzing/IN_ip-header/dhcp.pkt @@ -0,0 +1 @@ +../IN_dhcp/dhcp.pkt \ No newline at end of file diff --git a/src/frontend/libslirp/fuzzing/IN_ip-header/dhcp_capture.pcap b/src/frontend/libslirp/fuzzing/IN_ip-header/dhcp_capture.pcap new file mode 120000 index 00000000..e2d9c0db --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_ip-header/icmp_capture.pcap b/src/frontend/libslirp/fuzzing/IN_ip-header/icmp_capture.pcap new file mode 120000 index 00000000..a9ec1936 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_ip-header/nc-10.0.2.2-8080.pcap b/src/frontend/libslirp/fuzzing/IN_ip-header/nc-10.0.2.2-8080.pcap new file mode 120000 index 00000000..c99b568e --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_ip-header/nc-ident.pcap b/src/frontend/libslirp/fuzzing/IN_ip-header/nc-ident.pcap new file mode 120000 index 00000000..51ecdf0e --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_ip-header/ping_10-0-2-2.pcap b/src/frontend/libslirp/fuzzing/IN_ip-header/ping_10-0-2-2.pcap new file mode 120000 index 00000000..9a71a0e4 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_ip-header/tcp_qemucapt.pcap b/src/frontend/libslirp/fuzzing/IN_ip-header/tcp_qemucapt.pcap new file mode 120000 index 00000000..8daa990e --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_ip-header/tftp-get-blah.pkt b/src/frontend/libslirp/fuzzing/IN_ip-header/tftp-get-blah.pkt new file mode 120000 index 00000000..c95e90d9 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_ip-header/tftp_capture.pcap b/src/frontend/libslirp/fuzzing/IN_ip-header/tftp_capture.pcap new file mode 120000 index 00000000..cbaf3ab0 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_ip-header/tftp_get_libslirp-txt.pcap b/src/frontend/libslirp/fuzzing/IN_ip-header/tftp_get_libslirp-txt.pcap new file mode 120000 index 00000000..8aa1945d --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_ip6-header/DNS_freedesktop_1-1-1-1.pcap b/src/frontend/libslirp/fuzzing/IN_ip6-header/DNS_freedesktop_1-1-1-1.pcap new file mode 120000 index 00000000..651c2739 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_ip6-header/icmp_capture.pcap b/src/frontend/libslirp/fuzzing/IN_ip6-header/icmp_capture.pcap new file mode 120000 index 00000000..519f78a7 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_ip6-header/ping_10-0-2-2.pcap b/src/frontend/libslirp/fuzzing/IN_ip6-header/ping_10-0-2-2.pcap new file mode 120000 index 00000000..632a2868 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_ip6-header/tcp_qemucapt.pcap b/src/frontend/libslirp/fuzzing/IN_ip6-header/tcp_qemucapt.pcap new file mode 120000 index 00000000..b444205b --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_ip6-header/tftp_capture.pcap b/src/frontend/libslirp/fuzzing/IN_ip6-header/tftp_capture.pcap new file mode 120000 index 00000000..308ab28e --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_ip6-header/tftp_get_libslirp-txt.pcap b/src/frontend/libslirp/fuzzing/IN_ip6-header/tftp_get_libslirp-txt.pcap new file mode 120000 index 00000000..d3b4e76d --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_ndp/ndp.pcap b/src/frontend/libslirp/fuzzing/IN_ndp/ndp.pcap new file mode 100644 index 00000000..ed8db971 Binary files /dev/null and b/src/frontend/libslirp/fuzzing/IN_ndp/ndp.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_tcp-d b/src/frontend/libslirp/fuzzing/IN_tcp-d new file mode 120000 index 00000000..1bca80b4 --- /dev/null +++ b/src/frontend/libslirp/fuzzing/IN_tcp-d @@ -0,0 +1 @@ +IN_tcp \ No newline at end of file diff --git a/src/frontend/libslirp/fuzzing/IN_tcp-h b/src/frontend/libslirp/fuzzing/IN_tcp-h new file mode 120000 index 00000000..1bca80b4 --- /dev/null +++ b/src/frontend/libslirp/fuzzing/IN_tcp-h @@ -0,0 +1 @@ +IN_tcp \ No newline at end of file diff --git a/src/frontend/libslirp/fuzzing/IN_tcp/nc-10.0.2.2-8080.pcap b/src/frontend/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/frontend/libslirp/fuzzing/IN_tcp/nc-10.0.2.2-8080.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_tcp/nc-ident.pcap b/src/frontend/libslirp/fuzzing/IN_tcp/nc-ident.pcap new file mode 100644 index 00000000..3d0421b5 Binary files /dev/null and b/src/frontend/libslirp/fuzzing/IN_tcp/nc-ident.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_tcp/tcp_qemucapt.pcap b/src/frontend/libslirp/fuzzing/IN_tcp/tcp_qemucapt.pcap new file mode 100644 index 00000000..83a0eddf Binary files /dev/null and b/src/frontend/libslirp/fuzzing/IN_tcp/tcp_qemucapt.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_tcp6-d b/src/frontend/libslirp/fuzzing/IN_tcp6-d new file mode 120000 index 00000000..2ad34597 --- /dev/null +++ b/src/frontend/libslirp/fuzzing/IN_tcp6-d @@ -0,0 +1 @@ +IN_tcp6 \ No newline at end of file diff --git a/src/frontend/libslirp/fuzzing/IN_tcp6-h b/src/frontend/libslirp/fuzzing/IN_tcp6-h new file mode 120000 index 00000000..2ad34597 --- /dev/null +++ b/src/frontend/libslirp/fuzzing/IN_tcp6-h @@ -0,0 +1 @@ +IN_tcp6 \ No newline at end of file diff --git a/src/frontend/libslirp/fuzzing/IN_tcp6/tcp_qemucapt.pcap b/src/frontend/libslirp/fuzzing/IN_tcp6/tcp_qemucapt.pcap new file mode 100644 index 00000000..d7936b32 Binary files /dev/null and b/src/frontend/libslirp/fuzzing/IN_tcp6/tcp_qemucapt.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_tftp/tftp-get-blah.pkt b/src/frontend/libslirp/fuzzing/IN_tftp/tftp-get-blah.pkt new file mode 100644 index 00000000..c540ccf3 Binary files /dev/null and b/src/frontend/libslirp/fuzzing/IN_tftp/tftp-get-blah.pkt differ diff --git a/src/frontend/libslirp/fuzzing/IN_tftp/tftp_capture.pcap b/src/frontend/libslirp/fuzzing/IN_tftp/tftp_capture.pcap new file mode 100644 index 00000000..6fed4277 Binary files /dev/null and b/src/frontend/libslirp/fuzzing/IN_tftp/tftp_capture.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_tftp/tftp_get_libslirp-txt.pcap b/src/frontend/libslirp/fuzzing/IN_tftp/tftp_get_libslirp-txt.pcap new file mode 100644 index 00000000..18defe4d Binary files /dev/null and b/src/frontend/libslirp/fuzzing/IN_tftp/tftp_get_libslirp-txt.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_tftp6/tftp_capture.pcap b/src/frontend/libslirp/fuzzing/IN_tftp6/tftp_capture.pcap new file mode 100644 index 00000000..dd3ee718 Binary files /dev/null and b/src/frontend/libslirp/fuzzing/IN_tftp6/tftp_capture.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_tftp6/tftp_get_libslirp-txt.pcap b/src/frontend/libslirp/fuzzing/IN_tftp6/tftp_get_libslirp-txt.pcap new file mode 100644 index 00000000..caa144a0 Binary files /dev/null and b/src/frontend/libslirp/fuzzing/IN_tftp6/tftp_get_libslirp-txt.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_udp-h b/src/frontend/libslirp/fuzzing/IN_udp-h new file mode 120000 index 00000000..d1958903 --- /dev/null +++ b/src/frontend/libslirp/fuzzing/IN_udp-h @@ -0,0 +1 @@ +IN_udp \ No newline at end of file diff --git a/src/frontend/libslirp/fuzzing/IN_udp/DNS_freedesktop_1-1-1-1.pcap b/src/frontend/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/frontend/libslirp/fuzzing/IN_udp/DNS_freedesktop_1-1-1-1.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_udp/dhcp.pkt b/src/frontend/libslirp/fuzzing/IN_udp/dhcp.pkt new file mode 120000 index 00000000..b1a9e045 --- /dev/null +++ b/src/frontend/libslirp/fuzzing/IN_udp/dhcp.pkt @@ -0,0 +1 @@ +../IN_dhcp/dhcp.pkt \ No newline at end of file diff --git a/src/frontend/libslirp/fuzzing/IN_udp/dhcp_capture.pcap b/src/frontend/libslirp/fuzzing/IN_udp/dhcp_capture.pcap new file mode 120000 index 00000000..e2d9c0db --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_udp/tftp-get-blah.pkt b/src/frontend/libslirp/fuzzing/IN_udp/tftp-get-blah.pkt new file mode 120000 index 00000000..c95e90d9 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_udp/tftp_capture.pcap b/src/frontend/libslirp/fuzzing/IN_udp/tftp_capture.pcap new file mode 120000 index 00000000..cbaf3ab0 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_udp/tftp_get_libslirp-txt.pcap b/src/frontend/libslirp/fuzzing/IN_udp/tftp_get_libslirp-txt.pcap new file mode 120000 index 00000000..8aa1945d --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_udp6-h b/src/frontend/libslirp/fuzzing/IN_udp6-h new file mode 120000 index 00000000..4d7837b0 --- /dev/null +++ b/src/frontend/libslirp/fuzzing/IN_udp6-h @@ -0,0 +1 @@ +IN_udp6 \ No newline at end of file diff --git a/src/frontend/libslirp/fuzzing/IN_udp6/DNS_freedesktop_1-1-1-1.pcap b/src/frontend/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/frontend/libslirp/fuzzing/IN_udp6/DNS_freedesktop_1-1-1-1.pcap differ diff --git a/src/frontend/libslirp/fuzzing/IN_udp6/tftp_capture.pcap b/src/frontend/libslirp/fuzzing/IN_udp6/tftp_capture.pcap new file mode 120000 index 00000000..9bd68303 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/IN_udp6/tftp_get_libslirp-txt.pcap b/src/frontend/libslirp/fuzzing/IN_udp6/tftp_get_libslirp-txt.pcap new file mode 120000 index 00000000..39fc722b --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/README.md b/src/frontend/libslirp/fuzzing/README.md new file mode 100644 index 00000000..a028a98b --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/coverage.py b/src/frontend/libslirp/fuzzing/coverage.py new file mode 100755 index 00000000..861f2acf --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/fuzz-input.options b/src/frontend/libslirp/fuzzing/fuzz-input.options new file mode 100644 index 00000000..79488880 --- /dev/null +++ b/src/frontend/libslirp/fuzzing/fuzz-input.options @@ -0,0 +1,2 @@ +[libfuzzer] +max_len = 1024 diff --git a/src/frontend/libslirp/fuzzing/fuzz-main.c b/src/frontend/libslirp/fuzzing/fuzz-main.c new file mode 100644 index 00000000..1de031c2 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/helper.c b/src/frontend/libslirp/fuzzing/helper.c new file mode 100644 index 00000000..399cfb5c --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/helper.h b/src/frontend/libslirp/fuzzing/helper.h new file mode 100644 index 00000000..92b5f620 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/meson.build b/src/frontend/libslirp/fuzzing/meson.build new file mode 100644 index 00000000..c2017951 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/oss-fuzz.sh b/src/frontend/libslirp/fuzzing/oss-fuzz.sh new file mode 100755 index 00000000..0561bdba --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/reproducer.c b/src/frontend/libslirp/fuzzing/reproducer.c new file mode 100644 index 00000000..49704af7 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_base_fuzz.h b/src/frontend/libslirp/fuzzing/slirp_base_fuzz.h new file mode 100644 index 00000000..05b32d66 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_arp.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_arp.c new file mode 100644 index 00000000..c403e164 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_icmp.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_icmp.c new file mode 100644 index 00000000..8a422c4b --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_icmp6.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_icmp6.c new file mode 100644 index 00000000..bbe041c9 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_ip6_header.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_ip6_header.c new file mode 100644 index 00000000..4714f92d --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_ip_header.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_ip_header.c new file mode 100644 index 00000000..fe07540e --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_tcp.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_tcp.c new file mode 100644 index 00000000..461d4304 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_tcp6.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_tcp6.c new file mode 100644 index 00000000..64ad9032 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_tcp6_data.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_tcp6_data.c new file mode 100644 index 00000000..87982b0c --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_tcp6_header.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_tcp6_header.c new file mode 100644 index 00000000..e6c8b774 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_tcp_data.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_tcp_data.c new file mode 100644 index 00000000..05971c09 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_tcp_header.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_tcp_header.c new file mode 100644 index 00000000..96904ab2 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_udp.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_udp.c new file mode 100644 index 00000000..272258e4 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_udp6.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_udp6.c new file mode 100644 index 00000000..4b00de75 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_udp6_data.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_udp6_data.c new file mode 100644 index 00000000..83068557 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_udp6_header.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_udp6_header.c new file mode 100644 index 00000000..a2734b46 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_udp_data.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_udp_data.c new file mode 100644 index 00000000..bc8930c9 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/slirp_fuzz_udp_header.c b/src/frontend/libslirp/fuzzing/slirp_fuzz_udp_header.c new file mode 100644 index 00000000..b2dc2729 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/fuzzing/tftp/toto b/src/frontend/libslirp/fuzzing/tftp/toto new file mode 100644 index 00000000..0b70c57e Binary files /dev/null and b/src/frontend/libslirp/fuzzing/tftp/toto differ diff --git a/src/frontend/libslirp/glib/glib.c b/src/frontend/libslirp/glib/glib.c new file mode 100644 index 00000000..16bc9dcc --- /dev/null +++ b/src/frontend/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/frontend/libslirp/glib/glib.h b/src/frontend/libslirp/glib/glib.h new file mode 100644 index 00000000..e979b12a --- /dev/null +++ b/src/frontend/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/frontend/libslirp/meson.build b/src/frontend/libslirp/meson.build new file mode 100644 index 00000000..29dc0ad8 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/meson_options.txt b/src/frontend/libslirp/meson_options.txt new file mode 100644 index 00000000..17709ec3 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/arp_table.c b/src/frontend/libslirp/src/arp_table.c new file mode 100644 index 00000000..3cf2ecc2 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/bootp.c b/src/frontend/libslirp/src/bootp.c new file mode 100644 index 00000000..147955fc --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/bootp.h b/src/frontend/libslirp/src/bootp.h new file mode 100644 index 00000000..c5109602 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/cksum.c b/src/frontend/libslirp/src/cksum.c new file mode 100644 index 00000000..b1cb97b7 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/debug.h b/src/frontend/libslirp/src/debug.h new file mode 100644 index 00000000..f4da00cf --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/dhcpv6.c b/src/frontend/libslirp/src/dhcpv6.c new file mode 100644 index 00000000..77b451b9 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/dhcpv6.h b/src/frontend/libslirp/src/dhcpv6.h new file mode 100644 index 00000000..c0b4a248 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/dnssearch.c b/src/frontend/libslirp/src/dnssearch.c new file mode 100644 index 00000000..cbd1a197 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/if.c b/src/frontend/libslirp/src/if.c new file mode 100644 index 00000000..c49a64ce --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/if.h b/src/frontend/libslirp/src/if.h new file mode 100644 index 00000000..7cf9d275 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/ip.h b/src/frontend/libslirp/src/ip.h new file mode 100644 index 00000000..f0859f0c --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/ip6.h b/src/frontend/libslirp/src/ip6.h new file mode 100644 index 00000000..50765e6a --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/ip6_icmp.c b/src/frontend/libslirp/src/ip6_icmp.c new file mode 100644 index 00000000..3a5878fd --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/ip6_icmp.h b/src/frontend/libslirp/src/ip6_icmp.h new file mode 100644 index 00000000..7f8bc60b --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/ip6_input.c b/src/frontend/libslirp/src/ip6_input.c new file mode 100644 index 00000000..4aca0828 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/ip6_output.c b/src/frontend/libslirp/src/ip6_output.c new file mode 100644 index 00000000..834f1c0a --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/ip_icmp.c b/src/frontend/libslirp/src/ip_icmp.c new file mode 100644 index 00000000..74524fd3 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/ip_icmp.h b/src/frontend/libslirp/src/ip_icmp.h new file mode 100644 index 00000000..aad04165 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/ip_input.c b/src/frontend/libslirp/src/ip_input.c new file mode 100644 index 00000000..0a4b008a --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/ip_output.c b/src/frontend/libslirp/src/ip_output.c new file mode 100644 index 00000000..4f626059 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/libslirp-version.h.in b/src/frontend/libslirp/src/libslirp-version.h.in new file mode 100644 index 00000000..faa6c859 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/libslirp.h b/src/frontend/libslirp/src/libslirp.h new file mode 100644 index 00000000..68f7edfa --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/libslirp.map b/src/frontend/libslirp/src/libslirp.map new file mode 100644 index 00000000..3921f8a1 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/libslirp.test.map b/src/frontend/libslirp/src/libslirp.test.map new file mode 100644 index 00000000..773376bb --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/main.h b/src/frontend/libslirp/src/main.h new file mode 100644 index 00000000..ca36277e --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/mbuf.c b/src/frontend/libslirp/src/mbuf.c new file mode 100644 index 00000000..5ccbda31 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/mbuf.h b/src/frontend/libslirp/src/mbuf.h new file mode 100644 index 00000000..ff1ddc96 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/misc.c b/src/frontend/libslirp/src/misc.c new file mode 100644 index 00000000..3c258350 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/misc.h b/src/frontend/libslirp/src/misc.h new file mode 100644 index 00000000..39fe367a --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/ncsi-pkt.h b/src/frontend/libslirp/src/ncsi-pkt.h new file mode 100644 index 00000000..27bedf6a --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/ncsi.c b/src/frontend/libslirp/src/ncsi.c new file mode 100644 index 00000000..846da9a4 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/ndp_table.c b/src/frontend/libslirp/src/ndp_table.c new file mode 100644 index 00000000..fdb189d5 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/sbuf.c b/src/frontend/libslirp/src/sbuf.c new file mode 100644 index 00000000..6126baad --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/sbuf.h b/src/frontend/libslirp/src/sbuf.h new file mode 100644 index 00000000..a3eaf496 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/slirp.c b/src/frontend/libslirp/src/slirp.c new file mode 100644 index 00000000..fd68f632 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/slirp.h b/src/frontend/libslirp/src/slirp.h new file mode 100644 index 00000000..2715353f --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/socket.c b/src/frontend/libslirp/src/socket.c new file mode 100644 index 00000000..51a13647 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/socket.h b/src/frontend/libslirp/src/socket.h new file mode 100644 index 00000000..27d3b8a6 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/state.c b/src/frontend/libslirp/src/state.c new file mode 100644 index 00000000..f10edf07 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/stream.c b/src/frontend/libslirp/src/stream.c new file mode 100644 index 00000000..6cf326f6 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/stream.h b/src/frontend/libslirp/src/stream.h new file mode 100644 index 00000000..4cdc2369 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/tcp.h b/src/frontend/libslirp/src/tcp.h new file mode 100644 index 00000000..f678eaea --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/tcp_input.c b/src/frontend/libslirp/src/tcp_input.c new file mode 100644 index 00000000..728c26dd --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/tcp_output.c b/src/frontend/libslirp/src/tcp_output.c new file mode 100644 index 00000000..77c1204a --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/tcp_subr.c b/src/frontend/libslirp/src/tcp_subr.c new file mode 100644 index 00000000..3cfe2860 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/tcp_timer.c b/src/frontend/libslirp/src/tcp_timer.c new file mode 100644 index 00000000..aeb610fb --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/tcp_timer.h b/src/frontend/libslirp/src/tcp_timer.h new file mode 100644 index 00000000..3a2e9448 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/tcp_var.h b/src/frontend/libslirp/src/tcp_var.h new file mode 100644 index 00000000..c8da8cbd --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/tcpip.h b/src/frontend/libslirp/src/tcpip.h new file mode 100644 index 00000000..e9c794bd --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/tftp.c b/src/frontend/libslirp/src/tftp.c new file mode 100644 index 00000000..1b396924 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/tftp.h b/src/frontend/libslirp/src/tftp.h new file mode 100644 index 00000000..263c540a --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/udp.c b/src/frontend/libslirp/src/udp.c new file mode 100644 index 00000000..2965b184 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/udp.h b/src/frontend/libslirp/src/udp.h new file mode 100644 index 00000000..b0514f18 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/udp6.c b/src/frontend/libslirp/src/udp6.c new file mode 100644 index 00000000..effdf77d --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/util.c b/src/frontend/libslirp/src/util.c new file mode 100644 index 00000000..8267f0c5 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/util.h b/src/frontend/libslirp/src/util.h new file mode 100644 index 00000000..c378cee1 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/version.c b/src/frontend/libslirp/src/version.c new file mode 100644 index 00000000..93e0be9c --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/vmstate.c b/src/frontend/libslirp/src/vmstate.c new file mode 100644 index 00000000..2c3a727f --- /dev/null +++ b/src/frontend/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/frontend/libslirp/src/vmstate.h b/src/frontend/libslirp/src/vmstate.h new file mode 100644 index 00000000..cd77c850 --- /dev/null +++ b/src/frontend/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/frontend/libslirp/test/ncsitest.c b/src/frontend/libslirp/test/ncsitest.c new file mode 100644 index 00000000..f5ee0b5a --- /dev/null +++ b/src/frontend/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/frontend/libslirp/test/pingtest.c b/src/frontend/libslirp/test/pingtest.c new file mode 100644 index 00000000..247a5b10 --- /dev/null +++ b/src/frontend/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/ArchiveUtil.cpp b/src/frontend/qt_sdl/ArchiveUtil.cpp index 7d1eca9c..aa508e8d 100644 --- a/src/frontend/qt_sdl/ArchiveUtil.cpp +++ b/src/frontend/qt_sdl/ArchiveUtil.cpp @@ -120,13 +120,12 @@ QVector ExtractFileFromArchive(QString path, QString wantedFile, QByteA } -s32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32* filesize) +s32 ExtractFileFromArchive(QString path, QString wantedFile, std::unique_ptr& filedata, u32* filesize) { struct archive *a = archive_read_new(); struct archive_entry *entry; int r; - if (!filedata) return -1; archive_read_support_format_all(a); archive_read_support_filter_all(a); @@ -148,8 +147,8 @@ s32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32* size_t bytesToRead = archive_entry_size(entry); if (filesize) *filesize = bytesToRead; - *filedata = new u8[bytesToRead]; - ssize_t bytesRead = archive_read_data(a, *filedata, bytesToRead); + filedata = std::make_unique(bytesToRead); + ssize_t bytesRead = archive_read_data(a, filedata.get(), bytesToRead); archive_read_close(a); archive_read_free(a); diff --git a/src/frontend/qt_sdl/ArchiveUtil.h b/src/frontend/qt_sdl/ArchiveUtil.h index eaffb0dd..246670e7 100644 --- a/src/frontend/qt_sdl/ArchiveUtil.h +++ b/src/frontend/qt_sdl/ArchiveUtil.h @@ -37,7 +37,7 @@ namespace Archive using namespace melonDS; QVector ListArchive(QString path); -s32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32* filesize); +s32 ExtractFileFromArchive(QString path, QString wantedFile, std::unique_ptr& filedata, u32* filesize); //QVector ExtractFileFromArchive(QString path, QString wantedFile, QByteArray *romBuffer); //u32 ExtractFileFromArchive(const char* path, const char* wantedFile, u8 **romdata); diff --git a/src/frontend/qt_sdl/AudioInOut.cpp b/src/frontend/qt_sdl/AudioInOut.cpp index ae5529d9..1f1ee1c5 100644 --- a/src/frontend/qt_sdl/AudioInOut.cpp +++ b/src/frontend/qt_sdl/AudioInOut.cpp @@ -369,7 +369,7 @@ void UpdateSettings(NDS& nds) { MicClose(); - nds.SPU.SetInterpolation(Config::AudioInterp); + nds.SPU.SetInterpolation(static_cast(Config::AudioInterp)); SetupMicInputData(); MicOpen(); diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.cpp b/src/frontend/qt_sdl/AudioSettingsDialog.cpp index 5e8812e9..8e08ef2b 100644 --- a/src/frontend/qt_sdl/AudioSettingsDialog.cpp +++ b/src/frontend/qt_sdl/AudioSettingsDialog.cpp @@ -51,6 +51,7 @@ AudioSettingsDialog::AudioSettingsDialog(QWidget* parent, bool emuActive, EmuThr ui->cbInterpolation->addItem("Linear"); ui->cbInterpolation->addItem("Cosine"); ui->cbInterpolation->addItem("Cubic"); + ui->cbInterpolation->addItem("Gaussian (SNES)"); ui->cbInterpolation->setCurrentIndex(Config::AudioInterp); ui->cbBitDepth->addItem("Automatic"); @@ -173,7 +174,7 @@ 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(); diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 40815638..60962328 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -5,6 +5,9 @@ include(FixInterfaceIncludes) set(SOURCES_QT_SDL main.cpp main_shaders.h + Screen.cpp + Window.cpp + EmuThread.cpp CheatsDialog.cpp Config.cpp DateTimeDialog.cpp @@ -29,7 +32,6 @@ set(SOURCES_QT_SDL LAN_PCap.cpp LAN_Socket.cpp LocalMP.cpp - OSD.cpp OSD_shaders.h font.h Platform.cpp @@ -38,7 +40,7 @@ set(SOURCES_QT_SDL SaveManager.cpp CameraManager.cpp AudioInOut.cpp - + ArchiveUtil.h ArchiveUtil.cpp @@ -82,16 +84,26 @@ if (BUILD_STATIC) endif() pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2) -pkg_check_modules(Slirp REQUIRED IMPORTED_TARGET slirp) pkg_check_modules(LibArchive REQUIRED IMPORTED_TARGET libarchive) pkg_check_modules(Zstd REQUIRED IMPORTED_TARGET libzstd) -fix_interface_includes(PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive) +fix_interface_includes(PkgConfig::SDL2 PkgConfig::LibArchive) add_compile_definitions(ARCHIVE_SUPPORT_ENABLED) add_executable(melonDS ${SOURCES_QT_SDL}) +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) + target_link_libraries(melonDS PRIVATE PkgConfig::Slirp) +else() + add_subdirectory("../libslirp" + "${CMAKE_BINARY_DIR}/libslirp" + EXCLUDE_FROM_ALL) + target_link_libraries(melonDS PRIVATE slirp) +endif() + if (WIN32) target_link_libraries(melonDS PUBLIC opengl32) @@ -100,6 +112,10 @@ 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) @@ -158,14 +174,16 @@ else() target_include_directories(melonDS PUBLIC ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() target_link_libraries(melonDS PRIVATE core) -target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive PkgConfig::Zstd) +target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::LibArchive PkgConfig::Zstd) target_link_libraries(melonDS PRIVATE ${QT_LINK_LIBS} ${CMAKE_DL_LIBS}) -if (UNIX) - option(PORTABLE "Make a portable build that looks for its configuration in the current directory" OFF) -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") @@ -184,10 +202,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) diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index b6fca7d6..d6d01825 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -22,6 +22,7 @@ #include #include "Platform.h" #include "Config.h" +#include "GPU.h" namespace Config @@ -59,8 +60,10 @@ bool Threaded3D; int GL_ScaleFactor; bool GL_BetterPolygons; +bool GL_HiresCoordinates; bool LimitFPS; +int MaxFPS; bool AudioSync; bool ShowOSD; @@ -141,6 +144,7 @@ bool MouseHide; int MouseHideSeconds; bool PauseLostFocus; +std::string UITheme; int64_t RTCOffset; @@ -244,13 +248,15 @@ ConfigEntry ConfigFile[] = {"ScreenVSync", 1, &ScreenVSync, false, false}, {"ScreenVSyncInterval", 0, &ScreenVSyncInterval, 1, false}, - {"3DRenderer", 0, &_3DRenderer, 0, false}, + {"3DRenderer", 0, &_3DRenderer, renderer3D_Software, false}, {"Threaded3D", 1, &Threaded3D, true, false}, {"GL_ScaleFactor", 0, &GL_ScaleFactor, 1, false}, {"GL_BetterPolygons", 1, &GL_BetterPolygons, false, false}, + {"GL_HiresCoordinates", 1, &GL_HiresCoordinates, true, false}, {"LimitFPS", 1, &LimitFPS, true, false}, + {"MaxFPS", 0, &MaxFPS, 1000, false}, {"AudioSync", 1, &AudioSync, false}, {"ShowOSD", 1, &ShowOSD, true, false}, @@ -342,6 +348,7 @@ ConfigEntry ConfigFile[] = {"MouseHide", 1, &MouseHide, false, false}, {"MouseHideSeconds", 0, &MouseHideSeconds, 5, false}, {"PauseLostFocus", 1, &PauseLostFocus, false, false}, + {"UITheme", 2, &UITheme, (std::string)"", false}, {"RTCOffset", 3, &RTCOffset, (int64_t)0, true}, @@ -374,7 +381,7 @@ ConfigEntry ConfigFile[] = }; -void LoadFile(int inst) +bool LoadFile(int inst, int actualinst) { Platform::FileHandle* f; if (inst > 0) @@ -382,11 +389,17 @@ void LoadFile(int inst) char name[100] = {0}; snprintf(name, 99, kUniqueConfigFile, inst+1); f = Platform::OpenLocalFile(name, Platform::FileMode::ReadText); + + if (!Platform::CheckLocalFileWritable(name)) return false; } else + { f = Platform::OpenLocalFile(kConfigFile, Platform::FileMode::ReadText); - if (!f) return; + if (actualinst == 0 && !Platform::CheckLocalFileWritable(kConfigFile)) return false; + } + + if (!f) return true; char linebuf[1024]; char entryname[32]; @@ -421,9 +434,10 @@ void LoadFile(int inst) } CloseFile(f); + return true; } -void Load() +bool Load() { for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) @@ -436,12 +450,14 @@ void Load() case 3: *(int64_t*)entry->Value = std::get(entry->Default); break; } } - - LoadFile(0); - + int inst = Platform::InstanceID(); + + bool ret = LoadFile(0, inst); if (inst > 0) - LoadFile(inst); + ret = LoadFile(inst, inst); + + return ret; } void Save() diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h index 5e3db823..38a1c34c 100644 --- a/src/frontend/qt_sdl/Config.h +++ b/src/frontend/qt_sdl/Config.h @@ -51,6 +51,16 @@ enum micInputType_MAX, }; +enum +{ + renderer3D_Software = 0, +#ifdef OGLRENDERER_ENABLED + renderer3D_OpenGL, + renderer3D_OpenGLCompute, +#endif + renderer3D_Max, +}; + namespace Config { @@ -103,8 +113,10 @@ extern bool Threaded3D; extern int GL_ScaleFactor; extern bool GL_BetterPolygons; +extern bool GL_HiresCoordinates; extern bool LimitFPS; +extern int MaxFPS; extern bool AudioSync; extern bool ShowOSD; @@ -184,6 +196,7 @@ extern bool EnableCheats; extern bool MouseHide; extern int MouseHideSeconds; extern bool PauseLostFocus; +extern std::string UITheme; extern int64_t RTCOffset; @@ -202,7 +215,7 @@ extern bool GdbARM7BreakOnStartup; extern bool GdbARM9BreakOnStartup; -void Load(); +bool Load(); void Save(); } diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.cpp b/src/frontend/qt_sdl/EmuSettingsDialog.cpp index 0a834a65..ca9c6716 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.cpp +++ b/src/frontend/qt_sdl/EmuSettingsDialog.cpp @@ -380,6 +380,12 @@ void EmuSettingsDialog::on_btnFirmwareBrowse_clicked() 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); @@ -436,6 +442,12 @@ void EmuSettingsDialog::on_btnDLDISDBrowse_clicked() 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); @@ -468,6 +480,13 @@ void EmuSettingsDialog::on_btnDSiFirmwareBrowse_clicked() 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); @@ -482,6 +501,13 @@ void EmuSettingsDialog::on_btnDSiNANDBrowse_clicked() 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); @@ -510,6 +536,12 @@ void EmuSettingsDialog::on_btnDSiSDBrowse_clicked() 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); diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.ui b/src/frontend/qt_sdl/EmuSettingsDialog.ui index 74bc0865..2746e1da 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.ui +++ b/src/frontend/qt_sdl/EmuSettingsDialog.ui @@ -26,7 +26,7 @@ - 5 + 0 diff --git a/src/frontend/qt_sdl/EmuThread.cpp b/src/frontend/qt_sdl/EmuThread.cpp new file mode 100644 index 00000000..ee25dbd4 --- /dev/null +++ b/src/frontend/qt_sdl/EmuThread.cpp @@ -0,0 +1,792 @@ +/* + 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 "main.h" +#include "Input.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 "GPU3D_Compute.h" + +#include "Savestate.h" + +#include "ROMManager.h" +#include "EmuThread.h" +//#include "ArchiveUtil.h" +//#include "CameraManager.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) +{ + EmuStatus = emuStatus_Exit; + EmuRunning = emuStatus_Paused; + EmuPauseStack = EmuPauseStackRunning; + RunningSomething = false; + + 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())); +} + +std::unique_ptr EmuThread::CreateConsole( + std::unique_ptr &&ndscart, + std::unique_ptr &&gbacart) noexcept +{ + auto arm7bios = ROMManager::LoadARM7BIOS(); + if (!arm7bios) + return nullptr; + + 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), + std::move(arm9bios), + std::move(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) + { + 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), + std::move(arm9ibios), + std::move(arm7ibios), + std::move(*nand), + std::move(sdcard), + Config::DSiFullBIOSBoot, + }; + + args.GBAROM = nullptr; + + return std::make_unique(std::move(args)); + } + + return std::make_unique(std::move(ndsargs)); +} + +bool EmuThread::UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept +{ + // 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(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::Current = NDS.get(); + + return true; +} + +void EmuThread::run() +{ + u32 mainScreenPos[3]; + Platform::FileHandle* file; + + UpdateConsole(nullptr, nullptr); + // No carts are inserted when melonDS first boots + + mainScreenPos[0] = 0; + mainScreenPos[1] = 0; + mainScreenPos[2] = 0; + autoScreenSizing = 0; + + videoSettingsDirty = false; + + if (mainWindow->hasOGL) + { + screenGL = static_cast(mainWindow->panel); + screenGL->initOpenGL(); + videoRenderer = Config::_3DRenderer; + } + else + { + screenGL = nullptr; + videoRenderer = 0; + } + + updateRenderer(); + + Input::Init(); + + u32 nframes = 0; + perfCountsSec = 1.0 / SDL_GetPerformanceFrequency(); + double lastTime = SDL_GetPerformanceCounter() * perfCountsSec; + double frameLimitError = 0.0; + double lastMeasureTime = lastTime; + + 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) + { + Input::Process(); + + if (Input::HotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange(); + + if (Input::HotkeyPressed(HK_Pause)) emit windowEmuPause(); + if (Input::HotkeyPressed(HK_Reset)) emit windowEmuReset(); + if (Input::HotkeyPressed(HK_FrameStep)) emit windowEmuFrameStep(); + + if (Input::HotkeyPressed(HK_FullscreenToggle)) emit windowFullscreenToggle(); + + if (Input::HotkeyPressed(HK_SwapScreens)) emit swapScreensToggle(); + if (Input::HotkeyPressed(HK_SwapScreenEmphasis)) emit screenEmphasisToggle(); + + if (EmuRunning == emuStatus_Running || EmuRunning == emuStatus_FrameStep) + { + EmuStatus = emuStatus_Running; + if (EmuRunning == emuStatus_FrameStep) EmuRunning = emuStatus_Paused; + + if (Input::HotkeyPressed(HK_SolarSensorDecrease)) + { + int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorDown, true); + if (level != -1) + { + mainWindow->osdAddMessage(0, "Solar sensor level: %d", level); + } + } + if (Input::HotkeyPressed(HK_SolarSensorIncrease)) + { + int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorUp, true); + if (level != -1) + { + mainWindow->osdAddMessage(0, "Solar sensor level: %d", level); + } + } + + if (NDS->ConsoleType == 1) + { + DSi& dsi = static_cast(*NDS); + double currentTime = SDL_GetPerformanceCounter() * perfCountsSec; + + // Handle power button + if (Input::HotkeyDown(HK_PowerButton)) + { + dsi.I2C.GetBPTWL()->SetPowerButtonHeld(currentTime); + } + else if (Input::HotkeyReleased(HK_PowerButton)) + { + dsi.I2C.GetBPTWL()->SetPowerButtonReleased(currentTime); + } + + // Handle volume buttons + if (Input::HotkeyDown(HK_VolumeUp)) + { + dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Up); + } + else if (Input::HotkeyReleased(HK_VolumeUp)) + { + dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Up); + } + + if (Input::HotkeyDown(HK_VolumeDown)) + { + dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Down); + } + else if (Input::HotkeyReleased(HK_VolumeDown)) + { + dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Down); + } + + dsi.I2C.GetBPTWL()->ProcessVolumeSwitchInput(currentTime); + } + + // 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 (screenGL) + { + screenGL->setSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); + videoRenderer = Config::_3DRenderer; + } +#ifdef OGLRENDERER_ENABLED + else +#endif + { + videoRenderer = 0; + } + + updateRenderer(); + + videoSettingsDirty = false; + } + + // process input and hotkeys + NDS->SetKeyMask(Input::InputMask); + + if (Input::HotkeyPressed(HK_Lid)) + { + bool lid = !NDS->IsLidClosed(); + NDS->SetLidClosed(lid); + mainWindow->osdAddMessage(0, lid ? "Lid closed" : "Lid opened"); + } + + // microphone input + AudioInOut::MicProcess(*NDS); + + // auto screen layout + if (Config::ScreenSizing == Frontend::screenSizing_Auto) + { + mainScreenPos[2] = mainScreenPos[1]; + mainScreenPos[1] = mainScreenPos[0]; + mainScreenPos[0] = NDS->PowerControl9 >> 15; + + int guess; + if (mainScreenPos[0] == mainScreenPos[2] && + mainScreenPos[0] != mainScreenPos[1]) + { + // constant flickering, likely displaying 3D on both screens + // TODO: when both screens are used for 2D only...??? + guess = Frontend::screenSizing_Even; + } + else + { + if (mainScreenPos[0] == 1) + guess = Frontend::screenSizing_EmphTop; + else + guess = Frontend::screenSizing_EmphBot; + } + + if (guess != autoScreenSizing) + { + autoScreenSizing = guess; + emit screenLayoutChange(); + } + } + + + // emulate + u32 nlines; + if (NDS->GPU.GetRenderer3D().NeedsShaderCompile()) + { + compileShaders(); + nlines = 1; + } + else + { + nlines = NDS->RunFrame(); + } + + if (ROMManager::NDSSave) + ROMManager::NDSSave->CheckFlush(); + + if (ROMManager::GBASave) + ROMManager::GBASave->CheckFlush(); + + if (ROMManager::FirmwareSave) + ROMManager::FirmwareSave->CheckFlush(); + + if (!screenGL) + { + FrontBufferLock.lock(); + FrontBuffer = NDS->GPU.FrontBuffer; + FrontBufferLock.unlock(); + } + else + { + FrontBuffer = NDS->GPU.FrontBuffer; + screenGL->drawScreenGL(); + } + +#ifdef MELONCAP + MelonCap::Update(); +#endif // MELONCAP + + if (EmuRunning == emuStatus_Exit) break; + + winUpdateCount++; + if (winUpdateCount >= winUpdateFreq && !screenGL) + { + emit windowUpdate(); + winUpdateCount = 0; + } + + bool fastforward = Input::HotkeyDown(HK_FastForward); + + if (fastforward && screenGL && Config::ScreenVSync) + { + screenGL->setSwapInterval(0); + } + + if (Config::DSiVolumeSync && NDS->ConsoleType == 1) + { + DSi& dsi = static_cast(*NDS); + u8 volumeLevel = dsi.I2C.GetBPTWL()->GetVolumeLevel(); + if (volumeLevel != dsiVolumeLevel) + { + dsiVolumeLevel = volumeLevel; + emit syncVolumeLevel(); + } + + Config::AudioVolume = volumeLevel * (256.0 / 31.0); + } + + if (Config::AudioSync && !fastforward) + AudioInOut::AudioSync(*this->NDS); + + double frametimeStep = nlines / (60.0 * 263.0); + + { + bool limitfps = Config::LimitFPS && !fastforward; + + double practicalFramelimit = limitfps ? frametimeStep : 1.0 / Config::MaxFPS; + + double curtime = SDL_GetPerformanceCounter() * perfCountsSec; + + frameLimitError += practicalFramelimit - (curtime - lastTime); + if (frameLimitError < -practicalFramelimit) + frameLimitError = -practicalFramelimit; + if (frameLimitError > practicalFramelimit) + frameLimitError = practicalFramelimit; + + if (round(frameLimitError * 1000.0) > 0.0) + { + SDL_Delay(round(frameLimitError * 1000.0)); + double timeBeforeSleep = curtime; + curtime = SDL_GetPerformanceCounter() * perfCountsSec; + frameLimitError -= curtime - timeBeforeSleep; + } + + lastTime = curtime; + } + + nframes++; + if (nframes >= 30) + { + double time = SDL_GetPerformanceCounter() * perfCountsSec; + double dt = time - lastMeasureTime; + lastMeasureTime = time; + + u32 fps = round(nframes / dt); + nframes = 0; + + float fpstarget = 1.0/frametimeStep; + + winUpdateFreq = fps / (u32)round(fpstarget); + if (winUpdateFreq < 1) + winUpdateFreq = 1; + + int inst = Platform::InstanceID(); + if (inst == 0) + sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); + else + sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1); + changeWindowTitle(melontitle); + } + } + else + { + // paused + nframes = 0; + lastTime = SDL_GetPerformanceCounter() * perfCountsSec; + lastMeasureTime = lastTime; + + emit windowUpdate(); + + EmuStatus = EmuRunning; + + int inst = Platform::InstanceID(); + if (inst == 0) + sprintf(melontitle, "melonDS " MELONDS_VERSION); + else + sprintf(melontitle, "melonDS (%d)", inst+1); + changeWindowTitle(melontitle); + + SDL_Delay(75); + + if (screenGL) + screenGL->drawScreenGL(); + + ContextRequestKind contextRequest = ContextRequest; + if (contextRequest == contextRequest_InitGL) + { + screenGL = static_cast(mainWindow->panel); + screenGL->initOpenGL(); + ContextRequest = contextRequest_None; + } + else if (contextRequest == contextRequest_DeInitGL) + { + screenGL->deinitOpenGL(); + screenGL = nullptr; + ContextRequest = contextRequest_None; + } + } + } + + 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); + } + + EmuStatus = emuStatus_Exit; + + NDS::Current = nullptr; + // nds is out of scope, so unique_ptr cleans it up for us +} + +void EmuThread::changeWindowTitle(char* title) +{ + emit windowTitleChange(QString(title)); +} + +void EmuThread::emuRun() +{ + EmuRunning = emuStatus_Running; + EmuPauseStack = EmuPauseStackRunning; + RunningSomething = true; + + // checkme + emit windowEmuStart(); + AudioInOut::Enable(); +} + +void EmuThread::initContext() +{ + ContextRequest = contextRequest_InitGL; + while (ContextRequest != contextRequest_None); +} + +void EmuThread::deinitContext() +{ + ContextRequest = contextRequest_DeInitGL; + while (ContextRequest != contextRequest_None); +} + +void EmuThread::emuPause() +{ + EmuPauseStack++; + if (EmuPauseStack > EmuPauseStackPauseThreshold) return; + + PrevEmuStatus = EmuRunning; + EmuRunning = emuStatus_Paused; + while (EmuStatus != emuStatus_Paused); + + AudioInOut::Disable(); +} + +void EmuThread::emuUnpause() +{ + if (EmuPauseStack < EmuPauseStackPauseThreshold) return; + + EmuPauseStack--; + if (EmuPauseStack >= EmuPauseStackPauseThreshold) return; + + EmuRunning = PrevEmuStatus; + + AudioInOut::Enable(); +} + +void EmuThread::emuStop() +{ + EmuRunning = emuStatus_Exit; + EmuPauseStack = EmuPauseStackRunning; + + AudioInOut::Disable(); +} + +void EmuThread::emuFrameStep() +{ + if (EmuPauseStack < EmuPauseStackPauseThreshold) emit windowEmuPause(); + EmuRunning = emuStatus_FrameStep; +} + +bool EmuThread::emuIsRunning() +{ + return EmuRunning == emuStatus_Running; +} + +bool EmuThread::emuIsActive() +{ + return (RunningSomething == 1); +} + +void EmuThread::updateRenderer() +{ + if (videoRenderer != lastVideoRenderer) + { + printf("creating renderer %d\n", videoRenderer); + switch (videoRenderer) + { + case renderer3D_Software: + NDS->GPU.SetRenderer3D(std::make_unique()); + break; + case renderer3D_OpenGL: + NDS->GPU.SetRenderer3D(GLRenderer::New()); + break; + case renderer3D_OpenGLCompute: + NDS->GPU.SetRenderer3D(ComputeRenderer::New()); + break; + default: __builtin_unreachable(); + } + } + lastVideoRenderer = videoRenderer; + + switch (videoRenderer) + { + case renderer3D_Software: + static_cast(NDS->GPU.GetRenderer3D()).SetThreaded(Config::Threaded3D, NDS->GPU); + break; + case renderer3D_OpenGL: + static_cast(NDS->GPU.GetRenderer3D()).SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor); + break; + case renderer3D_OpenGLCompute: + static_cast(NDS->GPU.GetRenderer3D()).SetRenderSettings(Config::GL_ScaleFactor, Config::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 + { + NDS->GPU.GetRenderer3D().ShaderCompileStep(currentShader, shadersCount); + } while (NDS->GPU.GetRenderer3D().NeedsShaderCompile() && + (SDL_GetPerformanceCounter() - startTime) * perfCountsSec < 1.0 / 6.0); + mainWindow->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 new file mode 100644 index 00000000..4b19acf9 --- /dev/null +++ b/src/frontend/qt_sdl/EmuThread.h @@ -0,0 +1,138 @@ +/* + 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 EMUTHREAD_H +#define EMUTHREAD_H + +#include +#include + +#include +#include +#include + +#include "NDSCart.h" +#include "GBACart.h" + +using Keep = std::monostate; +using UpdateConsoleNDSArgs = std::variant>; +using UpdateConsoleGBAArgs = std::variant>; +namespace melonDS +{ +class NDS; +} + +class ScreenPanelGL; + +class EmuThread : public QThread +{ + Q_OBJECT + void run() override; + +public: + explicit EmuThread(QObject* parent = nullptr); + + void changeWindowTitle(char* title); + + // to be called from the UI thread + void emuRun(); + void emuPause(); + void emuUnpause(); + void emuStop(); + void emuFrameStep(); + + bool emuIsRunning(); + bool emuIsActive(); + + void initContext(); + void deinitContext(); + + 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 windowEmuReset(); + void windowEmuFrameStep(); + + void windowLimitFPSChange(); + + void screenLayoutChange(); + + void windowFullscreenToggle(); + + void swapScreensToggle(); + void screenEmphasisToggle(); + + void syncVolumeLevel(); + +private: + void updateRenderer(); + void compileShaders(); + + std::unique_ptr CreateConsole( + std::unique_ptr&& ndscart, + std::unique_ptr&& gbacart + ) noexcept; + + enum EmuStatusKind + { + emuStatus_Exit, + emuStatus_Running, + emuStatus_Paused, + emuStatus_FrameStep, + }; + std::atomic EmuStatus; + + EmuStatusKind PrevEmuStatus; + EmuStatusKind EmuRunning; + + 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; + + ScreenPanelGL* screenGL; + + int autoScreenSizing; + + int lastVideoRenderer = -1; + + double perfCountsSec; +}; + +#endif // EMUTHREAD_H diff --git a/src/frontend/qt_sdl/Input.cpp b/src/frontend/qt_sdl/Input.cpp index c429cd36..7ebd7e2a 100644 --- a/src/frontend/qt_sdl/Input.cpp +++ b/src/frontend/qt_sdl/Input.cpp @@ -128,6 +128,11 @@ void KeyRelease(QKeyEvent* event) KeyHotkeyMask &= ~(1< + #include "types.h" namespace Input @@ -38,6 +40,7 @@ void CloseJoystick(); void KeyPress(QKeyEvent* event); void KeyRelease(QKeyEvent* event); +void KeyReleaseAll(); void Process(); diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp index 2f7417f6..851e7abf 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp @@ -16,15 +16,16 @@ 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); @@ -34,6 +35,19 @@ InterfaceSettingsDialog::InterfaceSettingsDialog(QWidget* parent) : QDialog(pare ui->spinMouseHideSeconds->setEnabled(Config::MouseHide != 0); ui->spinMouseHideSeconds->setValue(Config::MouseHideSeconds); ui->cbPauseLostFocus->setChecked(Config::PauseLostFocus != 0); + ui->spinMaxFPS->setValue(Config::MaxFPS); + + const QList themeKeys = QStyleFactory::keys(); + const QString currentTheme = qApp->style()->objectName(); + + ui->cbxUITheme->addItem("System default", ""); + + for (int i = 0; i < themeKeys.length(); i++) + { + ui->cbxUITheme->addItem(themeKeys[i], themeKeys[i]); + if (!Config::UITheme.empty() && themeKeys[i].compare(currentTheme, Qt::CaseInsensitive) == 0) + ui->cbxUITheme->setCurrentIndex(i + 1); + } } InterfaceSettingsDialog::~InterfaceSettingsDialog() @@ -60,9 +74,18 @@ void InterfaceSettingsDialog::done(int r) Config::MouseHide = ui->cbMouseHide->isChecked() ? 1:0; Config::MouseHideSeconds = ui->spinMouseHideSeconds->value(); Config::PauseLostFocus = ui->cbPauseLostFocus->isChecked() ? 1:0; + Config::MaxFPS = ui->spinMaxFPS->value(); + + QString themeName = ui->cbxUITheme->currentData().toString(); + Config::UITheme = themeName.toStdString(); Config::Save(); + if (!Config::UITheme.empty()) + qApp->setStyle(themeName); + else + qApp->setStyle(*systemThemeName); + emit updateMouseTimer(); } diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.ui b/src/frontend/qt_sdl/InterfaceSettingsDialog.ui index 8ee9feda..21d8434e 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.ui +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.ui @@ -6,8 +6,8 @@ 0 0 - 262 - 113 + 337 + 275 @@ -19,32 +19,113 @@ Interface settings - melonDS - - - - - Hide after + + + + + 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 + + + + + Fast-forward limit + + + spinMaxFPS + + + + + + + FPS + + + 60 + + + 1000 + + + 1000 + + + + - - - - Hide mouse after inactivity - - - - - - - + Qt::Horizontal @@ -54,20 +135,8 @@ - - - - seconds of inactivity - - - - - cbMouseHide - spinMouseHideSeconds - cbPauseLostFocus - diff --git a/src/frontend/qt_sdl/LAN_Socket.cpp b/src/frontend/qt_sdl/LAN_Socket.cpp index e938af80..df0d754b 100644 --- a/src/frontend/qt_sdl/LAN_Socket.cpp +++ b/src/frontend/qt_sdl/LAN_Socket.cpp @@ -26,7 +26,7 @@ #include "FIFO.h" #include "Platform.h" -#include +#include #ifdef __WIN32__ #include diff --git a/src/frontend/qt_sdl/LocalMP.cpp b/src/frontend/qt_sdl/LocalMP.cpp index a0dfdf76..7ea98686 100644 --- a/src/frontend/qt_sdl/LocalMP.cpp +++ b/src/frontend/qt_sdl/LocalMP.cpp @@ -130,7 +130,7 @@ bool SemInit(int num) char semname[64]; sprintf(semname, "Local\\melonNIFI_Sem%02d", num); - HANDLE sem = CreateSemaphore(nullptr, 0, 64, semname); + HANDLE sem = CreateSemaphoreA(nullptr, 0, 64, semname); SemPool[num] = sem; SemInited[num] = true; return sem != INVALID_HANDLE_VALUE; @@ -248,7 +248,9 @@ bool Init() Log(LogLevel::Info, "MP sharedmem doesn't exist. creating\n"); if (!MPQueue->create(kQueueSize)) { - Log(LogLevel::Error, "MP sharedmem create failed :(\n"); + Log(LogLevel::Error, "MP sharedmem create failed :( (%d)\n", MPQueue->error()); + delete MPQueue; + MPQueue = nullptr; return false; } @@ -303,10 +305,13 @@ void DeInit() if (MPQueue) { MPQueue->lock(); - MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); - header->ConnectedBitmask &= ~(1 << InstanceID); - header->InstanceBitmask &= ~(1 << InstanceID); - header->NumInstances--; + if (MPQueue->data() != nullptr) + { + MPQueueHeader *header = (MPQueueHeader *) MPQueue->data(); + header->ConnectedBitmask &= ~(1 << InstanceID); + header->InstanceBitmask &= ~(1 << InstanceID); + header->NumInstances--; + } MPQueue->unlock(); SemPoolDeinit(); @@ -325,6 +330,7 @@ void SetRecvTimeout(int timeout) void Begin() { + if (!MPQueue) return; MPQueue->lock(); MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); PacketReadOffset = header->PacketWriteOffset; @@ -337,6 +343,7 @@ void Begin() void End() { + if (!MPQueue) return; MPQueue->lock(); MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); //SemReset(InstanceID); @@ -418,6 +425,7 @@ void FIFOWrite(int fifo, void* buf, int len) 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]; @@ -473,6 +481,7 @@ int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp) int RecvPacketGeneric(u8* packet, bool block, u64* timestamp) { + if (!MPQueue) return 0; for (;;) { if (!SemWait(InstanceID, block ? RecvTimeout : 0)) @@ -549,6 +558,8 @@ int SendAck(u8* packet, int len, u64 timestamp) int RecvHostPacket(u8* packet, u64* timestamp) { + if (!MPQueue) return -1; + if (LastHostID != -1) { // check if the host is still connected @@ -568,6 +579,8 @@ int RecvHostPacket(u8* packet, u64* timestamp) u16 RecvReplies(u8* packets, u64 timestamp, u16 aidmask) { + if (!MPQueue) return 0; + u16 ret = 0; u16 myinstmask = (1 << InstanceID); u16 curinstmask; diff --git a/src/frontend/qt_sdl/OSD.cpp b/src/frontend/qt_sdl/OSD.cpp deleted file mode 100644 index 25072df7..00000000 --- a/src/frontend/qt_sdl/OSD.cpp +++ /dev/null @@ -1,475 +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 "../types.h" - -#include "main.h" -#include "OpenGLSupport.h" -#include - -#include "OSD.h" -#include "OSD_shaders.h" -#include "font.h" - -#include "Config.h" - -using namespace melonDS; - -extern MainWindow* mainWindow; - -namespace OSD -{ - -const u32 kOSDMargin = 6; - -struct Item -{ - Uint32 Timestamp; - char Text[256]; - u32 Color; - - u32 Width, Height; - u32* Bitmap; - - bool NativeBitmapLoaded; - QImage NativeBitmap; - - bool GLTextureLoaded; - GLuint GLTexture; -}; - -std::deque ItemQueue; - -GLuint Shader[3]; -GLint uScreenSize, uOSDPos, uOSDSize; -GLfloat uScaleFactor; -GLuint OSDVertexArray; -GLuint OSDVertexBuffer; - -QMutex Rendering; - - -bool Init(bool openGL) -{ - if (openGL) - { - OpenGL::BuildShaderProgram(kScreenVS_OSD, kScreenFS_OSD, Shader, "OSDShader"); - - GLuint pid = Shader[2]; - glBindAttribLocation(pid, 0, "vPosition"); - glBindFragDataLocation(pid, 0, "oColor"); - - OpenGL::LinkShaderProgram(Shader); - glUseProgram(pid); - glUniform1i(glGetUniformLocation(pid, "OSDTex"), 0); - - uScreenSize = glGetUniformLocation(pid, "uScreenSize"); - uOSDPos = glGetUniformLocation(pid, "uOSDPos"); - uOSDSize = glGetUniformLocation(pid, "uOSDSize"); - uScaleFactor = glGetUniformLocation(pid, "uScaleFactor"); - - float vertices[6*2] = - { - 0, 0, - 1, 1, - 1, 0, - 0, 0, - 0, 1, - 1, 1 - }; - - glGenBuffers(1, &OSDVertexBuffer); - glBindBuffer(GL_ARRAY_BUFFER, OSDVertexBuffer); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - - glGenVertexArrays(1, &OSDVertexArray); - glBindVertexArray(OSDVertexArray); - glEnableVertexAttribArray(0); // position - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)(0)); - } - - return true; -} - -void DeInit() -{ - for (auto it = ItemQueue.begin(); it != ItemQueue.end(); ) - { - Item& item = *it; - - if (item.GLTextureLoaded) glDeleteTextures(1, &item.GLTexture); - if (item.Bitmap) delete[] item.Bitmap; - - it = ItemQueue.erase(it); - } -} - - -int FindBreakPoint(const char* text, int i) -{ - // i = character that went out of bounds - - for (int j = i; j >= 0; j--) - { - if (text[j] == ' ') - return j; - } - - return i; -} - -void LayoutText(const char* text, u32* width, u32* height, int* breaks) -{ - u32 w = 0; - u32 h = 14; - u32 totalw = 0; - u32 maxw = mainWindow->panelWidget->width() - (kOSDMargin*2); - int lastbreak = -1; - int numbrk = 0; - u16* ptr; - - memset(breaks, 0, sizeof(int)*64); - - for (int i = 0; text[i] != '\0'; ) - { - int glyphsize; - if (text[i] == ' ') - { - glyphsize = 6; - } - else - { - u32 ch = text[i]; - if (ch < 0x10 || ch > 0x7E) ch = 0x7F; - - ptr = &font[(ch-0x10) << 4]; - glyphsize = ptr[0]; - if (!glyphsize) glyphsize = 6; - else glyphsize += 2; // space around the character - } - - w += glyphsize; - if (w > maxw) - { - // wrap shit as needed - if (text[i] == ' ') - { - if (numbrk >= 64) break; - breaks[numbrk++] = i; - i++; - } - else - { - int brk = FindBreakPoint(text, i); - if (brk != lastbreak) i = brk; - - if (numbrk >= 64) break; - breaks[numbrk++] = i; - - lastbreak = brk; - } - - w = 0; - h += 14; - } - else - i++; - - if (w > totalw) totalw = w; - } - - *width = totalw; - *height = h; -} - -u32 RainbowColor(u32 inc) -{ - // inspired from Acmlmboard - - if (inc < 100) return 0xFFFF9B9B + (inc << 8); - else if (inc < 200) return 0xFFFFFF9B - ((inc-100) << 16); - else if (inc < 300) return 0xFF9BFF9B + (inc-200); - else if (inc < 400) return 0xFF9BFFFF - ((inc-300) << 8); - else if (inc < 500) return 0xFF9B9BFF + ((inc-400) << 16); - else return 0xFFFF9BFF - (inc-500); -} - -void RenderText(u32 color, const char* text, Item* item) -{ - u32 w, h; - int breaks[64]; - - bool rainbow = (color == 0); - u32 rainbowinc = ((text[0] * 17) + (SDL_GetTicks() * 13)) % 600; - - color |= 0xFF000000; - const u32 shadow = 0xE0000000; - - LayoutText(text, &w, &h, breaks); - - item->Width = w; - item->Height = h; - item->Bitmap = new u32[w*h]; - memset(item->Bitmap, 0, w*h*sizeof(u32)); - - u32 x = 0, y = 1; - u32 maxw = mainWindow->panelWidget->width() - (kOSDMargin*2); - int curline = 0; - u16* ptr; - - for (int i = 0; text[i] != '\0'; ) - { - int glyphsize; - if (text[i] == ' ') - { - x += 6; - } - else - { - u32 ch = text[i]; - if (ch < 0x10 || ch > 0x7E) ch = 0x7F; - - ptr = &font[(ch-0x10) << 4]; - int glyphsize = ptr[0]; - if (!glyphsize) x += 6; - else - { - x++; - - if (rainbow) - { - color = RainbowColor(rainbowinc); - rainbowinc = (rainbowinc + 30) % 600; - } - - // draw character - for (int cy = 0; cy < 12; cy++) - { - u16 val = ptr[4+cy]; - - for (int cx = 0; cx < glyphsize; cx++) - { - if (val & (1<Bitmap[((y+cy) * w) + x+cx] = color; - } - } - - x += glyphsize; - x++; - } - } - - i++; - if (breaks[curline] && i >= breaks[curline]) - { - i = breaks[curline++]; - if (text[i] == ' ') i++; - - x = 0; - y += 14; - } - } - - // shadow - for (y = 0; y < h; y++) - { - for (x = 0; x < w; x++) - { - u32 val; - - val = item->Bitmap[(y * w) + x]; - if ((val >> 24) == 0xFF) continue; - - if (x > 0) val = item->Bitmap[(y * w) + x-1]; - if (x < w-1) val |= item->Bitmap[(y * w) + x+1]; - if (y > 0) - { - if (x > 0) val |= item->Bitmap[((y-1) * w) + x-1]; - val |= item->Bitmap[((y-1) * w) + x]; - if (x < w-1) val |= item->Bitmap[((y-1) * w) + x+1]; - } - if (y < h-1) - { - if (x > 0) val |= item->Bitmap[((y+1) * w) + x-1]; - val |= item->Bitmap[((y+1) * w) + x]; - if (x < w-1) val |= item->Bitmap[((y+1) * w) + x+1]; - } - - if ((val >> 24) == 0xFF) - item->Bitmap[(y * w) + x] = shadow; - } - } -} - - -void AddMessage(u32 color, const char* text) -{ - if (!Config::ShowOSD) return; - - Rendering.lock(); - - Item item; - - item.Timestamp = SDL_GetTicks(); - strncpy(item.Text, text, 255); item.Text[255] = '\0'; - item.Color = color; - item.Bitmap = nullptr; - - item.NativeBitmapLoaded = false; - item.GLTextureLoaded = false; - - ItemQueue.push_back(item); - - Rendering.unlock(); -} - -void Update() -{ - if (!Config::ShowOSD) - { - Rendering.lock(); - for (auto it = ItemQueue.begin(); it != ItemQueue.end(); ) - { - Item& item = *it; - - if (item.GLTextureLoaded) glDeleteTextures(1, &item.GLTexture); - if (item.Bitmap) delete[] item.Bitmap; - - it = ItemQueue.erase(it); - } - Rendering.unlock(); - return; - } - - Rendering.lock(); - - Uint32 tick_now = SDL_GetTicks(); - Uint32 tick_min = tick_now - 2500; - - for (auto it = ItemQueue.begin(); it != ItemQueue.end(); ) - { - Item& item = *it; - - if (item.Timestamp < tick_min) - { - if (item.GLTextureLoaded) glDeleteTextures(1, &item.GLTexture); - if (item.Bitmap) delete[] item.Bitmap; - - it = ItemQueue.erase(it); - continue; - } - - if (!item.Bitmap) - { - RenderText(item.Color, item.Text, &item); - } - - it++; - } - - Rendering.unlock(); -} - -void DrawNative(QPainter& painter) -{ - if (!Config::ShowOSD) return; - - Rendering.lock(); - - u32 y = kOSDMargin; - - painter.resetTransform(); - - for (auto it = ItemQueue.begin(); it != ItemQueue.end(); ) - { - Item& item = *it; - - if (!item.NativeBitmapLoaded) - { - item.NativeBitmap = QImage((const uchar*)item.Bitmap, item.Width, item.Height, QImage::Format_ARGB32_Premultiplied); - item.NativeBitmapLoaded = true; - } - - painter.drawImage(kOSDMargin, y, item.NativeBitmap); - - y += item.Height; - it++; - } - - Rendering.unlock(); -} - -void DrawGL(float w, float h) -{ - if (!Config::ShowOSD) return; - if (!mainWindow || !mainWindow->panel) return; - - Rendering.lock(); - - u32 y = kOSDMargin; - - glUseProgram(Shader[2]); - - glUniform2f(uScreenSize, w, h); - glUniform1f(uScaleFactor, mainWindow->devicePixelRatioF()); - - glBindBuffer(GL_ARRAY_BUFFER, OSDVertexBuffer); - glBindVertexArray(OSDVertexArray); - - glActiveTexture(GL_TEXTURE0); - - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - - for (auto it = ItemQueue.begin(); it != ItemQueue.end(); ) - { - Item& item = *it; - - if (!item.GLTextureLoaded) - { - glGenTextures(1, &item.GLTexture); - glBindTexture(GL_TEXTURE_2D, item.GLTexture); - 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, item.Width, item.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, item.Bitmap); - - item.GLTextureLoaded = true; - } - - glBindTexture(GL_TEXTURE_2D, item.GLTexture); - glUniform2i(uOSDPos, kOSDMargin, y); - glUniform2i(uOSDSize, item.Width, item.Height); - glDrawArrays(GL_TRIANGLES, 0, 2*3); - - y += item.Height; - it++; - } - - glDisable(GL_BLEND); - glUseProgram(0); - - Rendering.unlock(); -} - -} diff --git a/src/frontend/qt_sdl/OSD.h b/src/frontend/qt_sdl/OSD.h deleted file mode 100644 index 64131d5b..00000000 --- a/src/frontend/qt_sdl/OSD.h +++ /dev/null @@ -1,39 +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 OSD_H -#define OSD_H - -#include "types.h" - -namespace OSD -{ - -using namespace melonDS; -bool Init(bool openGL); -void DeInit(); - -void AddMessage(u32 color, const char* text); - -void Update(); -void DrawNative(QPainter& painter); -void DrawGL(float w, float h); - -} - -#endif // OSD_H diff --git a/src/frontend/qt_sdl/PathSettingsDialog.cpp b/src/frontend/qt_sdl/PathSettingsDialog.cpp index 1d698537..71342087 100644 --- a/src/frontend/qt_sdl/PathSettingsDialog.cpp +++ b/src/frontend/qt_sdl/PathSettingsDialog.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "types.h" #include "Config.h" @@ -37,6 +38,7 @@ 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) { @@ -101,6 +103,12 @@ void PathSettingsDialog::on_btnSaveFileBrowse_clicked() QString::fromStdString(EmuDirectory)); if (dir.isEmpty()) return; + + if (!QTemporaryFile(dir).open()) + { + QMessageBox::critical(this, "melonDS", errordialog); + return; + } ui->txtSaveFilePath->setText(dir); } @@ -112,6 +120,12 @@ void PathSettingsDialog::on_btnSavestateBrowse_clicked() QString::fromStdString(EmuDirectory)); if (dir.isEmpty()) return; + + if (!QTemporaryFile(dir).open()) + { + QMessageBox::critical(this, "melonDS", errordialog); + return; + } ui->txtSavestatePath->setText(dir); } @@ -123,6 +137,12 @@ void PathSettingsDialog::on_btnCheatFileBrowse_clicked() QString::fromStdString(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/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index c2e2f47b..b3230a41 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -30,6 +31,7 @@ #include #include #include +#include #include #include "Platform.h" @@ -39,7 +41,6 @@ #include "LAN_Socket.h" #include "LAN_PCap.h" #include "LocalMP.h" -#include "OSD.h" #include "SPI_Firmware.h" #ifdef __WIN32__ @@ -53,10 +54,50 @@ extern CameraManager* camManager[2]; void emuStop(); +// TEMP +//#include "main.h" +//extern MainWindow* mainWindow; + namespace melonDS::Platform { +void PathInit(int argc, char** argv) +{ + // First, check for the portable directory next to the executable. + QString appdirpath = QCoreApplication::applicationDirPath(); + QString portablepath = appdirpath + QDir::separator() + "portable"; + +#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().toStdString(); + } + else + { + // If no overrides are specified, use the default path. +#if defined(__WIN32__) && defined(WIN32_PORTABLE) + EmuDirectory = appdirpath.toStdString(); +#else + QString confdir; + QDir config(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)); + config.mkdir("melonDS"); + confdir = config.absolutePath() + QDir::separator() + "melonDS"; + EmuDirectory = confdir.toStdString(); +#endif + } +} + QSharedMemory* IPCBuffer = nullptr; int IPCInstanceID; @@ -66,12 +107,25 @@ void IPCInit() 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 :(\n"); + Log(LogLevel::Error, "IPC sharedmem create failed: %s\n", IPCBuffer->errorString().toStdString().c_str()); delete IPCBuffer; IPCBuffer = nullptr; return; @@ -117,38 +171,7 @@ void IPCDeInit() void Init(int argc, char** argv) { -#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 - + PathInit(argc, argv); IPCInit(); } @@ -164,14 +187,14 @@ void SignalStop(StopReason reason) { case StopReason::GBAModeNotSupported: Log(LogLevel::Error, "!! GBA MODE NOT SUPPORTED\n"); - OSD::AddMessage(0xFFA0A0, "GBA mode not supported."); + //mainWindow->osdAddMessage(0xFFA0A0, "GBA mode not supported."); break; case StopReason::BadExceptionRegion: - OSD::AddMessage(0xFFA0A0, "Internal error."); + //mainWindow->osdAddMessage(0xFFA0A0, "Internal error."); break; case StopReason::PowerOff: case StopReason::External: - OSD::AddMessage(0xFFC040, "Shutdown"); + //mainWindow->osdAddMessage(0xFFC040, "Shutdown"); default: break; } @@ -193,120 +216,33 @@ std::string InstanceFileSuffix() return suffix; } - -int GetConfigInt(ConfigEntry entry) +static QIODevice::OpenMode GetQMode(FileMode mode) { - const int imgsizes[] = {0, 256, 512, 1024, 2048, 4096}; + 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; - switch (entry) - { -#ifdef JIT_ENABLED - case JIT_MaxBlockSize: return Config::JIT_MaxBlockSize; -#endif + if ((mode & FileMode::Write) && !(mode & FileMode::Preserve)) + qmode |= QIODevice::OpenModeFlag::Truncate; - case DLDI_ImageSize: return imgsizes[Config::DLDISize]; + if (mode & FileMode::NoCreate) + qmode |= QIODevice::OpenModeFlag::ExistingOnly; - case DSiSD_ImageSize: return imgsizes[Config::DSiSDSize]; + if (mode & FileMode::Text) + qmode |= QIODevice::OpenModeFlag::Text; - case AudioBitDepth: return Config::AudioBitDepth; - -#ifdef GDBSTUB_ENABLED - case GdbPortARM7: return Config::GdbPortARM7; - case GdbPortARM9: return Config::GdbPortARM9; -#endif - } - - return 0; -} - -bool GetConfigBool(ConfigEntry entry) -{ - switch (entry) - { -#ifdef JIT_ENABLED - case JIT_Enable: return Config::JIT_Enable != 0; - case JIT_LiteralOptimizations: return Config::JIT_LiteralOptimisations != 0; - case JIT_BranchOptimizations: return Config::JIT_BranchOptimisations != 0; - case JIT_FastMemory: return Config::JIT_FastMemory != 0; -#endif - - case ExternalBIOSEnable: return Config::ExternalBIOSEnable != 0; - - case DLDI_Enable: return Config::DLDIEnable != 0; - case DLDI_ReadOnly: return Config::DLDIReadOnly != 0; - case DLDI_FolderSync: return Config::DLDIFolderSync != 0; - - case DSiSD_Enable: return Config::DSiSDEnable != 0; - case DSiSD_ReadOnly: return Config::DSiSDReadOnly != 0; - case DSiSD_FolderSync: return Config::DSiSDFolderSync != 0; - - case DSi_FullBIOSBoot: return Config::DSiFullBIOSBoot != 0; - -#ifdef GDBSTUB_ENABLED - case GdbEnabled: return Config::GdbEnabled; - case GdbARM7BreakOnStartup: return Config::GdbARM7BreakOnStartup; - case GdbARM9BreakOnStartup: return Config::GdbARM9BreakOnStartup; -#endif - } - - return false; -} - -std::string GetConfigString(ConfigEntry entry) -{ - switch (entry) - { - case DLDI_ImagePath: return Config::DLDISDPath; - case DLDI_FolderPath: return Config::DLDIFolderPath; - - case DSiSD_ImagePath: return Config::DSiSDPath; - case DSiSD_FolderPath: return Config::DSiSDFolderPath; - - case WifiSettingsPath: return Config::WifiSettingsPath; - } - - return ""; -} - -bool GetConfigArray(ConfigEntry entry, void* data) -{ - switch (entry) - { - case Firm_MAC: - { - 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; - } - - return false; + 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'; @@ -345,16 +281,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()); @@ -380,15 +321,7 @@ 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 } return OpenFile(fullpath.toStdString(), mode); @@ -425,6 +358,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; diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/ROMManager.cpp index 2c0cee2c..9607c848 100644 --- a/src/frontend/qt_sdl/ROMManager.cpp +++ b/src/frontend/qt_sdl/ROMManager.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #ifdef ARCHIVE_SUPPORT_ENABLED @@ -68,8 +69,8 @@ std::string BaseGBAROMDir = ""; std::string BaseGBAROMName = ""; std::string BaseGBAAssetName = ""; -SaveManager* NDSSave = nullptr; -SaveManager* GBASave = nullptr; +std::unique_ptr NDSSave = nullptr; +std::unique_ptr GBASave = nullptr; std::unique_ptr FirmwareSave = nullptr; std::unique_ptr BackupState = nullptr; @@ -210,6 +211,9 @@ QString VerifyDSFirmware() f = Platform::OpenLocalFile(Config::FirmwarePath, FileMode::Read); if (!f) return "DS firmware was not found or could not be accessed. Check your emu settings."; + if (!Platform::CheckFileWritable(Config::FirmwarePath)) + return "DS firmware is unable to be written to.\nPlease check file/folder write permissions."; + len = FileLength(f); if (len == 0x20000) { @@ -237,6 +241,9 @@ QString VerifyDSiFirmware() f = Platform::OpenLocalFile(Config::DSiFirmwarePath, FileMode::Read); if (!f) return "DSi firmware was not found or could not be accessed. Check your emu settings."; + if (!Platform::CheckFileWritable(Config::FirmwarePath)) + return "DSi firmware is unable to be written to.\nPlease check file/folder write permissions."; + len = FileLength(f); if (len != 0x20000) { @@ -259,6 +266,9 @@ QString VerifyDSiNAND() f = Platform::OpenLocalFile(Config::DSiNANDPath, FileMode::ReadWriteExisting); if (!f) return "DSi NAND was not found or could not be accessed. Check your emu settings."; + if (!Platform::CheckFileWritable(Config::FirmwarePath)) + 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 @@ -303,6 +313,28 @@ QString VerifySetup() 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) { @@ -480,65 +512,95 @@ void LoadCheats(NDS& nds) nds.AREngine.SetCodeFile(CheatsOn ? CheatFile : nullptr); } -std::optional> LoadARM9BIOS() noexcept +std::unique_ptr LoadARM9BIOS() noexcept { + if (!Config::ExternalBIOSEnable) + { + return Config::ConsoleType == 0 ? std::make_unique(bios_arm9_bin) : nullptr; + } + if (FileHandle* f = OpenLocalFile(Config::BIOS9Path, Read)) { - std::array bios {}; + std::unique_ptr bios = std::make_unique(); FileRewind(f); - FileRead(bios.data(), sizeof(bios), 1, f); + FileRead(bios->data(), bios->size(), 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; + return nullptr; } -std::optional> LoadARM7BIOS() noexcept +std::unique_ptr LoadARM7BIOS() noexcept { + if (!Config::ExternalBIOSEnable) + { + return Config::ConsoleType == 0 ? std::make_unique(bios_arm7_bin) : nullptr; + } + if (FileHandle* f = OpenLocalFile(Config::BIOS7Path, Read)) { - std::array bios {}; - FileRead(bios.data(), sizeof(bios), 1, f); + std::unique_ptr bios = std::make_unique(); + FileRead(bios->data(), bios->size(), 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; + return nullptr; } -std::optional> LoadDSiARM9BIOS() noexcept +std::unique_ptr LoadDSiARM9BIOS() noexcept { if (FileHandle* f = OpenLocalFile(Config::DSiBIOS9Path, Read)) { - std::array bios {}; - FileRead(bios.data(), sizeof(bios), 1, f); + std::unique_ptr bios = std::make_unique(); + FileRead(bios->data(), bios->size(), 1, f); CloseFile(f); + + if (!Config::DSiFullBIOSBoot) + { + // 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", Config::DSiBIOS9Path.c_str()); return bios; } Log(Warn, "ARM9i BIOS not found\n"); - return std::nullopt; + return nullptr; } -std::optional> LoadDSiARM7BIOS() noexcept +std::unique_ptr LoadDSiARM7BIOS() noexcept { if (FileHandle* f = OpenLocalFile(Config::DSiBIOS7Path, Read)) { - std::array bios {}; - FileRead(bios.data(), sizeof(bios), 1, f); + std::unique_ptr bios = std::make_unique(); + FileRead(bios->data(), bios->size(), 1, f); CloseFile(f); + + if (!Config::DSiFullBIOSBoot) + { + // 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", Config::DSiBIOS7Path.c_str()); return bios; } Log(Warn, "ARM7i BIOS not found\n"); - return std::nullopt; + return nullptr; } Firmware GenerateFirmware(int type) noexcept @@ -580,7 +642,7 @@ Firmware GenerateFirmware(int type) noexcept } } - CustomizeFirmware(firmware); + CustomizeFirmware(firmware, true); // If we don't have Wi-fi settings to load, // then the defaults will have already been populated by the constructor. @@ -589,6 +651,16 @@ Firmware GenerateFirmware(int type) noexcept 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()); @@ -609,7 +681,7 @@ std::optional LoadFirmware(int type) noexcept return std::nullopt; } - CustomizeFirmware(firmware); + CustomizeFirmware(firmware, Config::FirmwareOverrideSettings); return firmware; } @@ -694,7 +766,25 @@ std::optional LoadNAND(const std::array& a return nandImage; } -constexpr int imgsizes[] = {0, 256, 512, 1024, 2048, 4096}; +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) @@ -704,97 +794,29 @@ std::optional LoadDSiSDCard() noexcept Config::DSiSDPath, imgsizes[Config::DSiSDSize], Config::DSiSDReadOnly, - Config::DSiSDFolderSync ? Config::DSiSDFolderPath : "" + Config::DSiSDFolderSync ? std::make_optional(Config::DSiSDFolderPath) : std::nullopt ); } -void LoadBIOSFiles(NDS& nds) +std::optional GetDLDISDCardArgs() noexcept { - if (Config::ExternalBIOSEnable) - { - if (FileHandle* f = Platform::OpenLocalFile(Config::BIOS9Path, FileMode::Read)) - { - FileRewind(f); - FileRead(nds.ARM9BIOS, sizeof(NDS::ARM9BIOS), 1, f); + if (!Config::DLDIEnable) + return std::nullopt; - Log(LogLevel::Info, "ARM9 BIOS loaded from %s\n", Config::BIOS9Path.c_str()); - Platform::CloseFile(f); - } - else - { - Log(LogLevel::Warn, "ARM9 BIOS not found\n"); + return FATStorageArgs{ + Config::DLDISDPath, + imgsizes[Config::DLDISize], + Config::DLDIReadOnly, + Config::DLDIFolderSync ? std::make_optional(Config::DLDIFolderPath) : std::nullopt + }; +} - for (int i = 0; i < 16; i++) - ((u32*)nds.ARM9BIOS)[i] = 0xE7FFDEFF; - } +std::optional LoadDLDISDCard() noexcept +{ + if (!Config::DLDIEnable) + return std::nullopt; - if (FileHandle* f = Platform::OpenLocalFile(Config::BIOS7Path, FileMode::Read)) - { - FileRead(nds.ARM7BIOS, sizeof(NDS::ARM7BIOS), 1, f); - - Log(LogLevel::Info, "ARM7 BIOS loaded from\n", Config::BIOS7Path.c_str()); - Platform::CloseFile(f); - } - else - { - Log(LogLevel::Warn, "ARM7 BIOS not found\n"); - - for (int i = 0; i < 16; i++) - ((u32*)nds.ARM7BIOS)[i] = 0xE7FFDEFF; - } - } - else - { - Log(LogLevel::Info, "Using built-in ARM7 and ARM9 BIOSes\n"); - memcpy(nds.ARM9BIOS, bios_arm9_bin, sizeof(bios_arm9_bin)); - memcpy(nds.ARM7BIOS, bios_arm7_bin, sizeof(bios_arm7_bin)); - } - - if (Config::ConsoleType == 1) - { - DSi& dsi = static_cast(nds); - if (FileHandle* f = Platform::OpenLocalFile(Config::DSiBIOS9Path, FileMode::Read)) - { - FileRead(dsi.ARM9iBIOS, sizeof(DSi::ARM9iBIOS), 1, f); - - Log(LogLevel::Info, "ARM9i BIOS loaded from %s\n", Config::DSiBIOS9Path.c_str()); - Platform::CloseFile(f); - } - else - { - Log(LogLevel::Warn, "ARM9i BIOS not found\n"); - - for (int i = 0; i < 16; i++) - ((u32*)dsi.ARM9iBIOS)[i] = 0xE7FFDEFF; - } - - if (FileHandle* f = Platform::OpenLocalFile(Config::DSiBIOS7Path, FileMode::Read)) - { - // TODO: check if the first 32 bytes are crapoed - FileRead(dsi.ARM7iBIOS, sizeof(DSi::ARM7iBIOS), 1, f); - - Log(LogLevel::Info, "ARM7i BIOS loaded from %s\n", Config::DSiBIOS7Path.c_str()); - CloseFile(f); - } - else - { - Log(LogLevel::Warn, "ARM7i BIOS not found\n"); - - for (int i = 0; i < 16; i++) - ((u32*)dsi.ARM7iBIOS)[i] = 0xE7FFDEFF; - } - - if (!Config::DSiFullBIOSBoot) - { - // herp - *(u32*)&dsi.ARM9iBIOS[0] = 0xEAFFFFFE; - *(u32*)&dsi.ARM7iBIOS[0] = 0xEAFFFFFE; - - // TODO!!!! - // hax the upper 32K out of the goddamn DSi - // done that :) -pcy - } - } + return FATStorage(*GetDLDISDCardArgs()); } void EnableCheats(NDS& nds, bool enable) @@ -835,16 +857,10 @@ void SetDateTime(NDS& nds) void Reset(EmuThread* thread) { - thread->RecreateConsole(); + thread->UpdateConsole(Keep {}, Keep {}); if (Config::ConsoleType == 1) EjectGBACart(*thread->NDS); - LoadBIOSFiles(*thread->NDS); - InstallFirmware(*thread->NDS); - if (Config::ConsoleType == 1) - { - InstallNAND(static_cast(*thread->NDS)); - } thread->NDS->Reset(); SetBatteryLevels(*thread->NDS); SetDateTime(*thread->NDS); @@ -867,6 +883,7 @@ void Reset(EmuThread* thread) GBASave->SetPath(newsave, false); } + InitFirmwareSaveManager(thread); if (FirmwareSave) { std::string oldsave = FirmwareSave->GetPath(); @@ -896,39 +913,30 @@ void Reset(EmuThread* thread) thread->NDS->SetupDirectBoot(BaseROMName); } } + + thread->NDS->Start(); } -bool LoadBIOS(EmuThread* thread) +bool BootToMenu(EmuThread* thread) { - thread->RecreateConsole(); - - LoadBIOSFiles(*thread->NDS); - - if (!InstallFirmware(*thread->NDS)) - return false; - - if (Config::ConsoleType == 1 && !InstallNAND(static_cast(*thread->NDS))) + // 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; - /*if (NDSSave) delete NDSSave; - NDSSave = nullptr; - - CartType = -1; - BaseROMDir = ""; - BaseROMName = ""; - BaseAssetName = "";*/ - + InitFirmwareSaveManager(thread); thread->NDS->Reset(); SetBatteryLevels(*thread->NDS); SetDateTime(*thread->NDS); return true; } -u32 DecompressROM(const u8* inContent, const u32 inSize, u8** outContent) +u32 DecompressROM(const u8* inContent, const u32 inSize, unique_ptr& outContent) { u64 realSize = ZSTD_getFrameContentSize(inContent, inSize); const u32 maxSize = 0x40000000; @@ -940,16 +948,16 @@ u32 DecompressROM(const u8* inContent, const u32 inSize, u8** outContent) if (realSize != ZSTD_CONTENTSIZE_UNKNOWN) { - u8* realContent = new u8[realSize]; - u64 decompressed = ZSTD_decompress(realContent, realSize, inContent, inSize); + auto newOutContent = make_unique(realSize); + u64 decompressed = ZSTD_decompress(newOutContent.get(), realSize, inContent, inSize); if (ZSTD_isError(decompressed)) { - delete[] realContent; + outContent = nullptr; return 0; } - *outContent = realContent; + outContent = std::move(newOutContent); return realSize; } else @@ -1004,9 +1012,8 @@ u32 DecompressROM(const u8* inContent, const u32 inSize, u8** outContent) } } while (inBuf.pos < inBuf.size); - ZSTD_freeDStream(dStream); - *outContent = new u8[outBuf.pos]; - memcpy(*outContent, outBuf.dst, outBuf.pos); + outContent = make_unique(outBuf.pos); + memcpy(outContent.get(), outBuf.dst, outBuf.pos); ZSTD_freeDStream(dStream); free(outBuf.dst); @@ -1023,42 +1030,6 @@ void ClearBackupState() } } -// We want both the firmware object and the path that was used to load it, -// since we'll need to give it to the save manager later -pair, string> LoadFirmwareFromFile() -{ - string loadedpath; - unique_ptr firmware = nullptr; - string firmwarepath = Config::ConsoleType == 0 ? Config::FirmwarePath : Config::DSiFirmwarePath; - - Log(LogLevel::Debug, "SPI firmware: loading from file %s\n", firmwarepath.c_str()); - - string firmwareinstancepath = firmwarepath + Platform::InstanceFileSuffix(); - - loadedpath = firmwareinstancepath; - FileHandle* f = Platform::OpenLocalFile(firmwareinstancepath, FileMode::Read); - if (!f) - { - loadedpath = firmwarepath; - f = Platform::OpenLocalFile(firmwarepath, FileMode::Read); - } - - if (f) - { - firmware = make_unique(f); - if (!firmware->Buffer()) - { - Log(LogLevel::Warn, "Couldn't read firmware file!\n"); - firmware = nullptr; - loadedpath = ""; - } - - CloseFile(f); - } - - return std::make_pair(std::move(firmware), loadedpath); -} - pair, string> GenerateDefaultFirmware() { // Construct the default firmware... @@ -1068,7 +1039,7 @@ pair, string> GenerateDefaultFirmware() // 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 = Platform::GetConfigString(ConfigEntry::WifiSettingsPath); + std::string wfcsettingspath = Config::WifiSettingsPath; settingspath = wfcsettingspath + Platform::InstanceFileSuffix(); FileHandle* f = Platform::OpenLocalFile(settingspath, FileMode::Read); if (!f) @@ -1116,54 +1087,89 @@ pair, string> GenerateDefaultFirmware() return std::make_pair(std::move(firmware), std::move(wfcsettingspath)); } -void CustomizeFirmware(Firmware& firmware) noexcept +bool ParseMacAddress(void* data) { - auto& currentData = firmware.GetEffectiveUserData(); + const std::string& mac_in = Config::FirmwareMAC; + u8* mac_out = (u8*)data; - // 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) + int o = 0; + u8 tmp = 0; + for (int i = 0; i < 18; i++) { - currentData.FavoriteColor = favoritecolor; + 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; } - u8 birthmonth = Config::FirmwareBirthdayMonth; - if (birthmonth != 0) - { // If the frontend specifies a birth month (rather than using the existing value)... - currentData.BirthdayMonth = birthmonth; - } + return false; +} - 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()) +void CustomizeFirmware(Firmware& firmware, bool overridesettings) noexcept +{ + if (overridesettings) { - 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)); + auto ¤tData = 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; @@ -1172,14 +1178,16 @@ void CustomizeFirmware(Firmware& firmware) noexcept memcpy(&mac, header.MacAddr.data(), sizeof(MacAddress)); - - MacAddress configuredMac; - rep = Platform::GetConfigArray(Platform::Firm_MAC, &configuredMac); - rep &= (configuredMac != MacAddress()); - - if (rep) + if (overridesettings) { - mac = configuredMac; + MacAddress configuredMac; + rep = ParseMacAddress(&configuredMac); + rep &= (configuredMac != MacAddress()); + + if (rep) + { + mac = configuredMac; + } } int inst = Platform::InstanceID(); @@ -1201,155 +1209,12 @@ void CustomizeFirmware(Firmware& firmware) noexcept firmware.UpdateChecksums(); } -static Platform::FileHandle* OpenNANDFile() noexcept -{ - std::string nandpath = Config::DSiNANDPath; - std::string instnand = nandpath + Platform::InstanceFileSuffix(); - - FileHandle* nandfile = Platform::OpenLocalFile(instnand, FileMode::ReadWriteExisting); - if ((!nandfile) && (Platform::InstanceID() > 0)) - { - FileHandle* orig = Platform::OpenLocalFile(nandpath, FileMode::Read); - if (!orig) - { - Log(LogLevel::Error, "Failed to open DSi NAND from %s\n", nandpath.c_str()); - return nullptr; - } - - QFile::copy(QString::fromStdString(nandpath), QString::fromStdString(instnand)); - - nandfile = Platform::OpenLocalFile(instnand, FileMode::ReadWriteExisting); - } - - return nandfile; -} - -bool InstallNAND(DSi& dsi) -{ - Platform::FileHandle* nandfile = OpenNANDFile(); - if (!nandfile) - return false; - - DSi_NAND::NANDImage nandImage(nandfile, &dsi.ARM7iBIOS[0x8308]); - if (!nandImage) - { - Log(LogLevel::Error, "Failed to parse DSi NAND\n"); - return false; - } - - // 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(LogLevel::Error, "Failed to mount DSi NAND\n"); - return false; - } - - DSi_NAND::DSiFirmwareSystemSettings settings {}; - if (!mount.ReadUserData(settings)) - { - Log(LogLevel::Error, "Failed to read DSi NAND user data\n"); - return false; - } - - // 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 false; - } - } - - dsi.NANDImage = std::make_unique(std::move(nandImage)); - return true; -} - -bool InstallFirmware(NDS& nds) -{ - FirmwareSave.reset(); - unique_ptr firmware; - string firmwarepath; - bool generated = false; - - if (Config::ExternalBIOSEnable) - { // If we want to try loading a firmware dump... - - tie(firmware, firmwarepath) = LoadFirmwareFromFile(); - if (!firmware) - { // Try to load the configured firmware dump. If that fails... - Log(LogLevel::Warn, "Firmware not found! Generating default firmware.\n"); - } - } - - if (!firmware) - { // If we haven't yet loaded firmware (either because the load failed or we want to use the default...) - tie(firmware, firmwarepath) = GenerateDefaultFirmware(); - } - - if (!firmware) - return false; - - if (Config::FirmwareOverrideSettings) - { - CustomizeFirmware(*firmware); - } - - FirmwareSave = std::make_unique(firmwarepath); - - return nds.SPI.GetFirmwareMem()->InstallFirmware(std::move(firmware)); -} - -bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) +// 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; - u8* filedata = nullptr; - u32 filelen; - - std::string basepath; - std::string romname; - - int num = filepath.count(); - if (num == 1) + if (int num = filepath.count(); num == 1) { // regular file @@ -1361,38 +1226,35 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) if (len > 0x40000000) { Platform::CloseFile(f); - delete[] filedata; return false; } Platform::FileRewind(f); - filedata = new u8[len]; - size_t nread = Platform::FileRead(filedata, (size_t)len, 1, f); + filedata = make_unique(len); + size_t nread = Platform::FileRead(filedata.get(), (size_t)len, 1, f); + Platform::CloseFile(f); if (nread != 1) { - Platform::CloseFile(f); - delete[] filedata; + filedata = nullptr; return false; } - Platform::CloseFile(f); filelen = (u32)len; if (filename.length() > 4 && filename.substr(filename.length() - 4) == ".zst") { - u8* outContent = nullptr; - u32 decompressed = DecompressROM(filedata, len, &outContent); + filelen = DecompressROM(filedata.get(), len, filedata); - if (decompressed > 0) + if (filelen > 0) { - delete[] filedata; - filedata = outContent; - filelen = decompressed; filename = filename.substr(0, filename.length() - 4); } else { - delete[] filedata; + filedata = nullptr; + filelen = 0; + basepath = ""; + romname = ""; return false; } } @@ -1400,19 +1262,21 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) 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); + s32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), filedata, &filelen); if (lenread < 0) return false; if (!filedata) return false; if (lenread != filelen) { - delete[] filedata; + filedata = nullptr; return false; } @@ -1421,80 +1285,128 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) std::string std_romname = filepath.at(1).toStdString(); romname = std_romname.substr(LastSep(std_romname)+1); + return true; } #endif else return false; +} + +QString 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 LoadROM(QMainWindow* mainWindow, 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)) + { + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM."); + return false; + } - if (NDSSave) delete NDSSave; NDSSave = nullptr; BaseROMDir = basepath; BaseROMName = romname; BaseAssetName = romname.substr(0, romname.rfind('.')); - emuthread->RecreateConsole(); - if (!InstallFirmware(*emuthread->NDS)) - { - return false; - } - - if (reset) - { - emuthread->NDS->EjectCart(); - LoadBIOSFiles(*emuthread->NDS); - if (Config::ConsoleType == 1) - InstallNAND(static_cast(*emuthread->NDS)); - - emuthread->NDS->Reset(); - SetBatteryLevels(*emuthread->NDS); - SetDateTime(*emuthread->NDS); - } - u32 savelen = 0; - u8* savedata = nullptr; + 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) + { + 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 = new u8[savelen]; - FileRead(savedata, savelen, 1, sav); + savedata = std::make_unique(savelen); + FileRead(savedata.get(), savelen, 1, sav); CloseFile(sav); } - bool res = emuthread->NDS->LoadCart(filedata, filelen, savedata, savelen); - if (res && reset) + 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 (Config::DirectBoot || emuthread->NDS->NeedsDirectBoot()) + // If we couldn't parse the ROM... + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM."); + return false; + } + + if (reset) + { + if (!emuthread->UpdateConsole(std::move(cart), Keep {})) { + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM."); + return false; + } + + InitFirmwareSaveManager(emuthread); + emuthread->NDS->Reset(); + + if (Config::DirectBoot || emuthread->NDS->NeedsDirectBoot()) + { // If direct boot is enabled or forced... emuthread->NDS->SetupDirectBoot(romname); } - } - if (res) + SetBatteryLevels(*emuthread->NDS); + SetDateTime(*emuthread->NDS); + } + else { - CartType = 0; - NDSSave = new SaveManager(savname); - - LoadCheats(*emuthread->NDS); + assert(emuthread->NDS != nullptr); + emuthread->NDS->SetNDSCart(std::move(cart)); } - if (savedata) delete[] savedata; - delete[] filedata; - return res; + CartType = 0; + NDSSave = std::make_unique(savname); + LoadCheats(*emuthread->NDS); + + return true; // success } void EjectCart(NDS& nds) { - if (NDSSave) delete NDSSave; NDSSave = nullptr; UnloadCheats(nds); @@ -1527,94 +1439,25 @@ QString CartLabel() } -bool LoadGBAROM(NDS& nds, QStringList filepath) +bool LoadGBAROM(QMainWindow* mainWindow, NDS& nds, QStringList filepath) { - if (Config::ConsoleType == 1) return false; - if (filepath.empty()) return false; + if (nds.ConsoleType == 1) + { + QMessageBox::critical(mainWindow, "melonDS", "The DSi doesn't have a GBA slot."); + return false; + } - u8* filedata; + unique_ptr filedata = nullptr; u32 filelen; - std::string basepath; std::string romname; - int num = filepath.count(); - if (num == 1) + if (!LoadROMData(filepath, filedata, filelen, basepath, romname)) { - // regular file - - std::string filename = filepath.at(0).toStdString(); - FileHandle* f = Platform::OpenFile(filename, FileMode::Read); - if (!f) return false; - - long len = FileLength(f); - if (len > 0x40000000) - { - CloseFile(f); - return false; - } - - FileRewind(f); - filedata = new u8[len]; - size_t nread = FileRead(filedata, (size_t)len, 1, f); - if (nread != 1) - { - CloseFile(f); - delete[] filedata; - return false; - } - - CloseFile(f); - filelen = (u32)len; - - if (filename.length() > 4 && filename.substr(filename.length() - 4) == ".zst") - { - u8* outContent = nullptr; - u32 decompressed = DecompressROM(filedata, len, &outContent); - - if (decompressed > 0) - { - delete[] filedata; - filedata = outContent; - filelen = decompressed; - filename = filename.substr(0, filename.length() - 4); - } - else - { - delete[] filedata; - return false; - } - } - - int pos = LastSep(filename); - basepath = filename.substr(0, pos); - romname = filename.substr(pos+1); - } -#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) - { - delete[] filedata; - 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); - } -#endif - else + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the GBA ROM."); return false; + } - if (GBASave) delete GBASave; GBASave = nullptr; BaseGBAROMDir = basepath; @@ -1622,42 +1465,59 @@ bool LoadGBAROM(NDS& nds, QStringList filepath) BaseGBAAssetName = romname.substr(0, romname.rfind('.')); u32 savelen = 0; - u8* savedata = nullptr; + 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) + { + 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); - FileRewind(sav); - savedata = new u8[savelen]; - FileRead(savedata, savelen, 1, sav); + if (savelen > 0) + { + FileRewind(sav); + savedata = std::make_unique(savelen); + FileRead(savedata.get(), savelen, 1, sav); + } CloseFile(sav); } - bool res = nds.LoadGBACart(filedata, filelen, savedata, savelen); - - if (res) + auto cart = GBACart::ParseROM(std::move(filedata), filelen, std::move(savedata), savelen); + if (!cart) { - GBACartType = 0; - GBASave = new SaveManager(savname); + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the GBA ROM."); + return false; } - if (savedata) delete[] savedata; - delete[] filedata; - return res; + 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; - if (GBASave) delete GBASave; GBASave = nullptr; nds.LoadGBAAddon(type); @@ -1670,7 +1530,6 @@ void LoadGBAAddon(NDS& nds, int type) void EjectGBACart(NDS& nds) { - if (GBASave) delete GBASave; GBASave = nullptr; nds.EjectGBACart(); @@ -1713,23 +1572,28 @@ QString GBACartLabel() void ROMIcon(const u8 (&data)[512], const u16 (&palette)[16], u32 (&iconRef)[32*32]) { - int index = 0; - for (int i = 0; i < 4; i++) + u32 paletteRGBA[16]; + for (int i = 0; i < 16; i++) { - for (int j = 0; j < 4; j++) + 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 k = 0; k < 8; k++) + for (int ypixel = 0; ypixel < 8; ypixel++) { - for (int l = 0; l < 8; l++) + for (int xpixel = 0; xpixel < 8; xpixel++) { - 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++; + 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++; } } } diff --git a/src/frontend/qt_sdl/ROMManager.h b/src/frontend/qt_sdl/ROMManager.h index 2163a680..ae854617 100644 --- a/src/frontend/qt_sdl/ROMManager.h +++ b/src/frontend/qt_sdl/ROMManager.h @@ -23,8 +23,11 @@ #include "SaveManager.h" #include "AREngine.h" #include "DSi_NAND.h" +#include #include "MemConstants.h" + +#include #include #include #include @@ -35,40 +38,49 @@ namespace melonDS class NDS; class DSi; class FATStorage; +class FATStorageArgs; } class EmuThread; namespace ROMManager { using namespace melonDS; -extern SaveManager* NDSSave; -extern SaveManager* GBASave; +extern std::unique_ptr NDSSave; +extern std::unique_ptr GBASave; extern std::unique_ptr FirmwareSave; QString VerifySetup(); void Reset(EmuThread* thread); -bool LoadBIOS(EmuThread* thread); + +/// Boots the emulated console into its system menu without starting a game. +bool BootToMenu(EmuThread* thread); void ClearBackupState(); -std::optional> LoadARM9BIOS() noexcept; -std::optional> LoadARM7BIOS() noexcept; -std::optional> LoadDSiARM9BIOS() noexcept; -std::optional> LoadDSiARM7BIOS() noexcept; +/// Returns the configured ARM9 BIOS loaded from disk, +/// the FreeBIOS if external BIOS is disabled and we're in NDS mode, +/// or nullptr if loading failed. +std::unique_ptr LoadARM9BIOS() noexcept; +std::unique_ptr LoadARM7BIOS() noexcept; +std::unique_ptr LoadDSiARM9BIOS() noexcept; +std::unique_ptr LoadDSiARM7BIOS() noexcept; +std::optional GetDSiSDCardArgs() noexcept; std::optional LoadDSiSDCard() noexcept; -void CustomizeFirmware(Firmware& firmware) noexcept; +std::optional GetDLDISDCardArgs() noexcept; +std::optional LoadDLDISDCard() noexcept; +void CustomizeFirmware(Firmware& firmware, bool overridesettings) 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; -bool InstallFirmware(NDS& nds); -bool InstallNAND(DSi& dsi); -bool LoadROM(EmuThread*, QStringList filepath, bool reset); + +/// Inserts a ROM into the emulated console. +bool LoadROM(QMainWindow* mainWindow, EmuThread*, QStringList filepath, bool reset); void EjectCart(NDS& nds); bool CartInserted(); QString CartLabel(); -bool LoadGBAROM(NDS& nds, QStringList filepath); +bool LoadGBAROM(QMainWindow* mainWindow, NDS& nds, QStringList filepath); void LoadGBAAddon(NDS& nds, int type); void EjectGBACart(NDS& nds); bool GBACartInserted(); diff --git a/src/frontend/qt_sdl/Screen.cpp b/src/frontend/qt_sdl/Screen.cpp new file mode 100644 index 00000000..9174d3dd --- /dev/null +++ b/src/frontend/qt_sdl/Screen.cpp @@ -0,0 +1,1066 @@ +/* + 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 +#ifndef _WIN32 +#ifndef APPLE +#include +#endif +#endif +#include + +#include "OpenGLSupport.h" +#include "duckstation/gl/context.h" + +#include "main.h" + +#include "NDS.h" +#include "GPU.h" +#include "GPU3D_Soft.h" +#include "GPU3D_OpenGL.h" +#include "Platform.h" +#include "Config.h" + +#include "main_shaders.h" +#include "OSD_shaders.h" +#include "font.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; + + +ScreenPanel::ScreenPanel(QWidget* parent) : QWidget(parent) +{ + setMouseTracking(true); + setAttribute(Qt::WA_AcceptTouchEvents); + QTimer* mouseTimer = setupMouseTimer(); + connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) setCursor(Qt::BlankCursor);}); + + osdEnabled = false; + osdID = 1; +} + +ScreenPanel::~ScreenPanel() +{ + mouseTimer->stop(); + delete mouseTimer; +} + +void ScreenPanel::setupScreenLayout() +{ + int w = width(); + int h = height(); + + int sizing = Config::ScreenSizing; + if (sizing == 3) sizing = autoScreenSizing; + + float aspectTop, aspectBot; + + for (auto ratio : aspectRatios) + { + if (ratio.id == Config::ScreenAspectTop) + aspectTop = ratio.ratio; + if (ratio.id == Config::ScreenAspectBot) + aspectBot = ratio.ratio; + } + + if (aspectTop == 0) + aspectTop = ((float) w / h) / (4.f / 3.f); + + 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); + + numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind); +} + +QSize ScreenPanel::screenGetMinSize(int factor = 1) +{ + bool isHori = (Config::ScreenRotation == Frontend::screenRot_90Deg + || Config::ScreenRotation == Frontend::screenRot_270Deg); + int gap = Config::ScreenGap * factor; + + int w = 256 * factor; + int h = 192 * factor; + + if (Config::ScreenSizing == Frontend::screenSizing_TopOnly + || Config::ScreenSizing == Frontend::screenSizing_BotOnly) + { + return QSize(w, h); + } + + if (Config::ScreenLayout == Frontend::screenLayout_Natural) + { + if (isHori) + return QSize(h+gap+h, w); + else + return QSize(w, h+gap+h); + } + else if (Config::ScreenLayout == Frontend::screenLayout_Vertical) + { + if (isHori) + return QSize(h, w+gap+w); + else + return QSize(w, h+gap+h); + } + else if (Config::ScreenLayout == Frontend::screenLayout_Horizontal) + { + if (isHori) + return QSize(h+gap+h, w); + else + return QSize(w+gap+w, h); + } + else // hybrid + { + if (isHori) + return QSize(h+gap+h, 3*w + (int)ceil((4*gap) / 3.0)); + else + return QSize(3*w + (int)ceil((4*gap) / 3.0), h+gap+h); + } +} + +void ScreenPanel::onScreenLayoutChanged() +{ + setMinimumSize(screenGetMinSize()); + setupScreenLayout(); +} + +void ScreenPanel::resizeEvent(QResizeEvent* event) +{ + setupScreenLayout(); + QWidget::resizeEvent(event); +} + +void ScreenPanel::mousePressEvent(QMouseEvent* event) +{ + event->accept(); + if (event->button() != Qt::LeftButton) return; + + int x = event->pos().x(); + int y = event->pos().y(); + + if (Frontend::GetTouchCoords(x, y, false)) + { + touching = true; + assert(emuThread->NDS != nullptr); + emuThread->NDS->TouchScreen(x, y); + } +} + +void ScreenPanel::mouseReleaseEvent(QMouseEvent* event) +{ + event->accept(); + if (event->button() != Qt::LeftButton) return; + + if (touching) + { + touching = false; + assert(emuThread->NDS != nullptr); + emuThread->NDS->ReleaseScreen(); + } +} + +void ScreenPanel::mouseMoveEvent(QMouseEvent* event) +{ + event->accept(); + + showCursor(); + + //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)) + { + assert(emuThread->NDS != nullptr); + emuThread->NDS->TouchScreen(x, y); + } +} + +void ScreenPanel::tabletEvent(QTabletEvent* event) +{ + event->accept(); + + switch(event->type()) + { + case QEvent::TabletPress: + case QEvent::TabletMove: + { + int x = event->x(); + int y = event->y(); + + if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TabletMove)) + { + touching = true; + assert(emuThread->NDS != nullptr); + emuThread->NDS->TouchScreen(x, y); + } + } + break; + case QEvent::TabletRelease: + if (touching) + { + assert(emuThread->NDS != nullptr); + emuThread->NDS->ReleaseScreen(); + touching = false; + } + break; + default: + break; + } +} + +void ScreenPanel::touchEvent(QTouchEvent* event) +{ + event->accept(); + + switch(event->type()) + { + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + if (event->touchPoints().length() > 0) + { + QPointF lastPosition = event->touchPoints().first().lastPos(); + int x = (int)lastPosition.x(); + int y = (int)lastPosition.y(); + + if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TouchUpdate)) + { + touching = true; + assert(emuThread->NDS != nullptr); + emuThread->NDS->TouchScreen(x, y); + } + } + break; + case QEvent::TouchEnd: + if (touching) + { + assert(emuThread->NDS != nullptr); + emuThread->NDS->ReleaseScreen(); + touching = false; + } + break; + default: + break; + } +} + +bool ScreenPanel::event(QEvent* event) +{ + if (event->type() == QEvent::TouchBegin + || event->type() == QEvent::TouchEnd + || event->type() == QEvent::TouchUpdate) + { + touchEvent((QTouchEvent*)event); + return true; + } + + return QWidget::event(event); +} + +void ScreenPanel::showCursor() +{ + mainWindow->panel->setCursor(Qt::ArrowCursor); + mouseTimer->start(); +} + +QTimer* ScreenPanel::setupMouseTimer() +{ + mouseTimer = new QTimer(); + mouseTimer->setSingleShot(true); + mouseTimer->setInterval(Config::MouseHideSeconds*1000); + mouseTimer->start(); + + return mouseTimer; +} + +int ScreenPanel::osdFindBreakPoint(const char* text, int i) +{ + // i = character that went out of bounds + + for (int j = i; j >= 0; j--) + { + if (text[j] == ' ') + return j; + } + + return i; +} + +void ScreenPanel::osdLayoutText(const char* text, int* width, int* height, int* breaks) +{ + int w = 0; + int h = 14; + int totalw = 0; + int maxw = ((QWidget*)this)->width() - (kOSDMargin*2); + int lastbreak = -1; + int numbrk = 0; + u16* ptr; + + memset(breaks, 0, sizeof(int)*64); + + for (int i = 0; text[i] != '\0'; ) + { + int glyphsize; + if (text[i] == ' ') + { + glyphsize = 6; + } + else + { + u32 ch = text[i]; + if (ch < 0x10 || ch > 0x7E) ch = 0x7F; + + ptr = &::font[(ch-0x10) << 4]; + glyphsize = ptr[0]; + if (!glyphsize) glyphsize = 6; + else glyphsize += 2; // space around the character + } + + w += glyphsize; + if (w > maxw) + { + // wrap shit as needed + if (text[i] == ' ') + { + if (numbrk >= 64) break; + breaks[numbrk++] = i; + i++; + } + else + { + int brk = osdFindBreakPoint(text, i); + if (brk != lastbreak) i = brk; + + if (numbrk >= 64) break; + breaks[numbrk++] = i; + + lastbreak = brk; + } + + w = 0; + h += 14; + } + else + i++; + + if (w > totalw) totalw = w; + } + + *width = totalw; + *height = h; +} + +unsigned int ScreenPanel::osdRainbowColor(int inc) +{ + // inspired from Acmlmboard + + if (inc < 100) return 0xFFFF9B9B + (inc << 8); + else if (inc < 200) return 0xFFFFFF9B - ((inc-100) << 16); + else if (inc < 300) return 0xFF9BFF9B + (inc-200); + else if (inc < 400) return 0xFF9BFFFF - ((inc-300) << 8); + else if (inc < 500) return 0xFF9B9BFF + ((inc-400) << 16); + else return 0xFFFF9BFF - (inc-500); +} + +void ScreenPanel::osdRenderItem(OSDItem* item) +{ + int w, h; + int breaks[64]; + + char* text = item->text; + u32 color = item->color; + + bool rainbow = (color == 0); + u32 ticks = (u32)QDateTime::currentMSecsSinceEpoch(); + u32 rainbowinc = ((text[0] * 17) + (ticks * 13)) % 600; + + color |= 0xFF000000; + const u32 shadow = 0xE0000000; + + osdLayoutText(text, &w, &h, breaks); + + item->bitmap = QImage(w, h, QImage::Format_ARGB32_Premultiplied); + u32* bitmap = (u32*)item->bitmap.bits(); + memset(bitmap, 0, w*h*sizeof(u32)); + + int x = 0, y = 1; + u32 maxw = ((QWidget*)this)->width() - (kOSDMargin*2); + int curline = 0; + u16* ptr; + + for (int i = 0; text[i] != '\0'; ) + { + int glyphsize; + if (text[i] == ' ') + { + x += 6; + } + else + { + u32 ch = text[i]; + if (ch < 0x10 || ch > 0x7E) ch = 0x7F; + + ptr = &::font[(ch-0x10) << 4]; + int glyphsize = ptr[0]; + if (!glyphsize) x += 6; + else + { + x++; + + if (rainbow) + { + color = osdRainbowColor(rainbowinc); + rainbowinc = (rainbowinc + 30) % 600; + } + + // draw character + for (int cy = 0; cy < 12; cy++) + { + u16 val = ptr[4+cy]; + + for (int cx = 0; cx < glyphsize; cx++) + { + if (val & (1<= breaks[curline]) + { + i = breaks[curline++]; + if (text[i] == ' ') i++; + + x = 0; + y += 14; + } + } + + // shadow + for (y = 0; y < h; y++) + { + for (x = 0; x < w; x++) + { + u32 val; + + val = bitmap[(y * w) + x]; + if ((val >> 24) == 0xFF) continue; + + if (x > 0) val = bitmap[(y * w) + x-1]; + if (x < w-1) val |= bitmap[(y * w) + x+1]; + if (y > 0) + { + if (x > 0) val |= bitmap[((y-1) * w) + x-1]; + val |= bitmap[((y-1) * w) + x]; + if (x < w-1) val |= bitmap[((y-1) * w) + x+1]; + } + if (y < h-1) + { + if (x > 0) val |= bitmap[((y+1) * w) + x-1]; + val |= bitmap[((y+1) * w) + x]; + if (x < w-1) val |= bitmap[((y+1) * w) + x+1]; + } + + if ((val >> 24) == 0xFF) + bitmap[(y * w) + x] = shadow; + } + } +} + +void ScreenPanel::osdDeleteItem(OSDItem* item) +{ +} + +void ScreenPanel::osdSetEnabled(bool enabled) +{ + osdMutex.lock(); + osdEnabled = enabled; + osdMutex.unlock(); +} + +void ScreenPanel::osdAddMessage(unsigned int color, const char* text) +{ + if (!osdEnabled) return; + + osdMutex.lock(); + + OSDItem item; + + item.id = osdID++; + item.timestamp = QDateTime::currentMSecsSinceEpoch(); + strncpy(item.text, text, 255); item.text[255] = '\0'; + item.color = color; + item.rendered = false; + + osdItems.push_back(item); + + osdMutex.unlock(); +} + +void ScreenPanel::osdUpdate() +{ + osdMutex.lock(); + + qint64 tick_now = QDateTime::currentMSecsSinceEpoch(); + qint64 tick_min = tick_now - 2500; + + for (auto it = osdItems.begin(); it != osdItems.end(); ) + { + OSDItem& item = *it; + + if ((!osdEnabled) || (item.timestamp < tick_min)) + { + osdDeleteItem(&item); + it = osdItems.erase(it); + continue; + } + + if (!item.rendered) + { + osdRenderItem(&item); + item.rendered = true; + } + + it++; + } + + osdMutex.unlock(); +} + + + +ScreenPanelNative::ScreenPanelNative(QWidget* parent) : ScreenPanel(parent) +{ + screen[0] = QImage(256, 192, QImage::Format_RGB32); + screen[1] = QImage(256, 192, QImage::Format_RGB32); + + screenTrans[0].reset(); + screenTrans[1].reset(); +} + +ScreenPanelNative::~ScreenPanelNative() +{ +} + +void ScreenPanelNative::setupScreenLayout() +{ + ScreenPanel::setupScreenLayout(); + + for (int i = 0; i < numScreens; i++) + { + float* mtx = screenMatrix[i]; + screenTrans[i].setMatrix(mtx[0], mtx[1], 0.f, + mtx[2], mtx[3], 0.f, + mtx[4], mtx[5], 1.f); + } +} + +void ScreenPanelNative::paintEvent(QPaintEvent* event) +{ + QPainter painter(this); + + // fill background + painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0)); + + 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]) + { + 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(); + + QRect screenrc(0, 0, 256, 192); + + for (int i = 0; i < numScreens; i++) + { + painter.setTransform(screenTrans[i]); + painter.drawImage(screenrc, screen[screenKind[i]]); + } + } + + osdUpdate(); + if (osdEnabled) + { + osdMutex.lock(); + + u32 y = kOSDMargin; + + painter.resetTransform(); + + for (auto it = osdItems.begin(); it != osdItems.end(); ) + { + OSDItem& item = *it; + + painter.drawImage(kOSDMargin, y, item.bitmap); + + y += item.bitmap.height(); + it++; + } + + osdMutex.unlock(); + } +} + + + +ScreenPanelGL::ScreenPanelGL(QWidget* parent) : ScreenPanel(parent) +{ + setAutoFillBackground(false); + setAttribute(Qt::WA_NativeWindow, true); + setAttribute(Qt::WA_NoSystemBackground, true); + setAttribute(Qt::WA_PaintOnScreen, true); + setAttribute(Qt::WA_KeyCompression, false); + setFocusPolicy(Qt::StrongFocus); + setMinimumSize(screenGetMinSize()); +} + +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()) + { + glContext = GL::Context::Create(*getWindowInfo(), versionsToTry); + glContext->DoneCurrent(); + } + + return glContext != nullptr; +} + +void ScreenPanelGL::setSwapInterval(int intv) +{ + if (!glContext) return; + + glContext->SetSwapInterval(intv); +} + +void ScreenPanelGL::initOpenGL() +{ + if (!glContext) return; + + glContext->MakeCurrent(); + + OpenGL::CompileVertexFragmentProgram(screenShaderProgram, + kScreenVS, kScreenFS, + "ScreenShader", + {{"vPosition", 0}, {"vTexcoord", 1}}, + {{"oColor", 0}}); + + glUseProgram(screenShaderProgram); + glUniform1i(glGetUniformLocation(screenShaderProgram, "ScreenTex"), 0); + + screenShaderScreenSizeULoc = glGetUniformLocation(screenShaderProgram, "uScreenSize"); + screenShaderTransformULoc = glGetUniformLocation(screenShaderProgram, "uTransform"); + + // to prevent bleeding between both parts of the screen + // with bilinear filtering enabled + const int paddedHeight = 192*2+2; + const float padPixels = 1.f / paddedHeight; + + const float vertices[] = + { + 0.f, 0.f, 0.f, 0.f, + 0.f, 192.f, 0.f, 0.5f - padPixels, + 256.f, 192.f, 1.f, 0.5f - padPixels, + 0.f, 0.f, 0.f, 0.f, + 256.f, 192.f, 1.f, 0.5f - padPixels, + 256.f, 0.f, 1.f, 0.f, + + 0.f, 0.f, 0.f, 0.5f + padPixels, + 0.f, 192.f, 0.f, 1.f, + 256.f, 192.f, 1.f, 1.f, + 0.f, 0.f, 0.f, 0.5f + padPixels, + 256.f, 192.f, 1.f, 1.f, + 256.f, 0.f, 1.f, 0.5f + padPixels + }; + + glGenBuffers(1, &screenVertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + glGenVertexArrays(1, &screenVertexArray); + glBindVertexArray(screenVertexArray); + glEnableVertexAttribArray(0); // position + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(0)); + glEnableVertexAttribArray(1); // texcoord + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(2*4)); + + glGenTextures(1, &screenTexture); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, screenTexture); + 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, 256, paddedHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + // fill the padding + u8 zeroData[256*4*4]; + memset(zeroData, 0, sizeof(zeroData)); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256, 2, GL_RGBA, GL_UNSIGNED_BYTE, zeroData); + + OpenGL::CompileVertexFragmentProgram(osdShader, + kScreenVS_OSD, kScreenFS_OSD, + "OSDShader", + {{"vPosition", 0}}, + {{"oColor", 0}}); + + glUseProgram(osdShader); + glUniform1i(glGetUniformLocation(osdShader, "OSDTex"), 0); + + osdScreenSizeULoc = glGetUniformLocation(osdShader, "uScreenSize"); + osdPosULoc = glGetUniformLocation(osdShader, "uOSDPos"); + osdSizeULoc = glGetUniformLocation(osdShader, "uOSDSize"); + osdScaleFactorULoc = glGetUniformLocation(osdShader, "uScaleFactor"); + + const float osdvertices[6*2] = + { + 0, 0, + 1, 1, + 1, 0, + 0, 0, + 0, 1, + 1, 1 + }; + + glGenBuffers(1, &osdVertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, osdVertexBuffer); + glBufferData(GL_ARRAY_BUFFER, sizeof(osdvertices), osdvertices, GL_STATIC_DRAW); + + glGenVertexArrays(1, &osdVertexArray); + glBindVertexArray(osdVertexArray); + glEnableVertexAttribArray(0); // position + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)(0)); + + + glContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); + transferLayout(); +} + +void ScreenPanelGL::deinitOpenGL() +{ + if (!glContext) return; + + glDeleteTextures(1, &screenTexture); + + glDeleteVertexArrays(1, &screenVertexArray); + glDeleteBuffers(1, &screenVertexBuffer); + + glDeleteProgram(screenShaderProgram); + + for (const auto& [key, tex] : osdTextures) + { + glDeleteTextures(1, &tex); + } + osdTextures.clear(); + + glDeleteVertexArrays(1, &osdVertexArray); + glDeleteBuffers(1, &osdVertexBuffer); + + glDeleteProgram(osdShader); + + glContext->DoneCurrent(); + + lastScreenWidth = lastScreenHeight = -1; +} + +void ScreenPanelGL::osdRenderItem(OSDItem* item) +{ + ScreenPanel::osdRenderItem(item); + + 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, item->bitmap.width(), item->bitmap.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, item->bitmap.bits()); + + osdTextures[item->id] = tex; +} + +void ScreenPanelGL::osdDeleteItem(OSDItem* item) +{ + if (osdTextures.count(item->id)) + { + GLuint tex = osdTextures[item->id]; + glDeleteTextures(1, &tex); + osdTextures.erase(item->id); + } + + ScreenPanel::osdDeleteItem(item); +} + +void ScreenPanelGL::drawScreenGL() +{ + if (!glContext) return; + if (!emuThread->NDS) return; + + int w = windowInfo.surface_width; + int h = windowInfo.surface_height; + float factor = windowInfo.surface_scale; + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDisable(GL_DEPTH_TEST); + glDepthMask(false); + glDisable(GL_BLEND); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_STENCIL_TEST); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, w, h); + + 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 + emuThread->NDS->GPU.GetRenderer3D().BindOutputTexture(frontbuf); + } + else +#endif + { + // regular render + glBindTexture(GL_TEXTURE_2D, screenTexture); + + if (emuThread->NDS->GPU.Framebuffer[frontbuf][0] && emuThread->NDS->GPU.Framebuffer[frontbuf][1]) + { + 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()); + } + } + + 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 (osdEnabled) + { + osdMutex.lock(); + + u32 y = kOSDMargin; + + glUseProgram(osdShader); + + glUniform2f(osdScreenSizeULoc, w, h); + glUniform1f(osdScaleFactorULoc, factor); + + glBindBuffer(GL_ARRAY_BUFFER, osdVertexBuffer); + glBindVertexArray(osdVertexArray); + + glActiveTexture(GL_TEXTURE0); + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + for (auto it = osdItems.begin(); it != osdItems.end(); ) + { + OSDItem& item = *it; + + if (!osdTextures.count(item.id)) + continue; + + glBindTexture(GL_TEXTURE_2D, osdTextures[item.id]); + glUniform2i(osdPosULoc, kOSDMargin, y); + glUniform2i(osdSizeULoc, item.bitmap.width(), item.bitmap.height()); + glDrawArrays(GL_TRIANGLES, 0, 2*3); + + y += item.bitmap.height(); + it++; + } + + glDisable(GL_BLEND); + glUseProgram(0); + + osdMutex.unlock(); + } + + glContext->SwapBuffers(); +} + +qreal ScreenPanelGL::devicePixelRatioFromScreen() const +{ + const QScreen* screen_for_ratio = window()->windowHandle()->screen(); + if (!screen_for_ratio) + screen_for_ratio = QGuiApplication::primaryScreen(); + + return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast(1); +} + +int ScreenPanelGL::scaledWindowWidth() const +{ + return std::max(static_cast(std::ceil(static_cast(width()) * devicePixelRatioFromScreen())), 1); +} + +int ScreenPanelGL::scaledWindowHeight() const +{ + return std::max(static_cast(std::ceil(static_cast(height()) * devicePixelRatioFromScreen())), 1); +} + +std::optional ScreenPanelGL::getWindowInfo() +{ + WindowInfo wi; + + // Windows and Apple are easy here since there's no display connection. + #if defined(_WIN32) + wi.type = WindowInfo::Type::Win32; + wi.window_handle = reinterpret_cast(winId()); + #elif defined(__APPLE__) + wi.type = WindowInfo::Type::MacOS; + wi.window_handle = reinterpret_cast(winId()); + #else + QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); + const QString platform_name = QGuiApplication::platformName(); + if (platform_name == QStringLiteral("xcb")) + { + wi.type = WindowInfo::Type::X11; + wi.display_connection = pni->nativeResourceForWindow("display", windowHandle()); + wi.window_handle = reinterpret_cast(winId()); + } + else if (platform_name == QStringLiteral("wayland")) + { + wi.type = WindowInfo::Type::Wayland; + QWindow* handle = windowHandle(); + if (handle == nullptr) + return std::nullopt; + + wi.display_connection = pni->nativeResourceForWindow("display", handle); + wi.window_handle = pni->nativeResourceForWindow("surface", handle); + } + else + { + qCritical() << "Unknown PNI platform " << platform_name; + return std::nullopt; + } + #endif + + wi.surface_width = static_cast(scaledWindowWidth()); + wi.surface_height = static_cast(scaledWindowHeight()); + wi.surface_scale = static_cast(devicePixelRatioFromScreen()); + + return wi; +} + + +QPaintEngine* ScreenPanelGL::paintEngine() const +{ + return nullptr; +} + +void ScreenPanelGL::setupScreenLayout() +{ + ScreenPanel::setupScreenLayout(); + transferLayout(); +} + +void ScreenPanelGL::transferLayout() +{ + std::optional windowInfo = getWindowInfo(); + if (windowInfo.has_value()) + { + screenSettingsLock.lock(); + + if (lastScreenWidth != windowInfo->surface_width || lastScreenHeight != windowInfo->surface_height) + { + if (glContext) + glContext->ResizeSurface(windowInfo->surface_width, windowInfo->surface_height); + lastScreenWidth = windowInfo->surface_width; + 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 new file mode 100644 index 00000000..4ef4feca --- /dev/null +++ b/src/frontend/qt_sdl/Screen.h @@ -0,0 +1,196 @@ +/* + 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 SCREEN_H +#define SCREEN_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "glad/glad.h" +#include "FrontendUtil.h" +#include "duckstation/gl/context.h" + + +class EmuThread; + + +const struct { int id; float ratio; const char* label; } aspectRatios[] = +{ + { 0, 1, "4:3 (native)" }, + { 4, (5.f / 3) / (4.f / 3), "5:3 (3DS)"}, + { 1, (16.f / 9) / (4.f / 3), "16:9" }, + { 2, (21.f / 9) / (4.f / 3), "21:9" }, + { 3, 0, "window" } +}; +constexpr int AspectRatiosNum = sizeof(aspectRatios) / sizeof(aspectRatios[0]); + + +class ScreenPanel : public QWidget +{ + Q_OBJECT + +public: + explicit ScreenPanel(QWidget* parent); + virtual ~ScreenPanel(); + + QTimer* setupMouseTimer(); + void updateMouseTimer(); + QTimer* mouseTimer; + QSize screenGetMinSize(int factor); + + void osdSetEnabled(bool enabled); + void osdAddMessage(unsigned int color, const char* msg); + +private slots: + void onScreenLayoutChanged(); + +protected: + struct OSDItem + { + unsigned int id; + qint64 timestamp; + + char text[256]; + unsigned int color; + + bool rendered; + QImage bitmap; + }; + + QMutex osdMutex; + bool osdEnabled; + unsigned int osdID; + std::deque osdItems; + + virtual void setupScreenLayout(); + + void resizeEvent(QResizeEvent* event) override; + + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + + void tabletEvent(QTabletEvent* event) override; + 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); + void osdLayoutText(const char* text, int* width, int* height, int* breaks); + unsigned int osdRainbowColor(int inc); + + virtual void osdRenderItem(OSDItem* item); + virtual void osdDeleteItem(OSDItem* item); + + void osdUpdate(); +}; + + +class ScreenPanelNative : public ScreenPanel +{ + Q_OBJECT + +public: + explicit ScreenPanelNative(QWidget* parent); + virtual ~ScreenPanelNative(); + +protected: + void paintEvent(QPaintEvent* event) override; + +private: + void setupScreenLayout() override; + + QImage screen[2]; + QTransform screenTrans[Frontend::MaxScreenTransforms]; +}; + + +class ScreenPanelGL : public ScreenPanel +{ + Q_OBJECT + +public: + explicit ScreenPanelGL(QWidget* parent); + virtual ~ScreenPanelGL(); + + std::optional getWindowInfo(); + + bool createContext(); + + void setSwapInterval(int intv); + + void initOpenGL(); + void deinitOpenGL(); + void drawScreenGL(); + + GL::Context* getContext() { return glContext.get(); } + + void transferLayout(); +protected: + + qreal devicePixelRatioFromScreen() const; + int scaledWindowWidth() const; + int scaledWindowHeight() const; + + QPaintEngine* paintEngine() const override; + +private: + void setupScreenLayout() override; + + std::unique_ptr glContext; + + GLuint screenVertexBuffer, screenVertexArray; + GLuint screenTexture; + GLuint screenShaderProgram; + GLuint screenShaderTransformULoc, screenShaderScreenSizeULoc; + + QMutex screenSettingsLock; + WindowInfo windowInfo; + bool filter; + + int lastScreenWidth = -1, lastScreenHeight = -1; + + GLuint osdShader; + GLint osdScreenSizeULoc, osdPosULoc, osdSizeULoc; + GLfloat osdScaleFactorULoc; + GLuint osdVertexArray; + GLuint osdVertexBuffer; + std::map osdTextures; + + void osdRenderItem(OSDItem* item) override; + void osdDeleteItem(OSDItem* item) override; +}; + +#endif // SCREEN_H + diff --git a/src/frontend/qt_sdl/TitleManagerDialog.cpp b/src/frontend/qt_sdl/TitleManagerDialog.cpp index eda1bbce..21ca844e 100644 --- a/src/frontend/qt_sdl/TitleManagerDialog.cpp +++ b/src/frontend/qt_sdl/TitleManagerDialog.cpp @@ -114,7 +114,7 @@ void TitleManagerDialog::createTitleItem(u32 category, u32 titleid) u32 icondata[32*32]; ROMManager::ROMIcon(banner.Icon, banner.Palette, icondata); - QImage iconimg((const uchar*)icondata, 32, 32, QImage::Format_ARGB32); + QImage iconimg((const uchar*)icondata, 32, 32, QImage::Format_RGBA8888); QIcon icon(QPixmap::fromImage(iconimg.copy())); // TODO: make it possible to select other languages? diff --git a/src/frontend/qt_sdl/VideoSettingsDialog.cpp b/src/frontend/qt_sdl/VideoSettingsDialog.cpp index d5ee44c9..714707b8 100644 --- a/src/frontend/qt_sdl/VideoSettingsDialog.cpp +++ b/src/frontend/qt_sdl/VideoSettingsDialog.cpp @@ -23,6 +23,7 @@ #include "types.h" #include "Platform.h" #include "Config.h" +#include "GPU.h" #include "VideoSettingsDialog.h" #include "ui_VideoSettingsDialog.h" @@ -30,11 +31,20 @@ inline bool UsesGL() { - return (Config::ScreenUseGL != 0) || (Config::_3DRenderer != 0); + return (Config::ScreenUseGL != 0) || (Config::_3DRenderer != renderer3D_Software); } VideoSettingsDialog* VideoSettingsDialog::currentDlg = nullptr; +void VideoSettingsDialog::setEnabled() +{ + bool softwareRenderer = Config::_3DRenderer == renderer3D_Software; + ui->cbGLDisplay->setEnabled(softwareRenderer); + ui->cbSoftwareThreaded->setEnabled(softwareRenderer); + ui->cbxGLResolution->setEnabled(!softwareRenderer); + ui->cbBetterPolygons->setEnabled(Config::_3DRenderer == renderer3D_OpenGL); + ui->cbxComputeHiResCoords->setEnabled(Config::_3DRenderer == renderer3D_OpenGLCompute); +} VideoSettingsDialog::VideoSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::VideoSettingsDialog) { @@ -48,10 +58,12 @@ VideoSettingsDialog::VideoSettingsDialog(QWidget* parent) : QDialog(parent), ui( oldSoftThreaded = Config::Threaded3D; oldGLScale = Config::GL_ScaleFactor; oldGLBetterPolygons = Config::GL_BetterPolygons; + oldHiresCoordinates = Config::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 @@ -63,6 +75,10 @@ VideoSettingsDialog::VideoSettingsDialog(QWidget* parent) : QDialog(parent), ui( ui->rb3DOpenGL->setEnabled(false); #endif +#ifdef __APPLE__ + ui->rb3DCompute->setEnabled(false); +#endif + ui->cbGLDisplay->setChecked(Config::ScreenUseGL != 0); ui->cbVSync->setChecked(Config::ScreenVSync != 0); @@ -75,25 +91,13 @@ VideoSettingsDialog::VideoSettingsDialog(QWidget* parent) : QDialog(parent), ui( ui->cbxGLResolution->setCurrentIndex(Config::GL_ScaleFactor-1); ui->cbBetterPolygons->setChecked(Config::GL_BetterPolygons != 0); + ui->cbxComputeHiResCoords->setChecked(Config::GL_HiresCoordinates != 0); if (!Config::ScreenVSync) 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() @@ -119,6 +123,7 @@ void VideoSettingsDialog::on_VideoSettingsDialog_rejected() Config::Threaded3D = oldSoftThreaded; Config::GL_ScaleFactor = oldGLScale; Config::GL_BetterPolygons = oldGLBetterPolygons; + Config::GL_HiresCoordinates = oldHiresCoordinates; emit updateVideoSettings(old_gl != UsesGL()); @@ -133,31 +138,18 @@ 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; - 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); @@ -205,3 +197,10 @@ void VideoSettingsDialog::on_cbBetterPolygons_stateChanged(int state) emit updateVideoSettings(false); } + +void VideoSettingsDialog::on_cbxComputeHiResCoords_stateChanged(int state) +{ + Config::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..97e0dbd0 100644 --- a/src/frontend/qt_sdl/VideoSettingsDialog.h +++ b/src/frontend/qt_sdl/VideoSettingsDialog.h @@ -65,10 +65,12 @@ 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; @@ -81,6 +83,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/Window.cpp b/src/frontend/qt_sdl/Window.cpp new file mode 100644 index 00000000..536e0219 --- /dev/null +++ b/src/frontend/qt_sdl/Window.cpp @@ -0,0 +1,2059 @@ +/* + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#include +#include +#include +#ifndef APPLE +#include +#endif +#endif + +#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 "Platform.h" +#include "Config.h" +#include "version.h" +#include "Savestate.h" +#include "LocalMP.h" + +//#include "main_shaders.h" + +#include "ROMManager.h" +#include "ArchiveUtil.h" +#include "CameraManager.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; + + +// AAAAAAA +static bool FileExtensionInList(const QString& filename, const QStringList& extensions, Qt::CaseSensitivity cs = Qt::CaseInsensitive) +{ + return std::any_of(extensions.cbegin(), extensions.cend(), [&](const auto& ext) { + return filename.endsWith(ext, cs); + }); +} + +static bool MimeTypeInList(const QMimeType& mimetype, const QStringList& superTypeNames) +{ + return std::any_of(superTypeNames.cbegin(), superTypeNames.cend(), [&](const auto& superTypeName) { + return mimetype.inherits(superTypeName); + }); +} + + +static bool NdsRomByExtension(const QString& filename) +{ + return FileExtensionInList(filename, NdsRomExtensions); +} + +static bool GbaRomByExtension(const QString& filename) +{ + return FileExtensionInList(filename, GbaRomExtensions); +} + +static bool SupportedArchiveByExtension(const QString& filename) +{ + return FileExtensionInList(filename, ArchiveExtensions); +} + + +static bool NdsRomByMimetype(const QMimeType& mimetype) +{ + return mimetype.inherits(NdsRomMimeType); +} + +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); +} + + +#ifndef _WIN32 +static int signalFd[2]; +QSocketNotifier *signalSn; + +static void signalHandler(int) +{ + char a = 1; + write(signalFd[0], &a, sizeof(a)); +} +#endif + +MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) +{ +#ifndef _WIN32 + 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); +#endif + + oldW = Config::WindowWidth; + oldH = Config::WindowHeight; + oldMax = Config::WindowMaximized; + + 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); +#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(); + + { + QMenu* submenu = menu->addMenu("Screen size"); + + 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); + + 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) + { + 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); + } + } + + 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); + + 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); + } + setMenuBar(menubar); + + resize(Config::WindowWidth, Config::WindowHeight); + + if (Config::FirmwareUsername == "Arisotura") + actMPNewInstance->setText("Fart"); + +#ifdef Q_OS_MAC + QPoint screenCenter = screen()->availableGeometry().center(); + QRect frameGeo = frameGeometry(); + frameGeo.moveCenter(screenCenter); + move(frameGeo.topLeft()); +#endif + + if (oldMax) + showMaximized(); + else + show(); + + createScreenPanel(); + + actEjectCart->setEnabled(false); + actEjectGBACart->setEnabled(false); + + if (Config::ConsoleType == 1) + { + actInsertGBACart->setEnabled(false); + for (int i = 0; i < 1; i++) + actInsertGBAAddon[i]->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) + { + actScreenGap[i]->setChecked(true); + break; + } + } + + actScreenLayout[Config::ScreenLayout]->setChecked(true); + actScreenSizing[Config::ScreenSizing]->setChecked(true); + actIntegerScaling->setChecked(Config::IntegerScaling); + + actScreenSwap->setChecked(Config::ScreenSwap); + + 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); + } + + actScreenFiltering->setChecked(Config::ScreenFilter); + actShowOSD->setChecked(Config::ShowOSD); + + actLimitFramerate->setChecked(Config::LimitFPS); + actAudioSync->setChecked(Config::AudioSync); + + if (inst > 0) + { + actEmuSettings->setEnabled(false); + actVideoSettings->setEnabled(false); + actMPSettings->setEnabled(false); + actWifiSettings->setEnabled(false); + actInterfaceSettings->setEnabled(false); + +#ifdef __APPLE__ + actPreferences->setEnabled(false); +#endif // __APPLE__ + } +} + +MainWindow::~MainWindow() +{ + delete[] actScreenAspectTop; + delete[] actScreenAspectBot; +} + +void MainWindow::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); + + panel->osdAddMessage(color, msg); +} + +void MainWindow::closeEvent(QCloseEvent* event) +{ + if (hasOGL) + { + // we intentionally don't unpause here + emuThread->emuPause(); + emuThread->deinitContext(); + } + + QMainWindow::closeEvent(event); +} + +void MainWindow::createScreenPanel() +{ + hasOGL = (Config::ScreenUseGL != 0) || (Config::_3DRenderer != 0); + + if (hasOGL) + { + ScreenPanelGL* panelGL = new ScreenPanelGL(this); + panelGL->show(); + + panel = panelGL; + + panelGL->createContext(); + } + + if (!hasOGL) + { + ScreenPanelNative* panelNative = new ScreenPanelNative(this); + panel = panelNative; + panel->show(); + } + setCentralWidget(panel); + + actScreenFiltering->setEnabled(hasOGL); + panel->osdSetEnabled(Config::ShowOSD); + + connect(this, SIGNAL(screenLayoutChange()), panel, SLOT(onScreenLayoutChanged())); + emit screenLayoutChange(); +} + +GL::Context* MainWindow::getOGLContext() +{ + if (!hasOGL) return nullptr; + + ScreenPanelGL* glpanel = static_cast(panel); + return glpanel->getContext(); +} + +/*void MainWindow::initOpenGL() +{ + if (!hasOGL) return; + + ScreenPanelGL* glpanel = static_cast(panel); + return glpanel->initOpenGL(); +} + +void MainWindow::deinitOpenGL() +{ + if (!hasOGL) return; + + ScreenPanelGL* glpanel = static_cast(panel); + return glpanel->deinitOpenGL(); +} + +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) +{ + if (event->isAutoRepeat()) return; + + // TODO!! REMOVE ME IN RELEASE BUILDS!! + //if (event->key() == Qt::Key_F11) emuThread->NDS->debug(0); + + Input::KeyPress(event); +} + +void MainWindow::keyReleaseEvent(QKeyEvent* event) +{ + if (event->isAutoRepeat()) return; + + Input::KeyRelease(event); +} + + +void MainWindow::dragEnterEvent(QDragEnterEvent* event) +{ + if (!event->mimeData()->hasUrls()) return; + + QList urls = event->mimeData()->urls(); + if (urls.count() > 1) return; // not handling more than one file at once + + QString filename = urls.at(0).toLocalFile(); + + if (FileIsSupportedFiletype(filename)) + event->acceptProposedAction(); +} + +void MainWindow::dropEvent(QDropEvent* event) +{ + if (!event->mimeData()->hasUrls()) return; + + 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; + const auto matchMode = romInsideArchive ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault; + const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, matchMode); + + bool isNdsRom = NdsRomByExtension(filename) || NdsRomByMimetype(mimetype); + bool isGbaRom = GbaRomByExtension(filename) || GbaRomByMimetype(mimetype); + isNdsRom |= ZstdNdsRomByExtension(filename); + isGbaRom |= ZstdGbaRomByExtension(filename); + + if (isNdsRom) + { + if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) + { + emuThread->emuUnpause(); + return; + } + + const QString barredFilename = file.join('|'); + recentFileList.removeAll(barredFilename); + recentFileList.prepend(barredFilename); + updateRecentFilesMenu(); + + assert(emuThread->NDS != nullptr); + emuThread->NDS->Start(); + emuThread->emuRun(); + + updateCartInserted(false); + } + else if (isGbaRom) + { + if (!ROMManager::LoadGBAROM(mainWindow, *emuThread->NDS, file)) + { + 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); +} + +void MainWindow::focusOutEvent(QFocusEvent* event) +{ + AudioInOut::AudioMute(mainWindow); +} + +void MainWindow::onAppStateChanged(Qt::ApplicationState state) +{ + if (state == Qt::ApplicationInactive) + { + Input::KeyReleaseAll(); + if (Config::PauseLostFocus && emuThread->emuIsRunning()) + emuThread->emuPause(); + } + else if (state == Qt::ApplicationActive) + { + if (Config::PauseLostFocus && !pausedManually) + emuThread->emuUnpause(); + } +} + +bool MainWindow::verifySetup() +{ + QString res = ROMManager::VerifySetup(); + if (!res.isEmpty()) + { + QMessageBox::critical(this, "melonDS", res); + return false; + } + + return true; +} + +bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) +{ + if (!verifySetup()) + { + return false; + } + + bool gbaloaded = false; + if (!gbafile.isEmpty()) + { + if (!ROMManager::LoadGBAROM(mainWindow, *emuThread->NDS, gbafile)) return false; + + gbaloaded = true; + } + + bool ndsloaded = false; + if (!file.isEmpty()) + { + if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) return false; + + recentFileList.removeAll(file.join("|")); + recentFileList.prepend(file.join("|")); + updateRecentFilesMenu(); + ndsloaded = true; + } + + if (boot) + { + if (ndsloaded) + { + emuThread->NDS->Start(); + emuThread->emuRun(); + } + else + { + onBootFirmware(); + } + } + + updateCartInserted(false); + + if (gbaloaded) + { + updateCartInserted(true); + } + + return true; +} + +QStringList MainWindow::splitArchivePath(const QString& filename, bool useMemberSyntax) +{ + if (filename.isEmpty()) return {}; + +#ifdef ARCHIVE_SUPPORT_ENABLED + if (useMemberSyntax) + { + const QStringList filenameParts = filename.split('|'); + if (filenameParts.size() > 2) + { + QMessageBox::warning(this, "melonDS", "This path contains too many '|'."); + return {}; + } + + if (filenameParts.size() == 2) + { + const QString archive = filenameParts.at(0); + if (!QFileInfo(archive).exists()) + { + QMessageBox::warning(this, "melonDS", "This archive does not exist."); + return {}; + } + + const QString subfile = filenameParts.at(1); + if (!Archive::ListArchive(archive).contains(subfile)) + { + QMessageBox::warning(this, "melonDS", "This archive does not contain the desired file."); + return {}; + } + + return filenameParts; + } + } +#endif + + if (!QFileInfo(filename).exists()) + { + QMessageBox::warning(this, "melonDS", "This ROM file does not exist."); + return {}; + } + +#ifdef ARCHIVE_SUPPORT_ENABLED + if (SupportedArchiveByExtension(filename) + || SupportedArchiveByMimetype(QMimeDatabase().mimeTypeForFile(filename))) + { + const QString subfile = pickFileFromArchive(filename); + if (subfile.isEmpty()) + return {}; + + return { filename, subfile }; + } +#endif + + return { filename }; +} + +QString MainWindow::pickFileFromArchive(QString archiveFileName) +{ + QVector archiveROMList = Archive::ListArchive(archiveFileName); + + if (archiveROMList.size() <= 1) + { + if (!archiveROMList.isEmpty() && archiveROMList.at(0) == "OK") + QMessageBox::warning(this, "melonDS", "This archive is empty."); + else + QMessageBox::critical(this, "melonDS", "This archive could not be read. It may be corrupt or you don't have the permissions."); + return QString(); + } + + archiveROMList.removeFirst(); + + const auto notSupportedRom = [&](const auto& filename){ + if (NdsRomByExtension(filename) || GbaRomByExtension(filename)) + return false; + const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension); + return !(NdsRomByMimetype(mimetype) || GbaRomByMimetype(mimetype)); + }; + + archiveROMList.erase(std::remove_if(archiveROMList.begin(), archiveROMList.end(), notSupportedRom), + archiveROMList.end()); + + if (archiveROMList.isEmpty()) + { + QMessageBox::warning(this, "melonDS", "This archive does not contain any supported ROMs."); + return QString(); + } + + if (archiveROMList.size() == 1) + return archiveROMList.first(); + + bool ok; + const QString toLoad = QInputDialog::getItem( + this, "melonDS", + "This archive contains multiple files. Select which ROM you want to load.", + archiveROMList.toList(), 0, false, &ok + ); + + if (ok) return toLoad; + + // User clicked on cancel + + return QString(); +} + +QStringList MainWindow::pickROM(bool gba) +{ + const QString console = gba ? "GBA" : "DS"; + const QStringList& romexts = gba ? GbaRomExtensions : NdsRomExtensions; + + QString rawROMs = romexts.join(" *"); + QString extraFilters = ";;" + console + " ROMs (*" + rawROMs; + QString allROMs = rawROMs; + + QString zstdROMs = "*" + romexts.join(".zst *") + ".zst"; + extraFilters += ");;Zstandard-compressed " + console + " ROMs (" + zstdROMs + ")"; + allROMs += " " + zstdROMs; + +#ifdef ARCHIVE_SUPPORT_ENABLED + QString archives = "*" + ArchiveExtensions.join(" *"); + extraFilters += ";;Archives (" + archives + ")"; + allROMs += " " + archives; +#endif + extraFilters += ";;All files (*.*)"; + + const QString filename = QFileDialog::getOpenFileName( + this, "Open " + console + " ROM", + QString::fromStdString(Config::LastROMFolder), + "All supported files (*" + allROMs + ")" + extraFilters + ); + + if (filename.isEmpty()) return {}; + + Config::LastROMFolder = QFileInfo(filename).dir().path().toStdString(); + return splitArchivePath(filename, false); +} + +void MainWindow::updateCartInserted(bool gba) +{ + bool inserted; + if (gba) + { + inserted = ROMManager::GBACartInserted() && (Config::ConsoleType == 0); + actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); + 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); + } +} + +void MainWindow::onOpenFile() +{ + emuThread->emuPause(); + + if (!verifySetup()) + { + emuThread->emuUnpause(); + return; + } + + QStringList file = pickROM(false); + if (file.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) + { + emuThread->emuUnpause(); + return; + } + + QString filename = file.join('|'); + recentFileList.removeAll(filename); + 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] = ""; + updateRecentFilesMenu(); +} + +void MainWindow::updateRecentFilesMenu() +{ + recentMenu->clear(); + + for (int i = 0; i < recentFileList.size(); ++i) + { + if (i >= 10) break; + + QString item_full = recentFileList.at(i); + QString item_display = item_full; + int itemlen = item_full.length(); + const int maxlen = 100; + if (itemlen > maxlen) + { + int cut_start = 0; + while (item_full[cut_start] != '/' && item_full[cut_start] != '\\' && + cut_start < itemlen) + cut_start++; + + int cut_end = itemlen-1; + while (((item_full[cut_end] != '/' && item_full[cut_end] != '\\') || + (cut_start+4+(itemlen-cut_end) < maxlen)) && + cut_end > 0) + cut_end--; + + item_display.truncate(cut_start+1); + item_display += "..."; + item_display += QString(item_full).remove(0, cut_end); + } + + 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) + recentFileList.removeLast(); + + recentMenu->addSeparator(); + + QAction *actClearRecentList = recentMenu->addAction("Clear"); + connect(actClearRecentList, &QAction::triggered, this, &MainWindow::onClearRecentFiles); + + if (recentFileList.empty()) + actClearRecentList->setEnabled(false); + + Config::Save(); +} + +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(mainWindow, emuThread, file, true)) + { + emuThread->emuUnpause(); + return; + } + + recentFileList.removeAll(filename); + 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)) + { + // 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()) + { + emuThread->emuUnpause(); + return; + } + + if (!ROMManager::LoadROM(mainWindow, emuThread, file, false)) + { + emuThread->emuUnpause(); + return; + } + + emuThread->emuUnpause(); + + updateCartInserted(false); +} + +void MainWindow::onEjectCart() +{ + emuThread->emuPause(); + + ROMManager::EjectCart(*emuThread->NDS); + + emuThread->emuUnpause(); + + updateCartInserted(false); +} + +void MainWindow::onInsertGBACart() +{ + emuThread->emuPause(); + + QStringList file = pickROM(true); + if (file.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + if (!ROMManager::LoadGBAROM(mainWindow, *emuThread->NDS, file)) + { + emuThread->emuUnpause(); + return; + } + + emuThread->emuUnpause(); + + updateCartInserted(true); +} + +void MainWindow::onInsertGBAAddon() +{ + QAction* act = (QAction*)sender(); + int type = act->data().toInt(); + + emuThread->emuPause(); + + ROMManager::LoadGBAAddon(*emuThread->NDS, type); + + emuThread->emuUnpause(); + + updateCartInserted(true); +} + +void MainWindow::onEjectGBACart() +{ + emuThread->emuPause(); + + ROMManager::EjectGBACart(*emuThread->NDS); + + emuThread->emuUnpause(); + + updateCartInserted(true); +} + +void MainWindow::onSaveState() +{ + int slot = ((QAction*)sender())->data().toInt(); + + emuThread->emuPause(); + + std::string filename; + if (slot > 0) + { + filename = ROMManager::GetSavestateName(slot); + } + else + { + // TODO: specific 'last directory' for savestate files? + QString qfilename = QFileDialog::getSaveFileName(this, + "Save state", + QString::fromStdString(Config::LastROMFolder), + "melonDS savestates (*.mln);;Any file (*.*)"); + if (qfilename.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + filename = qfilename.toStdString(); + } + + if (ROMManager::SaveState(*emuThread->NDS, filename)) + { + if (slot > 0) osdAddMessage(0, "State saved to slot %d", slot); + else osdAddMessage(0, "State saved to file"); + + actLoadState[slot]->setEnabled(true); + } + else + { + osdAddMessage(0xFFA0A0, "State save failed"); + } + + emuThread->emuUnpause(); +} + +void MainWindow::onLoadState() +{ + int slot = ((QAction*)sender())->data().toInt(); + + emuThread->emuPause(); + + std::string filename; + if (slot > 0) + { + filename = ROMManager::GetSavestateName(slot); + } + else + { + // TODO: specific 'last directory' for savestate files? + QString qfilename = QFileDialog::getOpenFileName(this, + "Load state", + QString::fromStdString(Config::LastROMFolder), + "melonDS savestates (*.ml*);;Any file (*.*)"); + if (qfilename.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + filename = qfilename.toStdString(); + } + + if (!Platform::FileExists(filename)) + { + if (slot > 0) osdAddMessage(0xFFA0A0, "State slot %d is empty", slot); + else osdAddMessage(0xFFA0A0, "State file does not exist"); + + emuThread->emuUnpause(); + return; + } + + if (ROMManager::LoadState(*emuThread->NDS, filename)) + { + if (slot > 0) osdAddMessage(0, "State loaded from slot %d", slot); + else osdAddMessage(0, "State loaded from file"); + + actUndoStateLoad->setEnabled(true); + } + else + { + osdAddMessage(0xFFA0A0, "State load failed"); + } + + emuThread->emuUnpause(); +} + +void MainWindow::onUndoStateLoad() +{ + emuThread->emuPause(); + ROMManager::UndoStateLoad(*emuThread->NDS); + emuThread->emuUnpause(); + + osdAddMessage(0, "State load undone"); +} + +void MainWindow::onImportSavefile() +{ + emuThread->emuPause(); + QString path = QFileDialog::getOpenFileName(this, + "Select savefile", + QString::fromStdString(Config::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) + { + QMessageBox::critical(this, "melonDS", "Could not open the given savefile."); + emuThread->emuUnpause(); + return; + } + + if (RunningSomething) + { + 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(); +} + +void MainWindow::onQuit() +{ +#ifndef _WIN32 + signalSn->setEnabled(false); +#endif + QApplication::quit(); +} + + +void MainWindow::onPause(bool checked) +{ + if (!RunningSomething) return; + + if (checked) + { + emuThread->emuPause(); + osdAddMessage(0, "Paused"); + pausedManually = true; + } + else + { + emuThread->emuUnpause(); + osdAddMessage(0, "Resumed"); + pausedManually = false; + } +} + +void MainWindow::onReset() +{ + if (!RunningSomething) return; + + emuThread->emuPause(); + + actUndoStateLoad->setEnabled(false); + + ROMManager::Reset(emuThread); + + osdAddMessage(0, "Reset"); + emuThread->emuRun(); +} + +void MainWindow::onStop() +{ + if (!RunningSomething) return; + + emuThread->emuPause(); + emuThread->NDS->Stop(); +} + +void MainWindow::onFrameStep() +{ + if (!RunningSomething) return; + + emuThread->emuFrameStep(); +} + +void MainWindow::onOpenDateTime() +{ + DateTimeDialog* dlg = DateTimeDialog::openDlg(this); +} + +void MainWindow::onOpenPowerManagement() +{ + PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this, emuThread); +} + +void MainWindow::onEnableCheats(bool checked) +{ + Config::EnableCheats = checked?1:0; + ROMManager::EnableCheats(*emuThread->NDS, Config::EnableCheats != 0); +} + +void MainWindow::onSetupCheats() +{ + emuThread->emuPause(); + + CheatsDialog* dlg = CheatsDialog::openDlg(this); + connect(dlg, &CheatsDialog::finished, this, &MainWindow::onCheatsDialogFinished); +} + +void MainWindow::onCheatsDialogFinished(int res) +{ + emuThread->emuUnpause(); +} + +void MainWindow::onROMInfo() +{ + auto cart = emuThread->NDS->NDSCartSlot.GetCart(); + if (cart) + ROMInfoDialog* dlg = ROMInfoDialog::openDlg(this, *cart); +} + +void MainWindow::onRAMInfo() +{ + RAMInfoDialog* dlg = RAMInfoDialog::openDlg(this, emuThread); +} + +void MainWindow::onOpenTitleManager() +{ + TitleManagerDialog* dlg = TitleManagerDialog::openDlg(this); +} + +void MainWindow::onMPNewInstance() +{ + //QProcess::startDetached(QApplication::applicationFilePath()); + QProcess newinst; + newinst.setProgram(QApplication::applicationFilePath()); + newinst.setArguments(QApplication::arguments().mid(1, QApplication::arguments().length()-1)); + +#ifdef __WIN32__ + newinst.setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args) + { + args->flags |= CREATE_NEW_CONSOLE; + }); +#endif + + newinst.startDetached(); +} + +void MainWindow::onOpenEmuSettings() +{ + emuThread->emuPause(); + + EmuSettingsDialog* dlg = EmuSettingsDialog::openDlg(this); + connect(dlg, &EmuSettingsDialog::finished, this, &MainWindow::onEmuSettingsDialogFinished); +} + +void MainWindow::onEmuSettingsDialogFinished(int res) +{ + emuThread->emuUnpause(); + + if (Config::ConsoleType == 1) + { + actInsertGBACart->setEnabled(false); + for (int i = 0; i < 1; i++) + actInsertGBAAddon[i]->setEnabled(false); + actEjectGBACart->setEnabled(false); + } + else + { + actInsertGBACart->setEnabled(true); + for (int i = 0; i < 1; i++) + actInsertGBAAddon[i]->setEnabled(true); + actEjectGBACart->setEnabled(ROMManager::GBACartInserted()); + } + + if (EmuSettingsDialog::needsReset) + onReset(); + + actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); + + if (!RunningSomething) + actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); +} + +void MainWindow::onOpenInputConfig() +{ + emuThread->emuPause(); + + InputConfigDialog* dlg = InputConfigDialog::openDlg(this); + connect(dlg, &InputConfigDialog::finished, this, &MainWindow::onInputConfigFinished); +} + +void MainWindow::onInputConfigFinished(int res) +{ + emuThread->emuUnpause(); +} + +void MainWindow::onOpenVideoSettings() +{ + VideoSettingsDialog* dlg = VideoSettingsDialog::openDlg(this); + connect(dlg, &VideoSettingsDialog::updateVideoSettings, this, &MainWindow::onUpdateVideoSettings); +} + +void MainWindow::onOpenCameraSettings() +{ + emuThread->emuPause(); + + camStarted[0] = camManager[0]->isStarted(); + camStarted[1] = camManager[1]->isStarted(); + if (camStarted[0]) camManager[0]->stop(); + if (camStarted[1]) camManager[1]->stop(); + + CameraSettingsDialog* dlg = CameraSettingsDialog::openDlg(this); + connect(dlg, &CameraSettingsDialog::finished, this, &MainWindow::onCameraSettingsFinished); +} + +void MainWindow::onCameraSettingsFinished(int res) +{ + if (camStarted[0]) camManager[0]->start(); + if (camStarted[1]) camManager[1]->start(); + + emuThread->emuUnpause(); +} + +void MainWindow::onOpenAudioSettings() +{ + AudioSettingsDialog* dlg = AudioSettingsDialog::openDlg(this, emuThread->emuIsActive(), emuThread); + connect(emuThread, &EmuThread::syncVolumeLevel, dlg, &AudioSettingsDialog::onSyncVolumeLevel); + connect(emuThread, &EmuThread::windowEmuStart, dlg, &AudioSettingsDialog::onConsoleReset); + connect(dlg, &AudioSettingsDialog::updateAudioSettings, this, &MainWindow::onUpdateAudioSettings); + connect(dlg, &AudioSettingsDialog::finished, this, &MainWindow::onAudioSettingsFinished); +} + +void MainWindow::onOpenFirmwareSettings() +{ + emuThread->emuPause(); + + FirmwareSettingsDialog* dlg = FirmwareSettingsDialog::openDlg(this); + connect(dlg, &FirmwareSettingsDialog::finished, this, &MainWindow::onFirmwareSettingsFinished); +} + +void MainWindow::onFirmwareSettingsFinished(int res) +{ + if (FirmwareSettingsDialog::needsReset) + onReset(); + + emuThread->emuUnpause(); +} + +void MainWindow::onOpenPathSettings() +{ + emuThread->emuPause(); + + PathSettingsDialog* dlg = PathSettingsDialog::openDlg(this); + connect(dlg, &PathSettingsDialog::finished, this, &MainWindow::onPathSettingsFinished); +} + +void MainWindow::onPathSettingsFinished(int res) +{ + if (PathSettingsDialog::needsReset) + onReset(); + + emuThread->emuUnpause(); +} + +void MainWindow::onUpdateAudioSettings() +{ + assert(emuThread->NDS != nullptr); + emuThread->NDS->SPU.SetInterpolation(static_cast(Config::AudioInterp)); + + if (Config::AudioBitDepth == 0) + emuThread->NDS->SPU.SetDegrade10Bit(emuThread->NDS->ConsoleType == 0); + else + emuThread->NDS->SPU.SetDegrade10Bit(Config::AudioBitDepth == 1); +} + +void MainWindow::onAudioSettingsFinished(int res) +{ + AudioInOut::UpdateSettings(*emuThread->NDS); +} + +void MainWindow::onOpenMPSettings() +{ + emuThread->emuPause(); + + MPSettingsDialog* dlg = MPSettingsDialog::openDlg(this); + connect(dlg, &MPSettingsDialog::finished, this, &MainWindow::onMPSettingsFinished); +} + +void MainWindow::onMPSettingsFinished(int res) +{ + AudioInOut::AudioMute(mainWindow); + LocalMP::SetRecvTimeout(Config::MPRecvTimeout); + + emuThread->emuUnpause(); +} + +void MainWindow::onOpenWifiSettings() +{ + emuThread->emuPause(); + + WifiSettingsDialog* dlg = WifiSettingsDialog::openDlg(this); + connect(dlg, &WifiSettingsDialog::finished, this, &MainWindow::onWifiSettingsFinished); +} + +void MainWindow::onWifiSettingsFinished(int res) +{ + Platform::LAN_DeInit(); + Platform::LAN_Init(); + + if (WifiSettingsDialog::needsReset) + onReset(); + + emuThread->emuUnpause(); +} + +void MainWindow::onOpenInterfaceSettings() +{ + emuThread->emuPause(); + InterfaceSettingsDialog* dlg = InterfaceSettingsDialog::openDlg(this); + connect(dlg, &InterfaceSettingsDialog::finished, this, &MainWindow::onInterfaceSettingsFinished); + connect(dlg, &InterfaceSettingsDialog::updateMouseTimer, this, &MainWindow::onUpdateMouseTimer); +} + +void MainWindow::onUpdateMouseTimer() +{ + panel->mouseTimer->setInterval(Config::MouseHideSeconds*1000); +} + +void MainWindow::onInterfaceSettingsFinished(int res) +{ + emuThread->emuUnpause(); +} + +void MainWindow::onChangeSavestateSRAMReloc(bool checked) +{ + Config::SavestateRelocSRAM = checked?1:0; +} + +void MainWindow::onChangeScreenSize() +{ + int factor = ((QAction*)sender())->data().toInt(); + QSize diff = size() - panel->size(); + resize(panel->screenGetMinSize(factor) + diff); +} + +void MainWindow::onChangeScreenRotation(QAction* act) +{ + int rot = act->data().toInt(); + Config::ScreenRotation = rot; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenGap(QAction* act) +{ + int gap = act->data().toInt(); + Config::ScreenGap = gap; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenLayout(QAction* act) +{ + int layout = act->data().toInt(); + Config::ScreenLayout = layout; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenSwap(bool checked) +{ + Config::ScreenSwap = checked?1:0; + + // Swap between top and bottom screen when displaying one screen. + if (Config::ScreenSizing == Frontend::screenSizing_TopOnly) + { + // Bottom Screen. + Config::ScreenSizing = Frontend::screenSizing_BotOnly; + actScreenSizing[Frontend::screenSizing_TopOnly]->setChecked(false); + actScreenSizing[Config::ScreenSizing]->setChecked(true); + } + else if (Config::ScreenSizing == Frontend::screenSizing_BotOnly) + { + // Top Screen. + Config::ScreenSizing = Frontend::screenSizing_TopOnly; + actScreenSizing[Frontend::screenSizing_BotOnly]->setChecked(false); + actScreenSizing[Config::ScreenSizing]->setChecked(true); + } + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenSizing(QAction* act) +{ + int sizing = act->data().toInt(); + Config::ScreenSizing = sizing; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenAspect(QAction* act) +{ + int aspect = act->data().toInt(); + QActionGroup* group = act->actionGroup(); + + if (group == grpScreenAspectTop) + { + Config::ScreenAspectTop = aspect; + } + else + { + Config::ScreenAspectBot = aspect; + } + + emit screenLayoutChange(); +} + +void MainWindow::onChangeIntegerScaling(bool checked) +{ + Config::IntegerScaling = checked?1:0; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenFiltering(bool checked) +{ + Config::ScreenFilter = checked?1:0; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeShowOSD(bool checked) +{ + Config::ShowOSD = checked?1:0; + panel->osdSetEnabled(Config::ShowOSD); +} + +void MainWindow::onChangeLimitFramerate(bool checked) +{ + Config::LimitFPS = checked?1:0; +} + +void MainWindow::onChangeAudioSync(bool checked) +{ + Config::AudioSync = checked?1:0; +} + + +void MainWindow::onTitleUpdate(QString title) +{ + setWindowTitle(title); +} + +void ToggleFullscreen(MainWindow* mainWindow) +{ + if (!mainWindow->isFullScreen()) + { + mainWindow->showFullScreen(); + mainWindow->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); + } +} + +void MainWindow::onFullscreenToggled() +{ + ToggleFullscreen(this); +} + +void MainWindow::onScreenEmphasisToggled() +{ + int currentSizing = Config::ScreenSizing; + if (currentSizing == Frontend::screenSizing_EmphTop) + { + Config::ScreenSizing = Frontend::screenSizing_EmphBot; + } + else if (currentSizing == Frontend::screenSizing_EmphBot) + { + Config::ScreenSizing = Frontend::screenSizing_EmphTop; + } + + emit screenLayoutChange(); +} + +void MainWindow::onEmuStart() +{ + for (int i = 1; i < 9; i++) + { + actSaveState[i]->setEnabled(true); + actLoadState[i]->setEnabled(ROMManager::SavestateExists(i)); + } + actSaveState[0]->setEnabled(true); + actLoadState[0]->setEnabled(true); + actUndoStateLoad->setEnabled(false); + + actPause->setEnabled(true); + actPause->setChecked(false); + actReset->setEnabled(true); + actStop->setEnabled(true); + actFrameStep->setEnabled(true); + + actDateTime->setEnabled(false); + actPowerManagement->setEnabled(true); + + actTitleManager->setEnabled(false); +} + +void MainWindow::onEmuStop() +{ + emuThread->emuPause(); + + for (int i = 0; i < 9; i++) + { + actSaveState[i]->setEnabled(false); + actLoadState[i]->setEnabled(false); + } + actUndoStateLoad->setEnabled(false); + + actPause->setEnabled(false); + actReset->setEnabled(false); + actStop->setEnabled(false); + actFrameStep->setEnabled(false); + + actDateTime->setEnabled(true); + actPowerManagement->setEnabled(false); + + actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); +} + +void MainWindow::onUpdateVideoSettings(bool glchange) +{ + if (glchange) + { + emuThread->emuPause(); + if (hasOGL) emuThread->deinitContext(); + + delete panel; + createScreenPanel(); + connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(repaint())); + } + + printf("update video settings\n"); + videoSettingsDirty = true; + + if (glchange) + { + if (hasOGL) emuThread->initContext(); + emuThread->emuUnpause(); + } +} diff --git a/src/frontend/qt_sdl/Window.h b/src/frontend/qt_sdl/Window.h new file mode 100644 index 00000000..bc207480 --- /dev/null +++ b/src/frontend/qt_sdl/Window.h @@ -0,0 +1,299 @@ +/* + 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 WINDOW_H +#define WINDOW_H + +#include "glad/glad.h" +#include "FrontendUtil.h" +#include "duckstation/gl/context.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Screen.h" + + +class EmuThread; + +/* +class WindowBase : public QMainWindow +{ + Q_OBJECT + +public: + explicit WindowBase(QWidget* parent = nullptr); + ~WindowBase(); + + bool hasOGL; + GL::Context* getOGLContext(); + + //void onAppStateChanged(Qt::ApplicationState state); + +protected: + void resizeEvent(QResizeEvent* event) override; + void changeEvent(QEvent* event) override; + + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; + + void dragEnterEvent(QDragEnterEvent* event) override; + void dropEvent(QDropEvent* event) override; + + void focusInEvent(QFocusEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; + +signals: + void screenLayoutChange(); + +private slots: + //void onQuit(); + + //void onTitleUpdate(QString title); + + //void onEmuStart(); + //void onEmuStop(); + + //void onUpdateVideoSettings(bool glchange); + + void onFullscreenToggled(); + void onScreenEmphasisToggled(); + +private: + virtual void closeEvent(QCloseEvent* event) override; + + void createScreenPanel(); + + //bool pausedManually = false; + + int oldW, oldH; + bool oldMax; + +public: + ScreenPanel* panel; +};*/ + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget* parent = nullptr); + ~MainWindow(); + + bool hasOGL; + GL::Context* getOGLContext(); + /*void initOpenGL(); + void deinitOpenGL(); + 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, ...); + +protected: + void resizeEvent(QResizeEvent* event) override; + void changeEvent(QEvent* event) override; + + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; + + void dragEnterEvent(QDragEnterEvent* event) override; + void dropEvent(QDropEvent* event) override; + + void focusInEvent(QFocusEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; + +signals: + void screenLayoutChange(); + +private slots: + void onOpenFile(); + void onClickRecentFile(); + void onClearRecentFiles(); + void onBootFirmware(); + void onInsertCart(); + void onEjectCart(); + void onInsertGBACart(); + void onInsertGBAAddon(); + void onEjectGBACart(); + void onSaveState(); + void onLoadState(); + void onUndoStateLoad(); + void onImportSavefile(); + void onQuit(); + + void onPause(bool checked); + void onReset(); + void onStop(); + void onFrameStep(); + void onOpenPowerManagement(); + void onOpenDateTime(); + void onEnableCheats(bool checked); + void onSetupCheats(); + void onCheatsDialogFinished(int res); + void onROMInfo(); + void onRAMInfo(); + void onOpenTitleManager(); + void onMPNewInstance(); + + void onOpenEmuSettings(); + void onEmuSettingsDialogFinished(int res); + void onOpenInputConfig(); + void onInputConfigFinished(int res); + void onOpenVideoSettings(); + void onOpenCameraSettings(); + void onCameraSettingsFinished(int res); + void onOpenAudioSettings(); + void onUpdateAudioSettings(); + void onAudioSettingsFinished(int res); + void onOpenMPSettings(); + void onMPSettingsFinished(int res); + void onOpenWifiSettings(); + void onWifiSettingsFinished(int res); + void onOpenFirmwareSettings(); + void onFirmwareSettingsFinished(int res); + void onOpenPathSettings(); + void onPathSettingsFinished(int res); + void onOpenInterfaceSettings(); + void onInterfaceSettingsFinished(int res); + void onUpdateMouseTimer(); + void onChangeSavestateSRAMReloc(bool checked); + void onChangeScreenSize(); + void onChangeScreenRotation(QAction* act); + void onChangeScreenGap(QAction* act); + void onChangeScreenLayout(QAction* act); + void onChangeScreenSwap(bool checked); + void onChangeScreenSizing(QAction* act); + void onChangeScreenAspect(QAction* act); + void onChangeIntegerScaling(bool checked); + void onChangeScreenFiltering(bool checked); + void onChangeShowOSD(bool checked); + void onChangeLimitFramerate(bool checked); + void onChangeAudioSync(bool checked); + + void onTitleUpdate(QString title); + + void onEmuStart(); + void onEmuStop(); + + void onUpdateVideoSettings(bool glchange); + + void onFullscreenToggled(); + void onScreenEmphasisToggled(); + +private: + virtual void closeEvent(QCloseEvent* event) override; + + QStringList currentROM; + QStringList currentGBAROM; + QList recentFileList; + QMenu *recentMenu; + void updateRecentFilesMenu(); + + bool verifySetup(); + QString pickFileFromArchive(QString archiveFileName); + QStringList pickROM(bool gba); + void updateCartInserted(bool gba); + + void createScreenPanel(); + + bool pausedManually = false; + + int oldW, oldH; + bool oldMax; + +public: + ScreenPanel* panel; + + QAction* actOpenROM; + QAction* actBootFirmware; + QAction* actCurrentCart; + QAction* actInsertCart; + QAction* actEjectCart; + QAction* actCurrentGBACart; + QAction* actInsertGBACart; + QAction* actInsertGBAAddon[1]; + QAction* actEjectGBACart; + QAction* actImportSavefile; + QAction* actSaveState[9]; + QAction* actLoadState[9]; + QAction* actUndoStateLoad; + QAction* actQuit; + + QAction* actPause; + QAction* actReset; + QAction* actStop; + QAction* actFrameStep; + QAction* actPowerManagement; + QAction* actDateTime; + QAction* actEnableCheats; + QAction* actSetupCheats; + QAction* actROMInfo; + QAction* actRAMInfo; + QAction* actTitleManager; + QAction* actMPNewInstance; + + QAction* actEmuSettings; +#ifdef __APPLE__ + QAction* actPreferences; +#endif + QAction* actInputConfig; + QAction* actVideoSettings; + QAction* actCameraSettings; + QAction* actAudioSettings; + QAction* actMPSettings; + QAction* actWifiSettings; + QAction* actFirmwareSettings; + QAction* actPathSettings; + QAction* actInterfaceSettings; + QAction* actSavestateSRAMReloc; + QAction* actScreenSize[4]; + QActionGroup* grpScreenRotation; + QAction* actScreenRotation[Frontend::screenRot_MAX]; + QActionGroup* grpScreenGap; + QAction* actScreenGap[6]; + QActionGroup* grpScreenLayout; + QAction* actScreenLayout[Frontend::screenLayout_MAX]; + QAction* actScreenSwap; + QActionGroup* grpScreenSizing; + QAction* actScreenSizing[Frontend::screenSizing_MAX]; + QAction* actIntegerScaling; + QActionGroup* grpScreenAspectTop; + QAction** actScreenAspectTop; + QActionGroup* grpScreenAspectBot; + QAction** actScreenAspectBot; + QAction* actScreenFiltering; + QAction* actShowOSD; + QAction* actLimitFramerate; + QAction* actAudioSync; +}; + +void ToggleFullscreen(MainWindow* mainWindow); + +#endif // WINDOW_H diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index a0ac0860..54ade119 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -79,8 +79,8 @@ #include "version.h" #include "FrontendUtil.h" -#include "OSD.h" +#include "Args.h" #include "NDS.h" #include "NDSCart.h" #include "GBACart.h" @@ -98,7 +98,7 @@ #include "Savestate.h" -#include "main_shaders.h" +//#include "main_shaders.h" #include "ROMManager.h" #include "ArchiveUtil.h" @@ -108,14 +108,16 @@ // TODO: uniform variable spelling using namespace melonDS; -const QString NdsRomMimeType = "application/x-nintendo-ds-rom"; -const QStringList NdsRomExtensions { ".nds", ".srl", ".dsi", ".ids" }; +QString NdsRomMimeType = "application/x-nintendo-ds-rom"; +QStringList NdsRomExtensions { ".nds", ".srl", ".dsi", ".ids" }; -const QString GbaRomMimeType = "application/x-gba-rom"; -const QStringList GbaRomExtensions { ".gba", ".agb" }; +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). -const QStringList ArchiveMimeTypes +QStringList ArchiveMimeTypes { #ifdef ARCHIVE_SUPPORT_ENABLED "application/zip", @@ -137,7 +139,7 @@ const QStringList ArchiveMimeTypes #endif }; -const QStringList ArchiveExtensions +QStringList ArchiveExtensions { #ifdef ARCHIVE_SUPPORT_ENABLED ".zip", ".7z", ".rar", ".tar", @@ -170,1189 +172,7 @@ bool videoSettingsDirty; CameraManager* camManager[2]; bool camStarted[2]; -const struct { int id; float ratio; const char* label; } aspectRatios[] = -{ - { 0, 1, "4:3 (native)" }, - { 4, (5.f / 3) / (4.f / 3), "5:3 (3DS)"}, - { 1, (16.f / 9) / (4.f / 3), "16:9" }, - { 2, (21.f / 9) / (4.f / 3), "21:9" }, - { 3, 0, "window" } -}; -constexpr int AspectRatiosNum = sizeof(aspectRatios) / sizeof(aspectRatios[0]); - - -EmuThread::EmuThread(QObject* parent) : QThread(parent) -{ - EmuStatus = emuStatus_Exit; - EmuRunning = emuStatus_Paused; - EmuPauseStack = EmuPauseStackRunning; - RunningSomething = false; - - connect(this, SIGNAL(windowUpdate()), mainWindow->panelWidget, SLOT(repaint())); - connect(this, SIGNAL(windowTitleChange(QString)), mainWindow, SLOT(onTitleUpdate(QString))); - connect(this, SIGNAL(windowEmuStart()), mainWindow, SLOT(onEmuStart())); - connect(this, SIGNAL(windowEmuStop()), mainWindow, SLOT(onEmuStop())); - 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->panelWidget, SLOT(onScreenLayoutChanged())); - connect(this, SIGNAL(windowFullscreenToggle()), mainWindow, SLOT(onFullscreenToggled())); - connect(this, SIGNAL(swapScreensToggle()), mainWindow->actScreenSwap, SLOT(trigger())); - connect(this, SIGNAL(screenEmphasisToggle()), mainWindow, SLOT(onScreenEmphasisToggled())); - - static_cast(mainWindow->panel)->transferLayout(this); -} - -std::unique_ptr EmuThread::CreateConsole() -{ - if (Config::ConsoleType == 1) - { - return std::make_unique(); - } - - return std::make_unique(); -} - -void EmuThread::RecreateConsole() -{ - if (!NDS || NDS->ConsoleType != Config::ConsoleType) - { - NDS = nullptr; // To ensure the destructor is called before a new one is created - NDS::Current = nullptr; - - NDS = CreateConsole(); - // TODO: Insert ROMs - NDS::Current = NDS.get(); - } -} - - -void EmuThread::updateScreenSettings(bool filter, const WindowInfo& windowInfo, int numScreens, int* screenKind, float* screenMatrix) -{ - screenSettingsLock.lock(); - - if (lastScreenWidth != windowInfo.surface_width || lastScreenHeight != windowInfo.surface_height) - { - if (oglContext) - oglContext->ResizeSurface(windowInfo.surface_width, windowInfo.surface_height); - lastScreenWidth = windowInfo.surface_width; - lastScreenHeight = windowInfo.surface_height; - } - - this->filter = filter; - this->windowInfo = windowInfo; - this->numScreens = numScreens; - memcpy(this->screenKind, screenKind, sizeof(int)*numScreens); - memcpy(this->screenMatrix, screenMatrix, sizeof(float)*numScreens*6); - - screenSettingsLock.unlock(); -} - -void EmuThread::initOpenGL() -{ - GL::Context* windowctx = mainWindow->getOGLContext(); - - oglContext = windowctx; - oglContext->MakeCurrent(); - - OpenGL::BuildShaderProgram(kScreenVS, kScreenFS, screenShaderProgram, "ScreenShader"); - GLuint pid = screenShaderProgram[2]; - glBindAttribLocation(pid, 0, "vPosition"); - glBindAttribLocation(pid, 1, "vTexcoord"); - glBindFragDataLocation(pid, 0, "oColor"); - - OpenGL::LinkShaderProgram(screenShaderProgram); - - glUseProgram(pid); - glUniform1i(glGetUniformLocation(pid, "ScreenTex"), 0); - - screenShaderScreenSizeULoc = glGetUniformLocation(pid, "uScreenSize"); - screenShaderTransformULoc = glGetUniformLocation(pid, "uTransform"); - - // to prevent bleeding between both parts of the screen - // with bilinear filtering enabled - const int paddedHeight = 192*2+2; - const float padPixels = 1.f / paddedHeight; - - const float vertices[] = - { - 0.f, 0.f, 0.f, 0.f, - 0.f, 192.f, 0.f, 0.5f - padPixels, - 256.f, 192.f, 1.f, 0.5f - padPixels, - 0.f, 0.f, 0.f, 0.f, - 256.f, 192.f, 1.f, 0.5f - padPixels, - 256.f, 0.f, 1.f, 0.f, - - 0.f, 0.f, 0.f, 0.5f + padPixels, - 0.f, 192.f, 0.f, 1.f, - 256.f, 192.f, 1.f, 1.f, - 0.f, 0.f, 0.f, 0.5f + padPixels, - 256.f, 192.f, 1.f, 1.f, - 256.f, 0.f, 1.f, 0.5f + padPixels - }; - - glGenBuffers(1, &screenVertexBuffer); - glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - - glGenVertexArrays(1, &screenVertexArray); - glBindVertexArray(screenVertexArray); - glEnableVertexAttribArray(0); // position - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(0)); - glEnableVertexAttribArray(1); // texcoord - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(2*4)); - - glGenTextures(1, &screenTexture); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, screenTexture); - 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, 256, paddedHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - // fill the padding - u8 zeroData[256*4*4]; - memset(zeroData, 0, sizeof(zeroData)); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256, 2, GL_RGBA, GL_UNSIGNED_BYTE, zeroData); - - OSD::Init(true); - - oglContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); -} - -void EmuThread::deinitOpenGL() -{ - glDeleteTextures(1, &screenTexture); - - glDeleteVertexArrays(1, &screenVertexArray); - glDeleteBuffers(1, &screenVertexBuffer); - - OpenGL::DeleteShaderProgram(screenShaderProgram); - - OSD::DeInit(); - - oglContext->DoneCurrent(); - oglContext = nullptr; - - lastScreenWidth = lastScreenHeight = -1; -} - -void EmuThread::run() -{ - u32 mainScreenPos[3]; - Platform::FileHandle* file; - - RecreateConsole(); - - mainScreenPos[0] = 0; - mainScreenPos[1] = 0; - mainScreenPos[2] = 0; - autoScreenSizing = 0; - - videoSettingsDirty = false; - - if (mainWindow->hasOGL) - { - initOpenGL(); - videoRenderer = Config::_3DRenderer; - } - else - { - videoRenderer = 0; - } - - if (videoRenderer == 0) - { // If we're using the software renderer... - NDS->GPU.SetRenderer3D(std::make_unique(NDS->GPU, Config::Threaded3D != 0)); - } - else - { - auto glrenderer = melonDS::GLRenderer::New(NDS->GPU); - glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor); - NDS->GPU.SetRenderer3D(std::move(glrenderer)); - } - - NDS->SPU.SetInterpolation(Config::AudioInterp); - - Input::Init(); - - u32 nframes = 0; - double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency(); - double lastTime = SDL_GetPerformanceCounter() * perfCountsSec; - double frameLimitError = 0.0; - double lastMeasureTime = lastTime; - - 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) - { - Input::Process(); - - if (Input::HotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange(); - - if (Input::HotkeyPressed(HK_Pause)) emit windowEmuPause(); - if (Input::HotkeyPressed(HK_Reset)) emit windowEmuReset(); - if (Input::HotkeyPressed(HK_FrameStep)) emit windowEmuFrameStep(); - - if (Input::HotkeyPressed(HK_FullscreenToggle)) emit windowFullscreenToggle(); - - if (Input::HotkeyPressed(HK_SwapScreens)) emit swapScreensToggle(); - if (Input::HotkeyPressed(HK_SwapScreenEmphasis)) emit screenEmphasisToggle(); - - if (Input::HotkeyPressed(HK_SolarSensorDecrease)) - { - int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorDown, true); - if (level != -1) - { - char msg[64]; - sprintf(msg, "Solar sensor level: %d", level); - OSD::AddMessage(0, msg); - } - } - if (Input::HotkeyPressed(HK_SolarSensorIncrease)) - { - int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorUp, true); - if (level != -1) - { - char msg[64]; - sprintf(msg, "Solar sensor level: %d", level); - OSD::AddMessage(0, msg); - } - } - - if (NDS->ConsoleType == 1) - { - DSi& dsi = static_cast(*NDS); - double currentTime = SDL_GetPerformanceCounter() * perfCountsSec; - - // Handle power button - if (Input::HotkeyDown(HK_PowerButton)) - { - dsi.I2C.GetBPTWL()->SetPowerButtonHeld(currentTime); - } - else if (Input::HotkeyReleased(HK_PowerButton)) - { - dsi.I2C.GetBPTWL()->SetPowerButtonReleased(currentTime); - } - - // Handle volume buttons - if (Input::HotkeyDown(HK_VolumeUp)) - { - dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Up); - } - else if (Input::HotkeyReleased(HK_VolumeUp)) - { - dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Up); - } - - if (Input::HotkeyDown(HK_VolumeDown)) - { - dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Down); - } - else if (Input::HotkeyReleased(HK_VolumeDown)) - { - dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Down); - } - - dsi.I2C.GetBPTWL()->ProcessVolumeSwitchInput(currentTime); - } - - if (EmuRunning == emuStatus_Running || EmuRunning == emuStatus_FrameStep) - { - EmuStatus = emuStatus_Running; - if (EmuRunning == emuStatus_FrameStep) EmuRunning = emuStatus_Paused; - - // 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 (oglContext) - { - oglContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); - videoRenderer = Config::_3DRenderer; - } -#ifdef OGLRENDERER_ENABLED - else -#endif - { - videoRenderer = 0; - } - - videoRenderer = oglContext ? Config::_3DRenderer : 0; - - videoSettingsDirty = false; - - if (videoRenderer == 0) - { // If we're using the software renderer... - NDS->GPU.SetRenderer3D(std::make_unique(NDS->GPU, Config::Threaded3D != 0)); - } - else - { - auto glrenderer = melonDS::GLRenderer::New(NDS->GPU); - glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor); - NDS->GPU.SetRenderer3D(std::move(glrenderer)); - } - } - - // process input and hotkeys - NDS->SetKeyMask(Input::InputMask); - - if (Input::HotkeyPressed(HK_Lid)) - { - bool lid = !NDS->IsLidClosed(); - NDS->SetLidClosed(lid); - OSD::AddMessage(0, lid ? "Lid closed" : "Lid opened"); - } - - // microphone input - AudioInOut::MicProcess(*NDS); - - // auto screen layout - if (Config::ScreenSizing == Frontend::screenSizing_Auto) - { - mainScreenPos[2] = mainScreenPos[1]; - mainScreenPos[1] = mainScreenPos[0]; - mainScreenPos[0] = NDS->PowerControl9 >> 15; - - int guess; - if (mainScreenPos[0] == mainScreenPos[2] && - mainScreenPos[0] != mainScreenPos[1]) - { - // constant flickering, likely displaying 3D on both screens - // TODO: when both screens are used for 2D only...??? - guess = Frontend::screenSizing_Even; - } - else - { - if (mainScreenPos[0] == 1) - guess = Frontend::screenSizing_EmphTop; - else - guess = Frontend::screenSizing_EmphBot; - } - - if (guess != autoScreenSizing) - { - autoScreenSizing = guess; - emit screenLayoutChange(); - } - } - - - // emulate - u32 nlines = NDS->RunFrame(); - - if (ROMManager::NDSSave) - ROMManager::NDSSave->CheckFlush(); - - if (ROMManager::GBASave) - ROMManager::GBASave->CheckFlush(); - - if (ROMManager::FirmwareSave) - ROMManager::FirmwareSave->CheckFlush(); - - if (!oglContext) - { - FrontBufferLock.lock(); - FrontBuffer = NDS->GPU.FrontBuffer; - FrontBufferLock.unlock(); - } - else - { - FrontBuffer = NDS->GPU.FrontBuffer; - drawScreenGL(); - } - -#ifdef MELONCAP - MelonCap::Update(); -#endif // MELONCAP - - if (EmuRunning == emuStatus_Exit) break; - - winUpdateCount++; - if (winUpdateCount >= winUpdateFreq && !oglContext) - { - emit windowUpdate(); - winUpdateCount = 0; - } - - bool fastforward = Input::HotkeyDown(HK_FastForward); - - if (fastforward && oglContext && Config::ScreenVSync) - { - oglContext->SetSwapInterval(0); - } - - if (Config::DSiVolumeSync && NDS->ConsoleType == 1) - { - DSi& dsi = static_cast(*NDS); - u8 volumeLevel = dsi.I2C.GetBPTWL()->GetVolumeLevel(); - if (volumeLevel != dsiVolumeLevel) - { - dsiVolumeLevel = volumeLevel; - emit syncVolumeLevel(); - } - - Config::AudioVolume = volumeLevel * (256.0 / 31.0); - } - - if (Config::AudioSync && !fastforward) - AudioInOut::AudioSync(*emuThread->NDS); - - double frametimeStep = nlines / (60.0 * 263.0); - - { - 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; - - if (round(frameLimitError * 1000.0) > 0.0) - { - SDL_Delay(round(frameLimitError * 1000.0)); - double timeBeforeSleep = curtime; - curtime = SDL_GetPerformanceCounter() * perfCountsSec; - frameLimitError -= curtime - timeBeforeSleep; - } - - lastTime = curtime; - } - - nframes++; - if (nframes >= 30) - { - double time = SDL_GetPerformanceCounter() * perfCountsSec; - double dt = time - lastMeasureTime; - lastMeasureTime = time; - - u32 fps = round(nframes / dt); - nframes = 0; - - float fpstarget = 1.0/frametimeStep; - - winUpdateFreq = fps / (u32)round(fpstarget); - if (winUpdateFreq < 1) - winUpdateFreq = 1; - - int inst = Platform::InstanceID(); - if (inst == 0) - sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); - else - sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1); - changeWindowTitle(melontitle); - } - } - else - { - // paused - nframes = 0; - lastTime = SDL_GetPerformanceCounter() * perfCountsSec; - lastMeasureTime = lastTime; - - emit windowUpdate(); - - EmuStatus = EmuRunning; - - int inst = Platform::InstanceID(); - if (inst == 0) - sprintf(melontitle, "melonDS " MELONDS_VERSION); - else - sprintf(melontitle, "melonDS (%d)", inst+1); - changeWindowTitle(melontitle); - - SDL_Delay(75); - - if (oglContext) - drawScreenGL(); - - ContextRequestKind contextRequest = ContextRequest; - if (contextRequest == contextRequest_InitGL) - { - initOpenGL(); - ContextRequest = contextRequest_None; - } - else if (contextRequest == contextRequest_DeInitGL) - { - deinitOpenGL(); - ContextRequest = contextRequest_None; - } - } - } - - 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); - } - - EmuStatus = emuStatus_Exit; - - NDS::Current = nullptr; - // nds is out of scope, so unique_ptr cleans it up for us -} - -void EmuThread::changeWindowTitle(char* title) -{ - emit windowTitleChange(QString(title)); -} - -void EmuThread::emuRun() -{ - EmuRunning = emuStatus_Running; - EmuPauseStack = EmuPauseStackRunning; - RunningSomething = true; - - // checkme - emit windowEmuStart(); - AudioInOut::Enable(); -} - -void EmuThread::initContext() -{ - ContextRequest = contextRequest_InitGL; - while (ContextRequest != contextRequest_None); -} - -void EmuThread::deinitContext() -{ - ContextRequest = contextRequest_DeInitGL; - while (ContextRequest != contextRequest_None); -} - -void EmuThread::emuPause() -{ - EmuPauseStack++; - if (EmuPauseStack > EmuPauseStackPauseThreshold) return; - - PrevEmuStatus = EmuRunning; - EmuRunning = emuStatus_Paused; - while (EmuStatus != emuStatus_Paused); - - AudioInOut::Disable(); -} - -void EmuThread::emuUnpause() -{ - if (EmuPauseStack < EmuPauseStackPauseThreshold) return; - - EmuPauseStack--; - if (EmuPauseStack >= EmuPauseStackPauseThreshold) return; - - EmuRunning = PrevEmuStatus; - - AudioInOut::Enable(); -} - -void EmuThread::emuStop() -{ - EmuRunning = emuStatus_Exit; - EmuPauseStack = EmuPauseStackRunning; - - AudioInOut::Disable(); -} - -void EmuThread::emuFrameStep() -{ - if (EmuPauseStack < EmuPauseStackPauseThreshold) emit windowEmuPause(); - EmuRunning = emuStatus_FrameStep; -} - -bool EmuThread::emuIsRunning() -{ - return EmuRunning == emuStatus_Running; -} - -bool EmuThread::emuIsActive() -{ - return (RunningSomething == 1); -} - -void EmuThread::drawScreenGL() -{ - if (!NDS) return; - int w = windowInfo.surface_width; - int h = windowInfo.surface_height; - float factor = windowInfo.surface_scale; - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glDisable(GL_DEPTH_TEST); - glDepthMask(false); - glDisable(GL_BLEND); - glDisable(GL_SCISSOR_TEST); - glDisable(GL_STENCIL_TEST); - glClear(GL_COLOR_BUFFER_BIT); - - glViewport(0, 0, w, h); - - glUseProgram(screenShaderProgram[2]); - glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor); - - int frontbuf = FrontBuffer; - glActiveTexture(GL_TEXTURE0); - -#ifdef OGLRENDERER_ENABLED - if (NDS->GPU.GetRenderer3D().Accelerated) - { - // hardware-accelerated render - static_cast(NDS->GPU.GetRenderer3D()).GetCompositor().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(); - - OSD::Update(); - OSD::DrawGL(w, h); - - oglContext->SwapBuffers(); -} - -ScreenHandler::ScreenHandler(QWidget* widget) -{ - widget->setMouseTracking(true); - widget->setAttribute(Qt::WA_AcceptTouchEvents); - QTimer* mouseTimer = setupMouseTimer(); - widget->connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) widget->setCursor(Qt::BlankCursor);}); -} - -ScreenHandler::~ScreenHandler() -{ - mouseTimer->stop(); - delete mouseTimer; -} - -void ScreenHandler::screenSetupLayout(int w, int h) -{ - int sizing = Config::ScreenSizing; - if (sizing == 3) sizing = autoScreenSizing; - - float aspectTop, aspectBot; - - for (auto ratio : aspectRatios) - { - if (ratio.id == Config::ScreenAspectTop) - aspectTop = ratio.ratio; - if (ratio.id == Config::ScreenAspectBot) - aspectBot = ratio.ratio; - } - - if (aspectTop == 0) - aspectTop = ((float) w / h) / (4.f / 3.f); - - 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); - - numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind); -} - -QSize ScreenHandler::screenGetMinSize(int factor = 1) -{ - bool isHori = (Config::ScreenRotation == Frontend::screenRot_90Deg - || Config::ScreenRotation == Frontend::screenRot_270Deg); - int gap = Config::ScreenGap * factor; - - int w = 256 * factor; - int h = 192 * factor; - - if (Config::ScreenSizing == Frontend::screenSizing_TopOnly - || Config::ScreenSizing == Frontend::screenSizing_BotOnly) - { - return QSize(w, h); - } - - if (Config::ScreenLayout == Frontend::screenLayout_Natural) - { - if (isHori) - return QSize(h+gap+h, w); - else - return QSize(w, h+gap+h); - } - else if (Config::ScreenLayout == Frontend::screenLayout_Vertical) - { - if (isHori) - return QSize(h, w+gap+w); - else - return QSize(w, h+gap+h); - } - else if (Config::ScreenLayout == Frontend::screenLayout_Horizontal) - { - if (isHori) - return QSize(h+gap+h, w); - else - return QSize(w+gap+w, h); - } - else // hybrid - { - if (isHori) - return QSize(h+gap+h, 3*w + (int)ceil((4*gap) / 3.0)); - else - return QSize(3*w + (int)ceil((4*gap) / 3.0), h+gap+h); - } -} - -void ScreenHandler::screenOnMousePress(QMouseEvent* event) -{ - event->accept(); - if (event->button() != Qt::LeftButton) return; - - int x = event->pos().x(); - int y = event->pos().y(); - - if (Frontend::GetTouchCoords(x, y, false)) - { - touching = true; - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); - } -} - -void ScreenHandler::screenOnMouseRelease(QMouseEvent* event) -{ - event->accept(); - if (event->button() != Qt::LeftButton) return; - - if (touching) - { - touching = false; - assert(emuThread->NDS != nullptr); - emuThread->NDS->ReleaseScreen(); - } -} - -void ScreenHandler::screenOnMouseMove(QMouseEvent* event) -{ - event->accept(); - - showCursor(); - - 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)) - { - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); - } -} - -void ScreenHandler::screenHandleTablet(QTabletEvent* event) -{ - event->accept(); - - switch(event->type()) - { - case QEvent::TabletPress: - case QEvent::TabletMove: - { - int x = event->x(); - int y = event->y(); - - if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TabletMove)) - { - touching = true; - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); - } - } - break; - case QEvent::TabletRelease: - if (touching) - { - assert(emuThread->NDS != nullptr); - emuThread->NDS->ReleaseScreen(); - touching = false; - } - break; - default: - break; - } -} - -void ScreenHandler::screenHandleTouch(QTouchEvent* event) -{ - event->accept(); - - switch(event->type()) - { - case QEvent::TouchBegin: - case QEvent::TouchUpdate: - if (event->touchPoints().length() > 0) - { - QPointF lastPosition = event->touchPoints().first().lastPos(); - int x = (int)lastPosition.x(); - int y = (int)lastPosition.y(); - - if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TouchUpdate)) - { - touching = true; - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); - } - } - break; - case QEvent::TouchEnd: - if (touching) - { - assert(emuThread->NDS != nullptr); - emuThread->NDS->ReleaseScreen(); - touching = false; - } - break; - default: - break; - } -} - -void ScreenHandler::showCursor() -{ - mainWindow->panelWidget->setCursor(Qt::ArrowCursor); - mouseTimer->start(); -} - -QTimer* ScreenHandler::setupMouseTimer() -{ - mouseTimer = new QTimer(); - mouseTimer->setSingleShot(true); - mouseTimer->setInterval(Config::MouseHideSeconds*1000); - mouseTimer->start(); - - return mouseTimer; -} - -ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent), ScreenHandler(this) -{ - screen[0] = QImage(256, 192, QImage::Format_RGB32); - screen[1] = QImage(256, 192, QImage::Format_RGB32); - - screenTrans[0].reset(); - screenTrans[1].reset(); - - OSD::Init(false); -} - -ScreenPanelNative::~ScreenPanelNative() -{ - OSD::DeInit(); -} - -void ScreenPanelNative::setupScreenLayout() -{ - int w = width(); - int h = height(); - - screenSetupLayout(w, h); - - for (int i = 0; i < numScreens; i++) - { - float* mtx = screenMatrix[i]; - screenTrans[i].setMatrix(mtx[0], mtx[1], 0.f, - mtx[2], mtx[3], 0.f, - mtx[4], mtx[5], 1.f); - } -} - -void ScreenPanelNative::paintEvent(QPaintEvent* event) -{ - QPainter painter(this); - - // fill background - painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0)); - - 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]) - { - 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(); - - QRect screenrc(0, 0, 256, 192); - - for (int i = 0; i < numScreens; i++) - { - painter.setTransform(screenTrans[i]); - painter.drawImage(screenrc, screen[screenKind[i]]); - } - } - - OSD::Update(); - OSD::DrawNative(painter); -} - -void ScreenPanelNative::resizeEvent(QResizeEvent* event) -{ - setupScreenLayout(); -} - -void ScreenPanelNative::mousePressEvent(QMouseEvent* event) -{ - screenOnMousePress(event); -} - -void ScreenPanelNative::mouseReleaseEvent(QMouseEvent* event) -{ - screenOnMouseRelease(event); -} - -void ScreenPanelNative::mouseMoveEvent(QMouseEvent* event) -{ - screenOnMouseMove(event); -} - -void ScreenPanelNative::tabletEvent(QTabletEvent* event) -{ - screenHandleTablet(event); -} - -bool ScreenPanelNative::event(QEvent* event) -{ - if (event->type() == QEvent::TouchBegin - || event->type() == QEvent::TouchEnd - || event->type() == QEvent::TouchUpdate) - { - screenHandleTouch((QTouchEvent*)event); - return true; - } - return QWidget::event(event); -} - -void ScreenPanelNative::onScreenLayoutChanged() -{ - setMinimumSize(screenGetMinSize()); - setupScreenLayout(); -} - - -ScreenPanelGL::ScreenPanelGL(QWidget* parent) : QWidget(parent), ScreenHandler(this) -{ - setAutoFillBackground(false); - setAttribute(Qt::WA_NativeWindow, true); - setAttribute(Qt::WA_NoSystemBackground, true); - setAttribute(Qt::WA_PaintOnScreen, true); - setAttribute(Qt::WA_KeyCompression, false); - setFocusPolicy(Qt::StrongFocus); - setMinimumSize(screenGetMinSize()); -} - -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()) - { - glContext = GL::Context::Create(*getWindowInfo(), versionsToTry); - glContext->DoneCurrent(); - } - - return glContext != nullptr; -} - -qreal ScreenPanelGL::devicePixelRatioFromScreen() const -{ - const QScreen* screen_for_ratio = window()->windowHandle()->screen(); - if (!screen_for_ratio) - screen_for_ratio = QGuiApplication::primaryScreen(); - - return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast(1); -} - -int ScreenPanelGL::scaledWindowWidth() const -{ - return std::max(static_cast(std::ceil(static_cast(width()) * devicePixelRatioFromScreen())), 1); -} - -int ScreenPanelGL::scaledWindowHeight() const -{ - return std::max(static_cast(std::ceil(static_cast(height()) * devicePixelRatioFromScreen())), 1); -} - -std::optional ScreenPanelGL::getWindowInfo() -{ - WindowInfo wi; - - // Windows and Apple are easy here since there's no display connection. - #if defined(_WIN32) - wi.type = WindowInfo::Type::Win32; - wi.window_handle = reinterpret_cast(winId()); - #elif defined(__APPLE__) - wi.type = WindowInfo::Type::MacOS; - wi.window_handle = reinterpret_cast(winId()); - #else - QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); - const QString platform_name = QGuiApplication::platformName(); - if (platform_name == QStringLiteral("xcb")) - { - wi.type = WindowInfo::Type::X11; - wi.display_connection = pni->nativeResourceForWindow("display", windowHandle()); - wi.window_handle = reinterpret_cast(winId()); - } - else if (platform_name == QStringLiteral("wayland")) - { - wi.type = WindowInfo::Type::Wayland; - QWindow* handle = windowHandle(); - if (handle == nullptr) - return std::nullopt; - - wi.display_connection = pni->nativeResourceForWindow("display", handle); - wi.window_handle = pni->nativeResourceForWindow("surface", handle); - } - else - { - qCritical() << "Unknown PNI platform " << platform_name; - return std::nullopt; - } - #endif - - wi.surface_width = static_cast(scaledWindowWidth()); - wi.surface_height = static_cast(scaledWindowHeight()); - wi.surface_scale = static_cast(devicePixelRatioFromScreen()); - - return wi; -} - - -QPaintEngine* ScreenPanelGL::paintEngine() const -{ - return nullptr; -} - -void ScreenPanelGL::setupScreenLayout() -{ - int w = width(); - int h = height(); - - screenSetupLayout(w, h); - if (emuThread) - transferLayout(emuThread); -} - -void ScreenPanelGL::resizeEvent(QResizeEvent* event) -{ - setupScreenLayout(); - - QWidget::resizeEvent(event); -} - -void ScreenPanelGL::mousePressEvent(QMouseEvent* event) -{ - screenOnMousePress(event); -} - -void ScreenPanelGL::mouseReleaseEvent(QMouseEvent* event) -{ - screenOnMouseRelease(event); -} - -void ScreenPanelGL::mouseMoveEvent(QMouseEvent* event) -{ - screenOnMouseMove(event); -} - -void ScreenPanelGL::tabletEvent(QTabletEvent* event) -{ - screenHandleTablet(event); -} - -bool ScreenPanelGL::event(QEvent* event) -{ - if (event->type() == QEvent::TouchBegin - || event->type() == QEvent::TouchEnd - || event->type() == QEvent::TouchUpdate) - { - screenHandleTouch((QTouchEvent*)event); - return true; - } - return QWidget::event(event); -} - -void ScreenPanelGL::transferLayout(EmuThread* thread) -{ - std::optional windowInfo = getWindowInfo(); - if (windowInfo.has_value()) - thread->updateScreenSettings(Config::ScreenFilter, *windowInfo, numScreens, screenKind, &screenMatrix[0][0]); -} - -void ScreenPanelGL::onScreenLayoutChanged() -{ - setMinimumSize(screenGetMinSize()); - setupScreenLayout(); -} +//extern int AspectRatiosNum; static bool FileExtensionInList(const QString& filename, const QStringList& extensions, Qt::CaseSensitivity cs = Qt::CaseInsensitive) @@ -1427,1864 +247,7 @@ static bool FileIsSupportedFiletype(const QString& filename, bool insideArchive } -#ifndef _WIN32 -static int signalFd[2]; -QSocketNotifier *signalSn; -static void signalHandler(int) -{ - char a = 1; - write(signalFd[0], &a, sizeof(a)); -} -#endif - -MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) -{ -#ifndef _WIN32 - 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); -#endif - - oldW = Config::WindowWidth; - oldH = Config::WindowHeight; - oldMax = Config::WindowMaximized; - - 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); -#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(); - - { - QMenu* submenu = menu->addMenu("Screen size"); - - 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); - - 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) - { - 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); - } - } - - 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); - - 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); - } - setMenuBar(menubar); - - resize(Config::WindowWidth, Config::WindowHeight); - - if (Config::FirmwareUsername == "Arisotura") - actMPNewInstance->setText("Fart"); - -#ifdef Q_OS_MAC - QPoint screenCenter = screen()->availableGeometry().center(); - QRect frameGeo = frameGeometry(); - frameGeo.moveCenter(screenCenter); - move(frameGeo.topLeft()); -#endif - - if (oldMax) - showMaximized(); - else - show(); - - createScreenPanel(); - - actEjectCart->setEnabled(false); - actEjectGBACart->setEnabled(false); - - if (Config::ConsoleType == 1) - { - actInsertGBACart->setEnabled(false); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->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) - { - actScreenGap[i]->setChecked(true); - break; - } - } - - actScreenLayout[Config::ScreenLayout]->setChecked(true); - actScreenSizing[Config::ScreenSizing]->setChecked(true); - actIntegerScaling->setChecked(Config::IntegerScaling); - - actScreenSwap->setChecked(Config::ScreenSwap); - - 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); - } - - actScreenFiltering->setChecked(Config::ScreenFilter); - actShowOSD->setChecked(Config::ShowOSD); - - actLimitFramerate->setChecked(Config::LimitFPS); - actAudioSync->setChecked(Config::AudioSync); - - if (inst > 0) - { - actEmuSettings->setEnabled(false); - actVideoSettings->setEnabled(false); - actMPSettings->setEnabled(false); - actWifiSettings->setEnabled(false); - actInterfaceSettings->setEnabled(false); - -#ifdef __APPLE__ - actPreferences->setEnabled(false); -#endif // __APPLE__ - } -} - -MainWindow::~MainWindow() -{ - delete[] actScreenAspectTop; - delete[] actScreenAspectBot; -} - -void MainWindow::closeEvent(QCloseEvent* event) -{ - if (hasOGL) - { - // we intentionally don't unpause here - emuThread->emuPause(); - emuThread->deinitContext(); - } - - QMainWindow::closeEvent(event); -} - -void MainWindow::createScreenPanel() -{ - hasOGL = (Config::ScreenUseGL != 0) || (Config::_3DRenderer != 0); - - if (hasOGL) - { - ScreenPanelGL* panelGL = new ScreenPanelGL(this); - panelGL->show(); - - panel = panelGL; - panelWidget = panelGL; - - panelGL->createContext(); - } - - if (!hasOGL) - { - ScreenPanelNative* panelNative = new ScreenPanelNative(this); - panel = panelNative; - panelWidget = panelNative; - panelWidget->show(); - } - setCentralWidget(panelWidget); - - actScreenFiltering->setEnabled(hasOGL); - - connect(this, SIGNAL(screenLayoutChange()), panelWidget, SLOT(onScreenLayoutChanged())); - emit screenLayoutChange(); -} - -GL::Context* MainWindow::getOGLContext() -{ - if (!hasOGL) return nullptr; - - ScreenPanelGL* glpanel = static_cast(panel); - return glpanel->getContext(); -} - -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) -{ - if (event->isAutoRepeat()) return; - - // TODO!! REMOVE ME IN RELEASE BUILDS!! - //if (event->key() == Qt::Key_F11) NDS::debug(0); - - Input::KeyPress(event); -} - -void MainWindow::keyReleaseEvent(QKeyEvent* event) -{ - if (event->isAutoRepeat()) return; - - Input::KeyRelease(event); -} - - -void MainWindow::dragEnterEvent(QDragEnterEvent* event) -{ - if (!event->mimeData()->hasUrls()) return; - - QList urls = event->mimeData()->urls(); - if (urls.count() > 1) return; // not handling more than one file at once - - QString filename = urls.at(0).toLocalFile(); - - if (FileIsSupportedFiletype(filename)) - event->acceptProposedAction(); -} - -void MainWindow::dropEvent(QDropEvent* event) -{ - if (!event->mimeData()->hasUrls()) return; - - 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; - const auto matchMode = romInsideArchive ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault; - const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, matchMode); - - bool isNdsRom = NdsRomByExtension(filename) || NdsRomByMimetype(mimetype); - bool isGbaRom = GbaRomByExtension(filename) || GbaRomByMimetype(mimetype); - isNdsRom |= ZstdNdsRomByExtension(filename); - isGbaRom |= ZstdGbaRomByExtension(filename); - - if (isNdsRom) - { - if (!ROMManager::LoadROM(emuThread, file, true)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the DS ROM."); - emuThread->emuUnpause(); - return; - } - - const QString barredFilename = file.join('|'); - recentFileList.removeAll(barredFilename); - recentFileList.prepend(barredFilename); - updateRecentFilesMenu(); - - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); - emuThread->emuRun(); - - updateCartInserted(false); - } - else if (isGbaRom) - { - if (!ROMManager::LoadGBAROM(*emuThread->NDS, file)) - { - // 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); -} - -void MainWindow::focusOutEvent(QFocusEvent* event) -{ - AudioInOut::AudioMute(mainWindow); -} - -void MainWindow::onAppStateChanged(Qt::ApplicationState state) -{ - if (state == Qt::ApplicationInactive) - { - if (Config::PauseLostFocus && emuThread->emuIsRunning()) - emuThread->emuPause(); - } - else if (state == Qt::ApplicationActive) - { - if (Config::PauseLostFocus && !pausedManually) - emuThread->emuUnpause(); - } -} - -bool MainWindow::verifySetup() -{ - QString res = ROMManager::VerifySetup(); - if (!res.isEmpty()) - { - QMessageBox::critical(this, "melonDS", res); - return false; - } - - return true; -} - -bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) -{ - if (!verifySetup()) - { - return false; - } - - 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."); - return false; - } - - gbaloaded = true; - } - - bool ndsloaded = false; - if (!file.isEmpty()) - { - if (!ROMManager::LoadROM(emuThread, file, true)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - return false; - } - recentFileList.removeAll(file.join("|")); - recentFileList.prepend(file.join("|")); - updateRecentFilesMenu(); - ndsloaded = true; - } - - if (boot) - { - if (ndsloaded) - { - emuThread->NDS->Start(); - emuThread->emuRun(); - } - else - { - onBootFirmware(); - } - } - - updateCartInserted(false); - - if (gbaloaded) - { - updateCartInserted(true); - } - - return true; -} - -QStringList MainWindow::splitArchivePath(const QString& filename, bool useMemberSyntax) -{ - if (filename.isEmpty()) return {}; - -#ifdef ARCHIVE_SUPPORT_ENABLED - if (useMemberSyntax) - { - const QStringList filenameParts = filename.split('|'); - if (filenameParts.size() > 2) - { - QMessageBox::warning(this, "melonDS", "This path contains too many '|'."); - return {}; - } - - if (filenameParts.size() == 2) - { - const QString archive = filenameParts.at(0); - if (!QFileInfo(archive).exists()) - { - QMessageBox::warning(this, "melonDS", "This archive does not exist."); - return {}; - } - - const QString subfile = filenameParts.at(1); - if (!Archive::ListArchive(archive).contains(subfile)) - { - QMessageBox::warning(this, "melonDS", "This archive does not contain the desired file."); - return {}; - } - - return filenameParts; - } - } -#endif - - if (!QFileInfo(filename).exists()) - { - QMessageBox::warning(this, "melonDS", "This ROM file does not exist."); - return {}; - } - -#ifdef ARCHIVE_SUPPORT_ENABLED - if (SupportedArchiveByExtension(filename) - || SupportedArchiveByMimetype(QMimeDatabase().mimeTypeForFile(filename))) - { - const QString subfile = pickFileFromArchive(filename); - if (subfile.isEmpty()) - return {}; - - return { filename, subfile }; - } -#endif - - return { filename }; -} - -QString MainWindow::pickFileFromArchive(QString archiveFileName) -{ - QVector archiveROMList = Archive::ListArchive(archiveFileName); - - if (archiveROMList.size() <= 1) - { - if (!archiveROMList.isEmpty() && archiveROMList.at(0) == "OK") - QMessageBox::warning(this, "melonDS", "This archive is empty."); - else - QMessageBox::critical(this, "melonDS", "This archive could not be read. It may be corrupt or you don't have the permissions."); - return QString(); - } - - archiveROMList.removeFirst(); - - const auto notSupportedRom = [&](const auto& filename){ - if (NdsRomByExtension(filename) || GbaRomByExtension(filename)) - return false; - const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension); - return !(NdsRomByMimetype(mimetype) || GbaRomByMimetype(mimetype)); - }; - - archiveROMList.erase(std::remove_if(archiveROMList.begin(), archiveROMList.end(), notSupportedRom), - archiveROMList.end()); - - if (archiveROMList.isEmpty()) - { - QMessageBox::warning(this, "melonDS", "This archive does not contain any supported ROMs."); - return QString(); - } - - if (archiveROMList.size() == 1) - return archiveROMList.first(); - - bool ok; - const QString toLoad = QInputDialog::getItem( - this, "melonDS", - "This archive contains multiple files. Select which ROM you want to load.", - archiveROMList.toList(), 0, false, &ok - ); - - if (ok) return toLoad; - - // User clicked on cancel - - return QString(); -} - -QStringList MainWindow::pickROM(bool gba) -{ - const QString console = gba ? "GBA" : "DS"; - const QStringList& romexts = gba ? GbaRomExtensions : NdsRomExtensions; - - QString rawROMs = romexts.join(" *"); - QString extraFilters = ";;" + console + " ROMs (*" + rawROMs; - QString allROMs = rawROMs; - - QString zstdROMs = "*" + romexts.join(".zst *") + ".zst"; - extraFilters += ");;Zstandard-compressed " + console + " ROMs (" + zstdROMs + ")"; - allROMs += " " + zstdROMs; - -#ifdef ARCHIVE_SUPPORT_ENABLED - QString archives = "*" + ArchiveExtensions.join(" *"); - extraFilters += ";;Archives (" + archives + ")"; - allROMs += " " + archives; -#endif - extraFilters += ";;All files (*.*)"; - - const QString filename = QFileDialog::getOpenFileName( - this, "Open " + console + " ROM", - QString::fromStdString(Config::LastROMFolder), - "All supported files (*" + allROMs + ")" + extraFilters - ); - - if (filename.isEmpty()) return {}; - - Config::LastROMFolder = QFileInfo(filename).dir().path().toStdString(); - return splitArchivePath(filename, false); -} - -void MainWindow::updateCartInserted(bool gba) -{ - bool inserted; - if (gba) - { - inserted = ROMManager::GBACartInserted() && (Config::ConsoleType == 0); - actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); - 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); - } -} - -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)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - emuThread->emuUnpause(); - return; - } - - QString filename = file.join('|'); - recentFileList.removeAll(filename); - 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] = ""; - updateRecentFilesMenu(); -} - -void MainWindow::updateRecentFilesMenu() -{ - recentMenu->clear(); - - for (int i = 0; i < recentFileList.size(); ++i) - { - if (i >= 10) break; - - QString item_full = recentFileList.at(i); - QString item_display = item_full; - int itemlen = item_full.length(); - const int maxlen = 100; - if (itemlen > maxlen) - { - int cut_start = 0; - while (item_full[cut_start] != '/' && item_full[cut_start] != '\\' && - cut_start < itemlen) - cut_start++; - - int cut_end = itemlen-1; - while (((item_full[cut_end] != '/' && item_full[cut_end] != '\\') || - (cut_start+4+(itemlen-cut_end) < maxlen)) && - cut_end > 0) - cut_end--; - - item_display.truncate(cut_start+1); - item_display += "..."; - item_display += QString(item_full).remove(0, cut_end); - } - - 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) - recentFileList.removeLast(); - - recentMenu->addSeparator(); - - QAction *actClearRecentList = recentMenu->addAction("Clear"); - connect(actClearRecentList, &QAction::triggered, this, &MainWindow::onClearRecentFiles); - - if (recentFileList.empty()) - actClearRecentList->setEnabled(false); - - Config::Save(); -} - -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)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - emuThread->emuUnpause(); - return; - } - - recentFileList.removeAll(filename); - 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::LoadBIOS(emuThread)) - { - // 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()) - { - 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(); - - updateCartInserted(false); -} - -void MainWindow::onInsertGBACart() -{ - emuThread->emuPause(); - - QStringList file = pickROM(true); - if (file.isEmpty()) - { - 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); -} - -void MainWindow::onInsertGBAAddon() -{ - QAction* act = (QAction*)sender(); - int type = act->data().toInt(); - - emuThread->emuPause(); - - ROMManager::LoadGBAAddon(*emuThread->NDS, type); - - emuThread->emuUnpause(); - - updateCartInserted(true); -} - -void MainWindow::onEjectGBACart() -{ - emuThread->emuPause(); - - ROMManager::EjectGBACart(*emuThread->NDS); - - emuThread->emuUnpause(); - - updateCartInserted(true); -} - -void MainWindow::onSaveState() -{ - int slot = ((QAction*)sender())->data().toInt(); - - emuThread->emuPause(); - - std::string filename; - if (slot > 0) - { - filename = ROMManager::GetSavestateName(slot); - } - else - { - // TODO: specific 'last directory' for savestate files? - QString qfilename = QFileDialog::getSaveFileName(this, - "Save state", - QString::fromStdString(Config::LastROMFolder), - "melonDS savestates (*.mln);;Any file (*.*)"); - if (qfilename.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - filename = qfilename.toStdString(); - } - - if (ROMManager::SaveState(*emuThread->NDS, filename)) - { - char msg[64]; - if (slot > 0) sprintf(msg, "State saved to slot %d", slot); - else sprintf(msg, "State saved to file"); - OSD::AddMessage(0, msg); - - actLoadState[slot]->setEnabled(true); - } - else - { - OSD::AddMessage(0xFFA0A0, "State save failed"); - } - - emuThread->emuUnpause(); -} - -void MainWindow::onLoadState() -{ - int slot = ((QAction*)sender())->data().toInt(); - - emuThread->emuPause(); - - std::string filename; - if (slot > 0) - { - filename = ROMManager::GetSavestateName(slot); - } - else - { - // TODO: specific 'last directory' for savestate files? - QString qfilename = QFileDialog::getOpenFileName(this, - "Load state", - QString::fromStdString(Config::LastROMFolder), - "melonDS savestates (*.ml*);;Any file (*.*)"); - if (qfilename.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - filename = qfilename.toStdString(); - } - - if (!Platform::FileExists(filename)) - { - char msg[64]; - if (slot > 0) sprintf(msg, "State slot %d is empty", slot); - else sprintf(msg, "State file does not exist"); - OSD::AddMessage(0xFFA0A0, msg); - - emuThread->emuUnpause(); - return; - } - - if (ROMManager::LoadState(*emuThread->NDS, filename)) - { - char msg[64]; - if (slot > 0) sprintf(msg, "State loaded from slot %d", slot); - else sprintf(msg, "State loaded from file"); - OSD::AddMessage(0, msg); - - actUndoStateLoad->setEnabled(true); - } - else - { - OSD::AddMessage(0xFFA0A0, "State load failed"); - } - - emuThread->emuUnpause(); -} - -void MainWindow::onUndoStateLoad() -{ - emuThread->emuPause(); - ROMManager::UndoStateLoad(*emuThread->NDS); - emuThread->emuUnpause(); - - OSD::AddMessage(0, "State load undone"); -} - -void MainWindow::onImportSavefile() -{ - emuThread->emuPause(); - QString path = QFileDialog::getOpenFileName(this, - "Select savefile", - QString::fromStdString(Config::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) - { - QMessageBox::critical(this, "melonDS", "Could not open the given savefile."); - emuThread->emuUnpause(); - return; - } - - if (RunningSomething) - { - 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); - - u8* data = new u8[len]; - Platform::FileRewind(f); - Platform::FileRead(data, len, 1, f); - - assert(emuThread->NDS != nullptr); - emuThread->NDS->LoadSave(data, len); - delete[] data; - - CloseFile(f); - emuThread->emuUnpause(); -} - -void MainWindow::onQuit() -{ -#ifndef _WIN32 - signalSn->setEnabled(false); -#endif - QApplication::quit(); -} - - -void MainWindow::onPause(bool checked) -{ - if (!RunningSomething) return; - - if (checked) - { - emuThread->emuPause(); - OSD::AddMessage(0, "Paused"); - pausedManually = true; - } - else - { - emuThread->emuUnpause(); - OSD::AddMessage(0, "Resumed"); - pausedManually = false; - } -} - -void MainWindow::onReset() -{ - if (!RunningSomething) return; - - emuThread->emuPause(); - - actUndoStateLoad->setEnabled(false); - - ROMManager::Reset(emuThread); - - OSD::AddMessage(0, "Reset"); - emuThread->emuRun(); -} - -void MainWindow::onStop() -{ - if (!RunningSomething) return; - - emuThread->emuPause(); - emuThread->NDS->Stop(); -} - -void MainWindow::onFrameStep() -{ - if (!RunningSomething) return; - - emuThread->emuFrameStep(); -} - -void MainWindow::onOpenDateTime() -{ - DateTimeDialog* dlg = DateTimeDialog::openDlg(this); -} - -void MainWindow::onOpenPowerManagement() -{ - PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this, emuThread); -} - -void MainWindow::onEnableCheats(bool checked) -{ - Config::EnableCheats = checked?1:0; - ROMManager::EnableCheats(*emuThread->NDS, Config::EnableCheats != 0); -} - -void MainWindow::onSetupCheats() -{ - emuThread->emuPause(); - - CheatsDialog* dlg = CheatsDialog::openDlg(this); - connect(dlg, &CheatsDialog::finished, this, &MainWindow::onCheatsDialogFinished); -} - -void MainWindow::onCheatsDialogFinished(int res) -{ - emuThread->emuUnpause(); -} - -void MainWindow::onROMInfo() -{ - auto cart = emuThread->NDS->NDSCartSlot.GetCart(); - if (cart) - ROMInfoDialog* dlg = ROMInfoDialog::openDlg(this, *cart); -} - -void MainWindow::onRAMInfo() -{ - RAMInfoDialog* dlg = RAMInfoDialog::openDlg(this, emuThread); -} - -void MainWindow::onOpenTitleManager() -{ - TitleManagerDialog* dlg = TitleManagerDialog::openDlg(this); -} - -void MainWindow::onMPNewInstance() -{ - //QProcess::startDetached(QApplication::applicationFilePath()); - QProcess newinst; - newinst.setProgram(QApplication::applicationFilePath()); - newinst.setArguments(QApplication::arguments().mid(1, QApplication::arguments().length()-1)); - -#ifdef __WIN32__ - newinst.setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args) - { - args->flags |= CREATE_NEW_CONSOLE; - }); -#endif - - newinst.startDetached(); -} - -void MainWindow::onOpenEmuSettings() -{ - emuThread->emuPause(); - - EmuSettingsDialog* dlg = EmuSettingsDialog::openDlg(this); - connect(dlg, &EmuSettingsDialog::finished, this, &MainWindow::onEmuSettingsDialogFinished); -} - -void MainWindow::onEmuSettingsDialogFinished(int res) -{ - emuThread->emuUnpause(); - - if (Config::ConsoleType == 1) - { - actInsertGBACart->setEnabled(false); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(false); - actEjectGBACart->setEnabled(false); - } - else - { - actInsertGBACart->setEnabled(true); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(true); - actEjectGBACart->setEnabled(ROMManager::GBACartInserted()); - } - - if (EmuSettingsDialog::needsReset) - onReset(); - - actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); - - if (!RunningSomething) - actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); -} - -void MainWindow::onOpenInputConfig() -{ - emuThread->emuPause(); - - InputConfigDialog* dlg = InputConfigDialog::openDlg(this); - connect(dlg, &InputConfigDialog::finished, this, &MainWindow::onInputConfigFinished); -} - -void MainWindow::onInputConfigFinished(int res) -{ - emuThread->emuUnpause(); -} - -void MainWindow::onOpenVideoSettings() -{ - VideoSettingsDialog* dlg = VideoSettingsDialog::openDlg(this); - connect(dlg, &VideoSettingsDialog::updateVideoSettings, this, &MainWindow::onUpdateVideoSettings); -} - -void MainWindow::onOpenCameraSettings() -{ - emuThread->emuPause(); - - camStarted[0] = camManager[0]->isStarted(); - camStarted[1] = camManager[1]->isStarted(); - if (camStarted[0]) camManager[0]->stop(); - if (camStarted[1]) camManager[1]->stop(); - - CameraSettingsDialog* dlg = CameraSettingsDialog::openDlg(this); - connect(dlg, &CameraSettingsDialog::finished, this, &MainWindow::onCameraSettingsFinished); -} - -void MainWindow::onCameraSettingsFinished(int res) -{ - if (camStarted[0]) camManager[0]->start(); - if (camStarted[1]) camManager[1]->start(); - - emuThread->emuUnpause(); -} - -void MainWindow::onOpenAudioSettings() -{ - AudioSettingsDialog* dlg = AudioSettingsDialog::openDlg(this, emuThread->emuIsActive(), emuThread); - connect(emuThread, &EmuThread::syncVolumeLevel, dlg, &AudioSettingsDialog::onSyncVolumeLevel); - connect(emuThread, &EmuThread::windowEmuStart, dlg, &AudioSettingsDialog::onConsoleReset); - connect(dlg, &AudioSettingsDialog::updateAudioSettings, this, &MainWindow::onUpdateAudioSettings); - connect(dlg, &AudioSettingsDialog::finished, this, &MainWindow::onAudioSettingsFinished); -} - -void MainWindow::onOpenFirmwareSettings() -{ - emuThread->emuPause(); - - FirmwareSettingsDialog* dlg = FirmwareSettingsDialog::openDlg(this); - connect(dlg, &FirmwareSettingsDialog::finished, this, &MainWindow::onFirmwareSettingsFinished); -} - -void MainWindow::onFirmwareSettingsFinished(int res) -{ - if (FirmwareSettingsDialog::needsReset) - onReset(); - - emuThread->emuUnpause(); -} - -void MainWindow::onOpenPathSettings() -{ - emuThread->emuPause(); - - PathSettingsDialog* dlg = PathSettingsDialog::openDlg(this); - connect(dlg, &PathSettingsDialog::finished, this, &MainWindow::onPathSettingsFinished); -} - -void MainWindow::onPathSettingsFinished(int res) -{ - if (PathSettingsDialog::needsReset) - onReset(); - - emuThread->emuUnpause(); -} - -void MainWindow::onUpdateAudioSettings() -{ - assert(emuThread->NDS != nullptr); - emuThread->NDS->SPU.SetInterpolation(Config::AudioInterp); - - if (Config::AudioBitDepth == 0) - emuThread->NDS->SPU.SetDegrade10Bit(emuThread->NDS->ConsoleType == 0); - else - emuThread->NDS->SPU.SetDegrade10Bit(Config::AudioBitDepth == 1); -} - -void MainWindow::onAudioSettingsFinished(int res) -{ - AudioInOut::UpdateSettings(*emuThread->NDS); -} - -void MainWindow::onOpenMPSettings() -{ - emuThread->emuPause(); - - MPSettingsDialog* dlg = MPSettingsDialog::openDlg(this); - connect(dlg, &MPSettingsDialog::finished, this, &MainWindow::onMPSettingsFinished); -} - -void MainWindow::onMPSettingsFinished(int res) -{ - AudioInOut::AudioMute(mainWindow); - LocalMP::SetRecvTimeout(Config::MPRecvTimeout); - - emuThread->emuUnpause(); -} - -void MainWindow::onOpenWifiSettings() -{ - emuThread->emuPause(); - - WifiSettingsDialog* dlg = WifiSettingsDialog::openDlg(this); - connect(dlg, &WifiSettingsDialog::finished, this, &MainWindow::onWifiSettingsFinished); -} - -void MainWindow::onWifiSettingsFinished(int res) -{ - Platform::LAN_DeInit(); - Platform::LAN_Init(); - - if (WifiSettingsDialog::needsReset) - onReset(); - - emuThread->emuUnpause(); -} - -void MainWindow::onOpenInterfaceSettings() -{ - emuThread->emuPause(); - InterfaceSettingsDialog* dlg = InterfaceSettingsDialog::openDlg(this); - connect(dlg, &InterfaceSettingsDialog::finished, this, &MainWindow::onInterfaceSettingsFinished); - connect(dlg, &InterfaceSettingsDialog::updateMouseTimer, this, &MainWindow::onUpdateMouseTimer); -} - -void MainWindow::onUpdateMouseTimer() -{ - panel->mouseTimer->setInterval(Config::MouseHideSeconds*1000); -} - -void MainWindow::onInterfaceSettingsFinished(int res) -{ - emuThread->emuUnpause(); -} - -void MainWindow::onChangeSavestateSRAMReloc(bool checked) -{ - Config::SavestateRelocSRAM = checked?1:0; -} - -void MainWindow::onChangeScreenSize() -{ - int factor = ((QAction*)sender())->data().toInt(); - QSize diff = size() - panelWidget->size(); - resize(panel->screenGetMinSize(factor) + diff); -} - -void MainWindow::onChangeScreenRotation(QAction* act) -{ - int rot = act->data().toInt(); - Config::ScreenRotation = rot; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenGap(QAction* act) -{ - int gap = act->data().toInt(); - Config::ScreenGap = gap; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenLayout(QAction* act) -{ - int layout = act->data().toInt(); - Config::ScreenLayout = layout; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenSwap(bool checked) -{ - Config::ScreenSwap = checked?1:0; - - // Swap between top and bottom screen when displaying one screen. - if (Config::ScreenSizing == Frontend::screenSizing_TopOnly) - { - // Bottom Screen. - Config::ScreenSizing = Frontend::screenSizing_BotOnly; - actScreenSizing[Frontend::screenSizing_TopOnly]->setChecked(false); - actScreenSizing[Config::ScreenSizing]->setChecked(true); - } - else if (Config::ScreenSizing == Frontend::screenSizing_BotOnly) - { - // Top Screen. - Config::ScreenSizing = Frontend::screenSizing_TopOnly; - actScreenSizing[Frontend::screenSizing_BotOnly]->setChecked(false); - actScreenSizing[Config::ScreenSizing]->setChecked(true); - } - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenSizing(QAction* act) -{ - int sizing = act->data().toInt(); - Config::ScreenSizing = sizing; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenAspect(QAction* act) -{ - int aspect = act->data().toInt(); - QActionGroup* group = act->actionGroup(); - - if (group == grpScreenAspectTop) - { - Config::ScreenAspectTop = aspect; - } - else - { - Config::ScreenAspectBot = aspect; - } - - emit screenLayoutChange(); -} - -void MainWindow::onChangeIntegerScaling(bool checked) -{ - Config::IntegerScaling = checked?1:0; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenFiltering(bool checked) -{ - Config::ScreenFilter = checked?1:0; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeShowOSD(bool checked) -{ - Config::ShowOSD = checked?1:0; -} -void MainWindow::onChangeLimitFramerate(bool checked) -{ - Config::LimitFPS = checked?1:0; -} - -void MainWindow::onChangeAudioSync(bool checked) -{ - Config::AudioSync = checked?1:0; -} - - -void MainWindow::onTitleUpdate(QString title) -{ - setWindowTitle(title); -} - -void ToggleFullscreen(MainWindow* mainWindow) -{ - if (!mainWindow->isFullScreen()) - { - mainWindow->showFullScreen(); - mainWindow->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); - } -} - -void MainWindow::onFullscreenToggled() -{ - ToggleFullscreen(this); -} - -void MainWindow::onScreenEmphasisToggled() -{ - int currentSizing = Config::ScreenSizing; - if (currentSizing == Frontend::screenSizing_EmphTop) - { - Config::ScreenSizing = Frontend::screenSizing_EmphBot; - } - else if (currentSizing == Frontend::screenSizing_EmphBot) - { - Config::ScreenSizing = Frontend::screenSizing_EmphTop; - } - - emit screenLayoutChange(); -} - -void MainWindow::onEmuStart() -{ - for (int i = 1; i < 9; i++) - { - actSaveState[i]->setEnabled(true); - actLoadState[i]->setEnabled(ROMManager::SavestateExists(i)); - } - actSaveState[0]->setEnabled(true); - actLoadState[0]->setEnabled(true); - actUndoStateLoad->setEnabled(false); - - actPause->setEnabled(true); - actPause->setChecked(false); - actReset->setEnabled(true); - actStop->setEnabled(true); - actFrameStep->setEnabled(true); - - actDateTime->setEnabled(false); - actPowerManagement->setEnabled(true); - - actTitleManager->setEnabled(false); -} - -void MainWindow::onEmuStop() -{ - emuThread->emuPause(); - - for (int i = 0; i < 9; i++) - { - actSaveState[i]->setEnabled(false); - actLoadState[i]->setEnabled(false); - } - actUndoStateLoad->setEnabled(false); - - actPause->setEnabled(false); - actReset->setEnabled(false); - actStop->setEnabled(false); - actFrameStep->setEnabled(false); - - actDateTime->setEnabled(true); - actPowerManagement->setEnabled(false); - - actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); -} - -void MainWindow::onUpdateVideoSettings(bool glchange) -{ - if (glchange) - { - emuThread->emuPause(); - if (hasOGL) emuThread->deinitContext(); - - delete panel; - createScreenPanel(); - connect(emuThread, SIGNAL(windowUpdate()), panelWidget, SLOT(repaint())); - } - - videoSettingsDirty = true; - - if (glchange) - { - if (hasOGL) emuThread->initContext(); - emuThread->emuUnpause(); - } -} void emuStop() @@ -3297,8 +260,11 @@ void emuStop() MelonApplication::MelonApplication(int& argc, char** argv) : QApplication(argc, argv) { -#ifndef __APPLE__ +#if !defined(Q_OS_APPLE) setWindowIcon(QIcon(":/melon-icon")); + #if defined(Q_OS_UNIX) + setDesktopFileName(QString("net.kuribo64.melonDS")); + #endif #endif } @@ -3323,6 +289,11 @@ int main(int argc, char** argv) 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"); @@ -3330,10 +301,10 @@ 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); + Platform::Init(argc, argv); + CLI::CommandLineOptions* options = CLI::ManageArgs(melon); // http://stackoverflow.com/questions/14543333/joystick-wont-work-using-sdl @@ -3362,18 +333,18 @@ int main(int argc, char** argv) SDL_InitSubSystem(SDL_INIT_VIDEO); SDL_EnableScreenSaver(); SDL_DisableScreenSaver(); - Config::Load(); + if (!Config::Load()) QMessageBox::critical(NULL, "melonDS", "Unable to write to config.\nPlease check the write permissions of the folder you placed melonDS in."); -#define SANITIZE(var, min, max) { var = std::clamp(var, min, max); } +#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 + SANITIZE(Config::_3DRenderer, 0, renderer3D_Max); #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::AudioInterp, 0, 4); SANITIZE(Config::AudioVolume, 0, 256); SANITIZE(Config::MicInputType, 0, (int)micInputType_MAX); SANITIZE(Config::ScreenRotation, 0, (int)Frontend::screenRot_MAX); @@ -3391,6 +362,12 @@ int main(int argc, char** argv) camManager[0]->setXFlip(Config::Camera[0].XFlip); camManager[1]->setXFlip(Config::Camera[1].XFlip); + systemThemeName = new QString(QApplication::style()->objectName()); + + if (!Config::UITheme.empty()) + { + QApplication::setStyle(QString::fromStdString(Config::UITheme)); + } Input::JoystickID = Config::JoystickID; Input::OpenJoystick(); diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index 72ebfb19..5751f229 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -22,228 +22,18 @@ #include "glad/glad.h" #include -#include #include #include #include #include #include #include -#include #include #include -#include - -#include - +#include "Window.h" +#include "EmuThread.h" #include "FrontendUtil.h" -#include "duckstation/gl/context.h" - -namespace melonDS -{ -class NDS; -} - -class EmuThread : public QThread -{ - Q_OBJECT - void run() override; - -public: - explicit EmuThread(QObject* parent = nullptr); - - void changeWindowTitle(char* title); - - // to be called from the UI thread - void emuRun(); - void emuPause(); - void emuUnpause(); - void emuStop(); - void emuFrameStep(); - - bool emuIsRunning(); - bool emuIsActive(); - - void initContext(); - void deinitContext(); - - int FrontBuffer = 0; - QMutex FrontBufferLock; - - void updateScreenSettings(bool filter, const WindowInfo& windowInfo, int numScreens, int* screenKind, float* screenMatrix); - void RecreateConsole(); - std::unique_ptr NDS; // TODO: Proper encapsulation and synchronization -signals: - void windowUpdate(); - void windowTitleChange(QString title); - - void windowEmuStart(); - void windowEmuStop(); - void windowEmuPause(); - void windowEmuReset(); - void windowEmuFrameStep(); - - void windowLimitFPSChange(); - - void screenLayoutChange(); - - void windowFullscreenToggle(); - - void swapScreensToggle(); - void screenEmphasisToggle(); - - void syncVolumeLevel(); - -private: - std::unique_ptr CreateConsole(); - void drawScreenGL(); - void initOpenGL(); - void deinitOpenGL(); - - enum EmuStatusKind - { - emuStatus_Exit, - emuStatus_Running, - emuStatus_Paused, - emuStatus_FrameStep, - }; - std::atomic EmuStatus; - - EmuStatusKind PrevEmuStatus; - EmuStatusKind EmuRunning; - - 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; - - GL::Context* oglContext = nullptr; - GLuint screenVertexBuffer, screenVertexArray; - GLuint screenTexture; - GLuint screenShaderProgram[3]; - GLuint screenShaderTransformULoc, screenShaderScreenSizeULoc; - - QMutex screenSettingsLock; - WindowInfo windowInfo; - float screenMatrix[Frontend::MaxScreenTransforms][6]; - int screenKind[Frontend::MaxScreenTransforms]; - int numScreens; - bool filter; - - int lastScreenWidth = -1, lastScreenHeight = -1; -}; - - -class ScreenHandler -{ - Q_GADGET - -public: - ScreenHandler(QWidget* widget); - virtual ~ScreenHandler(); - QTimer* setupMouseTimer(); - void updateMouseTimer(); - QTimer* mouseTimer; - QSize screenGetMinSize(int factor); - -protected: - void screenSetupLayout(int w, int h); - - void screenOnMousePress(QMouseEvent* event); - void screenOnMouseRelease(QMouseEvent* event); - void screenOnMouseMove(QMouseEvent* event); - - void screenHandleTablet(QTabletEvent* event); - void screenHandleTouch(QTouchEvent* event); - - float screenMatrix[Frontend::MaxScreenTransforms][6]; - int screenKind[Frontend::MaxScreenTransforms]; - int numScreens; - - bool touching = false; - - void showCursor(); -}; - - -class ScreenPanelNative : public QWidget, public ScreenHandler -{ - Q_OBJECT - -public: - explicit ScreenPanelNative(QWidget* parent); - virtual ~ScreenPanelNative(); - -protected: - void paintEvent(QPaintEvent* event) override; - - void resizeEvent(QResizeEvent* event) override; - - void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - - void tabletEvent(QTabletEvent* event) override; - bool event(QEvent* event) override; -private slots: - void onScreenLayoutChanged(); - -private: - void setupScreenLayout(); - - QImage screen[2]; - QTransform screenTrans[Frontend::MaxScreenTransforms]; -}; - - -class ScreenPanelGL : public QWidget, public ScreenHandler -{ - Q_OBJECT - -public: - explicit ScreenPanelGL(QWidget* parent); - virtual ~ScreenPanelGL(); - - std::optional getWindowInfo(); - - bool createContext(); - - GL::Context* getContext() { return glContext.get(); } - - void transferLayout(EmuThread* thread); -protected: - - qreal devicePixelRatioFromScreen() const; - int scaledWindowWidth() const; - int scaledWindowHeight() const; - - QPaintEngine* paintEngine() const override; - - void resizeEvent(QResizeEvent* event) override; - - void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - - void tabletEvent(QTabletEvent* event) override; - bool event(QEvent* event) override; - -private slots: - void onScreenLayoutChanged(); - -private: - void setupScreenLayout(); - - std::unique_ptr glContext; -}; class MelonApplication : public QApplication { @@ -254,199 +44,6 @@ public: bool event(QEvent* event) override; }; -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget* parent = nullptr); - ~MainWindow(); - - bool hasOGL; - GL::Context* getOGLContext(); - - bool preloadROMs(QStringList file, QStringList gbafile, bool boot); - QStringList splitArchivePath(const QString& filename, bool useMemberSyntax); - - void onAppStateChanged(Qt::ApplicationState state); - -protected: - void resizeEvent(QResizeEvent* event) override; - void changeEvent(QEvent* event) override; - - void keyPressEvent(QKeyEvent* event) override; - void keyReleaseEvent(QKeyEvent* event) override; - - void dragEnterEvent(QDragEnterEvent* event) override; - void dropEvent(QDropEvent* event) override; - - void focusInEvent(QFocusEvent* event) override; - void focusOutEvent(QFocusEvent* event) override; - -signals: - void screenLayoutChange(); - -private slots: - void onOpenFile(); - void onClickRecentFile(); - void onClearRecentFiles(); - void onBootFirmware(); - void onInsertCart(); - void onEjectCart(); - void onInsertGBACart(); - void onInsertGBAAddon(); - void onEjectGBACart(); - void onSaveState(); - void onLoadState(); - void onUndoStateLoad(); - void onImportSavefile(); - void onQuit(); - - void onPause(bool checked); - void onReset(); - void onStop(); - void onFrameStep(); - void onOpenPowerManagement(); - void onOpenDateTime(); - void onEnableCheats(bool checked); - void onSetupCheats(); - void onCheatsDialogFinished(int res); - void onROMInfo(); - void onRAMInfo(); - void onOpenTitleManager(); - void onMPNewInstance(); - - void onOpenEmuSettings(); - void onEmuSettingsDialogFinished(int res); - void onOpenInputConfig(); - void onInputConfigFinished(int res); - void onOpenVideoSettings(); - void onOpenCameraSettings(); - void onCameraSettingsFinished(int res); - void onOpenAudioSettings(); - void onUpdateAudioSettings(); - void onAudioSettingsFinished(int res); - void onOpenMPSettings(); - void onMPSettingsFinished(int res); - void onOpenWifiSettings(); - void onWifiSettingsFinished(int res); - void onOpenFirmwareSettings(); - void onFirmwareSettingsFinished(int res); - void onOpenPathSettings(); - void onPathSettingsFinished(int res); - void onOpenInterfaceSettings(); - void onInterfaceSettingsFinished(int res); - void onUpdateMouseTimer(); - void onChangeSavestateSRAMReloc(bool checked); - void onChangeScreenSize(); - void onChangeScreenRotation(QAction* act); - void onChangeScreenGap(QAction* act); - void onChangeScreenLayout(QAction* act); - void onChangeScreenSwap(bool checked); - void onChangeScreenSizing(QAction* act); - void onChangeScreenAspect(QAction* act); - void onChangeIntegerScaling(bool checked); - void onChangeScreenFiltering(bool checked); - void onChangeShowOSD(bool checked); - void onChangeLimitFramerate(bool checked); - void onChangeAudioSync(bool checked); - - void onTitleUpdate(QString title); - - void onEmuStart(); - void onEmuStop(); - - void onUpdateVideoSettings(bool glchange); - - void onFullscreenToggled(); - void onScreenEmphasisToggled(); - -private: - virtual void closeEvent(QCloseEvent* event) override; - - QStringList currentROM; - QStringList currentGBAROM; - QList recentFileList; - QMenu *recentMenu; - void updateRecentFilesMenu(); - - bool verifySetup(); - QString pickFileFromArchive(QString archiveFileName); - QStringList pickROM(bool gba); - void updateCartInserted(bool gba); - - void createScreenPanel(); - - bool pausedManually = false; - - int oldW, oldH; - bool oldMax; - -public: - ScreenHandler* panel; - QWidget* panelWidget; - - QAction* actOpenROM; - QAction* actBootFirmware; - QAction* actCurrentCart; - QAction* actInsertCart; - QAction* actEjectCart; - QAction* actCurrentGBACart; - QAction* actInsertGBACart; - QAction* actInsertGBAAddon[1]; - QAction* actEjectGBACart; - QAction* actImportSavefile; - QAction* actSaveState[9]; - QAction* actLoadState[9]; - QAction* actUndoStateLoad; - QAction* actQuit; - - QAction* actPause; - QAction* actReset; - QAction* actStop; - QAction* actFrameStep; - QAction* actPowerManagement; - QAction* actDateTime; - QAction* actEnableCheats; - QAction* actSetupCheats; - QAction* actROMInfo; - QAction* actRAMInfo; - QAction* actTitleManager; - QAction* actMPNewInstance; - - QAction* actEmuSettings; -#ifdef __APPLE__ - QAction* actPreferences; -#endif - QAction* actInputConfig; - QAction* actVideoSettings; - QAction* actCameraSettings; - QAction* actAudioSettings; - QAction* actMPSettings; - QAction* actWifiSettings; - QAction* actFirmwareSettings; - QAction* actPathSettings; - QAction* actInterfaceSettings; - QAction* actSavestateSRAMReloc; - QAction* actScreenSize[4]; - QActionGroup* grpScreenRotation; - QAction* actScreenRotation[Frontend::screenRot_MAX]; - QActionGroup* grpScreenGap; - QAction* actScreenGap[6]; - QActionGroup* grpScreenLayout; - QAction* actScreenLayout[Frontend::screenLayout_MAX]; - QAction* actScreenSwap; - QActionGroup* grpScreenSizing; - QAction* actScreenSizing[Frontend::screenSizing_MAX]; - QAction* actIntegerScaling; - QActionGroup* grpScreenAspectTop; - QAction** actScreenAspectTop; - QActionGroup* grpScreenAspectBot; - QAction** actScreenAspectBot; - QAction* actScreenFiltering; - QAction* actShowOSD; - QAction* actLimitFramerate; - QAction* actAudioSync; -}; +extern QString* systemThemeName; #endif // MAIN_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/version.h b/src/version.h.in similarity index 75% rename from src/version.h rename to src/version.h.in index 131c610d..a3db45b3 100644 --- a/src/version.h +++ b/src/version.h.in @@ -19,7 +19,11 @@ #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 #endif // VERSION_H diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 00000000..fafa0868 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,26 @@ +{ + "dependencies": [ + "sdl2", + { + "name": "libarchive", + "default-features": false, + "features": ["bzip2", "crypto", "lz4", "zstd"] + }, + "zstd", + { + "name": "qtbase", + "default-features": false, + "features": ["gui", "png", "thread", "widgets", "opengl", "zstd"] + }, + { + "name": "qtbase", + "host": true, + "default-features": false + }, + { + "name": "qtmultimedia", + "default-features": false + }, + "qtsvg" + ] +}