mirror of
https://github.com/flyinghead/flycast.git
synced 2026-11-07 17:11:09 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a14c02e2bd | ||
|
|
802874e79d | ||
|
|
1fac3d3fef | ||
|
|
800d0ec2d8 | ||
|
|
6a6038f910 | ||
|
|
f86e4c6ea4 | ||
|
|
7b1df94c8e | ||
|
|
973e569a7d | ||
|
|
13cfe43858 | ||
|
|
e778e529be | ||
|
|
2c83abcce4 | ||
|
|
07535d22e8 | ||
|
|
98ceceed1e | ||
|
|
23d212f4f0 | ||
|
|
917a7552f9 | ||
|
|
697d15a133 | ||
|
|
34e1211871 | ||
|
|
cd89ae96ab | ||
|
|
01db8a203e | ||
|
|
6d49796a15 | ||
|
|
907d15155e | ||
|
|
e2f1484571 | ||
|
|
fe58ee8c20 | ||
|
|
7c22066aa3 | ||
|
|
3a498b1cb5 | ||
|
|
c52e6ccf56 | ||
|
|
de7597f0bc | ||
|
|
5f35ec7d72 | ||
|
|
fa9d39a355 | ||
|
|
db065c5700 | ||
|
|
620699865b | ||
|
|
a9bd1c74fd | ||
|
|
5543a95c4f | ||
|
|
78d5d09c1e | ||
|
|
31697241b7 | ||
|
|
e9f4020c56 | ||
|
|
5eb52090ad | ||
|
|
128e6ce29c | ||
|
|
38a3d83c77 | ||
|
|
19ed307f74 | ||
|
|
d207cd9198 | ||
|
|
1d356d5e55 | ||
|
|
32c2304459 | ||
|
|
34ad8c8262 | ||
|
|
abfc411392 | ||
|
|
9981e84912 | ||
|
|
b5acd1b533 | ||
|
|
6bb0fc3e18 | ||
|
|
08afcbe6b9 | ||
|
|
e9dd6187cb | ||
|
|
a5dc811814 | ||
|
|
5c86428c04 | ||
|
|
258f5c11d4 | ||
|
|
91c3084d52 | ||
|
|
506b4efd04 | ||
|
|
80471d57b0 | ||
|
|
ce8689101f | ||
|
|
518c496251 | ||
|
|
804edb00d7 | ||
|
|
f03faffd2f | ||
|
|
fa23eef593 | ||
|
|
658abb386a | ||
|
|
fa3c526e4f | ||
|
|
e6d3365c0b | ||
|
|
7422d1b831 | ||
|
|
6dafd41eaa | ||
|
|
ed953fcbfd | ||
|
|
ec56b5f260 | ||
|
|
bc3b8c71da | ||
|
|
0d9853df9a | ||
|
|
df7aedf506 | ||
|
|
df5feec55c | ||
|
|
bbdef06a09 | ||
|
|
b4c38d652e | ||
|
|
4dca1988da | ||
|
|
552a86086b | ||
|
|
9ec8b7e658 | ||
|
|
7d69c9cfd4 | ||
|
|
5ff3f974d9 | ||
|
|
f83fda7069 | ||
|
|
59ed35a7ea | ||
|
|
04669ebe41 | ||
|
|
98ce9fb5d7 | ||
|
|
2a57c9ffd9 | ||
|
|
2ea7920fdc | ||
|
|
9d4c337349 | ||
|
|
89b72a8e3e | ||
|
|
fe04dd6503 | ||
|
|
b1b80d8849 | ||
|
|
e36e9df2dc | ||
|
|
ea087b9140 | ||
|
|
869038f40a | ||
|
|
0a304f1396 | ||
|
|
628bd3dbb1 | ||
|
|
2ac5c772f0 | ||
|
|
fff68b3f57 | ||
|
|
e6707469d2 | ||
|
|
a99c8667b4 | ||
|
|
49ad26a7e5 | ||
|
|
da5be1a0a7 | ||
|
|
dd7a5f0620 | ||
|
|
6d73f7191c | ||
|
|
215a5af165 | ||
|
|
c0fb036d60 | ||
|
|
8933f9fe0c | ||
|
|
4509ee903c | ||
|
|
04b74c3a11 | ||
|
|
36c975e391 | ||
|
|
bcad2d76d0 | ||
|
|
9869ea8852 | ||
|
|
ea22ee8723 | ||
|
|
b777c15fdc | ||
|
|
93379c489f | ||
|
|
b03335bca9 | ||
|
|
604ebd58fe | ||
|
|
eddf263586 | ||
|
|
3f70e47adb | ||
|
|
0abac3465d | ||
|
|
a9b451c6f2 | ||
|
|
6838fbd642 | ||
|
|
34cd222e12 | ||
|
|
fb286f777c | ||
|
|
0ff538115f | ||
|
|
5aca173e80 | ||
|
|
2f0917bb7b | ||
|
|
f2013c652a | ||
|
|
230d83654f | ||
|
|
fa04b20837 | ||
|
|
52c8ecef1e | ||
|
|
d3c90b0f84 | ||
|
|
fe4cf1d586 | ||
|
|
08ffbe6cf1 | ||
|
|
b35b829a12 | ||
|
|
a75d1cff53 | ||
|
|
62d7a0432a | ||
|
|
066c82ddfd | ||
|
|
1145248aa7 | ||
|
|
daa46ec63e | ||
|
|
9814e2fe40 | ||
|
|
d8a76809e5 | ||
|
|
fd9d39b127 | ||
|
|
cc4a59b1f2 | ||
|
|
8f511f8960 | ||
|
|
9bdb0ad323 | ||
|
|
5c96b69ff0 | ||
|
|
7db29cc2fd | ||
|
|
162bc02007 | ||
|
|
12bb43652f | ||
|
|
71b739faee | ||
|
|
57e33d323a | ||
|
|
b4d7e75977 | ||
|
|
58b283fc02 | ||
|
|
149e651d24 | ||
|
|
c37be698db | ||
|
|
453dcefc12 | ||
|
|
ce3abe5a0b | ||
|
|
e2722869ff | ||
|
|
bbe606c8f7 | ||
|
|
5803d8aa14 | ||
|
|
180e670605 | ||
|
|
f831592f64 | ||
|
|
ba04273a25 | ||
|
|
804fcb4a9e | ||
|
|
c3763d8fc4 | ||
|
|
9473c0377a | ||
|
|
9c6c8e59d6 | ||
|
|
d367284ddf | ||
|
|
105f760613 | ||
|
|
8c5446706e | ||
|
|
7fcd6976fa | ||
|
|
982f26a0da | ||
|
|
d5e74ad756 | ||
|
|
9dbca96328 | ||
|
|
44e4c7b50d | ||
|
|
643374c91e | ||
|
|
33d5b38163 | ||
|
|
caa55c0297 | ||
|
|
a6242fd653 | ||
|
|
02c779ddc1 | ||
|
|
b0284ba9ba | ||
|
|
29a54e36ce | ||
|
|
7cc58de0c3 | ||
|
|
2550d10902 | ||
|
|
565f5174f7 | ||
|
|
f58719d9b7 | ||
|
|
8041f02984 | ||
|
|
41406fb31a | ||
|
|
e293832bef | ||
|
|
253970d8e5 | ||
|
|
63aecfd491 | ||
|
|
670b5001d4 | ||
|
|
2805df5484 | ||
|
|
0cd66cea5c | ||
|
|
d93612734b | ||
|
|
95e28f412f | ||
|
|
a749e51559 | ||
|
|
e82f38a8cc | ||
|
|
89bf160a9d | ||
|
|
e64b28a759 | ||
|
|
675ce1b63b | ||
|
|
63d19693bc | ||
|
|
8b5cb7513b | ||
|
|
940f5bb911 | ||
|
|
d70a265804 | ||
|
|
fc7b8c247d | ||
|
|
526b52b141 | ||
|
|
bcbc43fad6 | ||
|
|
d7c59e9ab5 | ||
|
|
14487d96ec | ||
|
|
f52c275c90 | ||
|
|
a27a81ba18 | ||
|
|
d1d7373d43 | ||
|
|
80cc71952e | ||
|
|
8c155b02c8 | ||
|
|
5ff2bc0c42 | ||
|
|
e435d72aa8 | ||
|
|
f68e60fe92 | ||
|
|
1e60ef78c7 | ||
|
|
a9c5ef8b41 | ||
|
|
d234bcf7d9 | ||
|
|
1e68c83356 | ||
|
|
436e125d59 | ||
|
|
444635372e | ||
|
|
abf284fc2d | ||
|
|
b4f89c9926 | ||
|
|
b043043568 | ||
|
|
8542ff2dc4 | ||
|
|
f6dead8c39 | ||
|
|
6de588bf57 | ||
|
|
1536d588f6 | ||
|
|
d3c64bf1f5 | ||
|
|
2f10d39910 | ||
|
|
7e917bc53a | ||
|
|
4a9ca07838 | ||
|
|
3e81e56499 | ||
|
|
39c991671f | ||
|
|
10dee60cda | ||
|
|
6925586fa2 | ||
|
|
d447d3d2db | ||
|
|
55a8d6094b | ||
|
|
1370b74fb9 | ||
|
|
46deb1dd7a | ||
|
|
7baf5c0a5b | ||
|
|
ce19baebc5 | ||
|
|
00892506d7 | ||
|
|
34bb486e54 | ||
|
|
1583b75dd7 | ||
|
|
569dc1cd1a | ||
|
|
8c0da03a15 | ||
|
|
3153ac762e | ||
|
|
b49dd9d184 | ||
|
|
4941fdf934 | ||
|
|
81d4756fe9 | ||
|
|
f35f0d51ac | ||
|
|
db8147e91f | ||
|
|
91d1430b42 | ||
|
|
cc1f7ca1c1 |
No files matched your search
@@ -11,13 +11,16 @@ jobs:
|
||||
name: Android
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
|
||||
steps:
|
||||
- name: Set up build environment
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install ccache cmake libcurl4-openssl-dev ninja-build
|
||||
sudo apt-get -y install ccache libcurl4-openssl-dev
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
@@ -28,21 +31,18 @@ jobs:
|
||||
distribution: 'temurin'
|
||||
cache: 'gradle'
|
||||
|
||||
- uses: actions/cache@v5
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.ccache
|
||||
key: android-ccache-${{ github.sha }}
|
||||
restore-keys: android-ccache-
|
||||
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ccache-android-${{ github.sha }}
|
||||
restore-keys: ccache-android-
|
||||
|
||||
- name: Disable release signing
|
||||
run: sed -i 's/signingConfigs.getByName("release")/signingConfigs.getByName("debug")/g' shell/android-studio/flycast/build.gradle.kts
|
||||
run: sed -i 's/signingConfigs.getByName("release")/signingConfigs.getByName("debug")/' shell/android-studio/flycast/build.gradle.kts
|
||||
if: github.repository != 'flyinghead/flycast' || github.event_name != 'push'
|
||||
|
||||
- name: Bump version code
|
||||
uses: chkfung/[email protected]
|
||||
with:
|
||||
gradlePath: shell/android-studio/flycast/build.gradle.kts
|
||||
versionCode: ${{ github.run_number }}
|
||||
run: sed -i 's/versionCode = 8/versionCode = ${{ github.run_number }}/' shell/android-studio/flycast/build.gradle.kts
|
||||
|
||||
- name: Gradle
|
||||
working-directory: shell/android-studio
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
SENTRY_UPLOAD_URL: ${{ secrets.SENTRY_UPLOAD_URL }}
|
||||
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: flycast-release.apk
|
||||
path: shell/android-studio/flycast/build/outputs/apk/release/flycast-release.apk
|
||||
@@ -85,18 +85,8 @@ jobs:
|
||||
rclone copy shell/android-studio/flycast/build/outputs/apk/release scaleway:flycast-builds/android/${GITHUB_REF#refs/}-$GITHUB_SHA --exclude '*.json'
|
||||
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push'
|
||||
|
||||
- name: Setup Sentry CLI
|
||||
uses: mathieu-bour/setup-sentry-cli@v2
|
||||
env:
|
||||
SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }}
|
||||
with:
|
||||
url: https://sentry.io
|
||||
token: ${{ env.SENTRY_TOKEN }}
|
||||
organization: flycast
|
||||
project: minidump
|
||||
version: 2.21.2
|
||||
if: ${{ env.SENTRY_TOKEN != '' }}
|
||||
|
||||
- uses: getsentry/action-setup-cli@v1
|
||||
|
||||
- name: Upload symbols to Sentry
|
||||
run: |
|
||||
VERSION=$(git describe --tags --always)
|
||||
@@ -105,5 +95,7 @@ jobs:
|
||||
sentry-cli debug-files upload symbols
|
||||
shell: bash
|
||||
env:
|
||||
SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }}
|
||||
if: ${{ env.SENTRY_TOKEN != '' && github.event_name == 'push' }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_TOKEN }}
|
||||
SENTRY_ORG: flycast
|
||||
SENTRY_PROJECT: minidump
|
||||
if: ${{ env.SENTRY_AUTH_TOKEN != '' && github.event_name == 'push' }}
|
||||
+10
-10
@@ -18,28 +18,28 @@ jobs:
|
||||
libretro: [ OFF, ON ]
|
||||
include:
|
||||
- operating_system: freebsd
|
||||
version: '15.0'
|
||||
pkginstall: sudo pkg update && sudo pkg install -y alsa-lib ccache cmake evdev-proto git libao libcdio libevdev libudev-devd libzip lua54 miniupnpc ninja pkgconf pulseaudio sdl2
|
||||
version: '15.1'
|
||||
pkginstall: sudo pkg upgrade -y && sudo pkg install -y alsa-lib ccache cmake evdev-proto git libao libcdio libevdev libudev-devd libzip lua54 miniupnpc ninja pkgconf pulseaudio sdl2
|
||||
- operating_system: netbsd
|
||||
version: '10.1'
|
||||
pkginstall: sudo pkgin update && sudo pkgin -y install alsa-lib ccache clang cmake git libao libcdio libusb1-1.0.30 libzip lua54 miniupnpc ninja-build pkgconf pulseaudio SDL2
|
||||
version: '11.0'
|
||||
pkginstall: sudo pkgin update && sudo pkgin -y install alsa-lib ccache clang cmake git libao libcdio libusb1 libzip lua54 miniupnpc ninja-build pkgconf pulseaudio SDL2
|
||||
- operating_system: openbsd
|
||||
version: '7.9'
|
||||
pkginstall: sudo pkg_add -u && sudo pkg_add ccache cmake git libao libcdio libusb1-1.0.29 libzip lua-5.4.8p0 miniupnpc ninja pulseaudio sdl2
|
||||
version: '8.0'
|
||||
pkginstall: sudo pkg_add -u && sudo pkg_add ccache cmake git libao libcdio libusb1-1.0.30 libzip lua-5.4.8p2 miniupnpc ninja pulseaudio sdl2
|
||||
exclude:
|
||||
- architecture: arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/cache@v5
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-${{ github.sha }}
|
||||
restore-keys: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-
|
||||
key: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-${{ matrix.libretro }}-${{ github.sha }}
|
||||
restore-keys: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-${{ matrix.libretro }}-
|
||||
|
||||
- name: Start VM
|
||||
uses: cross-platform-actions/action@v1
|
||||
|
||||
+51
-37
@@ -21,13 +21,14 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- {name: i686-pc-windows-msvc, os: windows-2022, shell: cmd, arch: x86, cmakeArgs: -G Ninja, buildType: Release}
|
||||
- {name: apple-darwin, os: macos-latest, shell: sh, cmakeArgs: -G Xcode -DUSE_DISCORD=ON, destDir: osx, buildType: RelWithDebInfo}
|
||||
- {name: x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: linux, buildType: RelWithDebInfo}
|
||||
- {name: x86_64-pc-windows-msvc, os: windows-2022, shell: cmd, arch: x64, cmakeArgs: -G Ninja -DUSE_DISCORD=ON, buildType: Release}
|
||||
- {name: x86_64-w64-mingw32, os: windows-2022, shell: 'msys2 {0}', cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: win, buildType: RelWithDebInfo}
|
||||
- {name: libretro-x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -G Ninja, buildType: Release}
|
||||
- {name: libretro-x86_64-w64-mingw32, os: windows-2022, shell: 'msys2 {0}', cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -G Ninja, buildType: Release}
|
||||
- { name: i686-pc-windows-msvc, os: windows-2022, shell: cmd, arch: x86, cmakeArgs: -G Ninja, buildType: Release }
|
||||
- { name: apple-darwin, os: macos-latest, shell: sh, cmakeArgs: -G Xcode -DUSE_DISCORD=ON -DMOLTENVK_DYLIB=$MOLTENVK_DYLIB, destDir: osx, buildType: RelWithDebInfo, packagePath: artifact/flycast-apple-darwin.zip }
|
||||
- { name: x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: linux, buildType: RelWithDebInfo }
|
||||
- { name: arm64-pc-linux-gnu, os: ubuntu-22.04-arm, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: linux, buildType: RelWithDebInfo }
|
||||
- { name: x86_64-pc-windows-msvc, os: windows-2022, shell: cmd, arch: x64, cmakeArgs: -G Ninja -DUSE_DISCORD=ON, buildType: Release }
|
||||
- { name: x86_64-w64-mingw32, os: windows-2022, shell: 'msys2 {0}', cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: win, buildType: RelWithDebInfo, packagePath: artifact/flycast-x86_64-w64-mingw32.zip }
|
||||
- { name: libretro-x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -G Ninja, buildType: Release }
|
||||
- { name: libretro-x86_64-w64-mingw32, os: windows-2022, shell: 'msys2 {0}', cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -G Ninja, buildType: Release }
|
||||
|
||||
steps:
|
||||
- name: Set up build environment (macOS)
|
||||
@@ -36,20 +37,26 @@ jobs:
|
||||
brew list -1 | grep python | while read formula; do brew unlink $formula; brew link --overwrite $formula; done
|
||||
brew update || :
|
||||
brew install libao ldid pulseaudio
|
||||
brew uninstall --ignore-dependencies zstd
|
||||
VULKAN_VER=1.4.341.1 && aria2c https://sdk.lunarg.com/sdk/download/$VULKAN_VER/mac/vulkan_sdk.zip?Human=true -o vulkan_sdk.zip
|
||||
unzip -q vulkan_sdk.zip
|
||||
./vulkansdk-macOS-$VULKAN_VER.app/Contents/MacOS/vulkansdk-macOS-$VULKAN_VER --root $HOME/VulkanSDK --accept-licenses --default-answer --confirm-command install copy_only=1
|
||||
rm -f vulkan_sdk.zip
|
||||
rm -rf ./vulkansdk-macOS-$VULKAN_VER.app
|
||||
echo "VULKAN_SDK=$HOME/VulkanSDK/macOS" >> $GITHUB_ENV
|
||||
|
||||
moltenvk_tag="v1.4.2"
|
||||
moltenvk_root="$RUNNER_TEMP/MoltenVK-$moltenvk_tag"
|
||||
rm -rf "$moltenvk_root"
|
||||
mkdir -p "$moltenvk_root"
|
||||
aria2c "https://github.com/KhronosGroup/MoltenVK/releases/download/$moltenvk_tag/MoltenVK-macos.tar" -d "$RUNNER_TEMP" -o "MoltenVK-macos-$moltenvk_tag.tar"
|
||||
tar -xf "$RUNNER_TEMP/MoltenVK-macos-$moltenvk_tag.tar" -C "$moltenvk_root"
|
||||
moltenvk_dylib="$(find "$moltenvk_root" -type f -path '*/macOS/*' -name libMoltenVK.dylib | head -n 1)"
|
||||
if [ -z "$moltenvk_dylib" ]; then
|
||||
echo "MoltenVK $moltenvk_tag was downloaded, but macOS libMoltenVK.dylib was not found." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "MOLTENVK_DYLIB=$moltenvk_dylib" >> $GITHUB_ENV
|
||||
if: runner.os == 'macOS'
|
||||
|
||||
- name: Set up build environment (Linux)
|
||||
run: |
|
||||
sudo add-apt-repository ppa:christianrauch/libdecoration
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install ccache libao-dev libasound2-dev libevdev-dev libgl1-mesa-dev liblua5.3-dev libminiupnpc-dev libpulse-dev libsdl2-dev libudev-dev libzip-dev ninja-build libcurl4-openssl-dev libcdio-dev libfuse2 locales
|
||||
sudo apt-get -y install ccache libao-dev libasound2-dev libevdev-dev libgl1-mesa-dev liblua5.3-dev libminiupnpc-dev libpulse-dev libsdl2-dev libudev-dev libzip-dev libcurl4-openssl-dev libcdio-dev libfuse2 locales
|
||||
sudo apt-get -y install libwayland-dev libdecor-0-dev libaudio-dev libjack-dev libsndio-dev libsamplerate0-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxkbcommon-dev libdrm-dev libgbm-dev libgles2-mesa-dev libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev
|
||||
sudo sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/g' /etc/locale.gen
|
||||
sudo locale-gen
|
||||
@@ -59,12 +66,13 @@ jobs:
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: MINGW64
|
||||
install: git make mingw-w64-x86_64-ccache mingw-w64-x86_64-cmake mingw-w64-x86_64-lua mingw-w64-x86_64-ninja mingw-w64-x86_64-SDL2 mingw-w64-x86_64-toolchain mingw-w64-x86_64-libcdio
|
||||
install: git make
|
||||
pacboy: ccache:p cmake:p libcdio:p lua:p ninja:p SDL2:p toolchain:p
|
||||
if: matrix.config.shell == 'msys2 {0}'
|
||||
|
||||
- name: Set up build environment (Windows, Visual Studio)
|
||||
run: |
|
||||
choco install ccache directx-sdk ninja
|
||||
choco install ccache directx-sdk
|
||||
echo DXSDK_DIR=C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)>> %GITHUB_ENV%
|
||||
if: matrix.config.shell == 'cmd'
|
||||
|
||||
@@ -73,23 +81,23 @@ jobs:
|
||||
arch: ${{ matrix.config.arch }}
|
||||
if: matrix.config.shell == 'cmd'
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Compile universal dependencies (macOS)
|
||||
run: |
|
||||
for formula in ./shell/apple/libomp.rb ./shell/apple/libpng-flycast.rb; do
|
||||
for formula in ./shell/apple/libpng-flycast.rb; do
|
||||
libname=$(basename "$formula" .rb)
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 HOMEBREW_DEVELOPER=1 brew install --build-from-source --formula "$formula" || \
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 HOMEBREW_DEVELOPER=1 brew reinstall --build-from-source --formula "$formula"
|
||||
|
||||
|
||||
prefix=$(brew --prefix $libname)
|
||||
# Find and verify the static library
|
||||
libfile=$(find "$prefix/lib" -name "*.a" | head -n1)
|
||||
lipo -info "$libfile"
|
||||
|
||||
|
||||
# Export prefix for subsequent CMake steps
|
||||
if [ -z "$CMAKE_PREFIX_PATH" ]; then
|
||||
CMAKE_PREFIX_PATH="$prefix"
|
||||
@@ -98,9 +106,11 @@ jobs:
|
||||
fi
|
||||
done
|
||||
echo "CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH" >> $GITHUB_ENV
|
||||
# After the last brew download: brew's own curl links brew's zstd
|
||||
brew uninstall --ignore-dependencies zstd
|
||||
if: matrix.config.name == 'apple-darwin'
|
||||
|
||||
- uses: actions/cache@v5
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ccache-${{ matrix.config.name }}-${{ github.sha }}
|
||||
@@ -119,7 +129,7 @@ jobs:
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.config.buildType }} -DCMAKE_INSTALL_PREFIX=artifact -DENABLE_CTEST=ON ${{ matrix.config.cmakeArgs }}
|
||||
cmake --build build --config ${{ matrix.config.buildType }}
|
||||
./build/flycast
|
||||
if: matrix.config.name == 'x86_64-pc-linux-gnu'
|
||||
if: matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu'
|
||||
|
||||
- name: Dump symbols
|
||||
run: |
|
||||
@@ -134,7 +144,7 @@ jobs:
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
if: matrix.config.name == 'x86_64-pc-linux-gnu'
|
||||
if: matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu'
|
||||
|
||||
- name: Dump symbols and make AppImage (linux)
|
||||
run: |
|
||||
@@ -142,29 +152,34 @@ jobs:
|
||||
dump_syms artifact/bin/flycast > symbols/flycast.sym 2>/dev/null
|
||||
strip artifact/bin/flycast
|
||||
shell/linux/make-appimage.sh . artifact/bin appimage
|
||||
mv flycast-x86_64.AppImage artifact/bin
|
||||
ARCH=$(uname -m)
|
||||
mv flycast-${ARCH}.AppImage artifact/bin
|
||||
rm artifact/bin/flycast
|
||||
if: matrix.config.name == 'x86_64-pc-linux-gnu'
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: flycast-${{ matrix.config.name }}
|
||||
path: |
|
||||
artifact/bin
|
||||
artifact/lib/libretro
|
||||
if: matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu'
|
||||
|
||||
- name: Package app (macos)
|
||||
run: |
|
||||
cd artifact/bin
|
||||
codesign --verify --deep --strict --all-architectures --verbose=4 Flycast.app
|
||||
zip -rm flycast.app.zip Flycast.app
|
||||
cp flycast.app.zip "../../${{ matrix.config.packagePath }}"
|
||||
if: matrix.config.name == 'apple-darwin'
|
||||
|
||||
- name: Package app (windows)
|
||||
run: |
|
||||
powershell Compress-Archive artifact/bin/flycast.exe artifact/bin/flycast.zip
|
||||
powershell Compress-Archive artifact/bin/flycast.exe artifact/bin/flycast.zip
|
||||
rm artifact/bin/flycast.exe
|
||||
cp artifact/bin/flycast.zip "${{ matrix.config.packagePath }}"
|
||||
if: matrix.config.name == 'x86_64-w64-mingw32'
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: flycast-${{ matrix.config.name }}
|
||||
archive: ${{ !matrix.config.packagePath }}
|
||||
path: |
|
||||
${{ matrix.config.packagePath || 'artifact/bin' }}
|
||||
${{ !matrix.config.packagePath && 'artifact/lib/libretro' || '' }}
|
||||
|
||||
- name: Setup Rclone
|
||||
uses: AnimMouse/setup-rclone@v1
|
||||
with:
|
||||
@@ -186,7 +201,7 @@ jobs:
|
||||
project: minidump
|
||||
version: 2.21.2
|
||||
if: ${{ env.SENTRY_TOKEN != '' }}
|
||||
|
||||
|
||||
- name: Upload symbols to Sentry (Windows, macOS, Linux)
|
||||
run: |
|
||||
VERSION=$(git describe --tags --always)
|
||||
@@ -196,5 +211,4 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }}
|
||||
if: ${{ env.SENTRY_TOKEN != '' && (matrix.config.name == 'x86_64-w64-mingw32' || matrix.config.name == 'apple-darwin' || matrix.config.name == 'x86_64-pc-linux-gnu') && github.event_name == 'push' }}
|
||||
|
||||
if: ${{ env.SENTRY_TOKEN != '' && (matrix.config.name == 'x86_64-w64-mingw32' || matrix.config.name == 'apple-darwin' || matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu') && github.event_name == 'push' }}
|
||||
@@ -12,19 +12,22 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
container: devkitpro/devkita64:latest
|
||||
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- {name: standalone, destDir: switch}
|
||||
- {name: libretro, cmakeArgs: -DLIBRETRO=ON}
|
||||
- { name: standalone, destDir: switch }
|
||||
- { name: libretro, cmakeArgs: -DLIBRETRO=ON }
|
||||
|
||||
steps:
|
||||
- name: Set up build environment
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install ccache cmake
|
||||
sudo apt-get -y install ccache gh
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
@@ -32,9 +35,9 @@ jobs:
|
||||
- name: Mark git repository as safe
|
||||
run: git config --global --add safe.directory $PWD
|
||||
|
||||
- uses: actions/cache@v5
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.ccache
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ccache-switch-${{ matrix.config.name }}-${{ github.sha }}
|
||||
restore-keys: ccache-switch-${{ matrix.config.name }}-
|
||||
|
||||
@@ -43,7 +46,7 @@ jobs:
|
||||
$DEVKITPRO/portlibs/switch/bin/aarch64-none-elf-cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=artifact -G Ninja ${{ matrix.config.cmakeArgs }}
|
||||
cmake --build build --config Release --target install
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: flycast-switch-${{ matrix.config.name }}
|
||||
path: |
|
||||
@@ -58,7 +61,6 @@ jobs:
|
||||
uses: AnimMouse/setup-rclone@v1
|
||||
with:
|
||||
rclone_config: ${{ secrets.RCLONE_CONFIG }}
|
||||
version: v1.69.1
|
||||
|
||||
- name: Upload to S3
|
||||
run: rclone copy artifact/bin scaleway:flycast-builds/${{ matrix.config.destDir }}/${GITHUB_REF#refs/}-$GITHUB_SHA
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
arch: [ Win32, x64, ARM64 ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
cd build\AppPackages\flycast\flycast_*_Test
|
||||
mkdir tmp
|
||||
ren *.msix flycast.msix
|
||||
makeappx.exe unpack /p .\flycast.msix /d tmp
|
||||
makeappx unpack /p .\flycast.msix /d tmp
|
||||
copy ..\..\..\Release\*.dll tmp
|
||||
makeappx pack /d tmp /p ..\..\..\artifact\flycast.appx
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
run: signtool sign /f shell\uwp\sign_cert.pfx /p '${{ secrets.SIGN_CERT_PWD }}' /v /fd SHA256 build\artifact\flycast.appx
|
||||
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push'
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: flycast-uwp-${{ matrix.arch }}
|
||||
path: build/artifact
|
||||
|
||||
+35
-7
@@ -37,6 +37,11 @@
|
||||
IOS_MINVER: 13.0
|
||||
MINVER: 13.0
|
||||
|
||||
.core-defs-libnx:
|
||||
extends: .core-defs
|
||||
variables:
|
||||
CORE_ARGS: -DLIBRETRO=ON -DUSE_OPENMP=OFF
|
||||
|
||||
.core-defs-android:
|
||||
extends: .core-defs
|
||||
script:
|
||||
@@ -80,8 +85,8 @@ include:
|
||||
|
||||
################################## CONSOLES ################################
|
||||
# Nintendo Switch
|
||||
# - project: 'libretro-infrastructure/ci-templates'
|
||||
# file: '/libnx-static.yml'
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/libnx-static-cmake.yml'
|
||||
|
||||
# tvOS arm64
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
@@ -96,7 +101,7 @@ include:
|
||||
stages:
|
||||
- build-prepare
|
||||
- build-shared
|
||||
# - build-static
|
||||
- build-static
|
||||
|
||||
##############################################################################
|
||||
#################################### STAGES ##################################
|
||||
@@ -182,10 +187,33 @@ libretro-build-ios-arm64:
|
||||
|
||||
################################### CONSOLES #################################
|
||||
# Nintendo Switch
|
||||
#libretro-build-libnx-aarch64:
|
||||
# extends:
|
||||
# - .libretro-libnx-static-retroarch-master
|
||||
# - .core-defs
|
||||
libretro-build-libnx-aarch64:
|
||||
extends:
|
||||
- .libretro-libnx-static-cmake-retroarch-master
|
||||
- .core-defs-libnx
|
||||
# The libnx image ships CMake 3.18.4 and this tree asks for 3.22.1, so the
|
||||
# job stops at configure. Put a newer one in front of it - in /tmp, since
|
||||
# the job does not run as a user who can write /opt.
|
||||
before_script:
|
||||
- !reference [.libretro-libnx-static-cmake, before_script]
|
||||
- |
|
||||
CMAKE_VER=3.31.6
|
||||
curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VER}/cmake-${CMAKE_VER}-linux-x86_64.tar.gz" | tar -xz -C /tmp
|
||||
export PATH="/tmp/cmake-${CMAKE_VER}-linux-x86_64/bin:$PATH"
|
||||
# The template's script, with one word changed. It builds --target
|
||||
# ${CORENAME}_libretro, and on Switch that target is flycast's own objects
|
||||
# and nothing else. `combined` is the target CMakeLists.txt defines for
|
||||
# exactly this case: it folds in xxhash, chdr, zip, elf and the resources,
|
||||
# which is what RetroArch links against afterwards.
|
||||
script:
|
||||
- cmake $CORE_ARGS "$CMAKE_SOURCE_ROOT" -B$BUILD_DIR -DCMAKE_BUILD_TYPE=Release -DLIBRETRO_STATIC=ON -DLIBRETRO_SUFFIX=_libnx -DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/Switch.cmake -DNSWITCH=ON -DCFLAGS=-DHAVE_LIBNX=1 -DCXXFLAGS=-DHAVE_LIBNX=1
|
||||
- cmake --build $BUILD_DIR --target combined --config Release -- -j $NUMPROC
|
||||
- mv $BUILD_DIR/$EXTRA_PATH/${CORENAME}_libretro_libnx.a retroarch-precompiled/libretro_libnx.a
|
||||
- cd retroarch-precompiled/
|
||||
- make -f Makefile.libnx -j10
|
||||
- mv retroarch_switch.nro ../${CORENAME}_libretro_libnx.nro
|
||||
- mv retroarch_switch.elf ../${CORENAME}_libretro_libnx.elf
|
||||
- cd ../
|
||||
|
||||
# tvOS arm64
|
||||
libretro-build-tvos-arm64:
|
||||
|
||||
+4
-1
@@ -49,7 +49,7 @@
|
||||
url = https://github.com/herumi/xbyak.git
|
||||
[submodule "core/deps/libjuice"]
|
||||
path = core/deps/libjuice
|
||||
url = https://github.com/paullouisageneau/libjuice.git
|
||||
url = https://github.com/flyinghead/libjuice.git
|
||||
[submodule "core/deps/websocketpp"]
|
||||
path = core/deps/websocketpp
|
||||
url = https://github.com/zaphoyd/websocketpp.git
|
||||
@@ -65,3 +65,6 @@
|
||||
[submodule "core/deps/gamesdk"]
|
||||
path = core/deps/gamesdk
|
||||
url = https://github.com/flyinghead/gamesdk.git
|
||||
[submodule "core/deps/basis_universal"]
|
||||
path = core/deps/basis_universal
|
||||
url = https://github.com/BinomialLLC/basis_universal.git
|
||||
+160
-72
@@ -87,8 +87,10 @@ option(USE_HOST_GLSLANG "Use host glslang" OFF)
|
||||
option(USE_HOST_LIBZIP "Use host libzip" ON)
|
||||
option(USE_HOST_SDL "Use host SDL library" ${USE_HOST_SDL_DEFAULT})
|
||||
option(USE_HOST_LIBCHDR "Use host libchdr" OFF)
|
||||
option(USE_HOST_LIBJUICE "Use host libjuice" OFF)
|
||||
option(USE_OPENMP "Use OpenMP if available" ON)
|
||||
option(USE_VULKAN "Build with Vulkan support" ON)
|
||||
set(MOLTENVK_DYLIB "" CACHE FILEPATH "Path to macOS MoltenVK dylib for macOS Vulkan builds")
|
||||
option(USE_DX9 "Build with Direct3D 9 support" ON)
|
||||
option(USE_DX11 "Build with Direct3D 11 support" ON)
|
||||
option(LIBRETRO "Build libretro core" OFF)
|
||||
@@ -105,6 +107,7 @@ option(ENABLE_DC_PROFILER "Build with support for target machine (SH4) profiler"
|
||||
option(ENABLE_FC_PROFILER "Build with support for host app (Flycast) profiler" OFF)
|
||||
option(USE_DISCORD "Use Discord Presence API" OFF)
|
||||
option(USE_LIBCDIO "Use libcdio for CDROM access" OFF)
|
||||
option(USE_DREAMLINK_DEVICES "Use DreamLink devices" ON)
|
||||
|
||||
if(IOS AND NOT LIBRETRO)
|
||||
set(USE_VULKAN OFF CACHE BOOL "Force vulkan off" FORCE)
|
||||
@@ -120,6 +123,11 @@ if(WEBOS)
|
||||
set(USE_VULKAN OFF CACHE BOOL "Force vulkan off" FORCE)
|
||||
endif()
|
||||
|
||||
if(ANDROID OR IOS OR NINTENDO_SWITCH OR LIBRETRO)
|
||||
# DreamLink only available for non-mobile Linux, MacOS, and Windows
|
||||
set(USE_DREAMLINK_DEVICES OFF CACHE BOOL "Force DreamLink devices off" FORCE)
|
||||
endif()
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include(CMakeRC)
|
||||
|
||||
@@ -205,6 +213,10 @@ elseif(LIBRETRO)
|
||||
if(NOT CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
|
||||
endif()
|
||||
if(NOT WIN32)
|
||||
# keep bundled libs like glslang from binding to the frontend's copies
|
||||
target_link_options(${PROJECT_NAME} PRIVATE "-Wl,--version-script,${CMAKE_CURRENT_SOURCE_DIR}/shell/libretro/link.T")
|
||||
endif()
|
||||
endif()
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "flycast_libretro"
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO"
|
||||
@@ -218,7 +230,10 @@ elseif(LIBRETRO)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE ${FOUNDATION})
|
||||
endif()
|
||||
if(ANDROID OR USE_GLES)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE GLES GLES3 HAVE_OPENGLES HAVE_OPENGLES3 HAVE_OIT)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE GLES GLES3 HAVE_OPENGLES HAVE_OPENGLES3 HAVE_OPENGLES_3_1 HAVE_OIT)
|
||||
if(ANDROID)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE android)
|
||||
endif()
|
||||
elseif(IOS)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE GLES GLES3 HAVE_OPENGLES HAVE_OPENGLES3)
|
||||
find_library(OPENGLES OpenGLES)
|
||||
@@ -271,6 +286,7 @@ elseif(WIN32)
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
|
||||
endif()
|
||||
elseif(APPLE)
|
||||
set(USE_OPENMP OFF CACHE BOOL "Force openmp off" FORCE)
|
||||
add_executable(${PROJECT_NAME} MACOSX_BUNDLE core/emulator.cpp)
|
||||
else()
|
||||
add_executable(${PROJECT_NAME} core/emulator.cpp)
|
||||
@@ -280,6 +296,25 @@ set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
CMAKE_CXX_STANDARD 20
|
||||
CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_library(flycast_basis_transcoder STATIC
|
||||
core/deps/basis_universal/transcoder/basisu_transcoder.cpp
|
||||
core/deps/basis_universal/zstd/zstddeclib.c)
|
||||
target_compile_features(flycast_basis_transcoder PRIVATE cxx_std_17)
|
||||
target_compile_definitions(flycast_basis_transcoder PRIVATE
|
||||
BASISD_SUPPORT_KTX2=1
|
||||
BASISD_SUPPORT_KTX2_ZSTD=1
|
||||
BASISD_SUPPORT_DXT1=1
|
||||
BASISD_SUPPORT_DXT5A=1
|
||||
BASISD_SUPPORT_ETC2_EAC_A8=1
|
||||
BASISD_SUPPORT_XUASTC=1)
|
||||
target_include_directories(flycast_basis_transcoder PUBLIC core/deps)
|
||||
set_target_properties(flycast_basis_transcoder PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||
target_compile_options(flycast_basis_transcoder PRIVATE
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-fno-strict-aliasing>)
|
||||
endif()
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE flycast_basis_transcoder)
|
||||
|
||||
if(WINDOWS_STORE)
|
||||
set(USE_OPENGL OFF)
|
||||
set(USE_VULKAN OFF)
|
||||
@@ -428,10 +463,6 @@ if(USE_BREAKPAD AND NOT LIBRETRO)
|
||||
endif()
|
||||
|
||||
if(USE_OPENMP)
|
||||
if(APPLE AND IS_DIRECTORY "/opt/homebrew/opt/libomp")
|
||||
list(PREPEND CMAKE_PREFIX_PATH "/opt/homebrew/opt/libomp")
|
||||
message(STATUS "Found and using Homebrew libomp hint.")
|
||||
endif()
|
||||
find_package(OpenMP)
|
||||
if(OpenMP_CXX_FOUND)
|
||||
if(MINGW)
|
||||
@@ -472,6 +503,37 @@ if(USE_VULKAN)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE glslang::glslang-default-resource-limits glslang::SPIRV)
|
||||
endif()
|
||||
|
||||
if(USE_DREAMLINK_DEVICES)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_DREAMLINK_DEVICES=1)
|
||||
|
||||
if (WINDOWS_STORE)
|
||||
# Use winrt implementation of the DreamPicoPort-API
|
||||
option(DREAMPICOPORT_WITH_LIBUSB "Use libusb to access DreamPicoPort-API" OFF)
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES "DragonFly|FreeBSD|NetBSD|OpenBSD|Haiku")
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LIBUSB REQUIRED libusb-1.0)
|
||||
|
||||
set(DREAMPICOPORT_LIBUSB_LIBRARIES "${LIBUSB_LIBRARIES}" CACHE STRING "Name of the libusb library to link against")
|
||||
set(DREAMPICOPORT_LIBUSB_INCLUDE_DIRS "${LIBUSB_INCLUDE_DIRS}" CACHE STRING "Path to libusb include directory, when applicable")
|
||||
else()
|
||||
if(MINGW)
|
||||
# Shim 'windowsapp' to prevent libusb from linking against the UWP library
|
||||
# and link against standard desktop libraries instead for Windows 7 support.
|
||||
if(NOT TARGET windowsapp)
|
||||
add_library(windowsapp INTERFACE)
|
||||
target_link_libraries(windowsapp INTERFACE advapi32 ole32 shell32 user32)
|
||||
endif()
|
||||
endif()
|
||||
# libusb
|
||||
add_subdirectory(core/deps/DreamPicoPort-API/ext/libusb-cmake)
|
||||
endif()
|
||||
|
||||
# DreamPicoPort-API
|
||||
option(DREAMPICOPORT_ADD_LIBUSB "Add internal libusb library" OFF) # Already included above, when applicable
|
||||
add_subdirectory(core/deps/DreamPicoPort-API EXCLUDE_FROM_ALL)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE dream_pico_port_api)
|
||||
endif()
|
||||
|
||||
if(NOT LIBRETRO)
|
||||
if(USE_OSS)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_OSS)
|
||||
@@ -491,39 +553,6 @@ if(NOT LIBRETRO)
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID AND NOT IOS)
|
||||
if (NOT NINTENDO_SWITCH)
|
||||
# DreamLink enabled for non-mobile Linux, MacOS, and Windows
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_DREAMLINK_DEVICES=1)
|
||||
set(USE_DREAMLINK_DEVICES ON) # Must be set before adding core/sdl
|
||||
|
||||
if (WINDOWS_STORE)
|
||||
# Use winrt implementation of the DreamPicoPort-API
|
||||
option(DREAMPICOPORT_WITH_LIBUSB "Use libusb to access DreamPicoPort-API" OFF)
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES "DragonFly|FreeBSD|NetBSD|OpenBSD|Haiku")
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LIBUSB REQUIRED libusb-1.0)
|
||||
|
||||
set(DREAMPICOPORT_LIBUSB_LIBRARIES "${LIBUSB_LIBRARIES}" CACHE STRING "Name of the libusb library to link against")
|
||||
set(DREAMPICOPORT_LIBUSB_INCLUDE_DIRS "${LIBUSB_INCLUDE_DIRS}" CACHE STRING "Path to libusb include directory, when applicable")
|
||||
else()
|
||||
if(MINGW)
|
||||
# Shim 'windowsapp' to prevent libusb from linking against the UWP library
|
||||
# and link against standard desktop libraries instead for Windows 7 support.
|
||||
if(NOT TARGET windowsapp)
|
||||
add_library(windowsapp INTERFACE)
|
||||
target_link_libraries(windowsapp INTERFACE advapi32 ole32 shell32 user32)
|
||||
endif()
|
||||
endif()
|
||||
# libusb
|
||||
add_subdirectory(core/deps/DreamPicoPort-API/ext/libusb-cmake)
|
||||
endif()
|
||||
|
||||
# DreamPicoPort-API
|
||||
option(DREAMPICOPORT_ADD_LIBUSB "Add internal libusb library" OFF) # Already included above, when applicable
|
||||
add_subdirectory(core/deps/DreamPicoPort-API EXCLUDE_FROM_ALL)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE dream_pico_port_api)
|
||||
endif()
|
||||
|
||||
if(USE_HOST_SDL)
|
||||
find_package(SDL2 2.0.18)
|
||||
endif()
|
||||
@@ -550,11 +579,6 @@ if(NOT LIBRETRO)
|
||||
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_SDL USE_SDL_AUDIO)
|
||||
add_subdirectory(core/sdl)
|
||||
|
||||
if((UNIX AND NOT APPLE) OR NINTENDO_SWITCH)
|
||||
find_package(CURL REQUIRED)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_package(ZLIB)
|
||||
@@ -574,6 +598,18 @@ if(NOT LIBRETRO)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# http
|
||||
if((UNIX AND NOT APPLE AND NOT ANDROID) OR NINTENDO_SWITCH)
|
||||
find_package(CURL REQUIRED)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl)
|
||||
elseif(LIBRETRO AND ANDROID)
|
||||
# libretro needs to enable prefab and add dependency 'implementation("io.github.vvb2060.ndk:curl:8.18.0")'
|
||||
find_package(curl REQUIRED CONFIG)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE curl::curl_static)
|
||||
elseif(LIBRETRO AND WIN32)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE wininet)
|
||||
endif()
|
||||
|
||||
find_package(PkgConfig)
|
||||
|
||||
if(NOT WITH_SYSTEM_ZLIB)
|
||||
@@ -789,18 +825,27 @@ if(WIN32)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE core/deps/dirent)
|
||||
endif()
|
||||
|
||||
if(NOT LIBRETRO AND NOT NINTENDO_SWITCH)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_ICE)
|
||||
# libjuice
|
||||
# libjuice
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_ICE)
|
||||
if (USE_HOST_LIBJUICE AND UNIX AND NOT APPLE AND NOT ANDROID)
|
||||
find_package(LibJuice)
|
||||
endif()
|
||||
if(NOT TARGET LibJuice::LibJuiceStatic)
|
||||
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
|
||||
option(NO_SERVER "Disable server support" ON)
|
||||
option(NO_TESTS "Disable tests build" ON)
|
||||
add_subdirectory(core/deps/libjuice EXCLUDE_FROM_ALL)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE LibJuice::LibJuiceStatic)
|
||||
if(NINTENDO_SWITCH)
|
||||
target_include_directories(juice-static SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/shell/switch)
|
||||
target_compile_definitions(juice-static PRIVATE getnameinfo=getnameinfo_fixed)
|
||||
endif()
|
||||
endif()
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE LibJuice::LibJuiceStatic)
|
||||
|
||||
# websocket++
|
||||
# websocket++
|
||||
if(NOT LIBRETRO AND NOT NINTENDO_SWITCH)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE core/deps/websocketpp)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE ASIO_STANDALONE)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE ASIO_STANDALONE USE_WEBSOCKET)
|
||||
if (MSVC)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||
_WEBSOCKETPP_CPP11_FUNCTIONAL_
|
||||
@@ -815,8 +860,16 @@ if(NOT LIBRETRO AND NOT NINTENDO_SWITCH)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# tinygettext
|
||||
set(TINYGETTEXT_WO_ICONV ON)
|
||||
add_subdirectory(core/deps/tinygettext EXCLUDE_FROM_ALL)
|
||||
if(MSVC)
|
||||
# language.cpp holds the native language names as non-ASCII literals. MSVC
|
||||
# decodes sources without a BOM using the active code page, so on a non-UTF-8
|
||||
# system locale those literals break the build (C2001/C2064). The main target
|
||||
# already passes /utf-8; this dependency needs it too.
|
||||
target_compile_options(tinygettext PRIVATE /utf-8)
|
||||
endif()
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE core/deps/tinygettext/include)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE tinygettext::tinygettext)
|
||||
|
||||
@@ -972,7 +1025,9 @@ if(LIBRETRO)
|
||||
shell/libretro/storage.cpp
|
||||
shell/libretro/vmu_xhair.cpp)
|
||||
if(APPLE)
|
||||
target_sources(${PROJECT_NAME} PRIVATE shell/libretro/oslib_apple.mm)
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
shell/libretro/oslib_apple.mm
|
||||
shell/apple/common/http_client.mm)
|
||||
endif()
|
||||
if(WIN32)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE mswsock)
|
||||
@@ -1361,19 +1416,24 @@ if(NOT LIBRETRO)
|
||||
COMMAND ${CMAKE_COMMAND} -E tar "cfv" "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>-${CMAKE_OSX_SYSROOT}/Flycast.ipa" --format=zip ${CMAKE_CURRENT_BINARY_DIR}/Payload)
|
||||
else()
|
||||
|
||||
add_subdirectory(core/deps/Syphon)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE Syphon)
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${SYPHON_METALLIB}
|
||||
$<TARGET_FILE_DIR:flycast>/../Resources/)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE VIDEO_ROUTING)
|
||||
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
shell/apple/common/http_client.mm
|
||||
shell/apple/common/util.mm
|
||||
shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.h
|
||||
shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.mm
|
||||
shell/apple/emulator-osx/emulator-osx/osx-main.mm)
|
||||
shell/apple/common/util.mm)
|
||||
# The app's main() lives in SDLApplicationDelegate.mm; the test binary
|
||||
# gets its main() from gtest_main and stubs the rest (tests/src/test_stubs.cpp).
|
||||
if(NOT BUILD_TESTING)
|
||||
add_subdirectory(core/deps/Syphon)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE Syphon)
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${SYPHON_METALLIB}
|
||||
$<TARGET_FILE_DIR:flycast>/../Resources/)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE VIDEO_ROUTING)
|
||||
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.h
|
||||
shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.mm
|
||||
shell/apple/emulator-osx/emulator-osx/osx-main.mm)
|
||||
endif()
|
||||
set(ASSETS shell/apple/emulator-osx/emulator-osx/Images.xcassets)
|
||||
target_sources(${PROJECT_NAME} PRIVATE ${ASSETS})
|
||||
source_group("Resources" FILES ${ASSETS})
|
||||
@@ -1416,9 +1476,15 @@ if(NOT LIBRETRO)
|
||||
${IOSURFACE_LIBRARY}
|
||||
${MULTITOUCH_SUPPORT_LIBRARY})
|
||||
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "$ENV{VULKAN_SDK}/lib/libMoltenVK.dylib"
|
||||
$<TARGET_FILE_DIR:flycast>/../Frameworks/libvulkan.dylib)
|
||||
if(USE_VULKAN)
|
||||
set(MOLTENVK_DYLIB_PATH "${MOLTENVK_DYLIB}")
|
||||
if(NOT MOLTENVK_DYLIB_PATH OR NOT EXISTS "${MOLTENVK_DYLIB_PATH}")
|
||||
message(FATAL_ERROR "A macOS MoltenVK dylib is required for macOS Vulkan builds. Run shell/apple/generate_xcode_project.command to download the configured MoltenVK release, or pass -DMOLTENVK_DYLIB=/path/to/libMoltenVK.dylib.")
|
||||
endif()
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${MOLTENVK_DYLIB_PATH}"
|
||||
$<TARGET_FILE_DIR:flycast>/../Frameworks/libMoltenVK.dylib)
|
||||
endif()
|
||||
endif()
|
||||
elseif(UNIX)
|
||||
if(NOT BUILD_TESTING)
|
||||
@@ -1478,16 +1544,38 @@ endif()
|
||||
|
||||
if(NINTENDO_SWITCH)
|
||||
if(LIBRETRO)
|
||||
# ar names a member after the basename of the object, so extracting every
|
||||
# archive into one directory loses whatever two of them happen to share:
|
||||
# zstd carries its own copy of xxhash.c, whose object lands on top of the
|
||||
# real xxHash one and takes XXH32 with it. A directory per archive keeps
|
||||
# them apart.
|
||||
add_custom_target(combined ALL
|
||||
COMMAND ${CMAKE_AR} -x $<TARGET_FILE:xxHash::xxhash>
|
||||
COMMAND ${CMAKE_AR} -x $<TARGET_FILE:chdr-static>
|
||||
COMMAND ${CMAKE_AR} -x $<TARGET_FILE:zip>
|
||||
COMMAND ${CMAKE_AR} -x $<TARGET_FILE:elf>
|
||||
COMMAND ${CMAKE_AR} -x $<TARGET_FILE:flycast::res>
|
||||
COMMAND ${CMAKE_AR} -rs flycast_libretro_libnx.a *.o
|
||||
COMMAND rm *.o
|
||||
COMMAND ${CMAKE_COMMAND} -E rm -rf combined-objs
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||
combined-objs/xxhash combined-objs/chdr combined-objs/zip
|
||||
combined-objs/zstd combined-objs/elf combined-objs/tinygettext
|
||||
combined-objs/res combined-objs/libjuice
|
||||
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/xxhash
|
||||
${CMAKE_AR} -x $<TARGET_FILE:xxHash::xxhash>
|
||||
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/chdr
|
||||
${CMAKE_AR} -x $<TARGET_FILE:chdr-static>
|
||||
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/zip
|
||||
${CMAKE_AR} -x $<TARGET_FILE:zip>
|
||||
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/zstd
|
||||
${CMAKE_AR} -x $<TARGET_FILE:libzstd_static>
|
||||
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/elf
|
||||
${CMAKE_AR} -x $<TARGET_FILE:elf>
|
||||
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/tinygettext
|
||||
${CMAKE_AR} -x $<TARGET_FILE:tinygettext::tinygettext>
|
||||
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/res
|
||||
${CMAKE_AR} -x $<TARGET_FILE:flycast::res>
|
||||
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/libjuice
|
||||
${CMAKE_AR} -x $<TARGET_FILE:LibJuice::LibJuiceStatic>
|
||||
COMMAND ${CMAKE_AR} -rs flycast_libretro_libnx.a combined-objs/*/*.o
|
||||
COMMAND ${CMAKE_COMMAND} -E rm -rf combined-objs
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}
|
||||
DEPENDS xxHash::xxhash chdr-static zip ${PROJECT_NAME})
|
||||
DEPENDS xxHash::xxhash chdr-static zip libzstd_static
|
||||
tinygettext::tinygettext ${PROJECT_NAME})
|
||||
else()
|
||||
nx_generate_nacp(flycast.nacp NAME "Flycast" AUTHOR "flyinghead, M4xw" VERSION "${GIT_VERSION}")
|
||||
nx_create_nro(flycast NACP flycast.nacp ICON "${CMAKE_SOURCE_DIR}/shell/switch/flycast.jpeg")
|
||||
|
||||
@@ -28,7 +28,7 @@ Automated test results are available from the builds page as well.
|
||||
|
||||
### Android 
|
||||
|
||||
Install Flycast from [**Google Play**](https://play.google.com/store/apps/details?id=com.flycast.emulator).
|
||||
 Install Flycast from [**Google Play**](https://play.google.com/store/apps/details?id=com.flycast.emulator).
|
||||
|
||||
### Flatpak (Linux )
|
||||
|
||||
@@ -36,41 +36,49 @@ Install Flycast from [**Google Play**](https://play.google.com/store/apps/detail
|
||||
|
||||
2. Install Flycast from [Flathub](https://flathub.org/apps/details/org.flycast.Flycast):
|
||||
|
||||
`flatpak install -y org.flycast.Flycast`
|
||||
 `flatpak install -y org.flycast.Flycast`
|
||||
|
||||
3. Run Flycast:
|
||||
|
||||
`flatpak run org.flycast.Flycast`
|
||||
 `flatpak run org.flycast.Flycast`
|
||||
|
||||
### Homebrew (macOS )
|
||||
|
||||
1. [Set up Homebrew](https://brew.sh).
|
||||
1. [Set up Homebrew](https://brew.sh) or run `brew update` if already installed.
|
||||
|
||||
2. Install Flycast via Homebrew:
|
||||
2. Choose one channel:
|
||||
|
||||
`brew install --cask flycast`
|
||||
| Channel | Install command |
|
||||
| -------------------- | ------------------------------------------------------- |
|
||||
| Master (recommended) | `brew install --cask flyinghead/flycast/flycast@master` |
|
||||
| Stable | `brew install --cask flyinghead/flycast/flycast` |
|
||||
| Nightly dev | `brew install --cask flyinghead/flycast/flycast@dev` |
|
||||
|
||||
3. Run Flycast from your Application folder
|
||||
|
||||
 See the <a href="https://github.com/flyinghead/homebrew-flycast#readme">Flycast tap</a> for updating, uninstalling, and switching channels.
|
||||
|
||||
### iOS
|
||||
|
||||
Due to persistent harassment from an iOS user, support for this platform has been dropped.
|
||||
 Due to persistent harassment from an iOS user, support for this platform has been dropped.
|
||||
|
||||
### Xbox One/Series 
|
||||
|
||||
Grab the latest build from [**the builds page**](https://flyinghead.github.io/flycast-builds/), or the [**GitHub Actions**](https://github.com/flyinghead/flycast/actions/workflows/uwp.yml). Then install it using the **Xbox Device Portal**.
|
||||
 Grab the latest build from [**the builds page**](https://flyinghead.github.io/flycast-builds/), or the [**GitHub Actions**](https://github.com/flyinghead/flycast/actions/workflows/uwp.yml). Then install it using the **Xbox Device Portal**.
|
||||
|
||||
## Build from source
|
||||
|
||||
### macOS
|
||||
|
||||
Right-click the bootstrap script and choose **Open**:
|
||||
 Right-click the bootstrap script and choose **Open**:
|
||||
|
||||
`shell/apple/generate_xcode_project.command`
|
||||
 `shell/apple/generate_xcode_project.command`
|
||||
|
||||
### Windows
|
||||
|
||||
Double-click the bootstrap script:
|
||||
 Double-click the bootstrap script:
|
||||
|
||||
`shell\windows\generate_vs_project.bat`
|
||||
 `shell\windows\generate_vs_project.bat`
|
||||
|
||||
### Linux
|
||||
|
||||
|
||||
@@ -73,8 +73,10 @@ constexpr u32 SAMPLE_COUNT = 512; // AudioBackend::push() is always called with
|
||||
class RingBuffer
|
||||
{
|
||||
std::vector<u8> buffer;
|
||||
std::atomic_int readCursor { 0 };
|
||||
std::atomic_int writeCursor { 0 };
|
||||
// The producer and the audio callback each update one cursor: keep them on
|
||||
// separate cache lines.
|
||||
alignas(64) std::atomic_int readCursor { 0 };
|
||||
alignas(64) std::atomic_int writeCursor { 0 };
|
||||
|
||||
u32 readSize() {
|
||||
return (u32)((writeCursor - readCursor + buffer.size()) % buffer.size());
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
+9
-1
@@ -57,6 +57,7 @@ static void parseConfigOption(const std::string& str)
|
||||
switch (step)
|
||||
{
|
||||
case 0:
|
||||
section = trim_trailing_ws(section);
|
||||
if (section.empty()) {
|
||||
WARN_LOG(COMMON, "Invalid -config option '%s'. Format is: -config section:key=value,...", str.c_str());
|
||||
return;
|
||||
@@ -78,6 +79,7 @@ static void parseConfigOption(const std::string& str)
|
||||
WARN_LOG(COMMON, "Invalid -config option '%s'. Format is: -config section:key=value,...", str.c_str());
|
||||
return;
|
||||
case 1:
|
||||
key = trim_trailing_ws(key);
|
||||
if (key.empty()) {
|
||||
WARN_LOG(COMMON, "Invalid -config option '%s'. Format is: -config section:key=value,...", str.c_str());
|
||||
return;
|
||||
@@ -138,8 +140,14 @@ static void parseConfigOption(const std::string& str)
|
||||
switch (step)
|
||||
{
|
||||
case 0:
|
||||
// Ignore leading spaces. Inner spaces are kept (game ids such as "INITIAL D Ver.2")
|
||||
// and trailing ones are trimmed at the ':' and '=' separators
|
||||
if (!section.empty())
|
||||
section += c;
|
||||
break;
|
||||
case 1:
|
||||
// Ignore
|
||||
if (!key.empty())
|
||||
key += c;
|
||||
break;
|
||||
case 2:
|
||||
value += c;
|
||||
|
||||
+16
-5
@@ -20,6 +20,8 @@
|
||||
#include "types.h"
|
||||
#include "stdclass.h"
|
||||
|
||||
#include <cctype>
|
||||
|
||||
namespace config {
|
||||
|
||||
const IniFile::Entry *IniFile::getEntry(const std::string& sectionName, const std::string& name) const
|
||||
@@ -71,7 +73,7 @@ bool IniFile::getBool(const std::string& section, const std::string& entry, bool
|
||||
static bool hasHexPrefix(const std::string& s)
|
||||
{
|
||||
size_t pos = 0;
|
||||
while (pos < s.length() - 1 && std::isspace((int8_t)s[pos]))
|
||||
while (pos + 1 < s.length() && std::isspace(static_cast<unsigned char>(s[pos])))
|
||||
pos++;
|
||||
if (pos + 1 >= s.length())
|
||||
return false;
|
||||
@@ -89,9 +91,18 @@ int IniFile::getInt(const std::string& section, const std::string& entry, int de
|
||||
ss.setf(std::ios_base::hex, std::ios_base::basefield);
|
||||
else
|
||||
ss.setf(std::ios_base::dec, std::ios_base::basefield);
|
||||
unsigned value = defaultValue;
|
||||
unsigned value = static_cast<unsigned>(defaultValue);
|
||||
ss >> value;
|
||||
return (int)value;
|
||||
if (ss)
|
||||
return static_cast<int>(value);
|
||||
// Compatibility for options migrated from Option<bool> to Option<int>.
|
||||
std::string token = *pValue;
|
||||
string_tolower(token);
|
||||
if (token == "yes" || token == "true" || token == "on")
|
||||
return 1;
|
||||
if (token == "no" || token == "false" || token == "off")
|
||||
return 0;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
int64_t IniFile::getInt64(const std::string& section, const std::string& entry, int64_t defaultValue) const
|
||||
@@ -217,9 +228,9 @@ void IniFile::load(const std::string& data, bool cEscape)
|
||||
}
|
||||
std::string value = line.substr(pos + 1);
|
||||
// Space characters before and after the equal sign are for readability, and not part of the entry name or value
|
||||
if (value[0] == ' ')
|
||||
if (!value.empty() && value[0] == ' ')
|
||||
value = value.substr(1);
|
||||
if (value[0] == '"')
|
||||
if (!value.empty() && value[0] == '"')
|
||||
{
|
||||
// quoted value
|
||||
size_t end = value.rfind('"');
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <locale>
|
||||
#include <sstream>
|
||||
|
||||
+17
-1
@@ -77,11 +77,12 @@ Option<int> TextureUpscale("rend.TextureUpscale2", 1);
|
||||
Option<int> MaxFilteredTextureSize("rend.MaxFilteredTextureSize", 256);
|
||||
Option<float> ExtraDepthScale("rend.ExtraDepthScale", 1.f);
|
||||
Option<bool> CustomTextures("rend.CustomTextures");
|
||||
Option<bool> PreloadCustomTextures("rend.PreloadCustomTextures");
|
||||
Option<int> PreloadCustomTextures("rend.PreloadCustomTextures", 0);
|
||||
Option<bool> DumpTextures("rend.DumpTextures");
|
||||
Option<bool> DumpUniqueTextures("rend.DumpUniqueTextures");
|
||||
Option<bool> DumpReplacedTextures("rend.DumpReplacedTextures");
|
||||
Option<int> ScreenStretching("rend.ScreenStretching", 100);
|
||||
Option<bool> StretchToFill("rend.StretchToFill");
|
||||
Option<bool> Fog("rend.Fog", true);
|
||||
Option<bool> FloatVMUs("rend.FloatVMUs");
|
||||
Option<bool> Rotate90("rend.Rotate90");
|
||||
@@ -121,6 +122,20 @@ Option<bool> EmulateFramebuffer("rend.EmulateFramebuffer", false);
|
||||
Option<bool> FixUpscaleBleedingEdge("rend.FixUpscaleBleedingEdge", true);
|
||||
Option<bool> CustomGpuDriver("rend.CustomGpuDriver", false);
|
||||
Option<bool> FramePacing("rend.FramePacing", true);
|
||||
|
||||
CustomTexturePreloadMode customTexturePreloadMode()
|
||||
{
|
||||
const auto mode = static_cast<CustomTexturePreloadMode>(PreloadCustomTextures.get());
|
||||
switch (mode)
|
||||
{
|
||||
case CustomTexturePreloadMode::Off:
|
||||
case CustomTexturePreloadMode::SystemMemory:
|
||||
case CustomTexturePreloadMode::VideoMemory:
|
||||
return mode;
|
||||
default:
|
||||
return CustomTexturePreloadMode::Off;
|
||||
}
|
||||
}
|
||||
#ifdef VIDEO_ROUTING
|
||||
Option<bool, false> VideoRouting("rend.VideoRouting", false);
|
||||
Option<bool, false> VideoRoutingScale("rend.VideoRoutingScale", false);
|
||||
@@ -244,6 +259,7 @@ Option<std::string, false> LuaFileName("LuaFileName", "flycast.lua");
|
||||
// RetroAchievements
|
||||
|
||||
Option<bool> EnableAchievements("Enabled", false, "achievements");
|
||||
Option<bool> ShowAchievementPopups("Show", true, "achievements");
|
||||
Option<bool> AchievementsHardcoreMode("HardcoreMode", false, "achievements");
|
||||
OptionString AchievementsUserName("UserName", "", "achievements");
|
||||
OptionString AchievementsToken("Token", "", "achievements");
|
||||
|
||||
+15
-6
@@ -17,16 +17,16 @@
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <type_traits>
|
||||
#include "cfg.h"
|
||||
#include "hw/maple/maple_cfg.h"
|
||||
#ifdef LIBRETRO
|
||||
#include <libretro.h>
|
||||
#endif
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace config {
|
||||
|
||||
@@ -447,11 +447,19 @@ extern Option<int> PerPixelLayers;
|
||||
#endif
|
||||
extern Option<float> ExtraDepthScale;
|
||||
extern Option<bool> CustomTextures;
|
||||
extern Option<bool> PreloadCustomTextures;
|
||||
enum class CustomTexturePreloadMode : int
|
||||
{
|
||||
Off = 0,
|
||||
SystemMemory = 1,
|
||||
VideoMemory = 2,
|
||||
};
|
||||
extern Option<int> PreloadCustomTextures;
|
||||
CustomTexturePreloadMode customTexturePreloadMode();
|
||||
extern Option<bool> DumpTextures;
|
||||
extern Option<bool> DumpUniqueTextures;
|
||||
extern Option<bool> DumpReplacedTextures;
|
||||
extern Option<int> ScreenStretching; // in percent. 150 means stretch from 4/3 to 6/3
|
||||
extern Option<bool> StretchToFill;
|
||||
extern Option<bool> Fog;
|
||||
extern Option<bool> FloatVMUs;
|
||||
extern Option<bool> Rotate90;
|
||||
@@ -571,6 +579,7 @@ extern Option<std::string, false> LuaFileName;
|
||||
// RetroAchievements
|
||||
|
||||
extern Option<bool> EnableAchievements;
|
||||
extern Option<bool> ShowAchievementPopups;
|
||||
extern Option<bool> AchievementsHardcoreMode;
|
||||
extern OptionString AchievementsUserName;
|
||||
extern OptionString AchievementsToken;
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
#include "emulator.h"
|
||||
#include "oslib/storage.h"
|
||||
#include "oslib/i18n.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace i18n;
|
||||
|
||||
const WidescreenCheat CheatManager::widescreen_cheats[] =
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "hw/sh4/dyna/shil.h"
|
||||
#include "cfg/option.h"
|
||||
#include <array>
|
||||
#include <signal.h>
|
||||
#include <csignal>
|
||||
#include <map>
|
||||
|
||||
#ifndef SIGTRAP
|
||||
|
||||
+1
-1
Submodule core/deps/asio updated: d3402006e8...3321a1a753.
Submodule
+1
Submodule core/deps/basis_universal added at 9bebe16726.
+1
-1
Submodule core/deps/breakpad updated: 1ab24bcc81...6f3c4af788.
@@ -4,11 +4,6 @@
|
||||
#include <errno.h>
|
||||
#include "cdipsr.h"
|
||||
|
||||
// Global variables
|
||||
|
||||
unsigned long temp_value;
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
unsigned long ask_type(hostfs::File *fsource, long header_position)
|
||||
@@ -16,6 +11,7 @@ unsigned long ask_type(hostfs::File *fsource, long header_position)
|
||||
|
||||
unsigned char filename_length;
|
||||
unsigned long track_mode;
|
||||
uint32_t temp_value = 0;
|
||||
|
||||
fsource->seek(header_position, SEEK_SET);
|
||||
fsource->read(&temp_value, 4, 1);
|
||||
@@ -43,6 +39,7 @@ bool CDI_read_track (hostfs::File *fsource, image_s *image, track_s *track)
|
||||
|
||||
unsigned char TRACK_START_MARK[10] = { 0, 0, 0x01, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
unsigned char current_start_mark[10];
|
||||
uint32_t temp_value = 0;
|
||||
|
||||
fsource->read(&temp_value, 4, 1);
|
||||
if (temp_value != 0)
|
||||
|
||||
@@ -13,6 +13,15 @@
|
||||
static const int UDP_HEADER_SIZE = 28; /* Size of IP + UDP headers */
|
||||
static const int NUM_SYNC_PACKETS = 5;
|
||||
static const int SYNC_RETRY_INTERVAL = 2000;
|
||||
|
||||
static bool IsValidAppData(const UdpMsg *msg, int len)
|
||||
{
|
||||
const int header_size = (const uint8 *)msg->u.app_data.data - (const uint8 *)msg;
|
||||
if (len < header_size)
|
||||
return false;
|
||||
return msg->u.app_data.size <= MAX_APPDATA_SIZE
|
||||
&& msg->u.app_data.size <= len - header_size;
|
||||
}
|
||||
static const int SYNC_FIRST_RETRY_INTERVAL = 500;
|
||||
static const int RUNNING_RETRY_INTERVAL = 200;
|
||||
static const int KEEP_ALIVE_INTERVAL = 200;
|
||||
@@ -320,6 +329,13 @@ UdpProtocol::HandlesMsg(sockaddr_in &from,
|
||||
void
|
||||
UdpProtocol::OnMsg(UdpMsg *msg, int len)
|
||||
{
|
||||
if (len < (int)sizeof(msg->hdr)) {
|
||||
return;
|
||||
}
|
||||
if (msg->hdr.type == UdpMsg::AppData && !IsValidAppData(msg, len)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool handled = false;
|
||||
typedef bool (UdpProtocol::*DispatchFn)(UdpMsg *msg, int len);
|
||||
static const DispatchFn table[] = {
|
||||
@@ -791,6 +807,8 @@ UdpProtocol::ClearSendQueue()
|
||||
|
||||
void UdpProtocol::SendAppData(const void *data, int len, bool spectators)
|
||||
{
|
||||
if (len < 0 || len > MAX_APPDATA_SIZE || (data == nullptr && len != 0))
|
||||
return;
|
||||
if (_udp == nullptr)
|
||||
return;
|
||||
if (_current_state != Synchronzied && _current_state != Running)
|
||||
@@ -806,6 +824,8 @@ void UdpProtocol::SendAppData(const void *data, int len, bool spectators)
|
||||
|
||||
bool UdpProtocol::OnAppData(UdpMsg *msg, int len)
|
||||
{
|
||||
if (!IsValidAppData(msg, len))
|
||||
return false;
|
||||
UdpProtocol::Event evt(UdpProtocol::Event::AppData);
|
||||
evt.u.app_data.spectators = msg->u.app_data.spectators != 0;
|
||||
evt.u.app_data.size = msg->u.app_data.size;
|
||||
|
||||
@@ -278,15 +278,9 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid
|
||||
if (bd->HasBindSampler && glBindSampler != NULL)
|
||||
glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 may set that otherwise.
|
||||
#endif
|
||||
vertex_array_object = 0;
|
||||
#ifndef GLES2
|
||||
if (GLGraphicsContext::Instance()->getMajorVersion() >= 3)
|
||||
{
|
||||
// Recreate the VAO every time
|
||||
// (This is to easily allow multiple GL contexts. VAO are not shared among GL contexts, and we don't track creation/deletion of windows so we don't have an obvious key to use to cache them.)
|
||||
glGenVertexArrays(1, &vertex_array_object);
|
||||
glBindVertexArray(vertex_array_object);
|
||||
}
|
||||
#endif
|
||||
glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle);
|
||||
@@ -309,8 +303,6 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
|
||||
if (fb_width <= 0 || fb_height <= 0)
|
||||
return;
|
||||
|
||||
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
|
||||
|
||||
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
|
||||
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
|
||||
if (draw_data->Textures != nullptr)
|
||||
@@ -325,6 +317,10 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
|
||||
// Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts)
|
||||
// The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound.
|
||||
GLuint vertex_array_object = 0;
|
||||
#ifndef GLES2
|
||||
if (GLGraphicsContext::Instance()->getMajorVersion() >= 3)
|
||||
glGenVertexArrays(1, &vertex_array_object);
|
||||
#endif
|
||||
ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
|
||||
|
||||
// Will project scissor/clipping rectangles into framebuffer space
|
||||
@@ -657,6 +653,10 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects()
|
||||
void ImGui_ImplOpenGL3_DestroyDeviceObjects()
|
||||
{
|
||||
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
|
||||
if (bd == nullptr) {
|
||||
WARN_LOG(RENDERER, "ImGui_ImplOpenGL3_DestroyDeviceObjects: backend data is null");
|
||||
return;
|
||||
}
|
||||
if (bd->VboHandle) { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; }
|
||||
if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; }
|
||||
if (bd->ShaderHandle) { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; }
|
||||
|
||||
+1
-1
Submodule core/deps/libjuice updated: 77daa8befd...8f0918a716.
@@ -3008,7 +3008,10 @@ static bool glsm_state_ctx_init(glsm_ctx_params_t *params)
|
||||
return false;
|
||||
|
||||
#ifdef HAVE_OPENGLES
|
||||
hw_render.context_type = RETRO_HW_CONTEXT_OPENGLES2;
|
||||
hw_render.context_type = params->context_type != RETRO_HW_CONTEXT_NONE
|
||||
? params->context_type : RETRO_HW_CONTEXT_OPENGLES2;
|
||||
hw_render.version_major = params->major;
|
||||
hw_render.version_minor = params->minor;
|
||||
#else
|
||||
hw_render.context_type = RETRO_HW_CONTEXT_OPENGL;
|
||||
if (params->context_type != RETRO_HW_CONTEXT_NONE)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
*********************************************************************/
|
||||
#define MAX_BLOCK_SIZE 1600
|
||||
#define MAX_BLOCK_COUNT 16
|
||||
#define MAX_BLOCK_COUNT 1024 /* 16 blocks (~1,000 timers) ran out with a few dozen connections a second */
|
||||
|
||||
#define DECLARE_HEAP(type, orderby) \
|
||||
struct heap_ ## type { \
|
||||
|
||||
@@ -69,6 +69,8 @@ static void debug_q(struct pico_queue *q)
|
||||
|
||||
static inline int32_t pico_enqueue(struct pico_queue *q, struct pico_frame *p)
|
||||
{
|
||||
uint32_t size;
|
||||
|
||||
if ((q->max_frames) && (q->max_frames <= q->frames))
|
||||
return -1;
|
||||
|
||||
@@ -98,18 +100,19 @@ static inline int32_t pico_enqueue(struct pico_queue *q, struct pico_frame *p)
|
||||
q->size += p->buffer_len + q->overhead;
|
||||
q->frames++;
|
||||
debug_q(q);
|
||||
/* Read under the lock: once unlocked, another thread may dequeue (and free) the frame,
|
||||
* and a size of 0 would make the caller free it again. */
|
||||
size = q->size;
|
||||
|
||||
if (q->shared)
|
||||
PICOTCP_MUTEX_UNLOCK(q->mutex);
|
||||
|
||||
return (int32_t)q->size;
|
||||
return (int32_t)size;
|
||||
}
|
||||
|
||||
static inline struct pico_frame *pico_dequeue(struct pico_queue *q)
|
||||
{
|
||||
struct pico_frame *p = q->head;
|
||||
if (!p)
|
||||
return NULL;
|
||||
struct pico_frame *p;
|
||||
|
||||
if (q->frames < 1)
|
||||
return NULL;
|
||||
@@ -117,15 +120,19 @@ static inline struct pico_frame *pico_dequeue(struct pico_queue *q)
|
||||
if (q->shared)
|
||||
PICOTCP_MUTEX_LOCK(q->mutex);
|
||||
|
||||
q->head = p->next;
|
||||
q->frames--;
|
||||
q->size -= p->buffer_len - q->overhead;
|
||||
if (q->head == NULL)
|
||||
q->tail = NULL;
|
||||
p = q->head;
|
||||
if (p)
|
||||
{
|
||||
q->head = p->next;
|
||||
q->frames--;
|
||||
q->size -= p->buffer_len - q->overhead;
|
||||
if (q->head == NULL)
|
||||
q->tail = NULL;
|
||||
|
||||
debug_q(q);
|
||||
debug_q(q);
|
||||
|
||||
p->next = NULL;
|
||||
p->next = NULL;
|
||||
}
|
||||
if (q->shared)
|
||||
PICOTCP_MUTEX_UNLOCK(q->mutex);
|
||||
|
||||
@@ -160,6 +167,11 @@ static inline void pico_queue_empty(struct pico_queue *q)
|
||||
|
||||
static inline void pico_queue_protect(struct pico_queue *q)
|
||||
{
|
||||
#ifdef PICO_SUPPORT_MUTEX
|
||||
/* Create the mutex now: creating it lazily on first use races between threads */
|
||||
if (q->mutex == NULL)
|
||||
q->mutex = pico_mutex_init();
|
||||
#endif
|
||||
q->shared = 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1710,7 +1710,12 @@ int pico_socket_connect(struct pico_socket *s, const void *remote_addr, uint16_t
|
||||
return -1;
|
||||
}
|
||||
|
||||
pico_socket_alter_state(s, PICO_SOCKET_STATE_BOUND, 0, 0);
|
||||
/* A socket with the same quad is already in the tree: carrying on would leave this one
|
||||
* untracked, and deleting it later would unlink (and free) the other one. */
|
||||
if (pico_socket_alter_state(s, PICO_SOCKET_STATE_BOUND, 0, 0) != 0) {
|
||||
pico_err = PICO_ERR_EADDRINUSE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef PICO_SUPPORT_UDP
|
||||
if (PROTO(s) == PICO_PROTO_UDP) {
|
||||
|
||||
+43
-16
@@ -25,6 +25,7 @@
|
||||
#include "hw/naomi/naomi_cart.h"
|
||||
#include "reios/reios.h"
|
||||
#include "hw/sh4/modules/mmu.h"
|
||||
#include "hw/sh4/modules/modules.h"
|
||||
#include "hw/sh4/sh4_if.h"
|
||||
#include "hw/sh4/sh4_mem.h"
|
||||
#include "hw/sh4/sh4_sched.h"
|
||||
@@ -42,15 +43,19 @@
|
||||
#include "hw/pvr/pvr.h"
|
||||
#include "profiler/fc_profiler.h"
|
||||
#include "oslib/storage.h"
|
||||
#include "wsi/context.h"
|
||||
#include <chrono>
|
||||
#ifndef LIBRETRO
|
||||
#include "ui/gui.h"
|
||||
#endif
|
||||
#include "hw/sh4/sh4_interpreter.h"
|
||||
#include "hw/sh4/dyna/ngen.h"
|
||||
#include "oslib/i18n.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
settings_t settings;
|
||||
constexpr char const *BIOS_TITLE = "Dreamcast BIOS";
|
||||
|
||||
@@ -258,7 +263,34 @@ static void loadSpecialSettings()
|
||||
|| prod_id == "T1208M" // Gigawing (JP)
|
||||
|| prod_id == "T1235M" // Vampire Chronicle for Matching Service
|
||||
|| prod_id == "T22901N" // Roadsters (US)
|
||||
|| prod_id == "T28202M"))// Shin Nihon Pro Wrestling 4
|
||||
|| prod_id == "T28202M" // Shin Nihon Pro Wrestling 4
|
||||
|| prod_id == "T9512N" // The Grinch (US)
|
||||
|| prod_id == "51034" // Generator Vol.1
|
||||
|| prod_id == "MK-51057" // Generator Vol.2
|
||||
|| prod_id == "T15128N" // Coaster Works
|
||||
|| prod_id == "T9505N" // ESPN-NBA 2 Night
|
||||
|| prod_id == "T-9703N" // NFL Blitz 2000
|
||||
|| prod_id == "T-8106N" // Shadow Man
|
||||
|| prod_id == "T15106N" // Slave Zero
|
||||
|| prod_id == "T40402N" // Tom Clancy's Rainbow Six - Rouge Spear
|
||||
|| prod_id == "T15125N" // Unreal Tournament
|
||||
|| prod_id == "6107140" // Dorimaga GD Vol.1
|
||||
|| prod_id == "T41202M" // Boku no tennis jinsei
|
||||
|| prod_id == "T9503M" // Eisei Meijin III
|
||||
|| prod_id == "T41201M" // Jet Coaster Dream
|
||||
|| prod_id == "T40902M" // Jet Coaster Dream 2
|
||||
|| prod_id == "T20105M" // Kanon
|
||||
|| prod_id == "T19702M" // Memories Off Complete
|
||||
|| prod_id == "T17001M" // Pen Pen Tri Iceron (JP)
|
||||
|| prod_id == "T1301M" // Revive - Sosei
|
||||
|| prod_id == "T15003M" // Seitai Heiki Expendable
|
||||
|| prod_id == "T38805M" // Sengoku Turb FID
|
||||
|| prod_id == "T20104M" // Sentimental Graffiti 2
|
||||
|| prod_id == "T16601M" // Shin Honkaku Hanafuda
|
||||
|| prod_id == "T1236M" // Super Street Fighter IIX For Matching Service
|
||||
|| prod_id == "T36501M" // Vigilante 8 (JP)
|
||||
|| prod_id == "T5301M" // World Neverland Plus - Olerud Kingdom Story
|
||||
|| prod_id == "HDR-0198")) // World Series Baseball 2K2 (JP)
|
||||
{
|
||||
NOTICE_LOG(BOOT, "Game doesn't support RGB. Using TV Composite instead");
|
||||
config::Cable.override(3);
|
||||
@@ -278,8 +310,7 @@ static void loadSpecialSettings()
|
||||
}
|
||||
else if (prod_id == "T17708N" // Stupid Invaders (US)
|
||||
|| prod_id == "T17711D" // Stupid Invaders (EU)
|
||||
|| prod_id == "T46509M" // Suika (JP)
|
||||
|| prod_id == "T36901M") // Cool Boarders Burrrn (JP)
|
||||
|| prod_id == "T46509M") // Suika (JP)
|
||||
{
|
||||
NOTICE_LOG(BOOT, "Forcing HLE BIOS");
|
||||
config::UseReios.override(true);
|
||||
@@ -396,7 +427,8 @@ static void loadSpecialSettings()
|
||||
}
|
||||
else if (settings.platform.isArcade())
|
||||
{
|
||||
if (prod_id == "COSMIC SMASH IN JAPAN")
|
||||
if (prod_id == "COSMIC SMASH IN JAPAN"
|
||||
|| prod_id == "MONKEY BALL JAPAN VERSION")
|
||||
{
|
||||
INFO_LOG(BOOT, "Enabling translucent depth multipass for game %s", prod_id.c_str());
|
||||
config::TranslucentPolygonDepthMask.override(true);
|
||||
@@ -553,7 +585,7 @@ int getGamePlatform(const std::string& filename)
|
||||
return DC_PLATFORM_DREAMCAST; // unknown
|
||||
if (extension == "zip" || extension == "7z")
|
||||
return naomi_cart_GetPlatform(filename.c_str());
|
||||
if (extension == "bin" || extension == "dat" || extension == "lst")
|
||||
if (extension == "bin" || extension == "dat")
|
||||
return DC_PLATFORM_NAOMI;
|
||||
|
||||
return DC_PLATFORM_DREAMCAST;
|
||||
@@ -865,7 +897,6 @@ void loadGameSpecificSettings()
|
||||
loadSpecialSettings();
|
||||
|
||||
config::Settings::instance().setGameId(settings.content.gameId);
|
||||
custom_texture.init();
|
||||
|
||||
// Reload per-game settings
|
||||
config::Settings::instance().load(true);
|
||||
@@ -897,11 +928,8 @@ void Emulator::stepRange(u32 from, u32 to)
|
||||
|
||||
void Emulator::loadstate(Deserializer& deser)
|
||||
{
|
||||
if (!custom_texture.preloaded())
|
||||
{
|
||||
custom_texture.terminate();
|
||||
custom_texture.init();
|
||||
}
|
||||
if (config::customTexturePreloadMode() == config::CustomTexturePreloadMode::Off)
|
||||
custom_texture.refresh();
|
||||
#if FEAT_AREC == DYNAREC_JIT
|
||||
aica::arm::recompiler::flush();
|
||||
#endif
|
||||
@@ -1123,7 +1151,6 @@ void Emulator::diskChange()
|
||||
{
|
||||
config::Settings::instance().reset();
|
||||
config::Settings::instance().load(false);
|
||||
custom_texture.terminate();
|
||||
if (!settings.content.path.empty())
|
||||
{
|
||||
hostfs::FileInfo info = hostfs::storage().getFileInfo(settings.content.path);
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <time.h>
|
||||
#include <ctime>
|
||||
|
||||
void loadGameSpecificSettings();
|
||||
void SaveSettings();
|
||||
|
||||
@@ -402,7 +402,8 @@ static void Write_SB_ADST(u32 addr, u32 data)
|
||||
SB_ADSUSP &= ~0x10;
|
||||
|
||||
// Schedule the end of DMA transfer interrupt
|
||||
int cycles = len * (SH4_MAIN_CLOCK / 2 / G2_BUS_CLOCK); // 16 bits @ 25 MHz
|
||||
// theoretical: 16 bits @ 25 MHz, actual: 11.3 MiB/s, here: 15.9 MiB/s
|
||||
int cycles = len * (SH4_MAIN_CLOCK / 2 / G2_BUS_CLOCK) * 3;
|
||||
if (cycles <= 512)
|
||||
dma_end_sched(0, 0, 0, nullptr);
|
||||
else
|
||||
@@ -544,6 +545,9 @@ void serialize(Serializer& ser)
|
||||
ser << aica_reg;
|
||||
|
||||
sgc::serialize(ser);
|
||||
sh4_sched_serialize(ser, aica_schid);
|
||||
sh4_sched_serialize(ser, rtc_schid);
|
||||
sh4_sched_serialize(ser, dma_sched_id);
|
||||
}
|
||||
|
||||
void deserialize(Deserializer& deser)
|
||||
@@ -559,10 +563,7 @@ void deserialize(Deserializer& deser)
|
||||
deser >> arm::armFiqEnable;
|
||||
deser >> arm::armMode;
|
||||
deser >> arm::Arm7Enabled;
|
||||
if (deser.version() >= Deserializer::V19)
|
||||
deser >> arm::arm7ClockTicks;
|
||||
else
|
||||
arm::arm7ClockTicks = 0;
|
||||
deser >> arm::arm7ClockTicks;
|
||||
|
||||
dsp::state.deserialize(deser);
|
||||
|
||||
@@ -573,11 +574,7 @@ void deserialize(Deserializer& deser)
|
||||
}
|
||||
|
||||
if (!deser.rollback())
|
||||
{
|
||||
aica_ram.deserialize(deser);
|
||||
if (settings.platform.isAtomiswave())
|
||||
deser.skip(6_MB, Deserializer::V30);
|
||||
}
|
||||
deser >> VREG;
|
||||
deser >> ARMRST;
|
||||
deser >> rtc_EN;
|
||||
@@ -586,6 +583,11 @@ void deserialize(Deserializer& deser)
|
||||
deser >> aica_reg;
|
||||
|
||||
sgc::deserialize(deser);
|
||||
if (deser.version() >= Deserializer::V62) {
|
||||
sh4_sched_deserialize(deser, aica_schid);
|
||||
sh4_sched_deserialize(deser, rtc_schid);
|
||||
sh4_sched_deserialize(deser, dma_sched_id);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace aica
|
||||
|
||||
@@ -6,6 +6,9 @@ namespace aica
|
||||
{
|
||||
|
||||
extern RamRegion aica_ram;
|
||||
extern int aica_schid;
|
||||
extern int rtc_schid;
|
||||
extern int dma_sched_id;
|
||||
u32 GetRTC_now();
|
||||
template<typename T> T readRtcReg(u32 addr);
|
||||
template<typename T> void writeRtcReg(u32 addr, T data);
|
||||
|
||||
@@ -41,16 +41,12 @@ struct DSPState
|
||||
|
||||
void deserialize(Deserializer& deser)
|
||||
{
|
||||
deser.skip(4096 * 8, Deserializer::V18); // DynCode
|
||||
deser >> TEMP;
|
||||
deser >> MEMS;
|
||||
deser >> MIXS;
|
||||
deser >> RBP;
|
||||
deser >> RBL;
|
||||
deser.skip(44, Deserializer::V18);
|
||||
deser >> MDEC_CT;
|
||||
deser.skip(33596 - 4096 * 8 - sizeof(TEMP) - sizeof(MEMS) - sizeof(MIXS) - 4 * 3 - 44,
|
||||
Deserializer::V18); // other dsp stuff
|
||||
if (!deser.rollback())
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
+27
-35
@@ -95,15 +95,14 @@ static const char* stream_names[]=
|
||||
};
|
||||
|
||||
//x.8 format
|
||||
static const s32 adpcm_qs[8] =
|
||||
static constexpr s32 adpcm_qs[8] =
|
||||
{
|
||||
0x0e6, 0x0e6, 0x0e6, 0x0e6, 0x133, 0x199, 0x200, 0x266,
|
||||
};
|
||||
//x.3 format
|
||||
static const s32 adpcm_scale[16] =
|
||||
static constexpr s32 adpcm_scale[8] =
|
||||
{
|
||||
1,3,5,7,9,11,13,15,
|
||||
-1,-3,-5,-7,-9,-11,-13,-15,
|
||||
1, 3, 5, 7, 9, 11, 13, 15,
|
||||
};
|
||||
|
||||
static const s32 qtable[32] = {
|
||||
@@ -203,12 +202,9 @@ public:
|
||||
{
|
||||
if (deser.version() < Deserializer::V49)
|
||||
{
|
||||
if (deser.version() >= Deserializer::V22)
|
||||
{
|
||||
deser.skip<int>(); // beepOn
|
||||
deser.skip<int>(); // beepPeriod
|
||||
deser.skip<int>(); // beepCounter
|
||||
}
|
||||
deser.skip<int>(); // beepOn
|
||||
deser.skip<int>(); // beepPeriod
|
||||
deser.skip<int>(); // beepCounter
|
||||
init();
|
||||
}
|
||||
else
|
||||
@@ -431,6 +427,7 @@ struct ChannelEx
|
||||
{
|
||||
u32 LSA;
|
||||
u32 LEA;
|
||||
u32 alignedLEA;
|
||||
|
||||
u8 looped;
|
||||
} loop;
|
||||
@@ -800,6 +797,8 @@ struct ChannelEx
|
||||
{
|
||||
loop.LSA = ccd->LSA;
|
||||
loop.LEA = ccd->LEA;
|
||||
// ADPCM long stream needs 4-sample alignment for LEA
|
||||
loop.alignedLEA = (loop.LEA + 3) & ~3;
|
||||
}
|
||||
|
||||
u32 EG_EffRate(u32 rate)
|
||||
@@ -1062,11 +1061,13 @@ static T readAicaRam(u32 addr) {
|
||||
template<PCMSType PCMS, bool Last>
|
||||
void ChannelEx::StepDecodeSample(u32 CA)
|
||||
{
|
||||
// Only ADPCM needs all intermediate samples to be decoded
|
||||
if constexpr (!Last && (PCMS == PCM16 || PCMS == PCM8 || PCMS == NOISE))
|
||||
return;
|
||||
|
||||
u32 next_addr = CA + 1;
|
||||
if (next_addr >= loop.LEA && loop.LEA > loop.LSA)
|
||||
const u32 lea = PCMS == ADPCM_STREAM ? loop.alignedLEA : loop.LEA;
|
||||
if (next_addr >= lea && lea > loop.LSA)
|
||||
next_addr = loop.LSA;
|
||||
|
||||
SampleType s0, s1;
|
||||
@@ -1095,16 +1096,11 @@ void ChannelEx::StepDecodeSample(u32 CA)
|
||||
case ADPCM_STREAM:
|
||||
{
|
||||
u8 ad1 = readAicaRam<u8>(SA + (CA >> 1));
|
||||
u8 ad2 = readAicaRam<u8>(SA + (next_addr >> 1));
|
||||
|
||||
ad1 >>= (CA & 1) * 4;
|
||||
ad2 >>= (next_addr & 1) * 4;
|
||||
|
||||
ad1 &= 0xF;
|
||||
ad2 &= 0xF;
|
||||
|
||||
s32 q = adpcm.last_quant;
|
||||
if (PCMS == ADPCM && CA == loop.LSA)
|
||||
if (CA == loop.LSA)
|
||||
{
|
||||
if (!adpcm.in_loop)
|
||||
{
|
||||
@@ -1112,7 +1108,7 @@ void ChannelEx::StepDecodeSample(u32 CA)
|
||||
adpcm.loopstart_quant = q;
|
||||
adpcm.loopstart_prev_sample = this->s0;
|
||||
}
|
||||
else
|
||||
else if (PCMS == ADPCM)
|
||||
{
|
||||
q = adpcm.loopstart_quant;
|
||||
this->s0 = adpcm.loopstart_prev_sample;
|
||||
@@ -1122,6 +1118,9 @@ void ChannelEx::StepDecodeSample(u32 CA)
|
||||
adpcm.last_quant = q;
|
||||
if constexpr (Last)
|
||||
{
|
||||
u8 ad2 = readAicaRam<u8>(SA + (next_addr >> 1));
|
||||
ad2 >>= (next_addr & 1) * 4;
|
||||
ad2 &= 0xF;
|
||||
SampleType prev = s0;
|
||||
if (PCMS == ADPCM && next_addr == loop.LSA && adpcm.in_loop)
|
||||
{
|
||||
@@ -1153,11 +1152,7 @@ void ChannelEx::StreamStep()
|
||||
sp.ip--;
|
||||
|
||||
u32 CA = this->CA + 1;
|
||||
|
||||
u32 ca_t = CA;
|
||||
if constexpr (PCMS == ADPCM_STREAM)
|
||||
ca_t &= ~3; // in adpcm "stream" mode, LEA and LSA are supposed to be 4-sample aligned
|
||||
// but some games don't respect this rule
|
||||
const u32 lea = PCMS == ADPCM_STREAM ? loop.alignedLEA : loop.LEA;
|
||||
|
||||
if constexpr (LPSLNK)
|
||||
{
|
||||
@@ -1168,13 +1163,13 @@ void ChannelEx::StreamStep()
|
||||
}
|
||||
}
|
||||
|
||||
if (ca_t >= loop.LEA)
|
||||
if (CA >= lea)
|
||||
{
|
||||
if (loop.LSA > loop.LEA)
|
||||
if (loop.LSA > lea)
|
||||
{
|
||||
// When LSA > LEA, aica won't stop when reaching LEA but will continue until LSA.
|
||||
// It will then reset CA to 0 and stop playing, even for looping sounds.
|
||||
if (ca_t >= loop.LSA)
|
||||
if (CA >= loop.LSA)
|
||||
{
|
||||
loop.looped = 1;
|
||||
CA = 0;
|
||||
@@ -1721,16 +1716,13 @@ void deserialize(Deserializer& deser)
|
||||
deser >> cdda_sector;
|
||||
deser >> cdda_index;
|
||||
midiSendBuffer.clear();
|
||||
if (deser.version() >= Deserializer::V28)
|
||||
u32 size;
|
||||
deser >> size;
|
||||
for (u32 i = 0; i < size; i++)
|
||||
{
|
||||
u32 size;
|
||||
deser >> size;
|
||||
for (u32 i = 0; i < size; i++)
|
||||
{
|
||||
u8 b;
|
||||
deser >> b;
|
||||
midiSendBuffer.push_back(b);
|
||||
}
|
||||
u8 b;
|
||||
deser >> b;
|
||||
midiSendBuffer.push_back(b);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -427,32 +427,11 @@ class Arm7Compiler : public MacroAssembler
|
||||
Add(rd, w0, op2);
|
||||
break;
|
||||
case 8: // TST
|
||||
#ifdef STRICT_MODE
|
||||
// In armv7, TST and TEQ do not affect the V flag.
|
||||
// This isn't the case in armv8 so we need to save
|
||||
// and restore it.
|
||||
// Since this is a bit complicated/slow, let's assume nobody
|
||||
// relies on this.
|
||||
Cset(w3, vs);
|
||||
#endif
|
||||
Tst(rn, op2);
|
||||
#ifdef STRICT_MODE
|
||||
Mrs(x0, NZCV);
|
||||
Bfi(x0, x3, 28, 1); // V is bit 28
|
||||
Msr(NZCV, x0);
|
||||
#endif
|
||||
break;
|
||||
case 9: // TEQ
|
||||
Eor(w0, rn, op2);
|
||||
#ifdef STRICT_MODE
|
||||
Cset(w3, vs);
|
||||
#endif
|
||||
Tst(w0, w0);
|
||||
#ifdef STRICT_MODE
|
||||
Mrs(x0, NZCV);
|
||||
Bfi(x0, x3, 28, 1); // V is bit 28
|
||||
Msr(NZCV, x0);
|
||||
#endif
|
||||
break;
|
||||
case 10: // CMP
|
||||
Cmp(rn, op2);
|
||||
@@ -474,10 +453,21 @@ class Arm7Compiler : public MacroAssembler
|
||||
die("invalid op");
|
||||
break;
|
||||
}
|
||||
if (set_carry_bit)
|
||||
if (set_carry_bit || logical_op_set_flags)
|
||||
{
|
||||
Mrs(x0, NZCV);
|
||||
Bfi(x0, x14, 29, 1); // C is bit 29 in NZCV
|
||||
if (set_carry_bit)
|
||||
Bfi(x0, x14, 29, 1); // C is bit 29 in NZCV
|
||||
if (logical_op_set_flags)
|
||||
{
|
||||
// ARMv7 logical ops (AND, EOR, ORR, BIC, MOV, MVN, TST, TEQ) leave V alone.
|
||||
// The AArch64 flag-setting equivalents (ANDS, BICS, TST) clear it, so put
|
||||
// back the V the op started with. loadFlags() ran before the op, so the
|
||||
// saved PSR still holds it.
|
||||
Ldr(w3, arm_reg_operand(RN_PSR_FLAGS));
|
||||
Ubfx(w3, w3, 28, 1); // w3 = V
|
||||
Bfi(x0, x3, 28, 1); // V is bit 28 in NZCV
|
||||
}
|
||||
Msr(NZCV, x0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ using namespace Xbyak::util;
|
||||
#include "arm7_rec.h"
|
||||
#include "oslib/unwind_info.h"
|
||||
#include "oslib/virtmem.h"
|
||||
#include "stdclass.h"
|
||||
|
||||
namespace aica::arm
|
||||
{
|
||||
@@ -45,11 +46,11 @@ static UnwindInfo unwinder;
|
||||
class Arm7Compiler;
|
||||
|
||||
#ifdef _WIN32
|
||||
static const std::array<Xbyak::Reg32, 8> alloc_regs {
|
||||
static constexpr std::array<Xbyak::Reg32, 8> alloc_regs {
|
||||
ebx, ebp, edi, esi, r12d, r13d, r14d, r15d
|
||||
};
|
||||
#else
|
||||
static const std::array<Xbyak::Reg32, 6> alloc_regs {
|
||||
static constexpr std::array<Xbyak::Reg32, 6> alloc_regs {
|
||||
ebx, ebp, r12d, r13d, r14d, r15d
|
||||
};
|
||||
#endif
|
||||
@@ -1003,8 +1004,13 @@ void arm7backend_compile(const std::vector<ArmOp>& block_ops, u32 cycles)
|
||||
virtmem::jit_set_exec(protStart, protSize, false);
|
||||
|
||||
Arm7Compiler assembler;
|
||||
assembler.compile(block_ops, cycles);
|
||||
|
||||
try {
|
||||
assembler.compile(block_ops, cycles);
|
||||
} catch (const Xbyak::Error& e) {
|
||||
virtmem::jit_set_exec(protStart, protSize, true);
|
||||
ERROR_LOG(AICA_ARM, "Xbyak error: %s", e.what());
|
||||
throw FlycastException(strprintf("ARM recompilation error: %s", e.what()));
|
||||
}
|
||||
virtmem::jit_set_exec(protStart, protSize, true);
|
||||
}
|
||||
|
||||
@@ -1016,8 +1022,13 @@ void arm7backend_flush()
|
||||
unwinder.clear();
|
||||
|
||||
Arm7Compiler assembler;
|
||||
assembler.generateMainLoop();
|
||||
|
||||
try {
|
||||
assembler.generateMainLoop();
|
||||
} catch (const Xbyak::Error& e) {
|
||||
virtmem::jit_set_exec(protStart, protSize, true);
|
||||
ERROR_LOG(AICA_ARM, "Xbyak error: %s", e.what());
|
||||
throw FlycastException(strprintf("ARM recompilation error: %s", e.what()));
|
||||
}
|
||||
virtmem::jit_set_exec(protStart, protSize, true);
|
||||
}
|
||||
|
||||
|
||||
+28
-11
@@ -21,6 +21,8 @@
|
||||
#include "hw/holly/holly_intc.h"
|
||||
#include "network/netservice.h"
|
||||
#include "serialize.h"
|
||||
#include "hw/sh4/sh4_sched.h"
|
||||
#include "cfg/option.h"
|
||||
|
||||
static RTL8139State *rtl8139device;
|
||||
|
||||
@@ -49,6 +51,24 @@ static u8 GAPS_ram[GAPSPCI_RAM_SIZE];
|
||||
static u8 GAPS_regs[GAPSPCI_REGS_SIZE];
|
||||
static u32 dmaOffset;
|
||||
static bool interruptPending;
|
||||
static constexpr u32 MAX_ETH_FRAME_SIZE = 1514;
|
||||
static int rxSchedId = -1;
|
||||
constexpr int RX_POLL_CYCLES = 5_sh4ms;
|
||||
|
||||
static int rxDeliver(int tag, int cycles, int jitter, void *arg)
|
||||
{
|
||||
u8 frame[MAX_ETH_FRAME_SIZE];
|
||||
while (rtl8139_can_receive(rtl8139device))
|
||||
{
|
||||
const u32 size = net::modbba::sendEthFrame(frame);
|
||||
if (size == 0)
|
||||
break;
|
||||
if (size < 6 || size > MAX_ETH_FRAME_SIZE)
|
||||
continue;
|
||||
rtl8139_receive(rtl8139device, frame, size);
|
||||
}
|
||||
return RX_POLL_CYCLES;
|
||||
}
|
||||
|
||||
static void setInterrupt()
|
||||
{
|
||||
@@ -64,7 +84,7 @@ void pci_set_irq(PCIDevice *pci_dev, int level)
|
||||
setInterrupt();
|
||||
}
|
||||
|
||||
void bba_Init()
|
||||
static void bba_Init()
|
||||
{
|
||||
NICConf nicConf = { 0xc, 0xa, 0xf, 0xe, 0, 0 };
|
||||
rtl8139device = rtl8139_init(&nicConf);
|
||||
@@ -73,6 +93,9 @@ void bba_Init()
|
||||
memset(&GAPS_regs[0], 0, sizeof(GAPS_regs));
|
||||
memcpy(&GAPS_regs[0], "GAPSPCI_BRIDGE_2", 0x10);
|
||||
memcpy(&GAPS_regs[0x1c], "SEGA", 4);
|
||||
|
||||
rxSchedId = sh4_sched_register(0, rxDeliver);
|
||||
sh4_sched_request(rxSchedId, RX_POLL_CYCLES);
|
||||
}
|
||||
|
||||
void bba_Term()
|
||||
@@ -83,6 +106,8 @@ void bba_Term()
|
||||
rtl8139_destroy(rtl8139device);
|
||||
rtl8139device = nullptr;
|
||||
}
|
||||
sh4_sched_unregister(rxSchedId);
|
||||
rxSchedId = -1;
|
||||
}
|
||||
|
||||
void bba_Reset(bool hard)
|
||||
@@ -90,7 +115,8 @@ void bba_Reset(bool hard)
|
||||
if (hard)
|
||||
{
|
||||
bba_Term();
|
||||
bba_Init();
|
||||
if (config::EmulateBBA && (settings.platform.isConsole() || settings.platform.isAtomiswave()))
|
||||
bba_Init();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,15 +239,6 @@ ssize_t qemu_send_packet(RTL8139State *s, const uint8_t *buf, int size)
|
||||
return size;
|
||||
}
|
||||
|
||||
int bba_recv_frame(const u8 *data, u32 len)
|
||||
{
|
||||
if (!rtl8139_can_receive(rtl8139device))
|
||||
return 0;
|
||||
rtl8139_receive(rtl8139device, data, len);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void pci_dma_read(PCIDevice *dev, dma_addr_t addr, void *buf, dma_addr_t len)
|
||||
{
|
||||
memcpy(buf, &GAPS_ram[addr & GAPSPCI_RAM_MASK], len);
|
||||
|
||||
@@ -19,11 +19,9 @@
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
|
||||
void bba_Init();
|
||||
void bba_Term();
|
||||
void bba_Reset(bool hard);
|
||||
u32 bba_ReadMem(u32 addr, u32 sz);
|
||||
void bba_WriteMem(u32 addr, u32 data, u32 sz);
|
||||
void bba_Serialize(Serializer& ser);
|
||||
void bba_Deserialize(Deserializer& deser);
|
||||
int bba_recv_frame(const u8 *data, u32 len);
|
||||
@@ -2411,6 +2411,25 @@ void rtl8139_serialize(RTL8139State *s, Serializer& ser)
|
||||
|
||||
bool rtl8139_deserialize(RTL8139State *s, Deserializer& deser)
|
||||
{
|
||||
if (s == nullptr)
|
||||
{
|
||||
deser.skip(256 * 3 + sizeof(PCIDevice::io_regions)
|
||||
+ 8 + 8 + 4 * 4 + 4 * 4
|
||||
+ 4 + 4 + 4 + 4
|
||||
+ 2 + 2
|
||||
+ 4 + 4 + 4 + 2
|
||||
+ 6
|
||||
+ 2
|
||||
+ 2
|
||||
+ 5 * 2
|
||||
+ sizeof(MACAddr)
|
||||
+ 4
|
||||
+ sizeof(EEprom9346::contents) + sizeof(EEprom9346::mode) + sizeof(EEprom9346::tick)
|
||||
+ sizeof(EEprom9346::address) + sizeof(EEprom9346::input) + sizeof(EEprom9346::output) + 4
|
||||
+ 4 + 4 + 8
|
||||
+ 12);
|
||||
return false;
|
||||
}
|
||||
deser.deserialize(s->parent_obj.config, 256);
|
||||
deser.deserialize(s->parent_obj.cmask, 256);
|
||||
deser.deserialize(s->parent_obj.wmask, 256);
|
||||
@@ -2472,8 +2491,7 @@ bool rtl8139_deserialize(RTL8139State *s, Deserializer& deser)
|
||||
deser >> s->TimerInt;
|
||||
deser >> s->TCTR_base;
|
||||
|
||||
if (deser.version() >= Deserializer::V38)
|
||||
sh4_sched_deserialize(deser, s->schedId);
|
||||
sh4_sched_deserialize(deser, s->schedId);
|
||||
|
||||
return s->bChipCmdState & CmdRxEnb;
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "oslib/oslib.h"
|
||||
#include "stdclass.h"
|
||||
#include "oslib/storage.h"
|
||||
#include "oslib/directory.h"
|
||||
|
||||
bool MemChip::Load(const std::string& file)
|
||||
{
|
||||
@@ -40,17 +41,8 @@ void MemChip::Load(const u8 *data, size_t size)
|
||||
memcpy(this->data + write_protect_size, data, this->size - write_protect_size);
|
||||
}
|
||||
|
||||
void WritableChip::Save(const std::string& file)
|
||||
{
|
||||
FILE *f = nowide::fopen(file.c_str(), "wb");
|
||||
if (f == nullptr)
|
||||
{
|
||||
ERROR_LOG(FLASHROM, "Cannot save flash/nvmem to file '%s'", file.c_str());
|
||||
return;
|
||||
}
|
||||
if (std::fwrite(data + write_protect_size, 1, size - write_protect_size, f) != size - write_protect_size)
|
||||
ERROR_LOG(FLASHROM, "Failed or truncated write to flash file '%s'", file.c_str());
|
||||
std::fclose(f);
|
||||
void WritableChip::Save(const std::string& file) {
|
||||
safeFullSave(file, data + write_protect_size, size - write_protect_size);
|
||||
}
|
||||
|
||||
bool MemChip::Load(const std::string &prefix, const std::string &names_ro, const std::string &title)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
#include <string.h>
|
||||
#include <cstring>
|
||||
|
||||
class X76F100SerialFlash
|
||||
{
|
||||
|
||||
+73
-66
@@ -15,7 +15,9 @@
|
||||
#include "imgread/common.h"
|
||||
#include "serialize.h"
|
||||
|
||||
int gdrom_schid;
|
||||
int gdrom_schid = -1;
|
||||
static int statusSchid = -1;
|
||||
static int nextStatus = -1;
|
||||
|
||||
//Sense: ASC - ASCQ - Key
|
||||
int sns_asc;
|
||||
@@ -32,15 +34,6 @@ cdda_t cdda;
|
||||
|
||||
static gd_states gd_state;
|
||||
static DiscType gd_disk_type;
|
||||
/*
|
||||
GD rom reset -> GDS_WAITCMD
|
||||
|
||||
GDS_WAITCMD -> ATA/SPI command [Command code is on ata_command]
|
||||
SPI Command -> GDS_WAITPACKET -> GDS_SPI_* , depending on input
|
||||
|
||||
GDS_SPI_READSECTOR -> Depending on features , it can do quite a few things
|
||||
*/
|
||||
static u32 data_write_mode=0;
|
||||
|
||||
//Registers
|
||||
static u32 DriveSel;
|
||||
@@ -154,6 +147,8 @@ void DmaBuffer::deserialize(Deserializer& deser)
|
||||
{
|
||||
index = 0;
|
||||
deser >> size;
|
||||
if (size > sizeof(cache))
|
||||
throw Deserializer::Exception("Invalid GD-ROM DMA buffer size");
|
||||
deser.deserialize(&cache[0], size);
|
||||
}
|
||||
}
|
||||
@@ -171,12 +166,25 @@ void PioBuffer::deserialize(Deserializer& deser)
|
||||
deser >> next_state;
|
||||
deser >> index;
|
||||
deser >> size;
|
||||
if (size > Capacity / sizeof(_data[0]) || index > size)
|
||||
throw Deserializer::Exception("Invalid GD-ROM PIO buffer state");
|
||||
if (deser.version() < Deserializer::V54)
|
||||
deser >> _data;
|
||||
else
|
||||
deser.deserialize(&_data[0], size);
|
||||
}
|
||||
|
||||
static void cddaTransition(int state, u64 duration = 20_sh4ms, int transientState = GD_BUSY)
|
||||
{
|
||||
SecNumber.Status = transientState;
|
||||
nextStatus = state;
|
||||
sh4_sched_request(statusSchid, duration);
|
||||
}
|
||||
|
||||
static void cddaCancelTransition() {
|
||||
sh4_sched_request(statusSchid, -1);
|
||||
}
|
||||
|
||||
static void gd_set_state(gd_states state)
|
||||
{
|
||||
gd_states prev=gd_state;
|
||||
@@ -257,6 +265,9 @@ static void gd_set_state(gd_states state)
|
||||
sector_count = maxSectors;
|
||||
next_state = gds_readsector_pio;
|
||||
}
|
||||
else {
|
||||
cddaTransition(GD_PAUSE, 67_sh4ms, GD_PLAY);
|
||||
}
|
||||
|
||||
u16 *buffer = pio_buff.fill(sector_count * read_params.sector_type);
|
||||
libGDR_ReadSector((u8*)buffer, read_params.start_sector, sector_count, read_params.sector_type);
|
||||
@@ -345,6 +356,7 @@ void gd_setdisc()
|
||||
|
||||
gd_disk_type = newd;
|
||||
SecNumber.DiscFormat = gd_disk_type >> 4;
|
||||
cddaCancelTransition();
|
||||
}
|
||||
|
||||
static void gd_reset()
|
||||
@@ -440,6 +452,7 @@ static void gd_process_ata_cmd()
|
||||
Error.full = 1;
|
||||
sns_key = 0;
|
||||
SecNumber.Status = GD_PAUSE;
|
||||
cddaCancelTransition();
|
||||
IntReason.full = 1;
|
||||
// DC Checker expects these values
|
||||
ByteCount.low = 0x14;
|
||||
@@ -749,7 +762,9 @@ static void gd_process_spi_cmd()
|
||||
{
|
||||
#define readcmd packet_cmd.GDReadBlock
|
||||
|
||||
SecNumber.Status = GD_PLAY;
|
||||
cdda.status = cdda_t::NoInfo;
|
||||
cddaCancelTransition();
|
||||
u32 sector_type = 2048;
|
||||
if (readcmd.head == 1 && readcmd.subh == 1 && readcmd.data == 1 && readcmd.expdtype == 3 && readcmd.other == 0)
|
||||
sector_type = 2340;
|
||||
@@ -911,7 +926,6 @@ static void gd_process_spi_cmd()
|
||||
case SPI_CD_PLAY:
|
||||
{
|
||||
const u32 param_type = packet_cmd.data_8[1] & 7;
|
||||
printf_spicmd("SPI_CD_PLAY param_type=%d", param_type);
|
||||
|
||||
if (param_type == 1 || param_type == 2)
|
||||
{
|
||||
@@ -927,30 +941,24 @@ static void gd_process_spi_cmd()
|
||||
cdda.EndAddr.FAD = ses_inf[3] << 16 | ses_inf[4] << 8 | ses_inf[5];
|
||||
}
|
||||
cdda.repeats = packet_cmd.data_8[6] & 0xF;
|
||||
cdda.status = cdda_t::Playing;
|
||||
SecNumber.Status = GD_PLAY;
|
||||
|
||||
GDStatus.DSC = 1;
|
||||
printf_spicmd("SPI_CD_PLAY %d -> %d repeat %d", cdda.CurrAddr.FAD, cdda.EndAddr.FAD, cdda.repeats);
|
||||
cddaTransition(GD_PLAY, 5_sh4ms, SecNumber.Status);
|
||||
}
|
||||
else if (param_type == 7)
|
||||
{
|
||||
printf_spicmd("SPI_CD_PLAY pause");
|
||||
if (cdda.status == cdda_t::Paused)
|
||||
{
|
||||
// Resume from previous pos unless we're at the end
|
||||
if (cdda.CurrAddr.FAD > cdda.EndAddr.FAD)
|
||||
{
|
||||
cdda.status = cdda_t::Terminated;
|
||||
SecNumber.Status = GD_STANDBY;
|
||||
}
|
||||
cddaTransition(GD_STANDBY);
|
||||
else
|
||||
{
|
||||
cdda.status = cdda_t::Playing;
|
||||
SecNumber.Status = GD_PLAY;
|
||||
}
|
||||
cddaTransition(GD_PLAY);
|
||||
}
|
||||
}
|
||||
else
|
||||
else {
|
||||
die("SPI_CD_PLAY: unknown parameter");
|
||||
}
|
||||
|
||||
DEBUG_LOG(GDROM, "CDDA StartAddr=%d EndAddr=%d repeats=%d status=%d CurrAddr=%d",cdda.StartAddr.FAD,
|
||||
cdda.EndAddr.FAD, cdda.repeats, cdda.status, cdda.CurrAddr.FAD);
|
||||
@@ -964,42 +972,30 @@ static void gd_process_spi_cmd()
|
||||
const u32 param_type = packet_cmd.data_8[1] & 7;
|
||||
printf_spicmd("SPI_CD_SEEK param_type=%d", param_type);
|
||||
|
||||
SecNumber.Status = GD_PAUSE;
|
||||
if (cdda.status == cdda_t::Playing)
|
||||
cdda.status = cdda_t::Paused;
|
||||
|
||||
if (param_type == 1 || param_type == 2)
|
||||
{
|
||||
bool min_sec_frame = param_type == 2;
|
||||
cdda.StartAddr.FAD = cdda.CurrAddr.FAD = GetFAD(&packet_cmd.data_8[2], min_sec_frame);
|
||||
#ifdef STRICT_MODE
|
||||
SecNumber.Status = GD_SEEK;
|
||||
GDStatus.DSC = 0;
|
||||
sh4_sched_request(gdrom_schid, SH4_MAIN_CLOCK / 50); // 20 ms
|
||||
#else
|
||||
GDStatus.DSC = 1;
|
||||
#endif
|
||||
cddaTransition(GD_PAUSE, 20_sh4ms, GD_SEEK);
|
||||
}
|
||||
else if (param_type == 3)
|
||||
{
|
||||
//stop audio , goto home
|
||||
cdda.StartAddr.FAD = cdda.CurrAddr.FAD = 150;
|
||||
cdda.status = cdda_t::NoInfo;
|
||||
#ifdef STRICT_MODE
|
||||
SecNumber.Status = GD_BUSY;
|
||||
GDStatus.DSC = 0;
|
||||
sh4_sched_request(gdrom_schid, SH4_MAIN_CLOCK / 50); // 20 ms
|
||||
#else
|
||||
SecNumber.Status = GD_STANDBY;
|
||||
GDStatus.DSC = 1;
|
||||
#endif
|
||||
cddaTransition(GD_STANDBY, 5_sh4ms);
|
||||
}
|
||||
else if (param_type == 4)
|
||||
{
|
||||
//pause audio -- nothing more
|
||||
SecNumber.Status = GD_PAUSE;
|
||||
cdda.status = cdda_t::Paused;
|
||||
cddaCancelTransition();
|
||||
}
|
||||
else
|
||||
else {
|
||||
die("SPI_CD_SEEK : not known parameter..");
|
||||
}
|
||||
|
||||
DEBUG_LOG(GDROM, "CDDA StartAddr=%d EndAddr=%d repeats=%d status=%d CurrAddr=%d",cdda.StartAddr.FAD,
|
||||
cdda.EndAddr.FAD, cdda.repeats, cdda.status, cdda.CurrAddr.FAD);
|
||||
@@ -1017,7 +1013,7 @@ static void gd_process_spi_cmd()
|
||||
|
||||
case SPI_GET_SCD:
|
||||
{
|
||||
printf_spicmd("SPI_GET_SCD");
|
||||
printf_spicmd("SPI_GET_SCD %d", packet_cmd.data_8[1] & 0xF);
|
||||
|
||||
const u32 format = packet_cmd.data_8[1] & 0xF;
|
||||
const u32 alloc_len = (packet_cmd.data_8[3] << 8) | packet_cmd.data_8[4];
|
||||
@@ -1230,16 +1226,6 @@ static int getGDROMTicks()
|
||||
//is this needed ?
|
||||
static int GDRomschd(int tag, int cycles, int jitter, void *arg)
|
||||
{
|
||||
if (SecNumber.Status == GD_SEEK)
|
||||
{
|
||||
SecNumber.Status = GD_PAUSE;
|
||||
GDStatus.DSC = 1;
|
||||
}
|
||||
else if (SecNumber.Status == GD_BUSY)
|
||||
{
|
||||
SecNumber.Status = GD_STANDBY;
|
||||
GDStatus.DSC = 1;
|
||||
}
|
||||
if (!(SB_GDST & 1) || !(SB_GDEN & 1) || (dma_buff.isEmpty() && read_params.remaining_sectors == 0))
|
||||
return 0;
|
||||
|
||||
@@ -1298,13 +1284,33 @@ static int GDRomschd(int tag, int cycles, int jitter, void *arg)
|
||||
asic_RaiseInterrupt(holly_GDROM_DMA);
|
||||
}
|
||||
// Read ALL sectors and all buffer
|
||||
if (read_params.remaining_sectors == 0 && dma_buff.isEmpty())
|
||||
//verify(!SB_GDST&1) -> dc can do multi read dma
|
||||
if (read_params.remaining_sectors == 0 && dma_buff.isEmpty()) {
|
||||
gd_set_state(gds_procpacketdone);
|
||||
cddaTransition(GD_PAUSE, 67_sh4ms, GD_PLAY);
|
||||
}
|
||||
|
||||
return getGDROMTicks();
|
||||
}
|
||||
|
||||
static int statusSched(int tag, int cycles, int jitter, void *arg)
|
||||
{
|
||||
if (nextStatus != -1)
|
||||
{
|
||||
GDStatus.DSC = 1;
|
||||
int prevCddaStatus = cdda.status;
|
||||
if (nextStatus == GD_PAUSE && cdda.status == cdda_t::Playing)
|
||||
cdda.status = cdda_t::Paused;
|
||||
else if (nextStatus == GD_STANDBY)
|
||||
cdda.status = cdda_t::Terminated;
|
||||
else if (nextStatus == GD_PLAY)
|
||||
cdda.status = cdda_t::Playing;
|
||||
DEBUG_LOG(GDROM, "statusSched: Status %d -> %d, cdda status %d -> %d", SecNumber.Status, nextStatus, prevCddaStatus, cdda.status);
|
||||
SecNumber.Status = nextStatus;
|
||||
nextStatus = -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//DMA Start
|
||||
static void GDROM_DmaStart(u32 addr, u32 data)
|
||||
{
|
||||
@@ -1346,6 +1352,7 @@ static void GDROM_DmaEnable(u32 addr, u32 data)
|
||||
void gdrom_reg_Init()
|
||||
{
|
||||
gdrom_schid = sh4_sched_register(0, &GDRomschd);
|
||||
statusSchid = sh4_sched_register(0, &statusSched);
|
||||
gd_disc_change();
|
||||
}
|
||||
|
||||
@@ -1353,6 +1360,8 @@ void gdrom_reg_Term()
|
||||
{
|
||||
sh4_sched_unregister(gdrom_schid);
|
||||
gdrom_schid = -1;
|
||||
sh4_sched_unregister(statusSchid);
|
||||
statusSchid = -1;
|
||||
}
|
||||
|
||||
void gdrom_reg_Reset(bool hard)
|
||||
@@ -1390,7 +1399,6 @@ void gdrom_reg_Reset(bool hard)
|
||||
cdda = {};
|
||||
gd_disk_type = NoDisk;
|
||||
|
||||
data_write_mode = 0;
|
||||
DriveSel = 0xa0;
|
||||
Error = {};
|
||||
IntReason = {};
|
||||
@@ -1423,7 +1431,6 @@ void serialize(Serializer& ser)
|
||||
ser << cdda;
|
||||
ser << gd_state;
|
||||
ser << gd_disk_type;
|
||||
ser << data_write_mode;
|
||||
ser << DriveSel;
|
||||
ser << Error;
|
||||
|
||||
@@ -1433,6 +1440,8 @@ void serialize(Serializer& ser)
|
||||
ser << SecNumber;
|
||||
ser << GDStatus;
|
||||
ser << ByteCount;
|
||||
sh4_sched_serialize(ser, gdrom_schid);
|
||||
sh4_sched_serialize(ser, statusSchid);
|
||||
}
|
||||
|
||||
void deserialize(Deserializer& deser)
|
||||
@@ -1446,20 +1455,14 @@ void deserialize(Deserializer& deser)
|
||||
deser >> packet_cmd;
|
||||
deser >> set_mode_offset;
|
||||
deser >> read_params;
|
||||
if (deser.version() >= Deserializer::V17) {
|
||||
dma_buff.deserialize(deser);
|
||||
}
|
||||
else {
|
||||
deser >> packet_cmd;
|
||||
dma_buff.clear();
|
||||
}
|
||||
dma_buff.deserialize(deser);
|
||||
pio_buff.deserialize(deser);
|
||||
deser.skip<u32>(Deserializer::V44); // set_mode_offset (repeat)
|
||||
deser >> ata_command;
|
||||
deser >> cdda;
|
||||
deser >> gd_state;
|
||||
deser >> gd_disk_type;
|
||||
deser >> data_write_mode;
|
||||
deser.skip<u32>(Deserializer::V62); // data_write_mode
|
||||
deser >> DriveSel;
|
||||
deser >> Error;
|
||||
|
||||
@@ -1469,6 +1472,10 @@ void deserialize(Deserializer& deser)
|
||||
deser >> SecNumber;
|
||||
deser >> GDStatus;
|
||||
deser >> ByteCount;
|
||||
if (deser.version() >= Deserializer::V62) {
|
||||
sh4_sched_deserialize(deser, gdrom_schid);
|
||||
sh4_sched_deserialize(deser, statusSchid);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+1
-16
@@ -651,7 +651,6 @@ void sb_Init()
|
||||
maple_Init();
|
||||
aica::sbInit();
|
||||
|
||||
bba_Init();
|
||||
ModemInit();
|
||||
}
|
||||
|
||||
@@ -706,19 +705,5 @@ void sb_serialize(Serializer& ser)
|
||||
void sb_deserialize(Deserializer& deser)
|
||||
{
|
||||
deser >> sb_regs;
|
||||
if (deser.version() < Deserializer::V33)
|
||||
deser >> SB_ISTNRM;
|
||||
if (deser.version() >= Deserializer::V24)
|
||||
deser >> SB_ISTNRM1;
|
||||
else
|
||||
SB_ISTNRM1 = 0;
|
||||
if (deser.version() < Deserializer::V33)
|
||||
{
|
||||
if (deser.version() < Deserializer::V30)
|
||||
{
|
||||
deser.skip<u32>(); // SB_FFST_rc;
|
||||
deser.skip<u32>(); // SB_FFST;
|
||||
}
|
||||
deser >> SB_ADST;
|
||||
}
|
||||
deser >> SB_ISTNRM1;
|
||||
}
|
||||
@@ -7,4 +7,5 @@ target_sources(${PROJECT_NAME} PRIVATE
|
||||
maple_helper.h
|
||||
maple_if.cpp
|
||||
maple_if.h
|
||||
maple_jvs.cpp)
|
||||
maple_jvs.cpp
|
||||
wccf_cam.cpp)
|
||||
+24
-22
@@ -4,6 +4,7 @@
|
||||
#include "hw/naomi/naomi_cart.h"
|
||||
#include "hw/naomi/card_reader.h"
|
||||
#include "hw/sh4/modules/modules.h"
|
||||
#include "hw/sh4/sh4_sched.h"
|
||||
#include "cfg/option.h"
|
||||
#include "stdclass.h"
|
||||
#include "serialize.h"
|
||||
@@ -14,6 +15,7 @@ MapleInputState mapleInputState[4];
|
||||
extern bool maple_ddt_pending_reset;
|
||||
extern std::vector<std::pair<u32, std::vector<u32>>> mapleDmaOut;
|
||||
extern bool SDCKBOccupied;
|
||||
extern int maple_schid;
|
||||
|
||||
void (*MapleConfigMap::UpdateVibration)(u32 port, float power, float inclination, u32 duration_ms);
|
||||
|
||||
@@ -227,7 +229,8 @@ static void mcfg_Create(MapleDeviceType type, u32 bus, u32 port, s32 player_num
|
||||
}
|
||||
}
|
||||
std::shared_ptr<maple_device> dev = maple_Create(type);
|
||||
dev->Setup(bus, port, player_num);
|
||||
if (dev != nullptr)
|
||||
dev->Setup(bus, port, player_num);
|
||||
}
|
||||
|
||||
static void createNaomiDevices()
|
||||
@@ -257,6 +260,12 @@ static void createNaomiDevices()
|
||||
insertRfidCard(1);
|
||||
}
|
||||
}
|
||||
else if (gameId.substr(0, 4) == "WCCF")
|
||||
{
|
||||
if (config::MultiboardSlaves <= 1)
|
||||
// CCD cam
|
||||
mcfg_Create(MDT_WccfCamera, 1, 5, 0);
|
||||
}
|
||||
else if (gameId == "THE KING OF ROUTE66")
|
||||
{
|
||||
mcfg_Create(MDT_SegaController, 1, 5);
|
||||
@@ -502,6 +511,8 @@ void mcfg_SerializeDevices(Serializer& ser)
|
||||
{
|
||||
ser << maple_ddt_pending_reset;
|
||||
ser << SDCKBOccupied;
|
||||
sh4_sched_serialize(ser, maple_schid);
|
||||
|
||||
ser << (u32)mapleDmaOut.size();
|
||||
for (const auto& pair : mapleDmaOut)
|
||||
{
|
||||
@@ -526,30 +537,23 @@ void mcfg_DeserializeDevices(Deserializer& deser)
|
||||
{
|
||||
if (!deser.rollback())
|
||||
mcfg_DestroyDevices(false);
|
||||
u8 eeprom[128];
|
||||
if (deser.version() < Deserializer::V23)
|
||||
{
|
||||
deser >> eeprom;
|
||||
deser.skip(128); // Unused eeprom space
|
||||
deser.skip<bool>(); // EEPROM_loaded
|
||||
}
|
||||
deser >> maple_ddt_pending_reset;
|
||||
if (deser.version() >= Deserializer::V47)
|
||||
deser >> SDCKBOccupied;
|
||||
if (deser.version() >= Deserializer::V62)
|
||||
sh4_sched_deserialize(deser, maple_schid);
|
||||
|
||||
mapleDmaOut.clear();
|
||||
if (deser.version() >= Deserializer::V23)
|
||||
u32 size;
|
||||
deser >> size;
|
||||
for (u32 i = 0; i < size; i++)
|
||||
{
|
||||
u32 size;
|
||||
deser >> size;
|
||||
for (u32 i = 0; i < size; i++)
|
||||
{
|
||||
u32 address;
|
||||
deser >> address;
|
||||
u32 dataSize;
|
||||
deser >> dataSize;
|
||||
mapleDmaOut.emplace_back(address, std::vector<u32>(dataSize));
|
||||
deser.deserialize(mapleDmaOut.back().second.data(), dataSize);
|
||||
}
|
||||
u32 address;
|
||||
deser >> address;
|
||||
u32 dataSize;
|
||||
deser >> dataSize;
|
||||
mapleDmaOut.emplace_back(address, std::vector<u32>(dataSize));
|
||||
deser.deserialize(mapleDmaOut.back().second.data(), dataSize);
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAPLE_PORTS; i++)
|
||||
@@ -564,8 +568,6 @@ void mcfg_DeserializeDevices(Deserializer& deser)
|
||||
MapleDevices[i][j]->deserialize(deser);
|
||||
}
|
||||
}
|
||||
if (deser.version() < Deserializer::V23 && EEPROM != nullptr)
|
||||
memcpy(EEPROM, eeprom, sizeof(eeprom));
|
||||
}
|
||||
|
||||
std::shared_ptr<MIE> getMieDevice()
|
||||
|
||||
@@ -25,6 +25,7 @@ enum MapleDeviceType
|
||||
MDT_Dreameye = 17,
|
||||
MDT_SegaControllerXL = 18,
|
||||
MDT_DreamParaParaController = 19,
|
||||
MDT_WccfCamera = 20,
|
||||
MDT_Count
|
||||
};
|
||||
|
||||
@@ -48,6 +49,11 @@ enum PlainJoystickTriggerId
|
||||
PJTI_Count = 4
|
||||
};
|
||||
|
||||
enum class MaplePortType {
|
||||
user_accessible,
|
||||
built_in,
|
||||
};
|
||||
|
||||
struct PlainJoystickState
|
||||
{
|
||||
PlainJoystickState()
|
||||
@@ -118,10 +124,12 @@ void mcfg_DestroyDevices(bool full = true);
|
||||
void mcfg_SerializeDevices(Serializer& ser);
|
||||
void mcfg_DeserializeDevices(Deserializer& deser);
|
||||
|
||||
constexpr int maple_getPortCount(MapleDeviceType type)
|
||||
constexpr int maple_getPortCount(MapleDeviceType type, MaplePortType port)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MDT_FishingController:
|
||||
return port == MaplePortType::user_accessible ? 0 : 1;
|
||||
case MDT_SegaController:
|
||||
case MDT_SegaControllerXL:
|
||||
return 2;
|
||||
|
||||
@@ -841,7 +841,6 @@ struct maple_microphone: maple_base
|
||||
deser >> gain;
|
||||
deser >> sampling;
|
||||
deser >> eight_khz;
|
||||
deser.skip(480 - sizeof(u32) - sizeof(bool) * 2, Deserializer::V23);
|
||||
if (sampling)
|
||||
StartAudioRecording(eight_khz);
|
||||
}
|
||||
@@ -1888,10 +1887,10 @@ std::shared_ptr<maple_device> maple_Create(MapleDeviceType type)
|
||||
case MDT_SegaControllerXL: return std::make_shared<FullController>();
|
||||
case MDT_DreamParaParaController: return std::make_shared<maple_dreamparapara_controller>();
|
||||
case MDT_RFIDReaderWriter: return RFIDReaderWriter::Create();
|
||||
case MDT_WccfCamera: return WccfCamera::Create();
|
||||
|
||||
default:
|
||||
ERROR_LOG(MAPLE, "Invalid device type %d", type);
|
||||
die("Invalid maple device type");
|
||||
break;
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
#include "types.h"
|
||||
#include "maple_cfg.h"
|
||||
#include "maple_helper.h"
|
||||
#include <cmath>
|
||||
#include "input/gamepad.h"
|
||||
#include "serialize.h"
|
||||
#include "hw/hwreg.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
enum MapleFunctionID
|
||||
{
|
||||
@@ -204,8 +203,6 @@ void limit_joystick_magnitude(s8& joyx, s8& joyy)
|
||||
}
|
||||
}
|
||||
|
||||
extern u8 *EEPROM;
|
||||
|
||||
#define SWAP32(a) ((((a) & 0xff) << 24) | (((a) & 0xff00) << 8) | (((a) >> 8) & 0xff00) | (((a) >> 24) & 0xff))
|
||||
|
||||
const char *GetCurrentGameButtonName(DreamcastKey key);
|
||||
@@ -305,6 +302,8 @@ struct BaseMIE : public maple_base
|
||||
u32 dma(u32 cmd) override;
|
||||
void reply(u8 code, u8 sizew = 0);
|
||||
|
||||
virtual u8 getExtDeviceMap() const { return maple_GetAttachedDevices(bus_id); }
|
||||
|
||||
virtual void handle_86_subcommand();
|
||||
virtual void firmwareLoaded(u32 hash) {}
|
||||
};
|
||||
@@ -318,3 +317,8 @@ struct RFIDReaderWriter : public BaseMIE
|
||||
{
|
||||
static std::shared_ptr<maple_device> Create();
|
||||
};
|
||||
|
||||
struct WccfCamera : public BaseMIE
|
||||
{
|
||||
static std::shared_ptr<maple_device> Create();
|
||||
};
|
||||
@@ -21,7 +21,7 @@ enum MaplePattern
|
||||
|
||||
std::shared_ptr<maple_device> MapleDevices[MAPLE_PORTS][6];
|
||||
|
||||
int maple_schid;
|
||||
int maple_schid = -1;
|
||||
|
||||
/*
|
||||
Maple host controller
|
||||
@@ -219,7 +219,7 @@ static void maple_DoDma()
|
||||
// and send everything to the main device.
|
||||
auto pDevice = MapleDevices[bus][5];
|
||||
if (pDevice != nullptr
|
||||
&& maple_getPortCount(pDevice->get_device_type()) != 0)
|
||||
&& maple_getPortCount(pDevice->get_device_type(), MaplePortType::built_in) != 0)
|
||||
{
|
||||
port = getPort(reci);
|
||||
if (port != 5)
|
||||
|
||||
+88
-45
@@ -22,11 +22,15 @@
|
||||
#include <xxhash.h>
|
||||
#include "oslib/oslib.h"
|
||||
#include "oslib/i18n.h"
|
||||
#include "oslib/directory.h"
|
||||
#include "stdclass.h"
|
||||
#include "cfg/option.h"
|
||||
#include "network/output.h"
|
||||
#include "hw/naomi/printer.h"
|
||||
#include "hw/naomi/card_reader.h"
|
||||
#include "hw/sh4/sh4_sched.h"
|
||||
#include "input/haptic.h"
|
||||
#include "hw/sh4/sh4_mem.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -635,10 +639,7 @@ public:
|
||||
}
|
||||
void deserialize(Deserializer& deser) override
|
||||
{
|
||||
if (deser.version() >= Deserializer::V31)
|
||||
deser >> out;
|
||||
else
|
||||
out = 0xff;
|
||||
deser >> out;
|
||||
jvs_837_13844::deserialize(deser);
|
||||
}
|
||||
|
||||
@@ -756,10 +757,7 @@ public:
|
||||
}
|
||||
void deserialize(Deserializer& deser) override
|
||||
{
|
||||
if (deser.version() >= Deserializer::V31)
|
||||
deser >> testMode;
|
||||
else
|
||||
testMode = false;
|
||||
deser >> testMode;
|
||||
if (deser.version() >= Deserializer::V51)
|
||||
{
|
||||
deser >> damper_high;
|
||||
@@ -942,8 +940,7 @@ public:
|
||||
}
|
||||
void deserialize(Deserializer& deser) override
|
||||
{
|
||||
if (deser.version() >= Deserializer::V31)
|
||||
deser >> gear;
|
||||
deser >> gear;
|
||||
jvs_837_13844_racing::deserialize(deser);
|
||||
}
|
||||
|
||||
@@ -1022,6 +1019,69 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class jvs_837_13844_wccf : public jvs_837_13844
|
||||
{
|
||||
public:
|
||||
jvs_837_13844_wccf(u8 node_id, MIEImpl *parent, int first_player = 0)
|
||||
: jvs_837_13844(node_id, parent, first_player)
|
||||
{}
|
||||
|
||||
void write_digital_out(int count, const u8 *data) override
|
||||
{
|
||||
jvs_837_13844::write_digital_out(count, data);
|
||||
if (count >= 3)
|
||||
{
|
||||
if (data[2] & 0x80) {
|
||||
// solenoid is on
|
||||
solenoidOn = true;
|
||||
solenoidWait = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// solenoid is off
|
||||
if (solenoidOn)
|
||||
{
|
||||
// wait 30 frames before ejecting the card
|
||||
if (++solenoidWait >= 30) {
|
||||
card_reader::ejectCard();
|
||||
solenoidOn = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data[0] & 1)
|
||||
{
|
||||
// Player card dispenser
|
||||
if (cardDispenser == 0)
|
||||
NOTICE_LOG(JVS, "WCCF player card dispensed");
|
||||
cardDispenser = sh4_sched_now64();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void read_digital_in(const u32 *buttons, u32 *v) override
|
||||
{
|
||||
jvs_837_13844::read_digital_in(buttons, v);
|
||||
v[1] |= NAOMI_RIGHT_KEY | NAOMI_BTN1_KEY; // card dispenser sensor off, standby on
|
||||
if (cardDispenser != 0)
|
||||
{
|
||||
// idle: wait for owed cards > 0 && standby=on
|
||||
// payout: assert card vendor payout
|
||||
// payoutwait: wait for !sensor on, decrement owed cards
|
||||
// recvwait: wait for !sensor off -> idle
|
||||
u64 now = sh4_sched_now64();
|
||||
if (now - cardDispenser > 1000_sh4ms)
|
||||
cardDispenser = 0;
|
||||
else
|
||||
v[1] &= ~(NAOMI_RIGHT_KEY | NAOMI_BTN1_KEY); // card dispenser sensor on, standby off
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool solenoidOn = false;
|
||||
int solenoidWait = 0;
|
||||
u64 cardDispenser = 0;
|
||||
};
|
||||
|
||||
// Ninja assault
|
||||
class jvs_namco_jyu : public jvs_io_board
|
||||
{
|
||||
@@ -1309,8 +1369,11 @@ u32 BaseMIE::RawDma(const u32 *buffer_in, u32 buffer_in_len, u32 *buffer_out)
|
||||
u32 resp = dma(cmd);
|
||||
if (resp != MDRE_UnknownCmd)
|
||||
{
|
||||
const u32 reci = (buffer_in[0] >> 8) & 0xFF;
|
||||
u32 reci = (buffer_in[0] >> 8) & 0xFF;
|
||||
const u32 sender = (buffer_in[0] >> 16) & 0xFF;
|
||||
if (reci & 0x20)
|
||||
reci |= getExtDeviceMap();
|
||||
|
||||
buffer_out[0] = (resp << 0 ) | (sender << 8) | (reci << 16) | ((out_len / 4) << 24);
|
||||
return out_len + 4;
|
||||
}
|
||||
@@ -1383,7 +1446,7 @@ u32 BaseMIE::RawDma(const u32 *buffer_in, u32 buffer_in_len, u32 *buffer_out)
|
||||
|
||||
case MDC_JVSGetId:
|
||||
{
|
||||
DEBUG_LOG(JVS, "bus[%d] JVS Get Id", bus_id);
|
||||
LOGJVS("bus[%d] JVS Get Id", bus_id);
|
||||
static const char ID[56] = "315-6149 COPYRIGHT SEGA ENTERPRISES CO,LTD. 1998";
|
||||
reply(MDRS_JVSGetIdReply, 7);
|
||||
wptr(ID, 28);
|
||||
@@ -1395,7 +1458,7 @@ u32 BaseMIE::RawDma(const u32 *buffer_in, u32 buffer_in_len, u32 *buffer_out)
|
||||
break;
|
||||
|
||||
default:
|
||||
INFO_LOG(MAPLE, "BaseMIE: Unknown Maple command %x", cmd);
|
||||
INFO_LOG(MAPLE, "bus[%d] BaseMIE: Unknown Maple command %x", bus_id, cmd);
|
||||
reply(MDRE_UnknownCmd);
|
||||
break;;
|
||||
}
|
||||
@@ -1598,6 +1661,10 @@ MIEImpl::MIEImpl()
|
||||
{
|
||||
io_boards.push_back(std::make_unique<jvs_837_13844_samba>(1, this));
|
||||
}
|
||||
else if (gameId.substr(0, 4) == "WCCF" && config::MultiboardSlaves <= 1)
|
||||
{
|
||||
io_boards.push_back(std::make_unique<jvs_837_13844_wccf>(1, this));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (gameId == "POKASUKA GHOST (JAPANESE)" // Manic Panic Ghosts
|
||||
@@ -1894,18 +1961,7 @@ void MIEImpl::handle_86_subcommand()
|
||||
|
||||
std::string eeprom_file = getEepromPath();
|
||||
if (!eeprom_file.empty())
|
||||
{
|
||||
FILE* f = nowide::fopen(eeprom_file.c_str(), "wb");
|
||||
if (f)
|
||||
{
|
||||
std::fwrite(eeprom, 1, sizeof(eeprom), f);
|
||||
std::fclose(f);
|
||||
INFO_LOG(MAPLE, "Saved EEPROM to %s", eeprom_file.c_str());
|
||||
}
|
||||
else {
|
||||
WARN_LOG(MAPLE, "EEPROM SAVE FAILED to %s", eeprom_file.c_str());
|
||||
}
|
||||
}
|
||||
safeFullSave(eeprom_file, eeprom, sizeof(eeprom));
|
||||
|
||||
reply(MDRS_JVSReply, 1);
|
||||
memcpy(dma_buffer_out, eeprom, 4);
|
||||
@@ -2038,18 +2094,13 @@ void MIEImpl::deserialize(Deserializer& deser)
|
||||
{
|
||||
maple_base::deserialize(deser);
|
||||
deser >> crazy_mode;
|
||||
if (deser.version() >= Deserializer::V35)
|
||||
deser >> hotd2p;
|
||||
else
|
||||
hotd2p = settings.content.gameId == "hotd2p";
|
||||
deser >> hotd2p;
|
||||
deser >> jvs_repeat_request;
|
||||
deser >> jvs_receive_length;
|
||||
deser >> jvs_receive_buffer;
|
||||
if (deser.version() >= Deserializer::V23)
|
||||
deser >> eeprom;
|
||||
deser >> eeprom;
|
||||
u32 board_count;
|
||||
deser >> board_count;
|
||||
deser.skip(sizeof(size_t) - sizeof(u32), Deserializer::V23);
|
||||
for (u32 i = 0; i < board_count; i++)
|
||||
io_boards[i]->deserialize(deser);
|
||||
}
|
||||
@@ -2638,7 +2689,7 @@ u32 RFIDReaderWriterImpl::dma(u32 cmd)
|
||||
return (MapleDeviceRV)0xfe;
|
||||
|
||||
case 0xA1: // read card data
|
||||
DEBUG_LOG(JVS, "RFID card read (data? %d)", d4Seen);
|
||||
LOGJVS("RFID card read (data? %d)", d4Seen);
|
||||
w32(getStatus());
|
||||
if (!d4Seen)
|
||||
// serial0 and serial1 only
|
||||
@@ -2667,7 +2718,7 @@ u32 RFIDReaderWriterImpl::dma(u32 cmd)
|
||||
u32 offset = r8() * 4;
|
||||
size_t size = r8() * 4;
|
||||
skip(2);
|
||||
DEBUG_LOG(JVS, "RFID card write: offset 0x%x len %d", offset, (int)size);
|
||||
LOGJVS("RFID card write: offset 0x%x len %d", offset, (int)size);
|
||||
rptr(cardData + offset, std::min(size, sizeof(cardData) - offset));
|
||||
saveCard();
|
||||
return (MapleDeviceRV)0xfe;
|
||||
@@ -2694,7 +2745,7 @@ u32 RFIDReaderWriterImpl::dma(u32 cmd)
|
||||
counter = 0;
|
||||
break;
|
||||
}
|
||||
DEBUG_LOG(JVS, "RFID decrement %d", counter);
|
||||
LOGJVS("RFID decrement %d", counter);
|
||||
cardData[19 - counter]--;
|
||||
saveCard();
|
||||
w32(getStatus());
|
||||
@@ -2802,17 +2853,9 @@ void RFIDReaderWriterImpl::saveCard() const
|
||||
{
|
||||
if (transientData)
|
||||
return;
|
||||
std::string path = getCardPath();
|
||||
FILE *fp = nowide::fopen(path.c_str(), "wb");
|
||||
if (fp == nullptr)
|
||||
{
|
||||
WARN_LOG(NAOMI, "Can't create card file %s: errno %d", path.c_str(), errno);
|
||||
return;
|
||||
}
|
||||
const std::string path = getCardPath();
|
||||
INFO_LOG(NAOMI, "Saving card file to %s", path.c_str());
|
||||
if (fwrite(cardData, 1, sizeof(cardData), fp) != sizeof(cardData))
|
||||
WARN_LOG(NAOMI, "Truncated write to file: %s", path.c_str());
|
||||
fclose(fp);
|
||||
safeFullSave(path, cardData, sizeof(cardData));
|
||||
}
|
||||
|
||||
void RFIDReaderWriterImpl::serialize(Serializer& ser) const
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+26
-11
@@ -61,8 +61,11 @@ void *readConst(u32 addr, bool& ismem, u32 sz)
|
||||
else
|
||||
{
|
||||
ismem = true;
|
||||
addr <<= iirf;
|
||||
addr >>= iirf;
|
||||
// Only the low bits of iirf hold the mask shift. Shifting a 32-bit value by the
|
||||
// whole tagged pointer is undefined, and GCC 16 on 32-bit ARM drops the
|
||||
// address entirely (every access hits offset 0).
|
||||
addr <<= (iirf & HANDLER_MAX);
|
||||
addr >>= (iirf & HANDLER_MAX);
|
||||
|
||||
return &(((u8 *)ptr)[addr]);
|
||||
}
|
||||
@@ -94,8 +97,11 @@ void *writeConst(u32 addr, bool& ismem, u32 sz)
|
||||
else
|
||||
{
|
||||
ismem = true;
|
||||
addr <<= iirf;
|
||||
addr >>= iirf;
|
||||
// Only the low bits of iirf hold the mask shift. Shifting a 32-bit value by the
|
||||
// whole tagged pointer is undefined, and GCC 16 on 32-bit ARM drops the
|
||||
// address entirely (every access hits offset 0).
|
||||
addr <<= (iirf & HANDLER_MAX);
|
||||
addr >>= (iirf & HANDLER_MAX);
|
||||
|
||||
return &(((u8 *)ptr)[addr]);
|
||||
}
|
||||
@@ -112,8 +118,11 @@ T DYNACALL readt(u32 addr)
|
||||
|
||||
if (likely(ptr != nullptr))
|
||||
{
|
||||
addr <<= iirf;
|
||||
addr >>= iirf;
|
||||
// Only the low bits of iirf hold the mask shift. Shifting a 32-bit value by the
|
||||
// whole tagged pointer is undefined, and GCC 16 on 32-bit ARM drops the
|
||||
// address entirely (every access hits offset 0).
|
||||
addr <<= (iirf & HANDLER_MAX);
|
||||
addr >>= (iirf & HANDLER_MAX);
|
||||
|
||||
return *(T *)&((u8 *)ptr)[addr];
|
||||
}
|
||||
@@ -156,8 +165,11 @@ void DYNACALL writet(u32 addr, T data)
|
||||
|
||||
if (likely(ptr != nullptr))
|
||||
{
|
||||
addr <<= iirf;
|
||||
addr >>= iirf;
|
||||
// Only the low bits of iirf hold the mask shift. Shifting a 32-bit value by the
|
||||
// whole tagged pointer is undefined, and GCC 16 on 32-bit ARM drops the
|
||||
// address entirely (every access hits offset 0).
|
||||
addr <<= (iirf & HANDLER_MAX);
|
||||
addr >>= (iirf & HANDLER_MAX);
|
||||
|
||||
*(T *)&((u8 *)ptr)[addr] = data;
|
||||
}
|
||||
@@ -398,6 +410,9 @@ static void termMappings()
|
||||
aica::aica_ram.free();
|
||||
free_pages(elan::RAM);
|
||||
elan::RAM = nullptr;
|
||||
// Don't leave the address map pointing at freed memory. A null entry
|
||||
// routes accesses to handler 0 (not mapped), which touches nothing.
|
||||
memset(memInfo_ptr, 0, sizeof(memInfo_ptr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,7 +422,7 @@ void initMappings()
|
||||
// Fallback to statically allocated buffers, this results in slow-ops being generated.
|
||||
if (ram_base == nullptr)
|
||||
{
|
||||
WARN_LOG(VMEM, "Warning! nvmem is DISABLED (due to failure or not being built-in");
|
||||
WARN_LOG(VMEM, "Warning! nvmem is DISABLED (due to failure or not being built-in)");
|
||||
|
||||
// Allocate it all and initialize it.
|
||||
p_sh4rcb = (Sh4RCB*)malloc_pages(sizeof(Sh4RCB));
|
||||
@@ -535,8 +550,8 @@ u32 getVramOffset(void *addr)
|
||||
else
|
||||
#endif
|
||||
{
|
||||
ptrdiff_t offset = (u8*)addr - &vram[0];
|
||||
if (offset < 0 || offset >= VRAM_SIZE)
|
||||
uintptr_t offset = virtmem::untag(addr) - virtmem::untag(&vram[0]);
|
||||
if (offset >= VRAM_SIZE)
|
||||
return -1;
|
||||
|
||||
return (u32)offset;
|
||||
|
||||
@@ -41,9 +41,10 @@ void AicaRamWatcher::unprotectMem(u32 addr, u32 size)
|
||||
|
||||
u32 AicaRamWatcher::getMemOffset(void *p)
|
||||
{
|
||||
if ((u8 *)p < &aica::aica_ram[0] || (u8 *)p >= &aica::aica_ram[ARAM_SIZE])
|
||||
uintptr_t offset = virtmem::untag(p) - virtmem::untag(&aica::aica_ram[0]);
|
||||
if (offset >= ARAM_SIZE)
|
||||
return -1;
|
||||
return (u32)((u8 *)p - &aica::aica_ram[0]);
|
||||
return (u32)offset;
|
||||
}
|
||||
|
||||
void ElanRamWatcher::protectMem(u32 addr, u32 size)
|
||||
@@ -69,9 +70,10 @@ void ElanRamWatcher::unprotectMem(u32 addr, u32 size)
|
||||
u32 ElanRamWatcher::getMemOffset(void *p)
|
||||
{
|
||||
using namespace elan;
|
||||
if ((u8 *)p < RAM || (u8 *)p >= &RAM[ERAM_SIZE])
|
||||
uintptr_t offset = virtmem::untag(p) - virtmem::untag(RAM);
|
||||
if (offset >= ERAM_SIZE)
|
||||
return -1;
|
||||
return (u32)((u8 *)p - RAM);
|
||||
return (u32)offset;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+177
-36
@@ -59,7 +59,7 @@ static u8 dspram[0x1000];
|
||||
static_assert(sizeof(regs_write_mask) == sizeof(modem_regs));
|
||||
static_assert(sizeof(por_dspram) == sizeof(dspram));
|
||||
|
||||
static int modem_sched;
|
||||
static int modem_sched = -1;
|
||||
|
||||
enum ModemStates
|
||||
{
|
||||
@@ -82,13 +82,24 @@ enum ConnectState
|
||||
HANDSHAKING,
|
||||
PRE_CONNECTED,
|
||||
CONNECTED,
|
||||
NEGO_COMPLETE
|
||||
NEGO_COMPLETE,
|
||||
RINGING_IN,
|
||||
};
|
||||
static ConnectState connect_state = DISCONNECTED;
|
||||
static float txFifoSize = 0.f;
|
||||
static std::deque<u8> rxFifo;
|
||||
static int cyclesPerByte;
|
||||
static float txRxRatio = 1.f;
|
||||
static std::string numberDialed;
|
||||
static std::function<void()> onAnswerCallback;
|
||||
static std::function<void()> onHandshakeCallback;
|
||||
static std::function<void(const std::string&)> onDialCallback;
|
||||
static std::function<void()> periodicCallback;
|
||||
static u32 periodicCycles;
|
||||
static u64 lastDSPRamWrite;
|
||||
static u64 ringStart;
|
||||
static bool peer2peerRawMode;
|
||||
static bool keepRinging;
|
||||
|
||||
static void schedule_callback(int ms);
|
||||
static const char *getDSPRamLabel(u32 addr);
|
||||
@@ -166,6 +177,7 @@ static InStream *curInput = &netIn;
|
||||
static OutStream *curOutput = &netOut;
|
||||
|
||||
static V42Protocol v42Proto { netIn, netOut };
|
||||
static HdlcSyncAsync hdlcProto { netIn, netOut };
|
||||
|
||||
static bool v8bis;
|
||||
static V8bisProtocol v8bisProto;
|
||||
@@ -196,9 +208,16 @@ static void configureStreams()
|
||||
curInput = &v8bisProto;
|
||||
curOutput = &v8bisProto;
|
||||
}
|
||||
else if (modem_regs.reg08.ASYN == 0) {
|
||||
curInput = &v42Proto;
|
||||
curOutput = &v42Proto;
|
||||
else if (modem_regs.reg08.ASYN == 0)
|
||||
{
|
||||
if (peer2peerRawMode) {
|
||||
curInput = &hdlcProto;
|
||||
curOutput = &hdlcProto;
|
||||
}
|
||||
else {
|
||||
curInput = &v42Proto;
|
||||
curOutput = &v42Proto;
|
||||
}
|
||||
}
|
||||
else {
|
||||
curInput = &netIn;
|
||||
@@ -334,6 +353,16 @@ static bool setModemSpeedFromCONF()
|
||||
return speed != 0;
|
||||
}
|
||||
|
||||
static void hangUp()
|
||||
{
|
||||
INFO_LOG(MODEM, "Hanging up modem");
|
||||
modem_regs.ABCODE = 0x96;
|
||||
modem_regs.reg1f.NEWS = 1;
|
||||
modem_regs.CONF = 0xC0;
|
||||
dspram[0x2e4] = 0;
|
||||
connect_state = DISCONNECTED;
|
||||
}
|
||||
|
||||
static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
@@ -362,6 +391,11 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
|
||||
break;
|
||||
case MS_END_DSP:
|
||||
DSPTestEnd();
|
||||
if (ringStart != 0) {
|
||||
// The game resets the modem before answering a call so restore the correct state
|
||||
connect_state = RINGING;
|
||||
ringStart = 0;
|
||||
}
|
||||
break;
|
||||
case MS_NORMAL:
|
||||
modem_regs.reg1f.NEWC = 0; // Not needed when state is CONNECTED but who cares
|
||||
@@ -369,18 +403,24 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
|
||||
switch (connect_state)
|
||||
{
|
||||
case DIALING:
|
||||
if (last_dial_time != 0 && sh4_sched_now64() - last_dial_time >= SH4_MAIN_CLOCK)
|
||||
if (last_dial_time == 0)
|
||||
last_dial_time = sh4_sched_now64();
|
||||
if (sh4_sched_now64() - last_dial_time >= SH4_MAIN_CLOCK)
|
||||
{
|
||||
LOG("Switching to RINGING state");
|
||||
connect_state = RINGING;
|
||||
schedule_callback(100);
|
||||
if (!numberDialed.empty())
|
||||
{
|
||||
NOTICE_LOG(MODEM, "Dialing %s", numberDialed.c_str());
|
||||
if (onDialCallback)
|
||||
onDialCallback(numberDialed);
|
||||
numberDialed.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
last_dial_time = sh4_sched_now64();
|
||||
|
||||
else {
|
||||
modem_regs.reg1e.TDBE = 1;
|
||||
schedule_callback(1000); // To switch to Ringing state
|
||||
callback_cycles = 1000_sh4ms; // To switch to Ringing state
|
||||
}
|
||||
break;
|
||||
case RINGING:
|
||||
@@ -388,11 +428,8 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
|
||||
last_dial_time = 0;
|
||||
LOG("\t\t *** %s STATE ***", connect_state == RINGING ? "RINGING" : "NEGO COMPLETE");
|
||||
modem_regs.reg1f.NEWS = 1;
|
||||
if (!modem_regs.reg09.DATA && connect_state == RINGING)
|
||||
{
|
||||
SET_STATUS_BIT(0x0f, modem_regs.reg0f.RI, 1);
|
||||
if (!modem_regs.reg09.DATA && connect_state == RINGING && modem_regs.reg09.ORG)
|
||||
SET_STATUS_BIT(0x0b, modem_regs.reg0b.ATV25, 1);
|
||||
}
|
||||
break;
|
||||
case HANDSHAKING:
|
||||
LOG("\t\t *** HANDSHAKING STATE ***");
|
||||
@@ -410,8 +447,7 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
|
||||
break;
|
||||
|
||||
case PRE_CONNECTED:
|
||||
if (modem_regs.reg03.RLSDE)
|
||||
SET_STATUS_BIT(0x0f, modem_regs.reg0f.RLSD, 1);
|
||||
SET_STATUS_BIT(0x0f, modem_regs.reg0f.RLSD, 1);
|
||||
bool validConfig;
|
||||
if (modem_regs.CONF == 0xAA)
|
||||
{
|
||||
@@ -493,8 +529,11 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
|
||||
dspram[0x208] = 0xff; // 2.4 - 19.2 kpbs supported
|
||||
dspram[0x209] = 0xbf; // 21.6 - 33.6 kpbs supported, asymmetric supported
|
||||
|
||||
callback_cycles = SH4_MAIN_CLOCK / 1000000 * 238; // 238 us
|
||||
callback_cycles = 238_sh4us;
|
||||
v42Proto.reset();
|
||||
v42Proto.setOriginator(modem_regs.reg09.ORG);
|
||||
hdlcProto.reset();
|
||||
hdlcProto.setOriginator(modem_regs.reg09.ORG);
|
||||
|
||||
break;
|
||||
|
||||
@@ -519,14 +558,46 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
|
||||
}
|
||||
if (rxFifo.size() < 16)
|
||||
{
|
||||
int c = curInput->read();
|
||||
if (c >= 0) {
|
||||
rxFifo.push_back(c);
|
||||
updateRxFifoStatus();
|
||||
try {
|
||||
int c = curInput->read();
|
||||
if (c >= 0) {
|
||||
rxFifo.push_back(c);
|
||||
updateRxFifoStatus();
|
||||
}
|
||||
} catch (const net::modbba::ConnectionClosed& e) {
|
||||
hangUp();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case DISCONNECTED:
|
||||
if (onAnswerCallback)
|
||||
{
|
||||
callback_cycles = SH4_MAIN_CLOCK;
|
||||
if (lastDSPRamWrite != 0
|
||||
&& sh4_sched_now64() - lastDSPRamWrite >= (u64)5 * SH4_MAIN_CLOCK)
|
||||
{
|
||||
INFO_LOG(MODEM, "Answer mode detected");
|
||||
onAnswerCallback();
|
||||
lastDSPRamWrite = 0;
|
||||
callback_cycles = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RINGING_IN:
|
||||
{
|
||||
// Japan standard: 1 s on, 2 s off
|
||||
u64 v = (sh4_sched_now64() - ringStart) % ((u64)3 * SH4_MAIN_CLOCK);
|
||||
bool ri = v < SH4_MAIN_CLOCK;
|
||||
if (ri != modem_regs.reg0f.RI) {
|
||||
modem_regs.reg1f.NEWS = 1;
|
||||
SET_STATUS_BIT(0x0f, modem_regs.reg0f.RI, ri);
|
||||
}
|
||||
callback_cycles = SH4_MAIN_CLOCK;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -537,6 +608,13 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg)
|
||||
}
|
||||
update_interrupt();
|
||||
|
||||
if (periodicCallback)
|
||||
{
|
||||
periodicCallback();
|
||||
if (callback_cycles == 0 || callback_cycles > (int)periodicCycles)
|
||||
callback_cycles = periodicCycles;
|
||||
}
|
||||
|
||||
return callback_cycles;
|
||||
}
|
||||
|
||||
@@ -548,6 +626,15 @@ void ModemInit()
|
||||
void ModemReset()
|
||||
{
|
||||
net::modbba::stop();
|
||||
onAnswerCallback = {};
|
||||
onHandshakeCallback = {};
|
||||
onDialCallback = {};
|
||||
periodicCallback = {};
|
||||
periodicCycles = 0;
|
||||
lastDSPRamWrite = 0;
|
||||
ringStart = 0;
|
||||
peer2peerRawMode = false;;
|
||||
keepRinging = false;
|
||||
}
|
||||
|
||||
void ModemTerm()
|
||||
@@ -660,6 +747,8 @@ static void modem_reset(u32 v)
|
||||
state = MS_RESET;
|
||||
LOG("Modem reset start ...");
|
||||
net::modbba::stop();
|
||||
lastDSPRamWrite = 0;
|
||||
keepRinging = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -678,11 +767,14 @@ static void check_start_handshake()
|
||||
{
|
||||
if (modem_regs.reg09.DTR
|
||||
&& (modem_regs.reg09.DATA || modem_regs.reg15.AUTO)
|
||||
&& (connect_state == RINGING || connect_state == NEGO_COMPLETE))
|
||||
&& (connect_state == RINGING || connect_state == NEGO_COMPLETE)
|
||||
&& !keepRinging)
|
||||
{
|
||||
LOG("DTR asserted. starting handshaking");
|
||||
connect_state = HANDSHAKING;
|
||||
schedule_callback(1);
|
||||
if (onHandshakeCallback)
|
||||
onHandshakeCallback();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -748,7 +840,7 @@ static void ModemNormalWrite(u32 reg, u32 data)
|
||||
{
|
||||
download_crc = (download_crc << 1) + ((download_crc & 0x80) >> 7) + (data & 0xFF);
|
||||
}
|
||||
else if (connect_state == DISCONNECTED || connect_state == DIALING)
|
||||
else if ((connect_state == DISCONNECTED || connect_state == DIALING) && !modem_regs.reg09.DATA)
|
||||
{
|
||||
//LOG("ModemNormalWrite : TBUFFER = %X", data);
|
||||
if (connect_state == DISCONNECTED)
|
||||
@@ -756,7 +848,10 @@ static void ModemNormalWrite(u32 reg, u32 data)
|
||||
INFO_LOG(MODEM, "MODEM Dialing");
|
||||
connect_state = DIALING;
|
||||
modem_regs.SECRXB = 0;
|
||||
numberDialed.clear();
|
||||
}
|
||||
if (data <= 9)
|
||||
numberDialed += '0' + data;
|
||||
schedule_callback(100);
|
||||
}
|
||||
else if (connect_state == CONNECTED && modem_regs.reg08.RTS)
|
||||
@@ -765,7 +860,12 @@ static void ModemNormalWrite(u32 reg, u32 data)
|
||||
#ifndef NDEBUG
|
||||
sent_bytes++;
|
||||
#endif
|
||||
curOutput->write(data);
|
||||
try {
|
||||
curOutput->write(data);
|
||||
} catch (const net::modbba::ConnectionClosed& e) {
|
||||
hangUp();
|
||||
break;
|
||||
}
|
||||
|
||||
modem_regs.reg1e.TDBE = 0;
|
||||
txFifoSize += 1.f;
|
||||
@@ -860,6 +960,10 @@ static void ModemNormalWrite(u32 reg, u32 data)
|
||||
}
|
||||
if ((dspram_addr == 0x26B || dspram_addr == 0x26c) && (modem_regs.MEDA & 0xFF) != 0)
|
||||
dspram[0x26F] = 1; // Saved Filtered EQM
|
||||
if (connect_state == DISCONNECTED && onAnswerCallback) {
|
||||
lastDSPRamWrite = sh4_sched_now64();
|
||||
schedule_callback(1000);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1048,19 +1152,17 @@ void ModemSerialize(Serializer& ser)
|
||||
}
|
||||
void ModemDeserialize(Deserializer& deser)
|
||||
{
|
||||
if (!config::EmulateBBA || deser.version() > Deserializer::V31)
|
||||
sh4_sched_deserialize(deser, modem_sched);
|
||||
if (deser.version() >= Deserializer::V20)
|
||||
{
|
||||
deser >> modem_regs;
|
||||
deser >> dspram;
|
||||
deser >> state;
|
||||
deser >> connect_state;
|
||||
deser >> last_dial_time;
|
||||
deser.skip<bool>(Deserializer::V58);
|
||||
}
|
||||
sh4_sched_deserialize(deser, modem_sched);
|
||||
deser >> modem_regs;
|
||||
deser >> dspram;
|
||||
deser >> state;
|
||||
deser >> connect_state;
|
||||
deser >> last_dial_time;
|
||||
deser.skip<bool>(Deserializer::V58);
|
||||
|
||||
rxFifo.clear();
|
||||
v42Proto.reset();
|
||||
hdlcProto.reset();
|
||||
v8bis = false;
|
||||
v8bisProto.reset();
|
||||
configureStreams();
|
||||
@@ -1211,3 +1313,42 @@ static const char *getDSPRamLabel(u32 addr)
|
||||
else
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void modemOnAnswerMode(std::function<void()> callback) {
|
||||
onAnswerCallback = callback;
|
||||
}
|
||||
void modemOnDial(std::function<void(const std::string&)> callback) {
|
||||
onDialCallback = callback;
|
||||
}
|
||||
void modemOnHandshake(std::function<void()> callback) {
|
||||
onHandshakeCallback = callback;
|
||||
}
|
||||
|
||||
void modemPeriodicCallback(u32 cycles, std::function<void()> callback)
|
||||
{
|
||||
periodicCallback = callback;
|
||||
periodicCycles = cycles;
|
||||
if (cycles != 0 && !sh4_sched_is_scheduled(modem_sched))
|
||||
sh4_sched_request(modem_sched, cycles);
|
||||
}
|
||||
|
||||
void modemPeer2Peer(bool enabled) {
|
||||
peer2peerRawMode = enabled;
|
||||
configureStreams();
|
||||
}
|
||||
|
||||
void modemKeepRinging(bool enabled)
|
||||
{
|
||||
keepRinging = enabled;
|
||||
if (!enabled)
|
||||
check_start_handshake();
|
||||
}
|
||||
|
||||
void modemIncomingCall()
|
||||
{
|
||||
connect_state = RINGING_IN;
|
||||
modem_regs.reg1f.NEWS = 1;
|
||||
SET_STATUS_BIT(0x0f, modem_regs.reg0f.RI, 1);
|
||||
ringStart = sh4_sched_now64();
|
||||
schedule_callback(1000);
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
#include <functional>
|
||||
|
||||
void ModemInit();
|
||||
void ModemReset();
|
||||
@@ -31,3 +32,10 @@ u32 ModemReadMem_A0_006(u32 addr,u32 size);
|
||||
void ModemWriteMem_A0_006(u32 addr,u32 data,u32 size);
|
||||
void ModemSerialize(Serializer& ser);
|
||||
void ModemDeserialize(Deserializer& deser);
|
||||
void modemOnAnswerMode(std::function<void()> callback);
|
||||
void modemOnHandshake(std::function<void()> callback);
|
||||
void modemOnDial(std::function<void(const std::string&)> callback);
|
||||
void modemPeer2Peer(bool enabled);
|
||||
void modemKeepRinging(bool enabled);
|
||||
void modemIncomingCall();
|
||||
void modemPeriodicCallback(u32 cycles, std::function<void()> callback);
|
||||
+369
-47
@@ -151,7 +151,7 @@ static u16 calcCrc16(const std::vector<u8>& data, size_t len = 0)
|
||||
return crc ^ 0xffff;
|
||||
}
|
||||
|
||||
void HdlcEncoder::sendFrame(const std::vector<u8>& data)
|
||||
void HdlcEncoder::sendFrame(const std::vector<u8>& data, int crc)
|
||||
{
|
||||
// opening flag
|
||||
sendFlag();
|
||||
@@ -159,7 +159,8 @@ void HdlcEncoder::sendFrame(const std::vector<u8>& data)
|
||||
for (u8 b : data)
|
||||
sendByte(b);
|
||||
// crc
|
||||
u16 crc = calcCrc16(data);
|
||||
if (crc < 0)
|
||||
crc = calcCrc16(data);
|
||||
sendByte(crc & 0xff);
|
||||
sendByte(crc >> 8);
|
||||
// closing flag
|
||||
@@ -221,7 +222,7 @@ void HdlcDecoder::write(u8 v)
|
||||
if (rxOnes == 6)
|
||||
{
|
||||
// we have a flag
|
||||
if (!rxFrame.empty())
|
||||
if (rxFrame.size() >= 4)
|
||||
{
|
||||
u16 crc = calcCrc16(rxFrame, rxFrame.size() - 2);
|
||||
if (crc != *(u16 *)(&rxFrame.back() - 1)) {
|
||||
@@ -229,12 +230,18 @@ void HdlcDecoder::write(u8 v)
|
||||
}
|
||||
else
|
||||
{
|
||||
rxFrame.pop_back();
|
||||
rxFrame.pop_back();
|
||||
if (!keepCrc) {
|
||||
rxFrame.pop_back();
|
||||
rxFrame.pop_back();
|
||||
}
|
||||
frames.push_back(std::move(rxFrame));
|
||||
}
|
||||
rxFrame.clear();
|
||||
}
|
||||
else if (!rxFrame.empty()) {
|
||||
WARN_LOG(MODEM, "Invalid ~empty frame: size %zd [%02x]", rxFrame.size(), rxFrame[0]);
|
||||
rxFrame.clear();
|
||||
}
|
||||
rxCurByte = 0;
|
||||
rxPosition = 0;
|
||||
}
|
||||
@@ -359,6 +366,8 @@ void V42Protocol::handleFrame()
|
||||
handleSabme(rxFrame[0], rxFrame[1]);
|
||||
break;
|
||||
case 0x0f: // DM
|
||||
WARN_LOG(MODEM, "Received DM");
|
||||
phase = Release;
|
||||
break;
|
||||
case 0x03: // UI
|
||||
WARN_LOG(MODEM, "Received UI");
|
||||
@@ -367,7 +376,17 @@ void V42Protocol::handleFrame()
|
||||
handleDisc(rxFrame[0]);
|
||||
break;
|
||||
case 0x63: // UA
|
||||
receiverReady = true;
|
||||
DEBUG_LOG(MODEM, "Received UA");
|
||||
if (!originator && phase == Establish) {
|
||||
phase = Connected;
|
||||
}
|
||||
else if (phase == DiscSent) {
|
||||
INFO_LOG(MODEM, "Received UA of DISC");
|
||||
phase = Release;
|
||||
}
|
||||
else {
|
||||
receiverReady = true;
|
||||
}
|
||||
break;
|
||||
case 0x87: // FRMR
|
||||
WARN_LOG(MODEM, "Received FRMR");
|
||||
@@ -393,6 +412,18 @@ void V42Protocol::handleFrame()
|
||||
}
|
||||
}
|
||||
|
||||
void V42Protocol::sendSabme() {
|
||||
std::vector<u8> sabme { 3, 0x7f };
|
||||
sendFrame(sabme);
|
||||
}
|
||||
|
||||
void V42Protocol::sendDisc()
|
||||
{
|
||||
std::vector<u8> disc { (u8)(1 | (!originator << 1)), 0x53 };
|
||||
sendFrame(disc);
|
||||
phase = DiscSent;
|
||||
}
|
||||
|
||||
void V42Protocol::handleSabme(u8 address, u8 control)
|
||||
{
|
||||
if (phase == Connected)
|
||||
@@ -442,10 +473,7 @@ void V42Protocol::handleIFrame(const std::vector<u8>& rxFrame)
|
||||
outputStream.write(c);
|
||||
}
|
||||
} catch (const v42b::Exception& e) {
|
||||
// send DISC
|
||||
std::vector<u8> disc { 1, 0x43 };
|
||||
sendFrame(disc);
|
||||
phase = Release;
|
||||
sendDisc();
|
||||
sentIFrames.clear();
|
||||
return;
|
||||
}
|
||||
@@ -464,6 +492,29 @@ void V42Protocol::handleIFrame(const std::vector<u8>& rxFrame)
|
||||
}
|
||||
}
|
||||
|
||||
void V42Protocol::sendXid()
|
||||
{
|
||||
// 03 af 82
|
||||
// [
|
||||
// gid, sz_msb, sz_lsb
|
||||
// [
|
||||
// paramId, valueSize, value...
|
||||
// ]
|
||||
// ]
|
||||
// As sent when originator
|
||||
std::vector<u8> xid { 0x03, 0xaf, 0x82,
|
||||
0x80, 0x00, 0x13,
|
||||
0x03, 0x03, 0x8a, 0x89, 0x00,
|
||||
0x05, 0x02, 0x02, 0x00,
|
||||
0x06, 0x02, 0x02, 0x00,
|
||||
0x07, 0x01, 0x0f,
|
||||
0x08, 0x01, 0x0f,
|
||||
0xff, 0x00, 0x05,
|
||||
0x01, 0x03, 0x4d, 0x4d, 0x41
|
||||
};
|
||||
sendFrame(xid);
|
||||
}
|
||||
|
||||
void V42Protocol::handleXid(std::vector<u8> rxFrame)
|
||||
{
|
||||
u32 maxCodeWords = 512;
|
||||
@@ -634,10 +685,12 @@ void V42Protocol::handleXid(std::vector<u8> rxFrame)
|
||||
i += paramSize;
|
||||
}
|
||||
}
|
||||
if (userDataOffset != 0)
|
||||
rxFrame.resize(userDataOffset);
|
||||
sendFrame(rxFrame);
|
||||
|
||||
if (originator)
|
||||
{
|
||||
if (userDataOffset != 0)
|
||||
rxFrame.resize(userDataOffset);
|
||||
sendFrame(rxFrame);
|
||||
}
|
||||
if (compressionEnabled)
|
||||
{
|
||||
NOTICE_LOG(MODEM, "V.42bis compression enabled: max dictionary size %d", maxCodeWords);
|
||||
@@ -722,8 +775,24 @@ void V42Protocol::sendIFrame()
|
||||
|
||||
int V42Protocol::read()
|
||||
{
|
||||
if (phase == None || phase == Detection) {
|
||||
v14Encoder.flush();
|
||||
if (phase == None || phase == Detection)
|
||||
{
|
||||
if (originator) {
|
||||
v14Encoder.flush();
|
||||
}
|
||||
else if (!v14Encoder.available())
|
||||
{
|
||||
if (odpCount <= 20)
|
||||
{
|
||||
// send ODP: DC1
|
||||
v14Encoder.setStopBits(9);
|
||||
v14Encoder.write(0x91);
|
||||
v14Encoder.write(0x11);
|
||||
v14Encoder.setStopBits(1);
|
||||
v14Encoder.flush();
|
||||
}
|
||||
v14Encoder.flush();
|
||||
}
|
||||
return v14Encoder.read();
|
||||
}
|
||||
else if (phase == V14)
|
||||
@@ -734,7 +803,7 @@ int V42Protocol::read()
|
||||
}
|
||||
if (hdlcEncoder.available() == 0)
|
||||
{
|
||||
if (phase == Connected)
|
||||
if (phase == Connected || phase == DiscSent)
|
||||
{
|
||||
sendIFrame();
|
||||
if (hdlcEncoder.available() != 0)
|
||||
@@ -748,7 +817,17 @@ int V42Protocol::read()
|
||||
return hdlcEncoder.read();
|
||||
}
|
||||
}
|
||||
sendFlag();
|
||||
else if (phase == Establish && !originator)
|
||||
{
|
||||
for (int i = 0; i < 16; i++)
|
||||
sendFlag();
|
||||
sendXid();
|
||||
sendSabme();
|
||||
if (hdlcEncoder.available() != 0)
|
||||
return hdlcEncoder.read();
|
||||
}
|
||||
if (phase != Release)
|
||||
sendFlag();
|
||||
}
|
||||
return hdlcEncoder.read();
|
||||
}
|
||||
@@ -757,12 +836,19 @@ void V42Protocol::write(u8 v)
|
||||
{
|
||||
if (phase == None || phase == Detection)
|
||||
{
|
||||
if (detectionTimer.expired()) {
|
||||
if (originator && detectionTimer.expired())
|
||||
{
|
||||
// V14 isn't currently used in answering mode. If needed, the detectionTimer should probably be reset
|
||||
// on first write since it's already expired by then.
|
||||
INFO_LOG(MODEM, "Switching to V.14 mode");
|
||||
phase = V14;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!originator && phase == None) {
|
||||
phase = Detection;
|
||||
odpCount = 0;
|
||||
}
|
||||
v14Decoder.write(v);
|
||||
int c = v14Decoder.read();
|
||||
if (c == -1)
|
||||
@@ -770,49 +856,81 @@ void V42Protocol::write(u8 v)
|
||||
v = c;
|
||||
}
|
||||
}
|
||||
if (phase == None)
|
||||
switch (phase)
|
||||
{
|
||||
if ((v == 0x11 || v == 0x91)
|
||||
&& (v14Decoder.receivedStopBits() == 9 || v14Decoder.receivedStopBits() == 17))
|
||||
case None:
|
||||
if (originator)
|
||||
{
|
||||
phase = Detection;
|
||||
lastRx = v;
|
||||
odpCount = 1;
|
||||
if ((v == 0x11 || v == 0x91)
|
||||
&& (v14Decoder.receivedStopBits() >= 9 && v14Decoder.receivedStopBits() <= 17))
|
||||
{
|
||||
phase = Detection;
|
||||
lastRx = v;
|
||||
odpCount = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (phase == Detection)
|
||||
{
|
||||
// V.42 ODP: DC1 with alternating parity followed by 8+1 or 16+1 ones
|
||||
if ((v == 0x11 || v == 0x91)
|
||||
&& (v14Decoder.receivedStopBits() == 9 || v14Decoder.receivedStopBits() == 17)
|
||||
&& v != lastRx)
|
||||
break;
|
||||
|
||||
case Detection:
|
||||
if (originator)
|
||||
{
|
||||
odpCount++;
|
||||
if (odpCount == 4)
|
||||
// V.42 ODP: DC1 with alternating parity followed by 8+1 or 16+1 ones
|
||||
if ((v == 0x11 || v == 0x91)
|
||||
&& (v14Decoder.receivedStopBits() >= 9 && v14Decoder.receivedStopBits() <= 17)
|
||||
&& v != lastRx)
|
||||
{
|
||||
odpCount++;
|
||||
if (odpCount == 4)
|
||||
{
|
||||
odpCount = 0;
|
||||
// send ADP
|
||||
v14Encoder.setStopBits(9);
|
||||
v14Encoder.write('E');
|
||||
v14Encoder.write('C');
|
||||
//v14Encoder.write(0); // switches to v14 only and start ppp nego
|
||||
v14Encoder.setStopBits(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
odpCount = 0;
|
||||
// send ADP
|
||||
v14Encoder.setStopBits(9);
|
||||
v14Encoder.write('E');
|
||||
v14Encoder.write('C');
|
||||
//v14Encoder.write(0); // switches to v14 only and start ppp nego
|
||||
v14Encoder.setStopBits(1);
|
||||
if (v == 0x7e)
|
||||
phase = Establish;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
odpCount = 0;
|
||||
if (v == 0x7e)
|
||||
phase = Establish;
|
||||
// Answerer
|
||||
if ((v == 'E' || v == 'C')
|
||||
&& (v14Decoder.receivedStopBits() >= 9 && v14Decoder.receivedStopBits() <= 17)
|
||||
&& v != lastRx)
|
||||
{
|
||||
++odpCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (odpCount >= 10)
|
||||
phase = Establish;
|
||||
else
|
||||
odpCount = 0;
|
||||
}
|
||||
}
|
||||
lastRx = v;
|
||||
}
|
||||
else if (phase == Establish || phase == Connected) {
|
||||
break;
|
||||
|
||||
case Establish:
|
||||
case Connected:
|
||||
case DiscSent:
|
||||
hdlcDecoder.write(v);
|
||||
handleFrame();
|
||||
}
|
||||
else if (phase == V14) {
|
||||
break;
|
||||
|
||||
case V14:
|
||||
v14PipeOut.write(v);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -850,6 +968,210 @@ void V42Protocol::ackIFrame(int seqNum)
|
||||
}
|
||||
}
|
||||
|
||||
int HdlcSyncAsync::read()
|
||||
{
|
||||
if (phase == None || phase == Detection)
|
||||
{
|
||||
if (originator) {
|
||||
v14Encoder.flush();
|
||||
}
|
||||
else if (!v14Encoder.available())
|
||||
{
|
||||
if (odpCount <= 20)
|
||||
{
|
||||
// send ODP: DC1
|
||||
v14Encoder.setStopBits(9);
|
||||
v14Encoder.write(0x91);
|
||||
v14Encoder.write(0x11);
|
||||
v14Encoder.setStopBits(1);
|
||||
v14Encoder.flush();
|
||||
}
|
||||
v14Encoder.flush();
|
||||
}
|
||||
return v14Encoder.read();
|
||||
}
|
||||
if (hdlcEncoder.available() == 0)
|
||||
{
|
||||
if (phase == Connected)
|
||||
{
|
||||
if (hdlcEncoder.available() == 0)
|
||||
handleAsyncFrame();
|
||||
if (hdlcEncoder.available() != 0)
|
||||
return hdlcEncoder.read();
|
||||
}
|
||||
if (phase != Release)
|
||||
hdlcEncoder.sendFlag();
|
||||
}
|
||||
return hdlcEncoder.read();
|
||||
}
|
||||
|
||||
void HdlcSyncAsync::write(u8 v)
|
||||
{
|
||||
if (phase == None || phase == Detection)
|
||||
{
|
||||
if (!originator && phase == None) {
|
||||
phase = Detection;
|
||||
odpCount = 0;
|
||||
}
|
||||
v14Decoder.write(v);
|
||||
int c = v14Decoder.read();
|
||||
if (c == -1)
|
||||
return;
|
||||
v = c;
|
||||
}
|
||||
switch (phase)
|
||||
{
|
||||
case None:
|
||||
if (originator)
|
||||
{
|
||||
if ((v == 0x11 || v == 0x91)
|
||||
&& (v14Decoder.receivedStopBits() >= 9 && v14Decoder.receivedStopBits() <= 17))
|
||||
{
|
||||
phase = Detection;
|
||||
lastRx = v;
|
||||
odpCount = 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Detection:
|
||||
if (originator)
|
||||
{
|
||||
// V.42 ODP: DC1 with alternating parity followed by 8+1 or 16+1 ones
|
||||
if ((v == 0x11 || v == 0x91)
|
||||
&& (v14Decoder.receivedStopBits() >= 9 && v14Decoder.receivedStopBits() <= 17)
|
||||
&& v != lastRx)
|
||||
{
|
||||
odpCount++;
|
||||
if (odpCount == 4)
|
||||
{
|
||||
odpCount = 0;
|
||||
// send ADP
|
||||
v14Encoder.setStopBits(9);
|
||||
v14Encoder.write('E');
|
||||
v14Encoder.write('C');
|
||||
v14Encoder.setStopBits(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
odpCount = 0;
|
||||
if (v == 0x7e)
|
||||
phase = Connected;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Answerer
|
||||
if ((v == 'E' || v == 'C')
|
||||
&& (v14Decoder.receivedStopBits() >= 9 && v14Decoder.receivedStopBits() <= 17)
|
||||
&& v != lastRx)
|
||||
{
|
||||
++odpCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (odpCount >= 10)
|
||||
phase = Connected;
|
||||
else
|
||||
odpCount = 0;
|
||||
}
|
||||
}
|
||||
lastRx = v;
|
||||
break;
|
||||
|
||||
case Connected:
|
||||
hdlcDecoder.write(v);
|
||||
handleSyncFrame();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HdlcSyncAsync::handleSyncFrame()
|
||||
{
|
||||
if (!hdlcDecoder.frameAvailable())
|
||||
return;
|
||||
outputStream.write(FLAG);
|
||||
std::vector<u8> frame = hdlcDecoder.getFrame();
|
||||
for (u8 b : frame)
|
||||
{
|
||||
if (b == FLAG || b == ESCAPE) {
|
||||
outputStream.write(ESCAPE);
|
||||
b ^= 0x20;
|
||||
}
|
||||
outputStream.write(b);
|
||||
}
|
||||
outputStream.write(FLAG);
|
||||
}
|
||||
|
||||
void HdlcSyncAsync::handleAsyncFrame()
|
||||
{
|
||||
if (inputStream.available() == 0)
|
||||
return;
|
||||
bool frameComplete = false;
|
||||
for (;;)
|
||||
{
|
||||
int c = inputStream.read();
|
||||
if (c == -1)
|
||||
break;
|
||||
if (c == FLAG)
|
||||
{
|
||||
if (inFrame.empty())
|
||||
continue;
|
||||
frameComplete = true;
|
||||
break;
|
||||
}
|
||||
else if (c == ESCAPE) {
|
||||
escapeSeen = true;
|
||||
continue;
|
||||
}
|
||||
if (escapeSeen) {
|
||||
c ^= 0x20;
|
||||
escapeSeen = false;
|
||||
}
|
||||
inFrame.push_back(c & 0xff);
|
||||
}
|
||||
if (!frameComplete)
|
||||
return;
|
||||
|
||||
if (inFrame.size() >= 4)
|
||||
{
|
||||
u16 crc = *(u16 *)&inFrame[inFrame.size() - 2];
|
||||
inFrame.pop_back();
|
||||
inFrame.pop_back();
|
||||
/*
|
||||
if ((inFrame[0] & 0xfd) == 1)
|
||||
{
|
||||
if (inFrame[1] == 9)
|
||||
WARN_LOG(MODEM, "[%d] Received REJ %d", originator, inFrame[2] >> 1);
|
||||
else if (inFrame[1] == 0xd)
|
||||
WARN_LOG(MODEM, "[%d] Received SREJ %d", originator, inFrame[2] >> 1);
|
||||
}
|
||||
*/
|
||||
hdlcEncoder.sendFrame(inFrame, crc);
|
||||
}
|
||||
else if (!inFrame.empty()) {
|
||||
WARN_LOG(MODEM, "Small frame (%d) received", (int)inFrame.size());
|
||||
}
|
||||
inFrame.clear();
|
||||
}
|
||||
|
||||
void HdlcSyncAsync::reset()
|
||||
{
|
||||
phase = None;
|
||||
lastRx = 0;
|
||||
odpCount = 0;
|
||||
v14Encoder.reset();
|
||||
v14Decoder.reset();
|
||||
hdlcEncoder.reset();
|
||||
hdlcDecoder.reset();
|
||||
inFrame.clear();
|
||||
escapeSeen = false;
|
||||
}
|
||||
|
||||
u8 V8bisProtocol::detectTone()
|
||||
{
|
||||
switch (toneState)
|
||||
|
||||
+67
-2
@@ -20,7 +20,6 @@
|
||||
#include "types.h"
|
||||
#include "internal.h"
|
||||
#include "v42bis.h"
|
||||
#include "network/netservice.h"
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <unordered_map>
|
||||
@@ -71,6 +70,9 @@ private:
|
||||
class HdlcDecoder : public OutStream
|
||||
{
|
||||
public:
|
||||
HdlcDecoder(bool keepCrc = false)
|
||||
: keepCrc(keepCrc)
|
||||
{}
|
||||
void write(u8 v);
|
||||
bool frameAvailable() const { return !frames.empty(); }
|
||||
std::vector<u8> getFrame();
|
||||
@@ -82,12 +84,13 @@ private:
|
||||
int rxOnes = 0;
|
||||
int rxPosition = 0;
|
||||
u8 rxCurByte = 0;
|
||||
bool keepCrc;
|
||||
};
|
||||
|
||||
class HdlcEncoder : public BufferedTransformer
|
||||
{
|
||||
public:
|
||||
void sendFrame(const std::vector<u8>& data);
|
||||
void sendFrame(const std::vector<u8>& data, int crc = -1);
|
||||
void sendFlag();
|
||||
void reset() override;
|
||||
|
||||
@@ -114,15 +117,21 @@ public:
|
||||
return 1;
|
||||
}
|
||||
|
||||
void setOriginator(bool originator) {
|
||||
this->originator = originator;
|
||||
}
|
||||
|
||||
private:
|
||||
InStream& inputStream;
|
||||
OutStream& outputStream;
|
||||
bool originator = true;
|
||||
|
||||
enum Phase {
|
||||
None,
|
||||
Detection,
|
||||
Establish,
|
||||
Connected,
|
||||
DiscSent,
|
||||
Release,
|
||||
V14 // Bomberman Online, Power Smash, Pro Yakyu Team de asobou net!
|
||||
};
|
||||
@@ -181,10 +190,66 @@ private:
|
||||
|
||||
void sendIFrame();
|
||||
void ackIFrame(int seqNum);
|
||||
void sendDisc();
|
||||
// Answerer
|
||||
void sendSabme();
|
||||
void sendXid();
|
||||
|
||||
friend class V42Test;
|
||||
};
|
||||
|
||||
//
|
||||
// Converts between synchronous HDLC and HDLC-like asynchronous frames
|
||||
// as used by ppp. This is used for Capcom peer-to-peer communication.
|
||||
// Upper layer protocols such as V.42 (and potentially V.42bis) are implemented
|
||||
// natively by each peer. This way error correction works end to end.
|
||||
//
|
||||
class HdlcSyncAsync : public InStream, public OutStream
|
||||
{
|
||||
public:
|
||||
HdlcSyncAsync(InStream& in, OutStream& out)
|
||||
: inputStream(in), outputStream(out)
|
||||
{}
|
||||
int read() override;
|
||||
void write(u8 v) override;
|
||||
void reset();
|
||||
|
||||
int available() override {
|
||||
return 1;
|
||||
}
|
||||
|
||||
void setOriginator(bool originator) {
|
||||
this->originator = originator;
|
||||
}
|
||||
|
||||
private:
|
||||
void handleSyncFrame();
|
||||
void handleAsyncFrame();
|
||||
|
||||
InStream& inputStream;
|
||||
OutStream& outputStream;
|
||||
bool originator = true;
|
||||
|
||||
enum Phase {
|
||||
None,
|
||||
Detection,
|
||||
Connected,
|
||||
Release,
|
||||
};
|
||||
Phase phase = None;
|
||||
u8 lastRx = 0;
|
||||
int odpCount = 0;
|
||||
V14Encoder v14Encoder;
|
||||
V14Decoder v14Decoder;
|
||||
HdlcEncoder hdlcEncoder;
|
||||
HdlcDecoder hdlcDecoder {true};
|
||||
std::vector<uint8_t> inFrame;
|
||||
bool escapeSeen = false;
|
||||
|
||||
static constexpr uint8_t FLAG = 0x7e;
|
||||
static constexpr uint8_t ESCAPE = 0x7d;
|
||||
};
|
||||
|
||||
//
|
||||
// V.8 bis is used by older browsers (Planet Web 1.125, DreamKey 1.0/1.5 and the many games that include them)
|
||||
// and Windows CE games.
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
#include "internal.h"
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
|
||||
@@ -154,10 +154,8 @@ void serialize(Serializer& ser)
|
||||
void deserialize(Deserializer& deser)
|
||||
{
|
||||
deser >> aw_maple_devs;
|
||||
if (deser.version() >= Deserializer::V20) {
|
||||
deser >> coin_chute_time;
|
||||
deser >> aw_ram_test_skipped;
|
||||
}
|
||||
deser >> coin_chute_time;
|
||||
deser >> aw_ram_test_skipped;
|
||||
}
|
||||
|
||||
} // namespace atomiswave
|
||||
@@ -18,10 +18,13 @@
|
||||
*/
|
||||
#include "card_reader.h"
|
||||
#include "oslib/oslib.h"
|
||||
#include "oslib/directory.h"
|
||||
#include "hw/sh4/modules/modules.h"
|
||||
#include "hw/maple/maple_cfg.h"
|
||||
#include "hw/maple/maple_devs.h"
|
||||
#include "oslib/i18n.h"
|
||||
#include "stdclass.h"
|
||||
#include "input/gamepad_device.h"
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <cerrno>
|
||||
@@ -40,6 +43,8 @@ public:
|
||||
INFO_LOG(NAOMI, "Card inserted");
|
||||
}
|
||||
|
||||
virtual void ejectCard() {}
|
||||
|
||||
protected:
|
||||
virtual bool loadCard() = 0;
|
||||
|
||||
@@ -61,16 +66,8 @@ protected:
|
||||
void saveCard(const u8 *cardData, u32 len)
|
||||
{
|
||||
std::string path = hostfs::getArcadeFlashPath() + ".card";
|
||||
FILE *fp = nowide::fopen(path.c_str(), "wb");
|
||||
if (fp == nullptr)
|
||||
{
|
||||
WARN_LOG(NAOMI, "Can't create card file %s: errno %d", path.c_str(), errno);
|
||||
return;
|
||||
}
|
||||
INFO_LOG(NAOMI, "Saving card file to %s", path.c_str());
|
||||
if (fwrite(cardData, 1, len, fp) != len)
|
||||
WARN_LOG(NAOMI, "Truncated write to file: %s", path.c_str());
|
||||
fclose(fp);
|
||||
safeFullSave(path, cardData, len);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
@@ -655,6 +652,343 @@ const u8 ClubKartCardReader::CommandBytes[][2]
|
||||
|
||||
static std::unique_ptr<CardReaderWriter> cardReader;
|
||||
|
||||
#define HWLOG(...) DEBUG_LOG(NAOMI, __VA_ARGS__)
|
||||
|
||||
// WCCF, Dragon Treasure IC card reader/writer (Saxa HW210), hooked to the SH4 SCIF port
|
||||
class SaxaHW210 : public CardReaderWriter, public SerialPort::Pipe
|
||||
{
|
||||
public:
|
||||
SaxaHW210() {
|
||||
SCIFSerialPort::Instance().setPipe(this);
|
||||
}
|
||||
~SaxaHW210() override {
|
||||
SCIFSerialPort::Instance().setPipe(nullptr);
|
||||
kcode[1] |= DC_DPAD_UP;
|
||||
}
|
||||
|
||||
void write(u8 b) override
|
||||
{
|
||||
inBuffer.push_back(b);
|
||||
if (inBuffer.size() < 6)
|
||||
return;
|
||||
|
||||
u32 size = (inBuffer[2] << 8) + inBuffer[3] + 5;
|
||||
if (inBuffer.size() < size)
|
||||
return;
|
||||
|
||||
u8 crc = calcCrc(inBuffer.begin(), inBuffer.begin() + size - 1);
|
||||
if (crc != inBuffer[size - 1]) {
|
||||
WARN_LOG(NAOMI, "Invalid CRC for command %02x size %d", inBuffer[1], size);
|
||||
}
|
||||
else
|
||||
{
|
||||
dumpPkt();
|
||||
size_t outStart = outBuffer.size();
|
||||
const u16 arg = (inBuffer[4] << 8) | inBuffer[5];
|
||||
switch (inBuffer[1])
|
||||
{
|
||||
case 0x10: // sz 0 arg 1000
|
||||
sendCmd(0x10); // returned arg should be != 0x20
|
||||
break;
|
||||
case 0x11: // sz 8 arg 6: 00 00 00 00 00 00
|
||||
sendCmd(0x11);
|
||||
break;
|
||||
case 0x14: // NFC field status?
|
||||
// sz 0 arg 1400:
|
||||
// called after returning arg=0x20 in cmd 10
|
||||
sendCmd(0x14, 0, cardInserted);
|
||||
break;
|
||||
case 0x20: // Select card slot
|
||||
// sz 8 arg 1: 00 00 00 00 00 00
|
||||
// arg is selected card slot#. Normally 1 but set to 0 to access other card
|
||||
// expects arg=0, no payload
|
||||
if (!cardInserted || (!cardExpired && arg == 0)) {
|
||||
sendCmd(0x20, 0, 0x8000);
|
||||
}
|
||||
else {
|
||||
cardIndex = arg;
|
||||
sendCmd(0x20);
|
||||
}
|
||||
break;
|
||||
case 0x21: // sz 10 arg 0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
// Returning arg=1 indicates that 2 cards are in the reader
|
||||
// payload is some card identifier, sent back in cmd 22
|
||||
sendCmd(0x21, 8, cardExpired && cardIndex == 1);
|
||||
outBuffer.push_back(0);
|
||||
outBuffer.push_back(cardIndex);
|
||||
outBuffer.push_back(0);
|
||||
outBuffer.push_back(cardIndex);
|
||||
for (int i = 0; i < 4; i++)
|
||||
outBuffer.push_back(0);
|
||||
break;
|
||||
case 0x22: // sz 8 arg 0: 00 00 00 00 00 00
|
||||
// expects arg=0
|
||||
// payload[0-1] is used as cmd arg for all operations below
|
||||
sendCmd(0x22, 8);
|
||||
outBuffer.push_back(0);
|
||||
outBuffer.push_back(arg);
|
||||
for (int i = 0; i < 6; i++)
|
||||
outBuffer.push_back(0);
|
||||
break;
|
||||
|
||||
case 0x24: // Read single block
|
||||
{
|
||||
// sz 8 arg 0: 00 04 00 00 00 00
|
||||
if (!cardInserted) {
|
||||
sendCmd(0x24, 0, 0x8000);
|
||||
break;
|
||||
}
|
||||
sendCmd(0x24, 8);
|
||||
u32 offset = inBuffer[7] * 8;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
if (offset + i < CARD_SZ)
|
||||
outBuffer.push_back(cardData[arg][offset + i]);
|
||||
else
|
||||
outBuffer.push_back(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x25: // Write single block
|
||||
// sz 10 arg 0: 00 00 00 04 95 71 25 76 12 34 56 78 00 00
|
||||
if (!cardInserted) {
|
||||
sendCmd(0x25, 0, 0x8000);
|
||||
break;
|
||||
}
|
||||
if (inBuffer[9] < 6) {
|
||||
// write-protected
|
||||
HWLOG("HW210: Block %d write-protected", inBuffer[9]);
|
||||
sendCmd(0x25, 0, 0x8001);
|
||||
}
|
||||
else {
|
||||
writeCard(arg, inBuffer[9], 1, &inBuffer[10]);
|
||||
sendCmd(0x25);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x26: // Decrement remaining writes
|
||||
// sz 8 arg 800: 00 05 00 01 00 00
|
||||
// sz 8 arg 800: 00 05 ff fd 00 00 after resigning
|
||||
{
|
||||
if (!cardInserted) {
|
||||
sendCmd(0x26, 0, 0x8000);
|
||||
break;
|
||||
}
|
||||
u8 *p = &cardData[arg][inBuffer[7] * 8];
|
||||
u16 remain = (p[0] << 8) | p[1];
|
||||
const u16 count = (inBuffer[8] << 8) | inBuffer[9];
|
||||
remain -= count;
|
||||
p[0] = remain >> 8;
|
||||
p[1] = remain & 0xff;
|
||||
// After transferring an expired club card, the game will decrease
|
||||
// the source card write count to 0. This prevents the card from being
|
||||
// transferred again. In the Flycast case, the old card doesn't exist anymore
|
||||
// so we ignore it.
|
||||
saveCard(arg);
|
||||
sendCmd(0x26);
|
||||
break;
|
||||
}
|
||||
case 0x27: // sz 8 arg 800: 00 00 00 00 00 00
|
||||
sendCmd(0x27);
|
||||
break;
|
||||
case 0x31: // sz 8 arg 800: 00 00 00 06 00 00
|
||||
sendCmd(0x31);
|
||||
break;
|
||||
case 0x33: // Get remaining writes
|
||||
{
|
||||
// sz 8 arg 0: 00 05 00 00 00 00
|
||||
// expects arg=8008 or 0
|
||||
if (!cardInserted) {
|
||||
sendCmd(0x33, 0, 0x8000);
|
||||
break;
|
||||
}
|
||||
sendCmd(0x33, 8);
|
||||
// must be ffff for blank cards
|
||||
const u8 *p = &cardData[arg][inBuffer[7] * 8];
|
||||
outBuffer.push_back(p[0]);
|
||||
outBuffer.push_back(p[1]);
|
||||
for (int i = 0; i < 6; i++)
|
||||
outBuffer.push_back(0);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x34: // Read card
|
||||
{
|
||||
// sz 8 arg 0: 00 08 00 1e 00 00
|
||||
// offset size (in 8-byte blocks)
|
||||
if (!cardInserted) {
|
||||
sendCmd(0x34, 0, 0x8000);
|
||||
break;
|
||||
}
|
||||
const u32 offset = ((inBuffer[6] << 8) | inBuffer[7]) * 8;
|
||||
u32 size = ((inBuffer[8] << 8) | inBuffer[9]) * 8;
|
||||
size = std::min(size, (u32)CARD_SZ - offset);
|
||||
sendCmd(0x34, size);
|
||||
for (u32 i = 0; i < size; i++)
|
||||
outBuffer.push_back(cardData[arg][offset + i]);
|
||||
break;
|
||||
}
|
||||
case 0x35: // Write card
|
||||
{
|
||||
if (!cardInserted) {
|
||||
sendCmd(0x35, 0, 0x8000);
|
||||
break;
|
||||
}
|
||||
const u32 block = (inBuffer[6] << 8) | inBuffer[7];
|
||||
if (block < 6) {
|
||||
// write-protected
|
||||
HWLOG("HW210: Block %d write-protected", block);
|
||||
sendCmd(0x35, 0, 0x8001);
|
||||
}
|
||||
else {
|
||||
writeCard(arg, block, (inBuffer[8] << 8) | inBuffer[9],
|
||||
&inBuffer[10]);
|
||||
sendCmd(0x35);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
WARN_LOG(NAOMI, "HW210: unhandled command %02x", inBuffer[1]);
|
||||
break;
|
||||
}
|
||||
if (outBuffer.size() != outStart)
|
||||
outBuffer.push_back(calcCrc(outBuffer.begin() + outStart, outBuffer.end()));
|
||||
}
|
||||
inBuffer.erase(inBuffer.begin(), inBuffer.begin() + size);
|
||||
}
|
||||
|
||||
int available() override {
|
||||
return outBuffer.size();
|
||||
}
|
||||
|
||||
u8 read() override
|
||||
{
|
||||
if (outBuffer.empty())
|
||||
return 0;
|
||||
u8 b = outBuffer.front();
|
||||
outBuffer.pop_front();
|
||||
return b;
|
||||
}
|
||||
|
||||
void ejectCard() override
|
||||
{
|
||||
if (!cardInserted)
|
||||
return;
|
||||
NOTICE_LOG(NAOMI, "Card ejected");
|
||||
os_notify(i18n::T("Card ejected"), 2000);
|
||||
cardInserted = false;
|
||||
cardExpired = false;
|
||||
memset(cardData[0], 0, CARD_SZ);
|
||||
memset(cardData[1], 0, CARD_SZ);
|
||||
kcode[1] |= DC_DPAD_UP;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool loadCard() override
|
||||
{
|
||||
memset(cardData[0], 0, CARD_SZ);
|
||||
memset(cardData[1], 0, CARD_SZ);
|
||||
bool ret = CardReaderWriter::loadCard(cardData[1], CARD_SZ);
|
||||
if (!ret) {
|
||||
initCard(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
cardExpired = cardData[1][0x28] == 0 && cardData[1][0x29] == 1;
|
||||
if (cardExpired) {
|
||||
memcpy(cardData[0], cardData[1], CARD_SZ);
|
||||
initCard(1);
|
||||
}
|
||||
}
|
||||
kcode[1] &= ~DC_DPAD_UP; // card sensor
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
void dumpPkt()
|
||||
{
|
||||
std::string payload;
|
||||
u32 size = (inBuffer[2] << 8) + inBuffer[3];
|
||||
for (u32 i = 2; i < size; i++)
|
||||
payload += strprintf("%02x ", inBuffer[4 + i]);
|
||||
if (inBuffer[1] != 0x24 || inBuffer[6] != 0 || inBuffer[7] != 0)
|
||||
HWLOG("HW210: cmd %02x sz %x arg %x: %s", inBuffer[1], size, (inBuffer[4] << 8) + inBuffer[5], payload.c_str());
|
||||
}
|
||||
|
||||
void sendCmd(u8 cmd, u16 payloadSz = 0, u16 arg = 0)
|
||||
{
|
||||
outBuffer.push_back(0x10); // status = OK? FF seems to be accepted too
|
||||
outBuffer.push_back(cmd);
|
||||
outBuffer.push_back((payloadSz + 2) >> 8);
|
||||
outBuffer.push_back(payloadSz + 2);
|
||||
outBuffer.push_back(arg >> 8);
|
||||
outBuffer.push_back(arg);
|
||||
}
|
||||
|
||||
void initCard(int index)
|
||||
{
|
||||
u8 *card = cardData[index];
|
||||
memset(card, 0, CARD_SZ);
|
||||
|
||||
// Block 0: factory-set UID
|
||||
for (int i = 0; i < 8; i++)
|
||||
card[i] = rand() & 0xff;
|
||||
// Block 4 must start with 95 71 25 7x ?? ?? ?? ??
|
||||
// ? can be any digit [0-9]
|
||||
// x must be the sum of all ? digits % 10
|
||||
static constexpr u8 UID[] = { 0x95, 0x71, 0x25, 0x70 };
|
||||
const u32 offset = 4 * 8;
|
||||
memcpy(&card[offset], UID, sizeof(UID));
|
||||
int sum = 0;
|
||||
srand(time(nullptr) + index * 2);
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
const u8 n = rand() % 100;
|
||||
card[i + offset + 4] = ((n / 10) << 4) | (n % 10);
|
||||
sum += n / 10 + n % 10;
|
||||
}
|
||||
card[offset + 3] = (card[offset + 3] & 0xf0) | (sum % 10);
|
||||
// Block 5: write counter
|
||||
card[0x28] = 0xff;
|
||||
card[0x29] = 0xff;
|
||||
// Block 6: copy of block #0 UID
|
||||
memcpy(card + 0x30, card, 8);
|
||||
NOTICE_LOG(NAOMI, "WCCF IC card %d created: %02x %02x %02x %02x %02x", index, card[offset + 3],
|
||||
card[offset + 4], card[offset + 5],
|
||||
card[offset + 6], card[offset + 7]);
|
||||
}
|
||||
|
||||
void writeCard(int cardIndex, u32 offset, u32 blocks, const u8 *data)
|
||||
{
|
||||
offset *= 8; // in bytes
|
||||
if (offset >= CARD_SZ)
|
||||
return;
|
||||
|
||||
u32 size = blocks * 8;
|
||||
size = std::min(size, (u32)CARD_SZ - offset);
|
||||
if (memcmp(&cardData[cardIndex][offset], data, size) == 0)
|
||||
return;
|
||||
|
||||
memcpy(&cardData[cardIndex][offset], data, size);
|
||||
saveCard(cardIndex);
|
||||
}
|
||||
|
||||
void saveCard(int cardIndex)
|
||||
{
|
||||
if (cardIndex == 1)
|
||||
CardReaderWriter::saveCard(cardData[1], CARD_SZ);
|
||||
else
|
||||
WARN_LOG(NAOMI, "Card #0 not saved");
|
||||
}
|
||||
|
||||
static constexpr size_t CARD_SZ = 0x270;
|
||||
u8 cardData[2][CARD_SZ] {};
|
||||
int cardIndex = 1;
|
||||
bool cardExpired = false;
|
||||
};
|
||||
|
||||
void initdInit() {
|
||||
term();
|
||||
cardReader = std::make_unique<InitialDCardReader>();
|
||||
@@ -674,6 +1008,11 @@ void clubkInit() {
|
||||
cardReader = std::make_unique<ClubKartCardReader>();
|
||||
}
|
||||
|
||||
void wccfInit() {
|
||||
term();
|
||||
cardReader = std::make_unique<SaxaHW210>();
|
||||
}
|
||||
|
||||
void term() {
|
||||
cardReader.reset();
|
||||
}
|
||||
@@ -765,6 +1104,11 @@ void insertCard(int playerNum)
|
||||
else
|
||||
insertRfidCard(playerNum);
|
||||
}
|
||||
void ejectCard()
|
||||
{
|
||||
if (cardReader != nullptr)
|
||||
cardReader->ejectCard();
|
||||
}
|
||||
|
||||
bool readerAvailable()
|
||||
{
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace card_reader {
|
||||
void initdInit();
|
||||
void derbyInit();
|
||||
void clubkInit();
|
||||
void wccfInit();
|
||||
void term();
|
||||
|
||||
void barcodeInit();
|
||||
@@ -33,6 +34,7 @@ std::string barcodeGetCard();
|
||||
void barcodeSetCard(const std::string& card);
|
||||
|
||||
void insertCard(int playerNum);
|
||||
void ejectCard();
|
||||
bool readerAvailable();
|
||||
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "dinokich_card.h"
|
||||
#include "cfg/option.h"
|
||||
#include "oslib/oslib.h"
|
||||
#include "oslib/directory.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace systemsp
|
||||
@@ -385,16 +386,7 @@ DinokichCardReader::Reply DinokichCardReader::on_63(const std::vector<u8>& f) {
|
||||
void DinokichCardReader::saveCard(int slotId)
|
||||
{
|
||||
Slot& slot = slots_[slotId];
|
||||
FILE *f = nowide::fopen(getCardDataPath(index + slotId).c_str(), "wb");
|
||||
if (f != nullptr)
|
||||
{
|
||||
if (fwrite(slot.eeprom.data(), 1, slot.eeprom.size(), f) != slot.eeprom.size())
|
||||
WARN_LOG(NAOMI, "Rfid card %d: truncated write", index);
|
||||
fclose(f);
|
||||
}
|
||||
else {
|
||||
WARN_LOG(NAOMI, "Card saved failed to %s", getCardDataPath(index).c_str());
|
||||
}
|
||||
safeFullSave(getCardDataPath(index + slotId), slot.eeprom.data(), slot.eeprom.size());
|
||||
}
|
||||
|
||||
void DinokichCardReader::serialize(Serializer& ser) const
|
||||
@@ -445,12 +437,9 @@ void DinokichCardReader::deserialize(Deserializer& deser)
|
||||
deser.skip<u8>(); // expectedBytes
|
||||
deser >> size;
|
||||
deser.skip(size);
|
||||
if (deser.version() >= Deserializer::V41)
|
||||
{
|
||||
deser.skip<int>(); // state
|
||||
deser.skip<int>(); // rowCounter
|
||||
deser.skip(128); // cardData
|
||||
}
|
||||
deser.skip<int>(); // state
|
||||
deser.skip<int>(); // rowCounter
|
||||
deser.skip(128); // cardData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "naomi.h"
|
||||
#include "oslib/i18n.h"
|
||||
#include <algorithm>
|
||||
#include <zlib.h>
|
||||
|
||||
/*
|
||||
|
||||
@@ -539,26 +540,28 @@ void GDCartridge::device_start(LoadProgress *progress, std::vector<u8> *digest)
|
||||
|
||||
// directory
|
||||
u8 dir_sector[2048];
|
||||
// find data of file
|
||||
u32 file_size;
|
||||
|
||||
if (netpic == 0) {
|
||||
// find data of file
|
||||
if (netpic == 0)
|
||||
{
|
||||
u32 dir = ((buffer[0x2 + 0] << 0) |
|
||||
(buffer[0x2 + 1] << 8) |
|
||||
(buffer[0x2 + 2] << 16) |
|
||||
(buffer[0x2 + 3] << 24));
|
||||
|
||||
read_gdrom(gdrom.get(), dir, dir_sector);
|
||||
find_file(name, dir_sector, file_start, file_size);
|
||||
find_file(name, dir_sector, file_start, fileSize);
|
||||
|
||||
if (file_start && (file_size == 0x100)) {
|
||||
if (file_start && fileSize == 0x100) {
|
||||
// read file
|
||||
read_gdrom(gdrom.get(), file_start, buffer);
|
||||
// get "rom" file name
|
||||
memset(name, '\0', 128);
|
||||
memcpy(name, buffer + 0xc0, FILENAME_LENGTH - 1);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
bool found = false;
|
||||
u32 i = 0;
|
||||
while (i < 2048 && buffer[i] != 0)
|
||||
@@ -583,15 +586,15 @@ void GDCartridge::device_start(LoadProgress *progress, std::vector<u8> *digest)
|
||||
}
|
||||
}
|
||||
|
||||
find_file(name, dir_sector, file_start, file_size);
|
||||
find_file(name, dir_sector, file_start, fileSize);
|
||||
if (file_start == 0)
|
||||
// mj1: filename in the pic is incorrect, probably because the disk isn't supposed to be run like this?
|
||||
// so grab the first .BIN and load it.
|
||||
find_file("*.BIN", dir_sector, file_start, file_size);
|
||||
find_file("*.BIN", dir_sector, file_start, fileSize);
|
||||
|
||||
if (file_start != 0)
|
||||
{
|
||||
u32 file_rounded_size = (file_size + 2047) & ~2048;
|
||||
u32 file_rounded_size = (fileSize + 2047) & ~2048;
|
||||
for (dimm_data_size = 4096; dimm_data_size < file_rounded_size; dimm_data_size <<= 1)
|
||||
;
|
||||
dimm_data = (u8 *)malloc(dimm_data_size);
|
||||
@@ -605,6 +608,20 @@ void GDCartridge::device_start(LoadProgress *progress, std::vector<u8> *digest)
|
||||
loadedSegments.end(), true);
|
||||
|
||||
des_generate_subkeys(rev64(key), des_subkeys);
|
||||
|
||||
if (fullLoad)
|
||||
{
|
||||
for (unsigned offset = 0; offset < fileSize; offset += SEGMENT_SIZE)
|
||||
{
|
||||
if (progress != nullptr)
|
||||
{
|
||||
if (progress->cancelled)
|
||||
throw LoadCancelledException();
|
||||
progress->progress = (float)offset / fileSize;
|
||||
}
|
||||
loadSegments(offset, SEGMENT_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dimm_data)
|
||||
@@ -620,13 +637,16 @@ void GDCartridge::loadSegments(u32 offset, u32 size)
|
||||
if (loadedSegments[segment])
|
||||
continue;
|
||||
DEBUG_LOG(NAOMI, "Loading segment %d", segment);
|
||||
const u32 start = segment * SEGMENT_SIZE;
|
||||
// load data
|
||||
read_gdrom(gdrom.get(), file_start + (segment * SEGMENT_SIZE) / 2048,
|
||||
dimm_data + segment * SEGMENT_SIZE,
|
||||
read_gdrom(gdrom.get(), file_start + start / 2048,
|
||||
dimm_data + start,
|
||||
SEGMENT_SIZE / 2048,
|
||||
nullptr);
|
||||
if (start < fileSize)
|
||||
crc = crc32(crc, dimm_data + start, std::min(SEGMENT_SIZE, fileSize - start));
|
||||
// decrypt loaded data
|
||||
u64 *pData = (u64 *)(dimm_data + segment * SEGMENT_SIZE);
|
||||
u64 *pData = (u64 *)(dimm_data + start);
|
||||
for (u32 i = 0; i < SEGMENT_SIZE; i += 8, pData++)
|
||||
*pData = des_encrypt_decrypt<true>(*pData, des_subkeys);
|
||||
loadedSegments[segment] = true;
|
||||
@@ -759,18 +779,18 @@ void GDCartridge::process()
|
||||
break;
|
||||
case 1: // network commands
|
||||
WARN_LOG(NAOMI, "Network command received cmd %x. Need full NetDIMM?", cmd);
|
||||
returnToNaomi(true, 0, -1);
|
||||
returnToNaomi(dimm_command & 0x7e00, true, 0, -1);
|
||||
break;
|
||||
default:
|
||||
WARN_LOG(NAOMI, "Unknown DIMM command group %d cmd %x", cmdGroup, cmd);
|
||||
returnToNaomi(true, 0, -1);
|
||||
returnToNaomi(dimm_command & 0x7e00, true, 0, -1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GDCartridge::returnToNaomi(bool failed, u16 offsetl, u32 parameter)
|
||||
void GDCartridge::returnToNaomi(u16 cmd, bool failed, u16 offsetl, u32 parameter)
|
||||
{
|
||||
dimm_command = ((dimm_command & 0x7e00) + 0x400) | (failed ? 0xff : 0x4);
|
||||
dimm_command = (cmd + 0x400) | (failed ? 0xff : 0x4);
|
||||
dimm_offsetl = offsetl;
|
||||
dimm_parameterh = parameter >> 16;
|
||||
dimm_parameterl = parameter;
|
||||
|
||||
@@ -33,10 +33,13 @@ public:
|
||||
void Serialize(Serializer &ser) const override;
|
||||
void Deserialize(Deserializer &deser) override;
|
||||
|
||||
u32 getFileSize() const { return fileSize; }
|
||||
u32 getCrc() const { return ~crc; }
|
||||
|
||||
protected:
|
||||
virtual void process();
|
||||
virtual int schedCallback();
|
||||
void returnToNaomi(bool failed, u16 offsetl, u32 parameter);
|
||||
void returnToNaomi(u16 cmd, bool failed, u16 offsetl, u32 parameter);
|
||||
|
||||
template<typename T>
|
||||
void peek(u32 address)
|
||||
@@ -69,6 +72,9 @@ protected:
|
||||
u16 dimm_parameterh;
|
||||
static constexpr u16 DIMM_STATUS = 0x111;
|
||||
int schedId;
|
||||
u32 fileSize = 0;
|
||||
u32 crc = 0;
|
||||
bool fullLoad = false;
|
||||
|
||||
private:
|
||||
const char *gdrom_name = nullptr;
|
||||
|
||||
@@ -323,18 +323,10 @@ private:
|
||||
deser >> maxBet;
|
||||
deser >> minBet;
|
||||
deser >> addBet;
|
||||
if (deser.version() >= Deserializer::V39)
|
||||
{
|
||||
deser >> paidAmount;
|
||||
deser >> wonAmount;
|
||||
deser >> betButton;
|
||||
deser >> curBase;
|
||||
}
|
||||
else
|
||||
{
|
||||
paidAmount = 0;
|
||||
wonAmount = 0;
|
||||
}
|
||||
deser >> paidAmount;
|
||||
deser >> wonAmount;
|
||||
deser >> betButton;
|
||||
deser >> curBase;
|
||||
}
|
||||
|
||||
u32 expectedBytes = 0;
|
||||
|
||||
+10
-22
@@ -269,30 +269,18 @@ void serialize(Serializer& ser)
|
||||
|
||||
void deserialize(Deserializer& deser)
|
||||
{
|
||||
if (deser.version() >= Deserializer::V27)
|
||||
{
|
||||
if (initialized) {
|
||||
deser >> midiTxBuf;
|
||||
deser >> midiTxBufIndex;
|
||||
}
|
||||
else if (deser.version() < Deserializer::V51) {
|
||||
deser.skip(4); // midiTxBuf
|
||||
deser.skip<u32>(); // midiTxBufIndex
|
||||
}
|
||||
if (initialized) {
|
||||
deser >> midiTxBuf;
|
||||
deser >> midiTxBufIndex;
|
||||
}
|
||||
else {
|
||||
midiTxBufIndex = 0;
|
||||
}
|
||||
if (deser.version() >= Deserializer::V34)
|
||||
{
|
||||
if (initialized)
|
||||
deser >> calibrating;
|
||||
else if (deser.version() < Deserializer::V51)
|
||||
deser.skip<bool>(); // calibrating
|
||||
}
|
||||
else {
|
||||
calibrating = false;
|
||||
else if (deser.version() < Deserializer::V51) {
|
||||
deser.skip(4); // midiTxBuf
|
||||
deser.skip<u32>(); // midiTxBufIndex
|
||||
}
|
||||
if (initialized)
|
||||
deser >> calibrating;
|
||||
else if (deser.version() < Deserializer::V51)
|
||||
deser.skip<bool>(); // calibrating
|
||||
if (initialized)
|
||||
{
|
||||
maxSpring = 0x7f;
|
||||
|
||||
+30
-41
@@ -25,6 +25,7 @@
|
||||
#include "naomi_cart.h"
|
||||
#include "naomi_regs.h"
|
||||
#include "naomi_m3comm.h"
|
||||
#include "naomi_flashrom.h"
|
||||
#include "multiboard.h"
|
||||
#include "serialize.h"
|
||||
#include "network/output.h"
|
||||
@@ -126,12 +127,21 @@ static int naomiDmaSched(int tag, int sch_cycl, int jitter, void *arg)
|
||||
void* ptr = CurrentCartridge->GetDmaPtr(block_len);
|
||||
if (block_len == 0)
|
||||
{
|
||||
INFO_LOG(NAOMI, "Aborted DMA transfer. Read past end of cart?");
|
||||
for (u32 i = 0; i < len; i += 8, start += 8)
|
||||
addrspace::write64(start, 0);
|
||||
if (SB_GDDIR == 1)
|
||||
{
|
||||
INFO_LOG(NAOMI, "Aborted DMA transfer. Read past end of cart?");
|
||||
for (u32 i = 0; i < len; i += 8, start += 8)
|
||||
addrspace::write64(start, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
WriteMemBlock_nommu_ptr(start, (u32*)ptr, block_len);
|
||||
if (SB_GDDIR == 1) {
|
||||
WriteMemBlock_nommu_ptr(start, (u32*)ptr, block_len);
|
||||
}
|
||||
else {
|
||||
const void *src = GetMemPtr(start, block_len);
|
||||
memcpy(ptr, src, block_len);
|
||||
}
|
||||
CurrentCartridge->AdvancePtr(block_len);
|
||||
len -= block_len;
|
||||
start += block_len;
|
||||
@@ -165,7 +175,6 @@ static void Naomi_DmaStart(u32 addr, u32 data)
|
||||
else if ((m3comm == nullptr || !m3comm->DmaStart(addr, data)) && CurrentCartridge != nullptr)
|
||||
{
|
||||
DEBUG_LOG(NAOMI, "NAOMI-DMA start addr %08X len %x", SB_GDSTAR, SB_GDLEN);
|
||||
verify(1 == SB_GDDIR);
|
||||
SB_GDST = 1;
|
||||
SB_GDSTARD = SB_GDSTAR & 0x1FFFFFE0;
|
||||
SB_GDLEND = 0;
|
||||
@@ -215,7 +224,19 @@ void naomi_reg_Init()
|
||||
'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'
|
||||
};
|
||||
romSerialId.setData(romSerialData);
|
||||
#if 0
|
||||
// Generate random main board serial id
|
||||
u8 mainSerialData[sizeof(romSerialData)];
|
||||
memcpy(mainSerialData, romSerialData, sizeof(romSerialData));
|
||||
for (unsigned i = 52; i < 52 + 16; i++)
|
||||
mainSerialData[i] = (rand() % 10) + '0';
|
||||
u16 crc = eeprom_crc(&mainSerialData[22], 46);
|
||||
mainSerialData[20] = crc >> 8;
|
||||
mainSerialData[21] = crc;
|
||||
mainSerialId.setData(mainSerialData);
|
||||
#else
|
||||
mainSerialId.setData(romSerialData);
|
||||
#endif
|
||||
if (dmaSchedId == -1)
|
||||
dmaSchedId = sh4_sched_register(0, naomiDmaSched);
|
||||
}
|
||||
@@ -236,11 +257,11 @@ const u8 *getGameSerialId()
|
||||
|
||||
void naomi_reg_Term()
|
||||
{
|
||||
naomi_cart_Close();
|
||||
multiboard.reset();
|
||||
m3comm.reset();
|
||||
networkOutput.term();
|
||||
if (dmaSchedId != -1)
|
||||
sh4_sched_unregister(dmaSchedId);
|
||||
sh4_sched_unregister(dmaSchedId);
|
||||
dmaSchedId = -1;
|
||||
midiffb::term();
|
||||
}
|
||||
@@ -289,40 +310,8 @@ void naomi_Serialize(Serializer& ser)
|
||||
}
|
||||
void naomi_Deserialize(Deserializer& deser)
|
||||
{
|
||||
if (deser.version() < Deserializer::V40)
|
||||
{
|
||||
deser.skip<u32>(); // GSerialBuffer
|
||||
deser.skip<u32>(); // BSerialBuffer
|
||||
deser.skip<int>(); // GBufPos
|
||||
deser.skip<int>(); // BBufPos
|
||||
deser.skip<int>(); // GState
|
||||
deser.skip<int>(); // BState
|
||||
deser.skip<int>(); // GOldClk
|
||||
deser.skip<int>(); // BOldClk
|
||||
deser.skip<int>(); // BControl
|
||||
deser.skip<int>(); // BCmd
|
||||
deser.skip<int>(); // BLastCmd
|
||||
deser.skip<int>(); // GControl
|
||||
deser.skip<int>(); // GCmd
|
||||
deser.skip<int>(); // GLastCmd
|
||||
deser.skip<int>(); // SerStep
|
||||
deser.skip<int>(); // SerStep2
|
||||
deser.skip(69); // BSerial
|
||||
deser.skip(69); // GSerial
|
||||
}
|
||||
else
|
||||
{
|
||||
mainSerialId.deserialize(deser);
|
||||
romSerialId.deserialize(deser);
|
||||
}
|
||||
if (deser.version() < Deserializer::V36)
|
||||
{
|
||||
deser.skip<u32>(); // reg_dimm_command;
|
||||
deser.skip<u32>(); // reg_dimm_offsetl;
|
||||
deser.skip<u32>(); // reg_dimm_parameterl;
|
||||
deser.skip<u32>(); // reg_dimm_parameterh;
|
||||
deser.skip<u32>(); // reg_dimm_status;
|
||||
}
|
||||
mainSerialId.deserialize(deser);
|
||||
romSerialId.deserialize(deser);
|
||||
atomiswave::deserialize(deser);
|
||||
midiffb::deserialize(deser);
|
||||
if (deser.version() >= Deserializer::V45)
|
||||
|
||||
+79
-122
@@ -278,7 +278,8 @@ static void loadMameRom(const std::string& path, const std::string& fileName, Lo
|
||||
GDCartridge *gdcart;
|
||||
if (strncmp(game->name, "vf4", 3) == 0
|
||||
|| strcmp(game->name, "mj1") == 0
|
||||
|| strncmp(game->name, "wccf", 4) == 0)
|
||||
|| strncmp(game->name, "wccf", 4) == 0
|
||||
|| strncmp(game->name, "dragntr", 7) == 0)
|
||||
gdcart = new NetDimm(game->size);
|
||||
else
|
||||
gdcart = new GDCartridge(game->size);
|
||||
@@ -466,143 +467,59 @@ static void loadDecryptedRom(const std::string& path, const std::string& fileNam
|
||||
}
|
||||
}
|
||||
|
||||
std::string folder;
|
||||
std::vector<std::string> files;
|
||||
std::vector<u32> fstart;
|
||||
std::vector<u32> fsize;
|
||||
u32 romSize = 0;
|
||||
// BIN/DAT loading
|
||||
hostfs::File* fp = hostfs::storage().openFile(path, "rb");
|
||||
if (fp == nullptr)
|
||||
throw FlycastException(strprintf(T("Error: can't open %s"), path.c_str()));
|
||||
|
||||
std::string extension = get_file_extension(fileName);
|
||||
if (extension == "lst")
|
||||
{
|
||||
// LST file
|
||||
hostfs::File *fl = hostfs::storage().openFile(path, "r");
|
||||
if (!fl)
|
||||
throw FlycastException(strprintf(T("Error: can't open %s"), path.c_str()));
|
||||
folder = hostfs::storage().getParentPath(path);
|
||||
fp->seek(0, SEEK_END);
|
||||
const u32 romSize = (u32)fp->tell();
|
||||
fp->seek(0, SEEK_SET);
|
||||
|
||||
char t[512];
|
||||
char* line = fl->gets(t, sizeof(t));
|
||||
if (!line)
|
||||
{
|
||||
delete fl;
|
||||
throw FlycastException(Ts("Error: Invalid LST file"));
|
||||
}
|
||||
|
||||
char* eon = strstr(line, "\n");
|
||||
if (!eon)
|
||||
DEBUG_LOG(NAOMI, "+Loading naomi rom that has no name");
|
||||
else
|
||||
{
|
||||
*eon = 0;
|
||||
DEBUG_LOG(NAOMI, "+Loading naomi rom : %s", line);
|
||||
}
|
||||
|
||||
line = fl->gets(t, sizeof(t));
|
||||
if (!line)
|
||||
{
|
||||
delete fl;
|
||||
throw FlycastException(Ts("Error: Invalid LST file"));
|
||||
}
|
||||
|
||||
while (line)
|
||||
{
|
||||
char filename[512];
|
||||
u32 addr, sz;
|
||||
if (sscanf(line, "\"%[^\"]\",%x,%x", filename, &addr, &sz) == 3)
|
||||
{
|
||||
files.emplace_back(filename);
|
||||
fstart.push_back(addr);
|
||||
fsize.push_back(sz);
|
||||
romSize = std::max(romSize, (addr + sz));
|
||||
}
|
||||
else if (line[0] != 0 && line[0] != '\n' && line[0] != '\r')
|
||||
WARN_LOG(NAOMI, "Warning: invalid line in .lst file: %s", line);
|
||||
|
||||
line = fl->gets(t, sizeof(t));
|
||||
}
|
||||
delete fl;
|
||||
}
|
||||
else
|
||||
{
|
||||
// BIN loading
|
||||
hostfs::File* fp = hostfs::storage().openFile(path, "rb");
|
||||
if (fp == nullptr)
|
||||
throw FlycastException(strprintf(T("Error: can't open %s"), path.c_str()));
|
||||
|
||||
fp->seek(0, SEEK_END);
|
||||
u32 file_size = (u32)fp->tell();
|
||||
INFO_LOG(NAOMI, "raw: %.2f MB set address space", romSize / 1024.f / 1024.f);
|
||||
if (romSize == 0) {
|
||||
delete fp;
|
||||
files.emplace_back(path);
|
||||
fstart.push_back(0);
|
||||
fsize.push_back(file_size);
|
||||
romSize = file_size;
|
||||
}
|
||||
|
||||
INFO_LOG(NAOMI, "+%zd romfiles, %.2f MB set address space", files.size(), romSize / 1024.f / 1024.f);
|
||||
if (romSize == 0)
|
||||
throw FlycastException(Ts("Invalid empty ROM"));
|
||||
|
||||
MD5Sum md5;
|
||||
}
|
||||
|
||||
// Allocate space for the rom
|
||||
u8 *romBase = (u8 *)malloc(romSize);
|
||||
if (romBase == nullptr)
|
||||
if (romBase == nullptr) {
|
||||
delete fp;
|
||||
throw FlycastException(Ts("Out of memory"));
|
||||
|
||||
bool load_error = false;
|
||||
|
||||
for (size_t i = 0; i<files.size(); i++)
|
||||
{
|
||||
hostfs::File *fp = nullptr;
|
||||
|
||||
if (files[i] != "null")
|
||||
{
|
||||
std::string filePath;
|
||||
if (folder.empty())
|
||||
filePath = files[i];
|
||||
else
|
||||
filePath = hostfs::storage().getSubPath(folder, files[i]);
|
||||
|
||||
fp = hostfs::storage().openFile(filePath, "rb");
|
||||
if (fp == nullptr)
|
||||
{
|
||||
ERROR_LOG(NAOMI, "Unable to open file %s: error %d", filePath.c_str(), errno);
|
||||
load_error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
u8* romDest = romBase + fstart[i];
|
||||
|
||||
if (fp == nullptr)
|
||||
{
|
||||
//printf("-Reserving ram at 0x%08X, size 0x%08X\n", fstart[i], fsize[i]);
|
||||
memset(romDest, -1, fsize[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
//printf("-Mapping \"%s\" at 0x%08X, size 0x%08X\n", files[i].c_str(), fstart[i], fsize[i]);
|
||||
bool mapped = fp->read(romDest, 1, fsize[i]) == fsize[i];
|
||||
if (config::GGPOEnable)
|
||||
md5.add(fp);
|
||||
delete fp;
|
||||
if (!mapped)
|
||||
{
|
||||
ERROR_LOG(NAOMI, "Unable to read file %s @ %08x size %x", files[i].c_str(),
|
||||
fstart[i], fsize[i]);
|
||||
load_error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (load_error)
|
||||
u32 offset = 0;
|
||||
bool error = false;
|
||||
while (offset < romSize && !error)
|
||||
{
|
||||
const u32 len = std::min(8192u, romSize - offset);
|
||||
error = fp->read(romBase + offset, len, 1) != 1;
|
||||
offset += len;
|
||||
if (progress != nullptr)
|
||||
{
|
||||
if (progress->cancelled)
|
||||
break;
|
||||
progress->progress = (float)offset / romSize;
|
||||
}
|
||||
}
|
||||
delete fp;
|
||||
if (progress->cancelled) {
|
||||
free(romBase);
|
||||
throw LoadCancelledException();
|
||||
}
|
||||
if (error)
|
||||
{
|
||||
ERROR_LOG(NAOMI, "Unable to read file %s size %d", path.c_str(), romSize);
|
||||
free(romBase);
|
||||
throw FlycastException(Ts("Error: Failed to load BIN/DAT file"));
|
||||
}
|
||||
if (config::GGPOEnable)
|
||||
{
|
||||
MD5Sum md5;
|
||||
md5.add(romBase, romSize);
|
||||
md5.getDigest(settings.network.md5.game);
|
||||
}
|
||||
|
||||
DEBUG_LOG(NAOMI, "Legacy ROM loaded successfully");
|
||||
|
||||
@@ -705,6 +622,11 @@ void naomi_cart_LoadRom(const std::string& path, const std::string& fileName, Lo
|
||||
{
|
||||
serialModemInit();
|
||||
}
|
||||
else if ((gameId.substr(0, 4) == "WCCF" && config::MultiboardSlaves <= 1)
|
||||
|| gameId.substr(0, 15) == "DRAGON TREASURE")
|
||||
{
|
||||
card_reader::wccfInit();
|
||||
}
|
||||
if (gameId == " TOUCH DE UNOH -------------"
|
||||
|| gameId == " TOUCH DE UNOH 2 -----------"
|
||||
|| (gameId == "F355 CHALLENGE JAPAN" && (config::MultiboardSlaves == 2 || romName == "f355"))
|
||||
@@ -753,6 +675,41 @@ void naomi_cart_LoadRom(const std::string& path, const std::string& fileName, Lo
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (gameId.substr(0, 4) == "WCCF" && config::MultiboardSlaves > 1)
|
||||
{
|
||||
// Main projectors are directly linked by a null-modem cable
|
||||
config::BattleCableEnable.override(true);
|
||||
if (!config::loadBool("naomi", "WCCFSlave"))
|
||||
{
|
||||
config::ActAsServer.override(true);
|
||||
config::NetworkServer.override("localhost:37392");
|
||||
config::LocalPort.override(37391);
|
||||
int x = config::loadInt("window", "left", (1920 - 640) / 2);
|
||||
int w = config::loadInt("window", "width", 640);
|
||||
std::string region = "config:Dreamcast.Region=" + std::to_string(config::Region);
|
||||
std::string left = "window:left=" + std::to_string(x + w);
|
||||
std::string title = "window:title=\"" + Ts("Right")
|
||||
+ " - " + settings.content.title + '"';
|
||||
std::string server = "naomi:WCCFServer=" + config::loadStr("naomi", "WCCFServer");
|
||||
|
||||
const char *args[] = {
|
||||
"-config", "naomi:WCCFSlave=yes",
|
||||
"-config", server.c_str(),
|
||||
"-config", region.c_str(),
|
||||
"-config", left.c_str(),
|
||||
"-config", title.c_str(),
|
||||
"-config", "network:MultiboardSlaves=2",
|
||||
settings.content.path.c_str()
|
||||
};
|
||||
os_RunInstance(std::size(args), args);
|
||||
}
|
||||
else
|
||||
{
|
||||
config::ActAsServer.override(false);
|
||||
config::NetworkServer.override("localhost:37391");
|
||||
config::LocalPort.override(37392);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
|
||||
@@ -20,11 +20,14 @@
|
||||
*/
|
||||
#include "naomi_flashrom.h"
|
||||
#include "hw/flashrom/nvmem.h"
|
||||
#include "hw/maple/maple_devs.h"
|
||||
#include "cfg/option.h"
|
||||
|
||||
static u16 eeprom_crc(const u8 *buf, int size)
|
||||
extern u8 *EEPROM;
|
||||
|
||||
// CRC-16-CCITT variant
|
||||
u16 eeprom_crc(const u8 *buf, int size)
|
||||
{
|
||||
// custom seed
|
||||
int n = 0xdebdeb00;
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
@@ -40,6 +43,7 @@ static u16 eeprom_crc(const u8 *buf, int size)
|
||||
n <<= 1;
|
||||
}
|
||||
}
|
||||
// extra round with a virtual null trailing byte
|
||||
for (int c = 0; c < 8; c++)
|
||||
{
|
||||
if (n & 0x80000000)
|
||||
|
||||
@@ -27,3 +27,4 @@ u8 read_naomi_eeprom(u32 offset);
|
||||
void write_naomi_eeprom(u32 offset, u8 value);
|
||||
void configure_naomi_eeprom(const RomBootID *bootId);
|
||||
void configure_maxspeed_flash(bool enableNetwork, bool master);
|
||||
u16 eeprom_crc(const u8 *buf, int size);
|
||||
@@ -39,11 +39,11 @@ const BIOS_t BIOS[] =
|
||||
"awbios",
|
||||
{
|
||||
// Sammy version
|
||||
{ 0, "bios0.ic23", 0x000000, 0x020000, 0x719b2b0b },
|
||||
{ 0, "bios0.ic23", 0x000000, 0x010000, 0x719b2b0b },
|
||||
//or
|
||||
//{ 0, "bios1.ic23", 0x000000, 0x020000, 0xd3e80a9f },
|
||||
//{ 0, "bios1.ic23", 0x000000, 0x010000, 0xd3e80a9f },
|
||||
// Sega version
|
||||
//{ 0, "fpr-24363.ic48", 0x000000, 0x020000, 0x82a105f0 },
|
||||
//{ 0, "fpr-24363.ic48", 0x000000, 0x010000, 0x82a105f0 },
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -6141,6 +6141,7 @@ const Game Games[] =
|
||||
{ "317-0329-jpn.pic", 0, 0x4000, 0x097f5f92 },
|
||||
},
|
||||
"cdp-10001c",
|
||||
&wccf_inputs,
|
||||
},
|
||||
// World Club Champion Football Serie A 2001-2002 DIMM FIRM Ver.3.03 (CDP-10003)
|
||||
{
|
||||
@@ -6173,6 +6174,7 @@ const Game Games[] =
|
||||
// SH4-based camera sensor board connected to MAPLE CN7
|
||||
},
|
||||
"cdv-10002",
|
||||
&wccf_inputs,
|
||||
},
|
||||
// World Club Champion Football Serie A 2002-2003 Ver.2.34 (Japan) (CDV-10008)
|
||||
{
|
||||
@@ -6188,6 +6190,7 @@ const Game Games[] =
|
||||
{ "317-0369-jpn.pic", 0, 0x4000, 0xfb3672ba },
|
||||
},
|
||||
"cdv-10008",
|
||||
&wccf_inputs,
|
||||
},
|
||||
// World Club Champion Football European Clubs 2004-2005 (Asia) (CDV-10013)
|
||||
{
|
||||
@@ -6203,6 +6206,7 @@ const Game Games[] =
|
||||
{ "317-0419-jpn.pic", 0, 0x4000, 0x04fae86b },
|
||||
},
|
||||
"cdv-10013",
|
||||
&wccf_inputs,
|
||||
},
|
||||
// World Club Champion Football European Clubs 2004-2005 Ver.1.1 (Export) (CDV-10015)
|
||||
{
|
||||
@@ -6218,6 +6222,7 @@ const Game Games[] =
|
||||
{ "317-0419-exp.pic", 0, 0x4000, 0x3f5e1445 },
|
||||
},
|
||||
"cdv-10015",
|
||||
&wccf_inputs,
|
||||
},
|
||||
// World Club Champion Football European Clubs 2004-2005 Ver.3.22 (Export) (CDV-10015P)
|
||||
{
|
||||
@@ -6233,6 +6238,7 @@ const Game Games[] =
|
||||
{ "317-0419-exp.pic", 0, 0x4000, 0x3f5e1445 },
|
||||
},
|
||||
"cdv-10015p",
|
||||
&wccf_inputs,
|
||||
},
|
||||
// World Club Champion Football European Clubs 2004-2005 Ver.1.1 (Japan) (CDV-10020)
|
||||
{
|
||||
@@ -6248,6 +6254,7 @@ const Game Games[] =
|
||||
{ "317-unk-jpn.pic", 0, 0x4000, 0xa4abd76b },
|
||||
},
|
||||
"cdv-10020",
|
||||
&wccf_inputs,
|
||||
},
|
||||
// World Club Champion Football European Clubs 2004-2005 Ver.1.2 (Japan) (CDV-10021)
|
||||
{
|
||||
@@ -6263,6 +6270,7 @@ const Game Games[] =
|
||||
{ "317-unk-jpn.pic", 0, 0x4000, 0xa4abd76b },
|
||||
},
|
||||
"cdv-10021",
|
||||
&wccf_inputs,
|
||||
},
|
||||
// World Club Champion Football European Clubs 2005-2006 (Japan) (CDV-10025)
|
||||
{
|
||||
@@ -6278,6 +6286,7 @@ const Game Games[] =
|
||||
{ "317-0456-jpn.pic", 0, 0x4000, 0xcf3bd834 },
|
||||
},
|
||||
"cdv-10025",
|
||||
&wccf_inputs,
|
||||
},
|
||||
// World Club Champion Football European Clubs 2005-2006 (Export) (CDV-10027)
|
||||
{
|
||||
@@ -6293,6 +6302,7 @@ const Game Games[] =
|
||||
{ "317-0456-exp.pic", 0, 0x4000, 0xfb7dd592 },
|
||||
},
|
||||
"cdv-10027",
|
||||
&wccf_inputs,
|
||||
},
|
||||
|
||||
// AtomisWave Roms
|
||||
|
||||
@@ -628,6 +628,33 @@ static InputDescriptors kick4csh_inputs = {
|
||||
NAO_BASE_BTN_DESC
|
||||
}
|
||||
};
|
||||
|
||||
static InputDescriptors wccf_inputs = {
|
||||
{
|
||||
// UDLR START
|
||||
// NAOMI_BTN0_KEY "PRESSURE"
|
||||
// NAOMI_BTN1_KEY "SHOOT"
|
||||
// NAOMI_BTN2_KEY card vendor error
|
||||
// NAOMI_BTN3_KEY card vendor empty
|
||||
// P2:
|
||||
// NAOMI_BTN0_KEY "KEEPER"
|
||||
// NAOMI_BTN1_KEY card vendor standby
|
||||
// NAOMI_BTN2_KEY "DATA"
|
||||
// UP club team card
|
||||
// DOWN SW1
|
||||
// LEFT SW2
|
||||
// RIGHT !card vendor sensor
|
||||
{ NAOMI_BTN0_KEY, "PRESSURE" },
|
||||
{ NAOMI_BTN1_KEY, "SHOOT" },
|
||||
{ NAOMI_BTN2_KEY, "SW1", 0, NAOMI_DOWN_KEY },
|
||||
{ NAOMI_BTN3_KEY, "KEEPER", 0, NAOMI_BTN0_KEY },
|
||||
{ NAOMI_BTN4_KEY, "DATA", 0, NAOMI_BTN2_KEY },
|
||||
NAO_DPAD_DESC
|
||||
NAO_START_DESC
|
||||
NAO_BASE_BTN_DESC
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// AtomisWave games
|
||||
//
|
||||
|
||||
+1055
-122
File diff suppressed because it is too large.
Load diff
+38
-25
@@ -19,11 +19,21 @@
|
||||
#pragma once
|
||||
#include "gdcartridge.h"
|
||||
#include "network/net_platform.h"
|
||||
#include "util/tsqueue.h"
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
//#define NET_TRACE
|
||||
|
||||
class NetDimmServer;
|
||||
|
||||
class NetDimm : public GDCartridge
|
||||
{
|
||||
public:
|
||||
enum ServerMsg { ControlRead, Reboot };
|
||||
|
||||
NetDimm(u32 size);
|
||||
~NetDimm();
|
||||
|
||||
void Init(LoadProgress *progress = nullptr, std::vector<u8> *digest = nullptr) override;
|
||||
|
||||
@@ -31,6 +41,15 @@ public:
|
||||
|
||||
void Deserialize(Deserializer &deser) override;
|
||||
|
||||
u32 getDimmSize() const { return dimm_data_size; }
|
||||
|
||||
u8 *getDimmData(u32 offset) {
|
||||
return &dimm_data[offset & (dimm_data_size - 1)];
|
||||
}
|
||||
|
||||
void controlRead(std::function<void(u32)> callback);
|
||||
void reboot();
|
||||
|
||||
protected:
|
||||
void process() override;
|
||||
int schedCallback() override;
|
||||
@@ -62,12 +81,7 @@ private:
|
||||
dimm_parameterh = value >> 16;
|
||||
}
|
||||
|
||||
sock_t getSocket(int idx)
|
||||
{
|
||||
if (idx < 1 || idx > (int)sockets.size())
|
||||
return INVALID_SOCKET;
|
||||
return sockets[idx - 1].fd;
|
||||
}
|
||||
sock_t getSocket(int idx);
|
||||
|
||||
bool isBusy() const
|
||||
{
|
||||
@@ -81,23 +95,7 @@ private:
|
||||
Socket() = default;
|
||||
Socket(sock_t fd) : fd(fd) {}
|
||||
|
||||
int close()
|
||||
{
|
||||
int rc = 0;
|
||||
if (fd != INVALID_SOCKET)
|
||||
rc = ::closesocket(fd);
|
||||
fd = INVALID_SOCKET;
|
||||
connecting = false;
|
||||
receiving = false;
|
||||
sending = false;
|
||||
connectTimeout = 0;
|
||||
connectTime = 0;
|
||||
sendTimeout = 0;
|
||||
sendTime = 0;
|
||||
recvTimeout = 0;
|
||||
recvTime = 0;
|
||||
return rc;
|
||||
}
|
||||
int close();
|
||||
|
||||
bool isClosed() const {
|
||||
return fd == INVALID_SOCKET;
|
||||
@@ -122,13 +120,28 @@ private:
|
||||
u64 recvTimeout = 0;
|
||||
u64 recvTime = 0;
|
||||
int lastError = 0;
|
||||
int port = 0;
|
||||
sockaddr *srcAddr = nullptr;
|
||||
socklen_t *addrLen = nullptr;
|
||||
#ifdef NET_TRACE
|
||||
void openTrace();
|
||||
void closeTrace();
|
||||
void traceRecv(const u8 *data, size_t len);
|
||||
void traceSend(const u8 *data, size_t len);
|
||||
|
||||
FILE *trcFile = nullptr;
|
||||
#endif
|
||||
};
|
||||
std::vector<Socket> sockets;
|
||||
int lastError = 0; // for socket() and select()
|
||||
bool dnsInProgress = false;
|
||||
u32 serverIp = 0; //0x0100007f for testing only
|
||||
bool finalTuned = false;
|
||||
bool wccf = false;
|
||||
|
||||
u32 dimmBufferOffset = 0x0f000000;
|
||||
std::unique_ptr<NetDimmServer> server;
|
||||
std::function<void(u32)> controlReadCallback;
|
||||
TsQueue<ServerMsg> serverQueue;
|
||||
|
||||
static constexpr int POLL_CYCLES = SH4_MAIN_CLOCK / 60;
|
||||
static constexpr int POLL_CYCLES = SH4_MAIN_CLOCK / 120;
|
||||
};
|
||||
@@ -1203,12 +1203,7 @@ void serialize(Serializer& ser)
|
||||
void deserialize(Deserializer& deser)
|
||||
{
|
||||
if (printer != nullptr)
|
||||
{
|
||||
if (deser.version() >= Deserializer::V35)
|
||||
printer->deserialize(deser);
|
||||
else
|
||||
init();
|
||||
}
|
||||
printer->deserialize(deser);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "hw/holly/sb.h"
|
||||
#include "oslib/storage.h"
|
||||
#include "oslib/oslib.h"
|
||||
#include "oslib/directory.h"
|
||||
#include "cfg/option.h"
|
||||
#include "card_reader.h"
|
||||
#include "naomi_roms.h"
|
||||
@@ -342,17 +343,9 @@ public:
|
||||
deser >> size;
|
||||
recvBuffer.resize(size);
|
||||
deser.deserialize(recvBuffer.data(), recvBuffer.size());
|
||||
if (deser.version() >= Deserializer::V41)
|
||||
{
|
||||
deser >> state;
|
||||
deser >> rowCounter;
|
||||
deser.deserialize(cardData.data(), cardData.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
state = Off;
|
||||
rowCounter = 0;
|
||||
}
|
||||
deser >> state;
|
||||
deser >> rowCounter;
|
||||
deser.deserialize(cardData.data(), cardData.size());
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -432,13 +425,7 @@ private:
|
||||
|
||||
void saveData()
|
||||
{
|
||||
FILE *f = nowide::fopen(getCardDataPath().c_str(), "wb");
|
||||
if (f == nullptr) {
|
||||
WARN_LOG(NAOMI, "Can't save RFID card: error %x", errno);
|
||||
return;
|
||||
}
|
||||
fwrite(cardData.data(), 1, cardData.size(), f);
|
||||
fclose(f);
|
||||
safeFullSave(getCardDataPath(), cardData.data(), cardData.size());
|
||||
}
|
||||
|
||||
u32 dinoShuffle(u32 v) {
|
||||
@@ -683,7 +670,7 @@ private:
|
||||
SerialPort *port;
|
||||
std::deque<u8> toSend;
|
||||
std::vector<u8> recvBuffer;
|
||||
int schedId = 0;
|
||||
int schedId;
|
||||
u32 lastPosX = ~0;
|
||||
u32 lastPosY = ~0;
|
||||
bool lastButton = false;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "serialize.h"
|
||||
#include "network/net_platform.h"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace systemsp
|
||||
{
|
||||
@@ -171,6 +172,8 @@ public:
|
||||
|
||||
void Serialize(Serializer &ser) const override;
|
||||
void Deserialize(Deserializer &deser) override;
|
||||
// The media is loaded into memory-backed RAM, so a savestate holds all of it
|
||||
u32 getSerializedMediaSize() const { return mediaName != nullptr ? RomSize : 0; }
|
||||
|
||||
bool GetBootId(RomBootID *bootId) override
|
||||
{
|
||||
|
||||
+94
-41
@@ -1,7 +1,9 @@
|
||||
#include "Renderer_if.h"
|
||||
#include "spg.h"
|
||||
#include "rend/CustomTexture.h"
|
||||
#include "rend/texconv.h"
|
||||
#include "rend/transform_matrix.h"
|
||||
#include "pvr_mem.h"
|
||||
#include "cfg/option.h"
|
||||
#include "emulator.h"
|
||||
#include "serialize.h"
|
||||
@@ -12,6 +14,7 @@
|
||||
#include "profiler/fc_profiler.h"
|
||||
#include "network/ggpo.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
#include <deque>
|
||||
|
||||
@@ -30,9 +33,6 @@ static cResetEvent vramRollback;
|
||||
|
||||
// direct framebuffer write detection
|
||||
static bool render_called = false;
|
||||
u32 fb_watch_addr_start;
|
||||
u32 fb_watch_addr_end;
|
||||
bool fb_dirty;
|
||||
|
||||
static bool pend_rend;
|
||||
static bool rendererEnabled = true;
|
||||
@@ -199,7 +199,10 @@ private:
|
||||
TA_context *taContext = DequeueRender();
|
||||
if (taContext == nullptr)
|
||||
return;
|
||||
renderer->processGpuCleanupOperations();
|
||||
|
||||
// tile clipping is used to calculate framebuffer size in RTT below
|
||||
setTileClipping(taContext->rend);
|
||||
int width, height;
|
||||
getScaledFramebufferSize(taContext->rend, width, height);
|
||||
taContext->rend.framebufferWidth = width;
|
||||
@@ -250,6 +253,7 @@ private:
|
||||
void renderFramebuffer(const FramebufferInfo& config)
|
||||
{
|
||||
FC_PROFILE_SCOPE;
|
||||
renderer->processGpuCleanupOperations();
|
||||
|
||||
#ifdef LIBRETRO
|
||||
int w, h;
|
||||
@@ -328,7 +332,7 @@ public:
|
||||
{
|
||||
// force 60/50 FPS now
|
||||
lastInterval = 1;
|
||||
stability = std::max(stability, 10);
|
||||
currentInterval = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -336,37 +340,46 @@ public:
|
||||
rendersFullSpeed = 0;
|
||||
}
|
||||
|
||||
const float refreshRate = SPG_CONTROL.isPAL() ? 20_sh4ms : 16667_sh4us;
|
||||
int interval = std::round(avgRenderInterval / refreshRate);
|
||||
float frac = std::abs(avgRenderInterval / refreshRate - interval);
|
||||
const float vblankPerRender = avgRenderInterval / (SPG_CONTROL.isPAL() ? 20_sh4ms : 16667_sh4us);
|
||||
int interval = std::round(vblankPerRender);
|
||||
float frac = std::abs(vblankPerRender - interval);
|
||||
|
||||
if (frac <= .05f || (interval == 1 && frac <= .2f))
|
||||
if ((interval == 2 && frac <= .05f)
|
||||
|| (interval == 1 && frac <= .2f))
|
||||
{
|
||||
if (lastInterval == (int)interval) {
|
||||
stability++;
|
||||
if (lastInterval == (int)interval)
|
||||
{
|
||||
if (++stable >= 10)
|
||||
currentInterval = std::min(lastInterval, 2);
|
||||
unstable = 0;
|
||||
}
|
||||
else {
|
||||
stability = 0;
|
||||
else
|
||||
{
|
||||
stable = 0;
|
||||
lastInterval = interval;
|
||||
unstable++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
stability = 0;
|
||||
stable = 0;
|
||||
unstable++;
|
||||
}
|
||||
if (unstable >= 30 && vblankPerRender < 2.f)
|
||||
// Force swap interval to 1 if the frame rate is off over 30 frames
|
||||
// Helps with games that render slightly above 30 FPS (ECCO 33 FPS, Armada ~40 FPS)
|
||||
currentInterval = 1;
|
||||
}
|
||||
|
||||
int swapInterval()
|
||||
{
|
||||
if (stability < 10)
|
||||
return -1;
|
||||
else
|
||||
return std::min(lastInterval, 2);
|
||||
int swapInterval() const {
|
||||
return currentInterval;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
lastInterval = 1;
|
||||
stability = 0;
|
||||
stable = 0;
|
||||
unstable = 0;
|
||||
currentInterval = 1;
|
||||
|
||||
lastRender = 0;
|
||||
renderInterval = 0;
|
||||
@@ -383,7 +396,9 @@ private:
|
||||
}
|
||||
|
||||
int lastInterval;
|
||||
int stability;
|
||||
int stable;
|
||||
int unstable;
|
||||
int currentInterval;
|
||||
|
||||
u64 lastRender;
|
||||
u64 renderInterval;
|
||||
@@ -474,12 +489,65 @@ void rend_term_renderer()
|
||||
|
||||
if (renderer != nullptr)
|
||||
{
|
||||
custom_texture.invalidateGpuPreloads();
|
||||
renderer->Term();
|
||||
delete renderer;
|
||||
renderer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void rend_process_custom_texture_preloads()
|
||||
{
|
||||
if (renderer != nullptr)
|
||||
{
|
||||
renderer->processGpuCleanupOperations();
|
||||
renderer->processCustomTexturePreloads();
|
||||
}
|
||||
}
|
||||
|
||||
bool rend_supports_gpu_texture_preload()
|
||||
{
|
||||
return renderer != nullptr && renderer->supportsGpuTexturePreload();
|
||||
}
|
||||
|
||||
std::shared_ptr<GpuPreloadedTexture> Renderer::findGpuPreloadedTexture(
|
||||
u32 currentHash, u32 oldVqHash, u32 oldHash) const
|
||||
{
|
||||
const auto find = [this](u32 hash) -> std::shared_ptr<GpuPreloadedTexture> {
|
||||
const auto found = gpuPreloadedTextures.find(hash);
|
||||
return found == gpuPreloadedTextures.end() ? nullptr : found->second;
|
||||
};
|
||||
if (auto texture = find(currentHash))
|
||||
return texture;
|
||||
if (oldVqHash != 0)
|
||||
if (auto texture = find(oldVqHash))
|
||||
return texture;
|
||||
if (oldHash != 0)
|
||||
return find(oldHash);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Renderer::addGpuPreloadedTexture(u32 hash, std::shared_ptr<GpuPreloadedTexture> texture)
|
||||
{
|
||||
assert(texture);
|
||||
gpuPreloadedTextures.emplace(hash, std::move(texture));
|
||||
}
|
||||
|
||||
void Renderer::clearGpuPreloadedTextures()
|
||||
{
|
||||
gpuPreloadedTextures.clear();
|
||||
}
|
||||
|
||||
void Renderer::processGpuCleanupOperations()
|
||||
{
|
||||
if (custom_texture.consumeGpuCleanupOperations())
|
||||
{
|
||||
clearTextureCache();
|
||||
clearGpuPreloadedTextures();
|
||||
resetTextureCache = false;
|
||||
}
|
||||
}
|
||||
|
||||
void rend_reset()
|
||||
{
|
||||
FinishRender(DequeueRender());
|
||||
@@ -598,7 +666,7 @@ int rend_end_render(int tag, int cycles, int jitter, void *arg)
|
||||
void rend_vblank()
|
||||
{
|
||||
if (config::EmulateFramebuffer
|
||||
|| (!render_called && fb_dirty && FB_R_CTRL.fb_enable))
|
||||
|| (!render_called && FB_R_CTRL.fb_enable && FramebufferWatcher::Instance().isDirty()))
|
||||
{
|
||||
if (rend_is_enabled())
|
||||
{
|
||||
@@ -609,21 +677,13 @@ void rend_vblank()
|
||||
if (!config::EmulateFramebuffer)
|
||||
DEBUG_LOG(PVR, "Direct framebuffer write detected");
|
||||
}
|
||||
fb_dirty = false;
|
||||
}
|
||||
|
||||
render_called = false;
|
||||
check_framebuffer_write();
|
||||
emu.vblank();
|
||||
swapIntervalDetector.vblank();
|
||||
}
|
||||
|
||||
void check_framebuffer_write()
|
||||
{
|
||||
u32 fb_size = (FB_R_SIZE.fb_y_size + 1) * (FB_R_SIZE.fb_x_size + FB_R_SIZE.fb_modulus) * 4;
|
||||
fb_watch_addr_start = (SPG_CONTROL.interlace ? FB_R_SOF2 : FB_R_SOF1) & VRAM_MASK;
|
||||
fb_watch_addr_end = fb_watch_addr_start + fb_size;
|
||||
}
|
||||
|
||||
void rend_cancel_emu_wait()
|
||||
{
|
||||
if (config::ThreadedRendering)
|
||||
@@ -680,20 +740,13 @@ void rend_serialize(Serializer& ser)
|
||||
{
|
||||
ser << fb_w_cur;
|
||||
ser << render_called;
|
||||
ser << fb_dirty;
|
||||
ser << fb_watch_addr_start;
|
||||
ser << fb_watch_addr_end;
|
||||
FramebufferWatcher::Instance().serialize(ser);
|
||||
}
|
||||
void rend_deserialize(Deserializer& deser)
|
||||
{
|
||||
deser >> fb_w_cur;
|
||||
if (deser.version() >= Deserializer::V20)
|
||||
{
|
||||
deser >> render_called;
|
||||
deser >> fb_dirty;
|
||||
deser >> fb_watch_addr_start;
|
||||
deser >> fb_watch_addr_end;
|
||||
}
|
||||
deser >> render_called;
|
||||
FramebufferWatcher::Instance().deserialize(deser);
|
||||
pend_rend = false;
|
||||
fbAddrHistory[0] = 1;
|
||||
fbAddrHistory[1] = 1;
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
#include "types.h"
|
||||
#include "ta_ctx.h"
|
||||
#include "emulator.h"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
struct GpuPreloadedTexture;
|
||||
|
||||
extern u32 FrameCount;
|
||||
|
||||
bool rend_init_renderer();
|
||||
@@ -21,6 +26,8 @@ void rend_start_rollback();
|
||||
void rend_allow_rollback();
|
||||
void rend_enable_renderer(bool enabled);
|
||||
bool rend_is_enabled();
|
||||
void rend_process_custom_texture_preloads();
|
||||
bool rend_supports_gpu_texture_preload();
|
||||
void rend_serialize(Serializer& ser);
|
||||
void rend_deserialize(Deserializer& deser);
|
||||
static void rend_updatePalette();
|
||||
@@ -75,10 +82,20 @@ struct Renderer
|
||||
virtual bool GetLastFrame(std::vector<u8>& data, int& width, int& height) { return false; }
|
||||
|
||||
virtual bool Present() { return true; }
|
||||
virtual void processCustomTexturePreloads() {}
|
||||
virtual bool supportsGpuTexturePreload() const { return false; }
|
||||
|
||||
virtual BaseTextureCacheData *GetTexture(TSP tsp, TCW tcw, int area = 0) { return nullptr; }
|
||||
std::shared_ptr<GpuPreloadedTexture> findGpuPreloadedTexture(
|
||||
u32 currentHash, u32 oldVqHash, u32 oldHash) const;
|
||||
void processGpuCleanupOperations();
|
||||
|
||||
protected:
|
||||
virtual void clearTextureCache() {}
|
||||
void addGpuPreloadedTexture(u32 hash, std::shared_ptr<GpuPreloadedTexture> texture);
|
||||
void clearGpuPreloadedTextures();
|
||||
|
||||
std::unordered_map<u32, std::shared_ptr<GpuPreloadedTexture>> gpuPreloadedTextures;
|
||||
bool resetTextureCache = false;
|
||||
bool clearLastFrame = false;
|
||||
bool updatePalette = true;
|
||||
@@ -100,11 +117,6 @@ private:
|
||||
|
||||
extern Renderer* renderer;
|
||||
|
||||
extern u32 fb_watch_addr_start;
|
||||
extern u32 fb_watch_addr_end;
|
||||
extern bool fb_dirty;
|
||||
|
||||
void check_framebuffer_write();
|
||||
static inline void rend_updatePalette() {
|
||||
if (renderer != nullptr)
|
||||
renderer->updatePalette = true;
|
||||
|
||||
+3
-18
@@ -514,26 +514,13 @@ struct State
|
||||
void deserialize(Deserializer& deser)
|
||||
{
|
||||
projMatrixIdx = -1;
|
||||
if (deser.version() < Deserializer::V24)
|
||||
{
|
||||
reset();
|
||||
resetProjectionMatrix();
|
||||
return;
|
||||
}
|
||||
ta_parse_reset();
|
||||
u32 listType;
|
||||
deser >> listType;
|
||||
ta_set_list_type(listType);
|
||||
deser >> gmp;
|
||||
deser >> instance;
|
||||
if (deser.version() < Deserializer::V40)
|
||||
{
|
||||
deser.skip<u32>(); // projMatrix address
|
||||
resetProjectionMatrix();
|
||||
}
|
||||
else {
|
||||
deser >> projMatrix;
|
||||
}
|
||||
deser >> projMatrix;
|
||||
u32 tileclip;
|
||||
deser >> tileclip;
|
||||
ta_set_tileclip(tileclip);
|
||||
@@ -1795,10 +1782,8 @@ void reset(bool hard)
|
||||
|
||||
void term()
|
||||
{
|
||||
if (schedId != -1) {
|
||||
sh4_sched_unregister(schedId);
|
||||
schedId = -1;
|
||||
}
|
||||
sh4_sched_unregister(schedId);
|
||||
schedId = -1;
|
||||
}
|
||||
|
||||
void vmem_init()
|
||||
|
||||
+1
-4
@@ -97,10 +97,7 @@ void deserialize(Deserializer& deser)
|
||||
|
||||
deser >> ta_fsm[2048];
|
||||
deser >> ta_fsm_cl;
|
||||
if (deser.version() >= Deserializer::V29)
|
||||
deser >> taRenderPass;
|
||||
else
|
||||
taRenderPass = 0;
|
||||
deser >> taRenderPass;
|
||||
DeserializeTAContext(deser);
|
||||
|
||||
if (!deser.rollback())
|
||||
|
||||
+93
-26
@@ -11,8 +11,6 @@
|
||||
#include "hw/holly/holly_intc.h"
|
||||
#include "serialize.h"
|
||||
|
||||
static u32 pvr_map32(u32 offset32);
|
||||
|
||||
RamRegion vram;
|
||||
|
||||
// YUV converter code
|
||||
@@ -226,11 +224,14 @@ void DYNACALL pvr_write32p(u32 addr, T data)
|
||||
return;
|
||||
}
|
||||
addr &= ~(sizeof(T) - 1);
|
||||
u32 vaddr = addr & VRAM_MASK;
|
||||
if (vaddr >= fb_watch_addr_start && vaddr < fb_watch_addr_end)
|
||||
fb_dirty = true;
|
||||
|
||||
*(T *)&vram[pvr_map32(addr)] = data;
|
||||
T *pVram = (T *)&vram[pvr_map32(addr)];
|
||||
if constexpr (!Internal)
|
||||
{
|
||||
// check needed to avoid spurious FB write detections with VF3tb history videos
|
||||
if (data != *pVram)
|
||||
FramebufferWatcher::Instance().vramCheck(addr & VRAM_MASK);
|
||||
}
|
||||
*pVram = data;
|
||||
}
|
||||
template void pvr_write32p<u8, false>(u32 addr, u8 data);
|
||||
template void pvr_write32p<u8, true>(u32 addr, u8 data);
|
||||
@@ -284,25 +285,6 @@ void DYNACALL TAWriteSQ(u32 address, const SQBuffer *sqb)
|
||||
|
||||
//Misc interface
|
||||
|
||||
#define VRAM_BANK_BIT 0x400000
|
||||
|
||||
static u32 pvr_map32(u32 offset32)
|
||||
{
|
||||
//64b wide bus is achieved by interleaving the banks every 32 bits
|
||||
const u32 static_bits = VRAM_MASK - (VRAM_BANK_BIT * 2 - 1) + 3;
|
||||
const u32 offset_bits = (VRAM_BANK_BIT - 1) & ~3;
|
||||
|
||||
u32 bank = (offset32 & VRAM_BANK_BIT) / VRAM_BANK_BIT;
|
||||
|
||||
u32 rv = offset32 & static_bits;
|
||||
|
||||
rv |= (offset32 & offset_bits) * 2;
|
||||
|
||||
rv |= bank * 4;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
template<typename T, bool upper>
|
||||
T DYNACALL pvr_read_area4(u32 addr)
|
||||
{
|
||||
@@ -334,3 +316,88 @@ template void pvr_write_area4<u32, false>(u32 addr, u32 data);
|
||||
template void pvr_write_area4<u8, true>(u32 addr, u8 data);
|
||||
template void pvr_write_area4<u16, true>(u32 addr, u16 data);
|
||||
template void pvr_write_area4<u32, true>(u32 addr, u32 data);
|
||||
|
||||
|
||||
FramebufferWatcher::FramebufferWatcher() {
|
||||
fbSizeUpdated();
|
||||
fbSofUpdated(FB_R_SOF1);
|
||||
}
|
||||
|
||||
void FramebufferWatcher::fbSizeUpdated()
|
||||
{
|
||||
size = (FB_R_SIZE.fb_y_size + 1) * (FB_R_SIZE.fb_x_size + FB_R_SIZE.fb_modulus) * 4;
|
||||
fbs[0].end = fbs[0].start + size;
|
||||
fbs[1].end = fbs[1].start + size;
|
||||
}
|
||||
|
||||
void FramebufferWatcher::fbSofUpdated(u32 sof)
|
||||
{
|
||||
u32 waddr = sof & VRAM_MASK;
|
||||
if (waddr == fbs[0].start || waddr == fbs[1].start)
|
||||
return;
|
||||
fbs[1] = fbs[0];
|
||||
fbs[0].start = waddr;
|
||||
fbs[0].end = waddr + size;
|
||||
fbs[0].dirty = false;
|
||||
DEBUG_LOG(PVR, "FramebufferWatcher: watching new %x old %x SOF1 %x", fbs[0].start, fbs[1].start, sof);
|
||||
}
|
||||
|
||||
void FramebufferWatcher::vramCheck(u32 vramOffset)
|
||||
{
|
||||
for (Framebuffer& fb : fbs)
|
||||
{
|
||||
if (vramOffset >= fb.start && vramOffset < fb.end)
|
||||
{
|
||||
if (!fb.dirty)
|
||||
DEBUG_LOG(PVR, "FB Write detected in [%x - %x]: %x", fb.start, fb.end, vramOffset);
|
||||
fb.dirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FramebufferWatcher::isDirty()
|
||||
{
|
||||
for (Framebuffer& fb : fbs)
|
||||
if (fb.dirty) {
|
||||
fb.dirty = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FramebufferWatcher::serialize(Serializer& ser)
|
||||
{
|
||||
for (const Framebuffer& fb : fbs)
|
||||
{
|
||||
ser << fb.dirty;
|
||||
ser << fb.start;
|
||||
ser << fb.end;
|
||||
}
|
||||
}
|
||||
|
||||
void FramebufferWatcher::deserialize(Deserializer& deser)
|
||||
{
|
||||
if (deser.version() >= Deserializer::V61)
|
||||
{
|
||||
for (Framebuffer& fb : fbs)
|
||||
{
|
||||
deser >> fb.dirty;
|
||||
deser >> fb.start;
|
||||
deser >> fb.end;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
deser >> fbs[0].dirty;
|
||||
deser >> fbs[0].start;
|
||||
deser >> fbs[0].end;
|
||||
fbs[1] = {};
|
||||
}
|
||||
fbSizeUpdated();
|
||||
}
|
||||
|
||||
FramebufferWatcher& FramebufferWatcher::Instance() {
|
||||
static FramebufferWatcher instance;
|
||||
return instance;
|
||||
}
|
||||
@@ -19,8 +19,45 @@ void YUV_deserialize(Deserializer& deser);
|
||||
void YUV_reset();
|
||||
|
||||
// 32-bit vram path handlers
|
||||
// The 32-bit bus interleaves the two VRAM banks every 32 bits.
|
||||
constexpr u32 VRAM_32_BANK_BIT = 0x400000;
|
||||
inline u32 pvr_map32(u32 offset32)
|
||||
{
|
||||
const u32 static_bits = VRAM_MASK - (VRAM_32_BANK_BIT * 2 - 1) + 3;
|
||||
const u32 offset_bits = (VRAM_32_BANK_BIT - 1) & ~3;
|
||||
const u32 bank = (offset32 & VRAM_32_BANK_BIT) / VRAM_32_BANK_BIT;
|
||||
|
||||
u32 rv = offset32 & static_bits;
|
||||
rv |= (offset32 & offset_bits) * 2;
|
||||
rv |= bank * 4;
|
||||
return rv;
|
||||
}
|
||||
|
||||
template<typename T> T DYNACALL pvr_read32p(u32 addr);
|
||||
template<typename T, bool Internal = false> void DYNACALL pvr_write32p(u32 addr, T data);
|
||||
// Area 4 handlers
|
||||
template<typename T, bool upper> T DYNACALL pvr_read_area4(u32 addr);
|
||||
template<typename T, bool upper> void DYNACALL pvr_write_area4(u32 addr, T data);
|
||||
|
||||
class FramebufferWatcher
|
||||
{
|
||||
FramebufferWatcher();
|
||||
|
||||
struct Framebuffer {
|
||||
u32 start = 0;
|
||||
u32 end = 0;
|
||||
bool dirty = false;
|
||||
};
|
||||
Framebuffer fbs[2];
|
||||
u32 size = 0;
|
||||
|
||||
public:
|
||||
void fbSizeUpdated();
|
||||
void fbSofUpdated(u32 sof);
|
||||
void vramCheck(u32 vramOffset);
|
||||
bool isDirty();
|
||||
void serialize(Serializer& ser);
|
||||
void deserialize(Deserializer& deser);
|
||||
|
||||
static FramebufferWatcher& Instance();
|
||||
};
|
||||
@@ -171,11 +171,9 @@ void pvr_WriteReg(u32 paddr,u32 data)
|
||||
return;
|
||||
|
||||
case FB_R_SIZE_addr:
|
||||
if (PvrReg(addr, u32) != data)
|
||||
{
|
||||
if (PvrReg(addr, u32) != data) {
|
||||
PvrReg(addr, u32) = data;
|
||||
fb_dirty = false;
|
||||
check_framebuffer_write();
|
||||
FramebufferWatcher::Instance().fbSizeUpdated();
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -192,6 +190,8 @@ void pvr_WriteReg(u32 paddr,u32 data)
|
||||
case FB_R_SOF1_addr:
|
||||
case FB_R_SOF2_addr:
|
||||
data &= 0x00fffffc;
|
||||
if (addr == FB_R_SOF1_addr)
|
||||
FramebufferWatcher::Instance().fbSofUpdated(data);
|
||||
rend_swap_frame(data);
|
||||
break;
|
||||
|
||||
|
||||
+9
-4
@@ -28,8 +28,8 @@ static u64 last_fps;
|
||||
constexpr int PIXEL_CLOCK = 27 * 1000 * 1000;
|
||||
static u32 Line_Cycles;
|
||||
static u32 Frame_Cycles;
|
||||
int render_end_schid;
|
||||
int vblank_schid;
|
||||
int render_end_schid = -1;
|
||||
int vblank_schid = -1;
|
||||
|
||||
static std::array<double, 4> real_times;
|
||||
static std::array<u64, 4> cpu_cycles;
|
||||
@@ -319,11 +319,12 @@ void spg_Serialize(Serializer& ser)
|
||||
ser << Frame_Cycles;
|
||||
ser << lightgun_line;
|
||||
ser << lightgun_hpos;
|
||||
sh4_sched_serialize(ser, vblank_schid);
|
||||
sh4_sched_serialize(ser, render_end_schid);
|
||||
}
|
||||
|
||||
void spg_Deserialize(Deserializer& deser)
|
||||
{
|
||||
if (deser.version() < Deserializer::V30)
|
||||
deser.skip<u32>(); // in_vblank
|
||||
deser >> clc_pvr_scanline;
|
||||
deser >> maple_int_pending;
|
||||
deser >> pvr_numscanlines;
|
||||
@@ -332,4 +333,8 @@ void spg_Deserialize(Deserializer& deser)
|
||||
deser >> Frame_Cycles;
|
||||
deser >> lightgun_line;
|
||||
deser >> lightgun_hpos;
|
||||
if (deser.version() >= Deserializer::V62) {
|
||||
sh4_sched_deserialize(deser, vblank_schid);
|
||||
sh4_sched_deserialize(deser, render_end_schid);
|
||||
}
|
||||
}
|
||||
+15
-31
@@ -237,18 +237,14 @@ static void deserializeContext(Deserializer& deser, TA_context **pctx)
|
||||
*pctx = nullptr;
|
||||
return;
|
||||
}
|
||||
*pctx = tactx_Find(address, true);
|
||||
u32 size;
|
||||
deser >> size;
|
||||
if (size > TA_DATA_SIZE)
|
||||
throw Deserializer::Exception("Invalid TA context size");
|
||||
*pctx = tactx_Find(address, true);
|
||||
tad_context& tad = (*pctx)->tad;
|
||||
deser.deserialize(tad.thd_root, size);
|
||||
tad.thd_data = tad.thd_root + size;
|
||||
if (deser.version() < Deserializer::V26)
|
||||
{
|
||||
u32 render_pass_count;
|
||||
deser >> render_pass_count;
|
||||
deser.skip(sizeof(u32) * render_pass_count);
|
||||
}
|
||||
}
|
||||
|
||||
void SerializeTAContext(Serializer& ser)
|
||||
@@ -268,30 +264,18 @@ void DeserializeTAContext(Deserializer& deser)
|
||||
{
|
||||
if (::ta_ctx != nullptr)
|
||||
SetCurrentTARC(TACTX_NONE);
|
||||
if (deser.version() >= Deserializer::V25)
|
||||
u32 listSize;
|
||||
deser >> listSize;
|
||||
for (const auto& ctx : ctx_list)
|
||||
tactx_Recycle(ctx);
|
||||
ctx_list.clear();
|
||||
for (u32 i = 0; i < listSize; i++)
|
||||
{
|
||||
u32 listSize;
|
||||
deser >> listSize;
|
||||
for (const auto& ctx : ctx_list)
|
||||
tactx_Recycle(ctx);
|
||||
ctx_list.clear();
|
||||
for (u32 i = 0; i < listSize; i++)
|
||||
{
|
||||
TA_context *ctx;
|
||||
deserializeContext(deser, &ctx);
|
||||
}
|
||||
int curCtx;
|
||||
deser >> curCtx;
|
||||
if (curCtx >= 0 && curCtx < (int)ctx_list.size())
|
||||
SetCurrentTARC(ctx_list[curCtx]->Address);
|
||||
}
|
||||
else
|
||||
{
|
||||
TA_context *ta_cur_ctx;
|
||||
deserializeContext(deser, &ta_cur_ctx);
|
||||
if (ta_cur_ctx != nullptr)
|
||||
SetCurrentTARC(ta_cur_ctx->Address);
|
||||
if (deser.version() >= Deserializer::V20)
|
||||
deserializeContext(deser, &ta_cur_ctx);
|
||||
TA_context *ctx;
|
||||
deserializeContext(deser, &ctx);
|
||||
}
|
||||
int curCtx;
|
||||
deser >> curCtx;
|
||||
if (curCtx >= 0 && curCtx < (int)ctx_list.size())
|
||||
SetCurrentTARC(ctx_list[curCtx]->Address);
|
||||
}
|
||||
@@ -415,6 +415,7 @@ u32 ta_get_list_type();
|
||||
void ta_set_list_type(u32 listType);
|
||||
void ta_parse_reset();
|
||||
void getRegionTileAddrAndSize(u32& address, u32& size);
|
||||
void setTileClipping(rend_context& ctx);
|
||||
|
||||
void sortTriangles(rend_context& ctx, RenderPass& pass, const RenderPass& previousPass);
|
||||
void sortPolyParams(std::vector<PolyParam>& polys, int first, int end, rend_context& ctx);
|
||||
|
||||
+58
-32
@@ -36,6 +36,31 @@ static f32 f16(u16 v)
|
||||
return *(f32*)&z;
|
||||
}
|
||||
|
||||
static void loadTexture(PolyParam *pp, int index)
|
||||
{
|
||||
if (pp->pcw.Texture == 0)
|
||||
return;
|
||||
if (index == 0)
|
||||
{
|
||||
pp->texture = renderer->GetTexture(pp->tsp, pp->tcw, 0);
|
||||
if (pp->texture == nullptr)
|
||||
pp->pcw.Texture = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
pp->texture1 = renderer->GetTexture(pp->tsp1, pp->tcw1, 1);
|
||||
if (pp->texture1 == nullptr)
|
||||
pp->pcw.Texture = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void loadTextures(PolyParam *pp)
|
||||
{
|
||||
loadTexture(pp, 0);
|
||||
if (pp->tsp1.full != (u32)-1)
|
||||
loadTexture(pp, 1);
|
||||
}
|
||||
|
||||
class BaseTAParser
|
||||
{
|
||||
static Ta_Dma *DYNACALL NullVertexData(Ta_Dma *data, Ta_Dma *data_end)
|
||||
@@ -549,8 +574,8 @@ private:
|
||||
d_pp->pcw = pp->pcw;
|
||||
d_pp->tileclip = tileclip_val;
|
||||
|
||||
if (d_pp->pcw.Texture && fetchTextures)
|
||||
d_pp->texture = renderer->GetTexture(d_pp->tsp, d_pp->tcw);
|
||||
if (fetchTextures)
|
||||
loadTexture(d_pp, 0);
|
||||
}
|
||||
|
||||
#define glob_param_bdc(pp) glob_param_bdc_( (TA_PolyParam0*)pp)
|
||||
@@ -609,8 +634,8 @@ private:
|
||||
|
||||
CurrentPP->tsp1.full = pp->tsp1.full;
|
||||
CurrentPP->tcw1.full = pp->tcw1.full;
|
||||
if (pp->pcw.Texture && fetchTextures)
|
||||
CurrentPP->texture1 = renderer->GetTexture(pp->tsp1, pp->tcw1, 1);
|
||||
if (fetchTextures)
|
||||
loadTexture(CurrentPP, 1);
|
||||
}
|
||||
|
||||
// Intensity, with Two Volumes
|
||||
@@ -622,8 +647,8 @@ private:
|
||||
|
||||
CurrentPP->tsp1.full = pp->tsp1.full;
|
||||
CurrentPP->tcw1.full = pp->tcw1.full;
|
||||
if (pp->pcw.Texture && fetchTextures)
|
||||
CurrentPP->texture1 = renderer->GetTexture(pp->tsp1, pp->tcw1, 1);
|
||||
if (fetchTextures)
|
||||
loadTexture(CurrentPP, 1);
|
||||
}
|
||||
|
||||
static void TACALL AppendPolyParam4B(void* vpp)
|
||||
@@ -981,8 +1006,8 @@ private:
|
||||
d_pp->pcw = spr->pcw;
|
||||
d_pp->tileclip = tileclip_val;
|
||||
|
||||
if (d_pp->pcw.Texture && fetchTextures)
|
||||
d_pp->texture = renderer->GetTexture(d_pp->tsp, d_pp->tcw);
|
||||
if (fetchTextures)
|
||||
loadTexture(d_pp, 0);
|
||||
|
||||
SFaceBaseColor = spr->BaseCol;
|
||||
SFaceOffsColor = spr->OffsCol;
|
||||
@@ -1207,7 +1232,7 @@ static void parseRenderPass(RenderPass& pass, const RenderPass& previousPass, re
|
||||
}
|
||||
}
|
||||
|
||||
static void setTileClipping(rend_context& ctx)
|
||||
void setTileClipping(rend_context& ctx)
|
||||
{
|
||||
u32 xmax, ymax;
|
||||
getRegionTileClipping((u32&)ctx.tileClip.origin.x, xmax, (u32&)ctx.tileClip.origin.y, ymax);
|
||||
@@ -1226,8 +1251,7 @@ static void ta_parse_vdrc(TA_context* ctx, bool primRestart)
|
||||
ta_parse_reset();
|
||||
|
||||
PolyParam *bgpp = &vd_rc.global_param_op.front();
|
||||
if (bgpp->pcw.Texture)
|
||||
bgpp->texture = renderer->GetTexture(bgpp->tsp, bgpp->tcw);
|
||||
loadTexture(bgpp, 0);
|
||||
|
||||
TA_context *childCtx = ctx;
|
||||
int pass = 0;
|
||||
@@ -1276,34 +1300,38 @@ static void ta_parse_vdrc(TA_context* ctx, bool primRestart)
|
||||
childCtx = childCtx->nextContext;
|
||||
pass++;
|
||||
}
|
||||
if (vd_rc.global_param_mvo_tr.size() > vd_rc.global_param_mvo.size())
|
||||
{
|
||||
// Hack for Xtreme Sports. This game shares the same MVO list between opaque and translucent.
|
||||
// BUT the translucent list is the one to use, contrary to other games. Detect this here and
|
||||
// swap the arrays, which also allows non-oit renderers to display them.
|
||||
bool swapMVO = false;
|
||||
for (const RenderPass& renderPass : vd_rc.render_passes)
|
||||
{
|
||||
if (renderPass.mv_op_tr_shared) {
|
||||
swapMVO = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (swapMVO)
|
||||
{
|
||||
std::swap(vd_rc.global_param_mvo, vd_rc.global_param_mvo_tr);
|
||||
for (RenderPass& renderPass : vd_rc.render_passes)
|
||||
std::swap(renderPass.mvo_count, renderPass.mvo_tr_count);
|
||||
}
|
||||
}
|
||||
|
||||
setTileClipping(ctx->rend);
|
||||
vd_ctx = nullptr;
|
||||
}
|
||||
|
||||
static void ta_parse_naomi2(TA_context* ctx, bool primRestart)
|
||||
{
|
||||
for (PolyParam& pp : ctx->rend.global_param_op)
|
||||
{
|
||||
if (pp.pcw.Texture)
|
||||
pp.texture = renderer->GetTexture(pp.tsp, pp.tcw, 0);
|
||||
if (pp.tsp1.full != (u32)-1)
|
||||
pp.texture1 = renderer->GetTexture(pp.tsp1, pp.tcw1, 1);
|
||||
}
|
||||
loadTextures(&pp);
|
||||
for (PolyParam& pp : ctx->rend.global_param_pt)
|
||||
{
|
||||
if (pp.pcw.Texture)
|
||||
pp.texture = renderer->GetTexture(pp.tsp, pp.tcw, 0);
|
||||
if (pp.tsp1.full != (u32)-1)
|
||||
pp.texture1 = renderer->GetTexture(pp.tsp1, pp.tcw1, 1);
|
||||
}
|
||||
loadTextures(&pp);
|
||||
for (PolyParam& pp : ctx->rend.global_param_tr)
|
||||
{
|
||||
if (pp.pcw.Texture)
|
||||
pp.texture = renderer->GetTexture(pp.tsp, pp.tcw, 0);
|
||||
if (pp.tsp1.full != (u32)-1)
|
||||
pp.texture1 = renderer->GetTexture(pp.tsp1, pp.tcw1, 1);
|
||||
}
|
||||
loadTextures(&pp);
|
||||
|
||||
ctx->rend.newRenderPass();
|
||||
RenderPass previousPass{};
|
||||
@@ -1321,8 +1349,6 @@ static void ta_parse_naomi2(TA_context* ctx, bool primRestart)
|
||||
}
|
||||
previousPass = pass;
|
||||
}
|
||||
|
||||
setTileClipping(ctx->rend);
|
||||
}
|
||||
|
||||
void ta_parse(TA_context *ctx, bool primRestart)
|
||||
|
||||
@@ -61,82 +61,88 @@ DynarecCodeEntryPtr DYNACALL bm_GetCodeByVAddr(u32 addr)
|
||||
|
||||
if (addr & 1)
|
||||
{
|
||||
switch (addr)
|
||||
if (!settings.content.windowsCE) {
|
||||
Do_Exception(addr, Sh4Ex_AddressErrorRead);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
#ifdef USE_WINCE_HACK
|
||||
case 0xfffffde7: // GetTickCount
|
||||
// This should make this syscall faster
|
||||
Sh4cntx.r[0] = sh4_sched_now64() * 1000 / SH4_MAIN_CLOCK;
|
||||
Sh4cntx.pc = Sh4cntx.pr;
|
||||
Sh4cntx.cycle_counter -= 100;
|
||||
break;
|
||||
case 0xfffffde7: // GetTickCount
|
||||
// This should make this syscall faster
|
||||
Sh4cntx.r[0] = sh4_sched_now64() * 1000 / SH4_MAIN_CLOCK;
|
||||
Sh4cntx.pc = Sh4cntx.pr;
|
||||
Sh4cntx.cycle_counter -= 100;
|
||||
break;
|
||||
|
||||
case 0xfffffd05: // QueryPerformanceCounter(u64 *)
|
||||
{
|
||||
bool isRam;
|
||||
u64 *ptr;
|
||||
u32 paddr;
|
||||
if (rdv_writeMemImmediate(Sh4cntx.r[4], sizeof(u64), (void*&)ptr, isRam, paddr) && isRam)
|
||||
case 0xfffffd05: // QueryPerformanceCounter(u64 *)
|
||||
{
|
||||
*ptr = sh4_sched_now64() >> 4;
|
||||
Sh4cntx.r[0] = 1;
|
||||
Sh4cntx.pc = Sh4cntx.pr;
|
||||
Sh4cntx.cycle_counter -= 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
Do_Exception(addr, Sh4Ex_AddressErrorRead);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0xfffffded: // GetProcAddressW
|
||||
{
|
||||
// Intercept the dricas authentication function and replace it with a stub (Hundred Swords, Rune Jade)
|
||||
static constexpr u16 Function[] = { 'U', 'A', '_', 'Q', 'u', 'e', 'r', 'y', 'D', '\0' };
|
||||
bool success = false;
|
||||
u32 vaddr = Sh4cntx.r[5];
|
||||
if ((vaddr & 0xffff0000) != 0) // not an ordinal
|
||||
{
|
||||
u32 pa = 0;
|
||||
for (unsigned i = 0; ; i++)
|
||||
bool isRam;
|
||||
u64 *ptr;
|
||||
u32 paddr;
|
||||
if (rdv_writeMemImmediate(Sh4cntx.r[4], sizeof(u64), (void*&)ptr, isRam, paddr) && isRam)
|
||||
{
|
||||
if (pa == 0 && mmu_full_lookup(vaddr, nullptr, pa) != MmuError::NONE)
|
||||
break;
|
||||
u16 c = ReadMem16_nommu(pa);
|
||||
if (c != Function[i])
|
||||
break;
|
||||
if (c == 0) {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
vaddr += 2;
|
||||
if ((vaddr & 0xfff) == 0)
|
||||
pa = 0;
|
||||
else
|
||||
pa += 2;
|
||||
*ptr = sh4_sched_now64() >> 4;
|
||||
Sh4cntx.r[0] = 1;
|
||||
Sh4cntx.pc = Sh4cntx.pr;
|
||||
Sh4cntx.cycle_counter -= 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
Do_Exception(addr, Sh4Ex_AddressErrorRead);
|
||||
}
|
||||
}
|
||||
if (success)
|
||||
break;
|
||||
case 0xfffffded: // GetProcAddressW
|
||||
{
|
||||
INFO_LOG(DYNAREC, "GetProcAddressW('UA_QueryD') intercepted");
|
||||
Sh4cntx.r[0] = 0xffff0001;
|
||||
Sh4cntx.pc = Sh4cntx.pr;
|
||||
// Intercept the dricas authentication function and replace it with a stub (Hundred Swords, Rune Jade)
|
||||
static constexpr u16 Function[] = { 'U', 'A', '_', 'Q', 'u', 'e', 'r', 'y', 'D', '\0' };
|
||||
bool success = false;
|
||||
u32 vaddr = Sh4cntx.r[5];
|
||||
if ((vaddr & 0xffff0000) != 0) // not an ordinal
|
||||
{
|
||||
u32 pa = 0;
|
||||
for (unsigned i = 0; ; i++)
|
||||
{
|
||||
if (pa == 0 && mmu_full_lookup(vaddr, nullptr, pa) != MmuError::NONE)
|
||||
break;
|
||||
u16 c = ReadMem16_nommu(pa);
|
||||
if (c != Function[i])
|
||||
break;
|
||||
if (c == 0) {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
vaddr += 2;
|
||||
if ((vaddr & 0xfff) == 0)
|
||||
pa = 0;
|
||||
else
|
||||
pa += 2;
|
||||
}
|
||||
}
|
||||
if (success)
|
||||
{
|
||||
INFO_LOG(DYNAREC, "GetProcAddressW('UA_QueryD') intercepted");
|
||||
Sh4cntx.r[0] = 0xffff0001;
|
||||
Sh4cntx.pc = Sh4cntx.pr;
|
||||
}
|
||||
else {
|
||||
Do_Exception(addr, Sh4Ex_AddressErrorRead);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Do_Exception(addr, Sh4Ex_AddressErrorRead);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0xffff0001: // UA_QueryD stub
|
||||
DEBUG_LOG(DYNAREC, "UA_QueryD called");
|
||||
Sh4cntx.r[0] = 0;
|
||||
Sh4cntx.pc = Sh4cntx.pr;
|
||||
break;
|
||||
break;
|
||||
case 0xffff0001: // UA_QueryD stub
|
||||
DEBUG_LOG(DYNAREC, "UA_QueryD called");
|
||||
Sh4cntx.r[0] = 0;
|
||||
Sh4cntx.pc = Sh4cntx.pr;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
Do_Exception(addr, Sh4Ex_AddressErrorRead);
|
||||
break;
|
||||
default:
|
||||
Do_Exception(addr, Sh4Ex_AddressErrorRead);
|
||||
break;
|
||||
}
|
||||
}
|
||||
addr = Sh4cntx.pc;
|
||||
}
|
||||
@@ -544,9 +550,10 @@ u32 bm_getRamOffset(void *p)
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if ((u8 *)p < &mem_b[0] || (u8 *)p >= &mem_b[RAM_SIZE])
|
||||
uintptr_t offset = virtmem::untag(p) - virtmem::untag(&mem_b[0]);
|
||||
if (offset >= RAM_SIZE)
|
||||
return -1;
|
||||
return (u32)((u8 *)p - &mem_b[0]);
|
||||
return (u32)offset;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -334,6 +334,9 @@ void* DYNACALL rdv_LinkBlock(u8* code,u32 dpc)
|
||||
void Sh4Recompiler::Reset(bool hard)
|
||||
{
|
||||
super::Reset(hard);
|
||||
if (hard)
|
||||
// in case ram base has changed
|
||||
sh4Dynarec->init(*getContext(), codeBuffer);
|
||||
ResetCache();
|
||||
if (hard)
|
||||
bm_Reset();
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "hw/sh4/modules/mmu.h"
|
||||
#include "ssa.h"
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
@@ -37,19 +38,18 @@ public:
|
||||
RegAlloc() = default;
|
||||
virtual ~RegAlloc() = default;
|
||||
|
||||
void DoAlloc(RuntimeBlockInfo* block, const nreg_t* regs_avail, const nregf_t* regsf_avail)
|
||||
template<std::size_t N, std::size_t NF>
|
||||
void DoAlloc(RuntimeBlockInfo* block, const std::array<nreg_t, N>& regs_avail, const std::array<nregf_t, NF>& regsf_avail)
|
||||
{
|
||||
this->block = block;
|
||||
SSAOptimizer optim(block);
|
||||
optim.AddVersionPass();
|
||||
|
||||
verify(host_gregs.empty());
|
||||
while (*regs_avail != (nreg_t)-1)
|
||||
host_gregs.push_back(*regs_avail++);
|
||||
host_gregs.insert(host_gregs.end(), regs_avail.begin(), regs_avail.end());
|
||||
|
||||
verify(host_fregs.empty());
|
||||
while (*regsf_avail != (nregf_t)-1)
|
||||
host_fregs.push_back(*regsf_avail++);
|
||||
host_fregs.insert(host_fregs.end(), regsf_avail.begin(), regsf_avail.end());
|
||||
}
|
||||
|
||||
void OpBegin(shil_opcode* op, int opid)
|
||||
|
||||
@@ -98,10 +98,15 @@ void Sh4Interpreter::Step()
|
||||
|
||||
void Sh4Interpreter::Reset(bool hard)
|
||||
{
|
||||
ctx = &p_sh4rcb->cntx;
|
||||
verify(!ctx->CpuRunning);
|
||||
|
||||
if (hard)
|
||||
{
|
||||
// in case ram base has changed
|
||||
sh4cycles.init(ctx);
|
||||
icache.init(ctx);
|
||||
ocache.init(ctx);
|
||||
int schedNext = ctx->sh4_sched_next;
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
ctx->sh4_sched_next = schedNext;
|
||||
|
||||
@@ -926,7 +926,7 @@ sh4op(i1011_iiii_iiii_iiii)
|
||||
// trapa #<imm>
|
||||
sh4op(i1100_0011_iiii_iiii)
|
||||
{
|
||||
WARN_LOG(INTERPRETER, "TRAP #%X", GetImm8(op));
|
||||
INFO_LOG(INTERPRETER, "TRAP #%X", GetImm8(op));
|
||||
debugger::debugTrap(Sh4Ex_Trap);
|
||||
CCN_TRA = (GetImm8(op) << 2);
|
||||
Do_Exception(ctx->pc, Sh4Ex_Trap);
|
||||
|
||||
@@ -176,9 +176,35 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
#endif
|
||||
|
||||
bool UTLB_Sync(u32 entry)
|
||||
bool fullMmuNeeded(int index)
|
||||
{
|
||||
const unsigned start = index < 0 ? 0 : index;
|
||||
const unsigned end = index < 0 ? std::size(UTLB) : index + 1;
|
||||
for (unsigned i = start; i < end; i++)
|
||||
{
|
||||
const TLB_Entry& entry = UTLB[i];
|
||||
if (entry.Data.V == 1
|
||||
// ignore many arcade and Visual Concepts games presumably bogus mappings
|
||||
&& entry.Address.VPN != 0x30040 && entry.Address.VPN != 0x30000
|
||||
// ignore identity mapping
|
||||
&& entry.Address.VPN != entry.Data.PPN
|
||||
// ignore store queue mapping
|
||||
&& (entry.Address.VPN & (0xFC000000 >> 10)) != (0xE0000000 >> 10))
|
||||
{
|
||||
INFO_LOG(SH4, "fullMmuNeeded[%d]: VPN %x PPN %x ASID %d sz %d", i,
|
||||
entry.Address.VPN << 10, entry.Data.PPN << 10,
|
||||
entry.Address.ASID, entry.Data.SZ1 * 2 + entry.Data.SZ0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UTLB_Sync(u32 entry)
|
||||
{
|
||||
TLB_Entry& tlb_entry = UTLB[entry];
|
||||
if (tlb_entry.Data.V == 0)
|
||||
return;
|
||||
u32 sz = tlb_entry.Data.SZ1 * 2 + tlb_entry.Data.SZ0;
|
||||
|
||||
tlb_entry.Address.VPN &= mmu_mask[sz] >> 10;
|
||||
@@ -190,6 +216,8 @@ bool UTLB_Sync(u32 entry)
|
||||
|
||||
cache_entry(tlb_entry);
|
||||
|
||||
DEBUG_LOG(SH4, "UTLB_Sync[%d]: VPN %x PPN %x ASID %d sz %d", entry, tlb_entry.Address.VPN << 10, tlb_entry.Data.PPN << 10,
|
||||
tlb_entry.Address.ASID, sz);
|
||||
if (!mmu_enabled())
|
||||
{
|
||||
if ((tlb_entry.Address.VPN & (0xFC000000 >> 10)) == (0xE0000000 >> 10))
|
||||
@@ -198,20 +226,18 @@ bool UTLB_Sync(u32 entry)
|
||||
u32 vpn_sq = ((tlb_entry.Address.VPN & 0x7FFFF) >> 10) & 0x3F;//upper bits are always known [0xE0/E1/E2/E3]
|
||||
sq_remap[vpn_sq] = tlb_entry.Data.PPN << 10;
|
||||
}
|
||||
else if (CCN_MMUCR.AT == 1 && tlb_entry.Address.VPN != 0x30040 && tlb_entry.Address.VPN != 0x30000 && tlb_entry.Data.V == 1)
|
||||
else if (CCN_MMUCR.AT == 1 && fullMmuNeeded(entry))
|
||||
{
|
||||
// Enable Full MMU if not an expected store queue mapping
|
||||
// VPN checks to ignore many arcade and Visual Concepts games presumably bogus mappings
|
||||
mmuOn = true;
|
||||
NOTICE_LOG(SH4, "Enabling on-demand Full MMU support");
|
||||
mmu_set_state();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ITLB_Sync(u32 entry)
|
||||
{
|
||||
DEBUG_LOG(SH4, "ITLB_Sync[%d]", entry);
|
||||
}
|
||||
|
||||
//Do a full lookup on the UTLB entry's
|
||||
@@ -249,28 +275,31 @@ MmuError mmu_full_lookup(u32 va, const TLB_Entry** tlb_entry_ret, u32& rv)
|
||||
}
|
||||
|
||||
#ifdef USE_WINCE_HACK
|
||||
// WinCE hack
|
||||
TLB_Entry& entry = UTLB[CCN_MMUCR.URC];
|
||||
if (wince_resolve_address(va, entry))
|
||||
if (settings.content.windowsCE)
|
||||
{
|
||||
CCN_PTEL.reg_data = entry.Data.reg_data;
|
||||
CCN_PTEA.reg_data = entry.Assistance.reg_data;
|
||||
CCN_PTEH.reg_data = entry.Address.reg_data;
|
||||
// WinCE hack to avoid calling the page fault handler
|
||||
TLB_Entry& entry = UTLB[CCN_MMUCR.URC];
|
||||
if (wince_resolve_address(va, entry))
|
||||
{
|
||||
CCN_PTEL.reg_data = entry.Data.reg_data;
|
||||
CCN_PTEA.reg_data = entry.Assistance.reg_data;
|
||||
CCN_PTEH.reg_data = entry.Address.reg_data;
|
||||
|
||||
lru_entry = *tlb_entry_ret = &entry;
|
||||
lru_entry = *tlb_entry_ret = &entry;
|
||||
|
||||
u32 sz = entry.Data.SZ1 * 2 + entry.Data.SZ0;
|
||||
lru_mask = mmu_mask[sz];
|
||||
lru_address = va & mmu_mask[sz];
|
||||
entry.Data.PPN &= mmu_mask[sz] >> 10;
|
||||
u32 sz = entry.Data.SZ1 * 2 + entry.Data.SZ0;
|
||||
lru_mask = mmu_mask[sz];
|
||||
lru_address = va & mmu_mask[sz];
|
||||
entry.Data.PPN &= mmu_mask[sz] >> 10;
|
||||
|
||||
rv = (entry.Data.PPN << 10) | (va & ~mmu_mask[sz]);
|
||||
rv = (entry.Data.PPN << 10) | (va & ~mmu_mask[sz]);
|
||||
|
||||
cache_entry(entry);
|
||||
cache_entry(entry);
|
||||
|
||||
p_sh4rcb->cntx.cycle_counter -= 164;
|
||||
p_sh4rcb->cntx.cycle_counter -= 164;
|
||||
|
||||
return MmuError::NONE;
|
||||
return MmuError::NONE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -333,5 +362,10 @@ void mmu_flush_table()
|
||||
lru_entry = nullptr;
|
||||
flush_cache();
|
||||
mmuAddressLUTFlush(true);
|
||||
|
||||
for (TLB_Entry& entry : ITLB)
|
||||
entry.Data.V = 0;
|
||||
for (TLB_Entry& entry : UTLB)
|
||||
entry.Data.V = 0;
|
||||
}
|
||||
#endif // FAST_MMU
|
||||
+25
-17
@@ -55,24 +55,18 @@ constexpr u32 ITLB_LRU_AND[4] =
|
||||
|
||||
#ifndef FAST_MMU
|
||||
//sync mem mapping to mmu , suspend compiled blocks if needed.entry is a UTLB entry # , -1 is for full sync
|
||||
bool UTLB_Sync(u32 entry)
|
||||
void UTLB_Sync(u32 entry)
|
||||
{
|
||||
printf_mmu("UTLB MEM remap %d : 0x%X to 0x%X : %d asid %d size %d", entry, UTLB[entry].Address.VPN << 10, UTLB[entry].Data.PPN << 10, UTLB[entry].Data.V,
|
||||
UTLB[entry].Address.ASID, UTLB[entry].Data.SZ0 + UTLB[entry].Data.SZ1 * 2);
|
||||
if (UTLB[entry].Data.V == 0)
|
||||
return true;
|
||||
return;
|
||||
|
||||
if ((UTLB[entry].Address.VPN & (0xFC000000 >> 10)) == (0xE0000000 >> 10))
|
||||
{
|
||||
// Used when FullMMU is off
|
||||
u32 vpn_sq = ((UTLB[entry].Address.VPN & 0x7FFFF) >> 10) & 0x3F;//upper bits are always known [0xE0/E1/E2/E3]
|
||||
sq_remap[vpn_sq] = UTLB[entry].Data.PPN << 10;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//sync mem mapping to mmu , suspend compiled blocks if needed.entry is a ITLB entry # , -1 is for full sync
|
||||
@@ -441,18 +435,27 @@ void mmu_set_state()
|
||||
{
|
||||
if (CCN_MMUCR.AT == 1)
|
||||
{
|
||||
// Detect if we're running Windows CE
|
||||
static const char magic[] = { 'S', 0, 'H', 0, '-', 0, '4', 0, ' ', 0, 'K', 0, 'e', 0, 'r', 0, 'n', 0, 'e', 0, 'l', 0 };
|
||||
if (memcmp(GetMemPtr(0x8c0110a8, 4), magic, sizeof(magic)) == 0
|
||||
|| memcmp(GetMemPtr(0x8c011118, 4), magic, sizeof(magic)) == 0)
|
||||
if (!settings.content.windowsCE)
|
||||
{
|
||||
mmuOn = true;
|
||||
NOTICE_LOG(SH4, "Enabling Full MMU support");
|
||||
// Detect if we're running Windows CE
|
||||
static const char magic[] = { 'S', 0, 'H', 0, '-', 0, '4', 0, ' ', 0, 'K', 0, 'e', 0, 'r', 0, 'n', 0, 'e', 0, 'l', 0 };
|
||||
if (memcmp(GetMemPtr(0x8c0110a8, 4), magic, sizeof(magic)) == 0
|
||||
|| memcmp(GetMemPtr(0x8c011118, 4), magic, sizeof(magic)) == 0)
|
||||
{
|
||||
mmuOn = true;
|
||||
settings.content.windowsCE = true;
|
||||
NOTICE_LOG(SH4, "Enabling Windows CE MMU support");
|
||||
}
|
||||
else if (fullMmuNeeded()) {
|
||||
mmuOn = true;
|
||||
NOTICE_LOG(SH4, "Enabling Full MMU support");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mmuOn = false;
|
||||
settings.content.windowsCE = false;
|
||||
}
|
||||
|
||||
SetMemoryHandlers();
|
||||
@@ -586,11 +589,16 @@ void mmu_serialize(Serializer& ser)
|
||||
|
||||
void mmu_deserialize(Deserializer& deser)
|
||||
{
|
||||
deser.skip(8, Deserializer::V33); // CCN_QACR_TR
|
||||
|
||||
deser >> UTLB;
|
||||
deser >> ITLB;
|
||||
|
||||
deser >> sq_remap;
|
||||
deser.skip(64 * 4, Deserializer::V23); // ITLB_LRU_USE
|
||||
|
||||
if (!settings.content.windowsCE)
|
||||
{
|
||||
for (unsigned i = 0; i < std::size(UTLB); i++)
|
||||
UTLB_Sync(i);
|
||||
for (unsigned i = 0; i < std::size(ITLB); i++)
|
||||
ITLB_Sync(i);
|
||||
}
|
||||
}
|
||||
@@ -55,13 +55,14 @@ constexpr u32 mmu_mask[4] =
|
||||
((0xFFFFFFFF) >> 20) << 20 //1 MB page
|
||||
};
|
||||
|
||||
bool UTLB_Sync(u32 entry);
|
||||
void UTLB_Sync(u32 entry);
|
||||
void ITLB_Sync(u32 entry);
|
||||
|
||||
bool mmu_match(u32 va, CCN_PTEH_type Address, CCN_PTEL_type Data);
|
||||
void mmu_set_state();
|
||||
void mmu_flush_table();
|
||||
[[noreturn]] void mmu_raise_exception(MmuError mmu_error, u32 address, u32 am);
|
||||
bool fullMmuNeeded(int index = -1);
|
||||
|
||||
static inline bool mmu_enabled()
|
||||
{
|
||||
@@ -122,12 +123,13 @@ extern u32 mmuAddressLUT[0x100000];
|
||||
|
||||
static inline void mmuAddressLUTFlush(bool full)
|
||||
{
|
||||
if (full)
|
||||
memset(mmuAddressLUT, 0, sizeof(mmuAddressLUT) / 2); // flush user memory
|
||||
if (full || !settings.content.windowsCE) {
|
||||
memset(mmuAddressLUT, 0, sizeof(mmuAddressLUT) / 2); // flush user memory (P0)
|
||||
}
|
||||
else
|
||||
{
|
||||
constexpr u32 slotPages = (32 * 1024 * 1024) >> 12;
|
||||
memset(mmuAddressLUT, 0, slotPages * sizeof(u32)); // flush slot 0
|
||||
memset(mmuAddressLUT, 0, slotPages * sizeof(u32)); // flush slot 0 (windows CE)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -110,6 +110,9 @@ public:
|
||||
void reset();
|
||||
void serialize(Serializer& ser);
|
||||
void deserialize(Deserializer& deser);
|
||||
void postDeserialize() {
|
||||
updateBaudRate();
|
||||
}
|
||||
|
||||
u8 SCFRDR2_read();
|
||||
void SCFTDR2_write(u8 data);
|
||||
|
||||
@@ -375,14 +375,10 @@ void SCIFSerialPort::init()
|
||||
|
||||
void SCIFSerialPort::term()
|
||||
{
|
||||
if (schedId != -1) {
|
||||
sh4_sched_unregister(schedId);
|
||||
schedId = -1;
|
||||
}
|
||||
if (brkSchedId != -1) {
|
||||
sh4_sched_unregister(brkSchedId);
|
||||
brkSchedId = -1;
|
||||
}
|
||||
sh4_sched_unregister(schedId);
|
||||
schedId = -1;
|
||||
sh4_sched_unregister(brkSchedId);
|
||||
brkSchedId = -1;
|
||||
}
|
||||
|
||||
void SCIFSerialPort::reset()
|
||||
@@ -441,7 +437,6 @@ void SCIFSerialPort::deserialize(Deserializer& deser)
|
||||
transmitting = false;
|
||||
}
|
||||
SCIF_SCBRR2 &= 0xff; // work around previous issues with dynarecs
|
||||
updateBaudRate();
|
||||
}
|
||||
|
||||
struct PTYPipe : public SerialPort::Pipe
|
||||
|
||||
@@ -17,7 +17,7 @@ static u64 tmu_mask[3];
|
||||
static u32 old_mode[3] = { 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
|
||||
static constexpr InterruptID tmu_intID[3] = { sh4_TMU0_TUNI0, sh4_TMU1_TUNI1, sh4_TMU2_TUNI2 };
|
||||
int tmu_sched[3];
|
||||
int tmu_sched[3] { -1, -1, -1 };
|
||||
static s64 tmu_ch_base[3];
|
||||
|
||||
inline static u64 now() {
|
||||
|
||||
Loaded 100 of 318 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user