mirror of
https://github.com/flyinghead/flycast.git
synced 2026-11-07 17:11:09 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7adbf3caef | ||
|
|
1b63c8a367 | ||
|
|
11ba75f843 | ||
|
|
e88b0ced22 | ||
|
|
6d81b99816 | ||
|
|
53efedbbe7 | ||
|
|
fa9d39a355 | ||
|
|
a9bd1c74fd | ||
|
|
5543a95c4f | ||
|
|
78d5d09c1e | ||
|
|
31697241b7 | ||
|
|
e9f4020c56 | ||
|
|
5eb52090ad | ||
|
|
128e6ce29c | ||
|
|
38a3d83c77 | ||
|
|
19ed307f74 | ||
|
|
d207cd9198 | ||
|
|
1d356d5e55 | ||
|
|
32c2304459 | ||
|
|
34ad8c8262 | ||
|
|
abfc411392 | ||
|
|
6bb0fc3e18 | ||
|
|
08afcbe6b9 | ||
|
|
e9dd6187cb | ||
|
|
a5dc811814 | ||
|
|
5c86428c04 | ||
|
|
258f5c11d4 | ||
|
|
80471d57b0 | ||
|
|
ce8689101f | ||
|
|
518c496251 | ||
|
|
804edb00d7 | ||
|
|
f03faffd2f | ||
|
|
fa23eef593 | ||
|
|
658abb386a | ||
|
|
fa3c526e4f | ||
|
|
e6d3365c0b | ||
|
|
ed953fcbfd | ||
|
|
0d9853df9a | ||
|
|
df7aedf506 | ||
|
|
df5feec55c | ||
|
|
bbdef06a09 | ||
|
|
b4c38d652e | ||
|
|
59ed35a7ea | ||
|
|
04669ebe41 | ||
|
|
98ce9fb5d7 | ||
|
|
2a57c9ffd9 | ||
|
|
2ea7920fdc | ||
|
|
9d4c337349 | ||
|
|
89b72a8e3e | ||
|
|
e36e9df2dc | ||
|
|
ea087b9140 | ||
|
|
869038f40a | ||
|
|
628bd3dbb1 | ||
|
|
dd7a5f0620 | ||
|
|
6d73f7191c | ||
|
|
215a5af165 | ||
|
|
c0fb036d60 | ||
|
|
8933f9fe0c | ||
|
|
4509ee903c | ||
|
|
04b74c3a11 | ||
|
|
9869ea8852 | ||
|
|
ea22ee8723 | ||
|
|
b777c15fdc | ||
|
|
93379c489f | ||
|
|
b03335bca9 | ||
|
|
604ebd58fe | ||
|
|
eddf263586 | ||
|
|
3f70e47adb | ||
|
|
0abac3465d | ||
|
|
a9b451c6f2 | ||
|
|
fb286f777c | ||
|
|
fa04b20837 | ||
|
|
52c8ecef1e | ||
|
|
a75d1cff53 | ||
|
|
daa46ec63e | ||
|
|
9814e2fe40 | ||
|
|
d8a76809e5 | ||
|
|
fd9d39b127 | ||
|
|
8f511f8960 | ||
|
|
12bb43652f | ||
|
|
71b739faee | ||
|
|
57e33d323a | ||
|
|
149e651d24 | ||
|
|
c37be698db | ||
|
|
e2722869ff | ||
|
|
180e670605 | ||
|
|
c3763d8fc4 | ||
|
|
9473c0377a | ||
|
|
9c6c8e59d6 | ||
|
|
7fcd6976fa | ||
|
|
d5e74ad756 | ||
|
|
9dbca96328 | ||
|
|
44e4c7b50d |
No files matched your search
@@ -7,8 +7,8 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Please Note: This form is the minimum required information for submitting bugs.**
|
||||
**Removing this form may lead to your issue being closed until it is completed.**
|
||||
<!-- Please Note: This form is the minimum required information for submitting bugs. -->
|
||||
<!-- Removing this form may lead to your issue being closed until it is completed. -->
|
||||
|
||||
Platform / OS / Hardware:
|
||||
|
||||
@@ -18,24 +18,23 @@ Hardware:
|
||||
|
||||
**_Description of the Issue_**
|
||||
|
||||
Add a short, concise description of the issue here
|
||||
<!-- Add a short, concise description of the issue here -->
|
||||
|
||||
**_Debugging Steps Tested_**
|
||||
|
||||
* Fill in any steps already tried here
|
||||
* Begin each new line with an asterisk
|
||||
* If no steps are required, please skip
|
||||
<!-- Fill in any steps already tried here -->
|
||||
<!-- Begin each new line with an asterisk -->
|
||||
<!-- If no steps are required, please skip -->
|
||||
|
||||
**_Logs Gathered_**
|
||||
|
||||
```
|
||||
|
||||
Please paste the contents of the log / logs here
|
||||
You may leave this field blank if you have none.
|
||||
Delete this field if you have none.
|
||||
|
||||
```
|
||||
|
||||
|
||||
**_Screenshots_**
|
||||
|
||||
(Replace this line with any issue screenshots)
|
||||
<!-- Replace this line with any issue screenshots -->
|
||||
@@ -8,13 +8,13 @@ assignees: ''
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
@@ -0,0 +1,11 @@
|
||||
## Description
|
||||
<!-- Summarize the change and its motivation. -->
|
||||
<!-- Use "Fixes #123" for an issue this PR closes, or "Related to #123" otherwise. -->
|
||||
|
||||
## Checklist
|
||||
- [ ] This PR targets `dev`, unless it is a critical, low-risk bug fix.
|
||||
- [ ] I linked the relevant issue or discussion, if one exists.
|
||||
- [ ] I added or updated tests where appropriate.
|
||||
- [ ] I tested the change on the relevant platform(s) and noted any limitations.
|
||||
- [ ] For UI changes, I included screenshots or a recording where helpful.
|
||||
- [ ] I kept the change focused, followed existing project patterns and avoided code duplication.
|
||||
@@ -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' }}
|
||||
@@ -18,11 +18,11 @@ 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
|
||||
@@ -30,16 +30,16 @@ jobs:
|
||||
- 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
|
||||
|
||||
+40
-29
@@ -21,13 +21,13 @@ 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: 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)
|
||||
@@ -37,19 +37,26 @@ jobs:
|
||||
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,7 +81,7 @@ jobs:
|
||||
arch: ${{ matrix.config.arch }}
|
||||
if: matrix.config.shell == 'cmd'
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
@@ -84,12 +92,12 @@ jobs:
|
||||
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"
|
||||
@@ -100,7 +108,7 @@ jobs:
|
||||
echo "CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH" >> $GITHUB_ENV
|
||||
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 }}
|
||||
@@ -146,25 +154,29 @@ jobs:
|
||||
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
|
||||
|
||||
- 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
|
||||
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 +198,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)
|
||||
@@ -197,4 +209,3 @@ jobs:
|
||||
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' }}
|
||||
|
||||
@@ -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:
|
||||
|
||||
+52
-11
@@ -89,6 +89,7 @@ option(USE_HOST_SDL "Use host SDL library" ${USE_HOST_SDL_DEFAULT})
|
||||
option(USE_HOST_LIBCHDR "Use host libchdr" 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)
|
||||
@@ -205,6 +206,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"
|
||||
@@ -219,6 +224,9 @@ elseif(LIBRETRO)
|
||||
endif()
|
||||
if(ANDROID OR USE_GLES)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE GLES GLES3 HAVE_OPENGLES HAVE_OPENGLES3 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)
|
||||
@@ -817,6 +825,13 @@ endif()
|
||||
|
||||
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)
|
||||
|
||||
@@ -1416,9 +1431,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 +1499,36 @@ 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
|
||||
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_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
|
||||
|
||||
|
||||
+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;
|
||||
|
||||
+2
-2
@@ -217,9 +217,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('"');
|
||||
|
||||
@@ -110,6 +110,7 @@ Option<int64_t> PixelBufferSize("rend.PixelBufferSize", 512_MB);
|
||||
Option<int> AnisotropicFiltering("rend.AnisotropicFiltering", 1);
|
||||
Option<int> TextureFiltering("rend.TextureFiltering", 0); // Default
|
||||
Option<bool> ThreadedRendering("rend.ThreadedRendering", true);
|
||||
Option<bool> VblankPacing("");
|
||||
Option<bool> DupeFrames("rend.DupeFrames", false);
|
||||
Option<int> PerPixelLayers("rend.PerPixelLayers", 32);
|
||||
#ifdef TARGET_UWP
|
||||
|
||||
@@ -471,6 +471,7 @@ extern Option<int64_t> PixelBufferSize;
|
||||
extern Option<int> AnisotropicFiltering;
|
||||
extern Option<int> TextureFiltering; // 0: default, 1: force nearest, 2: force linear
|
||||
extern Option<bool> ThreadedRendering;
|
||||
extern Option<bool> VblankPacing;
|
||||
extern Option<bool> DupeFrames;
|
||||
extern Option<bool> NativeDepthInterpolation;
|
||||
extern Option<bool> EmulateFramebuffer;
|
||||
|
||||
@@ -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);
|
||||
@@ -325,6 +319,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 +655,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; }
|
||||
|
||||
+12
-3
@@ -238,6 +238,13 @@ static void loadSpecialSettings()
|
||||
}
|
||||
else
|
||||
WARN_LOG(BOOT, "No region specified in IP.BIN");
|
||||
// Broadcast left on Default follows the region, the way Region's own
|
||||
// Default follows IP.BIN above: PAL for Europe, NTSC otherwise.
|
||||
// fixUpDCFlash() skips the out-of-range Default value, which left the
|
||||
// flash byte at the factory setting (NTSC) or at whatever the last
|
||||
// explicit choice wrote.
|
||||
if (config::Broadcast == 4)
|
||||
config::Broadcast.override(config::Region == 2 ? 1 : 0);
|
||||
if (config::Cable <= 1 && (!ip_meta.supportsVGA()
|
||||
|| prod_id == "T-12504N" // Caesar's Palace (NTSC)
|
||||
|| prod_id == "12502D-50")) // Caesar's Palace (PAL)
|
||||
@@ -258,7 +265,8 @@ 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)
|
||||
{
|
||||
NOTICE_LOG(BOOT, "Game doesn't support RGB. Using TV Composite instead");
|
||||
config::Cable.override(3);
|
||||
@@ -396,7 +404,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);
|
||||
@@ -1086,7 +1095,7 @@ void Emulator::vblank()
|
||||
EventManager::event(Event::VBlank);
|
||||
runner.execTasks();
|
||||
// Time out if a frame hasn't been rendered for 50 ms
|
||||
if (sh4_sched_now64() - startTime <= 50_sh4ms)
|
||||
if (!config::VblankPacing && sh4_sched_now64() - startTime <= 50_sh4ms)
|
||||
return;
|
||||
if (ggpo::active())
|
||||
ggpo::endOfFrame();
|
||||
|
||||
+15
-15
@@ -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] = {
|
||||
@@ -431,6 +430,7 @@ struct ChannelEx
|
||||
{
|
||||
u32 LSA;
|
||||
u32 LEA;
|
||||
u32 alignedLEA;
|
||||
|
||||
u8 looped;
|
||||
} loop;
|
||||
@@ -800,6 +800,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 +1064,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;
|
||||
@@ -1104,7 +1108,7 @@ void ChannelEx::StepDecodeSample(u32 CA)
|
||||
ad2 &= 0xF;
|
||||
|
||||
s32 q = adpcm.last_quant;
|
||||
if (PCMS == ADPCM && CA == loop.LSA)
|
||||
if (CA == loop.LSA)
|
||||
{
|
||||
if (!adpcm.in_loop)
|
||||
{
|
||||
@@ -1112,7 +1116,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;
|
||||
@@ -1153,11 +1157,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 +1168,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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+62
-4
@@ -21,9 +21,50 @@
|
||||
#include "hw/holly/holly_intc.h"
|
||||
#include "network/netservice.h"
|
||||
#include "serialize.h"
|
||||
#include "hw/sh4/sh4_sched.h"
|
||||
#include "cfg/option.h"
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
static RTL8139State *rtl8139device;
|
||||
|
||||
// The network backends (picotcp, DCNet) deliver frames from their own
|
||||
// threads, but the RTL8139 model and the SH-4 interrupt controller belong to
|
||||
// the emulation thread: raising the receive interrupt from another thread
|
||||
// races with SIIDRebuild() and can leave stray pending interrupts, which
|
||||
// the guest sees as an unhandled IRQ. Queue the frames here and hand them
|
||||
// to the chip from the SH-4 scheduler.
|
||||
static std::mutex rxMutex;
|
||||
static std::deque<std::vector<u8>> rxQueue;
|
||||
static std::atomic<bool> rxPending;
|
||||
static int rxSchedId = -1;
|
||||
constexpr size_t RX_QUEUE_MAX = 64;
|
||||
constexpr int RX_POLL_CYCLES = 1_sh4ms;
|
||||
|
||||
static int rxDeliver(int tag, int cycles, int jitter, void *arg)
|
||||
{
|
||||
if (rxPending.load(std::memory_order_acquire) && rtl8139device != nullptr)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(rxMutex);
|
||||
while (!rxQueue.empty() && rtl8139_can_receive(rtl8139device))
|
||||
{
|
||||
rtl8139_receive(rtl8139device, rxQueue.front().data(), rxQueue.front().size());
|
||||
rxQueue.pop_front();
|
||||
}
|
||||
rxPending = !rxQueue.empty();
|
||||
}
|
||||
return RX_POLL_CYCLES;
|
||||
}
|
||||
|
||||
static void rxClear()
|
||||
{
|
||||
std::lock_guard<std::mutex> _(rxMutex);
|
||||
rxQueue.clear();
|
||||
rxPending = false;
|
||||
}
|
||||
|
||||
// 1400 - 1600 GAPSPCI bridge registers
|
||||
// 1600 - 1700 standard PCI config
|
||||
// 1700 - 1800 rtl8139c I/O ports
|
||||
@@ -49,6 +90,7 @@ 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 void setInterrupt()
|
||||
{
|
||||
@@ -64,7 +106,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 +115,10 @@ 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);
|
||||
|
||||
rxClear();
|
||||
rxSchedId = sh4_sched_register(0, rxDeliver);
|
||||
sh4_sched_request(rxSchedId, RX_POLL_CYCLES);
|
||||
}
|
||||
|
||||
void bba_Term()
|
||||
@@ -83,6 +129,12 @@ void bba_Term()
|
||||
rtl8139_destroy(rtl8139device);
|
||||
rtl8139device = nullptr;
|
||||
}
|
||||
if (rxSchedId != -1)
|
||||
{
|
||||
sh4_sched_unregister(rxSchedId);
|
||||
rxSchedId = -1;
|
||||
}
|
||||
rxClear();
|
||||
}
|
||||
|
||||
void bba_Reset(bool hard)
|
||||
@@ -90,7 +142,8 @@ void bba_Reset(bool hard)
|
||||
if (hard)
|
||||
{
|
||||
bba_Term();
|
||||
bba_Init();
|
||||
if (config::EmulateBBA && (settings.platform.isConsole() || settings.platform.isAtomiswave()))
|
||||
bba_Init();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,11 +266,16 @@ ssize_t qemu_send_packet(RTL8139State *s, const uint8_t *buf, int size)
|
||||
return size;
|
||||
}
|
||||
|
||||
// Called from the network threads
|
||||
int bba_recv_frame(const u8 *data, u32 len)
|
||||
{
|
||||
if (!rtl8139_can_receive(rtl8139device))
|
||||
if (len < 6 || len > MAX_ETH_FRAME_SIZE)
|
||||
return 0;
|
||||
rtl8139_receive(rtl8139device, data, len);
|
||||
std::lock_guard<std::mutex> _(rxMutex);
|
||||
if (rxQueue.size() >= RX_QUEUE_MAX)
|
||||
return 0;
|
||||
rxQueue.emplace_back(data, data + len);
|
||||
rxPending.store(true, std::memory_order_release);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
|
||||
void bba_Init();
|
||||
void bba_Term();
|
||||
void bba_Reset(bool hard);
|
||||
u32 bba_ReadMem(u32 addr, u32 sz);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -154,6 +154,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,6 +173,8 @@ 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
|
||||
|
||||
@@ -651,7 +651,6 @@ void sb_Init()
|
||||
maple_Init();
|
||||
aica::sbInit();
|
||||
|
||||
bba_Init();
|
||||
ModemInit();
|
||||
}
|
||||
|
||||
|
||||
@@ -227,7 +227,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()
|
||||
|
||||
@@ -1891,7 +1891,6 @@ std::shared_ptr<maple_device> maple_Create(MapleDeviceType type)
|
||||
|
||||
default:
|
||||
ERROR_LOG(MAPLE, "Invalid device type %d", type);
|
||||
die("Invalid maple device type");
|
||||
break;
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
+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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -171,6 +171,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
|
||||
{
|
||||
|
||||
+32
-19
@@ -200,6 +200,8 @@ private:
|
||||
if (taContext == nullptr)
|
||||
return;
|
||||
|
||||
// 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;
|
||||
@@ -266,7 +268,7 @@ private:
|
||||
if (renderer->Present())
|
||||
{
|
||||
presented = true;
|
||||
if (!config::ThreadedRendering && !ggpo::active())
|
||||
if (!config::ThreadedRendering && !config::VblankPacing && !ggpo::active())
|
||||
emu.getSh4Executor()->Stop();
|
||||
#ifdef LIBRETRO
|
||||
retro_rend_present();
|
||||
@@ -328,7 +330,7 @@ public:
|
||||
{
|
||||
// force 60/50 FPS now
|
||||
lastInterval = 1;
|
||||
stability = std::max(stability, 10);
|
||||
currentInterval = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -336,37 +338,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 +394,9 @@ private:
|
||||
}
|
||||
|
||||
int lastInterval;
|
||||
int stability;
|
||||
int stable;
|
||||
int unstable;
|
||||
int currentInterval;
|
||||
|
||||
u64 lastRender;
|
||||
u64 renderInterval;
|
||||
|
||||
+2
-2
@@ -42,7 +42,7 @@ static u32 lightgun_hpos;
|
||||
static bool maple_int_pending;
|
||||
|
||||
#ifdef LIBRETRO
|
||||
extern bool retro_refresh_av_info(double fps);
|
||||
extern void retro_setfps(double fps);
|
||||
#endif
|
||||
|
||||
void CalculateSync()
|
||||
@@ -64,7 +64,7 @@ void CalculateSync()
|
||||
sh4_sched_request(vblank_schid, Line_Cycles);
|
||||
|
||||
#ifdef LIBRETRO
|
||||
retro_refresh_av_info((double)SH4_MAIN_CLOCK / ((double)Line_Cycles * (double)pvr_numscanlines));
|
||||
retro_setfps((double)SH4_MAIN_CLOCK / ((double)Line_Cycles * (double)pvr_numscanlines));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -237,9 +237,11 @@ 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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1207,7 +1207,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);
|
||||
@@ -1277,7 +1277,6 @@ static void ta_parse_vdrc(TA_context* ctx, bool primRestart)
|
||||
pass++;
|
||||
}
|
||||
|
||||
setTileClipping(ctx->rend);
|
||||
vd_ctx = nullptr;
|
||||
}
|
||||
|
||||
@@ -1321,8 +1320,6 @@ static void ta_parse_naomi2(TA_context* ctx, bool primRestart)
|
||||
}
|
||||
previousPass = pass;
|
||||
}
|
||||
|
||||
setTileClipping(ctx->rend);
|
||||
}
|
||||
|
||||
void ta_parse(TA_context *ctx, bool primRestart)
|
||||
|
||||
@@ -544,9 +544,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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -441,7 +441,6 @@ void SCIFSerialPort::deserialize(Deserializer& deser)
|
||||
transmitting = false;
|
||||
}
|
||||
SCIF_SCBRR2 &= 0xff; // work around previous issues with dynarecs
|
||||
updateBaudRate();
|
||||
}
|
||||
|
||||
struct PTYPipe : public SerialPort::Pipe
|
||||
|
||||
@@ -131,6 +131,9 @@ static void setHostRoundingMode(u32 roundingMode, u32 denorm2zero)
|
||||
//called when fpscr is changed and we must check for reg banks etc..
|
||||
void DYNACALL Sh4Context::UpdateFPSCR(Sh4Context *ctx)
|
||||
{
|
||||
// The FPU exception cause field is cleared by the next FPU instruction on real hardware.
|
||||
// FPU instructions don't update it here, so a cause bit written by the guest would stick forever.
|
||||
ctx->fpscr.full &= ~0x3f000u;
|
||||
if (ctx->fpscr.FR != ctx->old_fpscr.FR)
|
||||
// FPU bank change
|
||||
std::swap(ctx->xf, ctx->fr);
|
||||
|
||||
@@ -725,6 +725,8 @@ void deserialize(Deserializer& deser)
|
||||
p_sh4rcb->cntx.cycle_counter = SH4_TIMESLICE;
|
||||
|
||||
sh4_sched_deserialize(deser);
|
||||
// let the serial port reschedule its callback after the scheduler has been deserialized
|
||||
SCIFSerialPort::Instance().postDeserialize();
|
||||
}
|
||||
|
||||
void serialize2(Serializer& ser)
|
||||
|
||||
+47
-15
@@ -8,8 +8,8 @@ struct CHDDisc : Disc
|
||||
{
|
||||
// tracks are padded to a multiple of this many frames
|
||||
static constexpr u32 CD_TRACK_PADDING = 4;
|
||||
// lead out, lead in and pregap between 2 sessions of MIL-CDs
|
||||
static constexpr u32 SESSION_GAP = 11400;
|
||||
// lead out (01:30:00), lead in (01:00:00) and pregap (00:02:00) between 2 sessions of MIL-CDs
|
||||
static constexpr u32 SESSION_GAP = 6750 + 4500 + 150;
|
||||
|
||||
chd_file *chd = nullptr;
|
||||
u8* hunk_mem = nullptr;
|
||||
@@ -143,22 +143,30 @@ void CHDDisc::tryOpen(const char* file)
|
||||
u32 Offset = 0;
|
||||
bool isGdrom = head->version < 5; // MIL-CDs only supported starting with CHD v5
|
||||
bool needAudioSwap = false;
|
||||
u32 lastPregap = 0;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
char type[16], subtype[16], pgtype[16], pgsub[16];
|
||||
int tkid=-1, frames=0, pregap=0, postgap=0, padframes=0;
|
||||
strcpy(subtype, "NONE");
|
||||
strcpy(pgtype, "NONE");
|
||||
strcpy(pgsub, "NONE");
|
||||
|
||||
err = chd_get_metadata(chd, CDROM_TRACK_METADATA2_TAG, (u32)tracks.size(), temp, sizeof(temp), &temp_len, &tag, &flags);
|
||||
if (err == CHDERR_NONE)
|
||||
{
|
||||
//"TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d PREGAP:%d PGTYPE:%s PGSUB:%s POSTGAP:%d"
|
||||
sscanf(temp, CDROM_TRACK_METADATA2_FORMAT, &tkid, type, subtype, &frames, &pregap, pgtype, pgsub, &postgap);
|
||||
// CD-Rom audio tracks are always stored big-endian
|
||||
needAudioSwap = true;
|
||||
}
|
||||
else if (CHDERR_NONE== (err = chd_get_metadata(chd, CDROM_TRACK_METADATA_TAG, (u32)tracks.size(), temp, sizeof(temp), &temp_len, &tag, &flags)) )
|
||||
{
|
||||
//CDROM_TRACK_METADATA_FORMAT "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d"
|
||||
sscanf(temp, CDROM_TRACK_METADATA_FORMAT, &tkid, type, subtype, &frames);
|
||||
// CD-Rom audio tracks are always stored big-endian
|
||||
needAudioSwap = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -180,18 +188,30 @@ void CHDDisc::tryOpen(const char* file)
|
||||
if (tkid != (int)tracks.size() + 1)
|
||||
throw FlycastException(i18n::Ts("Unexpected track number"));
|
||||
|
||||
if (strcmp(subtype, "NONE") != 0 || pregap != 0 || postgap != 0)
|
||||
throw FlycastException(i18n::Ts("Unsupported subtype or pre/postgap"));
|
||||
// Subcode data is stored in the CHD but we don't use it. It doesn't change the sector layout.
|
||||
if (strcmp(subtype, "NONE") != 0)
|
||||
WARN_LOG(GDROM, "chd: track %d has subcode data (%s). Ignoring", tkid, subtype);
|
||||
// Postgap sectors are never stored in the file
|
||||
if (postgap != 0)
|
||||
WARN_LOG(GDROM, "chd: track %d has a %d frame postgap. Ignoring", tkid, postgap);
|
||||
|
||||
DEBUG_LOG(GDROM, "%s", temp);
|
||||
// When PGTYPE is prefixed with 'V', the pregap sectors are included in the track data,
|
||||
// and thus counted in FRAMES. Otherwise they only take up space on the disc.
|
||||
const u32 pregapInFile = pregap > 0 && pgtype[0] == 'V' ? (u32)pregap : 0;
|
||||
if ((int)pregapInFile > frames)
|
||||
throw FlycastException(i18n::Ts("Invalid CHD: pregap is longer than the track"));
|
||||
|
||||
Track t;
|
||||
t.StartFAD = total_frames;
|
||||
total_frames += frames;
|
||||
// StartFAD is the position of INDEX 01, hence after the pregap
|
||||
t.StartFAD = total_frames + pregap;
|
||||
lastPregap = pregap;
|
||||
total_frames = t.StartFAD + frames - pregapInFile;
|
||||
t.EndFAD = total_frames - 1 - padframes;
|
||||
t.CTRL = strcmp(type,"AUDIO") == 0 ? 0 : 4;
|
||||
|
||||
u32 sectorSize = getSectorSize(type);
|
||||
t.file = new CHDTrack(this, Offset - t.StartFAD, sectorSize,
|
||||
t.file = new CHDTrack(this, Offset + pregapInFile - t.StartFAD, sectorSize,
|
||||
// audio tracks are byteswapped in recent CHDv5+
|
||||
!t.isDataTrack() && needAudioSwap);
|
||||
|
||||
@@ -221,25 +241,37 @@ void CHDDisc::tryOpen(const char* file)
|
||||
sessions.push_back(ses);
|
||||
DEBUG_LOG(GDROM, "session 1: FAD %d", ses.StartFAD);
|
||||
|
||||
if (tracks.size() > 1)
|
||||
// MIL-CDs are multisession CD-Roms whose last session holds the bootable data track.
|
||||
// CHD files don't carry any session information so we assume that a multi-track disc
|
||||
// ending with a data track is a MIL-CD, and that its last session has a single track.
|
||||
if (tracks.size() > 1 && tracks.back().isDataTrack())
|
||||
{
|
||||
type = CdRom_XA;
|
||||
ses.FirstTrack = tracks.size();
|
||||
// session 1 lead-out: 01:30:00, session 2 lead-in: 01:00:00, pregap: 00:02:00
|
||||
tracks.back().StartFAD += SESSION_GAP;
|
||||
tracks.back().EndFAD += SESSION_GAP;
|
||||
((CHDTrack *)tracks.back().file)->Offset -= SESSION_GAP;
|
||||
// SESSION_GAP includes the pregap of the first track of the second session.
|
||||
// When these sectors are part of the track data they have already been accounted for.
|
||||
const u32 gap = SESSION_GAP - std::min(lastPregap, 150u);
|
||||
tracks.back().StartFAD += gap;
|
||||
tracks.back().EndFAD += gap;
|
||||
((CHDTrack *)tracks.back().file)->Offset -= gap;
|
||||
ses.StartFAD = tracks.back().StartFAD;
|
||||
sessions.push_back(ses);
|
||||
DEBUG_LOG(GDROM, "session 2: track %d FAD %d", ses.FirstTrack, ses.StartFAD);
|
||||
|
||||
EndFAD = LeadOut.StartFAD = total_frames + SESSION_GAP - 1;
|
||||
EndFAD = LeadOut.StartFAD = total_frames + gap;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Single-track CD-ROMs aren't supported with the exception of naomi mj1/cdp-10002b.chd
|
||||
// Single-session disc: mixed mode or audio CD. Single-track CD-ROMs aren't
|
||||
// supported with the exception of naomi mj1/cdp-10002b.chd
|
||||
type = CdRom;
|
||||
EndFAD = LeadOut.StartFAD = total_frames - 1;
|
||||
if (tracks.size() > 1)
|
||||
for (const Track& track : tracks)
|
||||
if (track.isDataTrack()) {
|
||||
type = CdRom_XA;
|
||||
break;
|
||||
}
|
||||
EndFAD = LeadOut.StartFAD = total_frames;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-5
@@ -83,6 +83,7 @@ Disc* cue_parse(const char* file, std::vector<u8> *digest)
|
||||
u32 currentFAD = 150;
|
||||
// SESSION context
|
||||
u32 session_number = 0;
|
||||
bool firstTrackOfSession = false;
|
||||
// FILE context
|
||||
std::string track_filename;
|
||||
u32 fileStartFAD = 0;
|
||||
@@ -113,9 +114,11 @@ Disc* cue_parse(const char* file, std::vector<u8> *digest)
|
||||
else if (cur_session != session_number)
|
||||
{
|
||||
session_number = cur_session;
|
||||
if (session_number == 2)
|
||||
if (session_number == 2) {
|
||||
// session 1 lead-out: 01:30:00, session 2 lead-in: 01:00:00, pregap: 00:02:00
|
||||
currentFAD += 11400;
|
||||
currentFAD += 6750 + 4500 + 150;
|
||||
firstTrackOfSession = true;
|
||||
}
|
||||
|
||||
Session ses;
|
||||
ses.FirstTrack = (u8)disc->tracks.size() + 1;
|
||||
@@ -154,14 +157,16 @@ Disc* cue_parse(const char* file, std::vector<u8> *digest)
|
||||
track_filename.clear();
|
||||
char last;
|
||||
do {
|
||||
cuesheet >> last;
|
||||
} while (isspace(last));
|
||||
if (!(cuesheet >> last))
|
||||
throw FlycastException(i18n::T("Invalid CUE file"));
|
||||
} while (isspace((unsigned char)last));
|
||||
|
||||
if (last == '"')
|
||||
{
|
||||
cuesheet >> std::noskipws;
|
||||
for (;;) {
|
||||
cuesheet >> last;
|
||||
if (!(cuesheet >> last))
|
||||
throw FlycastException(i18n::T("Invalid CUE file"));
|
||||
if (last == '"')
|
||||
break;
|
||||
track_filename += last;
|
||||
@@ -227,6 +232,14 @@ Disc* cue_parse(const char* file, std::vector<u8> *digest)
|
||||
int min = 0, sec = 0, frame = 0;
|
||||
if (sscanf(token.c_str(), "%d:%d:%d", &min, &sec, &frame) == 3)
|
||||
indexFAD = frame + 75 * (sec + 60 * min);
|
||||
if (firstTrackOfSession && indexFAD > 0) {
|
||||
// The session gap above already includes this track's pregap so don't
|
||||
// count the sectors stored before INDEX 01 twice.
|
||||
const u32 pregap = std::min(indexFAD, 150);
|
||||
fileStartFAD -= pregap;
|
||||
currentFAD -= pregap;
|
||||
}
|
||||
firstTrackOfSession = false;
|
||||
Track t;
|
||||
t.StartFAD = fileStartFAD + indexFAD;
|
||||
t.CTRL = (track_type == "AUDIO" || track_type == "CDG") ? 0 : 4;
|
||||
@@ -235,6 +248,8 @@ Disc* cue_parse(const char* file, std::vector<u8> *digest)
|
||||
DEBUG_LOG(GDROM, "file[%zd] \"%s\": session %d type %s FAD:%d -> %d %s", disc->tracks.size() + 1, track_filename.c_str(),
|
||||
session_number, track_type.c_str(), t.StartFAD, t.EndFAD, t.isrc.empty() ? "" : ("ISRC " + t.isrc).c_str());
|
||||
hostfs::File *track_file = hostfs::storage().openFile(track_filename, "rb");
|
||||
if (track_file == nullptr)
|
||||
throw FlycastException(strprintf(i18n::T("CUE file: cannot open track %s"), track_filename.c_str()));
|
||||
t.file = new RawTrackFile(track_file, indexFAD * track_secsize, t.StartFAD, track_secsize);
|
||||
disc->tracks.push_back(t);
|
||||
if (disc->tracks.size() >= 2) {
|
||||
|
||||
@@ -59,14 +59,16 @@ static Disc* load_gdi(const char* file, std::vector<u8> *digest)
|
||||
|
||||
char last;
|
||||
do {
|
||||
gdi >> last;
|
||||
} while (isspace(last));
|
||||
if (!(gdi >> last))
|
||||
throw FlycastException(i18n::T("Invalid GDI file"));
|
||||
} while (isspace((unsigned char)last));
|
||||
|
||||
if (last == '"')
|
||||
{
|
||||
gdi >> std::noskipws;
|
||||
for(;;) {
|
||||
gdi >> last;
|
||||
if (!(gdi >> last))
|
||||
throw FlycastException(i18n::T("Invalid GDI file"));
|
||||
if (last == '"')
|
||||
break;
|
||||
track_filename += last;
|
||||
|
||||
@@ -137,7 +137,7 @@ bool GamepadDevice::handleButtonInput(int port, DreamcastKey key, bool pressed)
|
||||
break;
|
||||
case EMU_BTN_SAVESTATE_RAM:
|
||||
if (pressed)
|
||||
gui_saveState(false, true);
|
||||
gui_saveState(true, true);
|
||||
break;
|
||||
case EMU_BTN_SCREENSHOT:
|
||||
if (pressed)
|
||||
|
||||
+24
-15
@@ -18,8 +18,17 @@
|
||||
#include "cfg/option.h"
|
||||
#include "hw/maple/maple_if.h"
|
||||
|
||||
std::array<std::array<std::list<MapleLink::Ptr>, 2>, 4> MapleLink::Links;
|
||||
std::mutex MapleLink::Mutex;
|
||||
std::mutex& MapleLink::mutex()
|
||||
{
|
||||
static std::mutex *m = new std::mutex();
|
||||
return *m;
|
||||
}
|
||||
|
||||
MapleLink::LinkTable& MapleLink::links()
|
||||
{
|
||||
static LinkTable *l = new LinkTable();
|
||||
return *l;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -44,10 +53,10 @@ GameState GameState::instance;
|
||||
std::size_t MapleLink::activeLinkCount(int bus) const
|
||||
{
|
||||
std::size_t count = 0;
|
||||
if (bus >= 0 && bus < (int)Links.size())
|
||||
if (bus >= 0 && bus < (int)links().size())
|
||||
{
|
||||
std::lock_guard<std::mutex> _(Mutex);
|
||||
for (const std::list<MapleLink::Ptr>& list : Links[bus]) {
|
||||
std::lock_guard<std::mutex> _(mutex());
|
||||
for (const std::list<MapleLink::Ptr>& list : links()[bus]) {
|
||||
if (!list.empty() && list.front().get() == this)
|
||||
++count;
|
||||
}
|
||||
@@ -61,22 +70,22 @@ bool MapleLink::isGameStarted() const {
|
||||
|
||||
void MapleLink::registerLink(int bus, int port)
|
||||
{
|
||||
if (bus >= 0 && bus < (int)Links.size()
|
||||
&& port >= 0 && port < (int)Links[0].size())
|
||||
if (bus >= 0 && bus < (int)links().size()
|
||||
&& port >= 0 && port < (int)links()[0].size())
|
||||
{
|
||||
if (this->bus != bus) {
|
||||
this->bus = bus;
|
||||
this->ports = 0;
|
||||
}
|
||||
this->ports |= 1 << port;
|
||||
std::lock_guard<std::mutex> _(Mutex);
|
||||
Links[bus][port].push_front(shared_from_this());
|
||||
std::lock_guard<std::mutex> _(mutex());
|
||||
links()[bus][port].push_front(shared_from_this());
|
||||
}
|
||||
}
|
||||
void MapleLink::unregisterLink(int bus, int port)
|
||||
{
|
||||
if (bus >= 0 && bus < (int)Links.size()
|
||||
&& port >= 0 && port < (int)Links[0].size())
|
||||
if (bus >= 0 && bus < (int)links().size()
|
||||
&& port >= 0 && port < (int)links()[0].size())
|
||||
{
|
||||
if (this->bus != bus) {
|
||||
this->bus = -1;
|
||||
@@ -85,15 +94,15 @@ void MapleLink::unregisterLink(int bus, int port)
|
||||
else {
|
||||
this->ports &= ~(1 << port);
|
||||
}
|
||||
std::lock_guard<std::mutex> _(Mutex);
|
||||
Links[bus][port].remove_if([this](const Ptr& item) { return item.get() == this; });
|
||||
std::lock_guard<std::mutex> _(mutex());
|
||||
links()[bus][port].remove_if([this](const Ptr& item) { return item.get() == this; });
|
||||
}
|
||||
}
|
||||
|
||||
bool MapleLink::StorageEnabled()
|
||||
{
|
||||
std::lock_guard<std::mutex> _(Mutex);
|
||||
for (const auto& ports : Links)
|
||||
std::lock_guard<std::mutex> _(mutex());
|
||||
for (const auto& ports : links())
|
||||
{
|
||||
for (const std::list<MapleLink::Ptr>& list : ports)
|
||||
if (!list.empty() && list.front()->storageEnabled())
|
||||
|
||||
@@ -48,8 +48,8 @@ public:
|
||||
|
||||
//! Returns the maple link at the given location if any
|
||||
static Ptr GetMapleLink(int bus, int port) {
|
||||
std::lock_guard<std::mutex> _(Mutex);
|
||||
std::list<MapleLink::Ptr>& list = Links[bus][port];
|
||||
std::lock_guard<std::mutex> _(mutex());
|
||||
std::list<MapleLink::Ptr>& list = links()[bus][port];
|
||||
if (list.empty())
|
||||
return nullptr;
|
||||
return list.front();
|
||||
@@ -66,10 +66,15 @@ protected:
|
||||
u32 ports = 0;
|
||||
|
||||
private:
|
||||
static std::mutex Mutex;
|
||||
using LinkTable = std::array<std::array<std::list<Ptr>, 2>, 4>;
|
||||
// Never destroyed: ~maple_sega_vmu() reaches GetMapleLink() from the
|
||||
// static destructor of MapleDevices[][] at process exit, after objects
|
||||
// with static storage in other translation units may already be gone.
|
||||
// Locking a destroyed std::mutex throws (libc++: "mutex lock failed").
|
||||
static std::mutex& mutex();
|
||||
//! Indexed by [bus][extension port][priority idx]
|
||||
//! Multiple links may be registered for a bus/port, and the last added to front takes precedence
|
||||
static std::array<std::array<std::list<Ptr>, 2>, 4> Links;
|
||||
static LinkTable& links();
|
||||
};
|
||||
|
||||
class BaseMapleLink : public MapleLink
|
||||
|
||||
@@ -146,6 +146,10 @@ void SetRelativeMousePosition(float xrel, float yrel, u32 mouseId)
|
||||
{
|
||||
if (mouseId >= std::size(mo_x_delta))
|
||||
return;
|
||||
// Relative motion has a different coordinate space. Rebase the next absolute
|
||||
// position instead of replaying movement since the last absolute event.
|
||||
mo_x_prev[mouseId] = -1;
|
||||
mo_y_prev[mouseId] = -1;
|
||||
int width = mo_width;
|
||||
int height = mo_height;
|
||||
if (config::Rotate90)
|
||||
|
||||
@@ -428,6 +428,26 @@ static void luaRegister(lua_State *L)
|
||||
.beginNamespace("emulator")
|
||||
.addFunction("startGame", gui_start_game) // FIXME threading!
|
||||
.addFunction("stopGame", std::function<void()>([]() { gui_stop_game(""); }))
|
||||
.addFunction("openGdrom", std::function<void()>([]() {
|
||||
bool restart = false;
|
||||
if (gui_state == GuiState::Closed) {
|
||||
gui_open_settings();
|
||||
restart = true;
|
||||
}
|
||||
emu.openGdrom();
|
||||
if (restart)
|
||||
gui_open_settings();
|
||||
}))
|
||||
.addFunction("insertGdrom", std::function<void(const std::string&)>([](const std::string& path) {
|
||||
bool restart = false;
|
||||
if (gui_state == GuiState::Closed) {
|
||||
gui_open_settings();
|
||||
restart = true;
|
||||
}
|
||||
emu.insertGdrom(path);
|
||||
if (restart)
|
||||
gui_open_settings();
|
||||
}))
|
||||
.addFunction("pause", std::function<void()>([]() {
|
||||
if (gui_state == GuiState::Closed)
|
||||
gui_open_settings();
|
||||
|
||||
+12
-1
@@ -42,6 +42,7 @@ namespace net::modbba
|
||||
{
|
||||
|
||||
static TsQueue<u8> toModem;
|
||||
static constexpr size_t MAX_ETH_FRAME_SIZE = 1514;
|
||||
|
||||
class DCNetService : public Service
|
||||
{
|
||||
@@ -280,6 +281,8 @@ private:
|
||||
iterator i = begin;
|
||||
uint16_t len = (uint8_t)*i++;
|
||||
len |= uint8_t(*i++) << 8;
|
||||
if (len > MAX_ETH_FRAME_SIZE)
|
||||
return std::make_pair(begin + 2, true);
|
||||
len += 2;
|
||||
if (end - begin < len)
|
||||
return std::make_pair(begin, false);
|
||||
@@ -299,6 +302,14 @@ private:
|
||||
socket.close(ignored);
|
||||
return;
|
||||
}
|
||||
uint16_t frameLen = recvBuffer[0] | recvBuffer[1] << 8;
|
||||
if (frameLen > MAX_ETH_FRAME_SIZE || len != (size_t)frameLen + 2)
|
||||
{
|
||||
ERROR_LOG(NETWORK, "Invalid Ethernet frame size: %d", frameLen);
|
||||
std::error_code ignored;
|
||||
socket.close(ignored);
|
||||
return;
|
||||
}
|
||||
/*
|
||||
verify(len - 2 == *(u16 *)&recvBuffer[0]);
|
||||
printf("In frame: dest %02x:%02x:%02x:%02x:%02x:%02x "
|
||||
@@ -422,7 +433,7 @@ public:
|
||||
this->handler = std::function<void(const std::error_code&, const asio::ip::address&, const std::string&)>(handler);
|
||||
try {
|
||||
asio::ip::udp::resolver resolver(io_context);
|
||||
auto it = resolver.resolve("dcnet.flyca.st", std::to_string(PORT));
|
||||
auto it = resolver.resolve(asio::ip::udp::v4(), "dcnet.flyca.st", std::to_string(PORT));
|
||||
if (it.empty()) {
|
||||
finish();
|
||||
return;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "cfg/option.h"
|
||||
#include "oslib/oslib.h"
|
||||
#include "oslib/i18n.h"
|
||||
#include "net_platform.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace ggpo
|
||||
@@ -595,6 +596,25 @@ void startSession(int localPort, int localPlayerNum)
|
||||
peerPort = atoi(config::NetworkServer.get().substr(colon + 1).c_str());
|
||||
}
|
||||
player.type = GGPO_PLAYERTYPE_REMOTE;
|
||||
// GGPO only takes a dotted IPv4 address: resolve host names here
|
||||
in_addr peerAddr;
|
||||
if (inet_pton(AF_INET, peerIp.c_str(), &peerAddr) != 1)
|
||||
{
|
||||
addrinfo hints {};
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
addrinfo *result = nullptr;
|
||||
if (getaddrinfo(peerIp.c_str(), nullptr, &hints, &result) != 0 || result == nullptr)
|
||||
{
|
||||
stopSession();
|
||||
throw FlycastException(strprintf(i18n::T("GGPO: unknown peer address %s"), peerIp.c_str()));
|
||||
}
|
||||
char address[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &((sockaddr_in *)result->ai_addr)->sin_addr, address, sizeof(address));
|
||||
freeaddrinfo(result);
|
||||
NOTICE_LOG(NETWORK, "GGPO: peer %s resolved to %s", peerIp.c_str(), address);
|
||||
peerIp = address;
|
||||
}
|
||||
strcpy(player.u.remote.ip_address, peerIp.c_str());
|
||||
player.u.remote.port = peerPort;
|
||||
player.player_num = (1 - localPlayerNum) + 1;
|
||||
|
||||
@@ -54,8 +54,10 @@ extern "C" {
|
||||
#include "hw/bba/bba.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <future>
|
||||
#include <vector>
|
||||
|
||||
#define RESOLVER1_OPENDNS_COM "208.67.222.222"
|
||||
#define AFO_ORIG_IP 0x83f2fb3f // 63.251.242.131 in network order
|
||||
@@ -403,7 +405,10 @@ private:
|
||||
}
|
||||
else {
|
||||
DEBUG_LOG(NETWORK, "TcpSocket[%s] outbound %d bytes", name.c_str(), (int)len);
|
||||
picoCallback(0);
|
||||
// A read notification can cover more data than fits in sendbuf.
|
||||
// Resume draining after the asynchronous write releases that buffer,
|
||||
// even if no new packet (and therefore no new read event) arrives.
|
||||
picoCallback(pico_sock != nullptr && pico.state != Closed ? PICO_SOCK_EV_RD : 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1173,14 +1178,40 @@ static void closeDumpFile()
|
||||
pcapngDump = nullptr;
|
||||
}
|
||||
}
|
||||
// Frames sent by the emulated BBA arrive on the emulation thread. picotcp is
|
||||
// built without PICO_SUPPORT_MUTEX, so pico_queue_protect() does nothing and
|
||||
// calling pico_stack_recv() here corrupts the device queue that the pico
|
||||
// thread is draining. Queue the frames and feed them from the pico thread.
|
||||
static std::mutex ethInMutex;
|
||||
static std::deque<std::vector<u8>> ethInQueue;
|
||||
constexpr size_t ETH_IN_QUEUE_MAX = 256;
|
||||
|
||||
static void pico_receive_eth_frame(const u8 *frame, u32 size)
|
||||
{
|
||||
if (pico_dev == nullptr) {
|
||||
start_pico();
|
||||
}
|
||||
else {
|
||||
dumpFrame(frame, size);
|
||||
pico_stack_recv(pico_dev, (u8 *)frame, size);
|
||||
std::lock_guard<std::mutex> _(ethInMutex);
|
||||
if (ethInQueue.size() < ETH_IN_QUEUE_MAX)
|
||||
ethInQueue.emplace_back(frame, frame + size);
|
||||
}
|
||||
}
|
||||
|
||||
// Called on the pico thread
|
||||
static void feed_eth_frames()
|
||||
{
|
||||
std::deque<std::vector<u8>> frames;
|
||||
{
|
||||
std::lock_guard<std::mutex> _(ethInMutex);
|
||||
frames.swap(ethInQueue);
|
||||
}
|
||||
if (pico_dev == nullptr)
|
||||
return;
|
||||
for (auto& frame : frames)
|
||||
{
|
||||
dumpFrame(frame.data(), frame.size());
|
||||
pico_stack_recv(pico_dev, frame.data(), frame.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1195,6 +1226,7 @@ static void picoTick(const std::error_code& ec, asio::steady_timer *timer)
|
||||
ERROR_LOG(NETWORK, "picoTick timer error: %s", ec.message().c_str());
|
||||
return;
|
||||
}
|
||||
feed_eth_frames();
|
||||
pico_stack_tick();
|
||||
timer->expires_at(timer->expiry() + asio::chrono::milliseconds(PICO_TICK_MS));
|
||||
timer->async_wait(std::bind(picoTick, asio::placeholders::error, timer));
|
||||
@@ -1456,6 +1488,8 @@ static void stop_pico()
|
||||
emu.setNetworkState(false);
|
||||
pico_thread_running = false;
|
||||
pico_thread.stop();
|
||||
std::lock_guard<std::mutex> _(ethInMutex);
|
||||
ethInQueue.clear();
|
||||
}
|
||||
|
||||
// picotcp mutex implementation
|
||||
|
||||
+31
-13
@@ -61,17 +61,25 @@ void init()
|
||||
}
|
||||
}
|
||||
|
||||
static bool crackUrl(const std::string& url, URL_COMPONENTS& components)
|
||||
struct CrackedUrl
|
||||
{
|
||||
static char scheme[16], host[256], path[256];
|
||||
char scheme[16];
|
||||
char host[256];
|
||||
char path[2048];
|
||||
URL_COMPONENTS components;
|
||||
};
|
||||
|
||||
static bool crackUrl(const std::string& url, CrackedUrl& cracked)
|
||||
{
|
||||
URL_COMPONENTS& components = cracked.components;
|
||||
components = {};
|
||||
components.dwStructSize = sizeof(components);
|
||||
components.lpszScheme = scheme;
|
||||
components.dwSchemeLength = sizeof(scheme) / sizeof(scheme[0]);
|
||||
components.lpszHostName = host;
|
||||
components.dwHostNameLength = sizeof(host) / sizeof(host[0]);
|
||||
components.lpszUrlPath = path;
|
||||
components.dwUrlPathLength = sizeof(path) / sizeof(path[0]);
|
||||
components.lpszScheme = cracked.scheme;
|
||||
components.dwSchemeLength = sizeof(cracked.scheme) / sizeof(cracked.scheme[0]);
|
||||
components.lpszHostName = cracked.host;
|
||||
components.dwHostNameLength = sizeof(cracked.host) / sizeof(cracked.host[0]);
|
||||
components.lpszUrlPath = cracked.path;
|
||||
components.dwUrlPathLength = sizeof(cracked.path) / sizeof(cracked.path[0]);
|
||||
|
||||
return InternetCrackUrlA(url.c_str(), url.length(), 0, &components);
|
||||
}
|
||||
@@ -82,9 +90,10 @@ static HINTERNET connect(const URL_COMPONENTS& comp) {
|
||||
|
||||
int get(const std::string& url, std::vector<u8>& content, const Headers *reqHeaders, Headers *respHeaders)
|
||||
{
|
||||
URL_COMPONENTS components;
|
||||
if (!crackUrl(url, components))
|
||||
CrackedUrl cracked;
|
||||
if (!crackUrl(url, cracked))
|
||||
return 500;
|
||||
const URL_COMPONENTS& components = cracked.components;
|
||||
|
||||
bool https = !strcmp(components.lpszScheme, "https");
|
||||
|
||||
@@ -111,6 +120,14 @@ int get(const std::string& url, std::vector<u8>& content, const Headers *reqHead
|
||||
}
|
||||
else
|
||||
{
|
||||
DWORD status;
|
||||
DWORD size = sizeof(status);
|
||||
DWORD index = 0;
|
||||
if (HttpQueryInfo(hreq, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &size, &index))
|
||||
rc = status;
|
||||
else
|
||||
WARN_LOG(NETWORK, "HttpQueryInfo Error %d", GetLastError());
|
||||
|
||||
if (respHeaders != nullptr)
|
||||
{
|
||||
std::string headers(4096, '\0');
|
||||
@@ -163,14 +180,15 @@ int get(const std::string& url, std::vector<u8>& content, const Headers *reqHead
|
||||
InternetCloseHandle(hreq);
|
||||
InternetCloseHandle(ic);
|
||||
|
||||
return 200;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int post(const std::string& url, const char *headers, const u8 *payload, u32 payloadSize, std::vector<u8>& reply)
|
||||
{
|
||||
URL_COMPONENTS components;
|
||||
if (!crackUrl(url, components))
|
||||
CrackedUrl cracked;
|
||||
if (!crackUrl(url, cracked))
|
||||
return 500;
|
||||
const URL_COMPONENTS& components = cracked.components;
|
||||
|
||||
bool https = !strcmp(components.lpszScheme, "https");
|
||||
|
||||
|
||||
@@ -48,6 +48,17 @@ void release_jit_block(void *code_area, size_t size);
|
||||
// Release a jit block previously allocated by prepare_jit_block (with dual RW and RX areas)
|
||||
void release_jit_block(void *code_area1, void *code_area2, size_t size);
|
||||
|
||||
// The address part of a host pointer. On arm64, Android may put a tag in the top
|
||||
// byte of heap pointers, and a signal handler receives fault addresses without it.
|
||||
static inline uintptr_t untag(const void *p)
|
||||
{
|
||||
#if HOST_CPU == CPU_ARM64 && defined(__ANDROID__)
|
||||
return (uintptr_t)p & 0x00ffffffffffffffull;
|
||||
#else
|
||||
return (uintptr_t)p;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool region_lock(void *start, std::size_t len);
|
||||
bool region_unlock(void *start, std::size_t len);
|
||||
bool region_set_exec(void *start, std::size_t len);
|
||||
|
||||
@@ -598,16 +598,19 @@ bool BaseTextureCacheData::Update()
|
||||
// xBRZ scaling
|
||||
if (textureUpscaling)
|
||||
{
|
||||
// xBRZ only supports scale factors 2 to 6. Out of range, scale() returns
|
||||
// without writing the destination buffer.
|
||||
const int factor = std::clamp((int)config::TextureUpscale, 2, 6);
|
||||
PixelBuffer<u32> tmp_buf;
|
||||
tmp_buf.init(width * config::TextureUpscale, height * config::TextureUpscale);
|
||||
tmp_buf.init(width * factor, height * factor);
|
||||
|
||||
if (tcw.PixelFmt == Pixel1555 || tcw.PixelFmt == Pixel4444)
|
||||
// Alpha channel formats. Palettes with alpha are already handled
|
||||
has_alpha = true;
|
||||
UpscalexBRZ(config::TextureUpscale, pb32.data(), tmp_buf.data(), width, height, has_alpha);
|
||||
UpscalexBRZ(factor, pb32.data(), tmp_buf.data(), width, height, has_alpha);
|
||||
pb32.steal_data(tmp_buf);
|
||||
upscaled_w *= config::TextureUpscale;
|
||||
upscaled_h *= config::TextureUpscale;
|
||||
upscaled_w *= factor;
|
||||
upscaled_h *= factor;
|
||||
}
|
||||
}
|
||||
temp_tex_buffer = pb32.data();
|
||||
|
||||
@@ -455,7 +455,16 @@ struct DX11OITRenderer : public DX11Renderer
|
||||
deviceContext->PSSetShaderResources(0, 1, &opaqueTextureView.get());
|
||||
auto sampler = samplers->getSampler(false);
|
||||
deviceContext->PSSetSamplers(0, 1, &sampler.get());
|
||||
deviceContext->RSSetScissorRects(1, &scissorRect);
|
||||
D3D11_RECT rect {};
|
||||
if (rendContext->isRTT) {
|
||||
rect.right = rendContext->framebufferWidth;
|
||||
rect.bottom = rendContext->framebufferHeight;
|
||||
}
|
||||
else {
|
||||
rect.right = width;
|
||||
rect.bottom = height;
|
||||
}
|
||||
deviceContext->RSSetScissorRects(1, &rect);
|
||||
deviceContext->OMSetDepthStencilState(depthStencilStates.getState(false, false, 0, false), 0);
|
||||
setCullMode(0);
|
||||
|
||||
|
||||
@@ -486,7 +486,6 @@ void gl4CreateTextures(int width, int height)
|
||||
void gl4DrawStrips(GLuint output_fbo, int width, int height)
|
||||
{
|
||||
checkOverflowAndReset();
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, geom_fbo[0]);
|
||||
if (!gl.rendContext->isRTT)
|
||||
{
|
||||
glcache.Disable(GL_SCISSOR_TEST);
|
||||
@@ -494,6 +493,7 @@ void gl4DrawStrips(GLuint output_fbo, int width, int height)
|
||||
if (gl.rendContext->clearFramebuffer)
|
||||
{
|
||||
// Clear framebuffer
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, geom_fbo[1]);
|
||||
glcache.ClearColor(VO_BORDER_COL.red(), VO_BORDER_COL.green(), VO_BORDER_COL.blue(), 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
@@ -504,7 +504,6 @@ void gl4DrawStrips(GLuint output_fbo, int width, int height)
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, output_fbo);
|
||||
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
glCheck();
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, geom_fbo[0]);
|
||||
}
|
||||
if (gl4ShaderUniforms.base_clipping.enabled)
|
||||
glcache.Enable(GL_SCISSOR_TEST);
|
||||
@@ -512,6 +511,7 @@ void gl4DrawStrips(GLuint output_fbo, int width, int height)
|
||||
if (texSamplers[0] == 0)
|
||||
glGenSamplers(2, texSamplers);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, geom_fbo[0]);
|
||||
glcache.DepthMask(GL_TRUE);
|
||||
glClearDepthf(0.f);
|
||||
glcache.StencilMask(0xFF);
|
||||
|
||||
+14
-8
@@ -18,8 +18,10 @@
|
||||
|
||||
#include <cstring>
|
||||
|
||||
// Vulkan and DirectX use the color values of the first vertex for flat shaded triangle strips.
|
||||
// On Dreamcast the last vertex is the provoking one so we must copy it onto the first.
|
||||
// Vulkan and DirectX use the color values of the first vertex for flat shaded triangles.
|
||||
// On Dreamcast the last vertex is the provoking one, so for strips we copy its colors onto the
|
||||
// first vertex, and for the depth-sorted triangle list we rotate each triangle's indices instead
|
||||
// (the sorted list shares vertices between triangles, so copying would corrupt neighbors).
|
||||
void setFirstProvokingVertex(rend_context& rendContext)
|
||||
{
|
||||
auto setProvokingVertex = [&rendContext](const std::vector<PolyParam>& list) {
|
||||
@@ -62,18 +64,22 @@ void setFirstProvokingVertex(rend_context& rendContext)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sorted triangles share vertices (tri0=(P0,P1,P2), tri1=(P2,P1,P3)), and
|
||||
// they're processed in depth order, so copying colors can overwrite a
|
||||
// source before it's read. Each triangle owns its 3 index slots though:
|
||||
// rotate (a,b,c) -> (c,a,b), which keeps the winding and puts the
|
||||
// provoking vertex first.
|
||||
for (const SortedTriangle& tri : rendContext.sortedTriangles)
|
||||
{
|
||||
if (rendContext.global_param_tr[tri.polyIndex].pcw.Gouraud)
|
||||
continue;
|
||||
for (u32 i = 0; i + 2 < tri.count; i += 3)
|
||||
{
|
||||
Vertex& vertex = rendContext.verts[rendContext.idx[tri.first + i]];
|
||||
Vertex& lastVertex = rendContext.verts[rendContext.idx[tri.first + i + 2]];
|
||||
memcpy(vertex.col, lastVertex.col, sizeof(vertex.col));
|
||||
memcpy(vertex.spc, lastVertex.spc, sizeof(vertex.spc));
|
||||
memcpy(vertex.col1, lastVertex.col1, sizeof(vertex.col1));
|
||||
memcpy(vertex.spc1, lastVertex.spc1, sizeof(vertex.spc1));
|
||||
u32 *idx = &rendContext.idx[tri.first + i];
|
||||
const u32 last = idx[2];
|
||||
idx[2] = idx[1];
|
||||
idx[1] = idx[0];
|
||||
idx[0] = last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,8 +84,18 @@ public:
|
||||
|
||||
void ResetPixelCounter(vk::CommandBuffer commandBuffer)
|
||||
{
|
||||
// Previous render passes and frames in flight must be done with the counter
|
||||
vk::BufferMemoryBarrier barrier(vk::AccessFlagBits::eShaderWrite, vk::AccessFlagBits::eTransferWrite,
|
||||
vk::QueueFamilyIgnored, vk::QueueFamilyIgnored, *pixelCounter->buffer, 0, vk::WholeSize);
|
||||
commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eFragmentShader, vk::PipelineStageFlagBits::eTransfer,
|
||||
{}, nullptr, barrier, nullptr);
|
||||
vk::BufferCopy copy(0, 0, sizeof(int));
|
||||
commandBuffer.copyBuffer(*pixelCounterReset->buffer, *pixelCounter->buffer, copy);
|
||||
// The reset must be visible to the fragment shaders of the next render pass
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader,
|
||||
{}, nullptr, barrier, nullptr);
|
||||
}
|
||||
|
||||
void Term()
|
||||
|
||||
@@ -231,7 +231,7 @@ protected:
|
||||
vk::CommandBuffer NewFrame() override;
|
||||
vk::Framebuffer getFramebuffer(int renderPass, int renderPassCount) override;
|
||||
vk::ImageLayout getAttachmentInitialLayout() override {
|
||||
return config::RenderToTextureBuffer ? vk::ImageLayout::eTransferSrcOptimal : vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
return vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -77,11 +77,37 @@ vk::UniqueRenderPass RenderPasses::MakeRenderPass(bool initial, bool last, bool
|
||||
std::vector<vk::SubpassDependency> dependencies = GetSubpassDependencies();
|
||||
dependencies.emplace_back(vk::SubpassExternal, 1, vk::PipelineStageFlagBits::eFragmentShader, vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::AccessFlagBits::eInputAttachmentRead, vk::AccessFlagBits::eColorAttachmentWrite, vk::DependencyFlagBits::eByRegion);
|
||||
// The OP+PT color and depth/stencil attachments and the OIT storage buffers are shared by consecutive
|
||||
// render passes and by all the frames in flight. Previous reads (input attachments) and writes (color, depth, a-buffers)
|
||||
// must be done before these attachments are cleared or loaded and the a-buffers are written again.
|
||||
// Attachments 1 and 2 are first used in subpass 0 and depth attachment 3 in subpass 1.
|
||||
constexpr vk::PipelineStageFlags oitStages = vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests
|
||||
| vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||
for (u32 subpass : { 0u, 1u })
|
||||
dependencies.emplace_back(vk::SubpassExternal, subpass, oitStages, oitStages,
|
||||
vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite | vk::AccessFlagBits::eShaderWrite,
|
||||
vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite
|
||||
| vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite
|
||||
| vk::AccessFlagBits::eInputAttachmentRead | vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite);
|
||||
// Attachment 0 is first used in subpass 2. With multiple render passes, it was the OP+PT color attachment
|
||||
// of the previous render pass (written as color and read as input attachment).
|
||||
dependencies.emplace_back(vk::SubpassExternal, 2,
|
||||
vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::AccessFlagBits::eColorAttachmentWrite,
|
||||
vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite);
|
||||
dependencies.emplace_back(0, 1, vk::PipelineStageFlagBits::eLateFragmentTests, vk::PipelineStageFlagBits::eFragmentShader,
|
||||
vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite,
|
||||
vk::AccessFlagBits::eInputAttachmentRead | vk::AccessFlagBits::eShaderRead, vk::DependencyFlagBits::eByRegion);
|
||||
dependencies.emplace_back(1, 2, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader,
|
||||
vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eInputAttachmentRead, vk::DependencyFlagBits::eByRegion);
|
||||
// Chains the external dependency above with the OP+PT color attachment transition to subpass 2
|
||||
dependencies.emplace_back(0, 1, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite,
|
||||
vk::DependencyFlagBits::eByRegion);
|
||||
// The OP+PT color attachment is stored at the end of subpass 2
|
||||
dependencies.emplace_back(1, 2, vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eInputAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite,
|
||||
vk::DependencyFlagBits::eByRegion);
|
||||
// This dependency is only needed if the render pass isn't the last: it's needed for the depth-only Tr pass
|
||||
// Unfortunately we want all render passes to be compatible, and that means all attachments must be identical
|
||||
dependencies.emplace_back(1, 2, vk::PipelineStageFlagBits::eFragmentShader,
|
||||
|
||||
@@ -149,6 +149,8 @@ bool VulkanContext::InitInstance(const char** extensions, uint32_t extensions_co
|
||||
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr;
|
||||
#if defined(__ANDROID__) && HOST_CPU == CPU_ARM64
|
||||
vkGetInstanceProcAddr = loadVulkanDriver();
|
||||
#elif defined(__APPLE__) && defined(USE_SDL)
|
||||
vkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(SDL_Vulkan_GetVkGetInstanceProcAddr());
|
||||
#else
|
||||
static vk::detail::DynamicLoader dl;
|
||||
vkGetInstanceProcAddr = dl.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr");
|
||||
|
||||
+61
-21
@@ -323,6 +323,35 @@ static std::shared_ptr<SDLMouse> getMouse(u32 mouseId)
|
||||
return mouse;
|
||||
}
|
||||
|
||||
static u32 openFileEventType()
|
||||
{
|
||||
static const u32 eventType = SDL_RegisterEvents(1);
|
||||
return eventType;
|
||||
}
|
||||
|
||||
// Queue a game to be started from the main loop.
|
||||
// A user event is used rather than SDL_DROPFILE because sdl2-compat doesn't carry over
|
||||
// the file name of drop events pushed by the application, and crashes when converting them back.
|
||||
bool sdl_queue_open_file(const char *path)
|
||||
{
|
||||
if (openFileEventType() == (u32)-1)
|
||||
return false;
|
||||
if (SDL_WasInit(SDL_INIT_EVENTS) == 0 && SDL_InitSubSystem(SDL_INIT_EVENTS) != 0)
|
||||
return false;
|
||||
|
||||
SDL_Event event = {};
|
||||
event.type = openFileEventType();
|
||||
event.user.data1 = SDL_strdup(path);
|
||||
if (event.user.data1 == nullptr)
|
||||
return false;
|
||||
if (SDL_PushEvent(&event) != 1)
|
||||
{
|
||||
SDL_free(event.user.data1);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void input_sdl_handle()
|
||||
{
|
||||
SDLGamepad::UpdateRumble();
|
||||
@@ -330,6 +359,12 @@ void input_sdl_handle()
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
if (event.type == openFileEventType())
|
||||
{
|
||||
gui_start_game((const char *)event.user.data1);
|
||||
SDL_free(event.user.data1);
|
||||
continue;
|
||||
}
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
@@ -341,6 +376,27 @@ void input_sdl_handle()
|
||||
checkRawInput();
|
||||
if (event.key.repeat == 0)
|
||||
{
|
||||
// Alt-Return and F11 toggle full screen
|
||||
if (event.type == SDL_KEYDOWN
|
||||
&& ((event.key.keysym.sym == SDLK_RETURN && (event.key.keysym.mod & KMOD_ALT))
|
||||
|| (event.key.keysym.sym == SDLK_F11 && (event.key.keysym.mod & (KMOD_ALT | KMOD_CTRL | KMOD_SHIFT | KMOD_GUI)) == 0)))
|
||||
{
|
||||
if (window_fullscreen)
|
||||
{
|
||||
SDL_SetWindowFullscreen(window, 0);
|
||||
if (!gameRunning || !mouseCaptured)
|
||||
SDL_ShowCursor(SDL_ENABLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
if (gameRunning)
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
}
|
||||
window_fullscreen = !window_fullscreen;
|
||||
break;
|
||||
}
|
||||
// Route other keyboard events to master in multiboard mode
|
||||
if (settings.naomi.slave) {
|
||||
Multiboard::keyboardEvent(event.key.keysym.scancode, event.type == SDL_KEYDOWN);
|
||||
break;
|
||||
@@ -369,25 +425,6 @@ void input_sdl_handle()
|
||||
};
|
||||
if (event.type == SDL_KEYDOWN)
|
||||
{
|
||||
// Alt-Return and F11 toggle full screen
|
||||
if ((event.key.keysym.sym == SDLK_RETURN && (event.key.keysym.mod & KMOD_ALT))
|
||||
|| (event.key.keysym.sym == SDLK_F11 && (event.key.keysym.mod & (KMOD_ALT | KMOD_CTRL | KMOD_SHIFT | KMOD_GUI)) == 0))
|
||||
{
|
||||
if (window_fullscreen)
|
||||
{
|
||||
SDL_SetWindowFullscreen(window, 0);
|
||||
if (!gameRunning || !mouseCaptured)
|
||||
SDL_ShowCursor(SDL_ENABLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
if (gameRunning)
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
}
|
||||
window_fullscreen = !window_fullscreen;
|
||||
break;
|
||||
}
|
||||
// Left-Alt + Left-CTRL toggles mouse capture
|
||||
if ((event.key.keysym.mod & KMOD_LALT) && (event.key.keysym.mod & KMOD_LCTRL)
|
||||
&& !(is_key_mapped(SDL_SCANCODE_LALT) || is_key_mapped(SDL_SCANCODE_LCTRL)))
|
||||
@@ -569,6 +606,7 @@ void input_sdl_handle()
|
||||
|
||||
case SDL_DROPFILE:
|
||||
gui_start_game(event.drop.file);
|
||||
SDL_free(event.drop.file);
|
||||
break;
|
||||
|
||||
// Switch touchscreen support
|
||||
@@ -610,7 +648,9 @@ void input_sdl_handle()
|
||||
|
||||
void sdlReceiveSlaveKeyboardEvent(u16 scancode, bool pressed)
|
||||
{
|
||||
sdl_keyboard->input((SDL_Scancode)scancode, pressed);
|
||||
if (sdl_keyboard != nullptr)
|
||||
// TODO raw input support
|
||||
sdl_keyboard->input((SDL_Scancode)scancode, pressed);
|
||||
if (pressed)
|
||||
{
|
||||
u32 flags = SDL_GetWindowFlags(window);
|
||||
@@ -960,7 +1000,7 @@ void sdl_window_create()
|
||||
die("error initializing SDL Video subsystem");
|
||||
}
|
||||
#if defined(__APPLE__) && defined(USE_VULKAN)
|
||||
SDL_Vulkan_LoadLibrary("libvulkan.dylib");
|
||||
SDL_Vulkan_LoadLibrary("@executable_path/../Frameworks/libMoltenVK.dylib");
|
||||
#endif
|
||||
}
|
||||
sdlDeInit.initialized = true;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
void input_sdl_init();
|
||||
void input_sdl_handle();
|
||||
void input_sdl_quit();
|
||||
bool sdl_queue_open_file(const char *path);
|
||||
void sdl_window_create();
|
||||
void sdl_window_destroy();
|
||||
bool sdl_recreate_window(u32 flags);
|
||||
|
||||
@@ -100,6 +100,10 @@ public:
|
||||
return bind;
|
||||
}
|
||||
// Axis
|
||||
// bind{} only zeroes the union's first member (button); direction is
|
||||
// read below even when no sign or '~' sets it.
|
||||
bind.value.axis.axis = 0;
|
||||
bind.value.axis.direction = 0;
|
||||
if (mapping[valuePos] == '+') {
|
||||
bind.value.axis.direction = 1;
|
||||
valuePos++;
|
||||
|
||||
+12
-6
@@ -75,6 +75,7 @@ void dc_deserialize(Deserializer& deser)
|
||||
sh4::deserialize(deser);
|
||||
|
||||
deser >> config::EmulateBBA.get();
|
||||
bba_Reset(true);
|
||||
if (config::EmulateBBA)
|
||||
bba_Deserialize(deser);
|
||||
ModemDeserialize(deser);
|
||||
@@ -85,12 +86,17 @@ void dc_deserialize(Deserializer& deser)
|
||||
|
||||
naomi_Deserialize(deser);
|
||||
|
||||
deser >> config::Broadcast.get();
|
||||
verify(config::Broadcast >= 0 && config::Broadcast <= 4);
|
||||
deser >> config::Cable.get();
|
||||
verify(config::Cable >= 0 && config::Cable <= 3);
|
||||
deser >> config::Region.get();
|
||||
verify(config::Region >= 0 && config::Region <= 3);
|
||||
// Validate before applying: a corrupt or incompatible state shouldn't
|
||||
// abort the process or leave invalid settings behind.
|
||||
int broadcast, cable, region;
|
||||
deser >> broadcast;
|
||||
deser >> cable;
|
||||
deser >> region;
|
||||
if (broadcast < 0 || broadcast > 4 || cable < 0 || cable > 3 || region < 0 || region > 3)
|
||||
throw Deserializer::Exception("Invalid broadcast, cable or region");
|
||||
config::Broadcast.get() = broadcast;
|
||||
config::Cable.get() = cable;
|
||||
config::Region.get() = region;
|
||||
|
||||
naomi_cart_deserialize(deser);
|
||||
reios_deserialize(deser);
|
||||
|
||||
@@ -264,3 +264,13 @@ std::string timeToShortDateTimeString(time_t time) {
|
||||
return i18n::formatShortDateTime(time);
|
||||
}
|
||||
|
||||
|
||||
MD5Sum& MD5Sum::add(hostfs::File *file)
|
||||
{
|
||||
file->seek(0, SEEK_SET);
|
||||
char buf[4096];
|
||||
size_t len;
|
||||
while ((len = file->read(buf, 1, sizeof(buf))) > 0)
|
||||
MD5_Update(&ctx, buf, (unsigned long)len);
|
||||
return *this;
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <cassert>
|
||||
#include <time.h>
|
||||
|
||||
@@ -186,6 +187,10 @@ std::string strprintf(const char *format, Args ... args)
|
||||
return out;
|
||||
}
|
||||
|
||||
namespace hostfs {
|
||||
class File;
|
||||
}
|
||||
|
||||
class MD5Sum
|
||||
{
|
||||
MD5_CTX ctx;
|
||||
@@ -209,8 +214,11 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
MD5Sum& add(hostfs::File *file);
|
||||
|
||||
template<typename T>
|
||||
MD5Sum& add(const T& v) {
|
||||
static_assert(!std::is_pointer<T>::value, "MD5Sum::add() would hash the pointer value, not the data it points to");
|
||||
MD5_Update(&ctx, &v, (unsigned long)sizeof(T));
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -87,6 +87,8 @@ void Boxart::fetchBoxart()
|
||||
return;
|
||||
if (toFetch.empty())
|
||||
return;
|
||||
if (getTimeMs() < pauseUntil)
|
||||
return;
|
||||
fetching = std::async(std::launch::async, [this]() {
|
||||
ThreadName _("BoxArt-scraper");
|
||||
if (offlineScraper == nullptr)
|
||||
@@ -119,7 +121,15 @@ void Boxart::fetchBoxart()
|
||||
toFetch.erase(toFetch.begin(), toFetch.begin() + size);
|
||||
}
|
||||
DEBUG_LOG(COMMON, "Scraping %d games", (int)boxart.size());
|
||||
offlineScraper->scrape(boxart);
|
||||
for (GameBoxart& b : boxart)
|
||||
{
|
||||
if (b.parsed)
|
||||
continue;
|
||||
offlineScraper->scrape(b);
|
||||
if (b.parsed)
|
||||
databaseDirty = true;
|
||||
}
|
||||
if (databaseDirty)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(mutex);
|
||||
for (GameBoxart& b : boxart)
|
||||
@@ -128,7 +138,6 @@ void Boxart::fetchBoxart()
|
||||
if (!config::FetchBoxart || b.scraped)
|
||||
b.busy = false;
|
||||
games[b.fileName] = b;
|
||||
databaseDirty = true;
|
||||
}
|
||||
}
|
||||
if (config::FetchBoxart)
|
||||
@@ -147,7 +156,7 @@ void Boxart::fetchBoxart()
|
||||
databaseDirty = true;
|
||||
} catch (const std::runtime_error& e) {
|
||||
if (*e.what() != '\0')
|
||||
INFO_LOG(COMMON, "thegamesdb error: %s", e.what());
|
||||
WARN_LOG(COMMON, "thegamesdb error: %s", e.what());
|
||||
{
|
||||
// put back failed items into toFetch array
|
||||
std::lock_guard<std::mutex> guard(mutex);
|
||||
@@ -165,6 +174,9 @@ void Boxart::fetchBoxart()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!databaseDirty)
|
||||
// No progress so pause for 10 s
|
||||
pauseUntil = getTimeMs() + 10 * 1000;
|
||||
saveDatabase();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ private:
|
||||
std::unique_ptr<Scraper> arcadeScraper;
|
||||
bool databaseLoaded = false;
|
||||
bool databaseDirty = false;
|
||||
u64 pauseUntil = 0;
|
||||
|
||||
std::vector<GameBoxart> toFetch;
|
||||
std::future<void> fetching;
|
||||
|
||||
@@ -76,13 +76,15 @@ json TheGamesDb::httpGet(const std::string& url)
|
||||
std::vector<u8> receivedData;
|
||||
int status = http::get(url, receivedData);
|
||||
bool success = http::success(status);
|
||||
if (status == 403)
|
||||
// hit rate-limit cap
|
||||
blackoutPeriod = getTimeMs() + 60 * 1000;
|
||||
else if (!success)
|
||||
if (status == 403 || status == 429) {
|
||||
// monthly allowance reached
|
||||
blackoutPeriod = getTimeMs() + 24 * 60 * 60 * 1000;
|
||||
throw std::runtime_error("TheGamesDB monthly allowance reached. Pausing for 24 hours");
|
||||
}
|
||||
if (!success)
|
||||
blackoutPeriod = getTimeMs() + 1000;
|
||||
if (!success || receivedData.empty())
|
||||
throw std::runtime_error("http error");
|
||||
throw std::runtime_error(strprintf("http error %d", status));
|
||||
|
||||
std::string content((const char *)&receivedData[0], receivedData.size());
|
||||
DEBUG_LOG(COMMON, "TheGameDb: received [%s]", content.c_str());
|
||||
|
||||
+5
-11
@@ -832,7 +832,8 @@ static void gameTooltip(const std::string& tip)
|
||||
static bool gameImageButton(ImguiTexture& texture, const std::string& tooltip, ImVec2 size, const std::string& gameName)
|
||||
{
|
||||
bool pressed = texture.button("##imagebutton", size, gameName);
|
||||
gameTooltip(tooltip);
|
||||
if (ImGui::IsRectVisible(size))
|
||||
gameTooltip(tooltip);
|
||||
|
||||
return pressed;
|
||||
}
|
||||
@@ -975,17 +976,10 @@ static void gui_display_content()
|
||||
if (counter % itemsPerLine != 0)
|
||||
ImGui::SameLine();
|
||||
counter++;
|
||||
if (ImGui::IsRectVisible(responsiveBoxVec2))
|
||||
{
|
||||
ImguiFileTexture tex(art.boxartPath);
|
||||
pressed = gameImageButton(tex, game.name, responsiveBoxVec2, gameName);
|
||||
}
|
||||
else {
|
||||
ImGui::Dummy(responsiveBoxVec2);
|
||||
}
|
||||
ImguiFileTexture tex(art.boxartPath);
|
||||
pressed = gameImageButton(tex, game.name, responsiveBoxVec2, gameName);
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
pressed = ImGui::Selectable(gameName.c_str());
|
||||
}
|
||||
if (pressed)
|
||||
|
||||
+12
-9
@@ -518,17 +518,20 @@ void ImguiTexture::draw(ImDrawList *drawList, const ImVec2& pos, const ImVec2& s
|
||||
bool ImguiTexture::button(const char* str_id, const ImVec2& image_size, const std::string& title,
|
||||
const ImVec4& bg_col, const ImVec4& tint_col)
|
||||
{
|
||||
ImTextureID id = getId();
|
||||
if (id == ImTextureID{})
|
||||
return ImGui::Button(title.c_str(), image_size);
|
||||
else
|
||||
const std::string label = title + str_id;
|
||||
if (ImGui::IsRectVisible(image_size))
|
||||
{
|
||||
const float ar = imguiDriver->getAspectRatio(id);
|
||||
const ImVec2 size = image_size - ImGui::GetStyle().FramePadding * 2;
|
||||
ImVec2 uv0, uv1;
|
||||
setUV(ar / size.x * size.y, uv0, uv1);
|
||||
return ImGui::ImageButton(str_id, id, size, uv0, uv1, bg_col, tint_col);
|
||||
ImTextureID id = getId();
|
||||
if (id != ImTextureID{})
|
||||
{
|
||||
const float ar = imguiDriver->getAspectRatio(id);
|
||||
const ImVec2 size = image_size - ImGui::GetStyle().FramePadding * 2;
|
||||
ImVec2 uv0, uv1;
|
||||
setUV(ar / size.x * size.y, uv0, uv1);
|
||||
return ImGui::ImageButton(label.c_str(), id, size, uv0, uv1, bg_col, tint_col);
|
||||
}
|
||||
}
|
||||
return ImGui::Button(label.c_str(), image_size);
|
||||
}
|
||||
|
||||
static u8 *loadImage(const std::string& path, int& width, int& height)
|
||||
|
||||
@@ -185,6 +185,8 @@ void EGLGraphicsContext::term()
|
||||
|
||||
if (display != EGL_NO_DISPLAY)
|
||||
{
|
||||
glFlush();
|
||||
glFinish();
|
||||
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
|
||||
if (context != EGL_NO_CONTEXT)
|
||||
|
||||
@@ -20,7 +20,7 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "com.flycast.emulator"
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
targetSdk = 36
|
||||
versionCode = 8
|
||||
versionName = gitVersionName()
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
+13
-14
@@ -133,20 +133,15 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat.
|
||||
Log.i("flycast", "Initializing input devices");
|
||||
InputDeviceManager.getInstance().startListening(getApplicationContext());
|
||||
register(this);
|
||||
|
||||
audioBackend = new AudioBackend();
|
||||
|
||||
onConfigurationChanged(getResources().getConfiguration());
|
||||
|
||||
// Ignore the Back button on Android 13+.
|
||||
// This is apparently required on Android 16 since returning true from onKeyDown() is ignored.
|
||||
/*
|
||||
// Back button handling on Android 13+
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, () -> {
|
||||
// Ignore the Back button. It is handled in onKeyDown()
|
||||
onBackKey();
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
// When viewing a resource, pass its URI to the native code for opening
|
||||
Intent intent = getIntent();
|
||||
@@ -166,7 +161,7 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat.
|
||||
}
|
||||
|
||||
// Called from native code
|
||||
private void showAlertDialog(String message)
|
||||
public void showAlertDialog(String message)
|
||||
{
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
@@ -354,16 +349,20 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat.
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
private void onBackKey() {
|
||||
if (JNIdc.guiIsContentBrowser()) {
|
||||
finish();
|
||||
}
|
||||
else {
|
||||
showMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (event.getRepeatCount() == 0) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
if (JNIdc.guiIsContentBrowser()) {
|
||||
finish();
|
||||
}
|
||||
else {
|
||||
showMenu();
|
||||
}
|
||||
onBackKey();
|
||||
return true;
|
||||
}
|
||||
InputDeviceManager deviceManager = InputDeviceManager.getInstance();
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ public final class InputDeviceManager implements InputManager.InputDeviceListene
|
||||
|
||||
public void startListening(Context applicationContext)
|
||||
{
|
||||
knownDevices.clear();
|
||||
maple_port = 0;
|
||||
hasTouchscreen = applicationContext.getPackageManager().hasSystemFeature("android.hardware.touchscreen");
|
||||
if (hasTouchscreen)
|
||||
|
||||
@@ -62,7 +62,7 @@ void SwappyGLGC::activate(bool enable)
|
||||
if (this->enabled == enable)
|
||||
return;
|
||||
this->enabled = enable;
|
||||
if (enable)
|
||||
if (enable && g_activity != nullptr)
|
||||
{
|
||||
SwappyGL_init(jni::env(), g_activity);
|
||||
swappyAvailable = SwappyGL_isEnabled();
|
||||
@@ -74,7 +74,9 @@ void SwappyGLGC::activate(bool enable)
|
||||
SwappyGL_setAutoPipelineMode(false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
SwappyGL_setWindow(nullptr);
|
||||
SwappyGL_destroy();
|
||||
swappyAvailable = false;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ static void activate(bool enable)
|
||||
{
|
||||
if (settings.input.fastForwardMode || !config::VSync || !googleTimingSupported)
|
||||
return;
|
||||
if (g_activity == nullptr)
|
||||
return;
|
||||
|
||||
// Fake call to make swappy use google timing for this device
|
||||
VkExtensionProperties prop { { VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME } };
|
||||
@@ -83,9 +85,9 @@ static void activate(bool enable)
|
||||
SwappyVk_setAutoPipelineMode(false);
|
||||
SwappyVk_setSwapIntervalNS((VkDevice)device, (VkSwapchainKHR)swapchain, SWAPPY_SWAP_60FPS);
|
||||
}
|
||||
else {
|
||||
if (swapchain)
|
||||
SwappyVk_destroySwapchain((VkDevice)device, (VkSwapchainKHR)swapchain);
|
||||
else if (swapchain) {
|
||||
SwappyVk_setWindow((VkDevice)device, (VkSwapchainKHR)swapchain, nullptr);
|
||||
SwappyVk_destroySwapchain((VkDevice)device, (VkSwapchainKHR)swapchain);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <unistd.h>
|
||||
#include "ui/gui.h"
|
||||
#include "oslib/oslib.h"
|
||||
#include "sdl/sdl.h"
|
||||
|
||||
#ifdef USE_BREAKPAD
|
||||
#include "client/mac/handler/exception_handler.h"
|
||||
@@ -357,31 +358,13 @@ static bool dumpCallback(const char *dump_dir, const char *minidump_id, void *co
|
||||
return succeeded;
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
* Catch document open requests...this lets us notice files when the app
|
||||
* was launched by double-clicking a document, or when a document was
|
||||
* dragged/dropped on the app's icon. You need to have a
|
||||
* CFBundleDocumentsType section in your Info.plist to get this message,
|
||||
* apparently.
|
||||
*
|
||||
* Files are added to gArgv, so to the app, they'll look like command line
|
||||
* arguments. Previously, apps launched from the finder had nothing but
|
||||
* an argv[0].
|
||||
*
|
||||
* This message may be received multiple times to open several docs on launch.
|
||||
*
|
||||
* This message is ignored once the app's mainline has been called.
|
||||
*/
|
||||
// Queue document opens until the main loop can safely load the game.
|
||||
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^(){
|
||||
gui_start_game([filename cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
});
|
||||
|
||||
return TRUE;
|
||||
// AppKit may deliver a document before SDL initializes the video subsystem.
|
||||
return sdl_queue_open_file([filename UTF8String]) ? YES : NO;
|
||||
}
|
||||
|
||||
|
||||
/* Called when the internal event loop has just started running */
|
||||
- (void) applicationDidFinishLaunching: (NSNotification *) note
|
||||
{
|
||||
|
||||
@@ -5,6 +5,12 @@ set -euo pipefail
|
||||
script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$script_dir/../../"
|
||||
|
||||
MOLTENVK_TAG="v1.4.2-pre.973bb08"
|
||||
MOLTENVK_ROOT="$PWD/build/deps/MoltenVK-${MOLTENVK_TAG}"
|
||||
|
||||
moltenvk_root=""
|
||||
moltenvk_dylib=""
|
||||
|
||||
install_homebrew() {
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
if [ -x /opt/homebrew/bin/brew ]; then
|
||||
@@ -18,6 +24,19 @@ install_homebrew() {
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_homebrew() {
|
||||
if command -v brew >/dev/null 2>&1; then
|
||||
return
|
||||
fi
|
||||
|
||||
read -p $'\033[1mHomebrew is required to install missing build tools.\033[0m Type \033[1;32my\033[0m to install it now: ' b
|
||||
if [ "$b" = "y" ]; then
|
||||
install_homebrew
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
init_submodules() {
|
||||
if [ -d .git ]; then
|
||||
if git rev-parse --verify HEAD >/dev/null 2>&1; then
|
||||
@@ -36,42 +55,50 @@ init_submodules() {
|
||||
fi
|
||||
}
|
||||
|
||||
install_vulkan_sdk() {
|
||||
local installer=""
|
||||
local install_root=""
|
||||
find_molten_vk() {
|
||||
local candidate=""
|
||||
|
||||
echo 'Downloading the latest Vulkan SDK from LunarG'
|
||||
curl -fL "https://sdk.lunarg.com/sdk/download/latest/mac/vulkan_sdk.zip?Human=true" -o vulkan_sdk.zip
|
||||
if [ ! -d "$MOLTENVK_ROOT" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo 'Extracting the Vulkan SDK installer'
|
||||
unzip -q vulkan_sdk.zip
|
||||
while IFS= read -r candidate; do
|
||||
case "$candidate" in
|
||||
*/macOS/*|*/macos-*) ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
|
||||
installer="$(find . -type f -path './vulkansdk-macOS-*.app/Contents/MacOS/*' -print | head -n 1)"
|
||||
install_root="$HOME/VulkanSDK"
|
||||
moltenvk_root="$MOLTENVK_ROOT"
|
||||
moltenvk_dylib="$candidate"
|
||||
return 0
|
||||
done < <(/usr/bin/find "$MOLTENVK_ROOT" -type f -name libMoltenVK.dylib)
|
||||
|
||||
echo "Installing Vulkan SDK into $install_root"
|
||||
"$installer" --root "$install_root" --accept-licenses --default-answer --confirm-command install copy_only=1
|
||||
echo >> ~/.zprofile
|
||||
echo "export VULKAN_SDK=$HOME/VulkanSDK/macOS" >> ~/.zprofile
|
||||
echo "VULKAN_SDK is now set in .zprofile"
|
||||
export VULKAN_SDK="$HOME/VulkanSDK/macOS"
|
||||
|
||||
rm -f vulkan_sdk.zip
|
||||
rm -rf ./vulkansdk-macOS-*.app
|
||||
return 1
|
||||
}
|
||||
|
||||
if ! command -v brew >/dev/null 2>&1; then
|
||||
read -p $'\033[1mHomebrew is required.\033[0m Type \033[1;32my\033[0m to install it now: ' b
|
||||
if [ "$b" = "y" ]; then
|
||||
install_homebrew
|
||||
else
|
||||
download_molten_vk() {
|
||||
local extract_tmp="$MOLTENVK_ROOT.tmp"
|
||||
local archive="$PWD/build/deps/MoltenVK-macos-${MOLTENVK_TAG}.tar"
|
||||
local url="https://github.com/vkedwardli/MoltenVK/releases/download/${MOLTENVK_TAG}/MoltenVK-macos.tar"
|
||||
|
||||
mkdir -p "$(dirname "$archive")"
|
||||
curl -fL "$url" -o "$archive"
|
||||
rm -rf "$extract_tmp"
|
||||
mkdir -p "$extract_tmp"
|
||||
tar -xf "$archive" -C "$extract_tmp"
|
||||
rm -rf "$MOLTENVK_ROOT"
|
||||
mv "$extract_tmp" "$MOLTENVK_ROOT"
|
||||
|
||||
if ! find_molten_vk; then
|
||||
echo "MoltenVK ${MOLTENVK_TAG} was downloaded, but macOS libMoltenVK.dylib was not found." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
if ! command -v cmake >/dev/null 2>&1; then
|
||||
read -p $'\033[1mCMake is required.\033[0m Type \033[1;32my\033[0m to install it with Homebrew: ' c
|
||||
if [ "$c" = "y" ]; then
|
||||
ensure_homebrew
|
||||
brew install cmake
|
||||
else
|
||||
exit 1
|
||||
@@ -130,6 +157,7 @@ if [ -z "$xcode_app" ]; then
|
||||
read -p $'Or type \033[1;32my\033[0m to install Xcode '"$xcode_version"$' with Homebrew (you will be prompted to sign in with your Apple account): ' c
|
||||
fi
|
||||
if [ "$c" = "y" ]; then
|
||||
ensure_homebrew
|
||||
brew install xcodes
|
||||
if [ "$xcode_version" = "latest" ]; then
|
||||
xcodes install --latest --no-superuser
|
||||
@@ -157,22 +185,6 @@ if [ ! -f core/deps/glslang/CMakeLists.txt ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "${VULKAN_SDK:-}" && -d "${VULKAN_SDK}" ]]; then
|
||||
echo "Using Vulkan SDK from $VULKAN_SDK"
|
||||
elif [ -f "$HOME/VulkanSDK/setup-env.sh" ]; then
|
||||
export VULKAN_SDK="$HOME/VulkanSDK/macOS"
|
||||
echo "Using Vulkan SDK from $VULKAN_SDK"
|
||||
else
|
||||
echo $'\033[1mVulkan SDK is required.\033[0m VULKAN_SDK is not configured.'
|
||||
read -p $'Type \033[1;32my\033[0m to install the latest Vulkan SDK: ' v
|
||||
if [ "$v" = "y" ]; then
|
||||
install_vulkan_sdk
|
||||
echo "Using Vulkan SDK from $VULKAN_SDK"
|
||||
else
|
||||
echo 'Vulkan will be disabled' >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "1) macOS ($(uname -m))"
|
||||
echo "2) iOS"
|
||||
read -p "Choose your target platform: " x
|
||||
@@ -187,11 +199,26 @@ if [ "${x:-1}" = "2" ]; then
|
||||
echo 'Building iOS xcodeproj for debugging'
|
||||
echo 'Remove CODE_SIGNING_ALLOWED=NO in Build Settings if you are using your Apple Developer Certificate for signing'
|
||||
else
|
||||
option="-DCMAKE_OSX_ARCHITECTURES=$(uname -m) -DUSE_VULKAN="
|
||||
if [[ -n "${VULKAN_SDK:-}" ]]; then
|
||||
option+="ON"
|
||||
if find_molten_vk; then
|
||||
echo "Using MoltenVK from $moltenvk_root"
|
||||
use_vulkan="ON"
|
||||
else
|
||||
option+="OFF"
|
||||
echo $'\033[1mMoltenVK is required for Vulkan.\033[0m'
|
||||
echo "Download URL: https://github.com/vkedwardli/MoltenVK/releases/download/${MOLTENVK_TAG}/MoltenVK-macos.tar"
|
||||
read -p $'Type \033[1;32my\033[0m to download MoltenVK from the configured release URL: ' v
|
||||
if [ "$v" = "y" ]; then
|
||||
download_molten_vk
|
||||
echo "Using MoltenVK from $moltenvk_root"
|
||||
use_vulkan="ON"
|
||||
else
|
||||
echo 'Vulkan will be disabled' >&2
|
||||
use_vulkan="OFF"
|
||||
fi
|
||||
fi
|
||||
|
||||
option="-DCMAKE_OSX_ARCHITECTURES=$(uname -m) -DUSE_VULKAN=$use_vulkan"
|
||||
if [ "$use_vulkan" = "ON" ]; then
|
||||
option+=" -DMOLTENVK_DYLIB=$moltenvk_dylib"
|
||||
fi
|
||||
lldbinitfolder="emulator-osx"
|
||||
echo 'Building macOS xcodeproj for debugging'
|
||||
|
||||
+24
-23
@@ -55,6 +55,8 @@
|
||||
#include "hw/sh4/sh4_sched.h"
|
||||
#include "keyboard_map.h"
|
||||
#include "hw/maple/maple_cfg.h"
|
||||
#include "hw/naomi/systemsp.h"
|
||||
#include "hw/pvr/elan.h"
|
||||
#include "hw/maple/maple_if.h"
|
||||
#include "hw/pvr/pvr_regs.h"
|
||||
#include "hw/pvr/Renderer_if.h"
|
||||
@@ -441,6 +443,8 @@ void retro_deinit()
|
||||
emu.term();
|
||||
#endif
|
||||
libretro_supports_bitmasks = false;
|
||||
led_state_cb = NULL;
|
||||
memset(retro_led_state, 0, sizeof(retro_led_state));
|
||||
categoriesSupported = false;
|
||||
platformIsDreamcast = true;
|
||||
platformIsArcade = false;
|
||||
@@ -760,16 +764,19 @@ bool setAVInfo(retro_system_av_info& avinfo)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool retro_refresh_av_info(double fps)
|
||||
void retro_setfps(double fps)
|
||||
{
|
||||
if (fps > 0 && fps < 100)
|
||||
fps_spg = fps;
|
||||
}
|
||||
|
||||
static bool retro_refresh_av_info()
|
||||
{
|
||||
retro_system_av_info avinfo;
|
||||
|
||||
if (first_run || game_data.empty())
|
||||
return false;
|
||||
|
||||
if (fps > 0 && fps < 100)
|
||||
fps_spg = fps;
|
||||
|
||||
if (setAVInfo(avinfo))
|
||||
{
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &avinfo);
|
||||
@@ -801,7 +808,7 @@ void retro_resize_renderer(int w, int h, float aspectRatio)
|
||||
else
|
||||
{
|
||||
// Check if timing change is needed instead
|
||||
if (retro_refresh_av_info(0))
|
||||
if (retro_refresh_av_info())
|
||||
return;
|
||||
|
||||
retro_game_geometry geometry;
|
||||
@@ -2464,22 +2471,16 @@ size_t retro_get_memory_size(unsigned type)
|
||||
size_t retro_serialize_size()
|
||||
{
|
||||
DEBUG_LOG(SAVESTATE, "retro_serialize_size");
|
||||
std::lock_guard<std::mutex> lock(mtx_serialization);
|
||||
|
||||
if (!first_run)
|
||||
try {
|
||||
emu.stop();
|
||||
} catch (const FlycastException& e) {
|
||||
ERROR_LOG(COMMON, "%s", e.what());
|
||||
return 0;
|
||||
}
|
||||
|
||||
Serializer ser;
|
||||
dc_serialize(ser);
|
||||
if (!first_run)
|
||||
emu.start();
|
||||
|
||||
return ser.size();
|
||||
// A frontend may size its buffer once, but a state grows and shrinks with the
|
||||
// devices plugged in and the frames being drawn. Report the memory regions plus
|
||||
// the allowance GGPO makes for everything else, Naomi's for System SP too.
|
||||
size_t size = settings.platform.ram_size + settings.platform.vram_size
|
||||
+ settings.platform.aram_size;
|
||||
if (settings.platform.isNaomi2())
|
||||
size += elan::ERAM_SIZE;
|
||||
if (settings.platform.isSystemSP() && systemsp::SystemSpCart::Instance != nullptr)
|
||||
size += systemsp::SystemSpCart::Instance->getSerializedMediaSize();
|
||||
return size + (settings.platform.isNaomi() || settings.platform.isSystemSP() ? 20_MB : 10_MB);
|
||||
}
|
||||
|
||||
bool retro_serialize(void *data, size_t size)
|
||||
@@ -2888,7 +2889,7 @@ static void updateLightgunCoordinates(u32 port)
|
||||
lightgun_params[port].y = mo_y_abs[port];
|
||||
}
|
||||
|
||||
void updateLightgunCoordinatesFromAnalogStick(int port)
|
||||
static void updateLightgunCoordinatesFromAnalogStick(int port)
|
||||
{
|
||||
int x = input_cb(port, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X);
|
||||
mo_x_abs[port] = 320 + x * 320 / 32767;
|
||||
@@ -3874,7 +3875,7 @@ static bool read_m3u(const char *file)
|
||||
log_cb(RETRO_LOG_ERROR, "Could not read file\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned disk_index = 0;
|
||||
while (filestream_gets(f, line, sizeof(line)) && disk_index <= disk_paths.size())
|
||||
{
|
||||
if (line[0] == '#')
|
||||
|
||||
@@ -644,6 +644,20 @@ struct retro_core_option_v2_definition option_defs_us[] = {
|
||||
},
|
||||
"enabled",
|
||||
},
|
||||
{
|
||||
CORE_OPTION_NAME "_vblank_pacing",
|
||||
"Vblank Pacing",
|
||||
NULL,
|
||||
"Each frame the frontend runs emulates exactly one vblank, whether or not the game drew. For frontends that pace the core by its frame rate rather than by blocking on audio; without it, loading screens that draw nothing run several vblanks per frame and their sound arrives in bursts. Needs Threaded Rendering disabled.",
|
||||
NULL,
|
||||
"performance",
|
||||
{
|
||||
{ "disabled", NULL },
|
||||
{ "enabled", NULL },
|
||||
{ NULL, NULL },
|
||||
},
|
||||
"disabled",
|
||||
},
|
||||
{
|
||||
CORE_OPTION_NAME "_auto_skip_frame",
|
||||
"Auto Skip Frame",
|
||||
@@ -837,6 +851,20 @@ struct retro_core_option_v2_definition option_defs_us[] = {
|
||||
},
|
||||
"disabled",
|
||||
},
|
||||
{
|
||||
CORE_OPTION_NAME "_dump_unique_textures",
|
||||
"Discard Video and Animated Textures",
|
||||
NULL,
|
||||
"Skip dumping video (YUV) and already updated textures.",
|
||||
NULL,
|
||||
"hacks",
|
||||
{
|
||||
{ "disabled", NULL },
|
||||
{ "enabled", NULL },
|
||||
{ NULL, NULL },
|
||||
},
|
||||
"disabled",
|
||||
},
|
||||
{
|
||||
CORE_OPTION_NAME "_dump_replaced_textures",
|
||||
"Dump Replaced Textures",
|
||||
|
||||
+3569
-2193
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,4 @@
|
||||
{
|
||||
global: retro_*;
|
||||
local: *;
|
||||
};
|
||||
@@ -89,6 +89,7 @@ Option<bool> IntegerScale("");
|
||||
Option<bool> LinearInterpolation("", true);
|
||||
Option<bool> VSync("", true);
|
||||
Option<bool> ThreadedRendering(CORE_OPTION_NAME "_threaded_rendering", true);
|
||||
Option<bool> VblankPacing(CORE_OPTION_NAME "_vblank_pacing");
|
||||
Option<int> AnisotropicFiltering(CORE_OPTION_NAME "_anisotropic_filtering");
|
||||
Option<int> TextureFiltering(CORE_OPTION_NAME "_texture_filtering");
|
||||
Option<bool> PowerVR2Filter(CORE_OPTION_NAME "_pvr2_filtering");
|
||||
|
||||
@@ -59,7 +59,7 @@ private:
|
||||
std::vector<BaseOption *> options;
|
||||
std::string gameId;
|
||||
retro_environment_t retroEnv = nullptr;
|
||||
const retro_core_option_v2_definition *optionDefs;
|
||||
const retro_core_option_v2_definition *optionDefs = nullptr;
|
||||
|
||||
template<typename T, bool>
|
||||
friend class Option;
|
||||
|
||||
@@ -119,11 +119,11 @@ std::string findNaomiBios(const std::string& name)
|
||||
{
|
||||
std::string basepath(game_dir_no_slash);
|
||||
basepath += PATH_DEFAULT_SLASH() + name;
|
||||
if (!file_exists(basepath))
|
||||
if (!path_is_valid(basepath.c_str()))
|
||||
{
|
||||
// File not found in system dir, try game dir instead
|
||||
basepath = g_roms_dir + name;
|
||||
if (!file_exists(basepath))
|
||||
if (!path_is_valid(basepath.c_str()))
|
||||
return "";
|
||||
}
|
||||
return basepath;
|
||||
|
||||
@@ -15,22 +15,26 @@ target_sources(${PROJECT_NAME} PRIVATE
|
||||
src/serialize_test.cpp
|
||||
src/AicaArmTest.cpp
|
||||
src/Sh4InterpreterTest.cpp
|
||||
src/SorterTest.cpp
|
||||
src/MmuTest.cpp
|
||||
src/HttpTest.cpp
|
||||
src/IniFileTest.cpp
|
||||
src/hw/modem/v42Test.cpp
|
||||
src/hw/modem/v42bisTest.cpp
|
||||
src/hw/sh4/modules/TimerTest.cpp
|
||||
src/imgread/ChdTest.cpp
|
||||
src/imgread/CueTest.cpp
|
||||
src/imgread/GdiTest.cpp
|
||||
src/input/ButtonComboTest.cpp
|
||||
src/input/GamepadInputHandlingTest.cpp
|
||||
src/input/MultiBindMappingTest.cpp
|
||||
src/input/MouseTest.cpp
|
||||
src/input/InputDefTest.cpp
|
||||
src/input/InputMappingConfigFileTest.cpp
|
||||
src/input/InputSetTest.cpp
|
||||
src/input/SDLControllerMappingTest.cpp
|
||||
src/oslib/I18nTest.cpp
|
||||
src/util/Md5SumTest.cpp
|
||||
src/util/PeriodicThreadTest.cpp
|
||||
src/util/TsQueueTest.cpp
|
||||
src/util/WorkerThreadTest.cpp)
|
||||
Binary file not shown.
@@ -0,0 +1,9 @@
|
||||
FILE "audiocd01.bin" BINARY
|
||||
TRACK 01 AUDIO
|
||||
INDEX 01 00:00:00
|
||||
FILE "audiocd02.bin" BINARY
|
||||
TRACK 02 AUDIO
|
||||
INDEX 01 00:00:00
|
||||
FILE "audiocd03.bin" BINARY
|
||||
TRACK 03 AUDIO
|
||||
INDEX 01 00:00:00
|
||||
Executable
+110
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Regenerates the .cue and .chd test images sitting in this directory.
|
||||
|
||||
./make_fixtures.py (needs chdman from mame-tools)
|
||||
|
||||
Each sector of the generated .bin files starts with the 8 bytes
|
||||
|
||||
{ track, 0x11, frame & 0xff, 0x22, frame >> 8, 0x33, 0x44, 0x55 }
|
||||
|
||||
and is zero filled afterwards, so a sector identifies the track and the frame
|
||||
it belongs to. Swapping the marker byte pairs gives a different value, which is
|
||||
what lets ChdTest catch a missing or spurious byte swap of the audio tracks.
|
||||
|
||||
The .bin files are deleted once chdman has read them: they are 8 MB of filler
|
||||
and everything needed to rebuild them is here.
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# name -> (cue header, [(track number, type, pregap frames, data frames)])
|
||||
# The pregap frames are stored in the track's own .bin, ie. the track has an
|
||||
# INDEX 00, unless the layout says otherwise below.
|
||||
FIXTURES = {
|
||||
# MIL-CD laid out as Redump dumps them: the audio tracks of the first
|
||||
# session carry their pregap, the data track of the second one doesn't.
|
||||
"milcd": [
|
||||
("REM SESSION 01", 1, "AUDIO", 0, 300),
|
||||
(None, 2, "AUDIO", 183, 300),
|
||||
(None, 3, "AUDIO", 210, 200),
|
||||
("REM SESSION 02", 4, "MODE2/2352", 0, 450),
|
||||
],
|
||||
# Same disc, but the pregap of the second session's track is in the image
|
||||
"milcd_pregap": [
|
||||
("REM SESSION 01", 1, "AUDIO", 0, 300),
|
||||
("REM SESSION 02", 2, "MODE2/2352", 150, 300),
|
||||
],
|
||||
# Same disc again, with that pregap declared but not stored
|
||||
"milcd_nogap": [
|
||||
("REM SESSION 01", 1, "AUDIO", 0, 300),
|
||||
("REM SESSION 02", 2, "MODE2/2352", -150, 300),
|
||||
],
|
||||
# Single session audio CD: no session gap must be inserted
|
||||
"audiocd": [
|
||||
(None, 1, "AUDIO", 0, 150),
|
||||
(None, 2, "AUDIO", 0, 150),
|
||||
(None, 3, "AUDIO", 0, 150),
|
||||
],
|
||||
}
|
||||
|
||||
SECTOR = 2352
|
||||
|
||||
|
||||
def msf(frames):
|
||||
return "%02d:%02d:%02d" % (frames // (75 * 60), frames // 75 % 60, frames % 75)
|
||||
|
||||
|
||||
def write_bin(path, track, frames):
|
||||
with open(path, "wb") as f:
|
||||
for i in range(frames):
|
||||
sector = bytearray(SECTOR)
|
||||
sector[0:8] = bytes([track, 0x11, i & 0xFF, 0x22, i >> 8, 0x33, 0x44, 0x55])
|
||||
f.write(sector)
|
||||
|
||||
|
||||
def build(name, tracks, chdman):
|
||||
bins = []
|
||||
cue = []
|
||||
for header, track, track_type, pregap, frames in tracks:
|
||||
if header:
|
||||
cue.append(header)
|
||||
# A negative pregap is declared with a PREGAP command instead of an
|
||||
# INDEX 00, ie. those sectors take up space on the disc but aren't stored
|
||||
stored_pregap = pregap if pregap > 0 else 0
|
||||
binname = "%s%02d.bin" % (name, track)
|
||||
write_bin(os.path.join(os.path.dirname(__file__) or ".", binname),
|
||||
track, stored_pregap + frames)
|
||||
bins.append(binname)
|
||||
cue.append('FILE "%s" BINARY' % binname)
|
||||
cue.append(" TRACK %02d %s" % (track, track_type))
|
||||
if pregap < 0:
|
||||
cue.append(" PREGAP %s" % msf(-pregap))
|
||||
if stored_pregap:
|
||||
cue.append(" INDEX 00 00:00:00")
|
||||
cue.append(" INDEX 01 %s" % msf(stored_pregap))
|
||||
|
||||
here = os.path.dirname(__file__) or "."
|
||||
cuepath = os.path.join(here, name + ".cue")
|
||||
with open(cuepath, "w") as f:
|
||||
f.write("\n".join(cue) + "\n")
|
||||
|
||||
chdpath = os.path.join(here, name + ".chd")
|
||||
subprocess.run([chdman, "createcd", "-i", cuepath, "-o", chdpath, "-f"],
|
||||
check=True, stdout=subprocess.DEVNULL)
|
||||
for binname in bins:
|
||||
os.remove(os.path.join(here, binname))
|
||||
print("%s: %d tracks, %d bytes" % (name + ".chd", len(tracks), os.path.getsize(chdpath)))
|
||||
|
||||
|
||||
def main():
|
||||
chdman = shutil.which("chdman")
|
||||
if chdman is None:
|
||||
sys.exit("chdman not found (apt install mame-tools)")
|
||||
for name, tracks in FIXTURES.items():
|
||||
build(name, tracks, chdman)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
@@ -0,0 +1,16 @@
|
||||
REM SESSION 01
|
||||
FILE "milcd01.bin" BINARY
|
||||
TRACK 01 AUDIO
|
||||
INDEX 01 00:00:00
|
||||
FILE "milcd02.bin" BINARY
|
||||
TRACK 02 AUDIO
|
||||
INDEX 00 00:00:00
|
||||
INDEX 01 00:02:33
|
||||
FILE "milcd03.bin" BINARY
|
||||
TRACK 03 AUDIO
|
||||
INDEX 00 00:00:00
|
||||
INDEX 01 00:02:60
|
||||
REM SESSION 02
|
||||
FILE "milcd04.bin" BINARY
|
||||
TRACK 04 MODE2/2352
|
||||
INDEX 01 00:00:00
|
||||
Binary file not shown.
@@ -0,0 +1,9 @@
|
||||
REM SESSION 01
|
||||
FILE "milcd_nogap01.bin" BINARY
|
||||
TRACK 01 AUDIO
|
||||
INDEX 01 00:00:00
|
||||
REM SESSION 02
|
||||
FILE "milcd_nogap02.bin" BINARY
|
||||
TRACK 02 MODE2/2352
|
||||
PREGAP 00:02:00
|
||||
INDEX 01 00:00:00
|
||||
Binary file not shown.
Loaded 100 of 106 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user