Merge remote-tracking branch 'upstream/master' into slope-gap-fix

This commit is contained in:
Jaklyy 2024-05-26 09:44:01 -04:00
commit a35d62275c
308 changed files with 38587 additions and 9631 deletions

View File

@ -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

View File

@ -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

85
.github/workflows/build-macos.yml vendored Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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}}

View File

@ -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)

88
CMakePresets.json Normal file
View File

@ -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" }
]
}
]
}

View File

@ -6,10 +6,9 @@
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-%23ff554d.svg"></a>
<a href="https://kiwiirc.com/client/irc.badnik.net/?nick=IRC-Source_?#melonds" alt="IRC channel: #melonds"><img src="https://img.shields.io/badge/IRC%20chat-%23melonds-%23dd2e44.svg"></a>
<br>
<a href="https://github.com/melonDS-emu/melonDS/actions?query=workflow%3A%22CMake+Build+%28Windows+x86-64%29%22+event%3Apush"><img src="https://img.shields.io/github/actions/workflow/status/melonDS-emu/melonDS/build-windows.yml?label=Windows%20x86-64&logo=GitHub&branch=master"></img></a>
<a href="https://github.com/melonDS-emu/melonDS/actions?query=workflow%3A%22CMake+Build+%28Ubuntu+x86-64%29%22+event%3Apush"><img src="https://img.shields.io/github/actions/workflow/status/melonDS-emu/melonDS/build-ubuntu.yml?label=Linux%20x86-64&logo=GitHub"></img></a>
<a href="https://github.com/melonDS-emu/melonDS/actions?query=workflow%3A%22CMake+Build+%28Ubuntu+aarch64%29%22+event%3Apush"><img src="https://img.shields.io/github/actions/workflow/status/melonDS-emu/melonDS/build-ubuntu-aarch64.yml?label=Linux%20ARM64&logo=GitHub"></img></a>
<a href="https://github.com/melonDS-emu/melonDS/actions/workflows/build-macos-universal.yml?query=event%3Apush"><img src="https://img.shields.io/github/actions/workflow/status/melonDS-emu/melonDS/build-macos-universal.yml?label=macOS%20Universal&logo=GitHub"></img></a>
<a href="https://github.com/melonDS-emu/melonDS/actions/workflows/build-windows.yml?query=event%3Apush"><img src="https://github.com/melonDS-emu/melonDS/actions/workflows/build-windows.yml/badge.svg" /></a>
<a href="https://github.com/melonDS-emu/melonDS/actions/workflows/build-ubuntu.yml?query=event%3Apush"><img src="https://github.com/melonDS-emu/melonDS/actions/workflows/build-ubuntu.yml/badge.svg" /></a>
<a href="https://github.com/melonDS-emu/melonDS/actions/workflows/build-macos.yml?query=event%3Apush"><img src="https://github.com/melonDS-emu/melonDS/actions/workflows/build-macos.yml/badge.svg" /></a>
</p>
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

View File

@ -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")

View File

@ -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}")

View File

@ -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})

19
cmake/SetupCCache.cmake Normal file
View File

@ -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}")

View File

@ -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)

View File

@ -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)

View File

@ -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

Binary file not shown.

Binary file not shown.

View File

@ -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;

View File

@ -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

View File

@ -17,6 +17,7 @@
*/
#include <stdio.h>
#include <assert.h>
#include <algorithm>
#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<GDBArgs> 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<GDBArgs> 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<GDBArgs> 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

View File

@ -20,9 +20,11 @@
#define ARM_H
#include <algorithm>
#include <optional>
#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<GDBArgs> 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<GDBArgs> 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<GDBArgs> gdb, bool jit);
void FillPipeline() override;

View File

@ -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;

View File

@ -19,10 +19,15 @@
#ifndef ARMJIT_H
#define ARMJIT_H
#include <algorithm>
#include <optional>
#include <memory>
#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<JITArgs> 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 <u32 num, int region>
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 <u32, int>
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<u32> 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<u32, JitBlock*> JitBlocks9 {};
std::unordered_map<u32, JitBlock*> 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<JITArgs>) 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 <u32, int>
void CheckAndInvalidate(u32 addr) noexcept {}
ARMJIT_Memory Memory;
};
}
#endif // JIT_ENABLED
#endif // ARMJIT_H
#endif

View File

@ -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

View File

@ -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

View File

