mirror of
https://github.com/Mr-Wiseguy/Zelda64Recomp.git
synced 2024-11-16 19:09:15 +01:00
Compare commits
32 Commits
8f32934fc9
...
fc9aad1fc3
Author | SHA1 | Date | |
---|---|---|---|
|
fc9aad1fc3 | ||
|
b9b567e024 | ||
|
1978a72690 | ||
|
5a80cd028c | ||
|
07cfe51010 | ||
|
b086945b67 | ||
|
1a6a3b3082 | ||
|
19fcd9bf31 | ||
|
b31cf0a76b | ||
|
0e31d3b582 | ||
|
2607c8098f | ||
|
e819c62da2 | ||
|
b0d0cab6c2 | ||
|
1c00519938 | ||
|
030d793056 | ||
|
bec699f0bd | ||
|
6e9ee3498b | ||
|
4d682fac8a | ||
|
d1f5ff3bba | ||
|
9ab97993e1 | ||
|
49baa68ea1 | ||
|
db1def4280 | ||
|
7d3ae05436 | ||
|
763c714653 | ||
|
ce406e9c5d | ||
|
3e5efc935e | ||
|
a3d287575d | ||
|
8319d97ad1 | ||
|
b6b3bca731 | ||
|
95b7a64f1c | ||
|
790b10a4b4 | ||
|
32b0fc5f53 |
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -6,8 +6,15 @@ labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## If you have a crash on startup, please make sure your graphics drivers are up to date before submitting a bug report.
|
||||
|
||||
**What is your GPU driver version? Old drivers, particularly on Nvidia, are known to cause crashes on boot on 1.1.0. If you are on Nvidia and the game is crashing on boot, please update to driver version 555.85 or newer before opening an issue.**
|
||||
|
||||
**Have you checked whether this issue is vanilla behavior? In other words, does it occur on original hardware?**
|
||||
|
||||
**Were you playing with intended mechanics, or using glitches? If it's the latter, which glitches?**
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
@ -27,6 +34,7 @@ Please attach a screenshot of the bug.
|
||||
- Version: [e.g. 1.0.0]
|
||||
- CPU: [e.g. Intel Core ..., AMD Ryzen ..., etc.]
|
||||
- GPU: [e.g. NVIDIA GeForce .../Radeon RX .../Intel UHD .../etc.]
|
||||
- GPU driver: [e.g Nvidia driver 545.XX, AMD driver 24.X.X, etc]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
40
.github/linux/appimage.sh
vendored
40
.github/linux/appimage.sh
vendored
@ -1,17 +1,45 @@
|
||||
curl -sSfLO "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-static-x86_64.AppImage"
|
||||
ARCH=$(uname -m)
|
||||
LINUX_DEPLOY_ARCH=$(uname -m)
|
||||
|
||||
if [ "$ARCH" = "x86_64" ]; then
|
||||
ARCH="x86_64"
|
||||
LINUX_DEPLOY_ARCH="x86_64"
|
||||
elif [ "$ARCH" = "aarch64" ]; then
|
||||
ARCH="arm_aarch64"
|
||||
LINUX_DEPLOY_ARCH="aarch64"
|
||||
else
|
||||
echo "Unsupported architecture: $ARCH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
curl -sSfLO "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$LINUX_DEPLOY_ARCH.AppImage"
|
||||
curl -sSfLO "https://github.com/linuxdeploy/linuxdeploy-plugin-gtk/raw/master/linuxdeploy-plugin-gtk.sh"
|
||||
|
||||
chmod a+x linuxdeploy*
|
||||
|
||||
mkdir -p AppDir/usr/bin
|
||||
cp Zelda64Recompiled AppDir/usr/bin/
|
||||
cp -r assets/ AppDir/usr/bin/
|
||||
cp gamecontrollerdb.txt AppDir/usr/bin/
|
||||
cp icons/512.png AppDir/Zelda64Recompiled.png
|
||||
cp .github/linux/Zelda64Recompiled.desktop AppDir/
|
||||
|
||||
./linuxdeploy-static-x86_64.AppImage --appimage-extract
|
||||
"./linuxdeploy-$LINUX_DEPLOY_ARCH.AppImage" --appimage-extract
|
||||
mv squashfs-root/ deploy
|
||||
ARCH=x86_64 ./deploy/AppRun --appdir=AppDir/ -d AppDir/Zelda64Recompiled.desktop -i AppDir/Zelda64Recompiled.png -e AppDir/usr/bin/Zelda64Recompiled --plugin gtk
|
||||
./deploy/AppRun --appdir=AppDir/ -d AppDir/Zelda64Recompiled.desktop -i AppDir/Zelda64Recompiled.png -e AppDir/usr/bin/Zelda64Recompiled --plugin gtk
|
||||
sed -i 's/exec/#exec/g' AppDir/AppRun
|
||||
echo 'cd "$this_dir"/usr/bin/' >> AppDir/AppRun
|
||||
echo './Zelda64Recompiled' >> AppDir/AppRun
|
||||
ARCH=x86_64 ./deploy/usr/bin/linuxdeploy-plugin-appimage --appdir=AppDir
|
||||
echo 'if [ -f "portable.txt" ]; then' >> AppDir/AppRun
|
||||
echo ' APP_FOLDER_PATH=$PWD' >> AppDir/AppRun
|
||||
echo ' cd "$this_dir"/usr/bin/' >> AppDir/AppRun
|
||||
echo ' APP_FOLDER_PATH=$APP_FOLDER_PATH ./Zelda64Recompiled' >> AppDir/AppRun
|
||||
echo 'else' >> AppDir/AppRun
|
||||
echo ' cd "$this_dir"/usr/bin/' >> AppDir/AppRun
|
||||
echo ' ./Zelda64Recompiled' >> AppDir/AppRun
|
||||
echo 'fi' >> AppDir/AppRun
|
||||
|
||||
# Remove conflicting libraries
|
||||
rm -rf AppDir/usr/lib/libgmodule*
|
||||
rm -rf AppDir/usr/lib/gio/modules/*.so
|
||||
rm -rf AppDir/usr/lib/libwayland*
|
||||
|
||||
./deploy/usr/bin/linuxdeploy-plugin-appimage --appdir=AppDir
|
||||
|
167
.github/workflows/validate.yml
vendored
167
.github/workflows/validate.yml
vendored
@ -1,28 +1,111 @@
|
||||
name: validate
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
workflow_call:
|
||||
inputs:
|
||||
SDL2_VERSION:
|
||||
type: string
|
||||
required: false
|
||||
default: '2.30.3'
|
||||
N64RECOMP_COMMIT:
|
||||
type: string
|
||||
required: false
|
||||
default: '2a2df89349ff25a3afb3a09617deb3a166efe2f3'
|
||||
DXC_CHECKSUM:
|
||||
type: string
|
||||
required: false
|
||||
default: '4e6f4e52989aca69739880b40b9f988357f15d10ca03284377b81f1502463ff5'
|
||||
secrets:
|
||||
ZRE_REPO_WITH_PAT:
|
||||
required: true
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
authorize:
|
||||
environment:
|
||||
${{ github.event_name == 'pull_request_target' &&
|
||||
github.event.pull_request.head.repo.full_name != github.repository &&
|
||||
'external' || 'internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo ✓
|
||||
build-unix:
|
||||
needs: authorize
|
||||
runs-on: ubuntu-22.04
|
||||
build-linux:
|
||||
runs-on: ${{ matrix.os }}
|
||||
container:
|
||||
image: dcvz/n64recomp:0.0.1-ubuntu-18.04
|
||||
strategy:
|
||||
matrix:
|
||||
type: [ Debug, Release ]
|
||||
os: [ ubuntu-22.04 ]
|
||||
name: ubuntu-18.04 (x64, ${{ matrix.type }})
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.ref }}
|
||||
submodules: recursive
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2.11
|
||||
with:
|
||||
key: ${{ matrix.os }}-z64re-ccache-${{ matrix.type }}-x64-${{ inputs.N64RECOMP_COMMIT }}
|
||||
- name: Prepare Build
|
||||
run: |-
|
||||
git clone ${{ secrets.ZRE_REPO_WITH_PAT }}
|
||||
unzip zre/files.zip > /dev/null 2>&1
|
||||
- name: Build N64Recomp & RSPRecomp
|
||||
run: |
|
||||
git clone https://github.com/Mr-Wiseguy/N64Recomp.git --recurse-submodules N64RecompSource
|
||||
cd N64RecompSource
|
||||
git checkout ${{ inputs.N64RECOMP_COMMIT }}
|
||||
git submodule update --init --recursive
|
||||
|
||||
# enable ccache
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
|
||||
# Build N64Recomp & RSPRecomp
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=g++-11 -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build
|
||||
cmake --build cmake-build --config Release --target N64Recomp -j $(nproc)
|
||||
cmake --build cmake-build --config Release --target RSPRecomp -j $(nproc)
|
||||
|
||||
# Copy N64Recomp & RSPRecomp to root directory
|
||||
cp cmake-build/N64Recomp ..
|
||||
cp cmake-build/RSPRecomp ..
|
||||
- name: Run N64Recomp & RSPRecomp
|
||||
run: |
|
||||
./N64Recomp us.rev1.toml
|
||||
./RSPRecomp aspMain.us.rev1.toml
|
||||
./RSPRecomp njpgdspMain.us.rev1.toml
|
||||
- name: Hotpatch DXC into RT64's contrib
|
||||
run: |
|
||||
# check if dxc was updated before we replace it, to detect changes
|
||||
echo ${{ inputs.DXC_CHECKSUM }} ./lib/rt64/src/contrib/dxc/bin/x64/dxc | sha256sum --status -c -
|
||||
|
||||
cp -v /usr/local/lib/libdxcompiler.so ./lib/rt64/src/contrib/dxc/lib/x64/libdxcompiler.so
|
||||
cp -v /usr/local/bin/dxc ./lib/rt64/src/contrib/dxc/bin/x64/dxc
|
||||
- name: Build ZeldaRecomp
|
||||
run: |-
|
||||
# enable ccache
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
|
||||
cmake -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=clang++-17 -DCMAKE_C_COMPILER=clang-17 -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build -DPATCHES_C_COMPILER=clang-17 -DPATCHES_LD=ld.lld-17 -DPATCHES_OBJCOPY=llvm-objcopy-17
|
||||
cmake --build cmake-build --config ${{ matrix.type }} --target Zelda64Recompiled -j $(nproc)
|
||||
- name: Prepare Archive
|
||||
run: |
|
||||
mv cmake-build/Zelda64Recompiled Zelda64Recompiled
|
||||
rm -rf assets/scss
|
||||
tar -czf Zelda64Recompiled.tar.gz Zelda64Recompiled assets/ gamecontrollerdb.txt
|
||||
- name: Archive Zelda64Recomp
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Zelda64Recompiled-${{ runner.os }}-X64-${{ matrix.type }}
|
||||
path: Zelda64Recompiled.tar.gz
|
||||
- name: Build AppImage
|
||||
run: |-
|
||||
./.github/linux/appimage.sh
|
||||
- name: Zelda64Recomp AppImage
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Zelda64Recompiled-AppImage-X64-${{ matrix.type }}
|
||||
path: Zelda64Recompiled-*.AppImage
|
||||
build-linux-arm64:
|
||||
runs-on: ${{ format('blaze/{0}', matrix.os) }}
|
||||
strategy:
|
||||
matrix:
|
||||
type: [ Debug, Release ]
|
||||
os: [ ubuntu-22.04 ]
|
||||
name: ${{ matrix.os }} (arm64, ${{ matrix.type }})
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@ -32,9 +115,8 @@ jobs:
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ${{ runner.os }}-z64re-ccache-${{ matrix.type }}
|
||||
key: ${{ matrix.os }}-z64re-ccache-${{ matrix.type }}-arm64-${{ inputs.N64RECOMP_COMMIT }}
|
||||
- name: Install Linux Dependencies
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ninja-build libsdl2-dev libgtk-3-dev lld llvm clang-15 libfuse2
|
||||
@ -45,31 +127,33 @@ jobs:
|
||||
# Enable ccache
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
|
||||
wget https://www.libsdl.org/release/SDL2-2.26.1.tar.gz
|
||||
tar -xzf SDL2-2.26.1.tar.gz
|
||||
cd SDL2-2.26.1
|
||||
wget https://github.com/libsdl-org/SDL/releases/download/release-${{ inputs.SDL2_VERSION }}/SDL2-${{ inputs.SDL2_VERSION }}.tar.gz
|
||||
tar -xzf SDL2-${{ inputs.SDL2_VERSION }}.tar.gz
|
||||
cd SDL2-${{ inputs.SDL2_VERSION }}
|
||||
./configure
|
||||
make -j 10
|
||||
sudo make install
|
||||
sudo cp -av /usr/local/lib/libSDL* /lib/x86_64-linux-gnu/
|
||||
sudo cp -av /usr/local/lib/libSDL* /lib/aarch64-linux-gnu/
|
||||
echo ::endgroup::
|
||||
- name: Prepare Build
|
||||
run: |-
|
||||
git clone ${{ secrets.ZRE_REPO_WITH_PAT }}
|
||||
./zre/process.sh
|
||||
cp ./zre/mm_shader_cache.bin ./shadercache/
|
||||
- name: Build N64Recomp & RSPRecomp
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
git clone https://github.com/Mr-Wiseguy/N64Recomp.git --recurse-submodules N64RecompSource
|
||||
cd N64RecompSource
|
||||
git checkout ${{ inputs.N64RECOMP_COMMIT }}
|
||||
git submodule update --init --recursive
|
||||
|
||||
# enable ccache
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
|
||||
# Build N64Recomp & RSPRecomp
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=g++-11 -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build
|
||||
cmake --build cmake-build --config Release --target N64Recomp -j 8
|
||||
cmake --build cmake-build --config Release --target RSPRecomp -j 8
|
||||
cmake --build cmake-build --config Release --target N64Recomp -j $(nproc)
|
||||
cmake --build cmake-build --config Release --target RSPRecomp -j $(nproc)
|
||||
|
||||
# Copy N64Recomp & RSPRecomp to root directory
|
||||
cp cmake-build/N64Recomp ..
|
||||
@ -85,30 +169,30 @@ jobs:
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
|
||||
cmake -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=clang++-15 -DCMAKE_C_COMPILER=clang-15 -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build
|
||||
cmake --build cmake-build --config ${{ matrix.type }} --target Zelda64Recompiled -j 8
|
||||
cmake --build cmake-build --config ${{ matrix.type }} --target Zelda64Recompiled -j $(nproc)
|
||||
- name: Prepare Archive
|
||||
run: |
|
||||
mv cmake-build/Zelda64Recompiled Zelda64Recompiled
|
||||
rm -rf assets/scss
|
||||
tar -czf Zelda64Recompiled-${{ runner.os }}-${{ matrix.type }}.tar.gz Zelda64Recompiled assets/
|
||||
tar -czf Zelda64Recompiled.tar.gz Zelda64Recompiled assets/ gamecontrollerdb.txt
|
||||
- name: Archive Zelda64Recomp
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Zelda64Recompiled-${{ runner.os }}-${{ matrix.type }}
|
||||
path: Zelda64Recompiled-${{ runner.os }}-${{ matrix.type }}.tar.gz
|
||||
name: Zelda64Recompiled-${{ runner.os }}-ARM64-${{ matrix.type }}
|
||||
path: Zelda64Recompiled.tar.gz
|
||||
- name: Prepare AppImage
|
||||
run: ./.github/linux/appimage.sh
|
||||
- name: Zelda64Recomp AppImage
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Zelda64Recompiled-AppImage-${{ matrix.type }}
|
||||
path: Zelda64Recompiled-x86_64.AppImage
|
||||
name: Zelda64Recompiled-AppImage-ARM64-${{ matrix.type }}
|
||||
path: Zelda64Recompiled-*.AppImage
|
||||
build-windows:
|
||||
needs: authorize
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
type: [ Debug, Release ]
|
||||
name: windows (${{ matrix.type }})
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@ -129,17 +213,21 @@ jobs:
|
||||
run: |-
|
||||
git clone ${{ secrets.ZRE_REPO_WITH_PAT }}
|
||||
./zre/process.ps1
|
||||
cp ./zre/mm_shader_cache.bin ./shadercache/
|
||||
- name: Build N64Recomp & RSPRecomp
|
||||
run: |
|
||||
git clone https://github.com/Mr-Wiseguy/N64Recomp.git --recurse-submodules N64RecompSource
|
||||
cd N64RecompSource
|
||||
git checkout ${{ inputs.N64RECOMP_COMMIT }}
|
||||
git submodule update --init --recursive
|
||||
|
||||
# enable ccache
|
||||
set $env:PATH="$env:USERPROFILE/.cargo/bin;$env:PATH"
|
||||
$cpuCores = (Get-CimInstance -ClassName Win32_Processor).NumberOfLogicalProcessors
|
||||
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build
|
||||
cmake --build cmake-build --config Release --target N64Recomp -j 8
|
||||
cmake --build cmake-build --config Release --target RSPRecomp -j 8
|
||||
cmake --build cmake-build --config Release --target N64Recomp -j $cpuCores
|
||||
cmake --build cmake-build --config Release --target RSPRecomp -j $cpuCores
|
||||
|
||||
# Copy N64Recomp & RSPRecomp to root directory
|
||||
cp cmake-build/N64Recomp.exe ..
|
||||
@ -153,9 +241,15 @@ jobs:
|
||||
run: |-
|
||||
# enable ccache
|
||||
set $env:PATH="$env:USERPROFILE/.cargo/bin;$env:PATH"
|
||||
$cpuCores = (Get-CimInstance -ClassName Win32_Processor).NumberOfLogicalProcessors
|
||||
|
||||
cmake -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER=clang-cl -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build -DCMAKE_CXX_FLAGS="-Xclang -fcxx-exceptions"
|
||||
cmake --build cmake-build --config ${{ matrix.type }} --target Zelda64Recompiled -j 8
|
||||
# remove LLVM from PATH so it doesn't overshadow the one provided by VS
|
||||
$env:PATH = ($env:PATH -split ';' | Where-Object { $_ -ne 'C:\Program Files\LLVM\bin' }) -join ';'
|
||||
|
||||
cmake -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER=clang-cl -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build -DCMAKE_CXX_FLAGS="-Xclang -fexceptions -Xclang -fcxx-exceptions"
|
||||
cmake --build cmake-build --config ${{ matrix.type }} --target Zelda64Recompiled -j $cpuCores
|
||||
env:
|
||||
SDL2_VERSION: ${{ inputs.SDL2_VERSION }}
|
||||
- name: Prepare Archive
|
||||
run: |
|
||||
Move-Item -Path "cmake-build/Zelda64Recompiled.exe" -Destination "Zelda64Recompiled.exe"
|
||||
@ -173,3 +267,4 @@ jobs:
|
||||
dxil.dll
|
||||
SDL2.dll
|
||||
assets/
|
||||
gamecontrollerdb.txt
|
||||
|
19
.github/workflows/validate_external.yml
vendored
Normal file
19
.github/workflows/validate_external.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
name: validate-external
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
jobs:
|
||||
authorize:
|
||||
if: github.repository != github.event.pull_request.head.repo.full_name
|
||||
environment:
|
||||
${{ github.event_name == 'pull_request_target' &&
|
||||
github.event.pull_request.head.repo.full_name != github.repository &&
|
||||
'external' || 'internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo ✓
|
||||
build:
|
||||
needs: authorize
|
||||
uses: ./.github/workflows/validate.yml
|
||||
secrets:
|
||||
ZRE_REPO_WITH_PAT: ${{ secrets.ZRE_REPO_WITH_PAT }}
|
12
.github/workflows/validate_internal.yml
vendored
Normal file
12
.github/workflows/validate_internal.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
name: validate-internal
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name == 'push' || github.repository == github.event.pull_request.head.repo.full_name
|
||||
uses: ./.github/workflows/validate.yml
|
||||
secrets: inherit
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
# VSCode file settings
|
||||
.vscode/settings.json
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
|
||||
# Input elf and rom files
|
||||
*.elf
|
||||
@ -56,3 +57,7 @@ node_modules/
|
||||
|
||||
# Recompiler Linux binary
|
||||
N64Recomp
|
||||
.DS_Store
|
||||
|
||||
# Controller mappings file
|
||||
gamecontrollerdb.txt
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -16,3 +16,6 @@
|
||||
[submodule "lib/sse2neon"]
|
||||
path = lib/sse2neon
|
||||
url = https://github.com/DLTcollab/sse2neon.git
|
||||
[submodule "lib/N64ModernRuntime"]
|
||||
path = lib/N64ModernRuntime
|
||||
url = https://github.com/N64Recomp/N64ModernRuntime.git
|
||||
|
103
CMakeLists.txt
103
CMakeLists.txt
@ -13,10 +13,6 @@ if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
|
||||
cmake_policy(SET CMP0135 NEW)
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(LINUX TRUE)
|
||||
endif()
|
||||
|
||||
set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
@ -35,6 +31,8 @@ add_subdirectory(${CMAKE_SOURCE_DIR}/lib/lunasvg)
|
||||
SET(ENABLE_SVG_PLUGIN ON CACHE BOOL "" FORCE)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/RmlUi)
|
||||
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime)
|
||||
|
||||
target_include_directories(rt64 PRIVATE ${CMAKE_BINARY_DIR}/rt64/src)
|
||||
|
||||
# RecompiledFuncs - Library containing the primary recompiler output
|
||||
@ -43,11 +41,14 @@ add_library(RecompiledFuncs STATIC)
|
||||
target_compile_options(RecompiledFuncs PRIVATE
|
||||
# -Wno-unused-but-set-variable
|
||||
-fno-strict-aliasing
|
||||
-Wno-unused-variable
|
||||
-Wno-implicit-function-declaration
|
||||
)
|
||||
|
||||
target_include_directories(RecompiledFuncs PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/ultramodern/include
|
||||
${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/librecomp/include
|
||||
)
|
||||
|
||||
file(GLOB FUNC_C_SOURCES ${CMAKE_SOURCE_DIR}/RecompiledFuncs/*.c)
|
||||
@ -61,10 +62,14 @@ add_library(PatchesLib STATIC)
|
||||
target_compile_options(PatchesLib PRIVATE
|
||||
# -Wno-unused-but-set-variable
|
||||
-fno-strict-aliasing
|
||||
-Wno-unused-variable
|
||||
-Wno-implicit-function-declaration
|
||||
)
|
||||
|
||||
target_include_directories(PatchesLib PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/ultramodern/include
|
||||
${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/librecomp/include
|
||||
)
|
||||
|
||||
target_sources(PatchesLib PRIVATE
|
||||
@ -75,8 +80,20 @@ target_sources(PatchesLib PRIVATE
|
||||
set_source_files_properties(${CMAKE_SOURCE_DIR}/RecompiledPatches/patches.c PROPERTIES COMPILE_FLAGS -fno-strict-aliasing)
|
||||
|
||||
# Build patches elf
|
||||
if(NOT DEFINED PATCHES_C_COMPILER)
|
||||
set(PATCHES_C_COMPILER clang)
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED PATCHES_LD)
|
||||
set(PATCHES_LD ld.lld)
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED PATCHES_OBJCOPY)
|
||||
set(PATCHES_OBJCOPY llvm-objcopy)
|
||||
endif()
|
||||
|
||||
add_custom_target(PatchesBin
|
||||
COMMAND make
|
||||
COMMAND ${CMAKE_COMMAND} -E env CC=${PATCHES_C_COMPILER} LD=${PATCHES_LD} OBJCOPY=${PATCHES_OBJCOPY} make
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/patches
|
||||
BYPRODUCTS ${CMAKE_SOURCE_DIR}/patches/patches.bin
|
||||
)
|
||||
@ -92,14 +109,25 @@ add_custom_command(OUTPUT
|
||||
${CMAKE_SOURCE_DIR}/RecompiledPatches/patches.c
|
||||
${CMAKE_SOURCE_DIR}/RecompiledPatches/recomp_overlays.inl
|
||||
${CMAKE_SOURCE_DIR}/RecompiledPatches/funcs.h
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/patch_loading.cpp
|
||||
COMMAND ./N64Recomp patches.toml && ${CMAKE_COMMAND} -E touch ${CMAKE_SOURCE_DIR}/src/recomp/patch_loading.cpp
|
||||
# TODO: Look into why modifying patches requires two builds to take
|
||||
COMMAND ./N64Recomp patches.toml
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/patches/patches.bin
|
||||
)
|
||||
|
||||
# Download controller db file for controller support via SDL2
|
||||
set(GAMECONTROLLERDB_COMMIT "b1e4090b3d4266e55feb0793efa35792e05faf66")
|
||||
set(GAMECONTROLLERDB_URL "https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/${GAMECONTROLLERDB_COMMIT}/gamecontrollerdb.txt")
|
||||
|
||||
file(DOWNLOAD ${GAMECONTROLLERDB_URL} ${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt
|
||||
TLS_VERIFY ON)
|
||||
|
||||
add_custom_target(DownloadGameControllerDB
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt)
|
||||
|
||||
# Main executable
|
||||
add_executable(Zelda64Recompiled)
|
||||
add_dependencies(Zelda64Recompiled DownloadGameControllerDB)
|
||||
|
||||
# Generate mm_shader_cache.c from the MM shader cache if it exists
|
||||
if (EXISTS ${CMAKE_SOURCE_DIR}/shadercache/mm_shader_cache.bin)
|
||||
@ -112,38 +140,10 @@ if (EXISTS ${CMAKE_SOURCE_DIR}/shadercache/mm_shader_cache.bin)
|
||||
endif()
|
||||
|
||||
set (SOURCES
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/audio.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/events.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/mesgqueue.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/misc_ultra.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/port_main.c
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/scheduling.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/threadqueue.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/task_win32.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/threads.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/timer.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/ultrainit.cpp
|
||||
${CMAKE_SOURCE_DIR}/ultramodern/rt64_layer.cpp
|
||||
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/ai.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/cont.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/dp.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/eep.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/euc-jp.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/flash.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/math_routines.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/overlays.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/patch_loading.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/pak.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/pi.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/ultra_stubs.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/ultra_translation.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/print.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/recomp.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/sp.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/vi.cpp
|
||||
|
||||
${CMAKE_SOURCE_DIR}/src/main/main.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/main/register_overlays.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/main/register_patches.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/main/rt64_render_context.cpp
|
||||
|
||||
${CMAKE_SOURCE_DIR}/src/game/input.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/game/controls.cpp
|
||||
@ -203,11 +203,17 @@ endif()
|
||||
|
||||
if (WIN32)
|
||||
include(FetchContent)
|
||||
|
||||
if (DEFINED ENV{SDL2_VERSION})
|
||||
set(SDL2_VERSION $ENV{SDL2_VERSION})
|
||||
else()
|
||||
set(SDL2_VERSION "2.30.3")
|
||||
endif()
|
||||
|
||||
# Fetch SDL2 on windows
|
||||
FetchContent_Declare(
|
||||
sdl2
|
||||
URL https://github.com/libsdl-org/SDL/releases/download/release-2.28.5/SDL2-devel-2.28.5-VC.zip
|
||||
URL_HASH MD5=d8173db078e54040c666f411c5a6afff
|
||||
URL https://github.com/libsdl-org/SDL/releases/download/release-${SDL2_VERSION}/SDL2-devel-${SDL2_VERSION}-VC.zip
|
||||
)
|
||||
FetchContent_MakeAvailable(sdl2)
|
||||
target_include_directories(Zelda64Recompiled PRIVATE
|
||||
@ -237,7 +243,7 @@ if (WIN32)
|
||||
target_sources(Zelda64Recompiled PRIVATE ${CMAKE_SOURCE_DIR}/icons/app.rc)
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(X11 REQUIRED)
|
||||
|
||||
@ -281,6 +287,8 @@ target_link_libraries(Zelda64Recompiled PRIVATE
|
||||
PatchesLib
|
||||
RecompiledFuncs
|
||||
SDL2
|
||||
librecomp
|
||||
ultramodern
|
||||
rt64
|
||||
RmlCore
|
||||
RmlDebugger
|
||||
@ -303,15 +311,12 @@ if (${WIN32})
|
||||
set (DXC "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/x64/dxc.exe")
|
||||
add_compile_definitions(NOMINMAX)
|
||||
else()
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64")
|
||||
if (APPLE)
|
||||
set (DXC "DYLD_LIBRARY_PATH=${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/lib/x64" "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/x64/dxc")
|
||||
else()
|
||||
set (DXC "LD_LIBRARY_PATH=${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/lib/x64" "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/x64/dxc")
|
||||
endif()
|
||||
else()
|
||||
if (APPLE)
|
||||
# Apple's binary is universal, so it'll work on both x86_64 and arm64
|
||||
set (DXC "DYLD_LIBRARY_PATH=${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/lib/arm64" "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/arm64/dxc-macos")
|
||||
else()
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64")
|
||||
set (DXC "LD_LIBRARY_PATH=${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/lib/x64" "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/x64/dxc")
|
||||
else()
|
||||
set (DXC "LD_LIBRARY_PATH=${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/lib/arm64" "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/arm64/dxc-linux")
|
||||
endif()
|
||||
@ -319,7 +324,7 @@ else()
|
||||
endif()
|
||||
|
||||
build_vertex_shader(Zelda64Recompiled "shaders/InterfaceVS.hlsl" "shaders/InterfaceVS.hlsl")
|
||||
build_pixel_shader (Zelda64Recompiled "shaders/InterfacePS.hlsl" "shaders/InterfacePS.hlsl")
|
||||
build_pixel_shader(Zelda64Recompiled "shaders/InterfacePS.hlsl" "shaders/InterfacePS.hlsl")
|
||||
|
||||
target_sources(Zelda64Recompiled PRIVATE ${SOURCES})
|
||||
|
||||
|
24
README.md
24
README.md
@ -5,6 +5,20 @@ Zelda 64: Recompiled is a project that uses [N64: Recompiled](https://github.com
|
||||
|
||||
### **This repository and its releases do not contain game assets. The original game is required to build or run this project.**
|
||||
|
||||
<div align="left" valign="middle">
|
||||
<a href="https://runblaze.dev">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://www.runblaze.dev/logo_dark.png">
|
||||
<img align="right" src="https://www.runblaze.dev/logo_light.png" height="102px"/>
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
<br style="display: none;"/>
|
||||
|
||||
_Thank you [Blaze](https://runblaze.dev) for supporting this project by providing Linux ARM64 and Apple Silicon macOS Github Action Runners!_
|
||||
|
||||
</div>
|
||||
|
||||
## Table of Contents
|
||||
* [System Requirements](#system-requirements)
|
||||
* [Features](#features)
|
||||
@ -13,7 +27,9 @@ Zelda 64: Recompiled is a project that uses [N64: Recompiled](https://github.com
|
||||
* [Easy-to-Use Menus](#easy-to-use-menus)
|
||||
* [High Framerate Support](#high-framerate-support)
|
||||
* [Widescreen and Ultrawide Support](#widescreen-and-ultrawide-support)
|
||||
* [Dual Analog Camera](#dual-analog-camera)
|
||||
* [Gyro Aim](#gyro-aim)
|
||||
* [Additional Control Options](#additional-control-options)
|
||||
* [Autosaving](#autosaving)
|
||||
* [Low Input Lag](#low-input-lag)
|
||||
* [Instant Load Times](#instant-load-times)
|
||||
@ -55,11 +71,17 @@ Any aspect ratio is supported, with most effects modded to work correctly in wid
|
||||
|
||||
**Note**: Some animation quirks can be seen at the edges of the screen in certain cutscenes when using very wide aspect ratios.
|
||||
|
||||
#### Dual Analog Camera
|
||||
Play with a dual analog control layout like later entries in the series! When this option is enabled, the right stick will control the camera. You can still have the C-Buttons mapped to the right stick if you so wish, so long as you also map them to other buttons on the controller. The right stick C-button inputs will be "silenced", except when you take out the ocarina, so you can still play the ocarina with the right stick.
|
||||
|
||||
#### Gyro Aim
|
||||
When playing with a supported controller, first-person items such as the bow can be aimed with your controller's gyro sensor. This includes (but is not limited to) controllers such as the Dualshock 4, Dualsense, Switch Pro, and most third party Switch controllers (such as the 8BitDo Pro 2 in Switch mode).
|
||||
|
||||
**Note**: Gamepad mappers such as BetterJoy or DS4Windows may intercept gyro data and prevent the game from receiving it. Most controllers are natively supported, so turning gamepad mappers off is recommended if you want to use gyro.
|
||||
|
||||
#### Additional Control Options
|
||||
Customize your experience by setting your stick deadzone to your liking, as well as adjusting the X and Y axis inversion for both aiming and the optional dual analog camera.
|
||||
|
||||
#### Autosaving
|
||||
Never worry about losing progress if your power goes out thanks to autosaving! The autosave system is designed to respect Majora's Mask's original save system and maintain the intention of owl saves by triggering automatically and replacing the previous autosave or owl save. However, if you'd still rather play with the untouched save system, simply turn off autosaving in the ingame menu.
|
||||
|
||||
@ -75,8 +97,6 @@ A Linux binary is available for playing on most up-to-date distros, including on
|
||||
To play on Steam Deck, extract the Linux build onto your deck. Then, in desktop mode, right click the Zelda64Recompiled executable file and select "Add to Steam". From there, you can return to Gaming mode and configure the controls as needed. See the [Steam Deck gyro aim FAQ section](#how-do-i-set-up-gyro-aiming-on-steam-deck) for more detailed instructions.
|
||||
|
||||
## Planned Features
|
||||
* Dual analog control scheme (with analog camera)
|
||||
* Configurable deadzone and analog stick sensitivity
|
||||
* Ocarina of Time support
|
||||
* Mod support and Randomizer
|
||||
* Texture Packs
|
||||
|
@ -331,13 +331,13 @@
|
||||
<b>Note: This option does not allow mouse buttons to activate items. Mouse aiming is intended to be used with inputs that are mapped to mouse movement, such as gyro on Steam Deck.</b>
|
||||
</p>
|
||||
<p data-if="cur_config_index == 4">
|
||||
Applies a deadzone to joystick inputs.
|
||||
</p>
|
||||
<p data-if="cur_config_index == 5">
|
||||
Allows the game to read controller input when out of focus.
|
||||
<br/>
|
||||
<b>This setting does not affect keyboard input.</b>
|
||||
</p>
|
||||
<p data-if="cur_config_index == 5">
|
||||
Applies a deadzone to joystick inputs.
|
||||
</p>
|
||||
<p data-if="cur_config_index == 6">
|
||||
Turns on autosaving and prevents owl saves from being deleted on load. Autosaves act as owl saves and take up the same slot as they do.
|
||||
<br/>
|
||||
|
@ -1,4 +0,0 @@
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic ignored "-Wunused-variable"
|
||||
#pragma clang diagnostic ignored "-Wimplicit-function-declaration"
|
||||
#endif
|
9
include/ovl_patches.hpp
Normal file
9
include/ovl_patches.hpp
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef __OVL_PATCHES_HPP__
|
||||
#define __OVL_PATCHES_HPP__
|
||||
|
||||
namespace zelda64 {
|
||||
void register_overlays();
|
||||
void register_patches();
|
||||
}
|
||||
|
||||
#endif
|
327
include/recomp.h
327
include/recomp.h
@ -1,327 +0,0 @@
|
||||
#ifndef __RECOMP_H__
|
||||
#define __RECOMP_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
#include <setjmp.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#if 0 // treat GPRs as 32-bit, should be better codegen
|
||||
typedef uint32_t gpr;
|
||||
|
||||
#define SIGNED(val) \
|
||||
((int32_t)(val))
|
||||
#else
|
||||
typedef uint64_t gpr;
|
||||
|
||||
#define SIGNED(val) \
|
||||
((int64_t)(val))
|
||||
#endif
|
||||
|
||||
#define ADD32(a, b) \
|
||||
((gpr)(int32_t)((a) + (b)))
|
||||
|
||||
#define SUB32(a, b) \
|
||||
((gpr)(int32_t)((a) - (b)))
|
||||
|
||||
#define MEM_W(offset, reg) \
|
||||
(*(int32_t*)(rdram + ((((reg) + (offset))) - 0xFFFFFFFF80000000)))
|
||||
//(*(int32_t*)(rdram + ((((reg) + (offset))) & 0x3FFFFFF)))
|
||||
|
||||
#define MEM_H(offset, reg) \
|
||||
(*(int16_t*)(rdram + ((((reg) + (offset)) ^ 2) - 0xFFFFFFFF80000000)))
|
||||
//(*(int16_t*)(rdram + ((((reg) + (offset)) ^ 2) & 0x3FFFFFF)))
|
||||
|
||||
#define MEM_B(offset, reg) \
|
||||
(*(int8_t*)(rdram + ((((reg) + (offset)) ^ 3) - 0xFFFFFFFF80000000)))
|
||||
//(*(int8_t*)(rdram + ((((reg) + (offset)) ^ 3) & 0x3FFFFFF)))
|
||||
|
||||
#define MEM_HU(offset, reg) \
|
||||
(*(uint16_t*)(rdram + ((((reg) + (offset)) ^ 2) - 0xFFFFFFFF80000000)))
|
||||
//(*(uint16_t*)(rdram + ((((reg) + (offset)) ^ 2) & 0x3FFFFFF)))
|
||||
|
||||
#define MEM_BU(offset, reg) \
|
||||
(*(uint8_t*)(rdram + ((((reg) + (offset)) ^ 3) - 0xFFFFFFFF80000000)))
|
||||
//(*(uint8_t*)(rdram + ((((reg) + (offset)) ^ 3) & 0x3FFFFFF)))
|
||||
|
||||
#define SD(val, offset, reg) { \
|
||||
*(uint32_t*)(rdram + ((((reg) + (offset) + 4)) - 0xFFFFFFFF80000000)) = (uint32_t)((gpr)(val) >> 0); \
|
||||
*(uint32_t*)(rdram + ((((reg) + (offset) + 0)) - 0xFFFFFFFF80000000)) = (uint32_t)((gpr)(val) >> 32); \
|
||||
}
|
||||
|
||||
//#define SD(val, offset, reg) { \
|
||||
// *(uint32_t*)(rdram + ((((reg) + (offset) + 4)) & 0x3FFFFFF)) = (uint32_t)((val) >> 32); \
|
||||
// *(uint32_t*)(rdram + ((((reg) + (offset) + 0)) & 0x3FFFFFF)) = (uint32_t)((val) >> 0); \
|
||||
//}
|
||||
|
||||
static inline uint64_t load_doubleword(uint8_t* rdram, gpr reg, gpr offset) {
|
||||
uint64_t ret = 0;
|
||||
uint64_t lo = (uint64_t)(uint32_t)MEM_W(reg, offset + 4);
|
||||
uint64_t hi = (uint64_t)(uint32_t)MEM_W(reg, offset + 0);
|
||||
ret = (lo << 0) | (hi << 32);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define LD(offset, reg) \
|
||||
load_doubleword(rdram, offset, reg)
|
||||
|
||||
static inline gpr do_lwl(uint8_t* rdram, gpr initial_value, gpr offset, gpr reg) {
|
||||
// Calculate the overall address
|
||||
gpr address = (offset + reg);
|
||||
|
||||
// Load the aligned word
|
||||
gpr word_address = address & ~0x3;
|
||||
uint32_t loaded_value = MEM_W(0, word_address);
|
||||
|
||||
// Mask the existing value and shift the loaded value appropriately
|
||||
gpr misalignment = address & 0x3;
|
||||
gpr masked_value = initial_value & ~(0xFFFFFFFFu << (misalignment * 8));
|
||||
loaded_value <<= (misalignment * 8);
|
||||
|
||||
// Cast to int32_t to sign extend first
|
||||
return (gpr)(int32_t)(masked_value | loaded_value);
|
||||
}
|
||||
|
||||
static inline gpr do_lwr(uint8_t* rdram, gpr initial_value, gpr offset, gpr reg) {
|
||||
// Calculate the overall address
|
||||
gpr address = (offset + reg);
|
||||
|
||||
// Load the aligned word
|
||||
gpr word_address = address & ~0x3;
|
||||
uint32_t loaded_value = MEM_W(0, word_address);
|
||||
|
||||
// Mask the existing value and shift the loaded value appropriately
|
||||
gpr misalignment = address & 0x3;
|
||||
gpr masked_value = initial_value & ~(0xFFFFFFFFu >> (24 - misalignment * 8));
|
||||
loaded_value >>= (24 - misalignment * 8);
|
||||
|
||||
// Cast to int32_t to sign extend first
|
||||
return (gpr)(int32_t)(masked_value | loaded_value);
|
||||
}
|
||||
|
||||
static inline void do_swl(uint8_t* rdram, gpr offset, gpr reg, gpr val) {
|
||||
// Calculate the overall address
|
||||
gpr address = (offset + reg);
|
||||
|
||||
// Get the initial value of the aligned word
|
||||
gpr word_address = address & ~0x3;
|
||||
uint32_t initial_value = MEM_W(0, word_address);
|
||||
|
||||
// Mask the initial value and shift the input value appropriately
|
||||
gpr misalignment = address & 0x3;
|
||||
uint32_t masked_initial_value = initial_value & ~(0xFFFFFFFFu >> (misalignment * 8));
|
||||
uint32_t shifted_input_value = ((uint32_t)val) >> (misalignment * 8);
|
||||
MEM_W(0, word_address) = masked_initial_value | shifted_input_value;
|
||||
}
|
||||
|
||||
static inline void do_swr(uint8_t* rdram, gpr offset, gpr reg, gpr val) {
|
||||
// Calculate the overall address
|
||||
gpr address = (offset + reg);
|
||||
|
||||
// Get the initial value of the aligned word
|
||||
gpr word_address = address & ~0x3;
|
||||
uint32_t initial_value = MEM_W(0, word_address);
|
||||
|
||||
// Mask the initial value and shift the input value appropriately
|
||||
gpr misalignment = address & 0x3;
|
||||
uint32_t masked_initial_value = initial_value & ~(0xFFFFFFFFu << (24 - misalignment * 8));
|
||||
uint32_t shifted_input_value = ((uint32_t)val) << (24 - misalignment * 8);
|
||||
MEM_W(0, word_address) = masked_initial_value | shifted_input_value;
|
||||
}
|
||||
|
||||
#define S32(val) \
|
||||
((int32_t)(val))
|
||||
|
||||
#define U32(val) \
|
||||
((uint32_t)(val))
|
||||
|
||||
#define S64(val) \
|
||||
((int64_t)(val))
|
||||
|
||||
#define U64(val) \
|
||||
((uint64_t)(val))
|
||||
|
||||
#define MUL_S(val1, val2) \
|
||||
((val1) * (val2))
|
||||
|
||||
#define MUL_D(val1, val2) \
|
||||
((val1) * (val2))
|
||||
|
||||
#define DIV_S(val1, val2) \
|
||||
((val1) / (val2))
|
||||
|
||||
#define DIV_D(val1, val2) \
|
||||
((val1) / (val2))
|
||||
|
||||
#define CVT_S_W(val) \
|
||||
((float)((int32_t)(val)))
|
||||
|
||||
#define CVT_D_W(val) \
|
||||
((double)((int32_t)(val)))
|
||||
|
||||
#define CVT_D_S(val) \
|
||||
((double)(val))
|
||||
|
||||
#define CVT_S_D(val) \
|
||||
((float)(val))
|
||||
|
||||
#define TRUNC_W_S(val) \
|
||||
((int32_t)(val))
|
||||
|
||||
#define TRUNC_W_D(val) \
|
||||
((int32_t)(val))
|
||||
|
||||
#define TRUNC_L_S(val) \
|
||||
((int64_t)(val))
|
||||
|
||||
#define TRUNC_L_D(val) \
|
||||
((int64_t)(val))
|
||||
|
||||
#define DEFAULT_ROUNDING_MODE 0
|
||||
|
||||
static inline int32_t do_cvt_w_s(float val, unsigned int rounding_mode) {
|
||||
switch (rounding_mode) {
|
||||
case 0: // round to nearest value
|
||||
return (int32_t)lroundf(val);
|
||||
case 1: // round to zero (truncate)
|
||||
return (int32_t)val;
|
||||
case 2: // round to positive infinity (ceil)
|
||||
return (int32_t)ceilf(val);
|
||||
case 3: // round to negative infinity (floor)
|
||||
return (int32_t)floorf(val);
|
||||
}
|
||||
assert(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CVT_W_S(val) \
|
||||
do_cvt_w_s(val, rounding_mode)
|
||||
|
||||
static inline int32_t do_cvt_w_d(double val, unsigned int rounding_mode) {
|
||||
switch (rounding_mode) {
|
||||
case 0: // round to nearest value
|
||||
return (int32_t)lround(val);
|
||||
case 1: // round to zero (truncate)
|
||||
return (int32_t)val;
|
||||
case 2: // round to positive infinity (ceil)
|
||||
return (int32_t)ceil(val);
|
||||
case 3: // round to negative infinity (floor)
|
||||
return (int32_t)floor(val);
|
||||
}
|
||||
assert(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CVT_W_D(val) \
|
||||
do_cvt_w_d(val, rounding_mode)
|
||||
|
||||
#define NAN_CHECK(val) \
|
||||
assert(val == val)
|
||||
|
||||
//#define NAN_CHECK(val)
|
||||
|
||||
typedef union {
|
||||
double d;
|
||||
struct {
|
||||
float fl;
|
||||
float fh;
|
||||
};
|
||||
struct {
|
||||
uint32_t u32l;
|
||||
uint32_t u32h;
|
||||
};
|
||||
uint64_t u64;
|
||||
} fpr;
|
||||
|
||||
typedef struct {
|
||||
gpr r0, r1, r2, r3, r4, r5, r6, r7,
|
||||
r8, r9, r10, r11, r12, r13, r14, r15,
|
||||
r16, r17, r18, r19, r20, r21, r22, r23,
|
||||
r24, r25, r26, r27, r28, r29, r30, r31;
|
||||
fpr f0, f1, f2, f3, f4, f5, f6, f7,
|
||||
f8, f9, f10, f11, f12, f13, f14, f15,
|
||||
f16, f17, f18, f19, f20, f21, f22, f23,
|
||||
f24, f25, f26, f27, f28, f29, f30, f31;
|
||||
uint64_t hi, lo;
|
||||
uint32_t* f_odd;
|
||||
uint32_t status_reg;
|
||||
uint8_t mips3_float_mode;
|
||||
} recomp_context;
|
||||
|
||||
// Checks if the target is an even float register or that mips3 float mode is enabled
|
||||
#define CHECK_FR(ctx, idx) \
|
||||
assert(((idx) & 1) == 0 || (ctx)->mips3_float_mode)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void cop0_status_write(recomp_context* ctx, gpr value);
|
||||
gpr cop0_status_read(recomp_context* ctx);
|
||||
void switch_error(const char* func, uint32_t vram, uint32_t jtbl);
|
||||
void do_break(uint32_t vram);
|
||||
|
||||
typedef void (recomp_func_t)(uint8_t* rdram, recomp_context* ctx);
|
||||
|
||||
recomp_func_t* get_function(int32_t vram);
|
||||
|
||||
#define LOOKUP_FUNC(val) \
|
||||
get_function((int32_t)(val))
|
||||
|
||||
extern int32_t section_addresses[];
|
||||
|
||||
#define LO16(x) \
|
||||
((x) & 0xFFFF)
|
||||
|
||||
#define HI16(x) \
|
||||
(((x) >> 16) + (((x) >> 15) & 1))
|
||||
|
||||
#define RELOC_HI16(section_index, offset) \
|
||||
HI16(section_addresses[section_index] + (offset))
|
||||
|
||||
#define RELOC_LO16(section_index, offset) \
|
||||
LO16(section_addresses[section_index] + (offset))
|
||||
|
||||
// For Banjo-Tooie
|
||||
void recomp_syscall_handler(uint8_t* rdram, recomp_context* ctx, int32_t instruction_vram);
|
||||
|
||||
// For the Mario Party games (not working)
|
||||
//// This has to be in this file so it can be inlined
|
||||
//struct jmp_buf_storage {
|
||||
// jmp_buf buffer;
|
||||
//};
|
||||
//
|
||||
//struct RecompJmpBuf {
|
||||
// int32_t owner;
|
||||
// struct jmp_buf_storage* storage;
|
||||
// uint64_t magic;
|
||||
//};
|
||||
//
|
||||
//// Randomly generated constant
|
||||
//#define SETJMP_MAGIC 0xe17afdfa939a437bu
|
||||
//
|
||||
//int32_t osGetThreadEx(void);
|
||||
//
|
||||
//#define setjmp_recomp(rdram, ctx) { \
|
||||
// struct RecompJmpBuf* buf = (struct RecompJmpBuf*)(&rdram[(uint64_t)ctx->r4 - 0xFFFFFFFF80000000]); \
|
||||
// \
|
||||
// /* Check if this jump buffer was previously set up */ \
|
||||
// if (buf->magic == SETJMP_MAGIC) { \
|
||||
// /* If so, free the old jmp_buf */ \
|
||||
// free(buf->storage); \
|
||||
// } \
|
||||
// \
|
||||
// buf->magic = SETJMP_MAGIC; \
|
||||
// buf->owner = osGetThreadEx(); \
|
||||
// buf->storage = (struct jmp_buf_storage*)calloc(1, sizeof(struct jmp_buf_storage)); \
|
||||
// ctx->r2 = setjmp(buf->storage->buffer); \
|
||||
//}
|
||||
|
||||
void pause_self(uint8_t *rdram);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@ -1,54 +0,0 @@
|
||||
#ifndef __RECOMP_CONFIG_H__
|
||||
#define __RECOMP_CONFIG_H__
|
||||
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
#include "../ultramodern/config.hpp"
|
||||
|
||||
namespace recomp {
|
||||
constexpr std::u8string_view program_id = u8"Zelda64Recompiled";
|
||||
constexpr std::u8string_view mm_game_id = u8"mm.n64.us.1.0";
|
||||
constexpr std::string_view program_name = "Zelda 64: Recompiled";
|
||||
|
||||
void load_config();
|
||||
void save_config();
|
||||
|
||||
void reset_input_bindings();
|
||||
void reset_cont_input_bindings();
|
||||
void reset_kb_input_bindings();
|
||||
|
||||
std::filesystem::path get_app_folder_path();
|
||||
|
||||
bool get_debug_mode_enabled();
|
||||
void set_debug_mode_enabled(bool enabled);
|
||||
|
||||
enum class AutosaveMode {
|
||||
On,
|
||||
Off,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
enum class AnalogCamMode {
|
||||
On,
|
||||
Off,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(recomp::AutosaveMode, {
|
||||
{recomp::AutosaveMode::On, "On"},
|
||||
{recomp::AutosaveMode::Off, "Off"}
|
||||
});
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(recomp::AnalogCamMode, {
|
||||
{recomp::AnalogCamMode::On, "On"},
|
||||
{recomp::AnalogCamMode::Off, "Off"}
|
||||
});
|
||||
|
||||
AutosaveMode get_autosave_mode();
|
||||
void set_autosave_mode(AutosaveMode mode);
|
||||
|
||||
AnalogCamMode get_analog_cam_mode();
|
||||
void set_analog_cam_mode(AnalogCamMode mode);
|
||||
};
|
||||
|
||||
#endif
|
14
include/recomp_files.h
Normal file
14
include/recomp_files.h
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef __RECOMP_FILES_H__
|
||||
#define __RECOMP_FILES_H__
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
namespace recomp {
|
||||
std::ifstream open_input_file_with_backup(const std::filesystem::path& filepath, std::ios_base::openmode mode = std::ios_base::in);
|
||||
std::ifstream open_input_backup_file(const std::filesystem::path& filepath, std::ios_base::openmode mode = std::ios_base::in);
|
||||
std::ofstream open_output_file_with_backup(const std::filesystem::path& filepath, std::ios_base::openmode mode = std::ios_base::out);
|
||||
bool finalize_output_file_with_backup(const std::filesystem::path& filepath);
|
||||
};
|
||||
|
||||
#endif
|
@ -1,40 +0,0 @@
|
||||
#ifndef __RECOMP_GAME__
|
||||
#define __RECOMP_GAME__
|
||||
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
#include "recomp.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "rt64_layer.h"
|
||||
|
||||
namespace recomp {
|
||||
enum class Game {
|
||||
OoT,
|
||||
MM,
|
||||
None,
|
||||
Quit
|
||||
};
|
||||
enum class RomValidationError {
|
||||
Good,
|
||||
FailedToOpen,
|
||||
NotARom,
|
||||
IncorrectRom,
|
||||
NotYet,
|
||||
IncorrectVersion,
|
||||
OtherError
|
||||
};
|
||||
void check_all_stored_roms();
|
||||
bool load_stored_rom(Game game);
|
||||
RomValidationError select_rom(const std::filesystem::path& rom_path, Game game);
|
||||
bool is_rom_valid(Game game);
|
||||
bool is_rom_loaded();
|
||||
void set_rom_contents(std::vector<uint8_t>&& new_rom);
|
||||
void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes);
|
||||
void do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr);
|
||||
void start(ultramodern::WindowHandle window_handle, const ultramodern::audio_callbacks_t& audio_callbacks, const ultramodern::input_callbacks_t& input_callbacks, const ultramodern::gfx_callbacks_t& gfx_callbacks);
|
||||
void start_game(Game game);
|
||||
void message_box(const char* message);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,50 +0,0 @@
|
||||
#ifndef __RECOMP_HELPERS__
|
||||
#define __RECOMP_HELPERS__
|
||||
|
||||
#include "recomp.h"
|
||||
#include "../ultramodern/ultra64.h"
|
||||
|
||||
template<int index, typename T>
|
||||
T _arg(uint8_t* rdram, recomp_context* ctx) {
|
||||
static_assert(index < 4, "Only args 0 through 3 supported");
|
||||
gpr raw_arg = (&ctx->r4)[index];
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
if constexpr (index < 2) {
|
||||
static_assert(index != 1, "Floats in arg 1 not supported");
|
||||
return ctx->f12.fl;
|
||||
}
|
||||
else {
|
||||
// static_assert in else workaround
|
||||
[] <bool flag = false>() {
|
||||
static_assert(flag, "Floats in a2/a3 not supported");
|
||||
}();
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_pointer_v<T>) {
|
||||
static_assert (!std::is_pointer_v<std::remove_pointer_t<T>>, "Double pointers not supported");
|
||||
return TO_PTR(std::remove_pointer_t<T>, raw_arg);
|
||||
}
|
||||
else if constexpr (std::is_integral_v<T>) {
|
||||
static_assert(sizeof(T) <= 4, "64-bit args not supported");
|
||||
return static_cast<T>(raw_arg);
|
||||
}
|
||||
else {
|
||||
// static_assert in else workaround
|
||||
[] <bool flag = false>() {
|
||||
static_assert(flag, "Unsupported type");
|
||||
}();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void _return(recomp_context* ctx, T val) {
|
||||
static_assert(sizeof(T) <= 4 && "Only 32-bit value returns supported currently");
|
||||
if (std::is_same_v<T, float>) {
|
||||
ctx->f0.fl = val;
|
||||
}
|
||||
else if (std::is_integral_v<T> && sizeof(T) <= 4) {
|
||||
ctx->r2 = int32_t(val);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -9,11 +9,14 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "ultramodern/input.hpp"
|
||||
|
||||
#include "json/json.hpp"
|
||||
|
||||
namespace recomp {
|
||||
// x-macros to build input enums and arrays.
|
||||
// First parameter is the enum name, second parameter is the bit field for the input (or 0 if there is no associated one), third is the readable name.
|
||||
// TODO refactor this to allow projects to rename these, or get rid of the readable name and leave that up to individual projects to map.
|
||||
#define DEFINE_N64_BUTTON_INPUTS() \
|
||||
DEFINE_INPUT(A, 0x8000, "Action") \
|
||||
DEFINE_INPUT(B, 0x4000, "Attack/Cancel") \
|
||||
@ -149,11 +152,13 @@ namespace recomp {
|
||||
InputField& get_input_binding(GameInput input, size_t binding_index, InputDevice device);
|
||||
void set_input_binding(GameInput input, size_t binding_index, InputDevice device, InputField value);
|
||||
|
||||
void get_n64_input(uint16_t* buttons_out, float* x_out, float* y_out);
|
||||
void set_rumble(bool);
|
||||
bool get_n64_input(int controller_num, uint16_t* buttons_out, float* x_out, float* y_out);
|
||||
void set_rumble(int controller_num, bool);
|
||||
void update_rumble();
|
||||
void handle_events();
|
||||
|
||||
ultramodern::input::connected_device_info_t get_connected_device_info(int controller_num);
|
||||
|
||||
// Rumble strength ranges from 0 to 100.
|
||||
int get_rumble_strength();
|
||||
void set_rumble_strength(int strength);
|
||||
@ -168,20 +173,6 @@ namespace recomp {
|
||||
void apply_joystick_deadzone(float x_in, float y_in, float* x_out, float* y_out);
|
||||
void set_right_analog_suppressed(bool suppressed);
|
||||
|
||||
enum class TargetingMode {
|
||||
Switch,
|
||||
Hold,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(recomp::TargetingMode, {
|
||||
{recomp::TargetingMode::Switch, "Switch"},
|
||||
{recomp::TargetingMode::Hold, "Hold"}
|
||||
});
|
||||
|
||||
TargetingMode get_targeting_mode();
|
||||
void set_targeting_mode(TargetingMode mode);
|
||||
|
||||
enum class BackgroundInputMode {
|
||||
On,
|
||||
Off,
|
||||
@ -196,35 +187,8 @@ namespace recomp {
|
||||
BackgroundInputMode get_background_input_mode();
|
||||
void set_background_input_mode(BackgroundInputMode mode);
|
||||
|
||||
enum class CameraInvertMode {
|
||||
InvertNone,
|
||||
InvertX,
|
||||
InvertY,
|
||||
InvertBoth,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(recomp::CameraInvertMode, {
|
||||
{recomp::CameraInvertMode::InvertNone, "InvertNone"},
|
||||
{recomp::CameraInvertMode::InvertX, "InvertX"},
|
||||
{recomp::CameraInvertMode::InvertY, "InvertY"},
|
||||
{recomp::CameraInvertMode::InvertBoth, "InvertBoth"}
|
||||
});
|
||||
|
||||
CameraInvertMode get_camera_invert_mode();
|
||||
void set_camera_invert_mode(CameraInvertMode mode);
|
||||
|
||||
CameraInvertMode get_analog_camera_invert_mode();
|
||||
void set_analog_camera_invert_mode(CameraInvertMode mode);
|
||||
|
||||
bool game_input_disabled();
|
||||
bool all_input_disabled();
|
||||
|
||||
// TODO move these
|
||||
void quicksave_save();
|
||||
void quicksave_load();
|
||||
|
||||
void open_quit_game_prompt();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,10 +0,0 @@
|
||||
#ifndef __RECOMP_OVERLAYS_H__
|
||||
#define __RECOMP_OVERLAYS_H__
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size);
|
||||
extern "C" void unload_overlays(int32_t ram_addr, uint32_t size);
|
||||
void init_overlays();
|
||||
|
||||
#endif
|
@ -14,7 +14,7 @@ namespace Rml {
|
||||
class Event;
|
||||
}
|
||||
|
||||
namespace recomp {
|
||||
namespace recompui {
|
||||
class UiEventListenerInstancer;
|
||||
|
||||
class MenuController {
|
||||
@ -118,6 +118,10 @@ namespace recomp {
|
||||
bool get_cont_active(void);
|
||||
void set_cont_active(bool active);
|
||||
void activate_mouse();
|
||||
|
||||
void message_box(const char* msg);
|
||||
|
||||
void set_render_hooks();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,94 +0,0 @@
|
||||
#ifndef __RSP_H__
|
||||
#define __RSP_H__
|
||||
|
||||
#include "rsp_vu.h"
|
||||
#include "recomp.h"
|
||||
#include <cstdio>
|
||||
|
||||
enum class RspExitReason {
|
||||
Invalid,
|
||||
Broke,
|
||||
ImemOverrun,
|
||||
UnhandledJumpTarget,
|
||||
Unsupported
|
||||
};
|
||||
|
||||
extern uint8_t dmem[];
|
||||
extern uint16_t rspReciprocals[512];
|
||||
extern uint16_t rspInverseSquareRoots[512];
|
||||
|
||||
#define RSP_MEM_B(offset, addr) \
|
||||
(*reinterpret_cast<int8_t*>(dmem + (0xFFF & (((offset) + (addr)) ^ 3))))
|
||||
|
||||
#define RSP_MEM_BU(offset, addr) \
|
||||
(*reinterpret_cast<uint8_t*>(dmem + (0xFFF & (((offset) + (addr)) ^ 3))))
|
||||
|
||||
static inline uint32_t RSP_MEM_W_LOAD(uint32_t offset, uint32_t addr) {
|
||||
uint32_t out;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
reinterpret_cast<uint8_t*>(&out)[i ^ 3] = RSP_MEM_BU(offset + i, addr);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline void RSP_MEM_W_STORE(uint32_t offset, uint32_t addr, uint32_t val) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
RSP_MEM_BU(offset + i, addr) = reinterpret_cast<uint8_t*>(&val)[i ^ 3];
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t RSP_MEM_HU_LOAD(uint32_t offset, uint32_t addr) {
|
||||
uint16_t out;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
reinterpret_cast<uint8_t*>(&out)[(i + 2) ^ 3] = RSP_MEM_BU(offset + i, addr);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline uint32_t RSP_MEM_H_LOAD(uint32_t offset, uint32_t addr) {
|
||||
int16_t out;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
reinterpret_cast<uint8_t*>(&out)[(i + 2) ^ 3] = RSP_MEM_BU(offset + i, addr);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline void RSP_MEM_H_STORE(uint32_t offset, uint32_t addr, uint32_t val) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
RSP_MEM_BU(offset + i, addr) = reinterpret_cast<uint8_t*>(&val)[(i + 2) ^ 3];
|
||||
}
|
||||
}
|
||||
|
||||
#define RSP_ADD32(a, b) \
|
||||
((int32_t)((a) + (b)))
|
||||
|
||||
#define RSP_SUB32(a, b) \
|
||||
((int32_t)((a) - (b)))
|
||||
|
||||
#define RSP_SIGNED(val) \
|
||||
((int32_t)(val))
|
||||
|
||||
#define SET_DMA_DMEM(dmem_addr) dma_dmem_address = (dmem_addr)
|
||||
#define SET_DMA_DRAM(dram_addr) dma_dram_address = (dram_addr)
|
||||
#define DO_DMA_READ(rd_len) dma_rdram_to_dmem(rdram, dma_dmem_address, dma_dram_address, (rd_len))
|
||||
#define DO_DMA_WRITE(wr_len) dma_dmem_to_rdram(rdram, dma_dmem_address, dma_dram_address, (wr_len))
|
||||
|
||||
static inline void dma_rdram_to_dmem(uint8_t* rdram, uint32_t dmem_addr, uint32_t dram_addr, uint32_t rd_len) {
|
||||
rd_len += 1; // Read length is inclusive
|
||||
dram_addr &= 0xFFFFF8;
|
||||
assert(dmem_addr + rd_len <= 0x1000);
|
||||
for (uint32_t i = 0; i < rd_len; i++) {
|
||||
RSP_MEM_B(i, dmem_addr) = MEM_B(0, (int64_t)(int32_t)(dram_addr + i + 0x80000000));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void dma_dmem_to_rdram(uint8_t* rdram, uint32_t dmem_addr, uint32_t dram_addr, uint32_t wr_len) {
|
||||
wr_len += 1; // Write length is inclusive
|
||||
dram_addr &= 0xFFFFF8;
|
||||
assert(dmem_addr + wr_len <= 0x1000);
|
||||
for (uint32_t i = 0; i < wr_len; i++) {
|
||||
MEM_B(0, (int64_t)(int32_t)(dram_addr + i + 0x80000000)) = RSP_MEM_B(i, dmem_addr);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
203
include/rsp_vu.h
203
include/rsp_vu.h
@ -1,203 +0,0 @@
|
||||
// This file is modified from the Ares N64 emulator core. Ares can
|
||||
// be found at https://github.com/ares-emulator/ares. The original license
|
||||
// for this portion of Ares is as follows:
|
||||
// ----------------------------------------------------------------------
|
||||
// ares
|
||||
//
|
||||
// Copyright(c) 2004 - 2021 ares team, Near et al
|
||||
//
|
||||
// Permission to use, copy, modify, and /or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright noticeand this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS.IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
// ----------------------------------------------------------------------
|
||||
#include <cstdint>
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
#define ARCHITECTURE_SUPPORTS_SSE4_1 1
|
||||
#include <nmmintrin.h>
|
||||
using v128 = __m128i;
|
||||
#elif defined(__aarch64__) || defined(_M_ARM64)
|
||||
#define ARCHITECTURE_SUPPORTS_SSE4_1 1
|
||||
#include "sse2neon.h"
|
||||
using v128 = __m128i;
|
||||
#endif
|
||||
|
||||
namespace Accuracy {
|
||||
namespace RSP {
|
||||
#if ARCHITECTURE_SUPPORTS_SSE4_1
|
||||
constexpr bool SISD = false;
|
||||
constexpr bool SIMD = true;
|
||||
#else
|
||||
constexpr bool SISD = true;
|
||||
constexpr bool SIMD = false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
using u8 = uint8_t;
|
||||
using s8 = int8_t;
|
||||
using u16 = uint16_t;
|
||||
using s16 = int16_t;
|
||||
using u32 = uint32_t;
|
||||
using s32 = int32_t;
|
||||
using u64 = uint64_t;
|
||||
using s64 = int64_t;
|
||||
using uint128_t = uint64_t[2];
|
||||
|
||||
template<u32 bits> inline auto sclamp(s64 x) -> s64 {
|
||||
enum : s64 { b = 1ull << (bits - 1), m = b - 1 };
|
||||
return (x > m) ? m : (x < -b) ? -b : x;
|
||||
}
|
||||
|
||||
template<u32 bits> inline auto sclip(s64 x) -> s64 {
|
||||
enum : u64 { b = 1ull << (bits - 1), m = b * 2 - 1 };
|
||||
return ((x & m) ^ b) - b;
|
||||
}
|
||||
|
||||
struct RSP {
|
||||
using r32 = uint32_t;
|
||||
using cr32 = const r32;
|
||||
|
||||
union r128 {
|
||||
struct { uint64_t u128[2]; };
|
||||
#if ARCHITECTURE_SUPPORTS_SSE4_1
|
||||
struct { __m128i v128; };
|
||||
|
||||
operator __m128i() const { return v128; }
|
||||
auto operator=(__m128i value) { v128 = value; }
|
||||
#endif
|
||||
|
||||
auto byte(u32 index) -> uint8_t& { return ((uint8_t*)&u128)[15 - index]; }
|
||||
auto byte(u32 index) const -> uint8_t { return ((uint8_t*)&u128)[15 - index]; }
|
||||
|
||||
auto element(u32 index) -> uint16_t& { return ((uint16_t*)&u128)[7 - index]; }
|
||||
auto element(u32 index) const -> uint16_t { return ((uint16_t*)&u128)[7 - index]; }
|
||||
|
||||
auto u8(u32 index) -> uint8_t& { return ((uint8_t*)&u128)[15 - index]; }
|
||||
auto u8(u32 index) const -> uint8_t { return ((uint8_t*)&u128)[15 - index]; }
|
||||
|
||||
auto s16(u32 index) -> int16_t& { return ((int16_t*)&u128)[7 - index]; }
|
||||
auto s16(u32 index) const -> int16_t { return ((int16_t*)&u128)[7 - index]; }
|
||||
|
||||
auto u16(u32 index) -> uint16_t& { return ((uint16_t*)&u128)[7 - index]; }
|
||||
auto u16(u32 index) const -> uint16_t { return ((uint16_t*)&u128)[7 - index]; }
|
||||
|
||||
//VCx registers
|
||||
auto get(u32 index) const -> bool { return u16(index) != 0; }
|
||||
auto set(u32 index, bool value) -> bool { return u16(index) = 0 - value, value; }
|
||||
|
||||
//vu-registers.cpp
|
||||
inline auto operator()(u32 index) const -> r128;
|
||||
};
|
||||
using cr128 = const r128;
|
||||
|
||||
struct VU {
|
||||
r128 r[32];
|
||||
r128 acch, accm, accl;
|
||||
r128 vcoh, vcol; //16-bit little endian
|
||||
r128 vcch, vccl; //16-bit little endian
|
||||
r128 vce; // 8-bit little endian
|
||||
s16 divin;
|
||||
s16 divout;
|
||||
bool divdp;
|
||||
} vpu;
|
||||
|
||||
static constexpr r128 zero{0};
|
||||
static constexpr r128 invert{(uint64_t)-1, (uint64_t)-1};
|
||||
|
||||
inline auto accumulatorGet(u32 index) const -> u64;
|
||||
inline auto accumulatorSet(u32 index, u64 value) -> void;
|
||||
inline auto accumulatorSaturate(u32 index, bool slice, u16 negative, u16 positive) const -> u16;
|
||||
|
||||
inline auto CFC2(r32& rt, u8 rd) -> void;
|
||||
inline auto CTC2(cr32& rt, u8 rd) -> void;
|
||||
template<u8 e> inline auto LBV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LDV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LFV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LHV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LLV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LPV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LQV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LRV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LSV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LTV(u8 vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LUV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto LWV(r128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto MFC2(r32& rt, cr128& vs) -> void;
|
||||
template<u8 e> inline auto MTC2(cr32& rt, r128& vs) -> void;
|
||||
template<u8 e> inline auto SBV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SDV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SFV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SHV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SLV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SPV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SQV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SRV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SSV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto STV(u8 vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SUV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto SWV(cr128& vt, cr32& rs, s8 imm) -> void;
|
||||
template<u8 e> inline auto VABS(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VADD(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VADDC(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VAND(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VCH(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VCL(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VCR(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VEQ(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VGE(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VLT(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<bool U, u8 e>
|
||||
inline auto VMACF(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMACF(r128& vd, cr128& vs, cr128& vt) -> void { VMACF<0, e>(vd, vs, vt); }
|
||||
template<u8 e> inline auto VMACU(r128& vd, cr128& vs, cr128& vt) -> void { VMACF<1, e>(vd, vs, vt); }
|
||||
inline auto VMACQ(r128& vd) -> void;
|
||||
template<u8 e> inline auto VMADH(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMADL(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMADM(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMADN(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMOV(r128& vd, u8 de, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMRG(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMUDH(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMUDL(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMUDM(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMUDN(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<bool U, u8 e>
|
||||
inline auto VMULF(r128& rd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VMULF(r128& rd, cr128& vs, cr128& vt) -> void { VMULF<0, e>(rd, vs, vt); }
|
||||
template<u8 e> inline auto VMULU(r128& rd, cr128& vs, cr128& vt) -> void { VMULF<1, e>(rd, vs, vt); }
|
||||
template<u8 e> inline auto VMULQ(r128& rd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VNAND(r128& rd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VNE(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
inline auto VNOP() -> void;
|
||||
template<u8 e> inline auto VNOR(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VNXOR(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VOR(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<bool L, u8 e>
|
||||
inline auto VRCP(r128& vd, u8 de, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VRCP(r128& vd, u8 de, cr128& vt) -> void { VRCP<0, e>(vd, de, vt); }
|
||||
template<u8 e> inline auto VRCPL(r128& vd, u8 de, cr128& vt) -> void { VRCP<1, e>(vd, de, vt); }
|
||||
template<u8 e> inline auto VRCPH(r128& vd, u8 de, cr128& vt) -> void;
|
||||
template<bool D, u8 e>
|
||||
inline auto VRND(r128& vd, u8 vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VRNDN(r128& vd, u8 vs, cr128& vt) -> void { VRND<0, e>(vd, vs, vt); }
|
||||
template<u8 e> inline auto VRNDP(r128& vd, u8 vs, cr128& vt) -> void { VRND<1, e>(vd, vs, vt); }
|
||||
template<bool L, u8 e>
|
||||
inline auto VRSQ(r128& vd, u8 de, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VRSQ(r128& vd, u8 de, cr128& vt) -> void { VRSQ<0, e>(vd, de, vt); }
|
||||
template<u8 e> inline auto VRSQL(r128& vd, u8 de, cr128& vt) -> void { VRSQ<1, e>(vd, de, vt); }
|
||||
template<u8 e> inline auto VRSQH(r128& vd, u8 de, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VSAR(r128& vd, cr128& vs) -> void;
|
||||
template<u8 e> inline auto VSUB(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VSUBC(r128& vd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VXOR(r128& rd, cr128& vs, cr128& vt) -> void;
|
||||
template<u8 e> inline auto VZERO(r128& rd, cr128& vs, cr128& vt) -> void;
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -1,50 +0,0 @@
|
||||
#ifndef __RT64_LAYER_H__
|
||||
#define __RT64_LAYER_H__
|
||||
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "../ultramodern/config.hpp"
|
||||
|
||||
namespace RT64 {
|
||||
struct Application;
|
||||
}
|
||||
|
||||
namespace ultramodern {
|
||||
enum class RT64SetupResult {
|
||||
Success,
|
||||
DynamicLibrariesNotFound,
|
||||
InvalidGraphicsAPI,
|
||||
GraphicsAPINotFound,
|
||||
GraphicsDeviceNotFound
|
||||
};
|
||||
|
||||
struct WindowHandle;
|
||||
struct RT64Context {
|
||||
public:
|
||||
~RT64Context();
|
||||
RT64Context(uint8_t* rdram, WindowHandle window_handle, bool developer_mode);
|
||||
bool valid() { return static_cast<bool>(app); }
|
||||
RT64SetupResult get_setup_result() { return setup_result; }
|
||||
|
||||
void update_config(const GraphicsConfig& old_config, const GraphicsConfig& new_config);
|
||||
void enable_instant_present();
|
||||
void send_dl(const OSTask* task);
|
||||
void update_screen(uint32_t vi_origin);
|
||||
void shutdown();
|
||||
void set_dummy_vi();
|
||||
uint32_t get_display_framerate();
|
||||
float get_resolution_scale();
|
||||
void load_shader_cache(std::span<const char> cache_binary);
|
||||
private:
|
||||
RT64SetupResult setup_result;
|
||||
std::unique_ptr<RT64::Application> app;
|
||||
};
|
||||
|
||||
RT64::UserConfiguration::Antialiasing RT64MaxMSAA();
|
||||
bool RT64SamplePositionsSupported();
|
||||
bool RT64HighPrecisionFBEnabled();
|
||||
}
|
||||
|
||||
void set_rt64_hooks();
|
||||
|
||||
#endif
|
||||
|
@ -1,23 +0,0 @@
|
||||
#ifndef __SECTIONS_H__
|
||||
#define __SECTIONS_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "recomp.h"
|
||||
|
||||
#define ARRLEN(x) (sizeof(x) / sizeof((x)[0]))
|
||||
|
||||
typedef struct {
|
||||
recomp_func_t* func;
|
||||
uint32_t offset;
|
||||
} FuncEntry;
|
||||
|
||||
typedef struct {
|
||||
uint32_t rom_addr;
|
||||
uint32_t ram_addr;
|
||||
uint32_t size;
|
||||
FuncEntry *funcs;
|
||||
size_t num_funcs;
|
||||
size_t index;
|
||||
} SectionTableEntry;
|
||||
|
||||
#endif
|
91
include/zelda_config.h
Normal file
91
include/zelda_config.h
Normal file
@ -0,0 +1,91 @@
|
||||
#ifndef __ZELDA_CONFIG_H__
|
||||
#define __ZELDA_CONFIG_H__
|
||||
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
#include "ultramodern/config.hpp"
|
||||
|
||||
namespace zelda64 {
|
||||
constexpr std::u8string_view program_id = u8"Zelda64Recompiled";
|
||||
constexpr std::string_view program_name = "Zelda 64: Recompiled";
|
||||
|
||||
// TODO: Move loading configs to the runtime once we have a way to allow per-project customization.
|
||||
void load_config();
|
||||
void save_config();
|
||||
|
||||
void reset_input_bindings();
|
||||
void reset_cont_input_bindings();
|
||||
void reset_kb_input_bindings();
|
||||
|
||||
std::filesystem::path get_app_folder_path();
|
||||
|
||||
bool get_debug_mode_enabled();
|
||||
void set_debug_mode_enabled(bool enabled);
|
||||
|
||||
enum class AutosaveMode {
|
||||
On,
|
||||
Off,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(zelda64::AutosaveMode, {
|
||||
{zelda64::AutosaveMode::On, "On"},
|
||||
{zelda64::AutosaveMode::Off, "Off"}
|
||||
});
|
||||
|
||||
enum class TargetingMode {
|
||||
Switch,
|
||||
Hold,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(zelda64::TargetingMode, {
|
||||
{zelda64::TargetingMode::Switch, "Switch"},
|
||||
{zelda64::TargetingMode::Hold, "Hold"}
|
||||
});
|
||||
|
||||
TargetingMode get_targeting_mode();
|
||||
void set_targeting_mode(TargetingMode mode);
|
||||
|
||||
enum class CameraInvertMode {
|
||||
InvertNone,
|
||||
InvertX,
|
||||
InvertY,
|
||||
InvertBoth,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(zelda64::CameraInvertMode, {
|
||||
{zelda64::CameraInvertMode::InvertNone, "InvertNone"},
|
||||
{zelda64::CameraInvertMode::InvertX, "InvertX"},
|
||||
{zelda64::CameraInvertMode::InvertY, "InvertY"},
|
||||
{zelda64::CameraInvertMode::InvertBoth, "InvertBoth"}
|
||||
});
|
||||
|
||||
CameraInvertMode get_camera_invert_mode();
|
||||
void set_camera_invert_mode(CameraInvertMode mode);
|
||||
|
||||
CameraInvertMode get_analog_camera_invert_mode();
|
||||
void set_analog_camera_invert_mode(CameraInvertMode mode);
|
||||
|
||||
enum class AnalogCamMode {
|
||||
On,
|
||||
Off,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(zelda64::AnalogCamMode, {
|
||||
{zelda64::AnalogCamMode::On, "On"},
|
||||
{zelda64::AnalogCamMode::Off, "Off"}
|
||||
});
|
||||
|
||||
AutosaveMode get_autosave_mode();
|
||||
void set_autosave_mode(AutosaveMode mode);
|
||||
|
||||
AnalogCamMode get_analog_cam_mode();
|
||||
void set_analog_cam_mode(AnalogCamMode mode);
|
||||
|
||||
void open_quit_game_prompt();
|
||||
};
|
||||
|
||||
#endif
|
@ -1,10 +1,10 @@
|
||||
#ifndef __RECOMP_DEBUG_H__
|
||||
#define __RECOMP_DEBUG_H__
|
||||
#ifndef __ZELDA_DEBUG_H__
|
||||
#define __ZELDA_DEBUG_H__
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace recomp {
|
||||
namespace zelda64 {
|
||||
struct SceneWarps {
|
||||
int index;
|
||||
std::string name;
|
9
include/zelda_game.h
Normal file
9
include/zelda_game.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef __ZELDA_GAME_H__
|
||||
#define __ZELDA_GAME_H__
|
||||
|
||||
namespace zelda64 {
|
||||
void quicksave_save();
|
||||
void quicksave_load();
|
||||
};
|
||||
|
||||
#endif
|
42
include/zelda_render.h
Normal file
42
include/zelda_render.h
Normal file
@ -0,0 +1,42 @@
|
||||
#ifndef __ZELDA_RENDER_H__
|
||||
#define __ZELDA_RENDER_H__
|
||||
|
||||
#include "common/rt64_user_configuration.h"
|
||||
#include "ultramodern/renderer_context.hpp"
|
||||
|
||||
namespace RT64 {
|
||||
struct Application;
|
||||
}
|
||||
|
||||
namespace zelda64 {
|
||||
namespace renderer {
|
||||
class RT64Context : public ultramodern::renderer::RendererContext {
|
||||
public:
|
||||
~RT64Context() override;
|
||||
RT64Context(uint8_t *rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode);
|
||||
|
||||
bool valid() override { return static_cast<bool>(app); }
|
||||
|
||||
bool update_config(const ultramodern::renderer::GraphicsConfig &old_config, const ultramodern::renderer::GraphicsConfig &new_config) override;
|
||||
|
||||
void enable_instant_present() override;
|
||||
void send_dl(const OSTask *task) override;
|
||||
void update_screen(uint32_t vi_origin) override;
|
||||
void shutdown() override;
|
||||
uint32_t get_display_framerate() const override;
|
||||
float get_resolution_scale() const override;
|
||||
void load_shader_cache(std::span<const char> cache_binary) override;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<RT64::Application> app;
|
||||
};
|
||||
|
||||
std::unique_ptr<ultramodern::renderer::RendererContext> create_render_context(uint8_t *rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode);
|
||||
|
||||
RT64::UserConfiguration::Antialiasing RT64MaxMSAA();
|
||||
bool RT64SamplePositionsSupported();
|
||||
bool RT64HighPrecisionFBEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -1,7 +1,7 @@
|
||||
#ifndef __RECOMP_SOUND_H__
|
||||
#define __RECOMP_SOUND_H__
|
||||
#ifndef __ZELDA_SOUND_H__
|
||||
#define __ZELDA_SOUND_H__
|
||||
|
||||
namespace recomp {
|
||||
namespace zelda64 {
|
||||
void reset_sound_settings();
|
||||
void set_main_volume(int volume);
|
||||
int get_main_volume();
|
1
lib/N64ModernRuntime
Submodule
1
lib/N64ModernRuntime
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 0c1811ca6f8291c6608f1d6626a73e863902ece9
|
2
lib/rt64
2
lib/rt64
@ -1 +1 @@
|
||||
Subproject commit 1adcbea31a04f2403da729eb5dfed3950dd7ec52
|
||||
Subproject commit 55ffc5e786b47e9139cf175c3cc3af62d56d5d31
|
@ -1,8 +1,8 @@
|
||||
TARGET = patches.elf
|
||||
|
||||
CC := clang
|
||||
LD := ld.lld
|
||||
OBJCOPY := llvm-objcopy
|
||||
CC ?= clang
|
||||
LD ?= ld.lld
|
||||
OBJCOPY ?= llvm-objcopy
|
||||
|
||||
CFLAGS := -target mips -mips2 -mabi=32 -O2 -G0 -mno-abicalls -mno-odd-spreg -mno-check-zero-division \
|
||||
-fomit-frame-pointer -ffast-math -fno-unsafe-math-optimizations -fno-builtin-memset \
|
||||
@ -27,7 +27,7 @@ $(C_OBJS): %.o : %.c
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) $< -MMD -MF $(@:.o=.d) -c -o $@
|
||||
|
||||
clean:
|
||||
rm -rf $(C_OBJS) $(TARGET) $(DATABIN)
|
||||
rm -rf $(C_OBJS) $(TARGET) $(DATABIN) $(C_DEPS)
|
||||
|
||||
-include $(C_DEPS)
|
||||
|
||||
|
@ -4,10 +4,15 @@
|
||||
#include "play_patches.h"
|
||||
#include "camera_patches.h"
|
||||
#include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h"
|
||||
#include "overlays/actors/ovl_Boss_04/z_boss_04.h"
|
||||
#include "overlays/actors/ovl_En_Clear_Tag/z_en_clear_tag.h"
|
||||
#include "z64shrink_window.h"
|
||||
#include "z64player.h"
|
||||
|
||||
static bool prev_analog_cam_active = false;
|
||||
static bool can_use_analog_cam = false;
|
||||
static bool analog_cam_active = false;
|
||||
static bool analog_cam_skip_once = false;
|
||||
|
||||
VecGeo analog_camera_pos = { .r = 66.0f, .pitch = 0, .yaw = 0 };
|
||||
|
||||
@ -62,6 +67,11 @@ void update_analog_cam(Camera* c) {
|
||||
analog_cam_active = true;
|
||||
}
|
||||
|
||||
if (analog_cam_skip_once) {
|
||||
analog_cam_active = false;
|
||||
analog_cam_skip_once = false;
|
||||
}
|
||||
|
||||
// Record the Z targeting state.
|
||||
prev_targeting_held = targeting_held;
|
||||
|
||||
@ -1873,3 +1883,221 @@ void analog_cam_post_play_update(PlayState* play) {
|
||||
active_cam->inputDir.y = analog_camera_pos.yaw + DEG_TO_BINANG(180);
|
||||
}
|
||||
}
|
||||
|
||||
bool get_analog_cam_active() {
|
||||
return analog_cam_active;
|
||||
}
|
||||
|
||||
void set_analog_cam_active(bool isActive) {
|
||||
analog_cam_active = isActive;
|
||||
}
|
||||
|
||||
// Calling this will avoid analog cam taking over for the following game loop.
|
||||
// E.g. using left stick inputs while in a deku flower taking priority over right stick.
|
||||
void skip_analog_cam_once() {
|
||||
analog_cam_skip_once = true;
|
||||
analog_cam_active = false;
|
||||
}
|
||||
|
||||
extern void func_809ECD00(Boss04* this, PlayState* play);
|
||||
extern s32 func_800B7298(struct PlayState* play, Actor* csActor, u8 csAction);
|
||||
extern u8 D_809EE4D0;
|
||||
|
||||
// @recomp Patch the Wart boss fight in the Great Bay temple so that the fight starts if you look at it with the right stick analog camera,
|
||||
// instead of requiring entering first person mode mode.
|
||||
void func_809EC568(Boss04* this, PlayState* play) {
|
||||
Player* player = GET_PLAYER(play);
|
||||
f32 x;
|
||||
f32 y;
|
||||
f32 z;
|
||||
s32 pad;
|
||||
// @recomp Manual relocation, TODO remove when automated.
|
||||
u8* D_809EE4D0_relocated = (u8*)actor_relocate(&this->actor, &D_809EE4D0);
|
||||
u16 maxProjectedPosToStartFight;
|
||||
|
||||
// @recomp Change the maximun projected position to start the fight depending on whether analog camera is enabled or not.
|
||||
// The default vanilla value makes it so that you have to pretty much start in the corner in order to be able to get the angle it wants.
|
||||
if (analog_cam_active) {
|
||||
maxProjectedPosToStartFight = 600.0f;
|
||||
} else {
|
||||
maxProjectedPosToStartFight = 300.0f; // vanilla value
|
||||
}
|
||||
|
||||
this->unk_704++;
|
||||
this->unk_1FE = 15;
|
||||
if ((this->unk_708 != 0) && (this->unk_708 < 10)) {
|
||||
this->actor.world.pos.y = (Math_SinS(this->unk_1F4 * 512) * 10.0f) + (this->actor.floorHeight + 160.0f);
|
||||
Matrix_RotateYS(this->actor.yawTowardsPlayer, MTXMODE_NEW);
|
||||
}
|
||||
|
||||
switch (this->unk_708) {
|
||||
case 0:
|
||||
this->unk_2C8 = 50;
|
||||
this->unk_2D0 = 2000.0f;
|
||||
// @recomp do not require being in c-up mode if analog cam is enabled
|
||||
// also, use the new variable instead of the vanilla value to check if the player is looking at the boss.
|
||||
if (((player->stateFlags1 & PLAYER_STATE1_100000) || (recomp_analog_cam_enabled())) && (this->actor.projectedPos.z > 0.0f) &&
|
||||
(fabsf(this->actor.projectedPos.x) < maxProjectedPosToStartFight) && (fabsf(this->actor.projectedPos.y) < maxProjectedPosToStartFight)) {
|
||||
if ((this->unk_704 >= 15) && (CutsceneManager_GetCurrentCsId() == CS_ID_NONE)) {
|
||||
Actor* boss;
|
||||
|
||||
this->unk_708 = 10;
|
||||
this->unk_704 = 0;
|
||||
Cutscene_StartManual(play, &play->csCtx);
|
||||
this->subCamId = Play_CreateSubCamera(play);
|
||||
Play_ChangeCameraStatus(play, CAM_ID_MAIN, CAM_STATUS_WAIT);
|
||||
Play_ChangeCameraStatus(play, this->subCamId, CAM_STATUS_ACTIVE);
|
||||
func_800B7298(play, &this->actor, PLAYER_CSACTION_WAIT);
|
||||
player->actor.world.pos.x = this->unk_6E8;
|
||||
player->actor.world.pos.z = this->unk_6F0 + 410.0f;
|
||||
player->actor.shape.rot.y = 0x7FFF;
|
||||
player->actor.world.rot.y = player->actor.shape.rot.y;
|
||||
Math_Vec3f_Copy(&this->subCamEye, &player->actor.world.pos);
|
||||
this->subCamEye.y += 100.0f;
|
||||
Math_Vec3f_Copy(&this->subCamAt, &this->actor.world.pos);
|
||||
Play_EnableMotionBlur(150);
|
||||
this->subCamFov = 60.0f;
|
||||
|
||||
boss = play->actorCtx.actorLists[ACTORCAT_BOSS].first;
|
||||
while (boss != NULL) {
|
||||
if (boss->id == ACTOR_EN_WATER_EFFECT) {
|
||||
Actor_Kill(boss);
|
||||
}
|
||||
boss = boss->next;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this->unk_704 = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 10:
|
||||
if (this->unk_704 == 3) {
|
||||
Actor_PlaySfx(&this->actor, NA_SE_EN_EYEGOLE_DEMO_EYE);
|
||||
this->unk_74A = 1;
|
||||
}
|
||||
this->unk_2D0 = 10000.0f;
|
||||
this->unk_2C8 = 300;
|
||||
Math_ApproachF(&this->subCamFov, 20.0f, 0.3f, 11.0f);
|
||||
if (this->unk_704 == 40) {
|
||||
this->unk_708 = 11;
|
||||
this->unk_704 = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 11:
|
||||
if (this->unk_704 > 50) {
|
||||
this->unk_708 = 12;
|
||||
this->unk_704 = 0;
|
||||
this->actor.gravity = -3.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
case 13:
|
||||
if (this->unk_704 == 45) {
|
||||
this->unk_708 = 1;
|
||||
this->unk_704 = 0;
|
||||
func_800B7298(play, &this->actor, PLAYER_CSACTION_21);
|
||||
this->actor.gravity = 0.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
case 12:
|
||||
Actor_PlaySfx(&this->actor, NA_SE_EN_ME_ATTACK - SFX_FLAG);
|
||||
Math_ApproachF(&this->subCamAt.x, this->actor.world.pos.x, 0.5f, 1000.0f);
|
||||
Math_ApproachF(&this->subCamAt.y, this->actor.world.pos.y, 0.5f, 1000.0f);
|
||||
Math_ApproachF(&this->subCamAt.z, this->actor.world.pos.z, 0.5f, 1000.0f);
|
||||
if (this->actor.bgCheckFlags & BGCHECKFLAG_GROUND_TOUCH) {
|
||||
Audio_PlaySfx(NA_SE_IT_BIG_BOMB_EXPLOSION);
|
||||
this->unk_6F4 = 15;
|
||||
this->unk_708 = 13;
|
||||
this->unk_704 = 0;
|
||||
this->unk_2DA = 10;
|
||||
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_CLEAR_TAG, this->actor.world.pos.x, this->actor.world.pos.y,
|
||||
this->actor.world.pos.z, 0, 0, 0, CLEAR_TAG_PARAMS(CLEAR_TAG_SPLASH));
|
||||
Actor_PlaySfx(&this->actor, NA_SE_EN_KONB_JUMP_LEV_OLD - SFX_FLAG);
|
||||
this->subCamAtOscillator = 20;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
player->actor.shape.rot.y = 0x7FFF;
|
||||
player->actor.world.rot.y = player->actor.shape.rot.y;
|
||||
Matrix_MultVecZ(-100.0f, &this->subCamEye);
|
||||
|
||||
this->subCamEye.x += player->actor.world.pos.x;
|
||||
this->subCamEye.y = Player_GetHeight(player) + player->actor.world.pos.y + 36.0f;
|
||||
this->subCamEye.z += player->actor.world.pos.z;
|
||||
|
||||
this->subCamAt.x = player->actor.world.pos.x;
|
||||
this->subCamAt.y = (Player_GetHeight(player) + player->actor.world.pos.y) - 4.0f;
|
||||
this->subCamAt.z = player->actor.world.pos.z;
|
||||
|
||||
if (this->unk_704 >= 35) {
|
||||
this->unk_704 = 0;
|
||||
this->unk_708 = 2;
|
||||
this->unk_728 = -200.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
case 3:
|
||||
Matrix_MultVecZ(500.0f, &this->subCamEye);
|
||||
this->subCamEye.x += this->actor.world.pos.x;
|
||||
this->subCamEye.y += this->actor.world.pos.y - 50.0f;
|
||||
this->subCamEye.z += this->actor.world.pos.z;
|
||||
this->subCamAt.x = this->actor.world.pos.x;
|
||||
this->subCamAt.z = this->actor.world.pos.z;
|
||||
this->subCamAt.y = (this->actor.world.pos.y - 70.0f) + this->unk_728;
|
||||
Math_ApproachZeroF(&this->unk_728, 0.05f, this->unk_73C);
|
||||
Math_ApproachF(&this->unk_73C, 3.0f, 1.0f, 0.05f);
|
||||
if (this->unk_704 == 20) {
|
||||
this->unk_708 = 3;
|
||||
}
|
||||
|
||||
if (this->unk_704 == 70) {
|
||||
this->unk_2C8 = 300;
|
||||
this->unk_2D0 = 0.0f;
|
||||
|
||||
*D_809EE4D0_relocated = 1;
|
||||
this->unk_2E2 = 60;
|
||||
this->unk_2E0 = 93;
|
||||
}
|
||||
|
||||
if (this->unk_704 > 140) {
|
||||
Camera* mainCam = Play_GetCamera(play, CAM_ID_MAIN);
|
||||
|
||||
this->unk_708 = 0;
|
||||
func_809ECD00(this, play);
|
||||
mainCam->eye = this->subCamEye;
|
||||
mainCam->eyeNext = this->subCamEye;
|
||||
mainCam->at = this->subCamAt;
|
||||
func_80169AFC(play, this->subCamId, 0);
|
||||
this->subCamId = SUB_CAM_ID_DONE;
|
||||
Cutscene_StopManual(play, &play->csCtx);
|
||||
func_800B7298(play, &this->actor, PLAYER_CSACTION_END);
|
||||
Play_DisableMotionBlur();
|
||||
SET_EVENTINF(EVENTINF_60);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (this->subCamId != SUB_CAM_ID_DONE) {
|
||||
Vec3f subCamAt;
|
||||
|
||||
ShrinkWindow_Letterbox_SetSizeTarget(27);
|
||||
if (this->subCamAtOscillator != 0) {
|
||||
this->subCamAtOscillator--;
|
||||
}
|
||||
Math_Vec3f_Copy(&subCamAt, &this->subCamAt);
|
||||
subCamAt.y += Math_SinS(this->subCamAtOscillator * 0x4000) * this->subCamAtOscillator * 1.5f;
|
||||
Play_SetCameraAtEye(play, this->subCamId, &subCamAt, &this->subCamEye);
|
||||
Play_SetCameraFov(play, this->subCamId, this->subCamFov);
|
||||
Math_ApproachF(&this->subCamFov, 60.0f, 0.1f, 1.0f);
|
||||
}
|
||||
this->actor.shape.rot.y = this->actor.yawTowardsPlayer;
|
||||
x = player->actor.world.pos.x - this->actor.world.pos.x;
|
||||
y = player->actor.world.pos.y - this->actor.world.pos.y;
|
||||
z = player->actor.world.pos.z - this->actor.world.pos.z;
|
||||
this->actor.shape.rot.x = Math_Atan2S(-y, sqrtf(SQ(x) + SQ(z)));
|
||||
}
|
||||
|
@ -58,13 +58,13 @@ s16 KaleidoScope_SetPageVertices(PlayState* play, Vtx* vtx, s16 vtxPage, s16 num
|
||||
#define PIXEL_OFFSET ((1 << 4))
|
||||
|
||||
vtx[4 * row + 0].v.tc[0] = PIXEL_OFFSET;
|
||||
vtx[4 * row + 0].v.tc[1] = (1 << 5) + PIXEL_OFFSET;
|
||||
vtx[4 * row + 0].v.tc[1] = PIXEL_OFFSET;
|
||||
vtx[4 * row + 1].v.tc[0] = PAGE_BG_WIDTH * (1 << 5) + PIXEL_OFFSET;
|
||||
vtx[4 * row + 1].v.tc[1] = (1 << 5) + PIXEL_OFFSET;
|
||||
vtx[4 * row + 1].v.tc[1] = PIXEL_OFFSET;
|
||||
vtx[4 * row + 2].v.tc[0] = PIXEL_OFFSET;
|
||||
vtx[4 * row + 2].v.tc[1] = (cur_y - next_y + 1) * (1 << 5) + PIXEL_OFFSET;
|
||||
vtx[4 * row + 2].v.tc[1] = (cur_y - next_y) * (1 << 5) + PIXEL_OFFSET;
|
||||
vtx[4 * row + 3].v.tc[0] = PAGE_BG_WIDTH * (1 << 5) + PIXEL_OFFSET;
|
||||
vtx[4 * row + 3].v.tc[1] = (cur_y - next_y + 1) * (1 << 5) + PIXEL_OFFSET;
|
||||
vtx[4 * row + 3].v.tc[1] = (cur_y - next_y) * (1 << 5) + PIXEL_OFFSET;
|
||||
|
||||
vtx[4 * row + 0].v.cn[0] = vtx[4 * row + 1].v.cn[0] = vtx[4 * row + 2].v.cn[0] = vtx[4 * row + 3].v.cn[0] = 0;
|
||||
vtx[4 * row + 0].v.cn[1] = vtx[4 * row + 1].v.cn[1] = vtx[4 * row + 2].v.cn[1] = vtx[4 * row + 3].v.cn[1] = 0;
|
||||
@ -245,7 +245,7 @@ Gfx* KaleidoScope_DrawPageSections(Gfx* gfx, Vtx* vertices, TexturePtr* textures
|
||||
G_IM_FMT_IA, G_IM_SIZ_8b, // fmt, siz
|
||||
PAGE_BG_WIDTH + 2, PAGE_BG_HEIGHT + 2, // width, height
|
||||
0, (bg_row + 0) * RECOMP_PAGE_ROW_HEIGHT, // uls, ult
|
||||
PAGE_BG_WIDTH + 2, (bg_row + 1) * RECOMP_PAGE_ROW_HEIGHT + 2, // lrs, lrt
|
||||
PAGE_BG_WIDTH + 2 - 1, (bg_row + 1) * RECOMP_PAGE_ROW_HEIGHT + 2 - 1, // lrs, lrt
|
||||
0, // pal
|
||||
G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP,
|
||||
G_TX_NOMASK, G_TX_NOMASK,
|
||||
@ -253,8 +253,8 @@ Gfx* KaleidoScope_DrawPageSections(Gfx* gfx, Vtx* vertices, TexturePtr* textures
|
||||
gDPSetTileSize(gfx++, G_TX_RENDERTILE,
|
||||
0 << G_TEXTURE_IMAGE_FRAC,
|
||||
0 << G_TEXTURE_IMAGE_FRAC,
|
||||
(PAGE_BG_WIDTH + 2) <<G_TEXTURE_IMAGE_FRAC,
|
||||
(RECOMP_PAGE_ROW_HEIGHT + 2) << G_TEXTURE_IMAGE_FRAC);
|
||||
(PAGE_BG_WIDTH + 2 - 1) <<G_TEXTURE_IMAGE_FRAC,
|
||||
(RECOMP_PAGE_ROW_HEIGHT + 2 - 1) << G_TEXTURE_IMAGE_FRAC);
|
||||
gSPVertex(gfx++, vertices + 4 * bg_row, 4, 0);
|
||||
gSP2Triangles(gfx++, 0, 3, 1, 0x0, 3, 0, 2, 0x0);
|
||||
}
|
||||
|
435
patches/input.c
435
patches/input.c
@ -4,6 +4,7 @@
|
||||
// Decomp rename, TODO update decomp and remove this
|
||||
#define AudioVoice_GetWord func_801A5100
|
||||
#include "z64voice.h"
|
||||
#include "audiothread_cmd.h"
|
||||
|
||||
s32 func_80847190(PlayState* play, Player* this, s32 arg2);
|
||||
s16 func_80832754(Player* this, s32 arg1);
|
||||
@ -2297,3 +2298,437 @@ void draw_dpad_icons(PlayState* play) {
|
||||
|
||||
CLOSE_DISPS(play->state.gfxCtx);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
/* 0x0 */ s8 x;
|
||||
/* 0x1 */ s8 y;
|
||||
} OcarinaControlStick; // size = 0x2
|
||||
|
||||
typedef enum {
|
||||
/* 0x0 */ SFX_CHANNEL_PLAYER0, // SfxPlayerBank
|
||||
/* 0x1 */ SFX_CHANNEL_PLAYER1,
|
||||
/* 0x2 */ SFX_CHANNEL_PLAYER2,
|
||||
/* 0x3 */ SFX_CHANNEL_ITEM0, // SfxItemBank
|
||||
/* 0x4 */ SFX_CHANNEL_ITEM1,
|
||||
/* 0x5 */ SFX_CHANNEL_ENV0, // SfxEnvironmentBank
|
||||
/* 0x6 */ SFX_CHANNEL_ENV1,
|
||||
/* 0x7 */ SFX_CHANNEL_ENV2,
|
||||
/* 0x8 */ SFX_CHANNEL_ENEMY0, // SfxEnemyBank
|
||||
/* 0x9 */ SFX_CHANNEL_ENEMY1,
|
||||
/* 0xA */ SFX_CHANNEL_ENEMY2,
|
||||
/* 0xB */ SFX_CHANNEL_SYSTEM0, // SfxSystemBank
|
||||
/* 0xC */ SFX_CHANNEL_SYSTEM1,
|
||||
/* 0xD */ SFX_CHANNEL_OCARINA, // SfxOcarinaBank
|
||||
/* 0xE */ SFX_CHANNEL_VOICE0, // SfxVoiceBank
|
||||
/* 0xF */ SFX_CHANNEL_VOICE1
|
||||
} SfxChannelIndex; // seqPlayerIndex = 2
|
||||
|
||||
extern u32 sOcarinaFlags;
|
||||
extern u8 sOcarinaDropInputTimer;
|
||||
extern u32 sOcarinaInputButtonStart;
|
||||
extern u32 sOcarinaInputButtonCur;
|
||||
extern u8 sCurOcarinaPitch;
|
||||
extern u8 sCurOcarinaButtonIndex;
|
||||
extern u32 sOcarinaInputButtonPrev;
|
||||
extern s32 sOcarinaInputButtonPress;
|
||||
extern u8 sRecordingState;
|
||||
extern s8 sCurOcarinaBendIndex;
|
||||
extern f32 sCurOcarinaBendFreq;
|
||||
extern s8 sCurOcarinaVibrato;
|
||||
extern OcarinaControlStick sOcarinaInputStickRel;
|
||||
extern u8 sPrevOcarinaPitch;
|
||||
extern f32 sDefaultOcarinaVolume;
|
||||
extern s8 sOcarinaInstrumentId;
|
||||
extern f32 AudioOcarina_BendPitchTwoSemitones(s8 bendIndex);
|
||||
|
||||
// @recomp Patch the function in order to read DPad inputs for the ocarina as well as CButton inputs.
|
||||
void AudioOcarina_PlayControllerInput(u8 isOcarinaSfxSuppressedWhenCancelled) {
|
||||
u32 ocarinaBtnsHeld;
|
||||
|
||||
// Prevents two different ocarina notes from being played on two consecutive frames
|
||||
if ((sOcarinaFlags != 0) && (sOcarinaDropInputTimer != 0)) {
|
||||
sOcarinaDropInputTimer--;
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensures the button pressed to start the ocarina does not also play an ocarina note
|
||||
// @recomp Check for DPad inputs as well.
|
||||
if ((sOcarinaInputButtonStart == 0) ||
|
||||
((sOcarinaInputButtonStart & (BTN_A | BTN_CRIGHT | BTN_CLEFT | BTN_CDOWN | BTN_CUP | BTN_DRIGHT | BTN_DLEFT | BTN_DDOWN | BTN_DUP)) !=
|
||||
(sOcarinaInputButtonCur & (BTN_A | BTN_CRIGHT | BTN_CLEFT | BTN_CDOWN | BTN_CUP | BTN_DRIGHT | BTN_DLEFT | BTN_DDOWN | BTN_DUP)))) {
|
||||
sOcarinaInputButtonStart = 0;
|
||||
if (1) {}
|
||||
sCurOcarinaPitch = OCARINA_PITCH_NONE;
|
||||
sCurOcarinaButtonIndex = OCARINA_BTN_INVALID;
|
||||
// @recomp Check for DPad inputs as well.
|
||||
ocarinaBtnsHeld = (sOcarinaInputButtonCur & (BTN_A | BTN_CRIGHT | BTN_CLEFT | BTN_CDOWN | BTN_CUP | BTN_DRIGHT | BTN_DLEFT | BTN_DDOWN | BTN_DUP)) &
|
||||
(sOcarinaInputButtonPrev & (BTN_A | BTN_CRIGHT | BTN_CLEFT | BTN_CDOWN | BTN_CUP | BTN_DRIGHT | BTN_DLEFT | BTN_DDOWN | BTN_DUP));
|
||||
|
||||
if (!(sOcarinaInputButtonPress & ocarinaBtnsHeld) && (sOcarinaInputButtonCur != 0)) {
|
||||
sOcarinaInputButtonPress = sOcarinaInputButtonCur;
|
||||
} else {
|
||||
sOcarinaInputButtonPress &= ocarinaBtnsHeld;
|
||||
}
|
||||
|
||||
// Interprets and transforms controller input into ocarina buttons and notes
|
||||
if (CHECK_BTN_ANY(sOcarinaInputButtonPress, BTN_A)) {
|
||||
sCurOcarinaPitch = OCARINA_PITCH_D4;
|
||||
sCurOcarinaButtonIndex = OCARINA_BTN_A;
|
||||
|
||||
// @recomp Check for DPad down input as well.
|
||||
} else if (CHECK_BTN_ANY(sOcarinaInputButtonPress, (BTN_CDOWN | BTN_DDOWN))) {
|
||||
sCurOcarinaPitch = OCARINA_PITCH_F4;
|
||||
sCurOcarinaButtonIndex = OCARINA_BTN_C_DOWN;
|
||||
|
||||
// @recomp Check for DPad right input as well.
|
||||
} else if (CHECK_BTN_ANY(sOcarinaInputButtonPress, BTN_CRIGHT | BTN_DRIGHT)) {
|
||||
sCurOcarinaPitch = OCARINA_PITCH_A4;
|
||||
sCurOcarinaButtonIndex = OCARINA_BTN_C_RIGHT;
|
||||
|
||||
// @recomp Check for DPad left input as well.
|
||||
} else if (CHECK_BTN_ANY(sOcarinaInputButtonPress, BTN_CLEFT | BTN_DLEFT)) {
|
||||
sCurOcarinaPitch = OCARINA_PITCH_B4;
|
||||
sCurOcarinaButtonIndex = OCARINA_BTN_C_LEFT;
|
||||
|
||||
// @recomp Check for DPad up input as well.
|
||||
} else if (CHECK_BTN_ANY(sOcarinaInputButtonPress, BTN_CUP | BTN_DUP)) {
|
||||
sCurOcarinaPitch = OCARINA_PITCH_D5;
|
||||
sCurOcarinaButtonIndex = OCARINA_BTN_C_UP;
|
||||
}
|
||||
|
||||
if (sOcarinaInputButtonCur) {}
|
||||
|
||||
// Pressing the R Button will raise the pitch by 1 semitone
|
||||
if ((sCurOcarinaPitch != OCARINA_PITCH_NONE) && CHECK_BTN_ANY(sOcarinaInputButtonCur, BTN_R) &&
|
||||
(sRecordingState != OCARINA_RECORD_SCARECROW_SPAWN)) {
|
||||
sCurOcarinaButtonIndex += OCARINA_BUTTON_FLAG_BFLAT_RAISE; // Flag to resolve B Flat 4
|
||||
sCurOcarinaPitch++; // Raise the pitch by 1 semitone
|
||||
}
|
||||
|
||||
// Pressing the Z Button will lower the pitch by 1 semitone
|
||||
if ((sCurOcarinaPitch != OCARINA_PITCH_NONE) && CHECK_BTN_ANY(sOcarinaInputButtonCur, BTN_Z) &&
|
||||
(sRecordingState != OCARINA_RECORD_SCARECROW_SPAWN)) {
|
||||
sCurOcarinaButtonIndex += OCARINA_BUTTON_FLAG_BFLAT_LOWER; // Flag to resolve B Flat 4
|
||||
sCurOcarinaPitch--; // Lower the pitch by 1 semitone
|
||||
}
|
||||
|
||||
if (sRecordingState != OCARINA_RECORD_SCARECROW_SPAWN) {
|
||||
// Bend the pitch of the note based on y control stick
|
||||
sCurOcarinaBendIndex = sOcarinaInputStickRel.y;
|
||||
sCurOcarinaBendFreq = AudioOcarina_BendPitchTwoSemitones(sCurOcarinaBendIndex);
|
||||
|
||||
// Add vibrato of the ocarina note based on the x control stick
|
||||
sCurOcarinaVibrato = ABS_ALT(sOcarinaInputStickRel.x) >> 2;
|
||||
// Sets vibrato to io port 6
|
||||
AUDIOCMD_CHANNEL_SET_IO(SEQ_PLAYER_SFX, SFX_CHANNEL_OCARINA, 6, sCurOcarinaVibrato);
|
||||
} else {
|
||||
// no bending or vibrato for recording state OCARINA_RECORD_SCARECROW_SPAWN
|
||||
sCurOcarinaBendIndex = 0;
|
||||
sCurOcarinaVibrato = 0;
|
||||
sCurOcarinaBendFreq = 1.0f; // No bend
|
||||
}
|
||||
|
||||
// Processes new and valid notes
|
||||
if ((sCurOcarinaPitch != OCARINA_PITCH_NONE) && (sPrevOcarinaPitch != sCurOcarinaPitch)) {
|
||||
// Sets ocarina instrument Id to io port 7, which is used
|
||||
// as an index in seq 0 to get the true instrument Id
|
||||
AUDIOCMD_CHANNEL_SET_IO(SEQ_PLAYER_SFX, SFX_CHANNEL_OCARINA, 7, sOcarinaInstrumentId - 1);
|
||||
// Sets pitch to io port 5
|
||||
AUDIOCMD_CHANNEL_SET_IO(SEQ_PLAYER_SFX, SFX_CHANNEL_OCARINA, 5, sCurOcarinaPitch);
|
||||
AudioSfx_PlaySfx(NA_SE_OC_OCARINA, &gSfxDefaultPos, 4, &sCurOcarinaBendFreq, &sDefaultOcarinaVolume,
|
||||
&gSfxDefaultReverb);
|
||||
} else if ((sPrevOcarinaPitch != OCARINA_PITCH_NONE) && (sCurOcarinaPitch == OCARINA_PITCH_NONE) &&
|
||||
!isOcarinaSfxSuppressedWhenCancelled) {
|
||||
// Stops ocarina sound when transitioning from playing to not playing a note
|
||||
AudioSfx_StopById(NA_SE_OC_OCARINA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern u8 sOcarinaHasStartedSong;
|
||||
extern u8 sOcarinaStaffPlayingPos;
|
||||
extern u8 sCurOcarinaSongWithoutMusicStaff[8];
|
||||
extern u8 sOcarinaWithoutMusicStaffPos;
|
||||
extern u8 sFirstOcarinaSongIndex;
|
||||
extern u32 sOcarinaAvailableSongFlags;
|
||||
extern u8 sLastOcarinaSongIndex;
|
||||
extern u8 sButtonToPitchMap[5];
|
||||
extern u8 sPlayedOcarinaSongIndexPlusOne;
|
||||
extern u8 sIsOcarinaInputEnabled;
|
||||
extern void AudioOcarina_CheckIfStartedSong(void);
|
||||
extern void AudioOcarina_UpdateCurOcarinaSong(void);
|
||||
|
||||
// @recomp Patch the L button check (for free ocarina playing) to account for DPad ocarina.
|
||||
void AudioOcarina_CheckSongsWithoutMusicStaff(void) {
|
||||
u32 pitch;
|
||||
u8 ocarinaStaffPlayingPosStart;
|
||||
u8 songIndex;
|
||||
u8 j;
|
||||
u8 k;
|
||||
|
||||
// @recomp Add the DPad inputs to the check.
|
||||
if (CHECK_BTN_ANY(sOcarinaInputButtonCur, BTN_L) &&
|
||||
CHECK_BTN_ANY(sOcarinaInputButtonCur, BTN_A | BTN_CRIGHT | BTN_CLEFT | BTN_CDOWN | BTN_CUP | BTN_DRIGHT | BTN_DLEFT | BTN_DDOWN | BTN_DUP)) {
|
||||
AudioOcarina_StartDefault(sOcarinaFlags);
|
||||
return;
|
||||
}
|
||||
|
||||
AudioOcarina_CheckIfStartedSong();
|
||||
|
||||
if (!sOcarinaHasStartedSong) {
|
||||
return;
|
||||
}
|
||||
|
||||
ocarinaStaffPlayingPosStart = sOcarinaStaffPlayingPos;
|
||||
if ((sPrevOcarinaPitch != sCurOcarinaPitch) && (sCurOcarinaPitch != OCARINA_PITCH_NONE)) {
|
||||
sOcarinaStaffPlayingPos++;
|
||||
if (sOcarinaStaffPlayingPos > ARRAY_COUNT(sCurOcarinaSongWithoutMusicStaff)) {
|
||||
sOcarinaStaffPlayingPos = 1;
|
||||
}
|
||||
|
||||
AudioOcarina_UpdateCurOcarinaSong();
|
||||
|
||||
if ((ABS_ALT(sCurOcarinaBendIndex) > 20) && (ocarinaStaffPlayingPosStart != sOcarinaStaffPlayingPos)) {
|
||||
sCurOcarinaSongWithoutMusicStaff[sOcarinaWithoutMusicStaffPos - 1] = OCARINA_PITCH_NONE;
|
||||
} else {
|
||||
sCurOcarinaSongWithoutMusicStaff[sOcarinaWithoutMusicStaffPos - 1] = sCurOcarinaPitch;
|
||||
}
|
||||
|
||||
// This nested for-loop tests to see if the notes from the ocarina are identical
|
||||
// to any of the songIndex from sFirstOcarinaSongIndex to sLastOcarinaSongIndex
|
||||
|
||||
// Loop through each of the songs
|
||||
for (songIndex = sFirstOcarinaSongIndex; songIndex < sLastOcarinaSongIndex; songIndex++) {
|
||||
// Checks to see if the song is available to be played
|
||||
if ((u32)sOcarinaAvailableSongFlags & (1 << songIndex)) {
|
||||
// Loops through all possible starting indices?
|
||||
// Loops through the notes of the song?
|
||||
for (j = 0, k = 0; (j < gOcarinaSongButtons[songIndex].numButtons) && (k == 0) &&
|
||||
(sOcarinaWithoutMusicStaffPos >= gOcarinaSongButtons[songIndex].numButtons);) {
|
||||
|
||||
pitch = sCurOcarinaSongWithoutMusicStaff[(sOcarinaWithoutMusicStaffPos -
|
||||
gOcarinaSongButtons[songIndex].numButtons) +
|
||||
j];
|
||||
|
||||
if (pitch == sButtonToPitchMap[gOcarinaSongButtons[songIndex].buttonIndex[j]]) {
|
||||
j++;
|
||||
} else {
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
// This conditional is true if songIndex = i is detected
|
||||
if (j == gOcarinaSongButtons[songIndex].numButtons) {
|
||||
sPlayedOcarinaSongIndexPlusOne = songIndex + 1;
|
||||
sIsOcarinaInputEnabled = false;
|
||||
sOcarinaFlags = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern s32 Player_GetMovementSpeedAndYaw(Player* this, f32* outSpeedTarget, s16* outYawTarget, f32 speedMode,
|
||||
PlayState* play);
|
||||
extern bool get_analog_cam_active();
|
||||
extern void skip_analog_cam_once();
|
||||
|
||||
// @recomp Updates yaw while inside of deku flower.
|
||||
void func_80855F9C(PlayState* play, Player* this) {
|
||||
f32 speedTarget;
|
||||
s16 yawTarget;
|
||||
|
||||
this->stateFlags2 |= PLAYER_STATE2_20;
|
||||
Player_GetMovementSpeedAndYaw(this, &speedTarget, &yawTarget, 0.018f, play);
|
||||
|
||||
// @recomp If left stick inputs are occurring, prevent analog cam.
|
||||
if ((play->state.input[0].rel.stick_y != 0 || play->state.input[0].rel.stick_x != 0)) {
|
||||
skip_analog_cam_once();
|
||||
}
|
||||
|
||||
if (get_analog_cam_active()) {
|
||||
// @recomp set current yaw to active camera's yaw.
|
||||
this->currentYaw = Camera_GetInputDirYaw(GET_ACTIVE_CAM(play));
|
||||
} else {
|
||||
Math_ScaledStepToS(&this->currentYaw, yawTarget, 0x258);
|
||||
}
|
||||
}
|
||||
|
||||
extern void set_analog_cam_active(bool isActive);
|
||||
extern void Player_Action_4(Player* this, PlayState* play);
|
||||
extern s32 Player_SetAction(PlayState* play, Player* this, PlayerActionFunc actionFunc, s32 arg3);
|
||||
extern LinkAnimationHeader gPlayerAnim_pg_maru_change;
|
||||
|
||||
s32 func_80857950(PlayState* play, Player* this) {
|
||||
// @recomp track if newly going from non-spike roll to spike roll (spike rolling when this->unk_B86[1] == 1)
|
||||
static bool wasOff = true;
|
||||
bool isOff = this->unk_B86[1] == 0;
|
||||
if (wasOff && !isOff) {
|
||||
// @recomp set analog cam to be active now that rolling has started
|
||||
set_analog_cam_active(false);
|
||||
}
|
||||
wasOff = isOff;
|
||||
|
||||
// @recomp Manual relocation, TODO remove when automated.
|
||||
Input* player_control_input = *(Input**)KaleidoManager_GetRamAddr(&sPlayerControlInput);
|
||||
|
||||
if (((this->unk_B86[1] == 0) && !CHECK_BTN_ALL(player_control_input->cur.button, BTN_A)) ||
|
||||
((this->av1.actionVar1 == 3) && (this->actor.velocity.y < 0.0f))) {
|
||||
|
||||
// @recomp Manual relocation, TODO remove when automated.
|
||||
PlayerActionFunc Player_Action_4_reloc = KaleidoManager_GetRamAddr(Player_Action_4);
|
||||
Player_SetAction(play, this, Player_Action_4_reloc, 1);
|
||||
|
||||
Math_Vec3f_Copy(&this->actor.world.pos, &this->actor.prevPos);
|
||||
PlayerAnimation_Change(play, &this->skelAnime, &gPlayerAnim_pg_maru_change, -2.0f / 3.0f, 7.0f, 0.0f,
|
||||
ANIMMODE_ONCE, 0.0f);
|
||||
Player_PlaySfx(this, NA_SE_PL_BALL_TO_GORON);
|
||||
wasOff = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef PlayerAnimationHeader* D_8085BE84_t[PLAYER_ANIMTYPE_MAX];
|
||||
extern PlayerAnimationHeader* D_8085BE84[PLAYER_ANIMGROUP_MAX][PLAYER_ANIMTYPE_MAX];
|
||||
extern LinkAnimationHeader gPlayerAnim_link_normal_backspace;
|
||||
|
||||
extern s32 func_80832F24(Player* this);
|
||||
extern s32 func_8083FE38(Player* this, PlayState* play);
|
||||
extern s32 Player_ActionChange_11(Player* this, PlayState* play);
|
||||
extern void func_8083A98C(Actor* thisx, PlayState* play2);
|
||||
extern void func_80836A98(Player* this, PlayerAnimationHeader* anim, PlayState* play);
|
||||
extern void func_80830B38(Player* this);
|
||||
extern void Player_AnimationPlayLoop(PlayState* play, Player* this, PlayerAnimationHeader* anim);
|
||||
extern s32 Player_UpdateUpperBody(Player* this, PlayState* play);
|
||||
extern void func_8082F164(Player* this, u16 button);
|
||||
extern s32 func_808401F4(PlayState* play, Player* this);
|
||||
extern void func_8082FA5C(PlayState* play, Player* this, PlayerMeleeWeaponState meleeWeaponState);
|
||||
extern s32 func_8083FD80(Player* this, PlayState* play);
|
||||
extern void func_8082DC38(Player* this);
|
||||
extern void func_80836A5C(Player* this, PlayState* play);
|
||||
|
||||
// @recomp Patch the shielding function to respect the aiming axis inversion setting.
|
||||
void Player_Action_18(Player* this, PlayState* play) {
|
||||
//@recomp Manual relocation. TODO remove when automated
|
||||
D_8085BE84_t* D_8085BE84_reloc = (D_8085BE84_t*)KaleidoManager_GetRamAddr(D_8085BE84);
|
||||
Input* sPlayerControlInput_reloc = *(Input**)KaleidoManager_GetRamAddr(&sPlayerControlInput);
|
||||
func_80832F24(this);
|
||||
|
||||
if (this->transformation == PLAYER_FORM_GORON) {
|
||||
SkelAnime_Update(&this->unk_2C8);
|
||||
|
||||
if (!func_8083FE38(this, play)) {
|
||||
if (!Player_ActionChange_11(this, play)) {
|
||||
this->stateFlags1 &= ~PLAYER_STATE1_400000;
|
||||
|
||||
if (this->itemAction <= PLAYER_IA_MINUS1) {
|
||||
func_80123C58(this);
|
||||
}
|
||||
|
||||
func_80836A98(this, D_8085BE84_reloc[PLAYER_ANIMGROUP_defense_end][this->modelAnimType], play);
|
||||
func_80830B38(this);
|
||||
} else {
|
||||
this->stateFlags1 |= PLAYER_STATE1_400000;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (PlayerAnimation_Update(play, &this->skelAnime)) {
|
||||
if (!Player_IsGoronOrDeku(this)) {
|
||||
Player_AnimationPlayLoop(play, this, D_8085BE84_reloc[PLAYER_ANIMGROUP_defense_wait][this->modelAnimType]);
|
||||
}
|
||||
|
||||
this->av2.actionVar2 = 1;
|
||||
this->av1.actionVar1 = 0;
|
||||
}
|
||||
|
||||
if (!Player_IsGoronOrDeku(this)) {
|
||||
this->stateFlags1 |= PLAYER_STATE1_400000;
|
||||
Player_UpdateUpperBody(this, play);
|
||||
this->stateFlags1 &= ~PLAYER_STATE1_400000;
|
||||
if (this->transformation == PLAYER_FORM_ZORA) {
|
||||
func_8082F164(this, BTN_R | BTN_B);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->av2.actionVar2 != 0) {
|
||||
f32 yStick = sPlayerControlInput_reloc->rel.stick_y * 180;
|
||||
f32 xStick = sPlayerControlInput_reloc->rel.stick_x * -120;
|
||||
s16 temp_a0 = this->actor.shape.rot.y - Camera_GetInputDirYaw(GET_ACTIVE_CAM(play));
|
||||
s16 var_a1;
|
||||
s16 temp_ft5;
|
||||
s16 var_a2;
|
||||
s16 var_a3;
|
||||
// @recomp Get the aiming camera inversion state.
|
||||
s32 inverted_x, inverted_y;
|
||||
recomp_get_inverted_axes(&inverted_x, &inverted_y);
|
||||
|
||||
// @recomp Invert the Y and X stick values based on the inverted aiming setting.
|
||||
if (!inverted_y) {
|
||||
yStick = -yStick;
|
||||
}
|
||||
if (inverted_x) {
|
||||
xStick = -xStick;
|
||||
}
|
||||
|
||||
var_a1 = (yStick * Math_CosS(temp_a0)) + (Math_SinS(temp_a0) * xStick);
|
||||
temp_ft5 = (xStick * Math_CosS(temp_a0)) - (Math_SinS(temp_a0) * yStick);
|
||||
|
||||
var_a1 = CLAMP_MAX(var_a1, 0xDAC);
|
||||
|
||||
var_a2 = ABS_ALT(var_a1 - this->actor.focus.rot.x) * 0.25f;
|
||||
var_a2 = CLAMP_MIN(var_a2, 0x64);
|
||||
|
||||
|
||||
var_a3 = ABS_ALT(temp_ft5 - this->upperLimbRot.y) * 0.25f;
|
||||
var_a3 = CLAMP_MIN(var_a3, 0x32);
|
||||
|
||||
Math_ScaledStepToS(&this->actor.focus.rot.x, var_a1, var_a2);
|
||||
|
||||
this->upperLimbRot.x = this->actor.focus.rot.x;
|
||||
Math_ScaledStepToS(&this->upperLimbRot.y, temp_ft5, var_a3);
|
||||
|
||||
if (this->av1.actionVar1 != 0) {
|
||||
if (!func_808401F4(play, this)) {
|
||||
if (this->skelAnime.curFrame < 2.0f) {
|
||||
func_8082FA5C(play, this, PLAYER_MELEE_WEAPON_STATE_1);
|
||||
}
|
||||
} else {
|
||||
this->av2.actionVar2 = 1;
|
||||
this->av1.actionVar1 = 0;
|
||||
}
|
||||
} else if (!func_8083FE38(this, play)) {
|
||||
if (Player_ActionChange_11(this, play)) {
|
||||
func_8083FD80(this, play);
|
||||
} else {
|
||||
this->stateFlags1 &= ~PLAYER_STATE1_400000;
|
||||
func_8082DC38(this);
|
||||
|
||||
if (Player_IsGoronOrDeku(this)) {
|
||||
func_80836A5C(this, play);
|
||||
PlayerAnimation_Change(play, &this->skelAnime, this->skelAnime.animation, 1.0f,
|
||||
Animation_GetLastFrame(this->skelAnime.animation), 0.0f, 2, 0.0f);
|
||||
} else {
|
||||
if (this->itemAction <= PLAYER_IA_MINUS1) {
|
||||
func_80123C58(this);
|
||||
}
|
||||
|
||||
func_80836A98(this, D_8085BE84_reloc[PLAYER_ANIMGROUP_defense_end][this->modelAnimType], play);
|
||||
}
|
||||
|
||||
Player_PlaySfx(this, NA_SE_IT_SHIELD_REMOVE);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->stateFlags1 |= PLAYER_STATE1_400000;
|
||||
Player_SetModelsForHoldingShield(this);
|
||||
this->unk_AA6 |= 0xC1;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
#ifdef MIPS
|
||||
#include "ultra64.h"
|
||||
#else
|
||||
#include "recomp.h"
|
||||
#include "librecomp/recomp.h"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -14,6 +14,7 @@ D_808DE5B0 = 0x808DE5B0;
|
||||
sHappyMaskSalesmanAnimationInfo = 0x80AD22C0;
|
||||
D_808890F0 = 0x808890F0;
|
||||
D_8088911C = 0x8088911C;
|
||||
D_809EE4D0 = 0x809EE4D0;
|
||||
|
||||
/* Dummy addresses that get recompiled into function calls */
|
||||
recomp_puts = 0x8F000000;
|
||||
|
@ -1,7 +1,9 @@
|
||||
#include "recomp_config.h"
|
||||
#include "zelda_config.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_sound.h"
|
||||
#include "../../ultramodern/config.hpp"
|
||||
#include "zelda_sound.h"
|
||||
#include "zelda_render.h"
|
||||
#include "ultramodern/config.hpp"
|
||||
#include "librecomp/files.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
@ -17,22 +19,23 @@ constexpr std::u8string_view general_filename = u8"general.json";
|
||||
constexpr std::u8string_view graphics_filename = u8"graphics.json";
|
||||
constexpr std::u8string_view controls_filename = u8"controls.json";
|
||||
constexpr std::u8string_view sound_filename = u8"sound.json";
|
||||
constexpr std::u8string_view program_id = u8"Zelda64Recompiled";
|
||||
|
||||
constexpr auto res_default = ultramodern::Resolution::Auto;
|
||||
constexpr auto hr_default = ultramodern::HUDRatioMode::Clamp16x9;
|
||||
constexpr auto api_default = ultramodern::GraphicsApi::Auto;
|
||||
constexpr auto ar_default = RT64::UserConfiguration::AspectRatio::Expand;
|
||||
constexpr auto msaa_default = RT64::UserConfiguration::Antialiasing::MSAA2X;
|
||||
constexpr auto rr_default = RT64::UserConfiguration::RefreshRate::Display;
|
||||
constexpr auto hpfb_default = ultramodern::HighPrecisionFramebuffer::Auto;
|
||||
constexpr auto res_default = ultramodern::renderer::Resolution::Auto;
|
||||
constexpr auto hr_default = ultramodern::renderer::HUDRatioMode::Clamp16x9;
|
||||
constexpr auto api_default = ultramodern::renderer::GraphicsApi::Auto;
|
||||
constexpr auto ar_default = ultramodern::renderer::AspectRatio::Expand;
|
||||
constexpr auto msaa_default = ultramodern::renderer::Antialiasing::MSAA2X;
|
||||
constexpr auto rr_default = ultramodern::renderer::RefreshRate::Display;
|
||||
constexpr auto hpfb_default = ultramodern::renderer::HighPrecisionFramebuffer::Auto;
|
||||
constexpr int ds_default = 1;
|
||||
constexpr int rr_manual_default = 60;
|
||||
constexpr bool developer_mode_default = false;
|
||||
|
||||
static bool is_steam_deck = false;
|
||||
|
||||
ultramodern::WindowMode wm_default() {
|
||||
return is_steam_deck ? ultramodern::WindowMode::Fullscreen : ultramodern::WindowMode::Windowed;
|
||||
ultramodern::renderer::WindowMode wm_default() {
|
||||
return is_steam_deck ? ultramodern::renderer::WindowMode::Fullscreen : ultramodern::renderer::WindowMode::Windowed;
|
||||
}
|
||||
|
||||
#ifdef __gnu_linux__
|
||||
@ -84,7 +87,7 @@ void call_if_key_exists(void (*func)(T), const json& j, const std::string& key)
|
||||
}
|
||||
|
||||
namespace ultramodern {
|
||||
void to_json(json& j, const GraphicsConfig& config) {
|
||||
void to_json(json& j, const renderer::GraphicsConfig& config) {
|
||||
j = json{
|
||||
{"res_option", config.res_option},
|
||||
{"wm_option", config.wm_option},
|
||||
@ -100,7 +103,7 @@ namespace ultramodern {
|
||||
};
|
||||
}
|
||||
|
||||
void from_json(const json& j, GraphicsConfig& config) {
|
||||
void from_json(const json& j, renderer::GraphicsConfig& config) {
|
||||
config.res_option = from_or_default(j, "res_option", res_default);
|
||||
config.wm_option = from_or_default(j, "wm_option", wm_default());
|
||||
config.hr_option = from_or_default(j, "hr_option", hr_default);
|
||||
@ -126,7 +129,12 @@ namespace recomp {
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path recomp::get_app_folder_path() {
|
||||
std::filesystem::path zelda64::get_app_folder_path() {
|
||||
// directly check for portable.txt (windows and native linux binary)
|
||||
if (std::filesystem::exists("portable.txt")) {
|
||||
return std::filesystem::current_path();
|
||||
}
|
||||
|
||||
std::filesystem::path recomp_dir{};
|
||||
|
||||
#if defined(_WIN32)
|
||||
@ -134,11 +142,16 @@ std::filesystem::path recomp::get_app_folder_path() {
|
||||
PWSTR known_path = NULL;
|
||||
HRESULT result = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &known_path);
|
||||
if (result == S_OK) {
|
||||
recomp_dir = std::filesystem::path{known_path} / recomp::program_id;
|
||||
recomp_dir = std::filesystem::path{known_path} / zelda64::program_id;
|
||||
}
|
||||
|
||||
CoTaskMemFree(known_path);
|
||||
#elif defined(__linux__)
|
||||
// check for APP_FOLDER_PATH env var used by AppImage
|
||||
if (getenv("APP_FOLDER_PATH") != nullptr) {
|
||||
return std::filesystem::path{getenv("APP_FOLDER_PATH")};
|
||||
}
|
||||
|
||||
const char *homedir;
|
||||
|
||||
if ((homedir = getenv("HOME")) == nullptr) {
|
||||
@ -146,53 +159,94 @@ std::filesystem::path recomp::get_app_folder_path() {
|
||||
}
|
||||
|
||||
if (homedir != nullptr) {
|
||||
recomp_dir = std::filesystem::path{homedir} / (std::u8string{u8".config/"} + std::u8string{recomp::program_id});
|
||||
recomp_dir = std::filesystem::path{homedir} / (std::u8string{u8".config/"} + std::u8string{zelda64::program_id});
|
||||
}
|
||||
#endif
|
||||
|
||||
return recomp_dir;
|
||||
}
|
||||
|
||||
void save_general_config(const std::filesystem::path& path) {
|
||||
std::ofstream config_file{path};
|
||||
bool read_json(std::ifstream input_file, nlohmann::json& json_out) {
|
||||
if (!input_file.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
input_file >> json_out;
|
||||
}
|
||||
catch (nlohmann::json::parse_error&) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read_json_with_backups(const std::filesystem::path& path, nlohmann::json& json_out) {
|
||||
// Try reading and parsing the base file.
|
||||
if (read_json(std::ifstream{path}, json_out)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try reading and parsing the backup file.
|
||||
if (read_json(recomp::open_input_backup_file(path), json_out)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Both reads failed.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool save_json_with_backups(const std::filesystem::path& path, const nlohmann::json& json_data) {
|
||||
{
|
||||
std::ofstream output_file = recomp::open_output_file_with_backup(path);
|
||||
if (!output_file.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
output_file << std::setw(4) << json_data;
|
||||
}
|
||||
return recomp::finalize_output_file_with_backup(path);
|
||||
}
|
||||
|
||||
bool save_general_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
recomp::to_json(config_json["targeting_mode"], recomp::get_targeting_mode());
|
||||
zelda64::to_json(config_json["targeting_mode"], zelda64::get_targeting_mode());
|
||||
recomp::to_json(config_json["background_input_mode"], recomp::get_background_input_mode());
|
||||
config_json["rumble_strength"] = recomp::get_rumble_strength();
|
||||
config_json["gyro_sensitivity"] = recomp::get_gyro_sensitivity();
|
||||
config_json["mouse_sensitivity"] = recomp::get_mouse_sensitivity();
|
||||
config_json["joystick_deadzone"] = recomp::get_joystick_deadzone();
|
||||
config_json["autosave_mode"] = recomp::get_autosave_mode();
|
||||
config_json["camera_invert_mode"] = recomp::get_camera_invert_mode();
|
||||
config_json["analog_cam_mode"] = recomp::get_analog_cam_mode();
|
||||
config_json["analog_camera_invert_mode"] = recomp::get_analog_camera_invert_mode();
|
||||
config_json["debug_mode"] = recomp::get_debug_mode_enabled();
|
||||
config_file << std::setw(4) << config_json;
|
||||
config_json["autosave_mode"] = zelda64::get_autosave_mode();
|
||||
config_json["camera_invert_mode"] = zelda64::get_camera_invert_mode();
|
||||
config_json["analog_cam_mode"] = zelda64::get_analog_cam_mode();
|
||||
config_json["analog_camera_invert_mode"] = zelda64::get_analog_camera_invert_mode();
|
||||
config_json["debug_mode"] = zelda64::get_debug_mode_enabled();
|
||||
|
||||
return save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
void set_general_settings_from_json(const nlohmann::json& config_json) {
|
||||
recomp::set_targeting_mode(from_or_default(config_json, "targeting_mode", recomp::TargetingMode::Switch));
|
||||
zelda64::set_targeting_mode(from_or_default(config_json, "targeting_mode", zelda64::TargetingMode::Switch));
|
||||
recomp::set_background_input_mode(from_or_default(config_json, "background_input_mode", recomp::BackgroundInputMode::On));
|
||||
recomp::set_rumble_strength(from_or_default(config_json, "rumble_strength", 25));
|
||||
recomp::set_gyro_sensitivity(from_or_default(config_json, "gyro_sensitivity", 50));
|
||||
recomp::set_mouse_sensitivity(from_or_default(config_json, "mouse_sensitivity", is_steam_deck ? 50 : 0));
|
||||
recomp::set_joystick_deadzone(from_or_default(config_json, "joystick_deadzone", 5));
|
||||
recomp::set_autosave_mode(from_or_default(config_json, "autosave_mode", recomp::AutosaveMode::On));
|
||||
recomp::set_camera_invert_mode(from_or_default(config_json, "camera_invert_mode", recomp::CameraInvertMode::InvertY));
|
||||
recomp::set_analog_cam_mode(from_or_default(config_json, "analog_cam_mode", recomp::AnalogCamMode::Off));
|
||||
recomp::set_analog_camera_invert_mode(from_or_default(config_json, "analog_camera_invert_mode", recomp::CameraInvertMode::InvertNone));
|
||||
recomp::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false));
|
||||
zelda64::set_autosave_mode(from_or_default(config_json, "autosave_mode", zelda64::AutosaveMode::On));
|
||||
zelda64::set_camera_invert_mode(from_or_default(config_json, "camera_invert_mode", zelda64::CameraInvertMode::InvertY));
|
||||
zelda64::set_analog_cam_mode(from_or_default(config_json, "analog_cam_mode", zelda64::AnalogCamMode::Off));
|
||||
zelda64::set_analog_camera_invert_mode(from_or_default(config_json, "analog_camera_invert_mode", zelda64::CameraInvertMode::InvertNone));
|
||||
zelda64::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false));
|
||||
}
|
||||
|
||||
void load_general_config(const std::filesystem::path& path) {
|
||||
std::ifstream config_file{path};
|
||||
bool load_general_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
config_file >> config_json;
|
||||
if (!read_json_with_backups(path, config_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
set_general_settings_from_json(config_json);
|
||||
return true;
|
||||
}
|
||||
|
||||
void assign_mapping(recomp::InputDevice device, recomp::GameInput input, const std::vector<recomp::InputField>& value) {
|
||||
@ -235,21 +289,21 @@ void assign_all_mappings(recomp::InputDevice device, const recomp::DefaultN64Map
|
||||
assign_mapping_complete(device, recomp::GameInput::TOGGLE_MENU, values.toggle_menu);
|
||||
};
|
||||
|
||||
void recomp::reset_input_bindings() {
|
||||
void zelda64::reset_input_bindings() {
|
||||
assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
|
||||
assign_all_mappings(recomp::InputDevice::Controller, recomp::default_n64_controller_mappings);
|
||||
}
|
||||
|
||||
void recomp::reset_cont_input_bindings() {
|
||||
void zelda64::reset_cont_input_bindings() {
|
||||
assign_all_mappings(recomp::InputDevice::Controller, recomp::default_n64_controller_mappings);
|
||||
}
|
||||
|
||||
void recomp::reset_kb_input_bindings() {
|
||||
void zelda64::reset_kb_input_bindings() {
|
||||
assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
|
||||
}
|
||||
|
||||
void reset_graphics_options() {
|
||||
ultramodern::GraphicsConfig new_config{};
|
||||
ultramodern::renderer::GraphicsConfig new_config{};
|
||||
new_config.res_option = res_default;
|
||||
new_config.wm_option = wm_default();
|
||||
new_config.hr_option = hr_default;
|
||||
@ -260,26 +314,25 @@ void reset_graphics_options() {
|
||||
new_config.hpfb_option = hpfb_default;
|
||||
new_config.rr_manual_value = rr_manual_default;
|
||||
new_config.developer_mode = developer_mode_default;
|
||||
ultramodern::set_graphics_config(new_config);
|
||||
ultramodern::renderer::set_graphics_config(new_config);
|
||||
}
|
||||
|
||||
void save_graphics_config(const std::filesystem::path& path) {
|
||||
std::ofstream config_file{path};
|
||||
|
||||
bool save_graphics_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
ultramodern::to_json(config_json, ultramodern::get_graphics_config());
|
||||
config_file << std::setw(4) << config_json;
|
||||
ultramodern::to_json(config_json, ultramodern::renderer::get_graphics_config());
|
||||
return save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
void load_graphics_config(const std::filesystem::path& path) {
|
||||
std::ifstream config_file{path};
|
||||
bool load_graphics_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
if (!read_json_with_backups(path, config_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
config_file >> config_json;
|
||||
|
||||
ultramodern::GraphicsConfig new_config{};
|
||||
ultramodern::renderer::GraphicsConfig new_config{};
|
||||
ultramodern::from_json(config_json, new_config);
|
||||
ultramodern::set_graphics_config(new_config);
|
||||
ultramodern::renderer::set_graphics_config(new_config);
|
||||
return true;
|
||||
}
|
||||
|
||||
void add_input_bindings(nlohmann::json& out, recomp::GameInput input, recomp::InputDevice device) {
|
||||
@ -291,7 +344,7 @@ void add_input_bindings(nlohmann::json& out, recomp::GameInput input, recomp::In
|
||||
}
|
||||
};
|
||||
|
||||
void save_controls_config(const std::filesystem::path& path) {
|
||||
bool save_controls_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
config_json["keyboard"] = {};
|
||||
@ -304,8 +357,7 @@ void save_controls_config(const std::filesystem::path& path) {
|
||||
add_input_bindings(config_json["controller"], cur_input, recomp::InputDevice::Controller);
|
||||
}
|
||||
|
||||
std::ofstream config_file{path};
|
||||
config_file << std::setw(4) << config_json;
|
||||
return save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
bool load_input_device_from_json(const nlohmann::json& config_json, recomp::InputDevice device, const std::string& key) {
|
||||
@ -349,11 +401,11 @@ bool load_input_device_from_json(const nlohmann::json& config_json, recomp::Inpu
|
||||
return true;
|
||||
}
|
||||
|
||||
void load_controls_config(const std::filesystem::path& path) {
|
||||
std::ifstream config_file{path};
|
||||
bool load_controls_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
config_file >> config_json;
|
||||
if (!read_json_with_backups(path, config_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!load_input_device_from_json(config_json, recomp::InputDevice::Keyboard, "keyboard")) {
|
||||
assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
|
||||
@ -362,35 +414,36 @@ void load_controls_config(const std::filesystem::path& path) {
|
||||
if (!load_input_device_from_json(config_json, recomp::InputDevice::Controller, "controller")) {
|
||||
assign_all_mappings(recomp::InputDevice::Controller, recomp::default_n64_controller_mappings);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void save_sound_config(const std::filesystem::path& path) {
|
||||
bool save_sound_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
config_json["main_volume"] = recomp::get_main_volume();
|
||||
config_json["bgm_volume"] = recomp::get_bgm_volume();
|
||||
config_json["low_health_beeps"] = recomp::get_low_health_beeps_enabled();
|
||||
config_json["main_volume"] = zelda64::get_main_volume();
|
||||
config_json["bgm_volume"] = zelda64::get_bgm_volume();
|
||||
config_json["low_health_beeps"] = zelda64::get_low_health_beeps_enabled();
|
||||
|
||||
std::ofstream config_file{path};
|
||||
config_file << std::setw(4) << config_json;
|
||||
return save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
void load_sound_config(const std::filesystem::path& path) {
|
||||
std::ifstream config_file{path};
|
||||
bool load_sound_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
if (!read_json_with_backups(path, config_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
config_file >> config_json;
|
||||
|
||||
recomp::reset_sound_settings();
|
||||
call_if_key_exists(recomp::set_main_volume, config_json, "main_volume");
|
||||
call_if_key_exists(recomp::set_bgm_volume, config_json, "bgm_volume");
|
||||
call_if_key_exists(recomp::set_low_health_beeps_enabled, config_json, "low_health_beeps");
|
||||
zelda64::reset_sound_settings();
|
||||
call_if_key_exists(zelda64::set_main_volume, config_json, "main_volume");
|
||||
call_if_key_exists(zelda64::set_bgm_volume, config_json, "bgm_volume");
|
||||
call_if_key_exists(zelda64::set_low_health_beeps_enabled, config_json, "low_health_beeps");
|
||||
return true;
|
||||
}
|
||||
|
||||
void recomp::load_config() {
|
||||
void zelda64::load_config() {
|
||||
detect_steam_deck();
|
||||
|
||||
std::filesystem::path recomp_dir = recomp::get_app_folder_path();
|
||||
std::filesystem::path recomp_dir = zelda64::get_app_folder_path();
|
||||
std::filesystem::path general_path = recomp_dir / general_filename;
|
||||
std::filesystem::path graphics_path = recomp_dir / graphics_filename;
|
||||
std::filesystem::path controls_path = recomp_dir / controls_filename;
|
||||
@ -400,42 +453,32 @@ void recomp::load_config() {
|
||||
std::filesystem::create_directories(recomp_dir);
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(general_path)) {
|
||||
load_general_config(general_path);
|
||||
}
|
||||
else {
|
||||
// TODO error handling for failing to save config files after resetting them.
|
||||
|
||||
if (!load_general_config(general_path)) {
|
||||
// Set the general settings from an empty json to use defaults.
|
||||
set_general_settings_from_json({});
|
||||
save_general_config(general_path);
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(graphics_path)) {
|
||||
load_graphics_config(graphics_path);
|
||||
}
|
||||
else {
|
||||
if (!load_graphics_config(graphics_path)) {
|
||||
reset_graphics_options();
|
||||
save_graphics_config(graphics_path);
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(controls_path)) {
|
||||
load_controls_config(controls_path);
|
||||
}
|
||||
else {
|
||||
recomp::reset_input_bindings();
|
||||
if (!load_controls_config(controls_path)) {
|
||||
zelda64::reset_input_bindings();
|
||||
save_controls_config(controls_path);
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(sound_path)) {
|
||||
load_sound_config(sound_path);
|
||||
}
|
||||
else {
|
||||
recomp::reset_sound_settings();
|
||||
if (!load_sound_config(sound_path)) {
|
||||
zelda64::reset_sound_settings();
|
||||
save_sound_config(sound_path);
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::save_config() {
|
||||
std::filesystem::path recomp_dir = recomp::get_app_folder_path();
|
||||
void zelda64::save_config() {
|
||||
std::filesystem::path recomp_dir = zelda64::get_app_folder_path();
|
||||
|
||||
if (recomp_dir.empty()) {
|
||||
return;
|
||||
@ -443,6 +486,8 @@ void recomp::save_config() {
|
||||
|
||||
std::filesystem::create_directories(recomp_dir);
|
||||
|
||||
// TODO error handling for failing to save config files.
|
||||
|
||||
save_general_config(recomp_dir / general_filename);
|
||||
save_graphics_config(recomp_dir / graphics_filename);
|
||||
save_controls_config(recomp_dir / controls_filename);
|
||||
|
@ -1,8 +1,8 @@
|
||||
#include <array>
|
||||
|
||||
#include "recomp_helpers.h"
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "recomp_input.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
|
||||
// Arrays that hold the mappings for every input for keyboard and controller respectively.
|
||||
using input_mapping = std::array<recomp::InputField, recomp::bindings_per_input>;
|
||||
@ -75,11 +75,15 @@ void recomp::set_input_binding(recomp::GameInput input, size_t binding_index, re
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::get_n64_input(uint16_t* buttons_out, float* x_out, float* y_out) {
|
||||
bool recomp::get_n64_input(int controller_num, uint16_t* buttons_out, float* x_out, float* y_out) {
|
||||
uint16_t cur_buttons = 0;
|
||||
float cur_x = 0.0f;
|
||||
float cur_y = 0.0f;
|
||||
|
||||
if (controller_num != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!recomp::game_input_disabled()) {
|
||||
for (size_t i = 0; i < n64_button_values.size(); i++) {
|
||||
size_t input_index = (size_t)GameInput::N64_BUTTON_START + i;
|
||||
@ -107,4 +111,6 @@ void recomp::get_n64_input(uint16_t* buttons_out, float* x_out, float* y_out) {
|
||||
*buttons_out = cur_buttons;
|
||||
*x_out = std::clamp(cur_x, -1.0f, 1.0f);
|
||||
*y_out = std::clamp(cur_y, -1.0f, 1.0f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
#include <atomic>
|
||||
#include "recomp_debug.h"
|
||||
#include "recomp_helpers.h"
|
||||
#include "zelda_debug.h"
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "../patches/input.h"
|
||||
|
||||
std::atomic<uint16_t> pending_warp = 0xFFFF;
|
||||
std::atomic<uint32_t> pending_set_time = 0xFFFF;
|
||||
|
||||
void recomp::do_warp(int area, int scene, int entrance) {
|
||||
const recomp::SceneWarps game_scene = recomp::game_warps[area].scenes[scene];
|
||||
void zelda64::do_warp(int area, int scene, int entrance) {
|
||||
const zelda64::SceneWarps game_scene = zelda64::game_warps[area].scenes[scene];
|
||||
int game_scene_index = game_scene.index;
|
||||
pending_warp.store(((game_scene_index & 0xFF) << 8) | ((entrance & 0x0F) << 4));
|
||||
}
|
||||
@ -17,7 +17,7 @@ extern "C" void recomp_get_pending_warp(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, pending_warp.exchange(0xFFFF));
|
||||
}
|
||||
|
||||
void recomp::set_time(uint8_t day, uint8_t hour, uint8_t minute) {
|
||||
void zelda64::set_time(uint8_t day, uint8_t hour, uint8_t minute) {
|
||||
pending_set_time.store((day << 16) | (uint16_t(hour) << 8) | minute);
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "librecomp/recomp.h"
|
||||
#include "recomp_input.h"
|
||||
#include "zelda_config.h"
|
||||
#include "recomp_ui.h"
|
||||
#include "SDL.h"
|
||||
#include "rt64_layer.h"
|
||||
#include "promptfont.h"
|
||||
#include "GamepadMotion.hpp"
|
||||
|
||||
@ -75,13 +75,13 @@ void recomp::stop_scanning_input() {
|
||||
|
||||
void queue_if_enabled(SDL_Event* event) {
|
||||
if (!recomp::all_input_disabled()) {
|
||||
recomp::queue_event(*event);
|
||||
recompui::queue_event(*event);
|
||||
}
|
||||
}
|
||||
|
||||
static std::atomic_bool cursor_enabled = true;
|
||||
|
||||
void recomp::set_cursor_visible(bool visible) {
|
||||
void recompui::set_cursor_visible(bool visible) {
|
||||
cursor_enabled.store(visible);
|
||||
}
|
||||
|
||||
@ -102,15 +102,16 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
{
|
||||
SDL_KeyboardEvent* keyevent = &event->key;
|
||||
|
||||
// Skip repeated events.
|
||||
if (event->key.repeat) {
|
||||
// Skip repeated events when not in the menu
|
||||
if (recompui::get_current_menu() == recompui::Menu::None &&
|
||||
event->key.repeat) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ((keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_RETURN && (keyevent->keysym.mod & SDL_Keymod::KMOD_ALT)) ||
|
||||
keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_F11
|
||||
) {
|
||||
recomp::toggle_fullscreen();
|
||||
recompui::toggle_fullscreen();
|
||||
}
|
||||
if (scanning_device != recomp::InputDevice::COUNT) {
|
||||
if (keyevent->keysym.scancode == SDL_Scancode::SDL_SCANCODE_ESCAPE) {
|
||||
@ -155,12 +156,12 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (recomp::get_current_menu() != recomp::Menu::Config) {
|
||||
recomp::set_current_menu(recomp::Menu::Config);
|
||||
if (recompui::get_current_menu() != recompui::Menu::Config) {
|
||||
recompui::set_current_menu(recompui::Menu::Config);
|
||||
}
|
||||
|
||||
recomp::open_quit_game_prompt();
|
||||
recomp::activate_mouse();
|
||||
zelda64::open_quit_game_prompt();
|
||||
recompui::activate_mouse();
|
||||
break;
|
||||
}
|
||||
case SDL_EventType::SDL_MOUSEWHEEL:
|
||||
@ -435,18 +436,35 @@ void recomp::poll_inputs() {
|
||||
bool save_is_held = InputState.keys[SDL_SCANCODE_F5] != 0;
|
||||
bool load_is_held = InputState.keys[SDL_SCANCODE_F7] != 0;
|
||||
if (save_is_held && !save_was_held) {
|
||||
recomp::quicksave_save();
|
||||
zelda64::quicksave_save();
|
||||
}
|
||||
else if (load_is_held && !load_was_held) {
|
||||
recomp::quicksave_load();
|
||||
zelda64::quicksave_load();
|
||||
}
|
||||
save_was_held = save_is_held;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void recomp::set_rumble(bool on) {
|
||||
void recomp::set_rumble(int controller_num, bool on) {
|
||||
if (controller_num == 0) {
|
||||
InputState.rumble_active = on;
|
||||
}
|
||||
}
|
||||
|
||||
ultramodern::input::connected_device_info_t recomp::get_connected_device_info(int controller_num) {
|
||||
switch (controller_num) {
|
||||
case 0:
|
||||
return ultramodern::input::connected_device_info_t {
|
||||
.connected_device = ultramodern::input::Device::Controller,
|
||||
.connected_pak = ultramodern::input::Pak::RumblePak,
|
||||
};
|
||||
}
|
||||
|
||||
return ultramodern::input::connected_device_info_t {
|
||||
.connected_device = ultramodern::input::Device::None,
|
||||
.connected_pak = ultramodern::input::Pak::None,
|
||||
};
|
||||
}
|
||||
|
||||
static float smoothstep(float from, float to, float amount) {
|
||||
@ -649,7 +667,7 @@ void recomp::set_right_analog_suppressed(bool suppressed) {
|
||||
|
||||
bool recomp::game_input_disabled() {
|
||||
// Disable input if any menu is open.
|
||||
return recomp::get_current_menu() != recomp::Menu::None;
|
||||
return recompui::get_current_menu() != recompui::Menu::None;
|
||||
}
|
||||
|
||||
bool recomp::all_input_disabled() {
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
#if 0
|
||||
|
||||
#include "recomp_helpers.h"
|
||||
#include "recomp_input.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "librecomp/input.hpp"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
|
||||
enum class QuicksaveAction {
|
||||
None,
|
||||
@ -14,11 +14,11 @@ enum class QuicksaveAction {
|
||||
|
||||
std::atomic<QuicksaveAction> cur_quicksave_action = QuicksaveAction::None;
|
||||
|
||||
void recomp::quicksave_save() {
|
||||
void zelda64::quicksave_save() {
|
||||
cur_quicksave_action.store(QuicksaveAction::Save);
|
||||
}
|
||||
|
||||
void recomp::quicksave_load() {
|
||||
void zelda64::quicksave_load() {
|
||||
cur_quicksave_action.store(QuicksaveAction::Load);
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,18 @@
|
||||
#include <cmath>
|
||||
|
||||
#include "recomp.h"
|
||||
#include "recomp_overlays.h"
|
||||
#include "recomp_config.h"
|
||||
#include "librecomp/recomp.h"
|
||||
#include "librecomp/overlays.hpp"
|
||||
#include "zelda_config.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_sound.h"
|
||||
#include "recomp_helpers.h"
|
||||
#include "rt64_layer.h"
|
||||
#include "zelda_render.h"
|
||||
#include "zelda_sound.h"
|
||||
#include "librecomp/helpers.hpp"
|
||||
#include "../patches/input.h"
|
||||
#include "../patches/graphics.h"
|
||||
#include "../patches/sound.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "../ultramodern/config.hpp"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "ultramodern/config.hpp"
|
||||
|
||||
extern "C" void recomp_update_inputs(uint8_t* rdram, recomp_context* ctx) {
|
||||
recomp::poll_inputs();
|
||||
@ -59,32 +59,32 @@ extern "C" void recomp_get_target_framerate(uint8_t* rdram, recomp_context* ctx)
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_aspect_ratio(uint8_t* rdram, recomp_context* ctx) {
|
||||
ultramodern::GraphicsConfig graphics_config = ultramodern::get_graphics_config();
|
||||
ultramodern::renderer::GraphicsConfig graphics_config = ultramodern::renderer::get_graphics_config();
|
||||
float original = _arg<0, float>(rdram, ctx);
|
||||
int width, height;
|
||||
recomp::get_window_size(width, height);
|
||||
recompui::get_window_size(width, height);
|
||||
|
||||
switch (graphics_config.ar_option) {
|
||||
case RT64::UserConfiguration::AspectRatio::Original:
|
||||
case ultramodern::renderer::AspectRatio::Original:
|
||||
default:
|
||||
_return(ctx, original);
|
||||
return;
|
||||
case RT64::UserConfiguration::AspectRatio::Expand:
|
||||
case ultramodern::renderer::AspectRatio::Expand:
|
||||
_return(ctx, std::max(static_cast<float>(width) / height, original));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_targeting_mode(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, static_cast<int>(recomp::get_targeting_mode()));
|
||||
_return(ctx, static_cast<int>(zelda64::get_targeting_mode()));
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_bgm_volume(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, recomp::get_bgm_volume() / 100.0f);
|
||||
_return(ctx, zelda64::get_bgm_volume() / 100.0f);
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_low_health_beeps_enabled(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, static_cast<u32>(recomp::get_low_health_beeps_enabled()));
|
||||
_return(ctx, static_cast<u32>(zelda64::get_low_health_beeps_enabled()));
|
||||
}
|
||||
|
||||
extern "C" void recomp_time_us(uint8_t* rdram, recomp_context* ctx) {
|
||||
@ -92,7 +92,7 @@ extern "C" void recomp_time_us(uint8_t* rdram, recomp_context* ctx) {
|
||||
}
|
||||
|
||||
extern "C" void recomp_autosave_enabled(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return(ctx, static_cast<s32>(recomp::get_autosave_mode() == recomp::AutosaveMode::On));
|
||||
_return(ctx, static_cast<s32>(zelda64::get_autosave_mode() == zelda64::AutosaveMode::On));
|
||||
}
|
||||
|
||||
extern "C" void recomp_load_overlays(uint8_t * rdram, recomp_context * ctx) {
|
||||
@ -104,7 +104,7 @@ extern "C" void recomp_load_overlays(uint8_t * rdram, recomp_context * ctx) {
|
||||
}
|
||||
|
||||
extern "C" void recomp_high_precision_fb_enabled(uint8_t * rdram, recomp_context * ctx) {
|
||||
_return(ctx, static_cast<s32>(ultramodern::RT64HighPrecisionFBEnabled()));
|
||||
_return(ctx, static_cast<s32>(zelda64::renderer::RT64HighPrecisionFBEnabled()));
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_resolution_scale(uint8_t* rdram, recomp_context* ctx) {
|
||||
@ -115,24 +115,24 @@ extern "C" void recomp_get_inverted_axes(uint8_t* rdram, recomp_context* ctx) {
|
||||
s32* x_out = _arg<0, s32*>(rdram, ctx);
|
||||
s32* y_out = _arg<1, s32*>(rdram, ctx);
|
||||
|
||||
recomp::CameraInvertMode mode = recomp::get_camera_invert_mode();
|
||||
zelda64::CameraInvertMode mode = zelda64::get_camera_invert_mode();
|
||||
|
||||
*x_out = (mode == recomp::CameraInvertMode::InvertX || mode == recomp::CameraInvertMode::InvertBoth);
|
||||
*y_out = (mode == recomp::CameraInvertMode::InvertY || mode == recomp::CameraInvertMode::InvertBoth);
|
||||
*x_out = (mode == zelda64::CameraInvertMode::InvertX || mode == zelda64::CameraInvertMode::InvertBoth);
|
||||
*y_out = (mode == zelda64::CameraInvertMode::InvertY || mode == zelda64::CameraInvertMode::InvertBoth);
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_analog_inverted_axes(uint8_t* rdram, recomp_context* ctx) {
|
||||
s32* x_out = _arg<0, s32*>(rdram, ctx);
|
||||
s32* y_out = _arg<1, s32*>(rdram, ctx);
|
||||
|
||||
recomp::CameraInvertMode mode = recomp::get_analog_camera_invert_mode();
|
||||
zelda64::CameraInvertMode mode = zelda64::get_analog_camera_invert_mode();
|
||||
|
||||
*x_out = (mode == recomp::CameraInvertMode::InvertX || mode == recomp::CameraInvertMode::InvertBoth);
|
||||
*y_out = (mode == recomp::CameraInvertMode::InvertY || mode == recomp::CameraInvertMode::InvertBoth);
|
||||
*x_out = (mode == zelda64::CameraInvertMode::InvertX || mode == zelda64::CameraInvertMode::InvertBoth);
|
||||
*y_out = (mode == zelda64::CameraInvertMode::InvertY || mode == zelda64::CameraInvertMode::InvertBoth);
|
||||
}
|
||||
|
||||
extern "C" void recomp_analog_cam_enabled(uint8_t* rdram, recomp_context* ctx) {
|
||||
_return<s32>(ctx, recomp::get_analog_cam_mode() == recomp::AnalogCamMode::On);
|
||||
_return<s32>(ctx, zelda64::get_analog_cam_mode() == zelda64::AnalogCamMode::On);
|
||||
}
|
||||
|
||||
extern "C" void recomp_get_camera_inputs(uint8_t* rdram, recomp_context* ctx) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "recomp_debug.h"
|
||||
#include "zelda_debug.h"
|
||||
|
||||
std::vector<recomp::AreaWarps> recomp::game_warps {
|
||||
std::vector<zelda64::AreaWarps> zelda64::game_warps {
|
||||
{ "Clock Town", {
|
||||
{
|
||||
0, "Mayor's Residence", {
|
||||
|
@ -6,11 +6,12 @@
|
||||
#include <filesystem>
|
||||
#include <numeric>
|
||||
#include <stdexcept>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "nfd.h"
|
||||
|
||||
#include "../../ultramodern/ultra64.h"
|
||||
#include "../../ultramodern/ultramodern.hpp"
|
||||
#include "ultramodern/ultra64.h"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#define SDL_MAIN_HANDLED
|
||||
#ifdef _WIN32
|
||||
#include "SDL.h"
|
||||
@ -21,9 +22,15 @@
|
||||
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_config.h"
|
||||
#include "recomp_game.h"
|
||||
#include "recomp_sound.h"
|
||||
#include "zelda_config.h"
|
||||
#include "zelda_sound.h"
|
||||
#include "zelda_render.h"
|
||||
#include "ovl_patches.hpp"
|
||||
#include "librecomp/game.hpp"
|
||||
|
||||
#ifdef HAS_MM_SHADER_CACHE
|
||||
#include "mm_shader_cache.h"
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
@ -31,12 +38,8 @@
|
||||
#include "SDL_syswm.h"
|
||||
#endif
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "../../lib/rt64/src/contrib/stb/stb_image.h"
|
||||
|
||||
extern "C" void init();
|
||||
/*extern "C"*/ void start(ultramodern::WindowHandle window_handle, const ultramodern::audio_callbacks_t* audio_callbacks, const ultramodern::input_callbacks_t* input_callbacks);
|
||||
|
||||
template<typename... Ts>
|
||||
void exit_error(const char* str, Ts ...args) {
|
||||
// TODO pop up an error
|
||||
@ -115,11 +118,11 @@ bool SetImageAsIcon(const char* filename, SDL_Window* window)
|
||||
|
||||
SDL_Window* window;
|
||||
|
||||
ultramodern::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t) {
|
||||
ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t) {
|
||||
window = SDL_CreateWindow("Zelda 64: Recompiled", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 960, SDL_WINDOW_RESIZABLE );
|
||||
#if defined(__linux__)
|
||||
SetImageAsIcon("icons/512.png",window);
|
||||
if (ultramodern::get_graphics_config().wm_option == ultramodern::WindowMode::Fullscreen) { // TODO: Remove once RT64 gets native fullscreen support on Linux
|
||||
if (ultramodern::renderer::get_graphics_config().wm_option == ultramodern::renderer::WindowMode::Fullscreen) { // TODO: Remove once RT64 gets native fullscreen support on Linux
|
||||
SDL_SetWindowFullscreen(window,SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
} else {
|
||||
SDL_SetWindowFullscreen(window,0);
|
||||
@ -135,7 +138,7 @@ ultramodern::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t
|
||||
SDL_GetWindowWMInfo(window, &wmInfo);
|
||||
|
||||
#if defined(_WIN32)
|
||||
return ultramodern::WindowHandle{ wmInfo.info.win.window, GetCurrentThreadId() };
|
||||
return ultramodern::renderer::WindowHandle{ wmInfo.info.win.window, GetCurrentThreadId() };
|
||||
#elif defined(__ANDROID__)
|
||||
static_assert(false && "Unimplemented");
|
||||
#elif defined(__linux__)
|
||||
@ -143,7 +146,7 @@ ultramodern::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t
|
||||
exit_error("Unsupported SDL2 video driver \"%s\". Only X11 is supported on Linux.\n", SDL_GetCurrentVideoDriver());
|
||||
}
|
||||
|
||||
return ultramodern::WindowHandle{ wmInfo.info.x11.display, wmInfo.info.x11.window };
|
||||
return ultramodern::renderer::WindowHandle{ wmInfo.info.x11.display, wmInfo.info.x11.window };
|
||||
#else
|
||||
static_assert(false && "Unimplemented");
|
||||
#endif
|
||||
@ -192,7 +195,7 @@ void queue_samples(int16_t* audio_data, size_t sample_count) {
|
||||
|
||||
// Convert the audio from 16-bit values to floats and swap the audio channels into the
|
||||
// swap buffer to correct for the address xor caused by endianness handling.
|
||||
float cur_main_volume = recomp::get_main_volume() / 100.0f; // Get the current main volume, normalized to 0.0-1.0.
|
||||
float cur_main_volume = zelda64::get_main_volume() / 100.0f; // Get the current main volume, normalized to 0.0-1.0.
|
||||
for (size_t i = 0; i < sample_count; i += input_channels) {
|
||||
swap_buffer[i + 0 + duplicated_input_frames * input_channels] = audio_data[i + 1] * (0.5f / 32768.0f) * cur_main_volume;
|
||||
swap_buffer[i + 1 + duplicated_input_frames * input_channels] = audio_data[i + 0] * (0.5f / 32768.0f) * cur_main_volume;
|
||||
@ -302,6 +305,42 @@ void reset_audio(uint32_t output_freq) {
|
||||
update_audio_converter();
|
||||
}
|
||||
|
||||
extern RspUcodeFunc njpgdspMain;
|
||||
extern RspUcodeFunc aspMain;
|
||||
|
||||
RspUcodeFunc* get_rsp_microcode(const OSTask* task) {
|
||||
switch (task->t.type) {
|
||||
case M_AUDTASK:
|
||||
return aspMain;
|
||||
|
||||
case M_NJPEGTASK:
|
||||
return njpgdspMain;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown task: %" PRIu32 "\n", task->t.type);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void recomp_entrypoint(uint8_t * rdram, recomp_context * ctx);
|
||||
gpr get_entrypoint_address();
|
||||
|
||||
// array of supported GameEntry objects
|
||||
std::vector<recomp::GameEntry> supported_games = {
|
||||
{
|
||||
.rom_hash = 0xEF18B4A9E2386169ULL,
|
||||
.internal_name = "ZELDA MAJORA'S MASK",
|
||||
.game_id = u8"mm.n64.us.1.0",
|
||||
#ifdef HAS_MM_SHADER_CACHE
|
||||
.cache_data = {mm_shader_cache_bytes, sizeof(mm_shader_cache_bytes)},
|
||||
#endif
|
||||
.is_enabled = true,
|
||||
.entrypoint_address = get_entrypoint_address(),
|
||||
.entrypoint = recomp_entrypoint,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
#ifdef _WIN32
|
||||
@ -333,7 +372,29 @@ int main(int argc, char** argv) {
|
||||
SDL_InitSubSystem(SDL_INIT_AUDIO);
|
||||
reset_audio(48000);
|
||||
|
||||
recomp::load_config();
|
||||
// Source controller mappings file
|
||||
if (SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt") < 0) {
|
||||
fprintf(stderr, "Failed to load controller mappings: %s\n", SDL_GetError());
|
||||
}
|
||||
|
||||
// Register supported games and patches
|
||||
for (const auto& game : supported_games) {
|
||||
recomp::register_game(game);
|
||||
}
|
||||
|
||||
zelda64::register_overlays();
|
||||
zelda64::register_patches();
|
||||
|
||||
recomp::register_config_path(zelda64::get_app_folder_path());
|
||||
zelda64::load_config();
|
||||
|
||||
recomp::rsp::callbacks_t rsp_callbacks{
|
||||
.get_rsp_microcode = get_rsp_microcode,
|
||||
};
|
||||
|
||||
ultramodern::renderer::callbacks_t renderer_callbacks{
|
||||
.create_render_context = zelda64::renderer::create_render_context,
|
||||
};
|
||||
|
||||
ultramodern::gfx_callbacks_t gfx_callbacks{
|
||||
.create_gfx = create_gfx,
|
||||
@ -347,13 +408,23 @@ int main(int argc, char** argv) {
|
||||
.set_frequency = set_frequency,
|
||||
};
|
||||
|
||||
ultramodern::input_callbacks_t input_callbacks{
|
||||
ultramodern::input::callbacks_t input_callbacks{
|
||||
.poll_input = recomp::poll_inputs,
|
||||
.get_input = recomp::get_n64_input,
|
||||
.set_rumble = recomp::set_rumble,
|
||||
.get_connected_device_info = recomp::get_connected_device_info,
|
||||
};
|
||||
|
||||
recomp::start({}, audio_callbacks, input_callbacks, gfx_callbacks);
|
||||
ultramodern::events::callbacks_t thread_callbacks{
|
||||
.vi_callback = recomp::update_rumble,
|
||||
.gfx_init_callback = recompui::update_supported_options,
|
||||
};
|
||||
|
||||
ultramodern::error_handling::callbacks_t error_handling_callbacks{
|
||||
.message_box = recompui::message_box,
|
||||
};
|
||||
|
||||
recomp::start({}, rsp_callbacks, renderer_callbacks, audio_callbacks, input_callbacks, gfx_callbacks, thread_callbacks, error_handling_callbacks);
|
||||
|
||||
NFD_Quit();
|
||||
|
||||
|
19
src/main/register_overlays.cpp
Normal file
19
src/main/register_overlays.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "ovl_patches.hpp"
|
||||
#include "../../RecompiledFuncs/recomp_overlays.inl"
|
||||
|
||||
#include "librecomp/overlays.hpp"
|
||||
|
||||
void zelda64::register_overlays() {
|
||||
recomp::overlays::overlay_section_table_data_t sections {
|
||||
.code_sections = section_table,
|
||||
.num_code_sections = ARRLEN(section_table),
|
||||
.total_num_sections = num_sections,
|
||||
};
|
||||
|
||||
recomp::overlays::overlays_by_index_t overlays {
|
||||
.table = overlay_sections_by_index,
|
||||
.len = ARRLEN(overlay_sections_by_index),
|
||||
};
|
||||
|
||||
recomp::overlays::register_overlays(sections, overlays);
|
||||
}
|
10
src/main/register_patches.cpp
Normal file
10
src/main/register_patches.cpp
Normal file
@ -0,0 +1,10 @@
|
||||
#include "ovl_patches.hpp"
|
||||
#include "../../RecompiledPatches/patches_bin.h"
|
||||
#include "../../RecompiledPatches/recomp_overlays.inl"
|
||||
|
||||
#include "librecomp/overlays.hpp"
|
||||
#include "librecomp/game.hpp"
|
||||
|
||||
void zelda64::register_patches() {
|
||||
recomp::overlays::register_patches(mm_patches_bin, sizeof(mm_patches_bin), section_table);
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
// #include <Windows.h>
|
||||
|
||||
#define HLSL_CPU
|
||||
#include "hle/rt64_application.h"
|
||||
#include "rt64_layer.h"
|
||||
#include "rt64_render_hooks.h"
|
||||
|
||||
ultramodern::RT64Context::~RT64Context() = default;
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "ultramodern/config.hpp"
|
||||
|
||||
#include "zelda_render.h"
|
||||
#include "recomp_ui.h"
|
||||
|
||||
static RT64::UserConfiguration::Antialiasing device_max_msaa = RT64::UserConfiguration::Antialiasing::None;
|
||||
static bool sample_positions_supported = false;
|
||||
@ -42,9 +44,7 @@ unsigned int VI_V_BURST_REG = 0;
|
||||
unsigned int VI_X_SCALE_REG = 0;
|
||||
unsigned int VI_Y_SCALE_REG = 0;
|
||||
|
||||
void dummy_check_interrupts() {
|
||||
|
||||
}
|
||||
void dummy_check_interrupts() {}
|
||||
|
||||
RT64::UserConfiguration::Antialiasing compute_max_supported_aa(RT64::RenderSampleCounts bits) {
|
||||
if (bits & RT64::RenderSampleCount::Bits::COUNT_2) {
|
||||
@ -59,77 +59,122 @@ RT64::UserConfiguration::Antialiasing compute_max_supported_aa(RT64::RenderSampl
|
||||
return RT64::UserConfiguration::Antialiasing::None;
|
||||
}
|
||||
|
||||
RT64::UserConfiguration::InternalColorFormat to_rt64(ultramodern::HighPrecisionFramebuffer option) {
|
||||
RT64::UserConfiguration::AspectRatio to_rt64(ultramodern::renderer::AspectRatio option) {
|
||||
switch (option) {
|
||||
case ultramodern::HighPrecisionFramebuffer::Off:
|
||||
case ultramodern::renderer::AspectRatio::Original:
|
||||
return RT64::UserConfiguration::AspectRatio::Original;
|
||||
case ultramodern::renderer::AspectRatio::Expand:
|
||||
return RT64::UserConfiguration::AspectRatio::Expand;
|
||||
case ultramodern::renderer::AspectRatio::Manual:
|
||||
return RT64::UserConfiguration::AspectRatio::Manual;
|
||||
case ultramodern::renderer::AspectRatio::OptionCount:
|
||||
return RT64::UserConfiguration::AspectRatio::OptionCount;
|
||||
}
|
||||
}
|
||||
|
||||
RT64::UserConfiguration::Antialiasing to_rt64(ultramodern::renderer::Antialiasing option) {
|
||||
switch (option) {
|
||||
case ultramodern::renderer::Antialiasing::None:
|
||||
return RT64::UserConfiguration::Antialiasing::None;
|
||||
case ultramodern::renderer::Antialiasing::MSAA2X:
|
||||
return RT64::UserConfiguration::Antialiasing::MSAA2X;
|
||||
case ultramodern::renderer::Antialiasing::MSAA4X:
|
||||
return RT64::UserConfiguration::Antialiasing::MSAA4X;
|
||||
case ultramodern::renderer::Antialiasing::MSAA8X:
|
||||
return RT64::UserConfiguration::Antialiasing::MSAA8X;
|
||||
case ultramodern::renderer::Antialiasing::OptionCount:
|
||||
return RT64::UserConfiguration::Antialiasing::OptionCount;
|
||||
}
|
||||
}
|
||||
|
||||
RT64::UserConfiguration::RefreshRate to_rt64(ultramodern::renderer::RefreshRate option) {
|
||||
switch (option) {
|
||||
case ultramodern::renderer::RefreshRate::Original:
|
||||
return RT64::UserConfiguration::RefreshRate::Original;
|
||||
case ultramodern::renderer::RefreshRate::Display:
|
||||
return RT64::UserConfiguration::RefreshRate::Display;
|
||||
case ultramodern::renderer::RefreshRate::Manual:
|
||||
return RT64::UserConfiguration::RefreshRate::Manual;
|
||||
case ultramodern::renderer::RefreshRate::OptionCount:
|
||||
return RT64::UserConfiguration::RefreshRate::OptionCount;
|
||||
}
|
||||
}
|
||||
|
||||
RT64::UserConfiguration::InternalColorFormat to_rt64(ultramodern::renderer::HighPrecisionFramebuffer option) {
|
||||
switch (option) {
|
||||
case ultramodern::renderer::HighPrecisionFramebuffer::Off:
|
||||
return RT64::UserConfiguration::InternalColorFormat::Standard;
|
||||
case ultramodern::HighPrecisionFramebuffer::On:
|
||||
case ultramodern::renderer::HighPrecisionFramebuffer::On:
|
||||
return RT64::UserConfiguration::InternalColorFormat::High;
|
||||
case ultramodern::HighPrecisionFramebuffer::Auto:
|
||||
case ultramodern::renderer::HighPrecisionFramebuffer::Auto:
|
||||
return RT64::UserConfiguration::InternalColorFormat::Automatic;
|
||||
default:
|
||||
case ultramodern::renderer::HighPrecisionFramebuffer::OptionCount:
|
||||
return RT64::UserConfiguration::InternalColorFormat::OptionCount;
|
||||
}
|
||||
}
|
||||
|
||||
void set_application_user_config(RT64::Application* application, const ultramodern::GraphicsConfig& config) {
|
||||
void set_application_user_config(RT64::Application* application, const ultramodern::renderer::GraphicsConfig& config) {
|
||||
switch (config.res_option) {
|
||||
default:
|
||||
case ultramodern::Resolution::Auto:
|
||||
case ultramodern::renderer::Resolution::Auto:
|
||||
application->userConfig.resolution = RT64::UserConfiguration::Resolution::WindowIntegerScale;
|
||||
application->userConfig.downsampleMultiplier = 1;
|
||||
break;
|
||||
case ultramodern::Resolution::Original:
|
||||
case ultramodern::renderer::Resolution::Original:
|
||||
application->userConfig.resolution = RT64::UserConfiguration::Resolution::Manual;
|
||||
application->userConfig.resolutionMultiplier = config.ds_option;
|
||||
application->userConfig.downsampleMultiplier = config.ds_option;
|
||||
application->userConfig.resolutionMultiplier = std::max(config.ds_option, 1);
|
||||
application->userConfig.downsampleMultiplier = std::max(config.ds_option, 1);
|
||||
break;
|
||||
case ultramodern::Resolution::Original2x:
|
||||
case ultramodern::renderer::Resolution::Original2x:
|
||||
application->userConfig.resolution = RT64::UserConfiguration::Resolution::Manual;
|
||||
application->userConfig.resolutionMultiplier = 2.0 * config.ds_option;
|
||||
application->userConfig.downsampleMultiplier = config.ds_option;
|
||||
application->userConfig.resolutionMultiplier = 2.0 * std::max(config.ds_option, 1);
|
||||
application->userConfig.downsampleMultiplier = std::max(config.ds_option, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (config.hr_option) {
|
||||
default:
|
||||
case ultramodern::HUDRatioMode::Original:
|
||||
case ultramodern::renderer::HUDRatioMode::Original:
|
||||
application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Original;
|
||||
break;
|
||||
case ultramodern::HUDRatioMode::Clamp16x9:
|
||||
case ultramodern::renderer::HUDRatioMode::Clamp16x9:
|
||||
application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Manual;
|
||||
application->userConfig.extAspectTarget = 16.0/9.0;
|
||||
break;
|
||||
case ultramodern::HUDRatioMode::Full:
|
||||
case ultramodern::renderer::HUDRatioMode::Full:
|
||||
application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Expand;
|
||||
break;
|
||||
}
|
||||
|
||||
application->userConfig.aspectRatio = config.ar_option;
|
||||
application->userConfig.antialiasing = config.msaa_option;
|
||||
application->userConfig.refreshRate = config.rr_option;
|
||||
application->userConfig.aspectRatio = to_rt64(config.ar_option);
|
||||
application->userConfig.antialiasing = to_rt64(config.msaa_option);
|
||||
application->userConfig.refreshRate = to_rt64(config.rr_option);
|
||||
application->userConfig.refreshRateTarget = config.rr_manual_value;
|
||||
application->userConfig.internalColorFormat = to_rt64(config.hpfb_option);
|
||||
}
|
||||
|
||||
ultramodern::RT64SetupResult map_setup_result(RT64::Application::SetupResult rt64_result) {
|
||||
ultramodern::renderer::SetupResult map_setup_result(RT64::Application::SetupResult rt64_result) {
|
||||
switch (rt64_result) {
|
||||
case RT64::Application::SetupResult::Success:
|
||||
return ultramodern::RT64SetupResult::Success;
|
||||
return ultramodern::renderer::SetupResult::Success;
|
||||
case RT64::Application::SetupResult::DynamicLibrariesNotFound:
|
||||
return ultramodern::RT64SetupResult::DynamicLibrariesNotFound;
|
||||
return ultramodern::renderer::SetupResult::DynamicLibrariesNotFound;
|
||||
case RT64::Application::SetupResult::InvalidGraphicsAPI:
|
||||
return ultramodern::RT64SetupResult::InvalidGraphicsAPI;
|
||||
return ultramodern::renderer::SetupResult::InvalidGraphicsAPI;
|
||||
case RT64::Application::SetupResult::GraphicsAPINotFound:
|
||||
return ultramodern::RT64SetupResult::GraphicsAPINotFound;
|
||||
return ultramodern::renderer::SetupResult::GraphicsAPINotFound;
|
||||
case RT64::Application::SetupResult::GraphicsDeviceNotFound:
|
||||
return ultramodern::RT64SetupResult::GraphicsDeviceNotFound;
|
||||
return ultramodern::renderer::SetupResult::GraphicsDeviceNotFound;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Unhandled `RT64::Application::SetupResult` ?\n");
|
||||
assert(false);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
ultramodern::RT64Context::RT64Context(uint8_t* rdram, ultramodern::WindowHandle window_handle, bool debug) {
|
||||
zelda64::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::renderer::WindowHandle window_handle, bool debug) {
|
||||
static unsigned char dummy_rom_header[0x40];
|
||||
set_rt64_hooks();
|
||||
recompui::set_render_hooks();
|
||||
|
||||
// Set up the RT64 application core fields.
|
||||
RT64::Application::Core appCore{};
|
||||
@ -140,6 +185,9 @@ ultramodern::RT64Context::RT64Context(uint8_t* rdram, ultramodern::WindowHandle
|
||||
#elif defined(__linux__)
|
||||
appCore.window.display = window_handle.display;
|
||||
appCore.window.window = window_handle.window;
|
||||
#elif defined(__APPLE__)
|
||||
appCore.window.window = window_handle.window;
|
||||
appCore.window.view = window_handle.view;
|
||||
#endif
|
||||
|
||||
appCore.checkInterrupts = dummy_check_interrupts;
|
||||
@ -183,7 +231,7 @@ ultramodern::RT64Context::RT64Context(uint8_t* rdram, ultramodern::WindowHandle
|
||||
app = std::make_unique<RT64::Application>(appCore, appConfig);
|
||||
|
||||
// Set initial user config settings based on the current settings.
|
||||
ultramodern::GraphicsConfig cur_config = ultramodern::get_graphics_config();
|
||||
auto& cur_config = ultramodern::renderer::get_graphics_config();
|
||||
set_application_user_config(app.get(), cur_config);
|
||||
app->userConfig.developerMode = debug;
|
||||
// Force gbi depth branches to prevent LODs from kicking in.
|
||||
@ -192,14 +240,14 @@ ultramodern::RT64Context::RT64Context(uint8_t* rdram, ultramodern::WindowHandle
|
||||
app->enhancementConfig.textureLOD.scale = true;
|
||||
// Pick an API if the user has set an override.
|
||||
switch (cur_config.api_option) {
|
||||
case ultramodern::GraphicsApi::D3D12:
|
||||
case ultramodern::renderer::GraphicsApi::D3D12:
|
||||
app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::D3D12;
|
||||
break;
|
||||
case ultramodern::GraphicsApi::Vulkan:
|
||||
case ultramodern::renderer::GraphicsApi::Vulkan:
|
||||
app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Vulkan;
|
||||
break;
|
||||
default:
|
||||
case ultramodern::GraphicsApi::Auto:
|
||||
case ultramodern::renderer::GraphicsApi::Auto:
|
||||
// Don't override if auto is selected.
|
||||
break;
|
||||
}
|
||||
@ -210,13 +258,13 @@ ultramodern::RT64Context::RT64Context(uint8_t* rdram, ultramodern::WindowHandle
|
||||
thread_id = window_handle.thread_id;
|
||||
#endif
|
||||
setup_result = map_setup_result(app->setup(thread_id));
|
||||
if (setup_result != ultramodern::RT64SetupResult::Success) {
|
||||
if (setup_result != ultramodern::renderer::SetupResult::Success) {
|
||||
app = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the application's fullscreen state.
|
||||
app->setFullScreen(cur_config.wm_option == ultramodern::WindowMode::Fullscreen);
|
||||
app->setFullScreen(cur_config.wm_option == ultramodern::renderer::WindowMode::Fullscreen);
|
||||
|
||||
// Check if the selected device actually supports MSAA sample positions and MSAA for for the formats that will be used
|
||||
// and downgrade the configuration accordingly.
|
||||
@ -235,27 +283,33 @@ ultramodern::RT64Context::RT64Context(uint8_t* rdram, ultramodern::WindowHandle
|
||||
high_precision_fb_enabled = app->shaderLibrary->usesHDR;
|
||||
}
|
||||
|
||||
void ultramodern::RT64Context::send_dl(const OSTask* task) {
|
||||
zelda64::renderer::RT64Context::~RT64Context() = default;
|
||||
|
||||
void zelda64::renderer::RT64Context::send_dl(const OSTask* task) {
|
||||
app->state->rsp->reset();
|
||||
app->interpreter->loadUCodeGBI(task->t.ucode & 0x3FFFFFF, task->t.ucode_data & 0x3FFFFFF, true);
|
||||
app->processDisplayLists(app->core.RDRAM, task->t.data_ptr & 0x3FFFFFF, 0, true);
|
||||
}
|
||||
|
||||
void ultramodern::RT64Context::update_screen(uint32_t vi_origin) {
|
||||
void zelda64::renderer::RT64Context::update_screen(uint32_t vi_origin) {
|
||||
VI_ORIGIN_REG = vi_origin;
|
||||
|
||||
app->updateScreen();
|
||||
}
|
||||
|
||||
void ultramodern::RT64Context::shutdown() {
|
||||
void zelda64::renderer::RT64Context::shutdown() {
|
||||
if (app != nullptr) {
|
||||
app->end();
|
||||
}
|
||||
}
|
||||
|
||||
void ultramodern::RT64Context::update_config(const ultramodern::GraphicsConfig& old_config, const ultramodern::GraphicsConfig& new_config) {
|
||||
bool zelda64::renderer::RT64Context::update_config(const ultramodern::renderer::GraphicsConfig& old_config, const ultramodern::renderer::GraphicsConfig& new_config) {
|
||||
if (old_config == new_config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (new_config.wm_option != old_config.wm_option) {
|
||||
app->setFullScreen(new_config.wm_option == ultramodern::WindowMode::Fullscreen);
|
||||
app->setFullScreen(new_config.wm_option == ultramodern::renderer::WindowMode::Fullscreen);
|
||||
}
|
||||
|
||||
set_application_user_config(app.get(), new_config);
|
||||
@ -265,20 +319,21 @@ void ultramodern::RT64Context::update_config(const ultramodern::GraphicsConfig&
|
||||
if (new_config.msaa_option != old_config.msaa_option) {
|
||||
app->updateMultisampling();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ultramodern::RT64Context::enable_instant_present() {
|
||||
void zelda64::renderer::RT64Context::enable_instant_present() {
|
||||
// Enable the present early presentation mode for minimal latency.
|
||||
app->enhancementConfig.presentation.mode = RT64::EnhancementConfiguration::Presentation::Mode::PresentEarly;
|
||||
|
||||
app->updateEnhancementConfig();
|
||||
}
|
||||
|
||||
uint32_t ultramodern::RT64Context::get_display_framerate() {
|
||||
uint32_t zelda64::renderer::RT64Context::get_display_framerate() const {
|
||||
return app->presentQueue->ext.sharedResources->swapChainRate;
|
||||
}
|
||||
|
||||
float ultramodern::RT64Context::get_resolution_scale() {
|
||||
float zelda64::renderer::RT64Context::get_resolution_scale() const {
|
||||
constexpr int ReferenceHeight = 240;
|
||||
switch (app->userConfig.resolution) {
|
||||
case RT64::UserConfiguration::Resolution::WindowIntegerScale:
|
||||
@ -296,7 +351,7 @@ float ultramodern::RT64Context::get_resolution_scale() {
|
||||
}
|
||||
}
|
||||
|
||||
void ultramodern::RT64Context::load_shader_cache(std::span<const char> cache_binary) {
|
||||
void zelda64::renderer::RT64Context::load_shader_cache(std::span<const char> cache_binary) {
|
||||
// TODO figure out how to avoid a copy here.
|
||||
std::istringstream cache_stream{std::string{cache_binary.data(), cache_binary.size()}};
|
||||
|
||||
@ -306,14 +361,18 @@ void ultramodern::RT64Context::load_shader_cache(std::span<const char> cache_bin
|
||||
}
|
||||
}
|
||||
|
||||
RT64::UserConfiguration::Antialiasing ultramodern::RT64MaxMSAA() {
|
||||
RT64::UserConfiguration::Antialiasing zelda64::renderer::RT64MaxMSAA() {
|
||||
return device_max_msaa;
|
||||
}
|
||||
|
||||
bool ultramodern::RT64SamplePositionsSupported() {
|
||||
std::unique_ptr<ultramodern::renderer::RendererContext> zelda64::renderer::create_render_context(uint8_t* rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode) {
|
||||
return std::make_unique<zelda64::renderer::RT64Context>(rdram, window_handle, developer_mode);
|
||||
}
|
||||
|
||||
bool zelda64::renderer::RT64SamplePositionsSupported() {
|
||||
return sample_positions_supported;
|
||||
}
|
||||
|
||||
bool ultramodern::RT64HighPrecisionFBEnabled() {
|
||||
bool zelda64::renderer::RT64HighPrecisionFBEnabled() {
|
||||
return high_precision_fb_enabled;
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
#include "recomp.h"
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
|
||||
#define VI_NTSC_CLOCK 48681812
|
||||
|
||||
extern "C" void osAiSetFrequency_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
uint32_t freq = ctx->r4;
|
||||
// This makes actual audio frequency more accurate to console, but may not be desirable
|
||||
//uint32_t dacRate = (uint32_t)(((float)VI_NTSC_CLOCK / freq) + 0.5f);
|
||||
//freq = VI_NTSC_CLOCK / dacRate;
|
||||
ctx->r2 = freq;
|
||||
ultramodern::set_audio_frequency(freq);
|
||||
}
|
||||
|
||||
extern "C" void osAiSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ultramodern::queue_audio_buffer(rdram, ctx->r4, ctx->r5);
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osAiGetLength_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = ultramodern::get_remaining_audio_bytes();
|
||||
}
|
||||
|
||||
extern "C" void osAiGetStatus_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = 0x00000000; // Pretend the audio DMAs finish instantly
|
||||
}
|
@ -1,155 +0,0 @@
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp_helpers.h"
|
||||
|
||||
static ultramodern::input_callbacks_t input_callbacks;
|
||||
|
||||
std::chrono::high_resolution_clock::time_point input_poll_time;
|
||||
|
||||
void update_poll_time() {
|
||||
input_poll_time = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
extern "C" void recomp_set_current_frame_poll_id(uint8_t* rdram, recomp_context* ctx) {
|
||||
// TODO reimplement the system for tagging polls with IDs to handle games with multithreaded input polling.
|
||||
}
|
||||
|
||||
extern "C" void recomp_measure_latency(uint8_t* rdram, recomp_context* ctx) {
|
||||
ultramodern::measure_input_latency();
|
||||
}
|
||||
|
||||
void ultramodern::measure_input_latency() {
|
||||
// printf("Delta: %ld micros\n", std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - input_poll_time));
|
||||
}
|
||||
|
||||
void set_input_callbacks(const ultramodern::input_callbacks_t& callbacks) {
|
||||
input_callbacks = callbacks;
|
||||
}
|
||||
|
||||
static int max_controllers = 0;
|
||||
|
||||
extern "C" void osContInit_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) bitpattern = _arg<1, PTR(void)>(rdram, ctx);
|
||||
PTR(void) status = _arg<2, PTR(void)>(rdram, ctx);
|
||||
|
||||
// Set bit 0 to indicate that controller 0 is present
|
||||
MEM_B(0, bitpattern) = 0x01;
|
||||
|
||||
// Mark controller 0 as present
|
||||
MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus)
|
||||
MEM_B(2, status) = 0x00; // status: 0 (from joybus)
|
||||
MEM_B(3, status) = 0x00; // errno: 0 (from libultra)
|
||||
|
||||
max_controllers = 4;
|
||||
|
||||
// Mark controllers 1-3 as not connected
|
||||
for (size_t controller = 1; controller < max_controllers; controller++) {
|
||||
// Libultra doesn't write status or type for absent controllers
|
||||
MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
|
||||
}
|
||||
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
||||
|
||||
extern "C" void osContStartReadData_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
if (input_callbacks.poll_input) {
|
||||
input_callbacks.poll_input();
|
||||
}
|
||||
update_poll_time();
|
||||
|
||||
ultramodern::send_si_message(rdram);
|
||||
}
|
||||
|
||||
extern "C" void osContGetReadData_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) pad = _arg<0, PTR(void)>(rdram, ctx);
|
||||
|
||||
uint16_t buttons = 0;
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
|
||||
if (input_callbacks.get_input) {
|
||||
input_callbacks.get_input(&buttons, &x, &y);
|
||||
}
|
||||
|
||||
if (max_controllers > 0) {
|
||||
// button
|
||||
MEM_H(0, pad) = buttons;
|
||||
// stick_x
|
||||
MEM_B(2, pad) = (int8_t)(127 * x);
|
||||
// stick_y
|
||||
MEM_B(3, pad) = (int8_t)(127 * y);
|
||||
// errno
|
||||
MEM_B(4, pad) = 0;
|
||||
}
|
||||
for (int controller = 1; controller < max_controllers; controller++) {
|
||||
MEM_B(6 * controller + 4, pad) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osContStartQuery_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ultramodern::send_si_message(rdram);
|
||||
}
|
||||
|
||||
extern "C" void osContGetQuery_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
PTR(void) status = _arg<0, PTR(void)>(rdram, ctx);
|
||||
|
||||
// Mark controller 0 as present
|
||||
MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus)
|
||||
MEM_B(2, status) = 0x01; // status: 0x01 (from joybus, indicates that a pak is plugged into the controller)
|
||||
MEM_B(3, status) = 0x00; // errno: 0 (from libultra)
|
||||
|
||||
// Mark controllers 1-3 as not connected
|
||||
for (size_t controller = 1; controller < max_controllers; controller++) {
|
||||
// Libultra doesn't write status or type for absent controllers
|
||||
MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osContSetCh_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
max_controllers = std::min(_arg<0, u8>(rdram, ctx), u8(4));
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
||||
|
||||
extern "C" void __osMotorAccess_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx);
|
||||
s32 flag = _arg<1, s32>(rdram, ctx);
|
||||
s32 channel = MEM_W(8, pfs);
|
||||
|
||||
// Only respect accesses to controller 0.
|
||||
if (channel == 0) {
|
||||
input_callbacks.set_rumble(flag);
|
||||
}
|
||||
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
||||
|
||||
extern "C" void osMotorInit_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) pfs = _arg<1, PTR(void)>(rdram, ctx);
|
||||
s32 channel = _arg<2, s32>(rdram, ctx);
|
||||
MEM_W(8, pfs) = channel;
|
||||
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
||||
|
||||
extern "C" void osMotorStart_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx);
|
||||
s32 channel = MEM_W(8, pfs);
|
||||
|
||||
// Only respect accesses to controller 0.
|
||||
if (channel == 0) {
|
||||
input_callbacks.set_rumble(true);
|
||||
}
|
||||
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
||||
|
||||
extern "C" void osMotorStop_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx);
|
||||
s32 channel = MEM_W(8, pfs);
|
||||
|
||||
// Only respect accesses to controller 0.
|
||||
if (channel == 0) {
|
||||
input_callbacks.set_rumble(false);
|
||||
}
|
||||
|
||||
_return<s32>(ctx, 0);
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
#include "recomp.h"
|
||||
|
||||
enum class RDPStatusBit {
|
||||
XbusDmem = 0,
|
||||
Freeze = 1,
|
||||
Flush = 2,
|
||||
CommandBusy = 6,
|
||||
BufferReady = 7,
|
||||
DmaBusy = 8,
|
||||
EndValid = 9,
|
||||
StartValid = 10,
|
||||
};
|
||||
|
||||
constexpr void update_bit(uint32_t& state, uint32_t flags, RDPStatusBit bit) {
|
||||
int set_bit_pos = (int)bit * 2 + 0;
|
||||
int reset_bit_pos = (int)bit * 2 + 1;
|
||||
bool set = (flags & (1U << set_bit_pos)) != 0;
|
||||
bool reset = (flags & (1U << reset_bit_pos)) != 0;
|
||||
|
||||
if (set ^ reset) {
|
||||
if (set) {
|
||||
state |= (1U << (int)bit);
|
||||
}
|
||||
else {
|
||||
state &= ~(1U << (int)bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t rdp_state = 1 << (int)RDPStatusBit::BufferReady;
|
||||
|
||||
extern "C" void osDpSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osDpGetStatus_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = rdp_state;
|
||||
}
|
||||
|
||||
extern "C" void osDpSetStatus_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
update_bit(rdp_state, ctx->r4, RDPStatusBit::XbusDmem);
|
||||
update_bit(rdp_state, ctx->r4, RDPStatusBit::Freeze);
|
||||
update_bit(rdp_state, ctx->r4, RDPStatusBit::Flush);
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
#include "recomp.h"
|
||||
#include "../ultramodern/ultra64.h"
|
||||
|
||||
void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count);
|
||||
void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count);
|
||||
|
||||
constexpr int eeprom_block_size = 8;
|
||||
constexpr int eep4_size = 4096;
|
||||
constexpr int eep4_block_count = eep4_size / eeprom_block_size;
|
||||
constexpr int eep16_size = 16384;
|
||||
constexpr int eep16_block_count = eep16_size / eeprom_block_size;
|
||||
|
||||
extern "C" void osEepromProbe_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = 0x02; // EEP16K
|
||||
}
|
||||
|
||||
extern "C" void osEepromWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
assert(false);// ctx->r2 = 8; // CONT_NO_RESPONSE_ERROR
|
||||
}
|
||||
|
||||
extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
uint8_t eep_address = ctx->r5;
|
||||
gpr buffer = ctx->r6;
|
||||
int32_t nbytes = ctx->r7;
|
||||
|
||||
assert(!(nbytes & 7));
|
||||
assert(eep_address * eeprom_block_size + nbytes <= eep16_size);
|
||||
|
||||
save_write(rdram, buffer, eep_address * eeprom_block_size, nbytes);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osEepromRead_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
assert(false);// ctx->r2 = 8; // CONT_NO_RESPONSE_ERROR
|
||||
}
|
||||
|
||||
extern "C" void osEepromLongRead_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
uint8_t eep_address = ctx->r5;
|
||||
gpr buffer = ctx->r6;
|
||||
int32_t nbytes = ctx->r7;
|
||||
|
||||
assert(!(nbytes & 7));
|
||||
assert(eep_address * eeprom_block_size + nbytes <= eep16_size);
|
||||
|
||||
save_read(rdram, buffer, eep_address * eeprom_block_size, nbytes);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,11 +0,0 @@
|
||||
#ifndef __EUC_JP_H__
|
||||
#define __EUC_JP_H__
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace Encoding {
|
||||
std::string decode_eucjp(std::string_view src);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,141 +0,0 @@
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
// TODO move this out into ultramodern code
|
||||
|
||||
constexpr uint32_t flash_size = 1024 * 1024 / 8; // 1Mbit
|
||||
constexpr uint32_t page_size = 128;
|
||||
constexpr uint32_t pages_per_sector = 128;
|
||||
constexpr uint32_t page_count = flash_size / page_size;
|
||||
constexpr uint32_t sector_size = page_size * pages_per_sector;
|
||||
constexpr uint32_t sector_count = flash_size / sector_size;
|
||||
|
||||
void save_write_ptr(const void* in, uint32_t offset, uint32_t count);
|
||||
void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count);
|
||||
void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count);
|
||||
void save_clear(uint32_t start, uint32_t size, char value);
|
||||
|
||||
std::array<char, page_size> write_buffer;
|
||||
|
||||
extern "C" void osFlashInit_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = ultramodern::flash_handle;
|
||||
}
|
||||
|
||||
extern "C" void osFlashReadStatus_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
PTR(u8) flash_status = ctx->r4;
|
||||
|
||||
MEM_B(0, flash_status) = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashReadId_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
PTR(u32) flash_type = ctx->r4;
|
||||
PTR(u32) flash_maker = ctx->r5;
|
||||
|
||||
// Mimic a real flash chip's type and maker, as some games actually check if one is present.
|
||||
MEM_W(0, flash_type) = 0x11118001;
|
||||
MEM_W(0, flash_maker) = 0x00C2001E;
|
||||
}
|
||||
|
||||
extern "C" void osFlashClearStatus_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
|
||||
}
|
||||
|
||||
extern "C" void osFlashAllErase_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
save_clear(0, ultramodern::save_size, 0xFF);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashAllEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
save_clear(0, ultramodern::save_size, 0xFF);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
// This function is named sector but really means page.
|
||||
extern "C" void osFlashSectorErase_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint32_t page_num = (uint32_t)ctx->r4;
|
||||
|
||||
// Prevent out of bounds erase
|
||||
if (page_num >= page_count) {
|
||||
ctx->r2 = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
save_clear(page_num * page_size, page_size, 0xFF);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
// Same naming issue as above.
|
||||
extern "C" void osFlashSectorEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint32_t page_num = (uint32_t)ctx->r4;
|
||||
|
||||
// Prevent out of bounds erase
|
||||
if (page_num >= page_count) {
|
||||
ctx->r2 = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
save_clear(page_num * page_size, page_size, 0xFF);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashCheckEraseEnd_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
// All erases are blocking in this implementation, so this should always return OK.
|
||||
ctx->r2 = 0; // FLASH_STATUS_ERASE_OK
|
||||
}
|
||||
|
||||
extern "C" void osFlashWriteBuffer_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r4);
|
||||
int32_t pri = ctx->r5;
|
||||
PTR(void) dramAddr = ctx->r6;
|
||||
PTR(OSMesgQueue) mq = ctx->r7;
|
||||
|
||||
// Copy the input data into the write buffer
|
||||
for (size_t i = 0; i < page_size; i++) {
|
||||
write_buffer[i] = MEM_B(i, dramAddr);
|
||||
}
|
||||
|
||||
// Send the message indicating write completion
|
||||
osSendMesg(PASS_RDRAM mq, 0, OS_MESG_NOBLOCK);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashWriteArray_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint32_t page_num = ctx->r4;
|
||||
|
||||
// Copy the write buffer into the save file
|
||||
save_write_ptr(write_buffer.data(), page_num * page_size, page_size);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashReadArray_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r4);
|
||||
int32_t pri = ctx->r5;
|
||||
uint32_t page_num = ctx->r6;
|
||||
PTR(void) dramAddr = ctx->r7;
|
||||
uint32_t n_pages = MEM_W(0x10, ctx->r29);
|
||||
PTR(OSMesgQueue) mq = MEM_W(0x14, ctx->r29);
|
||||
|
||||
uint32_t offset = page_num * page_size;
|
||||
uint32_t count = n_pages * page_size;
|
||||
|
||||
// Read from the save file into the provided buffer
|
||||
save_read(PASS_RDRAM dramAddr, offset, count);
|
||||
|
||||
// Send the message indicating read completion
|
||||
osSendMesg(PASS_RDRAM mq, 0, OS_MESG_NOBLOCK);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osFlashChange_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
|
||||
extern "C" void __udivdi3_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t ret = a / b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __divdi3_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
int64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
int64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
int64_t ret = a / b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __umoddi3_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t ret = a % b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __ull_div_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t ret = a / b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __ll_div_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
int64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
int64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
int64_t ret = a / b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __ll_mul_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t ret = a * b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __ull_rem_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
|
||||
uint64_t ret = a % b;
|
||||
|
||||
ctx->r2 = (int32_t)(ret >> 32);
|
||||
ctx->r3 = (int32_t)(ret >> 0);
|
||||
}
|
||||
|
||||
extern "C" void __ull_to_d_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
double ret = (double)a;
|
||||
|
||||
ctx->f0.d = ret;
|
||||
}
|
||||
|
||||
extern "C" void __ull_to_f_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
|
||||
float ret = (float)a;
|
||||
|
||||
ctx->f0.fl = ret;
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include "recomp.h"
|
||||
#include "recomp_overlays.h"
|
||||
#include "../RecompiledFuncs/recomp_overlays.inl"
|
||||
|
||||
constexpr size_t num_code_sections = ARRLEN(section_table);
|
||||
|
||||
// SectionTableEntry sections[] defined in recomp_overlays.inl
|
||||
|
||||
struct LoadedSection {
|
||||
int32_t loaded_ram_addr;
|
||||
size_t section_table_index;
|
||||
|
||||
LoadedSection(int32_t loaded_ram_addr_, size_t section_table_index_) {
|
||||
loaded_ram_addr = loaded_ram_addr_;
|
||||
section_table_index = section_table_index_;
|
||||
}
|
||||
|
||||
bool operator<(const LoadedSection& rhs) {
|
||||
return loaded_ram_addr < rhs.loaded_ram_addr;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<LoadedSection> loaded_sections{};
|
||||
std::unordered_map<int32_t, recomp_func_t*> func_map{};
|
||||
|
||||
void load_overlay(size_t section_table_index, int32_t ram) {
|
||||
const SectionTableEntry& section = section_table[section_table_index];
|
||||
for (size_t function_index = 0; function_index < section.num_funcs; function_index++) {
|
||||
const FuncEntry& func = section.funcs[function_index];
|
||||
func_map[ram + func.offset] = func.func;
|
||||
}
|
||||
loaded_sections.emplace_back(ram, section_table_index);
|
||||
section_addresses[section.index] = ram;
|
||||
}
|
||||
|
||||
void load_special_overlay(const SectionTableEntry& section, int32_t ram) {
|
||||
for (size_t function_index = 0; function_index < section.num_funcs; function_index++) {
|
||||
const FuncEntry& func = section.funcs[function_index];
|
||||
func_map[ram + func.offset] = func.func;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extern "C" {
|
||||
int32_t section_addresses[num_sections];
|
||||
}
|
||||
|
||||
extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size) {
|
||||
// Search for the first section that's included in the loaded rom range
|
||||
// Sections were sorted by `init_overlays` so we can use the bounds functions
|
||||
auto lower = std::lower_bound(§ion_table[0], §ion_table[num_code_sections], rom,
|
||||
[](const SectionTableEntry& entry, uint32_t addr) {
|
||||
return entry.rom_addr < addr;
|
||||
}
|
||||
);
|
||||
auto upper = std::upper_bound(§ion_table[0], §ion_table[num_code_sections], (uint32_t)(rom + size),
|
||||
[](uint32_t addr, const SectionTableEntry& entry) {
|
||||
return addr < entry.size + entry.rom_addr;
|
||||
}
|
||||
);
|
||||
// Load the overlays that were found
|
||||
for (auto it = lower; it != upper; ++it) {
|
||||
load_overlay(std::distance(§ion_table[0], it), it->rom_addr - rom + ram_addr);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void unload_overlays(int32_t ram_addr, uint32_t size);
|
||||
|
||||
extern "C" void unload_overlay_by_id(uint32_t id) {
|
||||
uint32_t section_table_index = overlay_sections_by_index[id];
|
||||
const SectionTableEntry& section = section_table[section_table_index];
|
||||
|
||||
auto find_it = std::find_if(loaded_sections.begin(), loaded_sections.end(), [section_table_index](const LoadedSection& s) { return s.section_table_index == section_table_index; });
|
||||
|
||||
if (find_it != loaded_sections.end()) {
|
||||
// Determine where each function was loaded to and remove that entry from the function map
|
||||
for (size_t func_index = 0; func_index < section.num_funcs; func_index++) {
|
||||
const auto& func = section.funcs[func_index];
|
||||
uint32_t func_address = func.offset + find_it->loaded_ram_addr;
|
||||
func_map.erase(func_address);
|
||||
}
|
||||
// Reset the section's address in the address table
|
||||
section_addresses[section.index] = section.ram_addr;
|
||||
// Remove the section from the loaded section map
|
||||
loaded_sections.erase(find_it);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void load_overlay_by_id(uint32_t id, uint32_t ram_addr) {
|
||||
uint32_t section_table_index = overlay_sections_by_index[id];
|
||||
const SectionTableEntry& section = section_table[section_table_index];
|
||||
int32_t prev_address = section_addresses[section.index];
|
||||
if (/*ram_addr >= 0x80000000 && ram_addr < 0x81000000) {*/ prev_address == section.ram_addr) {
|
||||
load_overlay(section_table_index, ram_addr);
|
||||
}
|
||||
else {
|
||||
int32_t new_address = prev_address + ram_addr;
|
||||
unload_overlay_by_id(id);
|
||||
load_overlay(section_table_index, new_address);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void unload_overlays(int32_t ram_addr, uint32_t size) {
|
||||
for (auto it = loaded_sections.begin(); it != loaded_sections.end();) {
|
||||
const auto& section = section_table[it->section_table_index];
|
||||
|
||||
// Check if the unloaded region overlaps with the loaded section
|
||||
if (ram_addr < (it->loaded_ram_addr + section.size) && (ram_addr + size) >= it->loaded_ram_addr) {
|
||||
// Check if the section isn't entirely in the loaded region
|
||||
if (ram_addr > it->loaded_ram_addr || (ram_addr + size) < (it->loaded_ram_addr + section.size)) {
|
||||
fprintf(stderr,
|
||||
"Cannot partially unload section\n"
|
||||
" rom: 0x%08X size: 0x%08X loaded_addr: 0x%08X\n"
|
||||
" unloaded_ram: 0x%08X unloaded_size : 0x%08X\n",
|
||||
section.rom_addr, section.size, it->loaded_ram_addr, ram_addr, size);
|
||||
assert(false);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
// Determine where each function was loaded to and remove that entry from the function map
|
||||
for (size_t func_index = 0; func_index < section.num_funcs; func_index++) {
|
||||
const auto& func = section.funcs[func_index];
|
||||
uint32_t func_address = func.offset + it->loaded_ram_addr;
|
||||
func_map.erase(func_address);
|
||||
}
|
||||
// Reset the section's address in the address table
|
||||
section_addresses[section.index] = section.ram_addr;
|
||||
// Remove the section from the loaded section map
|
||||
it = loaded_sections.erase(it);
|
||||
// Skip incrementing the iterator
|
||||
continue;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void load_patch_functions();
|
||||
|
||||
void init_overlays() {
|
||||
for (size_t section_index = 0; section_index < num_code_sections; section_index++) {
|
||||
section_addresses[section_table[section_index].index] = section_table[section_index].ram_addr;
|
||||
}
|
||||
|
||||
// Sort the executable sections by rom address
|
||||
std::sort(§ion_table[0], §ion_table[num_code_sections],
|
||||
[](const SectionTableEntry& a, const SectionTableEntry& b) {
|
||||
return a.rom_addr < b.rom_addr;
|
||||
}
|
||||
);
|
||||
|
||||
load_patch_functions();
|
||||
}
|
||||
|
||||
extern "C" recomp_func_t * get_function(int32_t addr) {
|
||||
auto func_find = func_map.find(addr);
|
||||
if (func_find == func_map.end()) {
|
||||
fprintf(stderr, "Failed to find function at 0x%08X\n", addr);
|
||||
assert(false);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
return func_find->second;
|
||||
}
|
||||
|
@ -1,35 +0,0 @@
|
||||
#include "recomp.h"
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
|
||||
extern "C" void osPfsInitPak_recomp(uint8_t * rdram, recomp_context* ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsFreeBlocks_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsAllocateFile_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsDeleteFile_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsFileState_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsFindFile_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsReadWriteFile_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
||||
|
||||
extern "C" void osPfsChecker_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 1; // PFS_ERR_NOPACK
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include "recomp.h"
|
||||
#include "../../RecompiledPatches/recomp_overlays.inl"
|
||||
|
||||
void load_special_overlay(const SectionTableEntry& section, int32_t ram);
|
||||
|
||||
void load_patch_functions() {
|
||||
load_special_overlay(section_table[0], section_table[0].ram_addr);
|
||||
}
|
@ -1,321 +0,0 @@
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include "recomp.h"
|
||||
#include "recomp_game.h"
|
||||
#include "recomp_config.h"
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
|
||||
static std::vector<uint8_t> rom;
|
||||
|
||||
bool recomp::is_rom_loaded() {
|
||||
return !rom.empty();
|
||||
}
|
||||
|
||||
void recomp::set_rom_contents(std::vector<uint8_t>&& new_rom) {
|
||||
rom = std::move(new_rom);
|
||||
}
|
||||
|
||||
// Flashram occupies the same physical address as sram, but that issue is avoided because libultra exposes
|
||||
// a high-level interface for flashram. Because that high-level interface is reimplemented, low level accesses
|
||||
// that involve physical addresses don't need to be handled for flashram.
|
||||
constexpr uint32_t sram_base = 0x08000000;
|
||||
constexpr uint32_t rom_base = 0x10000000;
|
||||
constexpr uint32_t drive_base = 0x06000000;
|
||||
|
||||
constexpr uint32_t k1_to_phys(uint32_t addr) {
|
||||
return addr & 0x1FFFFFFF;
|
||||
}
|
||||
|
||||
constexpr uint32_t phys_to_k1(uint32_t addr) {
|
||||
return addr | 0xA0000000;
|
||||
}
|
||||
|
||||
extern "C" void __osPiGetAccess_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
}
|
||||
|
||||
extern "C" void __osPiRelAccess_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
}
|
||||
|
||||
extern "C" void osCartRomInit_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
OSPiHandle* handle = TO_PTR(OSPiHandle, ultramodern::cart_handle);
|
||||
handle->type = 0; // cart
|
||||
handle->baseAddress = phys_to_k1(rom_base);
|
||||
handle->domain = 0;
|
||||
|
||||
ctx->r2 = (gpr)ultramodern::cart_handle;
|
||||
}
|
||||
|
||||
extern "C" void osDriveRomInit_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
OSPiHandle* handle = TO_PTR(OSPiHandle, ultramodern::drive_handle);
|
||||
handle->type = 1; // bulk
|
||||
handle->baseAddress = phys_to_k1(drive_base);
|
||||
handle->domain = 0;
|
||||
|
||||
ctx->r2 = (gpr)ultramodern::drive_handle;
|
||||
}
|
||||
|
||||
extern "C" void osCreatePiManager_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
void recomp::do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes) {
|
||||
// TODO use word copies when possible
|
||||
|
||||
// TODO handle misaligned DMA
|
||||
assert((physical_addr & 0x1) == 0 && "Only PI DMA from aligned ROM addresses is currently supported");
|
||||
assert((ram_address & 0x7) == 0 && "Only PI DMA to aligned RDRAM addresses is currently supported");
|
||||
assert((num_bytes & 0x1) == 0 && "Only PI DMA with aligned sizes is currently supported");
|
||||
uint8_t* rom_addr = rom.data() + physical_addr - rom_base;
|
||||
for (size_t i = 0; i < num_bytes; i++) {
|
||||
MEM_B(i, ram_address) = *rom_addr;
|
||||
rom_addr++;
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr) {
|
||||
assert((physical_addr & 0x3) == 0 && "PIO not 4-byte aligned in device, currently unsupported");
|
||||
assert((ram_address & 0x3) == 0 && "PIO not 4-byte aligned in RDRAM, currently unsupported");
|
||||
uint8_t* rom_addr = rom.data() + physical_addr - rom_base;
|
||||
MEM_B(0, ram_address) = *rom_addr++;
|
||||
MEM_B(1, ram_address) = *rom_addr++;
|
||||
MEM_B(2, ram_address) = *rom_addr++;
|
||||
MEM_B(3, ram_address) = *rom_addr++;
|
||||
}
|
||||
|
||||
struct {
|
||||
std::array<char, 0x20000> save_buffer;
|
||||
std::thread saving_thread;
|
||||
moodycamel::LightweightSemaphore write_sempahore;
|
||||
std::mutex save_buffer_mutex;
|
||||
} save_context;
|
||||
|
||||
const std::u8string save_folder = u8"saves";
|
||||
const std::u8string save_filename = std::u8string{recomp::mm_game_id} + u8".bin";
|
||||
const std::u8string save_filename_temp = std::u8string{recomp::mm_game_id} + u8".bin.temp";
|
||||
const std::u8string save_filename_backup = std::u8string{recomp::mm_game_id} + u8".bin.bak";
|
||||
|
||||
std::filesystem::path get_save_file_path() {
|
||||
return recomp::get_app_folder_path() / save_folder / save_filename;
|
||||
}
|
||||
|
||||
std::filesystem::path get_save_file_path_temp() {
|
||||
return recomp::get_app_folder_path() / save_folder / save_filename_temp;
|
||||
}
|
||||
|
||||
std::filesystem::path get_save_file_path_backup() {
|
||||
return recomp::get_app_folder_path() / save_folder / save_filename_backup;
|
||||
}
|
||||
|
||||
void update_save_file() {
|
||||
{
|
||||
std::ofstream save_file{ get_save_file_path_temp(), std::ios_base::binary };
|
||||
|
||||
if (save_file.good()) {
|
||||
std::lock_guard lock{ save_context.save_buffer_mutex };
|
||||
save_file.write(save_context.save_buffer.data(), save_context.save_buffer.size());
|
||||
}
|
||||
else {
|
||||
recomp::message_box("Failed to write to the save file. Check your file permissions. If you have moved your appdata folder to Dropbox or similar, this can cause issues.");
|
||||
}
|
||||
}
|
||||
std::error_code ec;
|
||||
if (std::filesystem::exists(get_save_file_path(), ec)) {
|
||||
std::filesystem::copy_file(get_save_file_path(), get_save_file_path_backup(), std::filesystem::copy_options::overwrite_existing, ec);
|
||||
if (ec) {
|
||||
printf("[ERROR] Failed to copy save file backup\n");
|
||||
}
|
||||
}
|
||||
std::filesystem::copy_file(get_save_file_path_temp(), get_save_file_path(), std::filesystem::copy_options::overwrite_existing, ec);
|
||||
if (ec) {
|
||||
recomp::message_box("Failed to write to the save file. Check your file permissions. If you have moved your appdata folder to Dropbox or similar, this can cause issues.");
|
||||
}
|
||||
}
|
||||
|
||||
extern std::atomic_bool exited;
|
||||
|
||||
void saving_thread_func(RDRAM_ARG1) {
|
||||
while (!exited) {
|
||||
bool save_buffer_updated = false;
|
||||
// Repeatedly wait for a new action to be sent.
|
||||
constexpr int64_t wait_time_microseconds = 10000;
|
||||
constexpr int max_actions = 128;
|
||||
int num_actions = 0;
|
||||
|
||||
// Wait up to the given timeout for a write to come in. Allow multiple writes to coalesce together into a single save.
|
||||
// Cap the number of coalesced writes to guarantee that the save buffer eventually gets written out to the file even if the game
|
||||
// is constantly sending writes.
|
||||
while (save_context.write_sempahore.wait(wait_time_microseconds) && num_actions < max_actions) {
|
||||
save_buffer_updated = true;
|
||||
num_actions++;
|
||||
}
|
||||
|
||||
// If an action came through that affected the save file, save the updated contents.
|
||||
if (save_buffer_updated) {
|
||||
update_save_file();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void save_write_ptr(const void* in, uint32_t offset, uint32_t count) {
|
||||
{
|
||||
std::lock_guard lock { save_context.save_buffer_mutex };
|
||||
memcpy(&save_context.save_buffer[offset], in, count);
|
||||
}
|
||||
|
||||
save_context.write_sempahore.signal();
|
||||
}
|
||||
|
||||
void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) {
|
||||
{
|
||||
std::lock_guard lock { save_context.save_buffer_mutex };
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
save_context.save_buffer[offset + i] = MEM_B(i, rdram_address);
|
||||
}
|
||||
}
|
||||
|
||||
save_context.write_sempahore.signal();
|
||||
}
|
||||
|
||||
void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) {
|
||||
std::lock_guard lock { save_context.save_buffer_mutex };
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
MEM_B(i, rdram_address) = save_context.save_buffer[offset + i];
|
||||
}
|
||||
}
|
||||
|
||||
void save_clear(uint32_t start, uint32_t size, char value) {
|
||||
{
|
||||
std::lock_guard lock { save_context.save_buffer_mutex };
|
||||
std::fill_n(save_context.save_buffer.begin() + start, size, value);
|
||||
}
|
||||
|
||||
save_context.write_sempahore.signal();
|
||||
}
|
||||
|
||||
void ultramodern::init_saving(RDRAM_ARG1) {
|
||||
std::filesystem::path save_file_path = get_save_file_path();
|
||||
std::filesystem::path save_file_path_backup = get_save_file_path_backup();
|
||||
|
||||
// Ensure the save file directory exists.
|
||||
std::filesystem::create_directories(save_file_path.parent_path());
|
||||
|
||||
// Read the save file if it exists.
|
||||
std::ifstream save_file{ save_file_path, std::ios_base::binary };
|
||||
if (save_file.good()) {
|
||||
save_file.read(save_context.save_buffer.data(), save_context.save_buffer.size());
|
||||
} else {
|
||||
// Reading the save file faield, so try to read the backup save file.
|
||||
std::ifstream save_file_backup{ save_file_path_backup, std::ios_base::binary };
|
||||
if (save_file_backup.good()) {
|
||||
save_file_backup.read(save_context.save_buffer.data(), save_context.save_buffer.size());
|
||||
} else {
|
||||
// Otherwise clear the save file to all zeroes.
|
||||
save_context.save_buffer.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
save_context.saving_thread = std::thread{saving_thread_func, PASS_RDRAM};
|
||||
}
|
||||
|
||||
void ultramodern::join_saving_thread() {
|
||||
save_context.saving_thread.join();
|
||||
}
|
||||
|
||||
void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_addr, uint32_t size, uint32_t direction) {
|
||||
// TODO asynchronous transfer
|
||||
// TODO implement unaligned DMA correctly
|
||||
if (direction == 0) {
|
||||
if (physical_addr >= rom_base) {
|
||||
// read cart rom
|
||||
recomp::do_rom_read(rdram, rdram_address, physical_addr, size);
|
||||
|
||||
// Send a message to the mq to indicate that the transfer completed
|
||||
osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK);
|
||||
} else if (physical_addr >= sram_base) {
|
||||
// read sram
|
||||
save_read(rdram, rdram_address, physical_addr - sram_base, size);
|
||||
|
||||
// Send a message to the mq to indicate that the transfer completed
|
||||
osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK);
|
||||
} else {
|
||||
fprintf(stderr, "[WARN] PI DMA read from unknown region, phys address 0x%08X\n", physical_addr);
|
||||
}
|
||||
} else {
|
||||
if (physical_addr >= rom_base) {
|
||||
// write cart rom
|
||||
throw std::runtime_error("ROM DMA write unimplemented");
|
||||
} else if (physical_addr >= sram_base) {
|
||||
// write sram
|
||||
save_write(rdram, rdram_address, physical_addr - sram_base, size);
|
||||
|
||||
// Send a message to the mq to indicate that the transfer completed
|
||||
osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK);
|
||||
} else {
|
||||
fprintf(stderr, "[WARN] PI DMA write to unknown region, phys address 0x%08X\n", physical_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osPiStartDma_recomp(RDRAM_ARG recomp_context* ctx) {
|
||||
uint32_t mb = ctx->r4;
|
||||
uint32_t pri = ctx->r5;
|
||||
uint32_t direction = ctx->r6;
|
||||
uint32_t devAddr = ctx->r7 | rom_base;
|
||||
gpr dramAddr = MEM_W(0x10, ctx->r29);
|
||||
uint32_t size = MEM_W(0x14, ctx->r29);
|
||||
PTR(OSMesgQueue) mq = MEM_W(0x18, ctx->r29);
|
||||
uint32_t physical_addr = k1_to_phys(devAddr);
|
||||
|
||||
debug_printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size);
|
||||
|
||||
do_dma(PASS_RDRAM mq, dramAddr, physical_addr, size, direction);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osEPiStartDma_recomp(RDRAM_ARG recomp_context* ctx) {
|
||||
OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4);
|
||||
OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r5);
|
||||
uint32_t direction = ctx->r6;
|
||||
uint32_t devAddr = handle->baseAddress | mb->devAddr;
|
||||
gpr dramAddr = mb->dramAddr;
|
||||
uint32_t size = mb->size;
|
||||
PTR(OSMesgQueue) mq = mb->hdr.retQueue;
|
||||
uint32_t physical_addr = k1_to_phys(devAddr);
|
||||
|
||||
debug_printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size);
|
||||
|
||||
do_dma(PASS_RDRAM mq, dramAddr, physical_addr, size, direction);
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osEPiReadIo_recomp(RDRAM_ARG recomp_context * ctx) {
|
||||
OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4);
|
||||
uint32_t devAddr = handle->baseAddress | ctx->r5;
|
||||
gpr dramAddr = ctx->r6;
|
||||
uint32_t physical_addr = k1_to_phys(devAddr);
|
||||
|
||||
if (physical_addr > rom_base) {
|
||||
// cart rom
|
||||
recomp::do_rom_pio(PASS_RDRAM dramAddr, physical_addr);
|
||||
} else {
|
||||
// sram
|
||||
assert(false && "SRAM ReadIo unimplemented");
|
||||
}
|
||||
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osPiGetStatus_recomp(RDRAM_ARG recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void osPiRawStartDma_recomp(RDRAM_ARG recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
#include <vector>
|
||||
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
#include "euc-jp.h"
|
||||
|
||||
extern "C" void __checkHardware_msp_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void __checkHardware_kmc_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void __checkHardware_isv_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void __osInitialize_msp_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
}
|
||||
|
||||
extern "C" void __osInitialize_kmc_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
}
|
||||
|
||||
extern "C" void __osInitialize_isv_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
}
|
||||
|
||||
extern "C" void isPrintfInit_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
}
|
||||
|
||||
extern "C" void __osRdbSend_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
gpr buf = ctx->r4;
|
||||
size_t size = ctx->r5;
|
||||
u32 type = (u32)ctx->r6;
|
||||
std::unique_ptr<char[]> to_print = std::make_unique<char[]>(size + 1);
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
to_print[i] = MEM_B(i, buf);
|
||||
}
|
||||
to_print[size] = '\x00';
|
||||
|
||||
fwrite(to_print.get(), 1, size, stdout);
|
||||
|
||||
ctx->r2 = size;
|
||||
}
|
||||
|
||||
extern "C" void is_proutSyncPrintf_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
// Buffering to speed up print performance
|
||||
static std::vector<char> print_buffer;
|
||||
|
||||
gpr buf = ctx->r5;
|
||||
size_t size = ctx->r6;
|
||||
|
||||
//for (size_t i = 0; i < size; i++) {
|
||||
// // Add the new character to the buffer
|
||||
// char cur_char = MEM_B(i, buf);
|
||||
|
||||
// // If the new character is a newline, flush the buffer
|
||||
// if (cur_char == '\n') {
|
||||
// std::string utf8_str = Encoding::decode_eucjp(std::string_view{ print_buffer.data(), print_buffer.size() });
|
||||
// puts(utf8_str.c_str());
|
||||
// print_buffer.clear();
|
||||
// } else {
|
||||
// print_buffer.push_back(cur_char);
|
||||
// }
|
||||
//}
|
||||
|
||||
//fwrite(to_print.get(), size, 1, stdout);
|
||||
|
||||
ctx->r2 = 1;
|
||||
}
|
@ -1,445 +0,0 @@
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include "recomp.h"
|
||||
#include "recomp_overlays.h"
|
||||
#include "recomp_game.h"
|
||||
#include "recomp_config.h"
|
||||
#include "xxHash/xxh3.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "../../RecompiledPatches/patches_bin.h"
|
||||
#ifdef HAS_MM_SHADER_CACHE
|
||||
#include "mm_shader_cache.h"
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
inline uint32_t byteswap(uint32_t val) {
|
||||
return _byteswap_ulong(val);
|
||||
}
|
||||
#else
|
||||
constexpr uint32_t byteswap(uint32_t val) {
|
||||
return __builtin_bswap32(val);
|
||||
}
|
||||
#endif
|
||||
|
||||
struct RomEntry {
|
||||
uint64_t xxhash3_value;
|
||||
std::u8string stored_filename;
|
||||
std::string internal_rom_name;
|
||||
};
|
||||
|
||||
const std::unordered_map<recomp::Game, RomEntry> game_roms {
|
||||
{ recomp::Game::MM, { 0xEF18B4A9E2386169ULL, std::u8string{recomp::mm_game_id} + u8".z64", "ZELDA MAJORA'S MASK" }},
|
||||
};
|
||||
|
||||
bool check_hash(const std::vector<uint8_t>& rom_data, uint64_t expected_hash) {
|
||||
uint64_t calculated_hash = XXH3_64bits(rom_data.data(), rom_data.size());
|
||||
|
||||
return calculated_hash == expected_hash;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> read_file(const std::filesystem::path& path) {
|
||||
std::vector<uint8_t> ret;
|
||||
|
||||
std::ifstream file{ path, std::ios::binary};
|
||||
|
||||
if (file.good()) {
|
||||
file.seekg(0, std::ios::end);
|
||||
ret.resize(file.tellg());
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
file.read(reinterpret_cast<char*>(ret.data()), ret.size());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool write_file(const std::filesystem::path& path, const std::vector<uint8_t>& data) {
|
||||
std::ofstream out_file{ path, std::ios::binary };
|
||||
|
||||
if (!out_file.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out_file.write(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool check_stored_rom(const RomEntry& game_entry) {
|
||||
|
||||
std::vector stored_rom_data = read_file(recomp::get_app_folder_path() / game_entry.stored_filename);
|
||||
|
||||
if (!check_hash(stored_rom_data, game_entry.xxhash3_value)) {
|
||||
// Incorrect hash, remove the stored ROM file if it exists.
|
||||
std::filesystem::remove(recomp::get_app_folder_path() / game_entry.stored_filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::unordered_set<recomp::Game> valid_game_roms;
|
||||
|
||||
bool recomp::is_rom_valid(recomp::Game game) {
|
||||
return valid_game_roms.contains(game);
|
||||
}
|
||||
|
||||
void recomp::check_all_stored_roms() {
|
||||
for (const auto& cur_rom_entry: game_roms) {
|
||||
if (check_stored_rom(cur_rom_entry.second)) {
|
||||
valid_game_roms.insert(cur_rom_entry.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool recomp::load_stored_rom(recomp::Game game) {
|
||||
auto find_it = game_roms.find(game);
|
||||
|
||||
if (find_it == game_roms.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> stored_rom_data = read_file(recomp::get_app_folder_path() / find_it->second.stored_filename);
|
||||
|
||||
if (!check_hash(stored_rom_data, find_it->second.xxhash3_value)) {
|
||||
// The ROM no longer has the right hash, delete it.
|
||||
std::filesystem::remove(recomp::get_app_folder_path() / find_it->second.stored_filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
recomp::set_rom_contents(std::move(stored_rom_data));
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::array<uint8_t, 4> first_rom_bytes { 0x80, 0x37, 0x12, 0x40 };
|
||||
|
||||
enum class ByteswapType {
|
||||
NotByteswapped,
|
||||
Byteswapped4,
|
||||
Byteswapped2,
|
||||
Invalid
|
||||
};
|
||||
|
||||
ByteswapType check_rom_start(const std::vector<uint8_t>& rom_data) {
|
||||
if (rom_data.size() < 4) {
|
||||
return ByteswapType::Invalid;
|
||||
}
|
||||
|
||||
bool matched_all = true;
|
||||
|
||||
auto check_match = [&](uint8_t index0, uint8_t index1, uint8_t index2, uint8_t index3) {
|
||||
return
|
||||
rom_data[0] == first_rom_bytes[index0] &&
|
||||
rom_data[1] == first_rom_bytes[index1] &&
|
||||
rom_data[2] == first_rom_bytes[index2] &&
|
||||
rom_data[3] == first_rom_bytes[index3];
|
||||
};
|
||||
|
||||
// Check if the ROM is already in the correct byte order.
|
||||
if (check_match(0,1,2,3)) {
|
||||
return ByteswapType::NotByteswapped;
|
||||
}
|
||||
|
||||
// Check if the ROM has been byteswapped in groups of 4 bytes.
|
||||
if (check_match(3,2,1,0)) {
|
||||
return ByteswapType::Byteswapped4;
|
||||
}
|
||||
|
||||
// Check if the ROM has been byteswapped in groups of 2 bytes.
|
||||
if (check_match(1,0,3,2)) {
|
||||
return ByteswapType::Byteswapped2;
|
||||
}
|
||||
|
||||
// No match found.
|
||||
return ByteswapType::Invalid;
|
||||
}
|
||||
|
||||
void byteswap_data(std::vector<uint8_t>& rom_data, size_t index_xor) {
|
||||
for (size_t rom_pos = 0; rom_pos < rom_data.size(); rom_pos += 4) {
|
||||
uint8_t temp0 = rom_data[rom_pos + 0];
|
||||
uint8_t temp1 = rom_data[rom_pos + 1];
|
||||
uint8_t temp2 = rom_data[rom_pos + 2];
|
||||
uint8_t temp3 = rom_data[rom_pos + 3];
|
||||
|
||||
rom_data[rom_pos + (0 ^ index_xor)] = temp0;
|
||||
rom_data[rom_pos + (1 ^ index_xor)] = temp1;
|
||||
rom_data[rom_pos + (2 ^ index_xor)] = temp2;
|
||||
rom_data[rom_pos + (3 ^ index_xor)] = temp3;
|
||||
}
|
||||
}
|
||||
|
||||
recomp::RomValidationError recomp::select_rom(const std::filesystem::path& rom_path, Game game) {
|
||||
auto find_it = game_roms.find(game);
|
||||
|
||||
if (find_it == game_roms.end()) {
|
||||
return recomp::RomValidationError::OtherError;
|
||||
}
|
||||
|
||||
const RomEntry& game_entry = find_it->second;
|
||||
|
||||
std::vector<uint8_t> rom_data = read_file(rom_path);
|
||||
|
||||
if (rom_data.empty()) {
|
||||
return recomp::RomValidationError::FailedToOpen;
|
||||
}
|
||||
|
||||
// Pad the rom to the nearest multiple of 4 bytes.
|
||||
rom_data.resize((rom_data.size() + 3) & ~3);
|
||||
|
||||
ByteswapType byteswap_type = check_rom_start(rom_data);
|
||||
|
||||
switch (byteswap_type) {
|
||||
case ByteswapType::Invalid:
|
||||
return recomp::RomValidationError::NotARom;
|
||||
case ByteswapType::Byteswapped2:
|
||||
byteswap_data(rom_data, 1);
|
||||
break;
|
||||
case ByteswapType::Byteswapped4:
|
||||
byteswap_data(rom_data, 3);
|
||||
break;
|
||||
case ByteswapType::NotByteswapped:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!check_hash(rom_data, game_entry.xxhash3_value)) {
|
||||
const std::string_view name{ reinterpret_cast<const char*>(rom_data.data()) + 0x20, game_entry.internal_rom_name.size()};
|
||||
if (name == game_entry.internal_rom_name) {
|
||||
return recomp::RomValidationError::IncorrectVersion;
|
||||
}
|
||||
else {
|
||||
if (game == recomp::Game::MM && std::string_view{ reinterpret_cast<const char*>(rom_data.data()) + 0x20, 19 } == "THE LEGEND OF ZELDA") {
|
||||
return recomp::RomValidationError::NotYet;
|
||||
}
|
||||
else {
|
||||
return recomp::RomValidationError::IncorrectRom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
write_file(recomp::get_app_folder_path() / game_entry.stored_filename, rom_data);
|
||||
|
||||
return recomp::RomValidationError::Good;
|
||||
}
|
||||
|
||||
extern "C" void osGetMemSize_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 8 * 1024 * 1024;
|
||||
}
|
||||
|
||||
enum class StatusReg {
|
||||
FR = 0x04000000,
|
||||
};
|
||||
|
||||
extern "C" void cop0_status_write(recomp_context* ctx, gpr value) {
|
||||
uint32_t old_sr = ctx->status_reg;
|
||||
uint32_t new_sr = (uint32_t)value;
|
||||
uint32_t changed = old_sr ^ new_sr;
|
||||
|
||||
// Check if the FR bit changed
|
||||
if (changed & (uint32_t)StatusReg::FR) {
|
||||
// Check if the FR bit was set
|
||||
if (new_sr & (uint32_t)StatusReg::FR) {
|
||||
// FR = 1, odd single floats point to their own registers
|
||||
ctx->f_odd = &ctx->f1.u32l;
|
||||
ctx->mips3_float_mode = true;
|
||||
}
|
||||
// Otherwise, it was cleared
|
||||
else {
|
||||
// FR = 0, odd single floats point to the upper half of the previous register
|
||||
ctx->f_odd = &ctx->f0.u32h;
|
||||
ctx->mips3_float_mode = false;
|
||||
}
|
||||
|
||||
// Remove the FR bit from the changed bits as it's been handled
|
||||
changed &= ~(uint32_t)StatusReg::FR;
|
||||
}
|
||||
|
||||
// If any other bits were changed, assert false as they're not handled currently
|
||||
if (changed) {
|
||||
printf("Unhandled status register bits changed: 0x%08X\n", changed);
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Update the status register in the context
|
||||
ctx->status_reg = new_sr;
|
||||
}
|
||||
|
||||
extern "C" gpr cop0_status_read(recomp_context* ctx) {
|
||||
return (gpr)(int32_t)ctx->status_reg;
|
||||
}
|
||||
|
||||
extern "C" void switch_error(const char* func, uint32_t vram, uint32_t jtbl) {
|
||||
printf("Switch-case out of bounds in %s at 0x%08X for jump table at 0x%08X\n", func, vram, jtbl);
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
extern "C" void do_break(uint32_t vram) {
|
||||
printf("Encountered break at original vram 0x%08X\n", vram);
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
void run_thread_function(uint8_t* rdram, uint64_t addr, uint64_t sp, uint64_t arg) {
|
||||
recomp_context ctx{};
|
||||
ctx.r29 = sp;
|
||||
ctx.r4 = arg;
|
||||
ctx.mips3_float_mode = 0;
|
||||
ctx.f_odd = &ctx.f0.u32h;
|
||||
recomp_func_t* func = get_function(addr);
|
||||
func(rdram, &ctx);
|
||||
}
|
||||
|
||||
// Recomp generation functions
|
||||
extern "C" void recomp_entrypoint(uint8_t * rdram, recomp_context * ctx);
|
||||
gpr get_entrypoint_address();
|
||||
const char* get_rom_name();
|
||||
|
||||
void read_patch_data(uint8_t* rdram, gpr patch_data_address) {
|
||||
for (size_t i = 0; i < sizeof(mm_patches_bin); i++) {
|
||||
MEM_B(i, patch_data_address) = mm_patches_bin[i];
|
||||
}
|
||||
}
|
||||
|
||||
void init(uint8_t* rdram, recomp_context* ctx) {
|
||||
// Initialize the overlays
|
||||
init_overlays();
|
||||
|
||||
// Get entrypoint from recomp function
|
||||
gpr entrypoint = get_entrypoint_address();
|
||||
|
||||
// Load overlays in the first 1MB
|
||||
load_overlays(0x1000, (int32_t)entrypoint, 1024 * 1024);
|
||||
|
||||
// Initial 1MB DMA (rom address 0x1000 = physical address 0x10001000)
|
||||
recomp::do_rom_read(rdram, entrypoint, 0x10001000, 0x100000);
|
||||
|
||||
// Read in any extra data from patches
|
||||
read_patch_data(rdram, (gpr)(s32)0x80801000);
|
||||
|
||||
// Set up stack pointer
|
||||
ctx->r29 = 0xFFFFFFFF803FFFF0u;
|
||||
|
||||
// Set up context floats
|
||||
ctx->f_odd = &ctx->f0.u32h;
|
||||
ctx->mips3_float_mode = false;
|
||||
|
||||
// Initialize variables normally set by IPL3
|
||||
constexpr int32_t osTvType = 0x80000300;
|
||||
constexpr int32_t osRomType = 0x80000304;
|
||||
constexpr int32_t osRomBase = 0x80000308;
|
||||
constexpr int32_t osResetType = 0x8000030c;
|
||||
constexpr int32_t osCicId = 0x80000310;
|
||||
constexpr int32_t osVersion = 0x80000314;
|
||||
constexpr int32_t osMemSize = 0x80000318;
|
||||
constexpr int32_t osAppNMIBuffer = 0x8000031c;
|
||||
MEM_W(osTvType, 0) = 1; // NTSC
|
||||
MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base
|
||||
MEM_W(osResetType, 0) = 0; // cold reset
|
||||
MEM_W(osMemSize, 0) = 8 * 1024 * 1024; // 8MB
|
||||
}
|
||||
|
||||
std::atomic<recomp::Game> game_started = recomp::Game::None;
|
||||
|
||||
void recomp::start_game(recomp::Game game) {
|
||||
game_started.store(game);
|
||||
game_started.notify_all();
|
||||
}
|
||||
|
||||
bool ultramodern::is_game_started() {
|
||||
return game_started.load() != recomp::Game::None;
|
||||
}
|
||||
|
||||
void set_audio_callbacks(const ultramodern::audio_callbacks_t& callbacks);
|
||||
void set_input_callbacks(const ultramodern::input_callbacks_t& callback);
|
||||
|
||||
std::atomic_bool exited = false;
|
||||
|
||||
void ultramodern::quit() {
|
||||
exited.store(true);
|
||||
recomp::Game desired = recomp::Game::None;
|
||||
game_started.compare_exchange_strong(desired, recomp::Game::Quit);
|
||||
game_started.notify_all();
|
||||
}
|
||||
|
||||
void recomp::start(ultramodern::WindowHandle window_handle, const ultramodern::audio_callbacks_t& audio_callbacks, const ultramodern::input_callbacks_t& input_callbacks, const ultramodern::gfx_callbacks_t& gfx_callbacks_) {
|
||||
recomp::check_all_stored_roms();
|
||||
set_audio_callbacks(audio_callbacks);
|
||||
set_input_callbacks(input_callbacks);
|
||||
|
||||
ultramodern::gfx_callbacks_t gfx_callbacks = gfx_callbacks_;
|
||||
|
||||
ultramodern::gfx_callbacks_t::gfx_data_t gfx_data{};
|
||||
|
||||
if (gfx_callbacks.create_gfx) {
|
||||
gfx_data = gfx_callbacks.create_gfx();
|
||||
}
|
||||
|
||||
if (window_handle == ultramodern::WindowHandle{}) {
|
||||
if (gfx_callbacks.create_window) {
|
||||
window_handle = gfx_callbacks.create_window(gfx_data);
|
||||
}
|
||||
else {
|
||||
assert(false && "No create_window callback provided");
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate rdram_buffer
|
||||
std::unique_ptr<uint8_t[]> rdram_buffer = std::make_unique<uint8_t[]>(ultramodern::rdram_size);
|
||||
std::memset(rdram_buffer.get(), 0, ultramodern::rdram_size);
|
||||
|
||||
std::thread game_thread{[](ultramodern::WindowHandle window_handle, uint8_t* rdram) {
|
||||
debug_printf("[Recomp] Starting\n");
|
||||
|
||||
ultramodern::set_native_thread_name("Game Start Thread");
|
||||
|
||||
ultramodern::preinit(rdram, window_handle);
|
||||
|
||||
game_started.wait(recomp::Game::None);
|
||||
recomp_context context{};
|
||||
|
||||
switch (game_started.load()) {
|
||||
case recomp::Game::MM:
|
||||
if (!recomp::load_stored_rom(recomp::Game::MM)) {
|
||||
recomp::message_box("Error opening stored ROM! Please restart this program.");
|
||||
}
|
||||
|
||||
#ifdef HAS_MM_SHADER_CACHE
|
||||
ultramodern::load_shader_cache({mm_shader_cache_bytes, sizeof(mm_shader_cache_bytes)});
|
||||
#endif
|
||||
|
||||
init(rdram, &context);
|
||||
try {
|
||||
recomp_entrypoint(rdram, &context);
|
||||
} catch (ultramodern::thread_terminated& terminated) {
|
||||
|
||||
}
|
||||
break;
|
||||
case recomp::Game::Quit:
|
||||
break;
|
||||
}
|
||||
|
||||
debug_printf("[Recomp] Quitting\n");
|
||||
}, window_handle, rdram_buffer.get()};
|
||||
|
||||
while (!exited) {
|
||||
ultramodern::sleep_milliseconds(1);
|
||||
if (gfx_callbacks.update_gfx != nullptr) {
|
||||
gfx_callbacks.update_gfx(gfx_data);
|
||||
}
|
||||
}
|
||||
game_thread.join();
|
||||
ultramodern::join_event_threads();
|
||||
ultramodern::join_thread_cleaner_thread();
|
||||
ultramodern::join_saving_thread();
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
extern "C" void osSpTaskLoad_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
bool dump_frame = false;
|
||||
|
||||
extern "C" void osSpTaskStartGo_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
//printf("[sp] osSpTaskStartGo(0x%08X)\n", (uint32_t)ctx->r4);
|
||||
OSTask* task = TO_PTR(OSTask, ctx->r4);
|
||||
if (task->t.type == M_GFXTASK) {
|
||||
//printf("[sp] Gfx task: %08X\n", (uint32_t)ctx->r4);
|
||||
} else if (task->t.type == M_AUDTASK) {
|
||||
//printf("[sp] Audio task: %08X\n", (uint32_t)ctx->r4);
|
||||
}
|
||||
// For debugging
|
||||
if (dump_frame) {
|
||||
char addr_str[32];
|
||||
constexpr size_t ram_size = 0x800000;
|
||||
std::unique_ptr<char[]> ram_unswapped = std::make_unique<char[]>(ram_size);
|
||||
snprintf(addr_str, sizeof(addr_str) - 1, "%08X", task->t.data_ptr);
|
||||
addr_str[sizeof(addr_str) - 1] = '\0';
|
||||
std::ofstream dump_file{ "ramdump" + std::string{ addr_str } + ".bin", std::ios::binary};
|
||||
|
||||
for (size_t i = 0; i < ram_size; i++) {
|
||||
ram_unswapped[i] = rdram[i ^ 3];
|
||||
}
|
||||
|
||||
dump_file.write(ram_unswapped.get(), ram_size);
|
||||
dump_frame = false;
|
||||
}
|
||||
ultramodern::submit_rsp_task(rdram, ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osSpTaskYield_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
// Ignore yield requests (acts as if the task completed before it received the yield request)
|
||||
}
|
||||
|
||||
extern "C" void osSpTaskYielded_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
// Task yield requests are ignored, so always return 0 as tasks will never be yielded
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
extern "C" void __osSpSetPc_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
assert(false);
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
// None of these functions need to be reimplemented, so stub them out
|
||||
extern "C" void osUnmapTLBAll_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
// TODO this will need to be implemented in the future for any games that actually use the TLB
|
||||
}
|
||||
|
||||
extern "C" void osVoiceInit_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 11; // CONT_ERR_DEVICE
|
||||
}
|
||||
|
||||
extern "C" void osVoiceSetWord_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceCheckWord_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceStopReadData_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceMaskDictionary_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceStartReadData_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceControlGain_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceGetReadData_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osVoiceClearDictionary_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
}
|
||||
|
@ -1,163 +0,0 @@
|
||||
#include <memory>
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
extern "C" void osInitialize_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
osInitialize();
|
||||
}
|
||||
|
||||
extern "C" void __osInitialize_common_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
osInitialize();
|
||||
}
|
||||
|
||||
extern "C" void osCreateThread_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osCreateThread(rdram, (int32_t)ctx->r4, (OSId)ctx->r5, (int32_t)ctx->r6, (int32_t)ctx->r7,
|
||||
(int32_t)MEM_W(0x10, ctx->r29), (OSPri)MEM_W(0x14, ctx->r29));
|
||||
}
|
||||
|
||||
extern "C" void osStartThread_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osStartThread(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osStopThread_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
osStopThread(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osDestroyThread_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
osDestroyThread(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osYieldThread_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
assert(false);
|
||||
// osYieldThread(rdram);
|
||||
}
|
||||
|
||||
extern "C" void osSetThreadPri_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osSetThreadPri(rdram, (int32_t)ctx->r4, (OSPri)ctx->r5);
|
||||
}
|
||||
|
||||
extern "C" void osGetThreadPri_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = osGetThreadPri(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osGetThreadId_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = osGetThreadId(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osCreateMesgQueue_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osCreateMesgQueue(rdram, (int32_t)ctx->r4, (int32_t)ctx->r5, (s32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osRecvMesg_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = osRecvMesg(rdram, (int32_t)ctx->r4, (int32_t)ctx->r5, (s32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osSendMesg_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = osSendMesg(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osJamMesg_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = osJamMesg(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osSetEventMesg_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osSetEventMesg(rdram, (OSEvent)ctx->r4, (int32_t)ctx->r5, (OSMesg)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osViSetEvent_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
osViSetEvent(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (u32)ctx->r6);
|
||||
}
|
||||
|
||||
extern "C" void osGetCount_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = osGetCount();
|
||||
}
|
||||
|
||||
extern "C" void osGetTime_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t total_count = osGetTime();
|
||||
ctx->r2 = (int32_t)(total_count >> 32);
|
||||
ctx->r3 = (int32_t)(total_count >> 0);
|
||||
}
|
||||
|
||||
extern "C" void osSetTimer_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
uint64_t countdown = ((uint64_t)(ctx->r6) << 32) | ((ctx->r7) & 0xFFFFFFFFu);
|
||||
uint64_t interval = load_doubleword(rdram, ctx->r29, 0x10);
|
||||
ctx->r2 = osSetTimer(rdram, (int32_t)ctx->r4, countdown, interval, (int32_t)MEM_W(0x18, ctx->r29), (OSMesg)MEM_W(0x1C, ctx->r29));
|
||||
}
|
||||
|
||||
extern "C" void osStopTimer_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = osStopTimer(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osVirtualToPhysical_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = osVirtualToPhysical((int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osInvalDCache_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osInvalICache_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osWritebackDCache_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osWritebackDCacheAll_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osSetIntMask_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void __osDisableInt_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void __osRestoreInt_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void __osSetFpcCsr_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
ctx->r2 = 0;
|
||||
}
|
||||
|
||||
// For the Mario Party games (not working)
|
||||
//extern "C" void longjmp_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
// RecompJmpBuf* buf = TO_PTR(RecompJmpBuf, ctx->r4);
|
||||
//
|
||||
// // Check if this is a buffer that was set up with setjmp
|
||||
// if (buf->magic == SETJMP_MAGIC) {
|
||||
// // If so, longjmp to it
|
||||
// // Setjmp/longjmp does not work across threads, so verify that this buffer was made by this thread
|
||||
// assert(buf->owner == ultramodern::this_thread());
|
||||
// longjmp(buf->storage->buffer, ctx->r5);
|
||||
// } else {
|
||||
// // Otherwise, check if it was one built manually by the game with $ra pointing to a function
|
||||
// gpr sp = MEM_W(0, ctx->r4);
|
||||
// gpr ra = MEM_W(4, ctx->r4);
|
||||
// ctx->r29 = sp;
|
||||
// recomp_func_t* target = LOOKUP_FUNC(ra);
|
||||
// if (target == nullptr) {
|
||||
// fprintf(stderr, "Failed to find function for manual longjmp\n");
|
||||
// std::quick_exit(EXIT_FAILURE);
|
||||
// }
|
||||
// target(rdram, ctx);
|
||||
//
|
||||
// // TODO kill this thread if the target function returns
|
||||
// assert(false);
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//#undef setjmp_recomp
|
||||
//extern "C" void setjmp_recomp(uint8_t * rdram, recomp_context * ctx) {
|
||||
// fprintf(stderr, "Program called setjmp_recomp\n");
|
||||
// std::quick_exit(EXIT_FAILURE);
|
||||
//}
|
||||
//
|
||||
//extern "C" int32_t osGetThreadEx(void) {
|
||||
// return ultramodern::this_thread();
|
||||
//}
|
@ -1,47 +0,0 @@
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
extern "C" void osViSetYScale_recomp(uint8_t* rdram, recomp_context * ctx) {
|
||||
osViSetYScale(ctx->f12.fl);
|
||||
}
|
||||
|
||||
extern "C" void osViSetXScale_recomp(uint8_t* rdram, recomp_context * ctx) {
|
||||
osViSetXScale(ctx->f12.fl);
|
||||
}
|
||||
|
||||
extern "C" void osCreateViManager_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
;
|
||||
}
|
||||
|
||||
extern "C" void osViBlack_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osViBlack((uint32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osViSetSpecialFeatures_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osViSetSpecialFeatures((uint32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osViGetCurrentFramebuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = (gpr)(int32_t)osViGetCurrentFramebuffer();
|
||||
}
|
||||
|
||||
extern "C" void osViGetNextFramebuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
ctx->r2 = (gpr)(int32_t)osViGetNextFramebuffer();
|
||||
}
|
||||
|
||||
extern "C" void osViSwapBuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osViSwapBuffer(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern "C" void osViSetMode_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||
osViSetMode(rdram, (int32_t)ctx->r4);
|
||||
}
|
||||
|
||||
extern uint64_t total_vis;
|
||||
|
||||
extern "C" void wait_one_frame(uint8_t* rdram, recomp_context* ctx) {
|
||||
uint64_t cur_vis = total_vis;
|
||||
while (cur_vis == total_vis) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
|
||||
using ColourMap = Rml::UnorderedMap<Rml::String, Rml::Colourb>;
|
||||
|
||||
namespace recomp {
|
||||
namespace recompui {
|
||||
class PropertyParserColorHack : public Rml::PropertyParser {
|
||||
public:
|
||||
PropertyParserColorHack();
|
||||
|
@ -1,24 +1,24 @@
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_sound.h"
|
||||
#include "recomp_config.h"
|
||||
#include "recomp_debug.h"
|
||||
#include "zelda_sound.h"
|
||||
#include "zelda_config.h"
|
||||
#include "zelda_debug.h"
|
||||
#include "zelda_render.h"
|
||||
#include "promptfont.h"
|
||||
#include "../../ultramodern/config.hpp"
|
||||
#include "../../ultramodern/ultramodern.hpp"
|
||||
#include "ultramodern/config.hpp"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "RmlUi/Core.h"
|
||||
#include "rt64_layer.h"
|
||||
|
||||
ultramodern::GraphicsConfig new_options;
|
||||
ultramodern::renderer::GraphicsConfig new_options;
|
||||
Rml::DataModelHandle nav_help_model_handle;
|
||||
Rml::DataModelHandle general_model_handle;
|
||||
Rml::DataModelHandle controls_model_handle;
|
||||
Rml::DataModelHandle graphics_model_handle;
|
||||
Rml::DataModelHandle sound_options_model_handle;
|
||||
|
||||
recomp::PromptContext prompt_context;
|
||||
recompui::PromptContext prompt_context;
|
||||
|
||||
namespace recomp {
|
||||
namespace recompui {
|
||||
const std::unordered_map<ButtonVariant, std::string> button_variants {
|
||||
{ButtonVariant::Primary, "primary"},
|
||||
{ButtonVariant::Secondary, "secondary"},
|
||||
@ -198,13 +198,13 @@ void recomp::config_menu_set_cont_or_kb(bool cont_interacted) {
|
||||
}
|
||||
|
||||
void close_config_menu_impl() {
|
||||
recomp::save_config();
|
||||
zelda64::save_config();
|
||||
|
||||
if (ultramodern::is_game_started()) {
|
||||
recomp::set_current_menu(recomp::Menu::None);
|
||||
recompui::set_current_menu(recompui::Menu::None);
|
||||
}
|
||||
else {
|
||||
recomp::set_current_menu(recomp::Menu::Launcher);
|
||||
recompui::set_current_menu(recompui::Menu::Launcher);
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,9 +214,9 @@ extern SDL_Window* window;
|
||||
#endif
|
||||
|
||||
void apply_graphics_config(void) {
|
||||
ultramodern::set_graphics_config(new_options);
|
||||
ultramodern::renderer::set_graphics_config(new_options);
|
||||
#if defined(__linux__) // TODO: Remove once RT64 gets native fullscreen support on Linux
|
||||
if (new_options.wm_option == ultramodern::WindowMode::Fullscreen) {
|
||||
if (new_options.wm_option == ultramodern::renderer::WindowMode::Fullscreen) {
|
||||
SDL_SetWindowFullscreen(window,SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
} else {
|
||||
SDL_SetWindowFullscreen(window,0);
|
||||
@ -225,7 +225,7 @@ void apply_graphics_config(void) {
|
||||
}
|
||||
|
||||
void close_config_menu() {
|
||||
if (ultramodern::get_graphics_config() != new_options) {
|
||||
if (ultramodern::renderer::get_graphics_config() != new_options) {
|
||||
prompt_context.open_prompt(
|
||||
"Graphics options have changed",
|
||||
"Would you like to apply or discard the changes?",
|
||||
@ -237,12 +237,12 @@ void close_config_menu() {
|
||||
close_config_menu_impl();
|
||||
},
|
||||
[]() {
|
||||
new_options = ultramodern::get_graphics_config();
|
||||
new_options = ultramodern::renderer::get_graphics_config();
|
||||
graphics_model_handle.DirtyAllVariables();
|
||||
close_config_menu_impl();
|
||||
},
|
||||
recomp::ButtonVariant::Success,
|
||||
recomp::ButtonVariant::Error,
|
||||
recompui::ButtonVariant::Success,
|
||||
recompui::ButtonVariant::Error,
|
||||
true,
|
||||
"config__close-menu-button"
|
||||
);
|
||||
@ -252,7 +252,7 @@ void close_config_menu() {
|
||||
close_config_menu_impl();
|
||||
}
|
||||
|
||||
void recomp::open_quit_game_prompt() {
|
||||
void zelda64::open_quit_game_prompt() {
|
||||
prompt_context.open_prompt(
|
||||
"Are you sure you want to quit?",
|
||||
"Any progress since your last save will be lost.",
|
||||
@ -262,8 +262,8 @@ void recomp::open_quit_game_prompt() {
|
||||
ultramodern::quit();
|
||||
},
|
||||
[]() {},
|
||||
recomp::ButtonVariant::Error,
|
||||
recomp::ButtonVariant::Tertiary,
|
||||
recompui::ButtonVariant::Error,
|
||||
recompui::ButtonVariant::Tertiary,
|
||||
true,
|
||||
"config__quit-game-button"
|
||||
);
|
||||
@ -275,12 +275,12 @@ struct ControlOptionsContext {
|
||||
int gyro_sensitivity; // 0 to 100
|
||||
int mouse_sensitivity; // 0 to 100
|
||||
int joystick_deadzone; // 0 to 100
|
||||
recomp::TargetingMode targeting_mode;
|
||||
zelda64::TargetingMode targeting_mode;
|
||||
recomp::BackgroundInputMode background_input_mode;
|
||||
recomp::AutosaveMode autosave_mode;
|
||||
recomp::CameraInvertMode camera_invert_mode;
|
||||
recomp::AnalogCamMode analog_cam_mode;
|
||||
recomp::CameraInvertMode analog_camera_invert_mode;
|
||||
zelda64::AutosaveMode autosave_mode;
|
||||
zelda64::CameraInvertMode camera_invert_mode;
|
||||
zelda64::AnalogCamMode analog_cam_mode;
|
||||
zelda64::CameraInvertMode analog_camera_invert_mode;
|
||||
};
|
||||
|
||||
ControlOptionsContext control_options_context;
|
||||
@ -329,11 +329,11 @@ void recomp::set_joystick_deadzone(int deadzone) {
|
||||
}
|
||||
}
|
||||
|
||||
recomp::TargetingMode recomp::get_targeting_mode() {
|
||||
zelda64::TargetingMode zelda64::get_targeting_mode() {
|
||||
return control_options_context.targeting_mode;
|
||||
}
|
||||
|
||||
void recomp::set_targeting_mode(recomp::TargetingMode mode) {
|
||||
void zelda64::set_targeting_mode(zelda64::TargetingMode mode) {
|
||||
control_options_context.targeting_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("targeting_mode");
|
||||
@ -357,44 +357,44 @@ void recomp::set_background_input_mode(recomp::BackgroundInputMode mode) {
|
||||
);
|
||||
}
|
||||
|
||||
recomp::AutosaveMode recomp::get_autosave_mode() {
|
||||
zelda64::AutosaveMode zelda64::get_autosave_mode() {
|
||||
return control_options_context.autosave_mode;
|
||||
}
|
||||
|
||||
void recomp::set_autosave_mode(recomp::AutosaveMode mode) {
|
||||
void zelda64::set_autosave_mode(zelda64::AutosaveMode mode) {
|
||||
control_options_context.autosave_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("autosave_mode");
|
||||
}
|
||||
}
|
||||
|
||||
recomp::CameraInvertMode recomp::get_camera_invert_mode() {
|
||||
zelda64::CameraInvertMode zelda64::get_camera_invert_mode() {
|
||||
return control_options_context.camera_invert_mode;
|
||||
}
|
||||
|
||||
void recomp::set_camera_invert_mode(recomp::CameraInvertMode mode) {
|
||||
void zelda64::set_camera_invert_mode(zelda64::CameraInvertMode mode) {
|
||||
control_options_context.camera_invert_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("camera_invert_mode");
|
||||
}
|
||||
}
|
||||
|
||||
recomp::AnalogCamMode recomp::get_analog_cam_mode() {
|
||||
zelda64::AnalogCamMode zelda64::get_analog_cam_mode() {
|
||||
return control_options_context.analog_cam_mode;
|
||||
}
|
||||
|
||||
void recomp::set_analog_cam_mode(recomp::AnalogCamMode mode) {
|
||||
void zelda64::set_analog_cam_mode(zelda64::AnalogCamMode mode) {
|
||||
control_options_context.analog_cam_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("analog_cam_mode");
|
||||
}
|
||||
}
|
||||
|
||||
recomp::CameraInvertMode recomp::get_analog_camera_invert_mode() {
|
||||
zelda64::CameraInvertMode zelda64::get_analog_camera_invert_mode() {
|
||||
return control_options_context.analog_camera_invert_mode;
|
||||
}
|
||||
|
||||
void recomp::set_analog_camera_invert_mode(recomp::CameraInvertMode mode) {
|
||||
void zelda64::set_analog_camera_invert_mode(zelda64::CameraInvertMode mode) {
|
||||
control_options_context.analog_camera_invert_mode = mode;
|
||||
if (general_model_handle) {
|
||||
general_model_handle.DirtyVariable("analog_camera_invert_mode");
|
||||
@ -417,43 +417,43 @@ struct SoundOptionsContext {
|
||||
|
||||
SoundOptionsContext sound_options_context;
|
||||
|
||||
void recomp::reset_sound_settings() {
|
||||
void zelda64::reset_sound_settings() {
|
||||
sound_options_context.reset();
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyAllVariables();
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::set_main_volume(int volume) {
|
||||
void zelda64::set_main_volume(int volume) {
|
||||
sound_options_context.main_volume.store(volume);
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyVariable("main_volume");
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::get_main_volume() {
|
||||
int zelda64::get_main_volume() {
|
||||
return sound_options_context.main_volume.load();
|
||||
}
|
||||
|
||||
void recomp::set_bgm_volume(int volume) {
|
||||
void zelda64::set_bgm_volume(int volume) {
|
||||
sound_options_context.bgm_volume.store(volume);
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyVariable("bgm_volume");
|
||||
}
|
||||
}
|
||||
|
||||
int recomp::get_bgm_volume() {
|
||||
int zelda64::get_bgm_volume() {
|
||||
return sound_options_context.bgm_volume.load();
|
||||
}
|
||||
|
||||
void recomp::set_low_health_beeps_enabled(bool enabled) {
|
||||
void zelda64::set_low_health_beeps_enabled(bool enabled) {
|
||||
sound_options_context.low_health_beeps_enabled.store((int)enabled);
|
||||
if (sound_options_model_handle) {
|
||||
sound_options_model_handle.DirtyVariable("low_health_beeps_enabled");
|
||||
}
|
||||
}
|
||||
|
||||
bool recomp::get_low_health_beeps_enabled() {
|
||||
bool zelda64::get_low_health_beeps_enabled() {
|
||||
return (bool)sound_options_context.low_health_beeps_enabled.load();
|
||||
}
|
||||
|
||||
@ -471,7 +471,7 @@ struct DebugContext {
|
||||
bool debug_enabled = false;
|
||||
|
||||
DebugContext() {
|
||||
for (const auto& area : recomp::game_warps) {
|
||||
for (const auto& area : zelda64::game_warps) {
|
||||
area_names.emplace_back(area.name);
|
||||
}
|
||||
update_warp_names();
|
||||
@ -479,15 +479,15 @@ struct DebugContext {
|
||||
|
||||
void update_warp_names() {
|
||||
scene_names.clear();
|
||||
for (const auto& scene : recomp::game_warps[area_index].scenes) {
|
||||
for (const auto& scene : zelda64::game_warps[area_index].scenes) {
|
||||
scene_names.emplace_back(scene.name);
|
||||
}
|
||||
|
||||
entrance_names = recomp::game_warps[area_index].scenes[scene_index].entrances;
|
||||
entrance_names = zelda64::game_warps[area_index].scenes[scene_index].entrances;
|
||||
}
|
||||
};
|
||||
|
||||
void recomp::update_rml_display_refresh_rate() {
|
||||
void recompui::update_rml_display_refresh_rate() {
|
||||
static uint32_t lastRate = 0;
|
||||
if (!graphics_model_handle) return;
|
||||
|
||||
@ -500,7 +500,7 @@ void recomp::update_rml_display_refresh_rate() {
|
||||
|
||||
DebugContext debug_context;
|
||||
|
||||
class ConfigMenu : public recomp::MenuController {
|
||||
class ConfigMenu : public recompui::MenuController {
|
||||
public:
|
||||
ConfigMenu() {
|
||||
|
||||
@ -511,13 +511,13 @@ public:
|
||||
Rml::ElementDocument* load_document(Rml::Context* context) override {
|
||||
return context->LoadDocument("assets/config_menu.rml");
|
||||
}
|
||||
void register_events(recomp::UiEventListenerInstancer& listener) override {
|
||||
recomp::register_event(listener, "apply_options",
|
||||
void register_events(recompui::UiEventListenerInstancer& listener) override {
|
||||
recompui::register_event(listener, "apply_options",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
graphics_model_handle.DirtyVariable("options_changed");
|
||||
apply_graphics_config();
|
||||
});
|
||||
recomp::register_event(listener, "config_keydown",
|
||||
recompui::register_event(listener, "config_keydown",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
if (!prompt_context.open && event.GetId() == Rml::EventId::Keydown) {
|
||||
auto key = event.GetParameter<Rml::Input::KeyIdentifier>("key_identifier", Rml::Input::KeyIdentifier::KI_UNKNOWN);
|
||||
@ -533,23 +533,23 @@ public:
|
||||
}
|
||||
});
|
||||
// This needs to be separate from `close_config_menu` so it ensures that the event is only on the target
|
||||
recomp::register_event(listener, "close_config_menu_backdrop",
|
||||
recompui::register_event(listener, "close_config_menu_backdrop",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
if (event.GetPhase() == Rml::EventPhase::Target) {
|
||||
close_config_menu();
|
||||
}
|
||||
});
|
||||
recomp::register_event(listener, "close_config_menu",
|
||||
recompui::register_event(listener, "close_config_menu",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
close_config_menu();
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "open_quit_game_prompt",
|
||||
recompui::register_event(listener, "open_quit_game_prompt",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::open_quit_game_prompt();
|
||||
zelda64::open_quit_game_prompt();
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "toggle_input_device",
|
||||
recompui::register_event(listener, "toggle_input_device",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
cur_device = cur_device == recomp::InputDevice::Controller
|
||||
? recomp::InputDevice::Keyboard
|
||||
@ -558,7 +558,7 @@ public:
|
||||
controls_model_handle.DirtyVariable("inputs");
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "area_index_changed",
|
||||
recompui::register_event(listener, "area_index_changed",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
debug_context.area_index = event.GetParameter<int>("value", 0);
|
||||
debug_context.scene_index = 0;
|
||||
@ -570,7 +570,7 @@ public:
|
||||
debug_context.model_handle.DirtyVariable("entrance_names");
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "scene_index_changed",
|
||||
recompui::register_event(listener, "scene_index_changed",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
debug_context.scene_index = event.GetParameter<int>("value", 0);
|
||||
debug_context.entrance_index = 0;
|
||||
@ -579,14 +579,14 @@ public:
|
||||
debug_context.model_handle.DirtyVariable("entrance_names");
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "do_warp",
|
||||
recompui::register_event(listener, "do_warp",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::do_warp(debug_context.area_index, debug_context.scene_index, debug_context.entrance_index);
|
||||
zelda64::do_warp(debug_context.area_index, debug_context.scene_index, debug_context.entrance_index);
|
||||
});
|
||||
|
||||
recomp::register_event(listener, "set_time",
|
||||
recompui::register_event(listener, "set_time",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::set_time(debug_context.set_time_day, debug_context.set_time_hour, debug_context.set_time_minute);
|
||||
zelda64::set_time(debug_context.set_time_day, debug_context.set_time_hour, debug_context.set_time_minute);
|
||||
});
|
||||
}
|
||||
|
||||
@ -612,7 +612,7 @@ public:
|
||||
}
|
||||
|
||||
ultramodern::sleep_milliseconds(50);
|
||||
new_options = ultramodern::get_graphics_config();
|
||||
new_options = ultramodern::renderer::get_graphics_config();
|
||||
bind_config_list_events(constructor);
|
||||
|
||||
constructor.BindFunc("res_option",
|
||||
@ -639,7 +639,7 @@ public:
|
||||
});
|
||||
constructor.BindFunc("ds_option",
|
||||
[](Rml::Variant& out) {
|
||||
if (new_options.res_option == ultramodern::Resolution::Auto) {
|
||||
if (new_options.res_option == ultramodern::renderer::Resolution::Auto) {
|
||||
out = 1;
|
||||
} else {
|
||||
out = new_options.ds_option;
|
||||
@ -658,23 +658,23 @@ public:
|
||||
|
||||
constructor.BindFunc("options_changed",
|
||||
[](Rml::Variant& out) {
|
||||
out = (ultramodern::get_graphics_config() != new_options);
|
||||
out = (ultramodern::renderer::get_graphics_config() != new_options);
|
||||
});
|
||||
constructor.BindFunc("ds_info",
|
||||
[](Rml::Variant& out) {
|
||||
switch (new_options.res_option) {
|
||||
default:
|
||||
case ultramodern::Resolution::Auto:
|
||||
case ultramodern::renderer::Resolution::Auto:
|
||||
out = "Downsampling is not available at auto resolution";
|
||||
return;
|
||||
case ultramodern::Resolution::Original:
|
||||
case ultramodern::renderer::Resolution::Original:
|
||||
if (new_options.ds_option == 2) {
|
||||
out = "Rendered in 480p and scaled to 240p";
|
||||
} else if (new_options.ds_option == 4) {
|
||||
out = "Rendered in 960p and scaled to 240p";
|
||||
}
|
||||
return;
|
||||
case ultramodern::Resolution::Original2x:
|
||||
case ultramodern::renderer::Resolution::Original2x:
|
||||
if (new_options.ds_option == 2) {
|
||||
out = "Rendered in 960p and scaled to 480p";
|
||||
} else if (new_options.ds_option == 4) {
|
||||
@ -730,9 +730,9 @@ public:
|
||||
constructor.BindEventCallback("reset_input_bindings_to_defaults",
|
||||
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
||||
if (cur_device == recomp::InputDevice::Controller) {
|
||||
recomp::reset_cont_input_bindings();
|
||||
zelda64::reset_cont_input_bindings();
|
||||
} else {
|
||||
recomp::reset_kb_input_bindings();
|
||||
zelda64::reset_kb_input_bindings();
|
||||
}
|
||||
model_handle.DirtyAllVariables();
|
||||
});
|
||||
@ -966,14 +966,14 @@ public:
|
||||
constructor.Bind("prompt__confirmLabel", &prompt_context.confirmLabel);
|
||||
constructor.Bind("prompt__cancelLabel", &prompt_context.cancelLabel);
|
||||
|
||||
constructor.BindEventCallback("prompt__on_click", &recomp::PromptContext::on_click, &prompt_context);
|
||||
constructor.BindEventCallback("prompt__on_click", &recompui::PromptContext::on_click, &prompt_context);
|
||||
|
||||
prompt_context.model_handle = constructor.GetModelHandle();
|
||||
}
|
||||
|
||||
void make_bindings(Rml::Context* context) override {
|
||||
// initially set cont state for ui help
|
||||
recomp::config_menu_set_cont_or_kb(recomp::get_cont_active());
|
||||
recomp::config_menu_set_cont_or_kb(recompui::get_cont_active());
|
||||
make_nav_help_bindings(context);
|
||||
make_general_bindings(context);
|
||||
make_controls_bindings(context);
|
||||
@ -984,34 +984,34 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<recomp::MenuController> recomp::create_config_menu() {
|
||||
std::unique_ptr<recompui::MenuController> recompui::create_config_menu() {
|
||||
return std::make_unique<ConfigMenu>();
|
||||
}
|
||||
|
||||
bool recomp::get_debug_mode_enabled() {
|
||||
bool zelda64::get_debug_mode_enabled() {
|
||||
return debug_context.debug_enabled;
|
||||
}
|
||||
|
||||
void recomp::set_debug_mode_enabled(bool enabled) {
|
||||
void zelda64::set_debug_mode_enabled(bool enabled) {
|
||||
debug_context.debug_enabled = enabled;
|
||||
if (debug_context.model_handle) {
|
||||
debug_context.model_handle.DirtyVariable("debug_enabled");
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::update_supported_options() {
|
||||
msaa2x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA2X;
|
||||
msaa4x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA4X;
|
||||
msaa8x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA8X;
|
||||
sample_positions_supported = ultramodern::RT64SamplePositionsSupported();
|
||||
void recompui::update_supported_options() {
|
||||
msaa2x_supported = zelda64::renderer::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA2X;
|
||||
msaa4x_supported = zelda64::renderer::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA4X;
|
||||
msaa8x_supported = zelda64::renderer::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA8X;
|
||||
sample_positions_supported = zelda64::renderer::RT64SamplePositionsSupported();
|
||||
|
||||
new_options = ultramodern::get_graphics_config();
|
||||
new_options = ultramodern::renderer::get_graphics_config();
|
||||
|
||||
graphics_model_handle.DirtyAllVariables();
|
||||
}
|
||||
|
||||
void recomp::toggle_fullscreen() {
|
||||
new_options.wm_option = (new_options.wm_option == ultramodern::WindowMode::Windowed) ? ultramodern::WindowMode::Fullscreen : ultramodern::WindowMode::Windowed;
|
||||
void recompui::toggle_fullscreen() {
|
||||
new_options.wm_option = (new_options.wm_option == ultramodern::renderer::WindowMode::Windowed) ? ultramodern::renderer::WindowMode::Fullscreen : ultramodern::renderer::WindowMode::Windowed;
|
||||
apply_graphics_config();
|
||||
graphics_model_handle.DirtyVariable("wm_option");
|
||||
}
|
||||
|
@ -1,16 +1,18 @@
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_config.h"
|
||||
#include "recomp_game.h"
|
||||
#include "../../ultramodern/ultramodern.hpp"
|
||||
#include "zelda_config.h"
|
||||
#include "librecomp/game.hpp"
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "RmlUi/Core.h"
|
||||
#include "nfd.h"
|
||||
#include <filesystem>
|
||||
|
||||
std::string version_number = "v1.1.0";
|
||||
std::string version_number = "v1.1.1";
|
||||
|
||||
Rml::DataModelHandle model_handle;
|
||||
bool mm_rom_valid = false;
|
||||
|
||||
extern std::vector<recomp::GameEntry> supported_games;
|
||||
|
||||
void select_rom() {
|
||||
nfdnchar_t* native_path = nullptr;
|
||||
nfdresult_t result = NFD_OpenDialogN(&native_path, nullptr, 0, nullptr);
|
||||
@ -21,39 +23,39 @@ void select_rom() {
|
||||
NFD_FreePathN(native_path);
|
||||
native_path = nullptr;
|
||||
|
||||
recomp::RomValidationError rom_error = recomp::select_rom(path, recomp::Game::MM);
|
||||
|
||||
recomp::RomValidationError rom_error = recomp::select_rom(path, supported_games[0].game_id);
|
||||
switch (rom_error) {
|
||||
case recomp::RomValidationError::Good:
|
||||
mm_rom_valid = true;
|
||||
model_handle.DirtyVariable("mm_rom_valid");
|
||||
break;
|
||||
case recomp::RomValidationError::FailedToOpen:
|
||||
recomp::message_box("Failed to open ROM file.");
|
||||
recompui::message_box("Failed to open ROM file.");
|
||||
break;
|
||||
case recomp::RomValidationError::NotARom:
|
||||
recomp::message_box("This is not a valid ROM file.");
|
||||
recompui::message_box("This is not a valid ROM file.");
|
||||
break;
|
||||
case recomp::RomValidationError::IncorrectRom:
|
||||
recomp::message_box("This ROM is not the correct game.");
|
||||
recompui::message_box("This ROM is not the correct game.");
|
||||
break;
|
||||
case recomp::RomValidationError::NotYet:
|
||||
recomp::message_box("This game isn't supported yet.");
|
||||
recompui::message_box("This game isn't supported yet.");
|
||||
break;
|
||||
case recomp::RomValidationError::IncorrectVersion:
|
||||
recomp::message_box("This ROM is the correct game, but the wrong version.\nThis project requires the NTSC-U N64 version of the game.");
|
||||
recompui::message_box(
|
||||
"This ROM is the correct game, but the wrong version.\nThis project requires the NTSC-U N64 version of the game.");
|
||||
break;
|
||||
case recomp::RomValidationError::OtherError:
|
||||
recomp::message_box("An unknown error has occurred.");
|
||||
recompui::message_box("An unknown error has occurred.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LauncherMenu : public recomp::MenuController {
|
||||
class LauncherMenu : public recompui::MenuController {
|
||||
public:
|
||||
LauncherMenu() {
|
||||
mm_rom_valid = recomp::is_rom_valid(recomp::Game::MM);
|
||||
mm_rom_valid = recomp::is_rom_valid(supported_games[0].game_id);
|
||||
}
|
||||
~LauncherMenu() override {
|
||||
|
||||
@ -61,37 +63,37 @@ public:
|
||||
Rml::ElementDocument* load_document(Rml::Context* context) override {
|
||||
return context->LoadDocument("assets/launcher.rml");
|
||||
}
|
||||
void register_events(recomp::UiEventListenerInstancer& listener) override {
|
||||
recomp::register_event(listener, "select_rom",
|
||||
void register_events(recompui::UiEventListenerInstancer& listener) override {
|
||||
recompui::register_event(listener, "select_rom",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
select_rom();
|
||||
}
|
||||
);
|
||||
recomp::register_event(listener, "rom_selected",
|
||||
recompui::register_event(listener, "rom_selected",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
mm_rom_valid = true;
|
||||
model_handle.DirtyVariable("mm_rom_valid");
|
||||
}
|
||||
);
|
||||
recomp::register_event(listener, "start_game",
|
||||
recompui::register_event(listener, "start_game",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::start_game(recomp::Game::MM);
|
||||
recomp::set_current_menu(recomp::Menu::None);
|
||||
recomp::start_game(supported_games[0].game_id);
|
||||
recompui::set_current_menu(recompui::Menu::None);
|
||||
}
|
||||
);
|
||||
recomp::register_event(listener, "open_controls",
|
||||
recompui::register_event(listener, "open_controls",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::set_current_menu(recomp::Menu::Config);
|
||||
recomp::set_config_submenu(recomp::ConfigSubmenu::Controls);
|
||||
recompui::set_current_menu(recompui::Menu::Config);
|
||||
recompui::set_config_submenu(recompui::ConfigSubmenu::Controls);
|
||||
}
|
||||
);
|
||||
recomp::register_event(listener, "open_settings",
|
||||
recompui::register_event(listener, "open_settings",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::set_current_menu(recomp::Menu::Config);
|
||||
recomp::set_config_submenu(recomp::ConfigSubmenu::General);
|
||||
recompui::set_current_menu(recompui::Menu::Config);
|
||||
recompui::set_config_submenu(recompui::ConfigSubmenu::General);
|
||||
}
|
||||
);
|
||||
recomp::register_event(listener, "exit_game",
|
||||
recompui::register_event(listener, "exit_game",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
ultramodern::quit();
|
||||
}
|
||||
@ -107,6 +109,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<recomp::MenuController> recomp::create_launcher_menu() {
|
||||
std::unique_ptr<recompui::MenuController> recompui::create_launcher_menu() {
|
||||
return std::make_unique<LauncherMenu>();
|
||||
}
|
||||
|
@ -4,16 +4,21 @@
|
||||
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#ifdef _WIN32
|
||||
#include <SDL_video.h>
|
||||
#else
|
||||
#include <SDL2/SDL_video.h>
|
||||
#endif
|
||||
|
||||
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_game.h"
|
||||
#include "recomp_config.h"
|
||||
#include "librecomp/game.hpp"
|
||||
#include "zelda_config.h"
|
||||
#include "ui_rml_hacks.hpp"
|
||||
|
||||
#include "concurrentqueue.h"
|
||||
|
||||
#include "rt64_layer.h"
|
||||
#include "rt64_render_hooks.h"
|
||||
#include "rt64_render_interface_builders.h"
|
||||
|
||||
@ -718,7 +723,7 @@ Rml::Element* get_target(Rml::ElementDocument* document, Rml::Element* element)
|
||||
return element;
|
||||
}
|
||||
|
||||
namespace recomp {
|
||||
namespace recompui {
|
||||
class UiEventListener : public Rml::EventListener {
|
||||
event_handler_t* handler_;
|
||||
Rml::String param_;
|
||||
@ -759,7 +764,7 @@ namespace recomp {
|
||||
};
|
||||
}
|
||||
|
||||
void recomp::register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler) {
|
||||
void recompui::register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler) {
|
||||
listener.register_event(name, handler);
|
||||
}
|
||||
|
||||
@ -779,8 +784,8 @@ Rml::Element* find_autofocus_element(Rml::Element* start) {
|
||||
struct UIContext {
|
||||
struct UIRenderContext render;
|
||||
class {
|
||||
std::unordered_map<recomp::Menu, std::unique_ptr<recomp::MenuController>> menus;
|
||||
std::unordered_map<recomp::Menu, Rml::ElementDocument*> documents;
|
||||
std::unordered_map<recompui::Menu, std::unique_ptr<recompui::MenuController>> menus;
|
||||
std::unordered_map<recompui::Menu, Rml::ElementDocument*> documents;
|
||||
Rml::ElementDocument* current_document;
|
||||
Rml::Element* prev_focused;
|
||||
bool mouse_is_active_changed = false;
|
||||
@ -794,13 +799,13 @@ struct UIContext {
|
||||
std::unique_ptr<SystemInterface_SDL> system_interface;
|
||||
std::unique_ptr<RmlRenderInterface_RT64> render_interface;
|
||||
Rml::Context* context;
|
||||
recomp::UiEventListenerInstancer event_listener_instancer;
|
||||
recompui::UiEventListenerInstancer event_listener_instancer;
|
||||
|
||||
void unload() {
|
||||
render_interface.reset();
|
||||
}
|
||||
|
||||
void swap_document(recomp::Menu menu) {
|
||||
void swap_document(recompui::Menu menu) {
|
||||
if (current_document != nullptr) {
|
||||
Rml::Element* window_el = current_document->GetElementById("window");
|
||||
if (window_el != nullptr) {
|
||||
@ -831,7 +836,7 @@ struct UIContext {
|
||||
mouse_is_active_initialized = false;
|
||||
}
|
||||
|
||||
void swap_config_menu(recomp::ConfigSubmenu submenu) {
|
||||
void swap_config_menu(recompui::ConfigSubmenu submenu) {
|
||||
if (current_document != nullptr) {
|
||||
Rml::Element* config_tabset_base = current_document->GetElementById("config_tabset");
|
||||
if (config_tabset_base != nullptr) {
|
||||
@ -911,7 +916,7 @@ struct UIContext {
|
||||
}
|
||||
|
||||
if (mouse_is_active_initialized) {
|
||||
recomp::set_cursor_visible(mouse_is_active);
|
||||
recompui::set_cursor_visible(mouse_is_active);
|
||||
}
|
||||
|
||||
if (current_document == nullptr) {
|
||||
@ -939,7 +944,6 @@ struct UIContext {
|
||||
if (cont_is_active || non_mouse_interacted) {
|
||||
if (non_mouse_interacted) {
|
||||
auto focusedEl = current_document->GetFocusLeafNode();
|
||||
Rml::Variant* ti = focusedEl == nullptr ? nullptr : focusedEl->GetAttribute("tab-index");
|
||||
if (focusedEl == nullptr || RecompRml::CanFocusElement(focusedEl) != RecompRml::CanFocus::Yes) {
|
||||
Rml::Element* element = find_autofocus_element(current_document);
|
||||
if (element != nullptr) {
|
||||
@ -981,14 +985,14 @@ struct UIContext {
|
||||
}
|
||||
}
|
||||
|
||||
void add_menu(recomp::Menu menu, std::unique_ptr<recomp::MenuController>&& controller) {
|
||||
void add_menu(recompui::Menu menu, std::unique_ptr<recompui::MenuController>&& controller) {
|
||||
menus.emplace(menu, std::move(controller));
|
||||
}
|
||||
|
||||
void update_config_menu_loop(bool menu_changed) {
|
||||
static int prevTab = -1;
|
||||
if (menu_changed) prevTab = -1;
|
||||
recomp::update_rml_display_refresh_rate();
|
||||
recompui::update_rml_display_refresh_rate();
|
||||
|
||||
Rml::ElementTabSet *tabset = (Rml::ElementTabSet *)current_document->GetElementById("config_tabset");
|
||||
if (tabset == nullptr) return;
|
||||
@ -1022,7 +1026,7 @@ struct UIContext {
|
||||
void update_prompt_loop(void) {
|
||||
static bool wasShowingPrompt = false;
|
||||
|
||||
recomp::PromptContext *ctx = recomp::get_prompt_context();
|
||||
recompui::PromptContext *ctx = recompui::get_prompt_context();
|
||||
if (!ctx->open && wasShowingPrompt) {
|
||||
Rml::Element* focused = current_document->GetFocusLeafNode();
|
||||
if (focused) focused->Blur();
|
||||
@ -1088,8 +1092,8 @@ struct UIContext {
|
||||
|
||||
Rml::Element *confirmButton = current_document->GetElementById("prompt__confirm-button");
|
||||
Rml::Element *cancelButton = current_document->GetElementById("prompt__cancel-button");
|
||||
if (confirmButton != nullptr) confirmButton->SetClassNames("button button--" + recomp::button_variants.at(ctx->confirmVariant));
|
||||
if (cancelButton != nullptr) cancelButton->SetClassNames( "button button--" + recomp::button_variants.at(ctx->cancelVariant));
|
||||
if (confirmButton != nullptr) confirmButton->SetClassNames("button button--" + recompui::button_variants.at(ctx->confirmVariant));
|
||||
if (cancelButton != nullptr) cancelButton->SetClassNames( "button button--" + recompui::button_variants.at(ctx->cancelVariant));
|
||||
}
|
||||
} rml;
|
||||
};
|
||||
@ -1100,7 +1104,7 @@ std::mutex ui_context_mutex{};
|
||||
// TODO make this not be global
|
||||
extern SDL_Window* window;
|
||||
|
||||
void recomp::get_window_size(int& width, int& height) {
|
||||
void recompui::get_window_size(int& width, int& height) {
|
||||
SDL_GetWindowSizeInPixels(window, &width, &height);
|
||||
}
|
||||
|
||||
@ -1110,8 +1114,8 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||
#endif
|
||||
ui_context = std::make_unique<UIContext>();
|
||||
|
||||
ui_context->rml.add_menu(recomp::Menu::Config, recomp::create_config_menu());
|
||||
ui_context->rml.add_menu(recomp::Menu::Launcher, recomp::create_launcher_menu());
|
||||
ui_context->rml.add_menu(recompui::Menu::Config, recompui::create_config_menu());
|
||||
ui_context->rml.add_menu(recompui::Menu::Launcher, recompui::create_launcher_menu());
|
||||
|
||||
ui_context->render.interface = interface;
|
||||
ui_context->render.device = device;
|
||||
@ -1129,7 +1133,7 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||
Rml::Initialise();
|
||||
|
||||
// Apply the hack to replace RmlUi's default color parser with one that conforms to HTML5 alpha parsing for SASS compatibility
|
||||
recomp::apply_color_hack();
|
||||
recompui::apply_color_hack();
|
||||
|
||||
int width, height;
|
||||
SDL_GetWindowSizeInPixels(window, &width, &height);
|
||||
@ -1167,16 +1171,16 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||
|
||||
moodycamel::ConcurrentQueue<SDL_Event> ui_event_queue{};
|
||||
|
||||
void recomp::queue_event(const SDL_Event& event) {
|
||||
void recompui::queue_event(const SDL_Event& event) {
|
||||
ui_event_queue.enqueue(event);
|
||||
}
|
||||
|
||||
bool recomp::try_deque_event(SDL_Event& out) {
|
||||
bool recompui::try_deque_event(SDL_Event& out) {
|
||||
return ui_event_queue.try_dequeue(out);
|
||||
}
|
||||
|
||||
std::atomic<recomp::Menu> open_menu = recomp::Menu::Launcher;
|
||||
std::atomic<recomp::ConfigSubmenu> open_config_submenu = recomp::ConfigSubmenu::Count;
|
||||
std::atomic<recompui::Menu> open_menu = recompui::Menu::Launcher;
|
||||
std::atomic<recompui::ConfigSubmenu> open_config_submenu = recompui::ConfigSubmenu::Count;
|
||||
|
||||
int cont_button_to_key(SDL_ControllerButtonEvent& button) {
|
||||
switch (button.button) {
|
||||
@ -1236,15 +1240,15 @@ void apply_background_input_mode() {
|
||||
last_input_mode = cur_input_mode;
|
||||
}
|
||||
|
||||
bool recomp::get_cont_active() {
|
||||
bool recompui::get_cont_active() {
|
||||
return ui_context->rml.cont_is_active;
|
||||
}
|
||||
|
||||
void recomp::set_cont_active(bool active) {
|
||||
void recompui::set_cont_active(bool active) {
|
||||
ui_context->rml.cont_is_active = active;
|
||||
}
|
||||
|
||||
void recomp::activate_mouse() {
|
||||
void recompui::activate_mouse() {
|
||||
ui_context->rml.update_primary_input(true, false);
|
||||
ui_context->rml.update_focus(true, false);
|
||||
}
|
||||
@ -1267,12 +1271,12 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
bool reload_sheets = is_reload_held && !was_reload_held;
|
||||
was_reload_held = is_reload_held;
|
||||
|
||||
static recomp::Menu prev_menu = recomp::Menu::None;
|
||||
recomp::Menu cur_menu = open_menu.load();
|
||||
static recompui::Menu prev_menu = recompui::Menu::None;
|
||||
recompui::Menu cur_menu = open_menu.load();
|
||||
|
||||
if (reload_sheets) {
|
||||
ui_context->rml.load_documents();
|
||||
prev_menu = recomp::Menu::None;
|
||||
prev_menu = recompui::Menu::None;
|
||||
}
|
||||
|
||||
bool menu_changed = cur_menu != prev_menu;
|
||||
@ -1280,10 +1284,10 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
ui_context->rml.swap_document(cur_menu);
|
||||
}
|
||||
|
||||
recomp::ConfigSubmenu config_submenu = open_config_submenu.load();
|
||||
if (config_submenu != recomp::ConfigSubmenu::Count) {
|
||||
recompui::ConfigSubmenu config_submenu = open_config_submenu.load();
|
||||
if (config_submenu != recompui::ConfigSubmenu::Count) {
|
||||
ui_context->rml.swap_config_menu(config_submenu);
|
||||
open_config_submenu.store(recomp::ConfigSubmenu::Count);
|
||||
open_config_submenu.store(recompui::ConfigSubmenu::Count);
|
||||
}
|
||||
|
||||
prev_menu = cur_menu;
|
||||
@ -1296,15 +1300,15 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
bool cont_interacted = false;
|
||||
bool kb_interacted = false;
|
||||
|
||||
if (cur_menu == recomp::Menu::Config) {
|
||||
if (cur_menu == recompui::Menu::Config) {
|
||||
ui_context->rml.update_config_menu_loop(menu_changed);
|
||||
}
|
||||
if (cur_menu != recomp::Menu::None) {
|
||||
if (cur_menu != recompui::Menu::None) {
|
||||
ui_context->rml.update_prompt_loop();
|
||||
}
|
||||
|
||||
while (recomp::try_deque_event(cur_event)) {
|
||||
bool menu_is_open = cur_menu != recomp::Menu::None;
|
||||
while (recompui::try_deque_event(cur_event)) {
|
||||
bool menu_is_open = cur_menu != recompui::Menu::None;
|
||||
|
||||
if (!recomp::all_input_disabled()) {
|
||||
// Implement some additional behavior for specific events on top of what RmlUi normally does with them.
|
||||
@ -1323,7 +1327,7 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
last_mouse_pos[1] = cur_event.motion.y;
|
||||
|
||||
// if controller is the primary input, don't use mouse movement to allow cursor to reactivate
|
||||
if (recomp::get_cont_active()) {
|
||||
if (recompui::get_cont_active()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1401,15 +1405,15 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
}
|
||||
|
||||
if (open_config) {
|
||||
cur_menu = recomp::Menu::Config;
|
||||
open_menu.store(recomp::Menu::Config);
|
||||
cur_menu = recompui::Menu::Config;
|
||||
open_menu.store(recompui::Menu::Config);
|
||||
ui_context->rml.swap_document(cur_menu);
|
||||
}
|
||||
}
|
||||
} // end dequeue event loop
|
||||
|
||||
if (cont_interacted || kb_interacted || mouse_clicked) {
|
||||
recomp::set_cont_active(cont_interacted);
|
||||
recompui::set_cont_active(cont_interacted);
|
||||
}
|
||||
recomp::config_menu_set_cont_or_kb(ui_context->rml.cont_is_active);
|
||||
|
||||
@ -1421,7 +1425,7 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
|
||||
ui_context->rml.update_primary_input(mouse_moved, non_mouse_interacted);
|
||||
ui_context->rml.update_focus(mouse_moved, non_mouse_interacted);
|
||||
|
||||
if (cur_menu != recomp::Menu::None) {
|
||||
if (cur_menu != recompui::Menu::None) {
|
||||
int width = swap_chain_framebuffer->getWidth();
|
||||
int height = swap_chain_framebuffer->getHeight();
|
||||
|
||||
@ -1453,29 +1457,29 @@ void deinit_hook() {
|
||||
ui_context.reset();
|
||||
}
|
||||
|
||||
void set_rt64_hooks() {
|
||||
void recompui::set_render_hooks() {
|
||||
RT64::SetRenderHooks(init_hook, draw_hook, deinit_hook);
|
||||
}
|
||||
|
||||
void recomp::set_current_menu(Menu menu) {
|
||||
void recompui::set_current_menu(Menu menu) {
|
||||
open_menu.store(menu);
|
||||
if (menu == recomp::Menu::None) {
|
||||
if (menu == recompui::Menu::None) {
|
||||
ui_context->rml.system_interface->SetMouseCursor("arrow");
|
||||
}
|
||||
}
|
||||
|
||||
void recomp::set_config_submenu(recomp::ConfigSubmenu submenu) {
|
||||
void recompui::set_config_submenu(recompui::ConfigSubmenu submenu) {
|
||||
open_config_submenu.store(submenu);
|
||||
}
|
||||
|
||||
void recomp::destroy_ui() {
|
||||
void recompui::destroy_ui() {
|
||||
}
|
||||
|
||||
recomp::Menu recomp::get_current_menu() {
|
||||
recompui::Menu recompui::get_current_menu() {
|
||||
return open_menu.load();
|
||||
}
|
||||
|
||||
void recomp::message_box(const char* msg) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, recomp::program_name.data(), msg, nullptr);
|
||||
void recompui::message_box(const char* msg) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, zelda64::program_name.data(), msg, nullptr);
|
||||
printf("[ERROR] %s\n", msg);
|
||||
}
|
||||
|
@ -1,67 +0,0 @@
|
||||
#include "ultra64.h"
|
||||
#include "ultramodern.hpp"
|
||||
#include <cassert>
|
||||
|
||||
static uint32_t sample_rate = 48000;
|
||||
|
||||
static ultramodern::audio_callbacks_t audio_callbacks;
|
||||
|
||||
void set_audio_callbacks(const ultramodern::audio_callbacks_t& callbacks) {
|
||||
audio_callbacks = callbacks;
|
||||
}
|
||||
|
||||
void ultramodern::init_audio() {
|
||||
// Pick an initial dummy sample rate; this will be set by the game later to the true sample rate.
|
||||
set_audio_frequency(48000);
|
||||
}
|
||||
|
||||
void ultramodern::set_audio_frequency(uint32_t freq) {
|
||||
if (audio_callbacks.set_frequency) {
|
||||
audio_callbacks.set_frequency(freq);
|
||||
}
|
||||
sample_rate = freq;
|
||||
}
|
||||
|
||||
void ultramodern::queue_audio_buffer(RDRAM_ARG PTR(int16_t) audio_data_, uint32_t byte_count) {
|
||||
// Ensure that the byte count is an integer multiple of samples.
|
||||
assert((byte_count & 1) == 0);
|
||||
|
||||
// Calculate the number of samples from the number of bytes.
|
||||
uint32_t sample_count = byte_count / sizeof(int16_t);
|
||||
|
||||
// Queue the swapped audio data.
|
||||
if (audio_callbacks.queue_samples) {
|
||||
audio_callbacks.queue_samples(TO_PTR(int16_t, audio_data_), sample_count);
|
||||
}
|
||||
}
|
||||
|
||||
// For SDL2
|
||||
//uint32_t buffer_offset_frames = 1;
|
||||
// For Godot
|
||||
float buffer_offset_frames = 0.5f;
|
||||
|
||||
// If there's ever any audio popping, check here first. Some games are very sensitive to
|
||||
// the remaining sample count and reporting a number that's too high here can lead to issues.
|
||||
// Reporting a number that's too low can lead to audio lag in some games.
|
||||
uint32_t ultramodern::get_remaining_audio_bytes() {
|
||||
// Get the number of remaining buffered audio bytes.
|
||||
uint32_t buffered_byte_count;
|
||||
if (audio_callbacks.get_frames_remaining != nullptr) {
|
||||
buffered_byte_count = audio_callbacks.get_frames_remaining() * 2 * sizeof(int16_t);
|
||||
}
|
||||
else {
|
||||
buffered_byte_count = 100;
|
||||
}
|
||||
// Adjust the reported count to be some number of refreshes in the future, which helps ensure that
|
||||
// there are enough samples even if the audio thread experiences a small amount of lag. This prevents
|
||||
// audio popping on games that use the buffered audio byte count to determine how many samples
|
||||
// to generate.
|
||||
uint32_t samples_per_vi = (sample_rate / 60);
|
||||
if (buffered_byte_count > static_cast<uint32_t>(buffer_offset_frames * sizeof(int16_t) * samples_per_vi)) {
|
||||
buffered_byte_count -= static_cast<uint32_t>(buffer_offset_frames * sizeof(int16_t) * samples_per_vi);
|
||||
}
|
||||
else {
|
||||
buffered_byte_count = 0;
|
||||
}
|
||||
return buffered_byte_count;
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
#ifndef __CONFIG_HPP__
|
||||
#define __CONFIG_HPP__
|
||||
|
||||
#include "common/rt64_user_configuration.h"
|
||||
|
||||
namespace ultramodern {
|
||||
enum class Resolution {
|
||||
Original,
|
||||
Original2x,
|
||||
Auto,
|
||||
OptionCount
|
||||
};
|
||||
enum class WindowMode {
|
||||
Windowed,
|
||||
Fullscreen,
|
||||
OptionCount
|
||||
};
|
||||
enum class HUDRatioMode {
|
||||
Original,
|
||||
Clamp16x9,
|
||||
Full,
|
||||
OptionCount
|
||||
};
|
||||
enum class GraphicsApi {
|
||||
Auto,
|
||||
D3D12,
|
||||
Vulkan,
|
||||
OptionCount
|
||||
};
|
||||
enum class HighPrecisionFramebuffer {
|
||||
Auto,
|
||||
On,
|
||||
Off,
|
||||
OptionCount
|
||||
};
|
||||
|
||||
struct GraphicsConfig {
|
||||
Resolution res_option;
|
||||
WindowMode wm_option;
|
||||
HUDRatioMode hr_option;
|
||||
GraphicsApi api_option;
|
||||
RT64::UserConfiguration::AspectRatio ar_option;
|
||||
RT64::UserConfiguration::Antialiasing msaa_option;
|
||||
RT64::UserConfiguration::RefreshRate rr_option;
|
||||
HighPrecisionFramebuffer hpfb_option;
|
||||
int rr_manual_value;
|
||||
int ds_option;
|
||||
bool developer_mode;
|
||||
|
||||
auto operator<=>(const GraphicsConfig& rhs) const = default;
|
||||
};
|
||||
|
||||
void set_graphics_config(const GraphicsConfig& config);
|
||||
GraphicsConfig get_graphics_config();
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::Resolution, {
|
||||
{ultramodern::Resolution::Original, "Original"},
|
||||
{ultramodern::Resolution::Original2x, "Original2x"},
|
||||
{ultramodern::Resolution::Auto, "Auto"},
|
||||
});
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::WindowMode, {
|
||||
{ultramodern::WindowMode::Windowed, "Windowed"},
|
||||
{ultramodern::WindowMode::Fullscreen, "Fullscreen"}
|
||||
});
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::HUDRatioMode, {
|
||||
{ultramodern::HUDRatioMode::Original, "Original"},
|
||||
{ultramodern::HUDRatioMode::Clamp16x9, "Clamp16x9"},
|
||||
{ultramodern::HUDRatioMode::Full, "Full"},
|
||||
});
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::GraphicsApi, {
|
||||
{ultramodern::GraphicsApi::Auto, "Auto"},
|
||||
{ultramodern::GraphicsApi::D3D12, "D3D12"},
|
||||
{ultramodern::GraphicsApi::Vulkan, "Vulkan"},
|
||||
});
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::HighPrecisionFramebuffer, {
|
||||
{ultramodern::HighPrecisionFramebuffer::Auto, "Auto"},
|
||||
{ultramodern::HighPrecisionFramebuffer::On, "On"},
|
||||
{ultramodern::HighPrecisionFramebuffer::Off, "Off"},
|
||||
});
|
||||
};
|
||||
|
||||
#endif
|
@ -1,620 +0,0 @@
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cinttypes>
|
||||
#include <variant>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <cstring>
|
||||
|
||||
#include "blockingconcurrentqueue.h"
|
||||
|
||||
#include "ultra64.h"
|
||||
#include "ultramodern.hpp"
|
||||
#include "config.hpp"
|
||||
#include "rt64_layer.h"
|
||||
#include "recomp.h"
|
||||
#include "recomp_game.h"
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_input.h"
|
||||
#include "rsp.h"
|
||||
|
||||
struct SpTaskAction {
|
||||
OSTask task;
|
||||
};
|
||||
|
||||
struct SwapBuffersAction {
|
||||
uint32_t origin;
|
||||
};
|
||||
|
||||
struct UpdateConfigAction {
|
||||
};
|
||||
|
||||
struct LoadShaderCacheAction {
|
||||
std::span<const char> data;
|
||||
};
|
||||
|
||||
using Action = std::variant<SpTaskAction, SwapBuffersAction, UpdateConfigAction, LoadShaderCacheAction>;
|
||||
|
||||
static struct {
|
||||
struct {
|
||||
std::thread thread;
|
||||
PTR(OSMesgQueue) mq = NULLPTR;
|
||||
PTR(void) current_buffer = NULLPTR;
|
||||
PTR(void) next_buffer = NULLPTR;
|
||||
OSMesg msg = (OSMesg)0;
|
||||
int retrace_count = 1;
|
||||
} vi;
|
||||
struct {
|
||||
std::thread gfx_thread;
|
||||
std::thread task_thread;
|
||||
PTR(OSMesgQueue) mq = NULLPTR;
|
||||
OSMesg msg = (OSMesg)0;
|
||||
} sp;
|
||||
struct {
|
||||
PTR(OSMesgQueue) mq = NULLPTR;
|
||||
OSMesg msg = (OSMesg)0;
|
||||
} dp;
|
||||
struct {
|
||||
PTR(OSMesgQueue) mq = NULLPTR;
|
||||
OSMesg msg = (OSMesg)0;
|
||||
} ai;
|
||||
struct {
|
||||
PTR(OSMesgQueue) mq = NULLPTR;
|
||||
OSMesg msg = (OSMesg)0;
|
||||
} si;
|
||||
// The same message queue may be used for multiple events, so share a mutex for all of them
|
||||
std::mutex message_mutex;
|
||||
uint8_t* rdram;
|
||||
moodycamel::BlockingConcurrentQueue<Action> action_queue{};
|
||||
moodycamel::BlockingConcurrentQueue<OSTask*> sp_task_queue{};
|
||||
moodycamel::ConcurrentQueue<OSThread*> deleted_threads{};
|
||||
} events_context{};
|
||||
|
||||
extern "C" void osSetEventMesg(RDRAM_ARG OSEvent event_id, PTR(OSMesgQueue) mq_, OSMesg msg) {
|
||||
OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_);
|
||||
std::lock_guard lock{ events_context.message_mutex };
|
||||
|
||||
switch (event_id) {
|
||||
case OS_EVENT_SP:
|
||||
events_context.sp.msg = msg;
|
||||
events_context.sp.mq = mq_;
|
||||
break;
|
||||
case OS_EVENT_DP:
|
||||
events_context.dp.msg = msg;
|
||||
events_context.dp.mq = mq_;
|
||||
break;
|
||||
case OS_EVENT_AI:
|
||||
events_context.ai.msg = msg;
|
||||
events_context.ai.mq = mq_;
|
||||
break;
|
||||
case OS_EVENT_SI:
|
||||
events_context.si.msg = msg;
|
||||
events_context.si.mq = mq_;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, u32 retrace_count) {
|
||||
std::lock_guard lock{ events_context.message_mutex };
|
||||
events_context.vi.mq = mq_;
|
||||
events_context.vi.msg = msg;
|
||||
events_context.vi.retrace_count = retrace_count;
|
||||
}
|
||||
|
||||
uint64_t total_vis = 0;
|
||||
|
||||
|
||||
extern std::atomic_bool exited;
|
||||
|
||||
void set_dummy_vi();
|
||||
|
||||
void vi_thread_func() {
|
||||
ultramodern::set_native_thread_name("VI Thread");
|
||||
// This thread should be prioritized over every other thread in the application, as it's what allows
|
||||
// the game to generate new audio and gfx lists.
|
||||
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Critical);
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
int remaining_retraces = events_context.vi.retrace_count;
|
||||
|
||||
while (!exited) {
|
||||
// Determine the next VI time (more accurate than adding 16ms each VI interrupt)
|
||||
auto next = ultramodern::get_start() + (total_vis * 1000000us) / (60 * ultramodern::get_speed_multiplier());
|
||||
//if (next > std::chrono::high_resolution_clock::now()) {
|
||||
// printf("Sleeping for %" PRIu64 " us to get from %" PRIu64 " us to %" PRIu64 " us \n",
|
||||
// (next - std::chrono::high_resolution_clock::now()) / 1us,
|
||||
// (std::chrono::high_resolution_clock::now() - events_context.start) / 1us,
|
||||
// (next - events_context.start) / 1us);
|
||||
//} else {
|
||||
// printf("No need to sleep\n");
|
||||
//}
|
||||
// Detect if there's more than a second to wait and wait a fixed amount instead for the next VI if so, as that usually means the system clock went back in time.
|
||||
if (std::chrono::floor<std::chrono::seconds>(next - std::chrono::high_resolution_clock::now()) > 1s) {
|
||||
// printf("Skipping the next VI wait\n");
|
||||
next = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
ultramodern::sleep_until(next);
|
||||
// Calculate how many VIs have passed
|
||||
uint64_t new_total_vis = (ultramodern::time_since_start() * (60 * ultramodern::get_speed_multiplier()) / 1000ms) + 1;
|
||||
if (new_total_vis > total_vis + 1) {
|
||||
//printf("Skipped % " PRId64 " frames in VI interupt thread!\n", new_total_vis - total_vis - 1);
|
||||
}
|
||||
total_vis = new_total_vis;
|
||||
|
||||
remaining_retraces--;
|
||||
|
||||
{
|
||||
std::lock_guard lock{ events_context.message_mutex };
|
||||
uint8_t* rdram = events_context.rdram;
|
||||
if (remaining_retraces == 0) {
|
||||
remaining_retraces = events_context.vi.retrace_count;
|
||||
|
||||
if (ultramodern::is_game_started()) {
|
||||
if (events_context.vi.mq != NULLPTR) {
|
||||
if (osSendMesg(PASS_RDRAM events_context.vi.mq, events_context.vi.msg, OS_MESG_NOBLOCK) == -1) {
|
||||
//printf("Game skipped a VI frame!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
set_dummy_vi();
|
||||
static bool swap = false;
|
||||
uint32_t vi_origin = 0x400 + 0x280; // Skip initial RDRAM contents and add the usual origin offset
|
||||
// Offset by one FB every other frame so RT64 continues drawing
|
||||
if (swap) {
|
||||
vi_origin += 0x25800;
|
||||
}
|
||||
osViSwapBuffer(rdram, vi_origin);
|
||||
swap = !swap;
|
||||
}
|
||||
}
|
||||
if (events_context.ai.mq != NULLPTR) {
|
||||
if (osSendMesg(PASS_RDRAM events_context.ai.mq, events_context.ai.msg, OS_MESG_NOBLOCK) == -1) {
|
||||
//printf("Game skipped a AI frame!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO move recomp code out of ultramodern.
|
||||
recomp::update_rumble();
|
||||
}
|
||||
}
|
||||
|
||||
void sp_complete() {
|
||||
uint8_t* rdram = events_context.rdram;
|
||||
std::lock_guard lock{ events_context.message_mutex };
|
||||
osSendMesg(PASS_RDRAM events_context.sp.mq, events_context.sp.msg, OS_MESG_NOBLOCK);
|
||||
}
|
||||
|
||||
void dp_complete() {
|
||||
uint8_t* rdram = events_context.rdram;
|
||||
std::lock_guard lock{ events_context.message_mutex };
|
||||
osSendMesg(PASS_RDRAM events_context.dp.mq, events_context.dp.msg, OS_MESG_NOBLOCK);
|
||||
}
|
||||
|
||||
uint8_t dmem[0x1000];
|
||||
uint16_t rspReciprocals[512];
|
||||
uint16_t rspInverseSquareRoots[512];
|
||||
|
||||
using RspUcodeFunc = RspExitReason(uint8_t* rdram);
|
||||
extern RspUcodeFunc njpgdspMain;
|
||||
extern RspUcodeFunc aspMain;
|
||||
|
||||
// From Ares emulator. For license details, see rsp_vu.h
|
||||
void rsp_constants_init() {
|
||||
rspReciprocals[0] = u16(~0);
|
||||
for (u16 index = 1; index < 512; index++) {
|
||||
u64 a = index + 512;
|
||||
u64 b = (u64(1) << 34) / a;
|
||||
rspReciprocals[index] = u16((b + 1) >> 8);
|
||||
}
|
||||
|
||||
for (u16 index = 0; index < 512; index++) {
|
||||
u64 a = (index + 512) >> ((index % 2 == 1) ? 1 : 0);
|
||||
u64 b = 1 << 17;
|
||||
//find the largest b where b < 1.0 / sqrt(a)
|
||||
while (a * (b + 1) * (b + 1) < (u64(1) << 44)) b++;
|
||||
rspInverseSquareRoots[index] = u16(b >> 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Runs a recompiled RSP microcode
|
||||
void run_rsp_microcode(uint8_t* rdram, const OSTask* task, RspUcodeFunc* ucode_func) {
|
||||
// Load the OSTask into DMEM
|
||||
memcpy(&dmem[0xFC0], task, sizeof(OSTask));
|
||||
// Load the ucode data into DMEM
|
||||
dma_rdram_to_dmem(rdram, 0x0000, task->t.ucode_data, 0xF80 - 1);
|
||||
// Run the ucode
|
||||
RspExitReason exit_reason = ucode_func(rdram);
|
||||
// Ensure that the ucode exited correctly
|
||||
assert(exit_reason == RspExitReason::Broke);
|
||||
}
|
||||
|
||||
|
||||
void task_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_ready) {
|
||||
ultramodern::set_native_thread_name("SP Task Thread");
|
||||
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Normal);
|
||||
|
||||
// Notify the caller thread that this thread is ready.
|
||||
thread_ready->signal();
|
||||
|
||||
while (true) {
|
||||
// Wait until an RSP task has been sent
|
||||
OSTask* task;
|
||||
events_context.sp_task_queue.wait_dequeue(task);
|
||||
|
||||
if (task == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run the correct function based on the task type
|
||||
if (task->t.type == M_AUDTASK) {
|
||||
run_rsp_microcode(rdram, task, aspMain);
|
||||
}
|
||||
else if (task->t.type == M_NJPEGTASK) {
|
||||
run_rsp_microcode(rdram, task, njpgdspMain);
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "Unknown task type: %" PRIu32 "\n", task->t.type);
|
||||
assert(false);
|
||||
std::quick_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Tell the game that the RSP has completed
|
||||
sp_complete();
|
||||
}
|
||||
}
|
||||
|
||||
static std::atomic<ultramodern::GraphicsConfig> cur_config{};
|
||||
|
||||
void ultramodern::set_graphics_config(const ultramodern::GraphicsConfig& config) {
|
||||
cur_config = config;
|
||||
events_context.action_queue.enqueue(UpdateConfigAction{});
|
||||
}
|
||||
|
||||
ultramodern::GraphicsConfig ultramodern::get_graphics_config() {
|
||||
return cur_config;
|
||||
}
|
||||
|
||||
std::atomic_uint32_t display_refresh_rate = 60;
|
||||
std::atomic<float> resolution_scale = 1.0f;
|
||||
|
||||
uint32_t ultramodern::get_target_framerate(uint32_t original) {
|
||||
ultramodern::GraphicsConfig graphics_config = ultramodern::get_graphics_config();
|
||||
|
||||
switch (graphics_config.rr_option) {
|
||||
case RT64::UserConfiguration::RefreshRate::Original:
|
||||
default:
|
||||
return original;
|
||||
case RT64::UserConfiguration::RefreshRate::Manual:
|
||||
return graphics_config.rr_manual_value;
|
||||
case RT64::UserConfiguration::RefreshRate::Display:
|
||||
return display_refresh_rate.load();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ultramodern::get_display_refresh_rate() {
|
||||
return display_refresh_rate.load();
|
||||
}
|
||||
|
||||
float ultramodern::get_resolution_scale() {
|
||||
return resolution_scale.load();
|
||||
}
|
||||
|
||||
void ultramodern::load_shader_cache(std::span<const char> cache_data) {
|
||||
events_context.action_queue.enqueue(LoadShaderCacheAction{cache_data});
|
||||
}
|
||||
|
||||
std::atomic<ultramodern::RT64SetupResult> rt64_setup_result = ultramodern::RT64SetupResult::Success;
|
||||
|
||||
void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_ready, ultramodern::WindowHandle window_handle) {
|
||||
bool enabled_instant_present = false;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
ultramodern::set_native_thread_name("Gfx Thread");
|
||||
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Normal);
|
||||
|
||||
ultramodern::GraphicsConfig old_config = ultramodern::get_graphics_config();
|
||||
|
||||
ultramodern::RT64Context rt64{rdram, window_handle, cur_config.load().developer_mode};
|
||||
|
||||
if (!rt64.valid()) {
|
||||
// TODO move recomp code out of ultramodern.
|
||||
rt64_setup_result.store(rt64.get_setup_result());
|
||||
// Notify the caller thread that this thread is ready.
|
||||
thread_ready->signal();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO move recomp code out of ultramodern.
|
||||
recomp::update_supported_options();
|
||||
|
||||
rsp_constants_init();
|
||||
|
||||
// Notify the caller thread that this thread is ready.
|
||||
thread_ready->signal();
|
||||
|
||||
while (!exited) {
|
||||
// Try to pull an action from the queue
|
||||
Action action;
|
||||
if (events_context.action_queue.wait_dequeue_timed(action, 1ms)) {
|
||||
// Determine the action type and act on it
|
||||
if (const auto* task_action = std::get_if<SpTaskAction>(&action)) {
|
||||
// Turn on instant present if the game has been started and it hasn't been turned on yet.
|
||||
if (ultramodern::is_game_started() && !enabled_instant_present) {
|
||||
rt64.enable_instant_present();
|
||||
enabled_instant_present = true;
|
||||
}
|
||||
// Tell the game that the RSP completed instantly. This will allow it to queue other task types, but it won't
|
||||
// start another graphics task until the RDP is also complete. Games usually preserve the RSP inputs until the RDP
|
||||
// is finished as well, so sending this early shouldn't be an issue in most cases.
|
||||
// If this causes issues then the logic can be replaced with responding to yield requests.
|
||||
sp_complete();
|
||||
ultramodern::measure_input_latency();
|
||||
|
||||
auto rt64_start = std::chrono::high_resolution_clock::now();
|
||||
rt64.send_dl(&task_action->task);
|
||||
auto rt64_end = std::chrono::high_resolution_clock::now();
|
||||
dp_complete();
|
||||
// printf("RT64 ProcessDList time: %d us\n", static_cast<u32>(std::chrono::duration_cast<std::chrono::microseconds>(rt64_end - rt64_start).count()));
|
||||
}
|
||||
else if (const auto* swap_action = std::get_if<SwapBuffersAction>(&action)) {
|
||||
events_context.vi.current_buffer = events_context.vi.next_buffer;
|
||||
rt64.update_screen(swap_action->origin);
|
||||
display_refresh_rate = rt64.get_display_framerate();
|
||||
resolution_scale = rt64.get_resolution_scale();
|
||||
}
|
||||
else if (const auto* config_action = std::get_if<UpdateConfigAction>(&action)) {
|
||||
ultramodern::GraphicsConfig new_config = cur_config;
|
||||
if (old_config != new_config) {
|
||||
rt64.update_config(old_config, new_config);
|
||||
old_config = new_config;
|
||||
}
|
||||
}
|
||||
else if (const auto* load_shader_cache_action = std::get_if<LoadShaderCacheAction>(&action)) {
|
||||
rt64.load_shader_cache(load_shader_cache_action->data);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO move recomp code out of ultramodern.
|
||||
recomp::destroy_ui();
|
||||
rt64.shutdown();
|
||||
}
|
||||
|
||||
extern unsigned int VI_STATUS_REG;
|
||||
extern unsigned int VI_ORIGIN_REG;
|
||||
extern unsigned int VI_WIDTH_REG;
|
||||
extern unsigned int VI_INTR_REG;
|
||||
extern unsigned int VI_V_CURRENT_LINE_REG;
|
||||
extern unsigned int VI_TIMING_REG;
|
||||
extern unsigned int VI_V_SYNC_REG;
|
||||
extern unsigned int VI_H_SYNC_REG;
|
||||
extern unsigned int VI_LEAP_REG;
|
||||
extern unsigned int VI_H_START_REG;
|
||||
extern unsigned int VI_V_START_REG;
|
||||
extern unsigned int VI_V_BURST_REG;
|
||||
extern unsigned int VI_X_SCALE_REG;
|
||||
extern unsigned int VI_Y_SCALE_REG;
|
||||
|
||||
uint32_t hstart = 0;
|
||||
uint32_t vi_origin_offset = 320 * sizeof(uint16_t);
|
||||
bool vi_black = false;
|
||||
|
||||
void set_dummy_vi() {
|
||||
VI_STATUS_REG = 0x311E;
|
||||
VI_WIDTH_REG = 0x140;
|
||||
VI_V_SYNC_REG = 0x20D;
|
||||
VI_H_SYNC_REG = 0xC15;
|
||||
VI_LEAP_REG = 0x0C150C15;
|
||||
hstart = 0x006C02EC;
|
||||
VI_X_SCALE_REG = 0x200;
|
||||
VI_V_CURRENT_LINE_REG = 0x0;
|
||||
vi_origin_offset = 0x280;
|
||||
VI_Y_SCALE_REG = 0x400;
|
||||
VI_V_START_REG = 0x2501FF;
|
||||
VI_V_BURST_REG = 0xE0204;
|
||||
VI_INTR_REG = 0x2;
|
||||
}
|
||||
|
||||
extern "C" void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr) {
|
||||
if (vi_black) {
|
||||
VI_H_START_REG = 0;
|
||||
} else {
|
||||
VI_H_START_REG = hstart;
|
||||
}
|
||||
events_context.vi.next_buffer = frameBufPtr;
|
||||
events_context.action_queue.enqueue(SwapBuffersAction{ osVirtualToPhysical(frameBufPtr) + vi_origin_offset });
|
||||
}
|
||||
|
||||
extern "C" void osViSetMode(RDRAM_ARG PTR(OSViMode) mode_) {
|
||||
OSViMode* mode = TO_PTR(OSViMode, mode_);
|
||||
VI_STATUS_REG = mode->comRegs.ctrl;
|
||||
VI_WIDTH_REG = mode->comRegs.width;
|
||||
// burst
|
||||
VI_V_SYNC_REG = mode->comRegs.vSync;
|
||||
VI_H_SYNC_REG = mode->comRegs.hSync;
|
||||
VI_LEAP_REG = mode->comRegs.leap;
|
||||
hstart = mode->comRegs.hStart;
|
||||
VI_X_SCALE_REG = mode->comRegs.xScale;
|
||||
VI_V_CURRENT_LINE_REG = mode->comRegs.vCurrent;
|
||||
|
||||
// TODO swap these every VI to account for fields changing
|
||||
vi_origin_offset = mode->fldRegs[0].origin;
|
||||
VI_Y_SCALE_REG = mode->fldRegs[0].yScale;
|
||||
VI_V_START_REG = mode->fldRegs[0].vStart;
|
||||
VI_V_BURST_REG = mode->fldRegs[0].vBurst;
|
||||
VI_INTR_REG = mode->fldRegs[0].vIntr;
|
||||
}
|
||||
|
||||
#define VI_CTRL_TYPE_16 0x00002
|
||||
#define VI_CTRL_TYPE_32 0x00003
|
||||
#define VI_CTRL_GAMMA_DITHER_ON 0x00004
|
||||
#define VI_CTRL_GAMMA_ON 0x00008
|
||||
#define VI_CTRL_DIVOT_ON 0x00010
|
||||
#define VI_CTRL_SERRATE_ON 0x00040
|
||||
#define VI_CTRL_ANTIALIAS_MASK 0x00300
|
||||
#define VI_CTRL_ANTIALIAS_MODE_1 0x00100
|
||||
#define VI_CTRL_ANTIALIAS_MODE_2 0x00200
|
||||
#define VI_CTRL_ANTIALIAS_MODE_3 0x00300
|
||||
#define VI_CTRL_PIXEL_ADV_MASK 0x01000
|
||||
#define VI_CTRL_PIXEL_ADV_1 0x01000
|
||||
#define VI_CTRL_PIXEL_ADV_2 0x02000
|
||||
#define VI_CTRL_PIXEL_ADV_3 0x03000
|
||||
#define VI_CTRL_DITHER_FILTER_ON 0x10000
|
||||
|
||||
#define OS_VI_GAMMA_ON 0x0001
|
||||
#define OS_VI_GAMMA_OFF 0x0002
|
||||
#define OS_VI_GAMMA_DITHER_ON 0x0004
|
||||
#define OS_VI_GAMMA_DITHER_OFF 0x0008
|
||||
#define OS_VI_DIVOT_ON 0x0010
|
||||
#define OS_VI_DIVOT_OFF 0x0020
|
||||
#define OS_VI_DITHER_FILTER_ON 0x0040
|
||||
#define OS_VI_DITHER_FILTER_OFF 0x0080
|
||||
|
||||
extern "C" void osViSetSpecialFeatures(uint32_t func) {
|
||||
if ((func & OS_VI_GAMMA_ON) != 0) {
|
||||
VI_STATUS_REG |= VI_CTRL_GAMMA_ON;
|
||||
}
|
||||
|
||||
if ((func & OS_VI_GAMMA_OFF) != 0) {
|
||||
VI_STATUS_REG &= ~VI_CTRL_GAMMA_ON;
|
||||
}
|
||||
|
||||
if ((func & OS_VI_GAMMA_DITHER_ON) != 0) {
|
||||
VI_STATUS_REG |= VI_CTRL_GAMMA_DITHER_ON;
|
||||
}
|
||||
|
||||
if ((func & OS_VI_GAMMA_DITHER_OFF) != 0) {
|
||||
VI_STATUS_REG &= ~VI_CTRL_GAMMA_DITHER_ON;
|
||||
}
|
||||
|
||||
if ((func & OS_VI_DIVOT_ON) != 0) {
|
||||
VI_STATUS_REG |= VI_CTRL_DIVOT_ON;
|
||||
}
|
||||
|
||||
if ((func & OS_VI_DIVOT_OFF) != 0) {
|
||||
VI_STATUS_REG &= ~VI_CTRL_DIVOT_ON;
|
||||
}
|
||||
|
||||
if ((func & OS_VI_DITHER_FILTER_ON) != 0) {
|
||||
VI_STATUS_REG |= VI_CTRL_DITHER_FILTER_ON;
|
||||
VI_STATUS_REG &= ~VI_CTRL_ANTIALIAS_MASK;
|
||||
}
|
||||
|
||||
if ((func & OS_VI_DITHER_FILTER_OFF) != 0) {
|
||||
VI_STATUS_REG &= ~VI_CTRL_DITHER_FILTER_ON;
|
||||
//VI_STATUS_REG |= __osViNext->modep->comRegs.ctrl & VI_CTRL_ANTIALIAS_MASK;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osViBlack(uint8_t active) {
|
||||
vi_black = active;
|
||||
}
|
||||
|
||||
extern "C" void osViSetXScale(float scale) {
|
||||
if (scale != 1.0f) {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osViSetYScale(float scale) {
|
||||
if (scale != 1.0f) {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" PTR(void) osViGetNextFramebuffer() {
|
||||
return events_context.vi.next_buffer;
|
||||
}
|
||||
|
||||
extern "C" PTR(void) osViGetCurrentFramebuffer() {
|
||||
return events_context.vi.current_buffer;
|
||||
}
|
||||
|
||||
void ultramodern::submit_rsp_task(RDRAM_ARG PTR(OSTask) task_) {
|
||||
OSTask* task = TO_PTR(OSTask, task_);
|
||||
|
||||
// Send gfx tasks to the graphics action queue
|
||||
if (task->t.type == M_GFXTASK) {
|
||||
events_context.action_queue.enqueue(SpTaskAction{ *task });
|
||||
}
|
||||
// Set all other tasks as the RSP task
|
||||
else {
|
||||
events_context.sp_task_queue.enqueue(task);
|
||||
}
|
||||
}
|
||||
|
||||
void ultramodern::send_si_message(RDRAM_ARG1) {
|
||||
osSendMesg(PASS_RDRAM events_context.si.mq, events_context.si.msg, OS_MESG_NOBLOCK);
|
||||
}
|
||||
|
||||
std::string get_graphics_api_name(ultramodern::GraphicsApi api) {
|
||||
if (api == ultramodern::GraphicsApi::Auto) {
|
||||
#if defined(_WIN32)
|
||||
api = ultramodern::GraphicsApi::D3D12;
|
||||
#elif defined(__gnu_linux__)
|
||||
api = ultramodern::GraphicsApi::Vulkan;
|
||||
#else
|
||||
static_assert(false && "Unimplemented")
|
||||
#endif
|
||||
}
|
||||
|
||||
switch (api) {
|
||||
case ultramodern::GraphicsApi::D3D12:
|
||||
return "D3D12";
|
||||
case ultramodern::GraphicsApi::Vulkan:
|
||||
return "Vulkan";
|
||||
default:
|
||||
return "[Unknown graphics API]";
|
||||
}
|
||||
}
|
||||
|
||||
void ultramodern::init_events(RDRAM_ARG ultramodern::WindowHandle window_handle) {
|
||||
moodycamel::LightweightSemaphore gfx_thread_ready;
|
||||
moodycamel::LightweightSemaphore task_thread_ready;
|
||||
events_context.rdram = rdram;
|
||||
events_context.sp.gfx_thread = std::thread{ gfx_thread_func, rdram, &gfx_thread_ready, window_handle };
|
||||
events_context.sp.task_thread = std::thread{ task_thread_func, rdram, &task_thread_ready };
|
||||
|
||||
// Wait for the two sp threads to be ready before continuing to prevent the game from
|
||||
// running before we're able to handle RSP tasks.
|
||||
gfx_thread_ready.wait();
|
||||
task_thread_ready.wait();
|
||||
|
||||
ultramodern::RT64SetupResult setup_result = rt64_setup_result.load();
|
||||
if (rt64_setup_result != ultramodern::RT64SetupResult::Success) {
|
||||
auto show_rt64_error = [](const std::string& msg) {
|
||||
// TODO move recomp code out of ultramodern (message boxes).
|
||||
recomp::message_box(("An error has been encountered on startup: " + msg).c_str());
|
||||
};
|
||||
const std::string driver_os_suffix = "\nPlease make sure your GPU drivers and your OS are up to date.";
|
||||
switch (rt64_setup_result) {
|
||||
case ultramodern::RT64SetupResult::DynamicLibrariesNotFound:
|
||||
show_rt64_error("Failed to load dynamic libraries. Make sure the DLLs are next to the recomp executable.");
|
||||
break;
|
||||
case ultramodern::RT64SetupResult::InvalidGraphicsAPI:
|
||||
show_rt64_error(get_graphics_api_name(cur_config.load().api_option) + " is not supported on this platform. Please select a different graphics API.");
|
||||
break;
|
||||
case ultramodern::RT64SetupResult::GraphicsAPINotFound:
|
||||
show_rt64_error("Unable to initialize " + get_graphics_api_name(cur_config.load().api_option) + "." + driver_os_suffix);
|
||||
break;
|
||||
case ultramodern::RT64SetupResult::GraphicsDeviceNotFound:
|
||||
show_rt64_error("Unable to find compatible graphics device." + driver_os_suffix);
|
||||
break;
|
||||
}
|
||||
throw std::runtime_error("Failed to initialize RT64");
|
||||
}
|
||||
|
||||
events_context.vi.thread = std::thread{ vi_thread_func };
|
||||
}
|
||||
|
||||
void ultramodern::join_event_threads() {
|
||||
events_context.sp.gfx_thread.join();
|
||||
events_context.vi.thread.join();
|
||||
|
||||
// Send a null RSP task to indicate that the RSP task thread should exit.
|
||||
events_context.sp_task_queue.enqueue(nullptr);
|
||||
events_context.sp.task_thread.join();
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
#include <thread>
|
||||
|
||||
#include "blockingconcurrentqueue.h"
|
||||
|
||||
#include "ultra64.h"
|
||||
#include "ultramodern.hpp"
|
||||
#include "recomp.h"
|
||||
|
||||
struct QueuedMessage {
|
||||
PTR(OSMesgQueue) mq;
|
||||
OSMesg mesg;
|
||||
bool jam;
|
||||
};
|
||||
|
||||
static moodycamel::BlockingConcurrentQueue<QueuedMessage> external_messages {};
|
||||
|
||||
void enqueue_external_message(PTR(OSMesgQueue) mq, OSMesg msg, bool jam) {
|
||||
external_messages.enqueue({mq, msg, jam});
|
||||
}
|
||||
|
||||
bool do_send(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, bool jam, bool block);
|
||||
|
||||
void dequeue_external_messages(RDRAM_ARG1) {
|
||||
QueuedMessage to_send;
|
||||
while (external_messages.try_dequeue(to_send)) {
|
||||
do_send(PASS_RDRAM to_send.mq, to_send.mesg, to_send.jam, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ultramodern::wait_for_external_message(RDRAM_ARG1) {
|
||||
QueuedMessage to_send;
|
||||
external_messages.wait_dequeue(to_send);
|
||||
do_send(PASS_RDRAM to_send.mq, to_send.mesg, to_send.jam, false);
|
||||
}
|
||||
|
||||
extern "C" void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg, s32 count) {
|
||||
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
||||
mq->blocked_on_recv = NULLPTR;
|
||||
mq->blocked_on_send = NULLPTR;
|
||||
mq->msgCount = count;
|
||||
mq->msg = msg;
|
||||
mq->validCount = 0;
|
||||
mq->first = 0;
|
||||
}
|
||||
|
||||
s32 MQ_GET_COUNT(OSMesgQueue *mq) {
|
||||
return mq->validCount;
|
||||
}
|
||||
|
||||
s32 MQ_IS_EMPTY(OSMesgQueue *mq) {
|
||||
return mq->validCount == 0;
|
||||
}
|
||||
|
||||
s32 MQ_IS_FULL(OSMesgQueue* mq) {
|
||||
return MQ_GET_COUNT(mq) >= mq->msgCount;
|
||||
}
|
||||
|
||||
bool do_send(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, bool jam, bool block) {
|
||||
OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_);
|
||||
if (!block) {
|
||||
// If non-blocking, fail if the queue is full.
|
||||
if (MQ_IS_FULL(mq)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Otherwise, yield this thread until the queue has room.
|
||||
while (MQ_IS_FULL(mq)) {
|
||||
debug_printf("[Message Queue] Thread %d is blocked on send\n", TO_PTR(OSThread, ultramodern::this_thread())->id);
|
||||
ultramodern::thread_queue_insert(PASS_RDRAM GET_MEMBER(OSMesgQueue, mq_, blocked_on_send), ultramodern::this_thread());
|
||||
ultramodern::run_next_thread_and_wait(PASS_RDRAM1);
|
||||
}
|
||||
}
|
||||
|
||||
if (jam) {
|
||||
// Jams insert at the head of the message queue's buffer.
|
||||
mq->first = (mq->first + mq->msgCount - 1) % mq->msgCount;
|
||||
TO_PTR(OSMesg, mq->msg)[mq->first] = msg;
|
||||
mq->validCount++;
|
||||
}
|
||||
else {
|
||||
// Sends insert at the tail of the message queue's buffer.
|
||||
s32 last = (mq->first + mq->validCount) % mq->msgCount;
|
||||
TO_PTR(OSMesg, mq->msg)[last] = msg;
|
||||
mq->validCount++;
|
||||
}
|
||||
|
||||
// If any threads were blocked on receiving from this message queue, pop the first one and schedule it.
|
||||
PTR(PTR(OSThread)) blocked_queue = GET_MEMBER(OSMesgQueue, mq_, blocked_on_recv);
|
||||
if (!ultramodern::thread_queue_empty(PASS_RDRAM blocked_queue)) {
|
||||
ultramodern::schedule_running_thread(PASS_RDRAM ultramodern::thread_queue_pop(PASS_RDRAM blocked_queue));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool do_recv(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg_, bool block) {
|
||||
OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_);
|
||||
if (!block) {
|
||||
// If non-blocking, fail if the queue is empty
|
||||
if (MQ_IS_EMPTY(mq)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Otherwise, yield this thread in a loop until the queue is no longer full
|
||||
while (MQ_IS_EMPTY(mq)) {
|
||||
debug_printf("[Message Queue] Thread %d is blocked on receive\n", TO_PTR(OSThread, ultramodern::this_thread())->id);
|
||||
ultramodern::thread_queue_insert(PASS_RDRAM GET_MEMBER(OSMesgQueue, mq_, blocked_on_recv), ultramodern::this_thread());
|
||||
ultramodern::run_next_thread_and_wait(PASS_RDRAM1);
|
||||
}
|
||||
}
|
||||
|
||||
if (msg_ != NULLPTR) {
|
||||
*TO_PTR(OSMesg, msg_) = TO_PTR(OSMesg, mq->msg)[mq->first];
|
||||
}
|
||||
|
||||
mq->first = (mq->first + 1) % mq->msgCount;
|
||||
mq->validCount--;
|
||||
|
||||
// If any threads were blocked on sending to this message queue, pop the first one and schedule it.
|
||||
PTR(PTR(OSThread)) blocked_queue = GET_MEMBER(OSMesgQueue, mq_, blocked_on_send);
|
||||
if (!ultramodern::thread_queue_empty(PASS_RDRAM blocked_queue)) {
|
||||
ultramodern::schedule_running_thread(PASS_RDRAM ultramodern::thread_queue_pop(PASS_RDRAM blocked_queue));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) {
|
||||
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
||||
bool jam = false;
|
||||
|
||||
// Don't directly send to the message queue if this isn't a game thread to avoid contention.
|
||||
if (!ultramodern::is_game_thread()) {
|
||||
enqueue_external_message(mq_, msg, jam);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle any messages that have been received from an external thread.
|
||||
dequeue_external_messages(PASS_RDRAM1);
|
||||
|
||||
// Try to send the message.
|
||||
bool sent = do_send(PASS_RDRAM mq_, msg, jam, flags == OS_MESG_BLOCK);
|
||||
|
||||
// Check the queue to see if this thread should swap execution to another.
|
||||
ultramodern::check_running_queue(PASS_RDRAM1);
|
||||
|
||||
return sent ? 0 : -1;
|
||||
}
|
||||
|
||||
extern "C" s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) {
|
||||
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
||||
bool jam = true;
|
||||
|
||||
// Don't directly send to the message queue if this isn't a game thread to avoid contention.
|
||||
if (!ultramodern::is_game_thread()) {
|
||||
enqueue_external_message(mq_, msg, jam);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle any messages that have been received from an external thread.
|
||||
dequeue_external_messages(PASS_RDRAM1);
|
||||
|
||||
// Try to send the message.
|
||||
bool sent = do_send(PASS_RDRAM mq_, msg, jam, flags == OS_MESG_BLOCK);
|
||||
|
||||
// Check the queue to see if this thread should swap execution to another.
|
||||
ultramodern::check_running_queue(PASS_RDRAM1);
|
||||
|
||||
return sent ? 0 : -1;
|
||||
}
|
||||
|
||||
extern "C" s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg_, s32 flags) {
|
||||
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
|
||||
|
||||
assert(ultramodern::is_game_thread() && "RecvMesg not allowed outside of game threads.");
|
||||
|
||||
// Handle any messages that have been received from an external thread.
|
||||
dequeue_external_messages(PASS_RDRAM1);
|
||||
|
||||
// Try to receive a message.
|
||||
bool received = do_recv(PASS_RDRAM mq_, msg_, flags == OS_MESG_BLOCK);
|
||||
|
||||
// Check the queue to see if this thread should swap execution to another.
|
||||
ultramodern::check_running_queue(PASS_RDRAM1);
|
||||
|
||||
return received ? 0 : -1;
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
#include "ultra64.h"
|
||||
|
||||
#define K0BASE 0x80000000
|
||||
#define K1BASE 0xA0000000
|
||||
#define K2BASE 0xC0000000
|
||||
#define IS_KSEG0(x) ((u32)(x) >= K0BASE && (u32)(x) < K1BASE)
|
||||
#define IS_KSEG1(x) ((u32)(x) >= K1BASE && (u32)(x) < K2BASE)
|
||||
#define K0_TO_PHYS(x) ((u32)(x)&0x1FFFFFFF) /* kseg0 to physical */
|
||||
#define K1_TO_PHYS(x) ((u32)(x)&0x1FFFFFFF) /* kseg1 to physical */
|
||||
|
||||
u32 osVirtualToPhysical(PTR(void) addr) {
|
||||
uintptr_t addr_val = (uintptr_t)addr;
|
||||
if (IS_KSEG0(addr_val)) {
|
||||
return K0_TO_PHYS(addr_val);
|
||||
} else if (IS_KSEG1(addr_val)) {
|
||||
return K1_TO_PHYS(addr_val);
|
||||
} else {
|
||||
// TODO handle TLB mappings
|
||||
return (u32)addr_val;
|
||||
}
|
||||
}
|
||||
|
@ -1,83 +0,0 @@
|
||||
#if 0
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "ultra64.h"
|
||||
|
||||
#define THREAD_STACK_SIZE 0x1000
|
||||
|
||||
u8 idle_stack[THREAD_STACK_SIZE] ALIGNED(16);
|
||||
u8 main_stack[THREAD_STACK_SIZE] ALIGNED(16);
|
||||
u8 thread3_stack[THREAD_STACK_SIZE] ALIGNED(16);
|
||||
u8 thread4_stack[THREAD_STACK_SIZE] ALIGNED(16);
|
||||
|
||||
OSThread idle_thread;
|
||||
OSThread main_thread;
|
||||
OSThread thread3;
|
||||
OSThread thread4;
|
||||
|
||||
OSMesgQueue queue;
|
||||
OSMesg buf[1];
|
||||
|
||||
void thread3_func(UNUSED void *arg) {
|
||||
OSMesg val;
|
||||
printf("Thread3 recv\n");
|
||||
fflush(stdout);
|
||||
osRecvMesg(&queue, &val, OS_MESG_BLOCK);
|
||||
printf("Thread3 complete: %d\n", (int)(intptr_t)val);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void thread4_func(void *arg) {
|
||||
printf("Thread4 send %d\n", (int)(intptr_t)arg);
|
||||
fflush(stdout);
|
||||
osSendMesg(&queue, arg, OS_MESG_BLOCK);
|
||||
printf("Thread4 complete\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void main_thread_func(UNUSED void* arg) {
|
||||
osCreateMesgQueue(&queue, buf, sizeof(buf) / sizeof(buf[0]));
|
||||
|
||||
printf("main thread creating thread 3\n");
|
||||
osCreateThread(&thread3, 3, thread3_func, NULL, &thread3_stack[THREAD_STACK_SIZE], 14);
|
||||
printf("main thread starting thread 3\n");
|
||||
osStartThread(&thread3);
|
||||
|
||||
printf("main thread creating thread 4\n");
|
||||
osCreateThread(&thread4, 4, thread4_func, (void*)10, &thread4_stack[THREAD_STACK_SIZE], 13);
|
||||
printf("main thread starting thread 4\n");
|
||||
osStartThread(&thread4);
|
||||
|
||||
while (1) {
|
||||
printf("main thread doin stuff\n");
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
void idle_thread_func(UNUSED void* arg) {
|
||||
printf("idle thread\n");
|
||||
printf("creating main thread\n");
|
||||
osCreateThread(&main_thread, 2, main_thread_func, NULL, &main_stack[THREAD_STACK_SIZE], 11);
|
||||
printf("starting main thread\n");
|
||||
osStartThread(&main_thread);
|
||||
|
||||
// Set this thread's priority to 0, making it the idle thread
|
||||
osSetThreadPri(NULL, 0);
|
||||
|
||||
// idle
|
||||
while (1) {
|
||||
printf("idle thread doin stuff\n");
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
void bootproc(void) {
|
||||
osInitialize();
|
||||
|
||||
osCreateThread(&idle_thread, 1, idle_thread_func, NULL, &idle_stack[THREAD_STACK_SIZE], 127);
|
||||
printf("Starting idle thread\n");
|
||||
osStartThread(&idle_thread);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,38 +0,0 @@
|
||||
#include "ultramodern.hpp"
|
||||
|
||||
void ultramodern::schedule_running_thread(RDRAM_ARG PTR(OSThread) t_) {
|
||||
debug_printf("[Scheduling] Adding thread %d to the running queue\n", TO_PTR(OSThread, t_)->id);
|
||||
thread_queue_insert(PASS_RDRAM running_queue, t_);
|
||||
TO_PTR(OSThread, t_)->state = OSThreadState::QUEUED;
|
||||
}
|
||||
|
||||
void swap_to_thread(RDRAM_ARG OSThread *to) {
|
||||
debug_printf("[Scheduling] Thread %d giving execution to thread %d\n", TO_PTR(OSThread, ultramodern::this_thread())->id, to->id);
|
||||
// Insert this thread in the running queue.
|
||||
ultramodern::thread_queue_insert(PASS_RDRAM ultramodern::running_queue, ultramodern::this_thread());
|
||||
TO_PTR(OSThread, ultramodern::this_thread())->state = OSThreadState::QUEUED;
|
||||
// Unpause the target thread and wait for this one to be unpaused.
|
||||
ultramodern::resume_thread_and_wait(PASS_RDRAM to);
|
||||
}
|
||||
|
||||
void ultramodern::check_running_queue(RDRAM_ARG1) {
|
||||
// Check if there are any threads in the running queue.
|
||||
if (!thread_queue_empty(PASS_RDRAM running_queue)) {
|
||||
// Check if the highest priority thread in the queue is higher priority than the current thread.
|
||||
OSThread* next_thread = TO_PTR(OSThread, ultramodern::thread_queue_peek(PASS_RDRAM running_queue));
|
||||
OSThread* self = TO_PTR(OSThread, ultramodern::this_thread());
|
||||
if (next_thread->priority > self->priority) {
|
||||
ultramodern::thread_queue_pop(PASS_RDRAM running_queue);
|
||||
// Swap to the higher priority thread.
|
||||
swap_to_thread(PASS_RDRAM next_thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void pause_self(RDRAM_ARG1) {
|
||||
while (true) {
|
||||
// Wait until an external message arrives, then allow the next thread to run.
|
||||
ultramodern::wait_for_external_message(PASS_RDRAM1);
|
||||
ultramodern::check_running_queue(PASS_RDRAM1);
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include "ultra64.h"
|
||||
#include "ultramodern.hpp"
|
||||
|
||||
extern "C" unsigned int sleep(unsigned int seconds) {
|
||||
Sleep(seconds * 1000);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
@ -1,66 +0,0 @@
|
||||
#include <cassert>
|
||||
|
||||
#include "ultramodern.hpp"
|
||||
|
||||
static PTR(OSThread) running_queue_impl = NULLPTR;
|
||||
|
||||
static PTR(OSThread)* queue_to_ptr(RDRAM_ARG PTR(PTR(OSThread)) queue) {
|
||||
if (queue == ultramodern::running_queue) {
|
||||
return &running_queue_impl;
|
||||
}
|
||||
return TO_PTR(PTR(OSThread), queue);
|
||||
}
|
||||
|
||||
void ultramodern::thread_queue_insert(RDRAM_ARG PTR(PTR(OSThread)) queue_, PTR(OSThread) toadd_) {
|
||||
PTR(OSThread)* cur = queue_to_ptr(PASS_RDRAM queue_);
|
||||
OSThread* toadd = TO_PTR(OSThread, toadd_);
|
||||
debug_printf("[Thread Queue] Inserting thread %d into queue 0x%08X\n", toadd->id, (uintptr_t)queue_);
|
||||
while (*cur && TO_PTR(OSThread, *cur)->priority > toadd->priority) {
|
||||
cur = &TO_PTR(OSThread, *cur)->next;
|
||||
}
|
||||
toadd->next = (*cur);
|
||||
toadd->queue = queue_;
|
||||
*cur = toadd_;
|
||||
|
||||
debug_printf(" Contains:");
|
||||
cur = queue_to_ptr(PASS_RDRAM queue_);
|
||||
while (*cur) {
|
||||
debug_printf("%d (%d) ", TO_PTR(OSThread, *cur)->id, TO_PTR(OSThread, *cur)->priority);
|
||||
cur = &TO_PTR(OSThread, *cur)->next;
|
||||
}
|
||||
debug_printf("\n");
|
||||
}
|
||||
|
||||
PTR(OSThread) ultramodern::thread_queue_pop(RDRAM_ARG PTR(PTR(OSThread)) queue_) {
|
||||
PTR(OSThread)* queue = queue_to_ptr(PASS_RDRAM queue_);
|
||||
PTR(OSThread) ret = *queue;
|
||||
*queue = TO_PTR(OSThread, ret)->next;
|
||||
TO_PTR(OSThread, ret)->queue = NULLPTR;
|
||||
debug_printf("[Thread Queue] Popped thread %d from queue 0x%08X\n", TO_PTR(OSThread, ret)->id, (uintptr_t)queue_);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ultramodern::thread_queue_remove(RDRAM_ARG PTR(PTR(OSThread)) queue_, PTR(OSThread) t_) {
|
||||
debug_printf("[Thread Queue] Removing thread %d from queue 0x%08X\n", TO_PTR(OSThread, t_)->id, (uintptr_t)queue_);
|
||||
|
||||
PTR(PTR(OSThread)) cur = queue_;
|
||||
while (cur != NULLPTR) {
|
||||
PTR(OSThread)* cur_ptr = queue_to_ptr(PASS_RDRAM queue_);
|
||||
if (*cur_ptr == t_) {
|
||||
return true;
|
||||
}
|
||||
cur = TO_PTR(OSThread, *cur_ptr)->next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ultramodern::thread_queue_empty(RDRAM_ARG PTR(PTR(OSThread)) queue_) {
|
||||
PTR(OSThread)* queue = queue_to_ptr(PASS_RDRAM queue_);
|
||||
return *queue == NULLPTR;
|
||||
}
|
||||
|
||||
PTR(OSThread) ultramodern::thread_queue_peek(RDRAM_ARG PTR(PTR(OSThread)) queue_) {
|
||||
PTR(OSThread)* queue = queue_to_ptr(PASS_RDRAM queue_);
|
||||
return *queue;
|
||||
}
|
@ -1,346 +0,0 @@
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
#include "ultra64.h"
|
||||
#include "ultramodern.hpp"
|
||||
#include "blockingconcurrentqueue.h"
|
||||
|
||||
// Native APIs only used to set thread names for easier debugging
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
extern "C" void bootproc();
|
||||
|
||||
thread_local bool is_main_thread = false;
|
||||
// Whether this thread is part of the game (i.e. the start thread or one spawned by osCreateThread)
|
||||
thread_local bool is_game_thread = false;
|
||||
thread_local PTR(OSThread) thread_self = NULLPTR;
|
||||
|
||||
void ultramodern::set_main_thread() {
|
||||
::is_game_thread = true;
|
||||
is_main_thread = true;
|
||||
}
|
||||
|
||||
bool ultramodern::is_game_thread() {
|
||||
return ::is_game_thread;
|
||||
}
|
||||
|
||||
#if 0
|
||||
int main(int argc, char** argv) {
|
||||
ultramodern::set_main_thread();
|
||||
|
||||
bootproc();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 1
|
||||
void run_thread_function(uint8_t* rdram, uint64_t addr, uint64_t sp, uint64_t arg);
|
||||
#else
|
||||
#define run_thread_function(func, sp, arg) func(arg)
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
void ultramodern::set_native_thread_name(const std::string& name) {
|
||||
std::wstring wname{name.begin(), name.end()};
|
||||
|
||||
HRESULT r;
|
||||
r = SetThreadDescription(
|
||||
GetCurrentThread(),
|
||||
wname.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
void ultramodern::set_native_thread_priority(ThreadPriority pri) {
|
||||
int nPriority = THREAD_PRIORITY_NORMAL;
|
||||
|
||||
// Convert ThreadPriority to Win32 priority
|
||||
switch (pri) {
|
||||
case ThreadPriority::Low:
|
||||
nPriority = THREAD_PRIORITY_BELOW_NORMAL;
|
||||
break;
|
||||
case ThreadPriority::Normal:
|
||||
nPriority = THREAD_PRIORITY_NORMAL;
|
||||
break;
|
||||
case ThreadPriority::High:
|
||||
nPriority = THREAD_PRIORITY_ABOVE_NORMAL;
|
||||
break;
|
||||
case ThreadPriority::VeryHigh:
|
||||
nPriority = THREAD_PRIORITY_HIGHEST;
|
||||
break;
|
||||
case ThreadPriority::Critical:
|
||||
nPriority = THREAD_PRIORITY_TIME_CRITICAL;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("Invalid thread priority!");
|
||||
break;
|
||||
}
|
||||
// SetThreadPriority(GetCurrentThread(), nPriority);
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
void ultramodern::set_native_thread_name(const std::string& name) {
|
||||
pthread_setname_np(pthread_self(), name.c_str());
|
||||
}
|
||||
|
||||
void ultramodern::set_native_thread_priority(ThreadPriority pri) {
|
||||
// TODO linux thread priority
|
||||
// printf("set_native_thread_priority unimplemented\n");
|
||||
// int nPriority = THREAD_PRIORITY_NORMAL;
|
||||
|
||||
// // Convert ThreadPriority to Win32 priority
|
||||
// switch (pri) {
|
||||
// case ThreadPriority::Low:
|
||||
// nPriority = THREAD_PRIORITY_BELOW_NORMAL;
|
||||
// break;
|
||||
// case ThreadPriority::Normal:
|
||||
// nPriority = THREAD_PRIORITY_NORMAL;
|
||||
// break;
|
||||
// case ThreadPriority::High:
|
||||
// nPriority = THREAD_PRIORITY_ABOVE_NORMAL;
|
||||
// break;
|
||||
// case ThreadPriority::VeryHigh:
|
||||
// nPriority = THREAD_PRIORITY_HIGHEST;
|
||||
// break;
|
||||
// case ThreadPriority::Critical:
|
||||
// nPriority = THREAD_PRIORITY_TIME_CRITICAL;
|
||||
// break;
|
||||
// default:
|
||||
// throw std::runtime_error("Invalid thread priority!");
|
||||
// break;
|
||||
// }
|
||||
}
|
||||
#endif
|
||||
|
||||
std::atomic_int temporary_threads = 0;
|
||||
std::atomic_int permanent_threads = 0;
|
||||
|
||||
void wait_for_resumed(RDRAM_ARG UltraThreadContext* thread_context) {
|
||||
TO_PTR(OSThread, ultramodern::this_thread())->context->running.wait();
|
||||
// If this thread's context was replaced by another thread or deleted, destroy it again from its own context.
|
||||
// This will trigger thread cleanup instead.
|
||||
if (TO_PTR(OSThread, ultramodern::this_thread())->context != thread_context) {
|
||||
osDestroyThread(PASS_RDRAM NULLPTR);
|
||||
}
|
||||
}
|
||||
|
||||
void resume_thread(OSThread* t) {
|
||||
debug_printf("[Thread] Resuming execution of thread %d\n", t->id);
|
||||
t->context->running.signal();
|
||||
}
|
||||
|
||||
void run_next_thread(RDRAM_ARG1) {
|
||||
if (ultramodern::thread_queue_empty(PASS_RDRAM ultramodern::running_queue)) {
|
||||
throw std::runtime_error("No threads left to run!\n");
|
||||
}
|
||||
|
||||
OSThread* to_run = TO_PTR(OSThread, ultramodern::thread_queue_pop(PASS_RDRAM ultramodern::running_queue));
|
||||
debug_printf("[Scheduling] Resuming execution of thread %d\n", to_run->id);
|
||||
to_run->context->running.signal();
|
||||
}
|
||||
|
||||
void ultramodern::run_next_thread_and_wait(RDRAM_ARG1) {
|
||||
UltraThreadContext* cur_context = TO_PTR(OSThread, thread_self)->context;
|
||||
run_next_thread(PASS_RDRAM1);
|
||||
wait_for_resumed(PASS_RDRAM cur_context);
|
||||
}
|
||||
|
||||
void ultramodern::resume_thread_and_wait(RDRAM_ARG OSThread *t) {
|
||||
UltraThreadContext* cur_context = TO_PTR(OSThread, thread_self)->context;
|
||||
resume_thread(t);
|
||||
wait_for_resumed(PASS_RDRAM cur_context);
|
||||
}
|
||||
|
||||
static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entrypoint, PTR(void) arg, UltraThreadContext* thread_context) {
|
||||
OSThread *self = TO_PTR(OSThread, self_);
|
||||
debug_printf("[Thread] Thread created: %d\n", self->id);
|
||||
thread_self = self_;
|
||||
is_game_thread = true;
|
||||
|
||||
// Set the thread name
|
||||
ultramodern::set_native_thread_name("Game Thread " + std::to_string(self->id));
|
||||
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::High);
|
||||
|
||||
// TODO fix these being hardcoded (this is only used for quicksaving)
|
||||
if ((self->id == 2 && self->priority == 5) || self->id == 13) { // slowly, flashrom
|
||||
temporary_threads.fetch_add(1);
|
||||
}
|
||||
else if (self->id != 1 && self->id != 2) { // ignore idle and fault
|
||||
permanent_threads.fetch_add(1);
|
||||
}
|
||||
|
||||
// Signal the initialized semaphore to indicate that this thread can be started.
|
||||
thread_context->initialized.signal();
|
||||
|
||||
debug_printf("[Thread] Thread waiting to be started: %d\n", self->id);
|
||||
|
||||
// Wait until the thread is marked as running.
|
||||
wait_for_resumed(PASS_RDRAM thread_context);
|
||||
|
||||
// Make sure the thread wasn't replaced or destroyed before it was started.
|
||||
if (self->context == thread_context) {
|
||||
debug_printf("[Thread] Thread started: %d\n", self->id);
|
||||
try {
|
||||
// Run the thread's function with the provided argument.
|
||||
run_thread_function(PASS_RDRAM entrypoint, self->sp, arg);
|
||||
} catch (ultramodern::thread_terminated& terminated) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
debug_printf("[Thread] Thread destroyed before being started: %d\n", self->id);
|
||||
}
|
||||
|
||||
// Check if the thread hasn't been destroyed or replaced. If so, then the thread terminated or destroyed itself,
|
||||
// so mark this thread as destroyed and run the next queued thread.
|
||||
if (self->context == thread_context) {
|
||||
self->context = nullptr;
|
||||
run_next_thread(PASS_RDRAM1);
|
||||
}
|
||||
|
||||
// Dispose of this thread now that it's completed or terminated.
|
||||
ultramodern::cleanup_thread(thread_context);
|
||||
|
||||
// TODO fix these being hardcoded (this is only used for quicksaving)
|
||||
if ((self->id == 2 && self->priority == 5) || self->id == 13) { // slowly, flashrom
|
||||
temporary_threads.fetch_sub(1);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ultramodern::permanent_thread_count() {
|
||||
return permanent_threads.load();
|
||||
}
|
||||
|
||||
uint32_t ultramodern::temporary_thread_count() {
|
||||
return temporary_threads.load();
|
||||
}
|
||||
|
||||
extern "C" void osStartThread(RDRAM_ARG PTR(OSThread) t_) {
|
||||
OSThread* t = TO_PTR(OSThread, t_);
|
||||
debug_printf("[os] Start Thread %d\n", t->id);
|
||||
|
||||
// Wait until the thread is initialized to indicate that it's ready to be started.
|
||||
t->context->initialized.wait();
|
||||
|
||||
debug_printf("[os] Thread %d is ready to be started\n", t->id);
|
||||
|
||||
// If this is a game thread, insert the new thread into the running queue and then check the running queue.
|
||||
if (thread_self) {
|
||||
ultramodern::schedule_running_thread(PASS_RDRAM t_);
|
||||
ultramodern::check_running_queue(PASS_RDRAM1);
|
||||
}
|
||||
// Otherwise, immediately start the thread and terminate this one.
|
||||
else {
|
||||
t->state = OSThreadState::QUEUED;
|
||||
resume_thread(t);
|
||||
//throw ultramodern::thread_terminated{};
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osCreateThread(RDRAM_ARG PTR(OSThread) t_, OSId id, PTR(thread_func_t) entrypoint, PTR(void) arg, PTR(void) sp, OSPri pri) {
|
||||
debug_printf("[os] Create Thread %d\n", id);
|
||||
OSThread *t = TO_PTR(OSThread, t_);
|
||||
|
||||
t->next = NULLPTR;
|
||||
t->queue = NULLPTR;
|
||||
t->priority = pri;
|
||||
t->id = id;
|
||||
t->state = OSThreadState::STOPPED;
|
||||
t->sp = sp - 0x10; // Set up the first stack frame
|
||||
|
||||
// Spawn a new thread, which will immediately pause itself and wait until it's been started.
|
||||
// Pass the context as an argument to the thread function to ensure that it can't get cleared before the thread captures its value.
|
||||
t->context = new UltraThreadContext{};
|
||||
t->context->host_thread = std::thread{_thread_func, PASS_RDRAM t_, entrypoint, arg, t->context};
|
||||
}
|
||||
|
||||
extern "C" void osStopThread(RDRAM_ARG PTR(OSThread) t_) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
extern "C" void osDestroyThread(RDRAM_ARG PTR(OSThread) t_) {
|
||||
if (t_ == NULLPTR) {
|
||||
t_ = thread_self;
|
||||
}
|
||||
OSThread* t = TO_PTR(OSThread, t_);
|
||||
// Check if the thread is destroying itself (arg is null or thread_self)
|
||||
if (t_ == thread_self) {
|
||||
throw ultramodern::thread_terminated{};
|
||||
}
|
||||
// Otherwise if the thread isn't stopped, remove it from its currrent queue.,
|
||||
if (t->state != OSThreadState::STOPPED) {
|
||||
ultramodern::thread_queue_remove(PASS_RDRAM t->queue, t_);
|
||||
}
|
||||
// Check if the thread has already been destroyed to prevent destroying it again.
|
||||
UltraThreadContext* cur_context = t->context;
|
||||
if (cur_context != nullptr) {
|
||||
// Mark the target thread as destroyed and resume it. When it starts it'll check this and terminate itself instead of resuming.
|
||||
t->context = nullptr;
|
||||
cur_context->running.signal();
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void osSetThreadPri(RDRAM_ARG PTR(OSThread) t_, OSPri pri) {
|
||||
if (t_ == NULLPTR) {
|
||||
t_ = thread_self;
|
||||
}
|
||||
OSThread* t = TO_PTR(OSThread, t_);
|
||||
|
||||
if (t->priority != pri) {
|
||||
t->priority = pri;
|
||||
|
||||
if (t_ != ultramodern::this_thread() && t->state != OSThreadState::STOPPED) {
|
||||
ultramodern::thread_queue_remove(PASS_RDRAM t->queue, t_);
|
||||
ultramodern::thread_queue_insert(PASS_RDRAM t->queue, t_);
|
||||
}
|
||||
|
||||
ultramodern::check_running_queue(PASS_RDRAM1);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" OSPri osGetThreadPri(RDRAM_ARG PTR(OSThread) t) {
|
||||
if (t == NULLPTR) {
|
||||
t = thread_self;
|
||||
}
|
||||
return TO_PTR(OSThread, t)->priority;
|
||||
}
|
||||
|
||||
extern "C" OSId osGetThreadId(RDRAM_ARG PTR(OSThread) t) {
|
||||
if (t == NULLPTR) {
|
||||
t = thread_self;
|
||||
}
|
||||
return TO_PTR(OSThread, t)->id;
|
||||
}
|
||||
|
||||
PTR(OSThread) ultramodern::this_thread() {
|
||||
return thread_self;
|
||||
}
|
||||
|
||||
static std::thread thread_cleaner_thread;
|
||||
static moodycamel::BlockingConcurrentQueue<UltraThreadContext*> deleted_threads{};
|
||||
extern std::atomic_bool exited;
|
||||
|
||||
void thread_cleaner_func() {
|
||||
using namespace std::chrono_literals;
|
||||
while (!exited) {
|
||||
UltraThreadContext* to_delete;
|
||||
if (deleted_threads.wait_dequeue_timed(to_delete, 10ms)) {
|
||||
debug_printf("[Cleanup] Deleting thread context %p\n", to_delete);
|
||||
|
||||
to_delete->host_thread.join();
|
||||
delete to_delete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ultramodern::init_thread_cleanup() {
|
||||
thread_cleaner_thread = std::thread{thread_cleaner_func};
|
||||
}
|
||||
|
||||
void ultramodern::cleanup_thread(UltraThreadContext *cur_context) {
|
||||
deleted_threads.enqueue(cur_context);
|
||||
}
|
||||
|
||||
void ultramodern::join_thread_cleaner_thread() {
|
||||
thread_cleaner_thread.join();
|
||||
}
|
@ -1,224 +0,0 @@
|
||||
#include <thread>
|
||||
#include <variant>
|
||||
#include <set>
|
||||
#include "blockingconcurrentqueue.h"
|
||||
|
||||
#include "ultra64.h"
|
||||
#include "ultramodern.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include "Windows.h"
|
||||
#endif
|
||||
|
||||
// Start time for the program
|
||||
static std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now();
|
||||
// Game speed multiplier (1 means no speedup)
|
||||
constexpr uint32_t speed_multiplier = 1;
|
||||
// N64 CPU counter ticks per millisecond
|
||||
constexpr uint32_t counter_per_ms = 46'875 * speed_multiplier;
|
||||
|
||||
struct OSTimer {
|
||||
PTR(OSTimer) unused1;
|
||||
PTR(OSTimer) unused2;
|
||||
OSTime interval;
|
||||
OSTime timestamp;
|
||||
PTR(OSMesgQueue) mq;
|
||||
OSMesg msg;
|
||||
};
|
||||
|
||||
struct AddTimerAction {
|
||||
PTR(OSTimer) timer;
|
||||
};
|
||||
|
||||
struct RemoveTimerAction {
|
||||
PTR(OSTimer) timer;
|
||||
};
|
||||
|
||||
using Action = std::variant<AddTimerAction, RemoveTimerAction>;
|
||||
|
||||
struct {
|
||||
std::thread thread;
|
||||
moodycamel::BlockingConcurrentQueue<Action> action_queue{};
|
||||
} timer_context;
|
||||
|
||||
uint64_t duration_to_ticks(std::chrono::high_resolution_clock::duration duration) {
|
||||
uint64_t delta_micros = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
|
||||
// More accurate than using a floating point timer, will only overflow after running for 12.47 years
|
||||
// Units: (micros * (counts/millis)) / (micros/millis) = counts
|
||||
uint64_t total_count = (delta_micros * counter_per_ms) / 1000;
|
||||
|
||||
return total_count;
|
||||
}
|
||||
|
||||
std::chrono::microseconds ticks_to_duration(uint64_t ticks) {
|
||||
using namespace std::chrono_literals;
|
||||
return ticks * 1000us / counter_per_ms;
|
||||
}
|
||||
|
||||
std::chrono::high_resolution_clock::time_point ticks_to_timepoint(uint64_t ticks) {
|
||||
return start_time + ticks_to_duration(ticks);
|
||||
}
|
||||
|
||||
uint64_t time_now() {
|
||||
return duration_to_ticks(std::chrono::high_resolution_clock::now() - start_time);
|
||||
}
|
||||
|
||||
void timer_thread(RDRAM_ARG1) {
|
||||
ultramodern::set_native_thread_name("Timer Thread");
|
||||
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::VeryHigh);
|
||||
|
||||
// Lambda comparator function to keep the set ordered
|
||||
auto timer_sort = [PASS_RDRAM1](PTR(OSTimer) a_, PTR(OSTimer) b_) {
|
||||
OSTimer* a = TO_PTR(OSTimer, a_);
|
||||
OSTimer* b = TO_PTR(OSTimer, b_);
|
||||
|
||||
// Order by timestamp if the timers have different timestamps
|
||||
if (a->timestamp != b->timestamp) {
|
||||
return a->timestamp < b->timestamp;
|
||||
}
|
||||
|
||||
// If they have the exact same timestamp then order by address instead
|
||||
return a < b;
|
||||
};
|
||||
|
||||
// Ordered set of timers that are currently active
|
||||
std::set<PTR(OSTimer), decltype(timer_sort)> active_timers{timer_sort};
|
||||
|
||||
// Lambda to process a timer action to handle adding and removing timers
|
||||
auto process_timer_action = [&](const Action& action) {
|
||||
// Determine the action type and act on it
|
||||
if (const auto* add_action = std::get_if<AddTimerAction>(&action)) {
|
||||
active_timers.insert(add_action->timer);
|
||||
} else if (const auto* remove_action = std::get_if<RemoveTimerAction>(&action)) {
|
||||
active_timers.erase(remove_action->timer);
|
||||
}
|
||||
};
|
||||
|
||||
while (true) {
|
||||
// Empty the action queue
|
||||
Action cur_action;
|
||||
while (timer_context.action_queue.try_dequeue(cur_action)) {
|
||||
process_timer_action(cur_action);
|
||||
}
|
||||
|
||||
// If there's no timer to act on, wait for one to come in from the action queue
|
||||
while (active_timers.empty()) {
|
||||
timer_context.action_queue.wait_dequeue(cur_action);
|
||||
process_timer_action(cur_action);
|
||||
}
|
||||
|
||||
// Get the timer that's closest to running out
|
||||
PTR(OSTimer) cur_timer_ = *active_timers.begin();
|
||||
OSTimer* cur_timer = TO_PTR(OSTimer, cur_timer_);
|
||||
|
||||
// Remove the timer from the queue (it may get readded if waiting is interrupted)
|
||||
active_timers.erase(cur_timer_);
|
||||
|
||||
// Determine how long to wait to reach the timer's timestamp
|
||||
auto wait_duration = ticks_to_timepoint(cur_timer->timestamp) - std::chrono::high_resolution_clock::now();
|
||||
|
||||
// Wait for either the duration to complete or a new action to come through
|
||||
if (wait_duration.count() >= 0 && timer_context.action_queue.wait_dequeue_timed(cur_action, wait_duration)) {
|
||||
// Timer was interrupted by a new action
|
||||
// Add the current timer back to the queue (done first in case the action is to remove this timer)
|
||||
active_timers.insert(cur_timer_);
|
||||
// Process the new action
|
||||
process_timer_action(cur_action);
|
||||
}
|
||||
else {
|
||||
// Waiting for the timer completed, so send the timer's message to its message queue
|
||||
osSendMesg(PASS_RDRAM cur_timer->mq, cur_timer->msg, OS_MESG_NOBLOCK);
|
||||
// If the timer has a specified interval then reload it with that value
|
||||
if (cur_timer->interval != 0) {
|
||||
cur_timer->timestamp = cur_timer->interval + time_now();
|
||||
active_timers.insert(cur_timer_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ultramodern::init_timers(RDRAM_ARG1) {
|
||||
timer_context.thread = std::thread{ timer_thread, PASS_RDRAM1 };
|
||||
timer_context.thread.detach();
|
||||
}
|
||||
|
||||
uint32_t ultramodern::get_speed_multiplier() {
|
||||
return speed_multiplier;
|
||||
}
|
||||
|
||||
std::chrono::high_resolution_clock::time_point ultramodern::get_start() {
|
||||
return start_time;
|
||||
}
|
||||
|
||||
std::chrono::high_resolution_clock::duration ultramodern::time_since_start() {
|
||||
return std::chrono::high_resolution_clock::now() - start_time;
|
||||
}
|
||||
|
||||
extern "C" u32 osGetCount() {
|
||||
uint64_t total_count = time_now();
|
||||
|
||||
// Allow for overflows, which is how osGetCount behaves
|
||||
return (uint32_t)total_count;
|
||||
}
|
||||
|
||||
extern "C" OSTime osGetTime() {
|
||||
uint64_t total_count = time_now();
|
||||
|
||||
return total_count;
|
||||
}
|
||||
|
||||
extern "C" int osSetTimer(RDRAM_ARG PTR(OSTimer) t_, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg) {
|
||||
OSTimer* t = TO_PTR(OSTimer, t_);
|
||||
|
||||
// Determine the time when this timer will trigger off
|
||||
if (countdown == 0) {
|
||||
// Set the timestamp based on the interval
|
||||
t->timestamp = interval + time_now();
|
||||
} else {
|
||||
t->timestamp = countdown + time_now();
|
||||
}
|
||||
t->interval = interval;
|
||||
t->mq = mq;
|
||||
t->msg = msg;
|
||||
|
||||
timer_context.action_queue.enqueue(AddTimerAction{ t_ });
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int osStopTimer(RDRAM_ARG PTR(OSTimer) t_) {
|
||||
timer_context.action_queue.enqueue(RemoveTimerAction{ t_ });
|
||||
|
||||
// TODO don't blindly return 0 here; requires some response from the timer thread to know what the returned value was
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
// The implementations of std::chrono::sleep_until and sleep_for were affected by changing the system clock backwards in older versions
|
||||
// of Microsoft's STL. This was fixed as of Visual Studio 2022 17.9, but to be safe ultramodern uses Win32 Sleep directly.
|
||||
void ultramodern::sleep_milliseconds(uint32_t millis) {
|
||||
Sleep(millis);
|
||||
}
|
||||
|
||||
void ultramodern::sleep_until(const std::chrono::high_resolution_clock::time_point& time_point) {
|
||||
auto time_now = std::chrono::high_resolution_clock::now();
|
||||
if (time_point > time_now) {
|
||||
long long delta_ms = std::chrono::ceil<std::chrono::milliseconds>(time_point - time_now).count();
|
||||
// printf("Sleeping %lld %d ms\n", delta_ms, (uint32_t)delta_ms);
|
||||
Sleep(delta_ms);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void ultramodern::sleep_milliseconds(uint32_t millis) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{millis});
|
||||
}
|
||||
|
||||
void ultramodern::sleep_until(const std::chrono::high_resolution_clock::time_point& time_point) {
|
||||
std::this_thread::sleep_until(time_point);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,260 +0,0 @@
|
||||
#ifndef __ULTRA64_ultramodern_H__
|
||||
#define __ULTRA64_ultramodern_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define UNUSED __attribute__((unused))
|
||||
#define ALIGNED(x) __attribute__((aligned(x)))
|
||||
#else
|
||||
#define UNUSED
|
||||
#define ALIGNED(x)
|
||||
#endif
|
||||
|
||||
typedef int64_t s64;
|
||||
typedef uint64_t u64;
|
||||
typedef int32_t s32;
|
||||
typedef uint32_t u32;
|
||||
typedef int16_t s16;
|
||||
typedef uint16_t u16;
|
||||
typedef int8_t s8;
|
||||
typedef uint8_t u8;
|
||||
|
||||
#if 0 // For native compilation
|
||||
# define PTR(x) x*
|
||||
# define RDRAM_ARG
|
||||
# define RDRAM_ARG1
|
||||
# define PASS_RDRAM
|
||||
# define PASS_RDRAM1
|
||||
# define TO_PTR(type, var) var
|
||||
# define GET_MEMBER(type, addr, member) (&addr->member)
|
||||
# ifdef __cplusplus
|
||||
# define NULLPTR nullptr
|
||||
# endif
|
||||
#else
|
||||
# define PTR(x) int32_t
|
||||
# define RDRAM_ARG uint8_t *rdram,
|
||||
# define RDRAM_ARG1 uint8_t *rdram
|
||||
# define PASS_RDRAM rdram,
|
||||
# define PASS_RDRAM1 rdram
|
||||
# define TO_PTR(type, var) ((type*)(&rdram[(uint64_t)var - 0xFFFFFFFF80000000]))
|
||||
# define GET_MEMBER(type, addr, member) (addr + (intptr_t)&(((type*)nullptr)->member))
|
||||
# ifdef __cplusplus
|
||||
# define NULLPTR (PTR(void))0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef NULL
|
||||
#define NULL (PTR(void) 0)
|
||||
#endif
|
||||
|
||||
#define OS_MESG_NOBLOCK 0
|
||||
#define OS_MESG_BLOCK 1
|
||||
|
||||
typedef s32 OSPri;
|
||||
typedef s32 OSId;
|
||||
|
||||
typedef u64 OSTime;
|
||||
|
||||
#define OS_EVENT_SW1 0 /* CPU SW1 interrupt */
|
||||
#define OS_EVENT_SW2 1 /* CPU SW2 interrupt */
|
||||
#define OS_EVENT_CART 2 /* Cartridge interrupt: used by rmon */
|
||||
#define OS_EVENT_COUNTER 3 /* Counter int: used by VI/Timer Mgr */
|
||||
#define OS_EVENT_SP 4 /* SP task done interrupt */
|
||||
#define OS_EVENT_SI 5 /* SI (controller) interrupt */
|
||||
#define OS_EVENT_AI 6 /* AI interrupt */
|
||||
#define OS_EVENT_VI 7 /* VI interrupt: used by VI/Timer Mgr */
|
||||
#define OS_EVENT_PI 8 /* PI interrupt: used by PI Manager */
|
||||
#define OS_EVENT_DP 9 /* DP full sync interrupt */
|
||||
#define OS_EVENT_CPU_BREAK 10 /* CPU breakpoint: used by rmon */
|
||||
#define OS_EVENT_SP_BREAK 11 /* SP breakpoint: used by rmon */
|
||||
#define OS_EVENT_FAULT 12 /* CPU fault event: used by rmon */
|
||||
#define OS_EVENT_THREADSTATUS 13 /* CPU thread status: used by rmon */
|
||||
#define OS_EVENT_PRENMI 14 /* Pre NMI interrupt */
|
||||
|
||||
#define M_GFXTASK 1
|
||||
#define M_AUDTASK 2
|
||||
#define M_VIDTASK 3
|
||||
#define M_NJPEGTASK 4
|
||||
|
||||
/////////////
|
||||
// Structs //
|
||||
/////////////
|
||||
|
||||
// Threads
|
||||
|
||||
typedef struct UltraThreadContext UltraThreadContext;
|
||||
|
||||
typedef enum {
|
||||
STOPPED,
|
||||
QUEUED,
|
||||
RUNNING,
|
||||
BLOCKED
|
||||
} OSThreadState;
|
||||
|
||||
typedef struct OSThread_t {
|
||||
PTR(struct OSThread_t) next; // Next thread in the given queue
|
||||
OSPri priority;
|
||||
PTR(PTR(struct OSThread_t)) queue; // Queue this thread is in, if any
|
||||
uint32_t pad2;
|
||||
uint16_t flags; // These two are swapped to reflect rdram byteswapping
|
||||
uint16_t state;
|
||||
OSId id;
|
||||
int32_t pad3;
|
||||
UltraThreadContext* context; // An actual pointer regardless of platform
|
||||
int32_t sp;
|
||||
} OSThread;
|
||||
|
||||
typedef u32 OSEvent;
|
||||
typedef PTR(void) OSMesg;
|
||||
|
||||
typedef struct OSMesgQueue {
|
||||
PTR(OSThread) blocked_on_recv; /* Linked list of threads blocked on receiving from this queue */
|
||||
PTR(OSThread) blocked_on_send; /* Linked list of threads blocked on sending to this queue */
|
||||
s32 validCount; /* Number of messages in the queue */
|
||||
s32 first; /* Index of the first message in the ring buffer */
|
||||
s32 msgCount; /* Size of message buffer */
|
||||
PTR(OSMesg) msg; /* Pointer to circular buffer to store messages */
|
||||
} OSMesgQueue;
|
||||
|
||||
// RSP
|
||||
|
||||
typedef struct {
|
||||
u32 type;
|
||||
u32 flags;
|
||||
|
||||
PTR(u64) ucode_boot;
|
||||
u32 ucode_boot_size;
|
||||
|
||||
PTR(u64) ucode;
|
||||
u32 ucode_size;
|
||||
|
||||
PTR(u64) ucode_data;
|
||||
u32 ucode_data_size;
|
||||
|
||||
PTR(u64) dram_stack;
|
||||
u32 dram_stack_size;
|
||||
|
||||
PTR(u64) output_buff;
|
||||
PTR(u64) output_buff_size;
|
||||
|
||||
PTR(u64) data_ptr;
|
||||
u32 data_size;
|
||||
|
||||
PTR(u64) yield_data_ptr;
|
||||
u32 yield_data_size;
|
||||
} OSTask_s;
|
||||
|
||||
typedef union {
|
||||
OSTask_s t;
|
||||
int64_t force_structure_alignment;
|
||||
} OSTask;
|
||||
|
||||
// PI
|
||||
|
||||
struct OSIoMesgHdr {
|
||||
// These 3 reversed due to endianness
|
||||
u8 status; /* Return status */
|
||||
u8 pri; /* Message priority (High or Normal) */
|
||||
u16 type; /* Message type */
|
||||
PTR(OSMesgQueue) retQueue; /* Return message queue to notify I/O completion */
|
||||
};
|
||||
|
||||
struct OSIoMesg {
|
||||
OSIoMesgHdr hdr; /* Message header */
|
||||
PTR(void) dramAddr; /* RDRAM buffer address (DMA) */
|
||||
u32 devAddr; /* Device buffer address (DMA) */
|
||||
u32 size; /* DMA transfer size in bytes */
|
||||
u32 piHandle; /* PI device handle */
|
||||
};
|
||||
|
||||
struct OSPiHandle {
|
||||
PTR(OSPiHandle_s) unused; /* point to next handle on the table */
|
||||
// These four members reversed due to endianness
|
||||
u8 relDuration; /* domain release duration */
|
||||
u8 pageSize; /* domain page size */
|
||||
u8 latency; /* domain latency */
|
||||
u8 type; /* DEVICE_TYPE_BULK for disk */
|
||||
// These three members reversed due to endianness
|
||||
u16 padding; /* struct alignment padding */
|
||||
u8 domain; /* which domain */
|
||||
u8 pulse; /* domain pulse width */
|
||||
u32 baseAddress; /* Domain address */
|
||||
u32 speed; /* for roms only */
|
||||
/* The following are "private" elements" */
|
||||
u32 transferInfo[18]; /* for disk only */
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
u32 ctrl;
|
||||
u32 width;
|
||||
u32 burst;
|
||||
u32 vSync;
|
||||
u32 hSync;
|
||||
u32 leap;
|
||||
u32 hStart;
|
||||
u32 xScale;
|
||||
u32 vCurrent;
|
||||
} OSViCommonRegs;
|
||||
|
||||
typedef struct {
|
||||
u32 origin;
|
||||
u32 yScale;
|
||||
u32 vStart;
|
||||
u32 vBurst;
|
||||
u32 vIntr;
|
||||
} OSViFieldRegs;
|
||||
|
||||
typedef struct {
|
||||
u8 padding[3];
|
||||
u8 type;
|
||||
OSViCommonRegs comRegs;
|
||||
OSViFieldRegs fldRegs[2];
|
||||
} OSViMode;
|
||||
|
||||
///////////////
|
||||
// Functions //
|
||||
///////////////
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
void osInitialize(void);
|
||||
|
||||
typedef void (thread_func_t)(PTR(void));
|
||||
|
||||
void osCreateThread(RDRAM_ARG PTR(OSThread) t, OSId id, PTR(thread_func_t) entry, PTR(void) arg, PTR(void) sp, OSPri p);
|
||||
void osStartThread(RDRAM_ARG PTR(OSThread) t);
|
||||
void osStopThread(RDRAM_ARG PTR(OSThread) t);
|
||||
void osDestroyThread(RDRAM_ARG PTR(OSThread) t);
|
||||
void osYieldThread(RDRAM_ARG1);
|
||||
void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri);
|
||||
OSPri osGetThreadPri(RDRAM_ARG PTR(OSThread) thread);
|
||||
OSId osGetThreadId(RDRAM_ARG PTR(OSThread) t);
|
||||
|
||||
void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32);
|
||||
s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue), OSMesg, s32);
|
||||
s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue), OSMesg, s32);
|
||||
s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32);
|
||||
void osSetEventMesg(RDRAM_ARG OSEvent, PTR(OSMesgQueue), OSMesg);
|
||||
void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue), OSMesg, u32);
|
||||
void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr);
|
||||
void osViSetMode(RDRAM_ARG PTR(OSViMode));
|
||||
void osViSetSpecialFeatures(uint32_t func);
|
||||
void osViBlack(uint8_t active);
|
||||
void osViSetXScale(float scale);
|
||||
void osViSetYScale(float scale);
|
||||
PTR(void) osViGetNextFramebuffer();
|
||||
PTR(void) osViGetCurrentFramebuffer();
|
||||
u32 osGetCount();
|
||||
OSTime osGetTime();
|
||||
int osSetTimer(RDRAM_ARG PTR(OSTimer) timer, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg);
|
||||
int osStopTimer(RDRAM_ARG PTR(OSTimer) timer);
|
||||
u32 osVirtualToPhysical(PTR(void) addr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif
|
@ -1,14 +0,0 @@
|
||||
#include "ultra64.h"
|
||||
#include "ultramodern.hpp"
|
||||
|
||||
void ultramodern::preinit(RDRAM_ARG ultramodern::WindowHandle window_handle) {
|
||||
ultramodern::set_main_thread();
|
||||
ultramodern::init_events(PASS_RDRAM window_handle);
|
||||
ultramodern::init_timers(PASS_RDRAM1);
|
||||
ultramodern::init_audio();
|
||||
ultramodern::init_saving(PASS_RDRAM1);
|
||||
ultramodern::init_thread_cleanup();
|
||||
}
|
||||
|
||||
extern "C" void osInitialize() {
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
#ifndef __ultramodern_HPP__
|
||||
#define __ultramodern_HPP__
|
||||
|
||||
#include <thread>
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
#include <span>
|
||||
|
||||
#undef MOODYCAMEL_DELETE_FUNCTION
|
||||
#define MOODYCAMEL_DELETE_FUNCTION = delete
|
||||
#include "lightweightsemaphore.h"
|
||||
#include "ultra64.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <Windows.h>
|
||||
#elif defined(__ANDROID__)
|
||||
# include "android/native_window.h"
|
||||
#elif defined(__linux__)
|
||||
# include "X11/Xlib.h"
|
||||
# undef None
|
||||
# undef Status
|
||||
# undef LockMask
|
||||
# undef Always
|
||||
# undef Success
|
||||
#endif
|
||||
|
||||
struct UltraThreadContext {
|
||||
std::thread host_thread;
|
||||
moodycamel::LightweightSemaphore running;
|
||||
moodycamel::LightweightSemaphore initialized;
|
||||
};
|
||||
|
||||
namespace ultramodern {
|
||||
|
||||
#if defined(_WIN32)
|
||||
// Native HWND handle to the target window.
|
||||
struct WindowHandle {
|
||||
HWND window;
|
||||
DWORD thread_id = (DWORD)-1;
|
||||
auto operator<=>(const WindowHandle&) const = default;
|
||||
};
|
||||
#elif defined(__ANDROID__)
|
||||
using WindowHandle = ANativeWindow*;
|
||||
#elif defined(__linux__)
|
||||
struct WindowHandle {
|
||||
Display* display;
|
||||
Window window;
|
||||
auto operator<=>(const WindowHandle&) const = default;
|
||||
};
|
||||
#endif
|
||||
|
||||
// We need a place in rdram to hold the PI handles, so pick an address in extended rdram
|
||||
constexpr uint32_t rdram_size = 1024 * 1024 * 16; // 16MB to give extra room for anything custom
|
||||
constexpr int32_t cart_handle = 0x80800000;
|
||||
constexpr int32_t drive_handle = (int32_t)(cart_handle + sizeof(OSPiHandle));
|
||||
constexpr int32_t flash_handle = (int32_t)(drive_handle + sizeof(OSPiHandle));
|
||||
constexpr uint32_t save_size = 1024 * 1024 / 8; // Maximum save size, 1Mbit for flash
|
||||
|
||||
// Initialization.
|
||||
void preinit(RDRAM_ARG WindowHandle window_handle);
|
||||
void init_saving(RDRAM_ARG1);
|
||||
void init_events(RDRAM_ARG WindowHandle window_handle);
|
||||
void init_timers(RDRAM_ARG1);
|
||||
void init_thread_cleanup();
|
||||
|
||||
// Thread queues.
|
||||
constexpr PTR(PTR(OSThread)) running_queue = (PTR(PTR(OSThread)))-1;
|
||||
|
||||
void thread_queue_insert(RDRAM_ARG PTR(PTR(OSThread)) queue, PTR(OSThread) toadd);
|
||||
PTR(OSThread) thread_queue_pop(RDRAM_ARG PTR(PTR(OSThread)) queue);
|
||||
bool thread_queue_remove(RDRAM_ARG PTR(PTR(OSThread)) queue_, PTR(OSThread) t_);
|
||||
bool thread_queue_empty(RDRAM_ARG PTR(PTR(OSThread)) queue);
|
||||
PTR(OSThread) thread_queue_peek(RDRAM_ARG PTR(PTR(OSThread)) queue);
|
||||
|
||||
// Message queues.
|
||||
void wait_for_external_message(RDRAM_ARG1);
|
||||
|
||||
// Thread scheduling.
|
||||
void check_running_queue(RDRAM_ARG1);
|
||||
void run_next_thread_and_wait(RDRAM_ARG1);
|
||||
void resume_thread_and_wait(RDRAM_ARG OSThread* t);
|
||||
void schedule_running_thread(RDRAM_ARG PTR(OSThread) t);
|
||||
void cleanup_thread(UltraThreadContext* thread_context);
|
||||
uint32_t permanent_thread_count();
|
||||
uint32_t temporary_thread_count();
|
||||
struct thread_terminated : std::exception {};
|
||||
|
||||
enum class ThreadPriority {
|
||||
Low,
|
||||
Normal,
|
||||
High,
|
||||
VeryHigh,
|
||||
Critical
|
||||
};
|
||||
|
||||
void set_native_thread_name(const std::string& name);
|
||||
void set_native_thread_priority(ThreadPriority pri);
|
||||
PTR(OSThread) this_thread();
|
||||
void set_main_thread();
|
||||
bool is_game_thread();
|
||||
void submit_rsp_task(RDRAM_ARG PTR(OSTask) task);
|
||||
void send_si_message(RDRAM_ARG1);
|
||||
uint32_t get_speed_multiplier();
|
||||
|
||||
// Time
|
||||
std::chrono::high_resolution_clock::time_point get_start();
|
||||
std::chrono::high_resolution_clock::duration time_since_start();
|
||||
void measure_input_latency();
|
||||
void sleep_milliseconds(uint32_t millis);
|
||||
void sleep_until(const std::chrono::high_resolution_clock::time_point& time_point);
|
||||
|
||||
// Graphics
|
||||
uint32_t get_target_framerate(uint32_t original);
|
||||
uint32_t get_display_refresh_rate();
|
||||
float get_resolution_scale();
|
||||
void load_shader_cache(std::span<const char> cache_data);
|
||||
|
||||
// Audio
|
||||
void init_audio();
|
||||
void set_audio_frequency(uint32_t freq);
|
||||
void queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data, uint32_t byte_count);
|
||||
uint32_t get_remaining_audio_bytes();
|
||||
|
||||
struct audio_callbacks_t {
|
||||
using queue_samples_t = void(int16_t*, size_t);
|
||||
using get_samples_remaining_t = size_t();
|
||||
using set_frequency_t = void(uint32_t);
|
||||
queue_samples_t* queue_samples;
|
||||
get_samples_remaining_t* get_frames_remaining;
|
||||
set_frequency_t* set_frequency;
|
||||
};
|
||||
|
||||
// Input
|
||||
struct input_callbacks_t {
|
||||
using poll_input_t = void(void);
|
||||
using get_input_t = void(uint16_t*, float*, float*);
|
||||
using set_rumble_t = void(bool);
|
||||
poll_input_t* poll_input;
|
||||
get_input_t* get_input;
|
||||
set_rumble_t* set_rumble;
|
||||
};
|
||||
|
||||
struct gfx_callbacks_t {
|
||||
using gfx_data_t = void*;
|
||||
using create_gfx_t = gfx_data_t();
|
||||
using create_window_t = WindowHandle(gfx_data_t);
|
||||
using update_gfx_t = void(gfx_data_t);
|
||||
create_gfx_t* create_gfx;
|
||||
create_window_t* create_window;
|
||||
update_gfx_t* update_gfx;
|
||||
};
|
||||
bool is_game_started();
|
||||
void quit();
|
||||
void join_event_threads();
|
||||
void join_thread_cleaner_thread();
|
||||
void join_saving_thread();
|
||||
|
||||
} // namespace ultramodern
|
||||
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
|
||||
#define debug_printf(...)
|
||||
//#define debug_printf(...) printf(__VA_ARGS__);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user