@ -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));
{

View File

@ -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

View File

@ -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++)
{

View File

@ -473,8 +473,10 @@ void ARMJIT_Memory::RemapDTCM(u32 newBase, u32 newSize) noexcept
void ARMJIT_Memory::RemapNWRAM(int num) noexcept
{
auto* dsi = dynamic_cast<DSi*>(&NDS);
assert(dsi != nullptr);
if (NDS.ConsoleType == 0)
return;
auto* dsi = static_cast<DSi*>(&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<DSi&>(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<DSi&>(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<DSi&>(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<DSi&>(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<DSi&>(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<DSi&>(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])

View File

@ -20,33 +20,33 @@
#define ARMJIT_MEMORY
#include "types.h"
#include "TinyVector.h"
#include "ARM.h"
#include "MemConstants.h"
#if defined(__SWITCH__)
#include <switch.h>
#elif defined(_WIN32)
#ifdef JIT_ENABLED
# include "TinyVector.h"
# include "ARM.h"
# if defined(__SWITCH__)
# include <switch.h>
# elif defined(_WIN32)
#include <windows.h>
# else
# include <sys/mman.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <unistd.h>
# include <signal.h>
# endif
#else
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#endif
#ifndef JIT_ENABLED
#include <array>
#include "NDS.h"
# include <array>
#endif
namespace melonDS
{
#ifdef JIT_ENABLED
namespace Platform { struct DynamicLibrary; }
class Compiler;
class ARMJIT;
#endif
constexpr u32 RoundUp(u32 size) noexcept
{

View File

@ -99,7 +99,7 @@ public:
LiteralsLoaded &= ~(1 << reg);
}
bool IsLiteral(int reg)
bool IsLiteral(int reg) const
{
return LiteralsLoaded & (1 << reg);
}

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}

152
src/Args.h Normal file
View File

@ -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 <array>
#include <optional>
#include <memory>
#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<size_t N>
constexpr std::array<u8, N> BrokenBIOS = []() constexpr {
std::array<u8, N> 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<u8, ARM9BIOSSize>;
using ARM7BIOSImage = std::array<u8, ARM7BIOSSize>;
using DSiBIOSImage = std::array<u8, DSiBIOSSize>;
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<NDSCart::CartCommon> 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<GBACart::CartCommon> GBAROM = nullptr;
/// NDS ARM9 BIOS to install.
/// Defaults to FreeBIOS, which is not compatible with DSi mode.
std::unique_ptr<ARM9BIOSImage> ARM9BIOS = std::make_unique<ARM9BIOSImage>(bios_arm9_bin);
/// NDS ARM7 BIOS to install.
/// Defaults to FreeBIOS, which is not compatible with DSi mode.
std::unique_ptr<ARM7BIOSImage> ARM7BIOS = std::make_unique<ARM7BIOSImage>(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<JITArgs> 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<GDBArgs> 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<melonDS::Renderer3D> Renderer3D = std::make_unique<SoftRenderer>();
};
/// 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<DSiBIOSImage> ARM9iBIOS = std::make_unique<DSiBIOSImage>(BrokenBIOS<DSiBIOSSize>);
std::unique_ptr<DSiBIOSImage> ARM7iBIOS = std::make_unique<DSiBIOSImage>(BrokenBIOS<DSiBIOSSize>);
/// 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<FATStorage> DSiSDCard;
bool FullBIOSBoot = false;
};
}
#endif //MELONDS_ARGS_H

View File

@ -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 "$<$<CONFIG:DEBUG>:-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 "$<$<COMPILE_LANGUAGE:CXX>:-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)

View File

@ -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<<size); // add 1 left shifted by size to start to determine end point
// dont need to bounds check the end point because the force alignment inherently prevents it from breaking
u8 usermask = 0;
u8 privmask = 0;
@ -239,7 +243,7 @@ void ARMv5::UpdatePURegion(u32 n)
"PU region %d: %08X-%08X, user=%02X priv=%02X, %08X/%08X\n",
n,
start << 12,
end << 12,
(end << 12) - 1,
usermask,
privmask,
PU_DataRW,
@ -579,12 +583,12 @@ void ARMv5::CP15Write(u32 id, u32 val)
std::snprintf(log_output,
sizeof(log_output),
"PU: region %d = %08X : %s, %08X-%08X\n",
"PU: region %d = %08X : %s, start: %08X size: %02X\n",
(id >> 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]);

View File

@ -21,6 +21,7 @@
#include "DSi.h"
#include "DMA.h"
#include "GPU.h"
#include "GPU3D.h"
#include "DMA_Timings.h"
#include "Platform.h"

View File

@ -17,8 +17,10 @@
*/
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <inttypes.h>
#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<NDSCart::CartCommon>&& 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<NDSCart::CartCommon> 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<u8>(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:

View File

@ -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<DSi_NAND::NANDImage> NANDImage;
std::array<u8, DSiBIOSSize> ARM9iBIOS;
std::array<u8, DSiBIOSSize> 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<NDSCart::CartCommon>&& cart) override;
std::unique_ptr<NDSCart::CartCommon> 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<FATStorage>&& 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);
};

View File

@ -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;

View File

@ -54,7 +54,7 @@ public:
void Reset();
void DoSavestate(Savestate* file);
u32 ReadCnt();
u32 ReadCnt() const;
void WriteCnt(u32 val);
void WriteBlkCnt(u32 val);

View File

@ -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?

View File

@ -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);

View File

@ -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;
}

View File

@ -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();

View File

@ -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);

View File

@ -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;
};

View File

@ -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];

View File

@ -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;

View File

@ -22,6 +22,7 @@
#include "DSi_NDMA.h"
#include "GPU.h"
#include "DSi_AES.h"
#include "GPU3D.h"
namespace melonDS
{

View File

@ -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)
{

View File

@ -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

View File

@ -18,6 +18,7 @@
#include <stdio.h>
#include <string.h>
#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<FATStorage>&& 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_MMCStorage>(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_MMCStorage>(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_NWifi>(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<DSi_MMCStorage*>(Ports[0].get())->GetSDCard();
}
const FATStorage* DSi_SDHost::GetSDCard() const noexcept
{
if (Num != 0) return nullptr;
return static_cast<const DSi_MMCStorage*>(Ports[0].get())->GetSDCard();
}
DSi_NAND::NANDImage* DSi_SDHost::GetNAND() noexcept
{
if (Num != 0) return nullptr;
return static_cast<DSi_MMCStorage*>(Ports[1].get())->GetNAND();
}
const DSi_NAND::NANDImage* DSi_SDHost::GetNAND() const noexcept
{
if (Num != 0) return nullptr;
return static_cast<const DSi_MMCStorage*>(Ports[1].get())->GetNAND();
}
void DSi_SDHost::SetSDCard(FATStorage&& sdcard) noexcept
{
if (Num != 0) return;
static_cast<DSi_MMCStorage*>(Ports[0].get())->SetSDCard(std::move(sdcard));
}
void DSi_SDHost::SetSDCard(std::optional<FATStorage>&& 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_MMCStorage>(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<DSi_MMCStorage*>(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<DSi_MMCStorage*>(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<DSi_NAND::NANDImage>(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<FATStorage>(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<DSi_NAND::NANDImage>(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<DSi_NAND::NANDImage>(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<DSi_NAND::NANDImage>(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<DSi_NAND::NANDImage>(&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<DSi_NAND::NANDImage>(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<FATStorage>(&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<DSi_NAND::NANDImage>(&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<FATStorage>(&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<FATStorage>(&Storage))
{
SD->WriteSectors((u32)(addr >> 9), 1, data);
sd->WriteSectors((u32)(addr >> 9), 1, data);
}
else if (NAND)
else if (auto* nand = get_if<DSi_NAND::NANDImage>(&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());
}
}
}

View File

@ -20,28 +20,30 @@
#define DSI_SD_H
#include <cstring>
#include <variant>
#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<std::monostate, FATStorage, DSi_NAND::NANDImage>;
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<FATStorage>&& 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<FATStorage>&& 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<std::unique_ptr<DSi_SDDevice>, 2> Ports {};
u32 CurFIFO; // FIFO accessible for read/write
FIFO<u16, 0x100> 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<FATStorage>(&Storage); }
[[nodiscard]] const FATStorage* GetSDCard() const noexcept { return std::get_if<FATStorage>(&Storage); }
[[nodiscard]] DSi_NAND::NANDImage* GetNAND() noexcept { return std::get_if<DSi_NAND::NANDImage>(&Storage); }
[[nodiscard]] const DSi_NAND::NANDImage* GetNAND() const noexcept { return std::get_if<DSi_NAND::NANDImage>(&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<FATStorage>&& 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];

View File

@ -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);

View File

@ -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;

View File

@ -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<string>& 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<string>& 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>(FileMode::ReadWrite | FileMode::Preserve));
if (!FF_File)
File = Platform::OpenLocalFile(filename, static_cast<FileMode>(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;
}

View File

@ -22,41 +22,63 @@
#include <stdio.h>
#include <string>
#include <map>
#include <optional>
#include <filesystem>
#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<std::string> 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<std::string>& 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<std::string> 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<std::string>& sourcedir);
bool Save();
typedef struct

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -28,10 +28,13 @@
#ifndef FREEBIOS_H
#define FREEBIOS_H
#include <array>
#include "MemConstants.h"
namespace melonDS
{
extern unsigned char bios_arm7_bin[16384];
extern unsigned char bios_arm9_bin[4096];
extern std::array<u8, ARM7BIOSSize> bios_arm7_bin;
extern std::array<u8, ARM9BIOSSize> bios_arm9_bin;
}
#endif // FREEBIOS_H

File diff suppressed because it is too large Load Diff

View File

@ -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 <memory>
#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<CartCommon>&& 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<CartCommon> 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<CartCommon> 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 <memory>
#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<u8[]>&& rom, u32 len, std::unique_ptr<u8[]>&& 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<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 {};
std::unique_ptr<u8[]> 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<u8[]>&& rom, u32 len, std::unique_ptr<u8[]>&& 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<CartCommon>&& 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<CartCommon>&& 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<CartCommon> 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<CartCommon> 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<CartCommon> ParseROM(const u8* romdata, u32 romlen);
std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen);
std::unique_ptr<CartCommon> 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<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, std::unique_ptr<u8[]>&& sramdata, u32 sramlen);
}
#endif // GBACART_H

View File

@ -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>&& renderer3d, std::uniqu
NDS(nds),
GPU2D_A(0, *this),
GPU2D_B(1, *this),
GPU3D(nds, renderer3d ? std::move(renderer3d) : std::make_unique<SoftRenderer>(*this)),
GPU3D(nds, renderer3d ? std::move(renderer3d) : std::make_unique<SoftRenderer>()),
GPU2D_Renderer(renderer2d ? std::move(renderer2d) : std::make_unique<GPU2D::SoftRenderer>(*this))
{
NDS.RegisterEventFunc(Event_LCD, LCD_StartHBlank, MemberEventFunc(GPU, StartHBlank));
@ -75,7 +75,7 @@ GPU::GPU(melonDS::NDS& nds, std::unique_ptr<Renderer3D>&& 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<Renderer3D>&& renderer) noexcept
{
if (renderer == nullptr)
GPU3D.SetCurrentRenderer(std::make_unique<SoftRenderer>(*this));
GPU3D.SetCurrentRenderer(std::make_unique<SoftRenderer>());
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 <u32 Size, u32 MappingGranularity>
NonStupidBitField<Size/VRAMDirtyGranularity> VRAMTrackingSet<Size, MappingGranularity>::DeriveState(u32* currentMappings, GPU& gpu)
NonStupidBitField<Size/VRAMDirtyGranularity> VRAMTrackingSet<Size, MappingGranularity>::DeriveState(const u32* currentMappings, GPU& gpu)
{
NonStupidBitField<Size/VRAMDirtyGranularity> result;
u16 banksToBeZeroed = 0;
@ -1126,12 +1132,12 @@ NonStupidBitField<Size/VRAMDirtyGranularity> VRAMTrackingSet<Size, MappingGranul
return result;
}
template NonStupidBitField<32*1024/VRAMDirtyGranularity> 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);

View File

@ -49,7 +49,7 @@ struct VRAMTrackingSet
Mapping[i] = 0x8000;
}
}
NonStupidBitField<Size/VRAMDirtyGranularity> DeriveState(u32* currentMappings, GPU& gpu);
NonStupidBitField<Size/VRAMDirtyGranularity> 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<typename T>
T ReadVRAM_ABGExtPal(u32 addr) const noexcept
{

View File

@ -20,6 +20,7 @@
#include <string.h>
#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)
{

View File

@ -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;

View File

@ -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;

View File

@ -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<u32 bgmode> void DrawScanlineBGMode(u32 line);
void DrawScanlineBGMode6(u32 line);

View File

@ -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<Renderer3D>&& renderer) noexcept :
NDS(nds),
CurrentRenderer(renderer ? std::move(renderer) : std::make_unique<SoftRenderer>(nds.GPU))
CurrentRenderer(renderer ? std::move(renderer) : std::make_unique<SoftRenderer>())
{
}
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<Renderer3D>&& 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<SoftRenderer*>(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)

View File

@ -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<Renderer3D>&& renderer) noexcept { CurrentRenderer = std::move(renderer); }
void SetCurrentRenderer(std::unique_ptr<Renderer3D>&& 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);
};

1137
src/GPU3D_Compute.cpp Normal file

File diff suppressed because it is too large Load Diff

242
src/GPU3D_Compute.h Normal file
View File

@ -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 <memory>
#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<ComputeRenderer> 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<SetupIndices> 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<const char*>& defines);
};
}
#endif

1665
src/GPU3D_Compute_shaders.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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> GLRenderer::New(melonDS::GPU& gpu) noexcept
std::unique_ptr<GLRenderer> GLRenderer::New() noexcept
{
assert(glEnable != nullptr);
@ -117,7 +104,7 @@ std::unique_ptr<GLRenderer> GLRenderer::New(melonDS::GPU& gpu) noexcept
// Will be returned if the initialization succeeds,
// or cleaned up via RAII if it fails.
std::unique_ptr<GLRenderer> result = std::unique_ptr<GLRenderer>(new GLRenderer(std::move(*compositor), gpu));
std::unique_ptr<GLRenderer> result = std::unique_ptr<GLRenderer>(new GLRenderer(std::move(*compositor)));
compositor = std::nullopt;
glEnable(GL_DEPTH_TEST);
@ -126,21 +113,17 @@ std::unique_ptr<GLRenderer> 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> 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> 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<<ScaleFactor, 256<<ScaleFactor, h<<ScaleFactor);
GLboolean fogenable = (GPU.GPU3D.RenderDispCnt & (1<<7)) ? GL_TRUE : GL_FALSE;
GLboolean fogenable = (gpu3d.RenderDispCnt & (1<<7)) ? GL_TRUE : GL_FALSE;
// TODO: proper 'equal' depth test!
// (has margin of +-0x200 in Z-buffer mode, +-0xFF in W-buffer mode)
@ -865,7 +827,7 @@ void GLRenderer::RenderSceneChunk(int y, int h)
glEnable(GL_BLEND);
glBlendEquationSeparate(GL_FUNC_ADD, GL_MAX);
if (GPU.GPU3D.RenderDispCnt & (1<<3))
if (gpu3d.RenderDispCnt & (1<<3))
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
else
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ONE);
@ -877,7 +839,7 @@ void GLRenderer::RenderSceneChunk(int y, int h)
// pass 2: if needed, render translucent pixels that are against background pixels
// when background alpha is zero, those need to be rendered with blending disabled
if ((GPU.GPU3D.RenderClearAttr1 & 0x001F0000) == 0)
if ((gpu3d.RenderClearAttr1 & 0x001F0000) == 0)
{
glDisable(GL_BLEND);
@ -941,7 +903,7 @@ void GLRenderer::RenderSceneChunk(int y, int h)
if (rp->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);
}
}

View File

@ -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<GLRenderer> New(melonDS::GPU& gpu) noexcept;
static std::unique_ptr<GLRenderer> 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

View File

@ -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<u8>(vramaddr);
u8 pixel = ReadVRAM_Texture<u8>(vramaddr, gpu);
texpal <<= 4;
*color = ReadVRAM_TexPal<u16>(texpal + ((pixel&0x1F)<<1));
*color = ReadVRAM_TexPal<u16>(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<u8>(vramaddr);
u8 pixel = ReadVRAM_Texture<u8>(vramaddr, gpu);
pixel >>= ((s & 0x3) << 1);
pixel &= 0x3;
texpal <<= 3;
*color = ReadVRAM_TexPal<u16>(texpal + (pixel<<1));
*color = ReadVRAM_TexPal<u16>(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<u8>(vramaddr);
u8 pixel = ReadVRAM_Texture<u8>(vramaddr, gpu);
if (s & 0x1) pixel >>= 4;
else pixel &= 0xF;
texpal <<= 4;
*color = ReadVRAM_TexPal<u16>(texpal + (pixel<<1));
*color = ReadVRAM_TexPal<u16>(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<u8>(vramaddr);
u8 pixel = ReadVRAM_Texture<u8>(vramaddr, gpu);
texpal <<= 4;
*color = ReadVRAM_TexPal<u16>(texpal + (pixel<<1));
*color = ReadVRAM_TexPal<u16>(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<u8>(vramaddr);
u8 val = ReadVRAM_Texture<u8>(vramaddr, gpu);
val >>= (2 * (s & 0x3));
u16 palinfo = ReadVRAM_Texture<u16>(slot1addr);
u16 palinfo = ReadVRAM_Texture<u16>(slot1addr, gpu);
u32 paloffset = (palinfo & 0x3FFF) << 2;
texpal <<= 4;
switch (val & 0x3)
{
case 0:
*color = ReadVRAM_TexPal<u16>(texpal + paloffset);
*color = ReadVRAM_TexPal<u16>(texpal + paloffset, gpu);
*alpha = 31;
break;
case 1:
*color = ReadVRAM_TexPal<u16>(texpal + paloffset + 2);
*color = ReadVRAM_TexPal<u16>(texpal + paloffset + 2, gpu);
*alpha = 31;
break;
case 2:
if ((palinfo >> 14) == 1)
{
u16 color0 = ReadVRAM_TexPal<u16>(texpal + paloffset);
u16 color1 = ReadVRAM_TexPal<u16>(texpal + paloffset + 2);
u16 color0 = ReadVRAM_TexPal<u16>(texpal + paloffset, gpu);
u16 color1 = ReadVRAM_TexPal<u16>(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<u16>(texpal + paloffset);
u16 color1 = ReadVRAM_TexPal<u16>(texpal + paloffset + 2);
u16 color0 = ReadVRAM_TexPal<u16>(texpal + paloffset, gpu);
u16 color1 = ReadVRAM_TexPal<u16>(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<u16>(texpal + paloffset + 4);
*color = ReadVRAM_TexPal<u16>(texpal + paloffset + 4, gpu);
*alpha = 31;
break;
case 3:
if ((palinfo >> 14) == 2)
{
*color = ReadVRAM_TexPal<u16>(texpal + paloffset + 6);
*color = ReadVRAM_TexPal<u16>(texpal + paloffset + 6, gpu);
*alpha = 31;
}
else if ((palinfo >> 14) == 3)
{
u16 color0 = ReadVRAM_TexPal<u16>(texpal + paloffset);
u16 color1 = ReadVRAM_TexPal<u16>(texpal + paloffset + 2);
u16 color0 = ReadVRAM_TexPal<u16>(texpal + paloffset, gpu);
u16 color1 = ReadVRAM_TexPal<u16>(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<u8>(vramaddr);
u8 pixel = ReadVRAM_Texture<u8>(vramaddr, gpu);
texpal <<= 4;
*color = ReadVRAM_TexPal<u16>(texpal + ((pixel&0x7)<<1));
*color = ReadVRAM_TexPal<u16>(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<u16>(vramaddr);
*color = ReadVRAM_Texture<u16>(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<false>(&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<u16>(0x40000 + (yoff << 9) + (xoff << 1));
u16 val3 = ReadVRAM_Texture<u16>(0x60000 + (yoff << 9) + (xoff << 1));
u16 val2 = ReadVRAM_Texture<u16>(0x40000 + (yoff << 9) + (xoff << 1), gpu);
u16 val3 = ReadVRAM_Texture<u16>(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);
}

View File

@ -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 <typename T>
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 <typename T>
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;
};
}

269
src/GPU3D_Texcache.cpp Normal file
View File

@ -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 <int outputFmt>
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<outputFmt_RGB6A5>(u32 width, u32 height, u32* output, u8* texData);
template <int outputFmt>
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<outputFmt_RGB6A5>(u32, u32, u32*, u8*, u8*, u16*);
template <int outputFmt, int X, int Y>
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<outputFmt_RGB6A5, 5, 3>(u32, u32, u32*, u8*, u16*);
template void ConvertAXIYTexture<outputFmt_RGB6A5, 3, 5>(u32, u32, u32*, u8*, u16*);
template <int outputFmt, int colorBits>
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<outputFmt_RGB6A5, 2>(u32, u32, u32*, u8*, u16*, bool);
template void ConvertNColorsTexture<outputFmt_RGB6A5, 4>(u32, u32, u32*, u8*, u16*, bool);
template void ConvertNColorsTexture<outputFmt_RGB6A5, 8>(u32, u32, u32*, u8*, u16*, bool);
}

310
src/GPU3D_Texcache.h Normal file
View File

@ -0,0 +1,310 @@
#ifndef GPU3D_TEXCACHE
#define GPU3D_TEXCACHE
#include "types.h"
#include "GPU.h"
#include <assert.h>
#include <unordered_map>
#include <vector>
#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 <int outputFmt>
void ConvertBitmapTexture(u32 width, u32 height, u32* output, u8* texData);
template <int outputFmt>
void ConvertCompressedTexture(u32 width, u32 height, u32* output, u8* texData, u8* texAuxData, u16* palData);
template <int outputFmt, int X, int Y>
void ConvertAXIYTexture(u32 width, u32 height, u32* output, u8* texData, u16* palData);
template <int outputFmt, int colorBits>
void ConvertNColorsTexture(u32 width, u32 height, u32* output, u8* texData, u16* palData, bool color0Transparent);
template <typename TexLoaderT, typename TexHandleT>
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<outputFmt_RGB6A5>(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<outputFmt_RGB6A5>(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<outputFmt_RGB6A5, 3, 5>(width, height, DecodingBuffer, texData, palData); break;
case 6: ConvertAXIYTexture<outputFmt_RGB6A5, 5, 3>(width, height, DecodingBuffer, texData, palData); break;
case 2: ConvertNColorsTexture<outputFmt_RGB6A5, 2>(width, height, DecodingBuffer, texData, palData, color0Transparent); break;
case 3: ConvertNColorsTexture<outputFmt_RGB6A5, 4>(width, height, DecodingBuffer, texData, palData, color0Transparent); break;
case 4: ConvertNColorsTexture<outputFmt_RGB6A5, 8>(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<u32>((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<u64, TexCacheEntry> Cache;
TexLoaderT TexLoader;
std::vector<TexArrayEntry> FreeTextures[8][8];
std::vector<TexHandleT> TexArrays[8][8];
u32 DecodingBuffer[1024*1024];
};
}
#endif

View File

@ -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);
}
}

View File

@ -0,0 +1,25 @@
#ifndef GPU3D_TEXCACHEOPENGL
#define GPU3D_TEXCACHEOPENGL
#include "GPU3D_Texcache.h"
#include "OpenGLSupport.h"
namespace melonDS
{
template <typename, typename>
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<TexcacheOpenGLLoader, GLuint>;
}
#endif

View File

@ -36,32 +36,27 @@ using namespace OpenGL;
std::optional<GLCompositor> GLCompositor::New() noexcept
{
assert(glBindAttribLocation != nullptr);
GLuint CompShader {};
std::array<GLuint, 3> 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<GLuint, 3> 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);

View File

@ -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<GLuint, 3> CompShader) noexcept;
GLCompositor(GLuint CompShader) noexcept;
int Scale = 0;
int ScreenH = 0, ScreenW = 0;
std::array<GLuint, 3> CompShader {};
GLuint CompShader {};
GLuint CompScaleLoc = 0;
GLuint Comp3DXPosLoc = 0;

View File

@ -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<u32> Data;

View File

@ -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

View File

@ -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<ARM9BIOSImage>(bios_arm9_bin),
std::make_unique<ARM7BIOSImage>(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<JITArgs> 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<NDSCart::CartCommon>&& 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<u8, ARM7BIOSSize>& 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<u8, ARM9BIOSSize>& 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]);
}
}
}

133
src/NDS.h
View File

@ -21,7 +21,7 @@
#include <memory>
#include <string>
#include <memory>
#include <optional>
#include <functional>
#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<u8, ARM9BIOSSize> ARM9BIOS;
std::array<u8, ARM7BIOSSize> 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<u8, ARM9BIOSSize>& GetARM9BIOS() const noexcept { return ARM9BIOS; }
void SetARM9BIOS(const std::array<u8, ARM9BIOSSize>& bios) noexcept;
virtual bool NeedsDirectBoot();
[[nodiscard]] const std::array<u8, ARM7BIOSSize>& GetARM7BIOS() const noexcept { return ARM7BIOS; }
void SetARM7BIOS(const std::array<u8, ARM7BIOSSize>& 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<NDSCart::CartCommon>&& cart);
[[nodiscard]] bool CartInserted() const noexcept { return NDSCartSlot.GetCart() != nullptr; }
virtual std::unique_ptr<NDSCart::CartCommon> 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<Renderer3D>&& 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<GBACart::CartCommon>&& 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<GBACart::CartCommon> 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<JITArgs> args) noexcept;
#else
[[nodiscard]] bool IsJITEnabled() const noexcept { return false; }
void SetJITArgs(std::optional<JITArgs> args) noexcept {}
#endif
private:
void InitTimings();
u32 SchedListMask;
@ -423,12 +489,12 @@ private:
FIFO<u32, 16> IPCFIFO9; // FIFO in which the ARM9 writes
FIFO<u32, 16> 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 <bool EnableJIT>
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) {}
};

View File

@ -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<u8[]>&& 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<DSi&>(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<const NDSBanner*>(ROM + header.BannerOffset);
return reinterpret_cast<const NDSBanner*>(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<u8[]>&& 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<u8[]>&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, std::unique_ptr<u8[]>&& 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<u8[]>(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<u8[]>(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<u8[]>&& sram, u32 sramlen) :
CartRetailNAND(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen)
{
}
CartRetailNAND::~CartRetailNAND()
CartRetailNAND::CartRetailNAND(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& 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<u8[]>&& 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<u8[]>&& rom,
u32 len,
u32 chipid,
u32 irversion,
bool badDSiDump,
ROMListEntry romparams,
std::unique_ptr<u8[]>&& 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<u8[]>&& 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<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& 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<FATStorage>&& sdcard) :
CartSD(CopyToUnique(rom, len), len, chipid, romparams, std::move(sdcard))
{}
CartSD::CartSD(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& 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<FATStorage>&& sdcard) :
CartSD(rom, len, chipid, romparams, std::move(sdcard))
{}
CartHomebrew::CartHomebrew(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& 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<CartCommon>&& 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<CartCommon> ParseROM(const u8* romdata, u32 romlen)
std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen, std::optional<NDSCartArgs>&& args)
{
return ParseROM(CopyToUnique(romdata, romlen), romlen, std::move(args));
}
std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, std::optional<NDSCartArgs>&& args)
{
if (romdata == nullptr)
{
@ -1607,28 +1584,10 @@ std::unique_ptr<CartCommon> 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<CartCommon> 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<CartCommon> ParseROM(const u8* romdata, u32 romlen)
}
std::unique_ptr<CartCommon> cart;
std::unique_ptr<u8[]> sram = args ? std::move(args->SRAM) : nullptr;
u32 sramlen = args ? args->SRAMLength : 0;
if (homebrew)
cart = std::make_unique<CartHomebrew>(cartrom, cartromsize, cartid, romparams);
{
std::optional<FATStorage> sdcard = args && args->SDCard ? std::make_optional<FATStorage>(std::move(*args->SDCard)) : std::nullopt;
cart = std::make_unique<CartHomebrew>(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<FATStorage> sdcard = args && args->SDCard ? std::make_optional<FATStorage>(std::move(*args->SDCard)) : std::nullopt;
cart = std::make_unique<CartR4>(std::move(cartrom), cartromsize, cartid, romparams, CartR4TypeR4, CartR4LanguageEnglish, std::move(sdcard));
}
else if (cartid & 0x08000000)
cart = std::make_unique<CartRetailNAND>(cartrom, cartromsize, cartid, romparams);
cart = std::make_unique<CartRetailNAND>(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen);
else if (irversion != 0)
cart = std::make_unique<CartRetailIR>(cartrom, cartromsize, cartid, irversion, badDSiDump, romparams);
cart = std::make_unique<CartRetailIR>(std::move(cartrom), cartromsize, cartid, irversion, badDSiDump, romparams, std::move(sram), sramlen);
else if ((gamecode & 0xFFFFFF) == 0x505A55) // UZPx
cart = std::make_unique<CartRetailBT>(cartrom, cartromsize, cartid, romparams);
cart = std::make_unique<CartRetailBT>(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen);
else
cart = std::make_unique<CartRetail>(cartrom, cartromsize, cartid, badDSiDump, romparams);
if (romparams.SaveMemType > 0)
cart->SetupSave(romparams.SaveMemType);
cart = std::make_unique<CartRetail>(std::move(cartrom), cartromsize, cartid, badDSiDump, romparams, std::move(sram), sramlen);
args = std::nullopt;
return cart;
}
bool NDSCartSlot::InsertROM(std::unique_ptr<CartCommon>&& cart) noexcept
void NDSCartSlot::SetCart(std::unique_ptr<CartCommon>&& 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<CartCommon>&& 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<CartCommon>&& 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<CartCommon>&& 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<CartCommon> 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<CartCommon> 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?
}

View File

@ -22,7 +22,7 @@
#include <array>
#include <string>
#include <memory>
#include <array>
#include <variant>
#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<FATStorageArgs> 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<u8[]> 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<u8[]>&& 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<u8[]> 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<u8[]>&& sram,
u32 sramlen,
melonDS::NDSCart::CartType type = CartType::Retail
);
CartRetail(
std::unique_ptr<u8[]>&& rom,
u32 len, u32 chipid,
bool badDSiDump,
ROMListEntry romparams,
std::unique_ptr<u8[]>&& 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<u8[]> 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<u8[]>&& sram, u32 sramlen);
CartRetailNAND(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& 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<u8[]>&& sram, u32 sramlen);
CartRetailIR(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr<u8[]>&& 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<6F>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<u8[]>&& sram, u32 sramlen);
CartRetailBT(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& 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<FATStorage>&& sdcard = std::nullopt);
CartSD(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard = std::nullopt);
~CartSD() override;
virtual u32 Type() const override { return CartType::Homebrew; }
[[nodiscard]] const std::optional<FATStorage>& GetSDCard() const noexcept { return SD; }
void SetSDCard(FATStorage&& sdcard) noexcept { SD = std::move(sdcard); }
void SetSDCard(std::optional<FATStorage>&& 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<FATStorageArgs>&& 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<FATStorage> SD {};
};
// CartHomebrew -- homebrew 'cart' (no SRAM, DLDI)
class CartHomebrew : public CartSD
{
public:
CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard = std::nullopt);
CartHomebrew(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& 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<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage,
std::optional<FATStorage>&& 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<CartCommon>&& 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<CartCommon>&& cart) noexcept;
void SetCart(std::unique_ptr<CartCommon>&& 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<CartCommon> 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<u8, 8> ROMCommand {};
u8 SPIData;
u32 SPIDataPos;
bool SPIHold;
u8 SPIData = 0;
u32 SPIDataPos = 0;
bool SPIHold = false;
u32 ROMData;
u32 ROMData = 0;
std::array<u8, 0x4000> TransferData;
u32 TransferPos;
u32 TransferLen;
u32 TransferDir;
std::array<u8, 8> TransferCmd;
std::array<u8, 0x4000> TransferData {};
u32 TransferPos = 0;
u32 TransferLen = 0;
u32 TransferDir = 0;
std::array<u8, 8> TransferCmd {};
std::unique_ptr<CartCommon> Cart;
std::unique_ptr<CartCommon> Cart = nullptr;
std::array<u32, 0x412> Key1_KeyBuf;
std::array<u32, 0x412> 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<CartCommon> ParseROM(const u8* romdata, u32 romlen);
std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen, std::optional<NDSCartArgs>&& args = std::nullopt);
std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, std::optional<NDSCartArgs>&& args = std::nullopt);
}
#endif

371
src/NDSCartR4.cpp Normal file
View File

@ -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 <string.h>
#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<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage,
std::optional<FATStorage>&& 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;
}
}
}
}
}

View File

@ -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!");

View File

@ -26,11 +26,38 @@
#include <initializer_list>
#include <algorithm>
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 <u32 Size>
struct NonStupidBitField
{
@ -42,7 +69,7 @@ struct NonStupidBitField
NonStupidBitField<Size>& 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<Size>& other)
{
for (u32 i = 0; i < DataLength; i++)
@ -195,6 +247,7 @@ struct NonStupidBitField
}
return *this;
}
NonStupidBitField& operator&=(const NonStupidBitField<Size>& 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;
}
};
}

View File

@ -18,6 +18,14 @@
#include "OpenGLSupport.h"
#include <unordered_map>
#include <vector>
#include <assert.h>
#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<u64, ShaderCacheEntry> ShaderCache;
std::vector<u64> 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<AttributeTarget>& vertexInAttrs,
const std::initializer_list<AttributeTarget>& 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;
}
}

View File

@ -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<AttributeTarget>& vertexInAttrs,
const std::initializer_list<AttributeTarget>& fragmentOutAttrs);
bool CompileComputeProgram(GLuint& result, const std::string& source, const std::string& name);
}

View File

@ -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 <tt>Read | Write</tt>.
@ -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.

View File

@ -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;

View File

@ -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();

Some files were not shown because too many files have changed in this diff Show More