mirror of
https://github.com/flyinghead/flycast.git
synced 2026-11-08 08:06:48 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8855e13540 |
No files matched your search
@@ -1,2 +0,0 @@
|
||||
buy_me_a_coffee: hhwtpernwq
|
||||
custom: ['https://www.paypal.com/paypalme/FlycastEmu']
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
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.**
|
||||
|
||||
Platform / OS / Hardware:
|
||||
|
||||
Flycast version:
|
||||
|
||||
Hardware:
|
||||
|
||||
**_Description of the Issue_**
|
||||
|
||||
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
|
||||
|
||||
**_Logs Gathered_**
|
||||
|
||||
```
|
||||
|
||||
Please paste the contents of the log / logs here
|
||||
You may leave this field blank if you have none.
|
||||
|
||||
```
|
||||
|
||||
|
||||
**_Screenshots_**
|
||||
|
||||
(Replace this line with any issue screenshots)
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
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 [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
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.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -1,101 +0,0 @@
|
||||
name: Android
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'l10n_**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
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 libcurl4-openssl-dev
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
cache: 'gradle'
|
||||
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
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")/' shell/android-studio/flycast/build.gradle.kts
|
||||
if: github.repository != 'flyinghead/flycast' || github.event_name != 'push'
|
||||
|
||||
- name: Bump version code
|
||||
run: sed -i 's/versionCode = 8/versionCode = ${{ github.run_number }}/' shell/android-studio/flycast/build.gradle.kts
|
||||
|
||||
- name: Gradle
|
||||
working-directory: shell/android-studio
|
||||
run: ./gradlew assembleRelease bundleRelease --parallel
|
||||
env:
|
||||
SENTRY_UPLOAD_URL: ${{ secrets.SENTRY_UPLOAD_URL }}
|
||||
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: flycast-release.apk
|
||||
path: shell/android-studio/flycast/build/outputs/apk/release/flycast-release.apk
|
||||
|
||||
- name: Build dump_syms
|
||||
working-directory: core/deps/breakpad
|
||||
run: |
|
||||
./configure --prefix=$PWD
|
||||
make -j2
|
||||
make install
|
||||
|
||||
- name: Extract symbols
|
||||
run: |
|
||||
DUMP_SYMS=./core/deps/breakpad/bin/dump_syms
|
||||
SO_DIR=shell/android-studio/flycast/build/intermediates/merged_native_libs/release/mergeReleaseNativeLibs/out/lib
|
||||
for arch in arm64-v8a armeabi-v7a x86 x86_64 ; do
|
||||
$DUMP_SYMS $SO_DIR/$arch/libflycast.so > libflycast.so.sym
|
||||
BUILD_ID=`head -1 libflycast.so.sym | awk '{ print $4 }'`
|
||||
mkdir -p symbols/libflycast.so/$BUILD_ID
|
||||
mv libflycast.so.sym symbols/libflycast.so/$BUILD_ID
|
||||
done
|
||||
|
||||
- name: Setup Rclone
|
||||
uses: AnimMouse/setup-rclone@v1
|
||||
with:
|
||||
rclone_config: ${{ secrets.RCLONE_CONFIG }}
|
||||
|
||||
- name: Upload to S3
|
||||
run: |
|
||||
cp shell/android-studio/flycast/build/outputs/bundle/release/*.aab shell/android-studio/flycast/build/outputs/apk/release/
|
||||
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'
|
||||
|
||||
- uses: getsentry/action-setup-cli@v1
|
||||
|
||||
- name: Upload symbols to Sentry
|
||||
run: |
|
||||
VERSION=$(git describe --tags --always)
|
||||
sentry-cli releases new "$VERSION"
|
||||
sentry-cli releases set-commits "$VERSION" --auto
|
||||
sentry-cli debug-files upload symbols
|
||||
shell: bash
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_TOKEN }}
|
||||
SENTRY_ORG: flycast
|
||||
SENTRY_PROJECT: minidump
|
||||
if: ${{ env.SENTRY_AUTH_TOKEN != '' && github.event_name == 'push' }}
|
||||
@@ -1,60 +0,0 @@
|
||||
name: BSD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'l10n_**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
operating_system: [ freebsd, netbsd, openbsd ]
|
||||
architecture: [ arm64, x86-64 ]
|
||||
libretro: [ OFF, ON ]
|
||||
include:
|
||||
- operating_system: freebsd
|
||||
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: '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: '8.0'
|
||||
pkginstall: sudo pkg_add -u && sudo pkg_add ccache cmake git libao libcdio libusb1-1.0.30 libzip lua-5.4.8p2 miniupnpc ninja pulseaudio sdl2
|
||||
exclude:
|
||||
- architecture: arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
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
|
||||
with:
|
||||
operating_system: ${{ matrix.operating_system }}
|
||||
architecture: ${{ matrix.architecture }}
|
||||
version: ${{ matrix.version }}
|
||||
environment_variables: CCACHE_DIR
|
||||
|
||||
- name: Set up build environment
|
||||
run: ${{ matrix.pkginstall }}
|
||||
shell: cpa.sh {0}
|
||||
|
||||
- name: CMake
|
||||
run: |
|
||||
cmake -B build -DUSE_LIBCDIO=ON -DCMAKE_BUILD_TYPE=Release -G Ninja -DLIBRETRO=${{ matrix.libretro }}
|
||||
cmake --build build --config Release
|
||||
shell: cpa.sh {0}
|
||||
@@ -1,214 +0,0 @@
|
||||
name: Linux, macOS, Windows
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'l10n_**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.config.name }}
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.config.shell }}
|
||||
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
|
||||
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 -DMOLTENVK_DYLIB=$MOLTENVK_DYLIB, destDir: osx, buildType: RelWithDebInfo, packagePath: artifact/flycast-apple-darwin.zip }
|
||||
- { name: x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: linux, buildType: RelWithDebInfo }
|
||||
- { name: arm64-pc-linux-gnu, os: ubuntu-22.04-arm, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: linux, buildType: RelWithDebInfo }
|
||||
- { name: x86_64-pc-windows-msvc, os: windows-2022, shell: cmd, arch: x64, cmakeArgs: -G Ninja -DUSE_DISCORD=ON, buildType: Release }
|
||||
- { name: x86_64-w64-mingw32, os: windows-2022, shell: 'msys2 {0}', cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: win, buildType: RelWithDebInfo, packagePath: artifact/flycast-x86_64-w64-mingw32.zip }
|
||||
- { name: libretro-x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -G Ninja, buildType: Release }
|
||||
- { name: libretro-x86_64-w64-mingw32, os: windows-2022, shell: 'msys2 {0}', cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -G Ninja, buildType: Release }
|
||||
|
||||
steps:
|
||||
- name: Set up build environment (macOS)
|
||||
run: |
|
||||
# Unlink and re-link to prevent errors when github mac runner images install python outside of brew. See https://github.com/actions/setup-python/issues/577
|
||||
brew list -1 | grep python | while read formula; do brew unlink $formula; brew link --overwrite $formula; done
|
||||
brew update || :
|
||||
brew install libao ldid pulseaudio
|
||||
|
||||
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 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
|
||||
if: runner.os == 'Linux'
|
||||
|
||||
- name: Set up build environment (Windows, MinGW)
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: MINGW64
|
||||
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
|
||||
echo DXSDK_DIR=C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)>> %GITHUB_ENV%
|
||||
if: matrix.config.shell == 'cmd'
|
||||
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: ${{ matrix.config.arch }}
|
||||
if: matrix.config.shell == 'cmd'
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Compile universal dependencies (macOS)
|
||||
run: |
|
||||
for formula in ./shell/apple/libpng-flycast.rb; do
|
||||
libname=$(basename "$formula" .rb)
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 HOMEBREW_DEVELOPER=1 brew install --build-from-source --formula "$formula" || \
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 HOMEBREW_DEVELOPER=1 brew reinstall --build-from-source --formula "$formula"
|
||||
|
||||
prefix=$(brew --prefix $libname)
|
||||
# Find and verify the static library
|
||||
libfile=$(find "$prefix/lib" -name "*.a" | head -n1)
|
||||
lipo -info "$libfile"
|
||||
|
||||
# Export prefix for subsequent CMake steps
|
||||
if [ -z "$CMAKE_PREFIX_PATH" ]; then
|
||||
CMAKE_PREFIX_PATH="$prefix"
|
||||
else
|
||||
CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH:$prefix"
|
||||
fi
|
||||
done
|
||||
echo "CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH" >> $GITHUB_ENV
|
||||
# After the last brew download: brew's own curl links brew's zstd
|
||||
brew uninstall --ignore-dependencies zstd
|
||||
if: matrix.config.name == 'apple-darwin'
|
||||
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ccache-${{ matrix.config.name }}-${{ github.sha }}
|
||||
restore-keys: ccache-${{ matrix.config.name }}-
|
||||
if: runner.os != 'macOS'
|
||||
|
||||
- name: CMake
|
||||
run: |
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.config.buildType }} -DCMAKE_INSTALL_PREFIX=artifact ${{ matrix.config.cmakeArgs }} -DSENTRY_UPLOAD_URL=${{ env.SENTRY_UPLOAD_URL }}
|
||||
cmake --build build --config ${{ matrix.config.buildType }} --target install
|
||||
env:
|
||||
SENTRY_UPLOAD_URL: ${{ secrets.SENTRY_UPLOAD_URL }}
|
||||
|
||||
- name: Unit Tests
|
||||
run: |
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.config.buildType }} -DCMAKE_INSTALL_PREFIX=artifact -DENABLE_CTEST=ON ${{ matrix.config.cmakeArgs }}
|
||||
cmake --build build --config ${{ matrix.config.buildType }}
|
||||
./build/flycast
|
||||
if: matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu'
|
||||
|
||||
- name: Dump symbols
|
||||
run: |
|
||||
mkdir -p symbols
|
||||
core/deps/breakpad/bin/dump_syms artifact/bin/flycast.exe > symbols/flycast.sym 2>/dev/null
|
||||
strip artifact/bin/flycast.exe
|
||||
if: matrix.config.name == 'x86_64-w64-mingw32'
|
||||
|
||||
- name: Build breakpad (linux)
|
||||
working-directory: core/deps/breakpad
|
||||
run: |
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
if: matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu'
|
||||
|
||||
- name: Dump symbols and make AppImage (linux)
|
||||
run: |
|
||||
mkdir -p symbols
|
||||
dump_syms artifact/bin/flycast > symbols/flycast.sym 2>/dev/null
|
||||
strip artifact/bin/flycast
|
||||
shell/linux/make-appimage.sh . artifact/bin appimage
|
||||
ARCH=$(uname -m)
|
||||
mv flycast-${ARCH}.AppImage artifact/bin
|
||||
rm artifact/bin/flycast
|
||||
if: matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu'
|
||||
|
||||
- name: Package app (macos)
|
||||
run: |
|
||||
cd artifact/bin
|
||||
codesign --verify --deep --strict --all-architectures --verbose=4 Flycast.app
|
||||
zip -rm flycast.app.zip Flycast.app
|
||||
cp flycast.app.zip "../../${{ matrix.config.packagePath }}"
|
||||
if: matrix.config.name == 'apple-darwin'
|
||||
|
||||
- name: Package app (windows)
|
||||
run: |
|
||||
powershell Compress-Archive artifact/bin/flycast.exe artifact/bin/flycast.zip
|
||||
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:
|
||||
rclone_config: ${{ secrets.RCLONE_CONFIG }}
|
||||
|
||||
- name: Upload to S3
|
||||
run: rclone copy artifact/bin scaleway:flycast-builds/${{ matrix.config.destDir }}/${GITHUB_REF#refs/}-$GITHUB_SHA
|
||||
shell: bash
|
||||
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push' && matrix.config.destDir != ''
|
||||
|
||||
- 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 != '' }}
|
||||
|
||||
- name: Upload symbols to Sentry (Windows, macOS, Linux)
|
||||
run: |
|
||||
VERSION=$(git describe --tags --always)
|
||||
sentry-cli releases new "$VERSION"
|
||||
sentry-cli releases set-commits "$VERSION" --auto
|
||||
sentry-cli debug-files upload symbols
|
||||
shell: bash
|
||||
env:
|
||||
SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }}
|
||||
if: ${{ env.SENTRY_TOKEN != '' && (matrix.config.name == 'x86_64-w64-mingw32' || matrix.config.name == 'apple-darwin' || matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu') && github.event_name == 'push' }}
|
||||
@@ -1,36 +0,0 @@
|
||||
# Prepare source texts & upload them to Crowdin
|
||||
|
||||
name: Crowdin Source Texts Upload
|
||||
|
||||
# on change to the English texts
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'shell/libretro/libretro_core_options.h'
|
||||
|
||||
jobs:
|
||||
upload_source_file:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup Java JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: 18
|
||||
distribution: zulu
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Upload Source
|
||||
shell: bash
|
||||
env:
|
||||
CROWDIN_API_KEY: ${{ secrets.CROWDIN_API_KEY }}
|
||||
run: |
|
||||
python3 intl/upload_workflow.py $CROWDIN_API_KEY "flyinghead-flycast" "shell/libretro/libretro_core_options.h"
|
||||
@@ -1,49 +0,0 @@
|
||||
# Download translations form Crowdin & Recreate libretro_core_options_intl.h
|
||||
|
||||
name: Crowdin Translation Integration
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# please choose a random time & weekday to avoid all repos synching at the same time
|
||||
- cron: '15 22 * * 5' # Fridays at 10:15 PM, UTC
|
||||
|
||||
jobs:
|
||||
create_intl_file:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup Java JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: 18
|
||||
distribution: zulu
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token.
|
||||
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository.
|
||||
|
||||
- name: Create intl file
|
||||
shell: bash
|
||||
env:
|
||||
CROWDIN_API_KEY: ${{ secrets.CROWDIN_API_KEY }}
|
||||
run: |
|
||||
python3 intl/download_workflow.py $CROWDIN_API_KEY "flyinghead-flycast" "shell/libretro/libretro_core_options_intl.h"
|
||||
|
||||
- name: Commit files
|
||||
run: |
|
||||
git config --local user.email "[email protected]"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git add intl/*_workflow.py "shell/libretro/libretro_core_options_intl.h"
|
||||
git commit -m "Fetch translations & Recreate libretro_core_options_intl.h"
|
||||
|
||||
- name: GitHub Push
|
||||
uses: ad-m/github-push-action@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: ${{ github.ref }}
|
||||
@@ -1,67 +0,0 @@
|
||||
name: Nintendo Switch
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'l10n_**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Switch ${{ matrix.config.name }}
|
||||
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 }
|
||||
|
||||
steps:
|
||||
- name: Set up build environment
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install ccache gh
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Mark git repository as safe
|
||||
run: git config --global --add safe.directory $PWD
|
||||
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ccache-switch-${{ matrix.config.name }}-${{ github.sha }}
|
||||
restore-keys: ccache-switch-${{ matrix.config.name }}-
|
||||
|
||||
- name: CMake
|
||||
run: |
|
||||
$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@v7
|
||||
with:
|
||||
name: flycast-switch-${{ matrix.config.name }}
|
||||
path: |
|
||||
artifact/bin
|
||||
artifact/lib/libretro
|
||||
|
||||
- name: Package app
|
||||
run: zip -m artifact/bin/flycast.nro.zip artifact/bin/flycast.nro
|
||||
if: hashFiles('artifact/bin/flycast.nro') != ''
|
||||
|
||||
- name: Setup Rclone
|
||||
uses: AnimMouse/setup-rclone@v1
|
||||
with:
|
||||
rclone_config: ${{ secrets.RCLONE_CONFIG }}
|
||||
|
||||
- name: Upload to S3
|
||||
run: rclone copy artifact/bin scaleway:flycast-builds/${{ matrix.config.destDir }}/${GITHUB_REF#refs/}-$GITHUB_SHA
|
||||
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push' && matrix.config.destDir != ''
|
||||
@@ -1,59 +0,0 @@
|
||||
name: Xbox, UWP
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'l10n_**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: UWP
|
||||
runs-on: windows-2022
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [ Win32, x64, ARM64 ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: CMake
|
||||
run: |
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release -G "Visual Studio 17 2022" -A ${{ matrix.arch }} -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0.22000.0
|
||||
cmake --build build --config Release -- /m
|
||||
shell: cmd
|
||||
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: Package app
|
||||
run: |
|
||||
mkdir build\artifact
|
||||
cd build\AppPackages\flycast\flycast_*_Test
|
||||
mkdir tmp
|
||||
ren *.msix flycast.msix
|
||||
makeappx unpack /p .\flycast.msix /d tmp
|
||||
copy ..\..\..\Release\*.dll tmp
|
||||
makeappx pack /d tmp /p ..\..\..\artifact\flycast.appx
|
||||
|
||||
- name: Sign app
|
||||
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@v7
|
||||
with:
|
||||
name: flycast-uwp-${{ matrix.arch }}
|
||||
path: build/artifact
|
||||
|
||||
- name: Setup Rclone
|
||||
uses: AnimMouse/setup-rclone@v1
|
||||
with:
|
||||
rclone_config: ${{ secrets.RCLONE_CONFIG }}
|
||||
|
||||
- name: Upload to S3
|
||||
run: rclone copy build/artifact scaleway:flycast-builds/xbox/${GITHUB_REF#refs/}-$GITHUB_SHA
|
||||
shell: bash
|
||||
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push' && matrix.arch == 'x64'
|
||||
+48
-17
@@ -1,22 +1,53 @@
|
||||
build/
|
||||
artifact/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
shell/apple/emulator-ios/OpenMP/OpenMP.xcframework
|
||||
*.class
|
||||
*.orig
|
||||
|
||||
# Visual Studio
|
||||
.vs/
|
||||
.vscode/
|
||||
out/
|
||||
bin/
|
||||
gen/
|
||||
obj/
|
||||
|
||||
# JetBrains IDES
|
||||
newdc/android/libnewdc/bin/libnewdc.jar
|
||||
newdc/android/libnewdc/bin/classes/com/example/newdc/JNIdc.class
|
||||
|
||||
shell/android/assets/build
|
||||
shell/android/tools
|
||||
|
||||
# iOS/Xcode general stuff
|
||||
.DS_Store
|
||||
*/build/*
|
||||
**/build/*
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
profile
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
.idea/
|
||||
cmake-build-*/
|
||||
*.hmap
|
||||
emulator.xccheckout
|
||||
reicast.xccheckout
|
||||
build/*
|
||||
*.so
|
||||
*Karen_angelXwind*
|
||||
local.properties
|
||||
ant.properties
|
||||
*build_obj
|
||||
Workdir/data
|
||||
Workdir/reicast*
|
||||
Workdir/vmu*
|
||||
Workdir/webui*
|
||||
Workdir/emu.cfg
|
||||
Workdir/lib*ant.properties
|
||||
|
||||
# Dependencies from .gitmodules
|
||||
core/deps/breakpad/
|
||||
core/deps/glslang/
|
||||
.cache/
|
||||
compile_commands.json
|
||||
CMakeUserPresets.json
|
||||
# Linux stuff
|
||||
shell/linux/.map
|
||||
shell/linux/nosym-reicast.elf
|
||||
shell/linux/reicast.elf
|
||||
|
||||
reicast-ios.xccheckout
|
||||
-229
@@ -1,229 +0,0 @@
|
||||
# DESCRIPTION: GitLab CI/CD for libRetro (NOT FOR GitLab-proper)
|
||||
|
||||
##############################################################################
|
||||
################################# BOILERPLATE ################################
|
||||
##############################################################################
|
||||
|
||||
# Core definitions
|
||||
.core-defs:
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
CORENAME: flycast
|
||||
CORE_ARGS: -DLIBRETRO=ON -DUSE_LIBCDIO=ON
|
||||
|
||||
.core-defs-linux:
|
||||
extends: .core-defs
|
||||
variables:
|
||||
CORE_ARGS: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE
|
||||
CC: /usr/bin/gcc-12
|
||||
CXX: /usr/bin/g++-12
|
||||
|
||||
.core-defs-osx-x64:
|
||||
extends: .core-defs
|
||||
variables:
|
||||
CORE_ARGS: -DLIBRETRO=ON -G Xcode -DCMAKE_OSX_ARCHITECTURES=x86_64
|
||||
EXTRA_PATH: Release
|
||||
|
||||
.core-defs-osx-arm64:
|
||||
extends: .core-defs
|
||||
variables:
|
||||
CORE_ARGS: -DLIBRETRO=ON -G Xcode
|
||||
EXTRA_PATH: Release
|
||||
|
||||
.core-defs-ios-arm64:
|
||||
extends: .core-defs
|
||||
variables:
|
||||
CORE_ARGS: -DLIBRETRO=ON -DUSE_OPENMP=OFF -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0
|
||||
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:
|
||||
- echo y | /android-sdk-linux/cmdline-tools/latest/bin/sdkmanager "ndk;${ANDROID_NDK_VERSION}"
|
||||
- cmake -DLIBRETRO=ON -DUSE_OPENMP=OFF -DANDROID_PLATFORM=android-$API_LEVEL -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_STL=c++_static -DANDROID_ABI=$ANDROID_ABI -DANDROID_ARM_MODE=arm -DANDROID_WEAK_API_DEFS=ON "$CMAKE_SOURCE_ROOT" -B$BUILD_DIR
|
||||
- cmake --build $BUILD_DIR --target ${CORENAME}_libretro --config Release -- -j $NUMPROC
|
||||
- mv $BUILD_DIR/${CORENAME}_libretro.so $LIBNAME
|
||||
- if [ $STRIP_CORE_LIB -eq 1 ]; then $NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip $LIBNAME; fi
|
||||
variables:
|
||||
ANDROID_NDK_VERSION: 29.0.14206865
|
||||
API_LEVEL: 21
|
||||
NDK_ROOT: /android-sdk-linux/ndk/$ANDROID_NDK_VERSION
|
||||
|
||||
# Inclusion templates, required for the build to work
|
||||
include:
|
||||
################################## DESKTOPS ################################
|
||||
# Windows
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/windows-cmake-mingw.yml'
|
||||
|
||||
# MacOS
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: 'osx-cmake-x86.yml'
|
||||
|
||||
# MacOS arm64
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: 'osx-cmake-arm64.yml'
|
||||
|
||||
# Linux
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/linux-cmake.yml'
|
||||
|
||||
################################## CELLULAR ################################
|
||||
# Android
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/android-cmake.yml'
|
||||
|
||||
# iOS arm64
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/ios-cmake.yml'
|
||||
|
||||
################################## CONSOLES ################################
|
||||
# Nintendo Switch
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/libnx-static-cmake.yml'
|
||||
|
||||
# tvOS arm64
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/tvos-cmake.yml'
|
||||
|
||||
#################################### MISC ##################################
|
||||
# webOS 32-bit (LGTV)
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/webos-cmake.yml'
|
||||
|
||||
# Stages for building
|
||||
stages:
|
||||
- build-prepare
|
||||
- build-shared
|
||||
- build-static
|
||||
|
||||
##############################################################################
|
||||
#################################### STAGES ##################################
|
||||
##############################################################################
|
||||
#
|
||||
################################### DESKTOPS #################################
|
||||
# Windows 64-bit
|
||||
libretro-build-windows-x64:
|
||||
extends:
|
||||
- .libretro-windows-cmake-x86_64
|
||||
- .core-defs
|
||||
|
||||
# Windows 32-bit
|
||||
libretro-build-windows-i686:
|
||||
extends:
|
||||
- .libretro-windows-cmake-x86
|
||||
- .core-defs
|
||||
|
||||
# Linux 64-bit
|
||||
libretro-build-linux-x64:
|
||||
extends:
|
||||
- .libretro-linux-cmake-x86_64
|
||||
- .core-defs-linux
|
||||
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-amd64-ubuntu:backports
|
||||
|
||||
# Linux 32-bit
|
||||
libretro-build-linux-i686:
|
||||
extends:
|
||||
- .libretro-linux-cmake-x86
|
||||
- .core-defs-linux
|
||||
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-i386-ubuntu:backports
|
||||
|
||||
# Linux 64-bit (ARM)
|
||||
libretro-build-linux-aarch64:
|
||||
extends:
|
||||
- .libretro-linux-cmake-aarch64
|
||||
- .core-defs-linux
|
||||
|
||||
|
||||
# MacOS 64-bit
|
||||
libretro-build-osx-x64:
|
||||
extends:
|
||||
- .libretro-osx-cmake-x86
|
||||
- .core-defs-osx-x64
|
||||
tags:
|
||||
- mac-apple-silicon
|
||||
|
||||
# MacOS arm 64-bit
|
||||
libretro-build-osx-arm64:
|
||||
extends:
|
||||
- .libretro-osx-cmake-arm64
|
||||
- .core-defs-osx-arm64
|
||||
|
||||
################################### CELLULAR #################################
|
||||
# Android ARMv7a
|
||||
android-armeabi-v7a:
|
||||
extends:
|
||||
- .libretro-android-cmake-armeabi-v7a
|
||||
- .core-defs-android
|
||||
|
||||
# Android ARMv8a
|
||||
android-arm64-v8a:
|
||||
extends:
|
||||
- .libretro-android-cmake-arm64-v8a
|
||||
- .core-defs-android
|
||||
|
||||
# Android 64-bit x86
|
||||
android-x86_64:
|
||||
extends:
|
||||
- .libretro-android-cmake-x86_64
|
||||
- .core-defs-android
|
||||
|
||||
# Android 32-bit x86
|
||||
android-x86:
|
||||
extends:
|
||||
- .libretro-android-cmake-x86
|
||||
- .core-defs-android
|
||||
|
||||
libretro-build-ios-arm64:
|
||||
extends:
|
||||
- .libretro-ios-cmake-arm64
|
||||
- .core-defs-ios-arm64
|
||||
|
||||
################################### CONSOLES #################################
|
||||
# Nintendo Switch
|
||||
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:
|
||||
extends:
|
||||
- .libretro-tvos-cmake-arm64
|
||||
- .core-defs-ios-arm64
|
||||
|
||||
#################################### MISC ##################################
|
||||
# webOS 32-bit
|
||||
libretro-build-webos-armv7a:
|
||||
extends:
|
||||
- .libretro-webos-armv7a-cmake
|
||||
- .core-defs
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
[submodule "core/deps/SDL"]
|
||||
path = core/deps/SDL
|
||||
url = https://github.com/libsdl-org/SDL.git
|
||||
[submodule "core/deps/libchdr"]
|
||||
path = core/deps/libchdr
|
||||
url = https://github.com/flyinghead/libchdr.git
|
||||
ignore = dirty
|
||||
[submodule "core/deps/luabridge"]
|
||||
path = core/deps/luabridge
|
||||
url = https://github.com/vinniefalco/LuaBridge.git
|
||||
[submodule "core/deps/breakpad"]
|
||||
path = core/deps/breakpad
|
||||
url = https://github.com/flyinghead/mingw-breakpad
|
||||
[submodule "core/deps/Vulkan-Headers"]
|
||||
path = core/deps/Vulkan-Headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
[submodule "core/deps/VulkanMemoryAllocator"]
|
||||
path = core/deps/VulkanMemoryAllocator
|
||||
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||
[submodule "core/deps/glslang"]
|
||||
path = core/deps/glslang
|
||||
url = https://github.com/KhronosGroup/glslang.git
|
||||
[submodule "core/deps/oboe"]
|
||||
path = core/deps/oboe
|
||||
url = https://github.com/google/oboe.git
|
||||
[submodule "core/deps/Syphon"]
|
||||
path = core/deps/Syphon
|
||||
url = https://github.com/vkedwardli/Syphon-Framework.git
|
||||
[submodule "core/deps/Spout"]
|
||||
path = core/deps/Spout
|
||||
url = https://github.com/vkedwardli/Spout2.git
|
||||
[submodule "core/deps/discord-rpc"]
|
||||
path = core/deps/discord-rpc
|
||||
url = https://github.com/harmonytf/discord-rpc
|
||||
[submodule "core/deps/libadrenotools"]
|
||||
path = core/deps/libadrenotools
|
||||
url = https://github.com/bylaws/libadrenotools
|
||||
[submodule "core/deps/rcheevos"]
|
||||
path = core/deps/rcheevos
|
||||
url = https://github.com/RetroAchievements/rcheevos.git
|
||||
[submodule "core/deps/googletest"]
|
||||
path = core/deps/googletest
|
||||
url = https://github.com/google/googletest.git
|
||||
[submodule "core/deps/asio"]
|
||||
path = core/deps/asio
|
||||
url = https://github.com/flyinghead/asio.git
|
||||
[submodule "core/deps/xbyak"]
|
||||
path = core/deps/xbyak
|
||||
url = https://github.com/herumi/xbyak.git
|
||||
[submodule "core/deps/libjuice"]
|
||||
path = core/deps/libjuice
|
||||
url = https://github.com/flyinghead/libjuice.git
|
||||
[submodule "core/deps/websocketpp"]
|
||||
path = core/deps/websocketpp
|
||||
url = https://github.com/zaphoyd/websocketpp.git
|
||||
[submodule "core/deps/DreamPicoPort-API"]
|
||||
path = core/deps/DreamPicoPort-API
|
||||
url = https://github.com/OrangeFox86/DreamPicoPort-API.git
|
||||
[submodule "core/deps/tinygettext"]
|
||||
path = core/deps/tinygettext
|
||||
url = https://github.com/flyinghead/tinygettext
|
||||
[submodule "core/deps/freetype"]
|
||||
path = core/deps/freetype
|
||||
url = https://gitlab.freedesktop.org/freetype/freetype.git
|
||||
[submodule "core/deps/gamesdk"]
|
||||
path = core/deps/gamesdk
|
||||
url = https://github.com/flyinghead/gamesdk.git
|
||||
[submodule "core/deps/basis_universal"]
|
||||
path = core/deps/basis_universal
|
||||
url = https://github.com/BinomialLLC/basis_universal.git
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
notifications:
|
||||
irc: chat.freenode.net#reicast
|
||||
language: android
|
||||
android:
|
||||
components:
|
||||
- platform-tools
|
||||
- tools
|
||||
- build-tools-22.0.0
|
||||
- android-21
|
||||
sudo: false
|
||||
before_install:
|
||||
- openssl aes-256-cbc -K $encrypted_c726d225a9d9_key -iv $encrypted_c726d225a9d9_iv
|
||||
-in debug.keystore.enc -out debug.keystore -d
|
||||
- GIT_HASH=`git log --pretty=format:'%h' -n 1`
|
||||
- GIT_BUILD=`git describe --all`-$GIT_HASH
|
||||
install:
|
||||
- wget http://dl.google.com/android/ndk/android-ndk-r10d-linux-x86_64.bin
|
||||
- chmod +x android-ndk-r10d-linux-x86_64.bin
|
||||
- ./android-ndk-r10d-linux-x86_64.bin -y | grep -v Extracting
|
||||
- mv android-ndk-r10d android-ndk
|
||||
- rm android-ndk-r10d-linux-x86_64.bin
|
||||
- export NDK=$(pwd)/android-ndk
|
||||
- export ANDROID_NDK=$(pwd)/android-ndk
|
||||
- export PATH=${PATH}:$ANDROID_HOME/tools
|
||||
- export PATH=${PATH}:$ANDROID_HOME/platform-tools
|
||||
- export PATH=${PATH}:$NDK
|
||||
- mkdir -p ~/.android
|
||||
- mv debug.keystore ~/.android/debug.keystore
|
||||
before_script:
|
||||
script:
|
||||
- cd shell/android
|
||||
- android update project -p . --target "android-21"
|
||||
- export NUMBER_OF_PROCESSORS=2
|
||||
- ant debug
|
||||
before_deploy:
|
||||
- cd ../../
|
||||
- mkdir -p artifacts/$GIT_BUILD/
|
||||
- cp shell/android/bin/reicast-debug.apk artifacts/$GIT_BUILD/reicast-android-debug-$GIT_HASH.apk
|
||||
deploy:
|
||||
provider: s3
|
||||
access_key_id: AKIAJR5J3OHAQUP5BHHQ
|
||||
secret_access_key:
|
||||
secure: dDmBiadS1cc2XCWWDZ1kwZGIoLkwEOLsdi5nzVdn1dHMsEKxN3u+3WFuuOfJHZYcdrtN7VhojCarPa+eGDDeiVme4m3y+QKMbI9lecHX9+t4jDNUyYep5TpeIVUkZ7fj4JsMi7U9/YFBTg7QnqvVsMAHE9vobkvjegjAYToBrlM=
|
||||
bucket: reicast-builds
|
||||
local-dir: artifacts
|
||||
upload-dir: builds
|
||||
skip_cleanup: true
|
||||
on:
|
||||
all_branches: true
|
||||
repo: reicast/reicast-emulator
|
||||
after_deploy:
|
||||
- echo "done"
|
||||
-1624
File diff suppressed because it is too large.
Load diff
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"version": 2,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "clang",
|
||||
"generator": "Ninja",
|
||||
"binaryDir": "build-clang",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "gcc",
|
||||
"generator": "Ninja",
|
||||
"binaryDir": "build-gcc",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,107 +1,169 @@
|
||||
# Flycast
|
||||
reicast
|
||||
===========
|
||||
reicast is a multi-platform Sega Dreamcast emulator.
|
||||
|
||||
[](https://github.com/flyinghead/flycast/actions/workflows/android.yml)
|
||||
[](https://github.com/flyinghead/flycast/actions/workflows/c-cpp.yml)
|
||||
[](https://github.com/flyinghead/flycast/actions/workflows/switch.yml)
|
||||
[](https://github.com/flyinghead/flycast/actions/workflows/uwp.yml)
|
||||
[](https://github.com/flyinghead/flycast/actions/workflows/bsd.yml)
|
||||
This is a developer-oriented resource, if you just want bins head over to http://reicast.com/
|
||||
|
||||
<img src="shell/linux/flycast.png" alt="flycast logo" width="150"/>
|
||||
Caution
|
||||
-------
|
||||
The source is a mess, and I need sleep.
|
||||
We had to discover some interesting arm bugs on the cleaned/refactored branch right after the release.
|
||||
Expect a much improved changed folder/make structure soon.
|
||||
|
||||
**Flycast** is a multi-platform Sega Dreamcast, Naomi, Naomi 2, and Atomiswave emulator derived from [**reicast**](https://github.com/skmp/reicast-emulator).
|
||||
Rebranding/forks
|
||||
----------------
|
||||
If you are interested into further porting/adapting/whatever, *please* don't fork off. I hate that. Really.
|
||||
|
||||
Information about configuration and supported features can be found on [**TheArcadeStriker's flycast wiki**](https://github.com/TheArcadeStriker/flycast-wiki/wiki).
|
||||
Let's try to keep everything under a single project :)
|
||||
|
||||
Join us on our [**Discord server**](https://discord.gg/X8YWP8w) for a chat.
|
||||
|
||||
## Downloads      
|
||||
|
||||
Get builds for your system from the [**builds page**](https://flyinghead.github.io/flycast-builds/) or [**GitHub Releases**](https://github.com/flyinghead/flycast/releases).
|
||||
|
||||
- **Latest master builds:** regular builds from the `master` branch with recent fixes and updates.
|
||||
- **Nightly dev builds:** experimental builds with the latest features and changes.
|
||||
- **Stable tagged releases:** versioned release builds published on GitHub Releases.
|
||||
|
||||
Automated test results are available from the builds page as well.
|
||||
|
||||
## Install
|
||||
|
||||
### Android 
|
||||
|
||||
 Install Flycast from [**Google Play**](https://play.google.com/store/apps/details?id=com.flycast.emulator).
|
||||
|
||||
### Flatpak (Linux )
|
||||
|
||||
1. [Set up Flatpak](https://www.flatpak.org/setup/).
|
||||
|
||||
2. Install Flycast from [Flathub](https://flathub.org/apps/details/org.flycast.Flycast):
|
||||
|
||||
 `flatpak install -y org.flycast.Flycast`
|
||||
|
||||
3. Run Flycast:
|
||||
|
||||
 `flatpak run org.flycast.Flycast`
|
||||
|
||||
### Homebrew (macOS )
|
||||
|
||||
1. [Set up Homebrew](https://brew.sh) or run `brew update` if already installed.
|
||||
|
||||
2. Choose one channel:
|
||||
|
||||
| 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.
|
||||
|
||||
### 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**.
|
||||
|
||||
## Build from source
|
||||
|
||||
### macOS
|
||||
|
||||
 Right-click the bootstrap script and choose **Open**:
|
||||
|
||||
 `shell/apple/generate_xcode_project.command`
|
||||
|
||||
### Windows
|
||||
|
||||
 Double-click the bootstrap script:
|
||||
|
||||
 `shell\windows\generate_vs_project.bat`
|
||||
|
||||
### Linux
|
||||
|
||||
#### Dependencies
|
||||
|
||||
- **C/C++ compiler toolchain** (e.g. `gcc`/`g++`)
|
||||
- **CMake**
|
||||
- **make**
|
||||
- **libcurl** (development headers)
|
||||
- **libudev** (development headers)
|
||||
- **SDL2** (development headers)
|
||||
- **Graphics API**: Vulkan, OpenGL
|
||||
|
||||
#### Build
|
||||
Building for Android
|
||||
--------------------
|
||||
Tools required:
|
||||
* Latest Android SDK
|
||||
- http://developer.android.com/sdk/index.html
|
||||
* NDK r8b or newer
|
||||
- https://developer.android.com/tools/sdk/ndk/index.html
|
||||
- If are not using r9c+, comment the "NDK_TOOLCHAIN_VERSION := 4.8" in shell/android/jni/Application.mk and shell/android/xperia/jni/Application.mk
|
||||
* Android 5.0.1 (API 21) & Android 2.3.1 (API 9)
|
||||
- http://developer.android.com/sdk/installing/adding-packages.html
|
||||
- note that API 9 is hidden (you must check to show obsolete in SDK manager)
|
||||
* Ant
|
||||
- http://ant.apache.org/
|
||||
|
||||
From project root directory:
|
||||
```
|
||||
$ git clone --recursive https://github.com/flyinghead/flycast.git
|
||||
$ cd flycast
|
||||
$ mkdir build && cd build
|
||||
$ cmake ..
|
||||
$ make
|
||||
export ANDROID_NDK=/ # Type the full path to your NDK here
|
||||
|
||||
cd shell/android/
|
||||
|
||||
android update project -p . --target "android-21"
|
||||
|
||||
ant debug
|
||||
```
|
||||
|
||||
## Packaging status
|
||||
Building for iOS
|
||||
----------------
|
||||
Tools required:
|
||||
* Latest Xcode
|
||||
- https://developer.apple.com/xcode/downloads/
|
||||
* An iOS device (reicast will not compile for the iOS Simulator)
|
||||
* iOS 5.x ~ 7.x
|
||||
* iOSOpenDev if developing without an official Apple certificate
|
||||
- http://iosopendev.com/download/
|
||||
|
||||
From project root directory:
|
||||
|
||||
```
|
||||
cd shell/ios/
|
||||
|
||||
xcodebuild -configuration Release
|
||||
```
|
||||
|
||||
Building for Linux
|
||||
------------------
|
||||
Requirements:
|
||||
* build-essential
|
||||
* libasound
|
||||
* libegl1-mesa-dev
|
||||
* libgles2-mesa-dev
|
||||
* libasound2-dev
|
||||
|
||||
From project root directory:
|
||||
|
||||
```
|
||||
cd shell/linux
|
||||
|
||||
make
|
||||
```
|
||||
|
||||
|
||||
Translations
|
||||
------------
|
||||
New and updated translations are always appreciated!
|
||||
All we ask is that you not use “regional” phrases that may not be generally understood.
|
||||
|
||||
Translations can be submitted as a pull request
|
||||
|
||||
|
||||
Development/Beta versions
|
||||
-------------
|
||||
| Platform | Status | Downloads
|
||||
| -------------------------------------------------- | -------------- | ---------
|
||||
|  Android | [](https://travis-ci.org/reicast/reicast-emulator) | [Reicast CI Builds](http://builds.reicast.com)
|
||||
|  iOS | [](https://app.ship.io/dashboard#/jobs/9843/history) | *TODO*
|
||||
|  Windows | [](https://ci.appveyor.com/project/skmp/reicast-emulator/branch/master) | [Reicast CI Builds](http://builds.reicast.com)
|
||||
|  Linux | [](https://app.wercker.com/project/bykey/bcabca642a2de044c6f58203b975878b) | *TODO*
|
||||
|  OSX | *TODO* | *TODO*
|
||||
|
||||
|
||||
Additional builds (iOS & android) can be found at [angelxwind's](http://reicast.angelxwind.net/) buildbot and [Random Stuff "Daily/Nightly/Testing" PPA](https://launchpad.net/~random-stuff/+archive/ubuntu/ppa) (for Ubuntu).
|
||||
|
||||
|
||||
Donations and stuff
|
||||
-------------------
|
||||
Well, glad you liked the project so far!
|
||||
|
||||
We're currently short on hardware.
|
||||
|
||||
If you would like to donate some devices, get in touch at team@reicast.com.
|
||||
GLES3 capable stuff, some mainstream hardware and rarities would be extremely
|
||||
appreciated.
|
||||
Keep in mind we're located in Greece for the most part
|
||||
|
||||
This has been tested/developed on
|
||||
* Galaxy tab 7.0 Plus
|
||||
* LG P990
|
||||
* Archos 10G9
|
||||
* Some Chinese tablet
|
||||
* OUYA
|
||||
* Various development boards
|
||||
* GCW Zero
|
||||
|
||||
We had to buy all of these, except the GCW Zero and a BeagleBone which were
|
||||
donated (Thanks! You rock!)
|
||||
|
||||
Apart from that, we don't accept monetary donations right now.
|
||||
We also don't plan to be releasing a premium version at any store.
|
||||
Most of the project has been developed by drk||Raziel (aka, skmp, drk, Raz,
|
||||
etc) but it has been based on the works of multiple people. It would be
|
||||
extremely unfair to charge for it and get all the credit :)
|
||||
|
||||
We're planning for an indiegogo campaign later on to help with sustained
|
||||
development and running costs, so follow @reicastdc on twitter for updates
|
||||
|
||||
Other Testing
|
||||
-------------
|
||||
Devices tested by the reicast team:
|
||||
* Apple iPhone 4 GSM Rev0 (N90AP)
|
||||
* Apple iPhone 4 CDMA (N92AP)
|
||||
* Apple iPod touch 4 (N81AP)
|
||||
* Apple iPod touch 3G (N18AP)
|
||||
* Apple iPhone 3GS (N88AP)
|
||||
* Apple iPhone 5s
|
||||
* Apple iPad 3
|
||||
* Sony Xperia X10a (es209ra)
|
||||
* Amazon Kindle Fire HD 7 (tate-pvt-08)
|
||||
* Nvidia Shield portable
|
||||
* Nvidia Shield tablet
|
||||
* Samsung Galaxy Note 4
|
||||
* LG Nexus 5
|
||||
* Asus Nexus 7 (2013)
|
||||
|
||||
Team
|
||||
----
|
||||
|
||||
You can check the currently active committers on [the pretty graphs page](https://github.com/reicast/reicast-emulator/graphs/contributors)
|
||||
|
||||
Our IRC channel is [#reicast @ chat.freenode.net](irc://chat.freenode.net/reicast).
|
||||
|
||||
The original reicast team consisted of drk||Raziel (mostly just writing code),
|
||||
PsyMan (debugging/testing and everything else) and a little bit of gb_away
|
||||
|
||||
|
||||
Special thanks
|
||||
--------------
|
||||
In previous iterations a lot of people have worked on this, notably David
|
||||
Miller (aka, ZeZu), the nullDC team, friends from #pcsx2 and all over the world :)
|
||||
|
||||
[](https://bitdeli.com/free "Bitdeli Badge")
|
||||
|
||||
[](https://repology.org/project/flycast/versions)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,398 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset=utf-8 http-equiv="Content-Language" content="en"/>
|
||||
<title>Minimal Websocket test app</title>
|
||||
<style type="text/css">
|
||||
div.title { font-size:18pt; font: Arial; font-weight:normal; text-align:center; color:#000000; }
|
||||
.browser { font-size:18pt; font: Arial; font-weight:normal; text-align:center; color:#ffff00; vertical-align:middle; text-align:center; background:#d0b070; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px;}
|
||||
.group2 { width:600px; vertical-align:middle; text-align:center; background:#f0f0e0; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px; }
|
||||
.explain { vertical-align:middle; text-align:center; background:#f0f0c0; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px; color:#404000; }
|
||||
.content { vertical-align:top; text-align:center; background:#fffff0; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px; }
|
||||
.canvas { vertical-align:top; text-align:center; background:#efefd0; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header></header>
|
||||
<article>
|
||||
|
||||
<table><tr><td>
|
||||
|
||||
<table width="100%"><tr><td valign=middle align=center><a href="http://libwebsockets.org"><img src="/libwebsockets.org-logo.png"></a></td><td>
|
||||
<section class="browser">Detected Browser: <div id=brow>...</div></section></td></tr></table>
|
||||
|
||||
</td></tr><tr><td>
|
||||
|
||||
<section id="increment" class="group2">
|
||||
<div class="title">libwebsockets "dumb-increment-protocol"</div>
|
||||
<table><tr><td>
|
||||
<table class="content" width="200px">
|
||||
<tr><td align=center><input type=button id=offset value="Reset counter" onclick="reset();" ></td></tr>
|
||||
<tr><td width=200px align=center><div id=number> </div></td></tr>
|
||||
<tr><td id=wsdi_statustd align=center class="explain"><div id=wsdi_status>Not initialized</div></td></tr>
|
||||
</tr>
|
||||
</table>
|
||||
</td><td class="explain">
|
||||
The incrementing number is coming from the server and is individual for
|
||||
each connection to the server... try opening a second browser window.
|
||||
<br/><br/>
|
||||
The button zeros just this connection's number.
|
||||
<br/><br/>
|
||||
|
||||
</td></tr></table>
|
||||
</section>
|
||||
<br>
|
||||
<section id="mirror" class="group2">
|
||||
<div class="title">libwebsockets "lws-mirror-protocol"</div>
|
||||
<div class="explain">
|
||||
Use the mouse to draw on the canvas below -- all other browser windows open
|
||||
on this page see your drawing in realtime and you can see any of theirs as
|
||||
well.
|
||||
<br/><br/>
|
||||
The lws-mirror protocol doesn't interpret what is being sent to it, it just
|
||||
re-sends it to every other websocket it has a connection with using that
|
||||
protocol, including the guy who sent the packet.
|
||||
<br/><br/>
|
||||
<b>libwebsockets-test-client</b> joins in by spamming circles on to this shared canvas when
|
||||
run.
|
||||
</div>
|
||||
<table class="content">
|
||||
<tr>
|
||||
<td>Drawing color:
|
||||
<select id="color" onchange="update_color();">
|
||||
<option value=#000000>Black</option>
|
||||
<option value=#0000ff>Blue</option>
|
||||
<option value=#20ff20>Green</option>
|
||||
<option value=#802020>Dark Red</option>
|
||||
</select>
|
||||
</td>
|
||||
<td id=wslm_statustd align=center class="explain"><div id=wslm_status>Not initialized</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan=2 width=500 class="content">
|
||||
<div id="wslm_drawing">
|
||||
</div></td>
|
||||
</tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
</td></tr><tr><td>
|
||||
Looking for support? <a href="http://libwebsockets.org">http://libwebsockets.org</a><br/>
|
||||
Join the mailing list: <a href="http://ml.libwebsockets.org/mailman/listinfo/libwebsockets">http://ml.libwebsockets.org/mailman/listinfo/libwebsockets</a>
|
||||
|
||||
</td></tr></table>
|
||||
|
||||
</article>
|
||||
|
||||
<script>
|
||||
|
||||
/* BrowserDetect came from http://www.quirksmode.org/js/detect.html */
|
||||
|
||||
var BrowserDetect = {
|
||||
init: function () {
|
||||
this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
|
||||
this.version = this.searchVersion(navigator.userAgent)
|
||||
|| this.searchVersion(navigator.appVersion)
|
||||
|| "an unknown version";
|
||||
this.OS = this.searchString(this.dataOS) || "an unknown OS";
|
||||
},
|
||||
searchString: function (data) {
|
||||
for (var i=0;i<data.length;i++) {
|
||||
var dataString = data[i].string;
|
||||
var dataProp = data[i].prop;
|
||||
this.versionSearchString = data[i].versionSearch || data[i].identity;
|
||||
if (dataString) {
|
||||
if (dataString.indexOf(data[i].subString) != -1)
|
||||
return data[i].identity;
|
||||
}
|
||||
else if (dataProp)
|
||||
return data[i].identity;
|
||||
}
|
||||
},
|
||||
searchVersion: function (dataString) {
|
||||
var index = dataString.indexOf(this.versionSearchString);
|
||||
if (index == -1) return;
|
||||
return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
|
||||
},
|
||||
dataBrowser: [
|
||||
{
|
||||
string: navigator.userAgent,
|
||||
subString: "Chrome",
|
||||
identity: "Chrome"
|
||||
},
|
||||
{ string: navigator.userAgent,
|
||||
subString: "OmniWeb",
|
||||
versionSearch: "OmniWeb/",
|
||||
identity: "OmniWeb"
|
||||
},
|
||||
{
|
||||
string: navigator.vendor,
|
||||
subString: "Apple",
|
||||
identity: "Safari",
|
||||
versionSearch: "Version"
|
||||
},
|
||||
{
|
||||
prop: window.opera,
|
||||
identity: "Opera",
|
||||
versionSearch: "Version"
|
||||
},
|
||||
{
|
||||
string: navigator.vendor,
|
||||
subString: "iCab",
|
||||
identity: "iCab"
|
||||
},
|
||||
{
|
||||
string: navigator.vendor,
|
||||
subString: "KDE",
|
||||
identity: "Konqueror"
|
||||
},
|
||||
{
|
||||
string: navigator.userAgent,
|
||||
subString: "Firefox",
|
||||
identity: "Firefox"
|
||||
},
|
||||
{
|
||||
string: navigator.vendor,
|
||||
subString: "Camino",
|
||||
identity: "Camino"
|
||||
},
|
||||
{ // for newer Netscapes (6+)
|
||||
string: navigator.userAgent,
|
||||
subString: "Netscape",
|
||||
identity: "Netscape"
|
||||
},
|
||||
{
|
||||
string: navigator.userAgent,
|
||||
subString: "MSIE",
|
||||
identity: "Explorer",
|
||||
versionSearch: "MSIE"
|
||||
},
|
||||
{
|
||||
string: navigator.userAgent,
|
||||
subString: "Gecko",
|
||||
identity: "Mozilla",
|
||||
versionSearch: "rv"
|
||||
},
|
||||
{ // for older Netscapes (4-)
|
||||
string: navigator.userAgent,
|
||||
subString: "Mozilla",
|
||||
identity: "Netscape",
|
||||
versionSearch: "Mozilla"
|
||||
}
|
||||
],
|
||||
dataOS : [
|
||||
{
|
||||
string: navigator.platform,
|
||||
subString: "Win",
|
||||
identity: "Windows"
|
||||
},
|
||||
{
|
||||
string: navigator.platform,
|
||||
subString: "Mac",
|
||||
identity: "Mac"
|
||||
},
|
||||
{
|
||||
string: navigator.userAgent,
|
||||
subString: "iPhone",
|
||||
identity: "iPhone/iPod"
|
||||
},
|
||||
{
|
||||
string: navigator.platform,
|
||||
subString: "Linux",
|
||||
identity: "Linux"
|
||||
}
|
||||
]
|
||||
|
||||
};
|
||||
BrowserDetect.init();
|
||||
|
||||
document.getElementById("brow").textContent = " " + BrowserDetect.browser + " "
|
||||
+ BrowserDetect.version +" " + BrowserDetect.OS +" ";
|
||||
|
||||
var pos = 0;
|
||||
|
||||
function get_appropriate_ws_url()
|
||||
{
|
||||
var pcol;
|
||||
var u = document.URL;
|
||||
|
||||
/*
|
||||
* We open the websocket encrypted if this page came on an
|
||||
* https:// url itself, otherwise unencrypted
|
||||
*/
|
||||
|
||||
if (u.substring(0, 5) == "https") {
|
||||
pcol = "wss://";
|
||||
u = u.substr(8);
|
||||
} else {
|
||||
pcol = "ws://";
|
||||
if (u.substring(0, 4) == "http")
|
||||
u = u.substr(7);
|
||||
}
|
||||
|
||||
u = u.split('/');
|
||||
|
||||
/* + "/xxx" bit is for IE10 workaround */
|
||||
|
||||
return pcol + u[0] + "/xxx";
|
||||
}
|
||||
|
||||
|
||||
document.getElementById("number").textContent = get_appropriate_ws_url();
|
||||
|
||||
/* dumb increment protocol */
|
||||
|
||||
var socket_di;
|
||||
|
||||
if (typeof MozWebSocket != "undefined") {
|
||||
socket_di = new MozWebSocket(get_appropriate_ws_url(),
|
||||
"dumb-increment-protocol");
|
||||
} else {
|
||||
socket_di = new WebSocket(get_appropriate_ws_url(),
|
||||
"dumb-increment-protocol");
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
socket_di.onopen = function() {
|
||||
document.getElementById("wsdi_statustd").style.backgroundColor = "#40ff40";
|
||||
document.getElementById("wsdi_status").textContent = " websocket connection opened ";
|
||||
}
|
||||
|
||||
socket_di.onmessage =function got_packet(msg) {
|
||||
document.getElementById("number").textContent = msg.data + "\n";
|
||||
}
|
||||
|
||||
socket_di.onclose = function(){
|
||||
document.getElementById("wsdi_statustd").style.backgroundColor = "#ff4040";
|
||||
document.getElementById("wsdi_status").textContent = " websocket connection CLOSED ";
|
||||
}
|
||||
} catch(exception) {
|
||||
alert('<p>Error' + exception);
|
||||
}
|
||||
|
||||
function reset() {
|
||||
socket_di.send("reset\n");
|
||||
}
|
||||
|
||||
|
||||
/* lws-mirror protocol */
|
||||
|
||||
var down = 0;
|
||||
var no_last = 1;
|
||||
var last_x = 0, last_y = 0;
|
||||
var ctx;
|
||||
var socket_lm;
|
||||
var color = "#000000";
|
||||
|
||||
if (typeof MozWebSocket != "undefined") {
|
||||
socket_lm = new MozWebSocket(get_appropriate_ws_url(),
|
||||
"lws-mirror-protocol");
|
||||
} else {
|
||||
socket_lm = new WebSocket(get_appropriate_ws_url(),
|
||||
"lws-mirror-protocol");
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
socket_lm.onopen = function() {
|
||||
document.getElementById("wslm_statustd").style.backgroundColor = "#40ff40";
|
||||
document.getElementById("wslm_status").textContent = " websocket connection opened ";
|
||||
}
|
||||
|
||||
socket_lm.onmessage =function got_packet(msg) {
|
||||
j = msg.data.split(';');
|
||||
f = 0;
|
||||
while (f < j.length - 1) {
|
||||
i = j[f].split(' ');
|
||||
if (i[0] == 'd') {
|
||||
ctx.strokeStyle = i[1];
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(+(i[2]), +(i[3]));
|
||||
ctx.lineTo(+(i[4]), +(i[5]));
|
||||
ctx.stroke();
|
||||
}
|
||||
if (i[0] == 'c') {
|
||||
ctx.strokeStyle = i[1];
|
||||
ctx.beginPath();
|
||||
ctx.arc(+(i[2]), +(i[3]), +(i[4]), 0, Math.PI*2, true);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
f++;
|
||||
}
|
||||
}
|
||||
|
||||
socket_lm.onclose = function(){
|
||||
document.getElementById("wslm_statustd").style.backgroundColor = "#ff4040";
|
||||
document.getElementById("wslm_status").textContent = " websocket connection CLOSED ";
|
||||
}
|
||||
} catch(exception) {
|
||||
alert('<p>Error' + exception);
|
||||
}
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.height = 300;
|
||||
canvas.width = 480;
|
||||
ctx = canvas.getContext("2d");
|
||||
|
||||
document.getElementById('wslm_drawing').appendChild(canvas);
|
||||
|
||||
canvas.addEventListener('mousemove', ev_mousemove, false);
|
||||
canvas.addEventListener('mousedown', ev_mousedown, false);
|
||||
canvas.addEventListener('mouseup', ev_mouseup, false);
|
||||
|
||||
offsetX = offsetY = 0;
|
||||
element = canvas;
|
||||
if (element.offsetParent) {
|
||||
do {
|
||||
offsetX += element.offsetLeft;
|
||||
offsetY += element.offsetTop;
|
||||
} while ((element = element.offsetParent));
|
||||
}
|
||||
|
||||
function update_color() {
|
||||
color = document.getElementById("color").value;
|
||||
}
|
||||
|
||||
function ev_mousedown (ev) {
|
||||
down = 1;
|
||||
}
|
||||
|
||||
function ev_mouseup(ev) {
|
||||
down = 0;
|
||||
no_last = 1;
|
||||
}
|
||||
|
||||
function ev_mousemove (ev) {
|
||||
var x, y;
|
||||
|
||||
if (ev.offsetX) {
|
||||
x = ev.offsetX;
|
||||
y = ev.offsetY;
|
||||
} else {
|
||||
x = ev.layerX - offsetX;
|
||||
y = ev.layerY - offsetY;
|
||||
|
||||
}
|
||||
|
||||
if (!down)
|
||||
return;
|
||||
if (no_last) {
|
||||
no_last = 0;
|
||||
last_x = x;
|
||||
last_y = y;
|
||||
return;
|
||||
}
|
||||
socket_lm.send("d " + color + " " + last_x + " " + last_y + " " + x + ' ' + y + ';');
|
||||
|
||||
last_x = x;
|
||||
last_y = y;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,29 @@
|
||||
version: git-{branch}-{build}
|
||||
configuration: Debug
|
||||
platform:
|
||||
- x86
|
||||
- x64
|
||||
build:
|
||||
project: shell/reicast.sln
|
||||
parallel: true
|
||||
verbosity: minimal
|
||||
|
||||
after_build:
|
||||
- cmd: mkdir artifacts
|
||||
- cmd: set PLATFORM2=%PLATFORM:x86=Win32%
|
||||
- cmd: move WorkDir\reicast_%PLATFORM2%_Slow.exe artifacts/reicast-win_%PLATFORM%-slow-%APPVEYOR_REPO_COMMIT%.exe
|
||||
|
||||
artifacts:
|
||||
- path: artifacts
|
||||
name: reicast-win_$(platform)-slow-$(APPVEYOR_REPO_COMMIT)
|
||||
|
||||
deploy:
|
||||
- provider: S3
|
||||
access_key_id: AKIAJETEBUZSLZF3YNKA
|
||||
secret_access_key:
|
||||
secure: ZxrysCeqMK99dOOzKyoeN4R5aO8JjA4RTrwVAQPQHz8sjyyKuVDZT2fC+9m6lUIi
|
||||
region:
|
||||
bucket: reicast-builds-windows
|
||||
folder: 'builds/heads/$(APPVEYOR_REPO_BRANCH)-$(APPVEYOR_REPO_COMMIT)'
|
||||
artifact: reicast-win_$(platform)-slow-$(APPVEYOR_REPO_COMMIT)
|
||||
set_public: true
|
||||
@@ -1 +0,0 @@
|
||||
/version.h
|
||||
+2
-14
@@ -1,16 +1,4 @@
|
||||
# core
|
||||
core
|
||||
===========
|
||||
|
||||
Here lies the core of our codebase. Everything that's OS inspecific rests here.
|
||||
** Please check per directory README for more info **
|
||||
|
||||
### Some rudimentary categories are:
|
||||
- hw -- DC Hardware Components Implementation
|
||||
- nullDC.cpp -- Main entry points
|
||||
- oslib -- Audio drivers
|
||||
- cfg -- Configuration backend structure
|
||||
- reios -- (Our) Implementation of the DreamCast BIOS
|
||||
- deps -- External dependencies
|
||||
- imgread -- Content drivers
|
||||
- rec-* -- Recompilers
|
||||
- rend -- Graphics renderers and UI
|
||||
All of the interesting bits are here
|
||||
@@ -1,3 +0,0 @@
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
achievements.cpp
|
||||
achievements.h)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
#include <future>
|
||||
#include <vector>
|
||||
|
||||
namespace achievements
|
||||
{
|
||||
#ifdef USE_RACHIEVEMENTS
|
||||
|
||||
struct Game
|
||||
{
|
||||
std::string image;
|
||||
std::string title;
|
||||
u32 unlockedAchievements;
|
||||
u32 totalAchievements;
|
||||
u32 points;
|
||||
u32 totalPoints;
|
||||
};
|
||||
|
||||
struct Achievement
|
||||
{
|
||||
Achievement() = default;
|
||||
Achievement(const std::string& image, const std::string& title, const std::string& description, const std::string& category, const std::string& status)
|
||||
: image(image), title(title), description(description), category(category), status(status) {}
|
||||
std::string image;
|
||||
std::string title;
|
||||
std::string description;
|
||||
std::string category;
|
||||
std::string status;
|
||||
};
|
||||
|
||||
bool init();
|
||||
void term();
|
||||
std::future<void> login(const char *username, const char *password);
|
||||
void logout();
|
||||
bool isLoggedOn();
|
||||
bool isActive();
|
||||
Game getCurrentGame();
|
||||
std::vector<Achievement> getAchievementList();
|
||||
bool canPause();
|
||||
void setHostOverride(const std::string& host);
|
||||
|
||||
#else
|
||||
|
||||
static inline bool isActive() {
|
||||
return false;
|
||||
}
|
||||
static inline bool canPause() {
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void serialize(Serializer& ser);
|
||||
void deserialize(Deserializer& deser);
|
||||
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
Created on: Nov 22, 2018
|
||||
|
||||
Copyright 2018 flyinghead
|
||||
|
||||
This file is part of reicast.
|
||||
|
||||
reicast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
reicast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with reicast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "7zArchive.h"
|
||||
#include "lzma/7z.h"
|
||||
#include "lzma/7zCrc.h"
|
||||
#include "lzma/Alloc.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#define kInputBufSize ((size_t)1 << 18)
|
||||
|
||||
static bool crc_tables_generated;
|
||||
|
||||
SRes SzArchive::ArchiveStream::Read(const ISeekInStream *p, void *buf, size_t *size)
|
||||
{
|
||||
auto file_archive = CONTAINER_FROM_VTBL(p, ArchiveStream, vt);
|
||||
*size = file_archive->file->read(buf, 1, *size);
|
||||
return file_archive->file->error() ? SZ_ERROR_READ : SZ_OK;
|
||||
}
|
||||
|
||||
SRes SzArchive::ArchiveStream::Seek(const ISeekInStream *p, Int64 *pos, ESzSeek origin)
|
||||
{
|
||||
auto file_archive = CONTAINER_FROM_VTBL(p, ArchiveStream, vt);
|
||||
if (file_archive->file->seek(*pos, origin) == 0) {
|
||||
*pos = file_archive->file->tell();
|
||||
return SZ_OK;
|
||||
}
|
||||
else {
|
||||
return SZ_ERROR_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
bool SzArchive::Open(hostfs::File *file)
|
||||
{
|
||||
SzArEx_Init(&szarchive);
|
||||
|
||||
if (archiveStream.file != nullptr)
|
||||
delete archiveStream.file;
|
||||
archiveStream.vt.Read = ArchiveStream::Read;
|
||||
archiveStream.vt.Seek = ArchiveStream::Seek;
|
||||
archiveStream.file = file;
|
||||
|
||||
LookToRead2_CreateVTable(&lookStream, 0);
|
||||
lookStream.buf = (Byte *)ISzAlloc_Alloc(&g_Alloc, kInputBufSize);
|
||||
if (lookStream.buf == NULL)
|
||||
{
|
||||
delete archiveStream.file;
|
||||
return false;
|
||||
}
|
||||
lookStream.bufSize = kInputBufSize;
|
||||
lookStream.realStream = &archiveStream.vt;
|
||||
LookToRead2_Init(&lookStream);
|
||||
|
||||
if (!crc_tables_generated)
|
||||
{
|
||||
CrcGenerateTable();
|
||||
crc_tables_generated = true;
|
||||
}
|
||||
SRes res = SzArEx_Open(&szarchive, &lookStream.vt, &g_Alloc, &g_Alloc);
|
||||
|
||||
return (res == SZ_OK);
|
||||
}
|
||||
|
||||
ArchiveFile* SzArchive::OpenFile(const char* name)
|
||||
{
|
||||
u16 fname[512];
|
||||
for (UInt32 i = 0; i < szarchive.NumFiles; i++)
|
||||
{
|
||||
if (SzArEx_IsDir(&szarchive, i))
|
||||
continue;
|
||||
|
||||
size_t len = SzArEx_GetFileNameUtf16(&szarchive, i, fname);
|
||||
char szname[512];
|
||||
size_t j = 0;
|
||||
for (; j < len && j < sizeof(szname) - 1; j++)
|
||||
szname[j] = fname[j];
|
||||
szname[j] = 0;
|
||||
if (strcmp(name, szname))
|
||||
continue;
|
||||
|
||||
size_t offset = 0;
|
||||
size_t out_size_processed = 0;
|
||||
SRes res = SzArEx_Extract(&szarchive, &lookStream.vt, i, &block_idx, &out_buffer, &out_buffer_size, &offset, &out_size_processed, &g_Alloc, &g_Alloc);
|
||||
if (res != SZ_OK)
|
||||
return NULL;
|
||||
|
||||
return new SzArchiveFile(out_buffer, offset, (u32)out_size_processed);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ArchiveFile* SzArchive::OpenFileByCrc(u32 crc)
|
||||
{
|
||||
if (crc == 0)
|
||||
return NULL;
|
||||
for (UInt32 i = 0; i < szarchive.NumFiles; i++)
|
||||
{
|
||||
unsigned isDir = SzArEx_IsDir(&szarchive, i);
|
||||
if (isDir)
|
||||
continue;
|
||||
|
||||
if (crc != szarchive.CRCs.Vals[i])
|
||||
continue;
|
||||
|
||||
size_t offset = 0;
|
||||
size_t out_size_processed = 0;
|
||||
SRes res = SzArEx_Extract(&szarchive, &lookStream.vt, i, &block_idx, &out_buffer, &out_buffer_size, &offset, &out_size_processed, &g_Alloc, &g_Alloc);
|
||||
if (res != SZ_OK)
|
||||
return NULL;
|
||||
|
||||
return new SzArchiveFile(out_buffer, offset, (u32)out_size_processed);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SzArchive::~SzArchive()
|
||||
{
|
||||
if (lookStream.buf != NULL)
|
||||
{
|
||||
delete archiveStream.file;
|
||||
ISzAlloc_Free(&g_Alloc, lookStream.buf);
|
||||
if (out_buffer != NULL)
|
||||
ISzAlloc_Free(&g_Alloc, out_buffer);
|
||||
SzArEx_Free(&szarchive, &g_Alloc);
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
Created on: Nov 23, 2018
|
||||
|
||||
Copyright 2018 flyinghead
|
||||
|
||||
This file is part of reicast.
|
||||
|
||||
reicast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
reicast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with reicast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "archive.h"
|
||||
#include "lzma/7z.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
class SzArchive : public Archive
|
||||
{
|
||||
public:
|
||||
SzArchive() : out_buffer(NULL) {
|
||||
memset(&archiveStream, 0, sizeof(archiveStream));
|
||||
memset(&lookStream, 0, sizeof(lookStream));
|
||||
}
|
||||
~SzArchive() override;
|
||||
|
||||
ArchiveFile* OpenFile(const char* name) override;
|
||||
ArchiveFile *OpenFileByCrc(u32 crc) override;
|
||||
|
||||
protected:
|
||||
bool Open(hostfs::File *file) override;
|
||||
|
||||
private:
|
||||
struct ArchiveStream
|
||||
{
|
||||
static SRes Read(const ISeekInStream *p, void *buf, size_t *size);
|
||||
static SRes Seek(const ISeekInStream *p, Int64 *pos, ESzSeek origin);
|
||||
|
||||
ISeekInStream vt;
|
||||
hostfs::File *file;
|
||||
};
|
||||
|
||||
CSzArEx szarchive;
|
||||
UInt32 block_idx; /* it can have any value before first call (if outBuffer = 0) */
|
||||
Byte *out_buffer; /* it must be 0 before first call for each new archive. */
|
||||
size_t out_buffer_size; /* it can have any value before first call (if outBuffer = 0) */
|
||||
ArchiveStream archiveStream;
|
||||
CLookToRead2 lookStream;
|
||||
|
||||
};
|
||||
|
||||
class SzArchiveFile : public ArchiveFile
|
||||
{
|
||||
public:
|
||||
SzArchiveFile(u8 *data, u32 offset, u32 length)
|
||||
: data(data), offset(offset), _length(length) {}
|
||||
u32 Read(void *buffer, u32 length) override
|
||||
{
|
||||
length = std::min(length, this->_length);
|
||||
memcpy(buffer, data + offset, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
size_t length() override {
|
||||
return _length;
|
||||
}
|
||||
|
||||
private:
|
||||
u8 *data;
|
||||
u32 offset;
|
||||
u32 _length;
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
7zArchive.cpp
|
||||
7zArchive.h
|
||||
archive.cpp
|
||||
archive.h
|
||||
rzip.cpp
|
||||
rzip.h
|
||||
ZipArchive.cpp
|
||||
ZipArchive.h)
|
||||
@@ -1,233 +0,0 @@
|
||||
/*
|
||||
Created on: Nov 23, 2018
|
||||
|
||||
Copyright 2018 flyinghead
|
||||
|
||||
This file is part of reicast.
|
||||
|
||||
reicast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
reicast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with reicast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "ZipArchive.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
ZipArchive::~ZipArchive()
|
||||
{
|
||||
zip_close(zip);
|
||||
}
|
||||
|
||||
static zip_int64_t ZipSourceCallback(void *userdata, void *data, zip_uint64_t len, zip_source_cmd_t cmd)
|
||||
{
|
||||
using Error = std::array<int, 2>;
|
||||
static Error error = {ZIP_ER_OK, 0};
|
||||
auto file = static_cast<hostfs::File*>(userdata);
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case ZIP_SOURCE_OPEN:
|
||||
return 0;
|
||||
|
||||
case ZIP_SOURCE_READ:
|
||||
{
|
||||
const auto bytes_read = file->read(data, 1, len);
|
||||
|
||||
if (file->error())
|
||||
{
|
||||
error = {ZIP_ER_INTERNAL, 0};
|
||||
return -1;
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
case ZIP_SOURCE_CLOSE:
|
||||
return 0;
|
||||
|
||||
case ZIP_SOURCE_STAT:
|
||||
{
|
||||
auto size = file->size();
|
||||
|
||||
if (size == -1)
|
||||
{
|
||||
error = {ZIP_ER_INTERNAL, 0};
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto stat = static_cast<zip_stat_t*>(data);
|
||||
zip_stat_init(stat);
|
||||
|
||||
stat->valid |= ZIP_STAT_SIZE;
|
||||
stat->size = size;
|
||||
|
||||
return sizeof(*stat);
|
||||
}
|
||||
|
||||
case ZIP_SOURCE_ERROR:
|
||||
{
|
||||
auto error_out = static_cast<std::array<int, 2>*>(data);
|
||||
*error_out = error;
|
||||
return sizeof(*error_out);
|
||||
}
|
||||
|
||||
case ZIP_SOURCE_FREE:
|
||||
delete file;
|
||||
return 0;
|
||||
|
||||
case ZIP_SOURCE_SEEK:
|
||||
{
|
||||
zip_error_t error;
|
||||
auto seek_info = ZIP_SOURCE_GET_ARGS(zip_source_args_seek, data, len, &error);
|
||||
|
||||
if (seek_info == nullptr)
|
||||
{
|
||||
error = {ZIP_ER_INTERNAL, 0};
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (file->seek(seek_info->offset, seek_info->whence) != 0)
|
||||
{
|
||||
error = {ZIP_ER_INTERNAL, 0};
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
case ZIP_SOURCE_TELL:
|
||||
{
|
||||
const auto position = file->tell();
|
||||
|
||||
if (position == -1)
|
||||
{
|
||||
error = {ZIP_ER_INTERNAL, 0};
|
||||
return -1;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
case ZIP_SOURCE_SUPPORTS:
|
||||
return zip_source_make_command_bitmap(
|
||||
ZIP_SOURCE_OPEN,
|
||||
ZIP_SOURCE_READ,
|
||||
ZIP_SOURCE_CLOSE,
|
||||
ZIP_SOURCE_STAT,
|
||||
ZIP_SOURCE_ERROR,
|
||||
ZIP_SOURCE_FREE,
|
||||
ZIP_SOURCE_SEEK,
|
||||
ZIP_SOURCE_TELL,
|
||||
ZIP_SOURCE_SUPPORTS,
|
||||
-1
|
||||
);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Unsupported command.
|
||||
error = {ZIP_ER_OPNOTSUPP, 0};
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool ZipArchive::Open(hostfs::File *file)
|
||||
{
|
||||
zip_error_t error;
|
||||
zip_source_t *source = zip_source_function_create(ZipSourceCallback, file, &error);
|
||||
if (source == nullptr)
|
||||
{
|
||||
delete file;
|
||||
return false;
|
||||
}
|
||||
zip = zip_open_from_source(source, 0, nullptr);
|
||||
if (zip == nullptr)
|
||||
zip_source_free(source);
|
||||
return zip != nullptr;
|
||||
}
|
||||
|
||||
ArchiveFile* ZipArchive::OpenFile(const char* name)
|
||||
{
|
||||
zip_file_t *zip_file = zip_fopen(zip, name, 0);
|
||||
if (zip_file == nullptr)
|
||||
return nullptr;
|
||||
zip_stat_t stat;
|
||||
zip_stat(zip, name, 0, &stat);
|
||||
return new ZipArchiveFile(zip_file, stat.size, stat.name);
|
||||
}
|
||||
|
||||
static zip_file *zip_fopen_by_crc(zip_t *za, u32 crc, int flags, zip_uint64_t& index)
|
||||
{
|
||||
if (crc == 0)
|
||||
return nullptr;
|
||||
|
||||
zip_int64_t n = zip_get_num_entries(za, 0);
|
||||
for (index = 0; index < (zip_uint64_t)n; index++)
|
||||
{
|
||||
zip_stat_t stat;
|
||||
if (zip_stat_index(za, index, flags, &stat) < -1)
|
||||
return nullptr;
|
||||
if (stat.crc == crc)
|
||||
return zip_fopen_index(za, index, flags);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ArchiveFile* ZipArchive::OpenFileByCrc(u32 crc)
|
||||
{
|
||||
zip_uint64_t index;
|
||||
zip_file_t *zip_file = zip_fopen_by_crc(zip, crc, 0, index);
|
||||
if (zip_file == nullptr)
|
||||
return nullptr;
|
||||
zip_stat_t stat;
|
||||
zip_stat_index(zip, index, 0, &stat);
|
||||
|
||||
return new ZipArchiveFile(zip_file, stat.size, stat.name);
|
||||
}
|
||||
|
||||
u32 ZipArchiveFile::Read(void* buffer, u32 length)
|
||||
{
|
||||
return zip_fread(zip_file, buffer, length);
|
||||
}
|
||||
|
||||
bool ZipArchive::Open(const void *data, size_t size)
|
||||
{
|
||||
zip_error_t error;
|
||||
zip_source_t *source = zip_source_buffer_create(data, size, 0, &error);
|
||||
if (source == nullptr)
|
||||
return false;
|
||||
zip = zip_open_from_source(source, 0, nullptr);
|
||||
if (zip == nullptr)
|
||||
zip_source_free(source);
|
||||
return zip != nullptr;
|
||||
}
|
||||
|
||||
ArchiveFile *ZipArchive::OpenFirstFile()
|
||||
{
|
||||
zip_file_t *zipFile = zip_fopen_index(zip, 0, 0);
|
||||
if (zipFile == nullptr)
|
||||
return nullptr;
|
||||
zip_stat_t stat;
|
||||
zip_stat_index(zip, 0, 0, &stat);
|
||||
return new ZipArchiveFile(zipFile, stat.size, stat.name);
|
||||
}
|
||||
|
||||
ArchiveFile *ZipArchive::OpenFileByIndex(size_t index)
|
||||
{
|
||||
zip_file_t *zipFile = zip_fopen_index(zip, index, 0);
|
||||
if (zipFile == nullptr)
|
||||
return nullptr;
|
||||
zip_stat_t stat;
|
||||
zip_stat_index(zip, index, 0, &stat);
|
||||
return new ZipArchiveFile(zipFile, stat.size, stat.name);
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
Created on: Nov 23, 2018
|
||||
|
||||
Copyright 2018 flyinghead
|
||||
|
||||
This file is part of reicast.
|
||||
|
||||
reicast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
reicast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with reicast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "archive.h"
|
||||
#include <zip.h>
|
||||
|
||||
class ZipArchive : public Archive
|
||||
{
|
||||
public:
|
||||
~ZipArchive() override;
|
||||
|
||||
ArchiveFile* OpenFile(const char* name) override;
|
||||
ArchiveFile* OpenFileByCrc(u32 crc) override;
|
||||
|
||||
bool Open(hostfs::File *file) override;
|
||||
bool Open(const void *data, size_t size);
|
||||
|
||||
ArchiveFile *OpenFirstFile();
|
||||
ArchiveFile *OpenFileByIndex(size_t index);
|
||||
|
||||
private:
|
||||
zip_t *zip = nullptr;
|
||||
};
|
||||
|
||||
class ZipArchiveFile : public ArchiveFile
|
||||
{
|
||||
public:
|
||||
ZipArchiveFile(zip_file_t *zip_file, size_t length, const char *name)
|
||||
: zip_file(zip_file), _length(length), name(name) {}
|
||||
~ZipArchiveFile() override {
|
||||
zip_fclose(zip_file);
|
||||
}
|
||||
u32 Read(void* buffer, u32 length) override;
|
||||
size_t length() override {
|
||||
return _length;
|
||||
}
|
||||
const char *getName() override {
|
||||
return name;
|
||||
}
|
||||
|
||||
private:
|
||||
zip_file_t *zip_file;
|
||||
size_t _length;
|
||||
const char *name;
|
||||
};
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
Created on: Nov 23, 2018
|
||||
|
||||
Copyright 2018 flyinghead
|
||||
|
||||
This file is part of reicast.
|
||||
|
||||
reicast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
reicast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with reicast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "archive.h"
|
||||
#include "7zArchive.h"
|
||||
#include "ZipArchive.h"
|
||||
#include "oslib/storage.h"
|
||||
|
||||
Archive *OpenArchive(const std::string& path)
|
||||
{
|
||||
hostfs::File *file = nullptr;
|
||||
hostfs::FileInfo fileInfo;
|
||||
try {
|
||||
fileInfo = hostfs::storage().getFileInfo(path);
|
||||
if (!fileInfo.isDirectory)
|
||||
file = hostfs::storage().openFile(path, "rb");
|
||||
} catch (const hostfs::StorageException& e) {
|
||||
}
|
||||
if (file == nullptr)
|
||||
{
|
||||
file = hostfs::storage().openFile(path + ".7z", "rb");
|
||||
if (file == nullptr)
|
||||
file = hostfs::storage().openFile(path + ".7Z", "rb");
|
||||
}
|
||||
if (file != nullptr)
|
||||
{
|
||||
Archive *sz_archive = new SzArchive();
|
||||
if (sz_archive->Open(file))
|
||||
return sz_archive;
|
||||
delete sz_archive;
|
||||
file = nullptr;
|
||||
}
|
||||
// Retry as a zip file
|
||||
try {
|
||||
if (!fileInfo.isDirectory)
|
||||
file = hostfs::storage().openFile(path, "rb");
|
||||
} catch (const hostfs::StorageException& e) {
|
||||
}
|
||||
if (file == nullptr)
|
||||
{
|
||||
file = hostfs::storage().openFile(path + ".zip", "rb");
|
||||
if (file == nullptr)
|
||||
{
|
||||
file = hostfs::storage().openFile(path + ".ZIP", "rb");
|
||||
if (file == nullptr)
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
Archive *zip_archive = new ZipArchive();
|
||||
if (zip_archive->Open(file))
|
||||
return zip_archive;
|
||||
delete zip_archive;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Archive::Open(const char* path)
|
||||
{
|
||||
hostfs::File *file = hostfs::storage().openFile(path, "rb");
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
return Open(file);
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
Created on: Nov 23, 2018
|
||||
|
||||
Copyright 2018 flyinghead
|
||||
|
||||
This file is part of reicast.
|
||||
|
||||
reicast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
reicast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with reicast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "oslib/storage.h"
|
||||
|
||||
class ArchiveFile
|
||||
{
|
||||
public:
|
||||
virtual ~ArchiveFile() = default;
|
||||
virtual u32 Read(void *buffer, u32 length) = 0;
|
||||
virtual size_t length() = 0;
|
||||
virtual const char *getName() { return nullptr; }
|
||||
};
|
||||
|
||||
class Archive
|
||||
{
|
||||
public:
|
||||
virtual ~Archive() = default;
|
||||
virtual ArchiveFile *OpenFile(const char *name) = 0;
|
||||
virtual ArchiveFile *OpenFileByCrc(u32 crc) = 0;
|
||||
|
||||
protected:
|
||||
virtual bool Open(hostfs::File *file) = 0;
|
||||
|
||||
private:
|
||||
bool Open(const char *name);
|
||||
|
||||
friend Archive *OpenArchive(const std::string& path);
|
||||
};
|
||||
|
||||
Archive *OpenArchive(const std::string& path);
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "rzip.h"
|
||||
#include <zlib.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
const u8 RZipHeader[8] = { '#', 'R', 'Z', 'I', 'P', 'v', 1, '#' };
|
||||
|
||||
bool RZipFile::Open(hostfs::File *file, bool write)
|
||||
{
|
||||
verify(this->file == nullptr);
|
||||
verify(file != nullptr);
|
||||
startOffset = file->tell();
|
||||
if (!write)
|
||||
{
|
||||
u8 header[sizeof(RZipHeader)];
|
||||
if (file->read(header, sizeof(header), 1) != 1
|
||||
|| memcmp(header, RZipHeader, sizeof(header))
|
||||
|| file->read(&maxChunkSize, sizeof(maxChunkSize), 1) != 1
|
||||
|| file->read(&size, sizeof(size), 1) != 1)
|
||||
{
|
||||
file->seek(startOffset, SEEK_SET);
|
||||
return false;
|
||||
}
|
||||
// savestates created on 32-bit platforms used to have a 32-bit size
|
||||
if (size >> 32 != 0)
|
||||
{
|
||||
size &= 0xffffffff;
|
||||
file->seek(-4, SEEK_CUR);
|
||||
}
|
||||
chunk = new u8[maxChunkSize];
|
||||
chunkIndex = 0;
|
||||
chunkSize = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxChunkSize = 1_MB;
|
||||
if (file->write(RZipHeader, sizeof(RZipHeader), 1) != 1
|
||||
|| file->write(&maxChunkSize, sizeof(maxChunkSize), 1) != 1
|
||||
|| file->write(&size, sizeof(size), 1) != 1)
|
||||
{
|
||||
file->seek(startOffset, SEEK_SET);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this->write = write;
|
||||
this->file = file;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RZipFile::Open(const std::string& path, bool write)
|
||||
{
|
||||
hostfs::File *f = hostfs::storage().openFile(path.c_str(), write ? "wb" : "rb");
|
||||
if (f == nullptr)
|
||||
return false;
|
||||
if (!Open(f, write)) {
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RZipFile::Close()
|
||||
{
|
||||
if (file != nullptr)
|
||||
{
|
||||
if (write)
|
||||
{
|
||||
file->seek(startOffset + sizeof(RZipHeader) + sizeof(maxChunkSize), SEEK_SET);
|
||||
file->write(&size, sizeof(size), 1);
|
||||
}
|
||||
delete file;
|
||||
file = nullptr;
|
||||
if (chunk != nullptr)
|
||||
{
|
||||
delete [] chunk;
|
||||
chunk = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t RZipFile::Read(void *data, size_t length)
|
||||
{
|
||||
verify(file != nullptr);
|
||||
verify(!write);
|
||||
|
||||
u8 *p = (u8 *)data;
|
||||
size_t rv = 0;
|
||||
while (rv < length)
|
||||
{
|
||||
if (chunkIndex == chunkSize)
|
||||
{
|
||||
chunkSize = 0;
|
||||
chunkIndex = 0;
|
||||
u32 zippedSize;
|
||||
if (file->read(&zippedSize, sizeof(zippedSize), 1) != 1)
|
||||
break;
|
||||
if (zippedSize == 0)
|
||||
continue;
|
||||
u8 *zipped = new u8[zippedSize];
|
||||
if (file->read(zipped, zippedSize, 1) != 1)
|
||||
{
|
||||
delete [] zipped;
|
||||
break;
|
||||
}
|
||||
uLongf tl = maxChunkSize;
|
||||
if (uncompress(chunk, &tl, zipped, zippedSize) != Z_OK)
|
||||
{
|
||||
delete [] zipped;
|
||||
break;
|
||||
}
|
||||
delete [] zipped;
|
||||
chunkSize = (u32)tl;
|
||||
}
|
||||
u32 l = std::min(chunkSize - chunkIndex, (u32)(length - rv));
|
||||
memcpy(p, chunk + chunkIndex, l);
|
||||
p += l;
|
||||
chunkIndex += l;
|
||||
rv += l;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
size_t RZipFile::Write(const void *data, size_t length)
|
||||
{
|
||||
verify(file != nullptr);
|
||||
verify(write);
|
||||
|
||||
size += length;
|
||||
const u8 *p = (const u8 *)data;
|
||||
// compression output buffer must be 0.1% larger + 12 bytes
|
||||
uLongf maxZippedSize = maxChunkSize + maxChunkSize / 1000 + 12;
|
||||
u8 *zipped = new u8[maxZippedSize];
|
||||
size_t rv = 0;
|
||||
while (rv < length)
|
||||
{
|
||||
uLongf zippedSize = maxZippedSize;
|
||||
uLongf uncompressedSize = std::min(maxChunkSize, (u32)(length - rv));
|
||||
u32 rc = compress(zipped, &zippedSize, p, uncompressedSize);
|
||||
if (rc != Z_OK)
|
||||
{
|
||||
WARN_LOG(SAVESTATE, "Compression error: %d", rc);
|
||||
break;
|
||||
}
|
||||
u32 sz = (u32)zippedSize;
|
||||
if (file->write(&sz, sizeof(sz), 1) != 1
|
||||
|| file->write(zipped, zippedSize, 1) != 1)
|
||||
{
|
||||
rv = 0;
|
||||
break;
|
||||
}
|
||||
p += uncompressedSize;
|
||||
rv += uncompressedSize;
|
||||
}
|
||||
delete [] zipped;
|
||||
|
||||
return rv;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// Implementation of the RZIP stream format as defined by libretro
|
||||
// https://github.com/libretro/libretro-common/blob/master/include/streams/rzip_stream.h
|
||||
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
#include "oslib/storage.h"
|
||||
|
||||
class RZipFile
|
||||
{
|
||||
public:
|
||||
~RZipFile() { Close(); }
|
||||
|
||||
bool Open(const std::string& path, bool write);
|
||||
bool Open(hostfs::File *file, bool write);
|
||||
void Close();
|
||||
size_t Size() const { return size; }
|
||||
size_t Read(void *data, size_t length);
|
||||
size_t Write(const void *data, size_t length);
|
||||
hostfs::File *rawFile() const { return file; }
|
||||
|
||||
private:
|
||||
hostfs::File *file = nullptr;
|
||||
u64 size = 0;
|
||||
u32 maxChunkSize = 0;
|
||||
u8 *chunk = nullptr;
|
||||
u32 chunkSize = 0;
|
||||
u32 chunkIndex = 0;
|
||||
bool write = false;
|
||||
long startOffset = 0;
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* E_Branches.h
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
EAPI B(u32 sImm24, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0A000000);
|
||||
|
||||
SET_CC;
|
||||
I |= ((sImm24>>2)&0xFFFFFF);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI BL(u32 sImm24, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0B000000);
|
||||
|
||||
SET_CC;
|
||||
I |= ((sImm24>>2)&0xFFFFFF);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
// Note: Either X variant will switch to THUMB* if bit0 of addr is 1
|
||||
//
|
||||
|
||||
EAPI BX(eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x012FFF10);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI BLX(eReg Rm, ConditionCode CC=AL) // Form II
|
||||
{
|
||||
DECL_Id(0x012FFF30);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI BXJ(eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x012FFF20);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// This encoding looks correct, but segfaults, the pc val is align(pc,4) but this should be right in ARM
|
||||
//
|
||||
#if defined(_DEVEL)
|
||||
EAPI BLX(u32 sImm24, bool toThumb) // Form I * H is derived so not needed, fixup sImm24 so one can just pass a real addr
|
||||
{
|
||||
DECL_Id(0xFA000000);
|
||||
|
||||
if(toThumb)
|
||||
I |= 1<<24; // SET_H
|
||||
|
||||
I |= ((sImm24>>2)&0xFFFFFF);
|
||||
EMIT_I;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
Executable
+844
@@ -0,0 +1,844 @@
|
||||
/*
|
||||
* <E_DataOp.h Title="ARMv7 ISA Emitter Middle-ware: Data-Processing Instructions" />
|
||||
*
|
||||
* <?CTYPE
|
||||
*
|
||||
* <opcode1>{<cond>}{S} <Rd>, <shifter_operand>
|
||||
* <opcode1> := MOV | MVN
|
||||
*
|
||||
* <opcode2>{<cond>} <Rn>, <shifter_operand>
|
||||
* <opcode2> := CMP | CMN | TST | TEQ
|
||||
*
|
||||
* <opcode3>{<cond>}{S} <Rd>, <Rn>, <shifter_operand>
|
||||
* <opcode3> := ADD | SUB | RSB | ADC | SBC | RSC | AND | BIC | EOR | ORR
|
||||
*
|
||||
* ?/>
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* imm Rd,[Rn,] imm
|
||||
* reg Rd,Rn,Rm shift
|
||||
* rsr Rd,Rn,Rm type Rs
|
||||
*
|
||||
* sp.imm Rd {SP} imm
|
||||
* sp.reg Rd {SP} Rm shift
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#if 0
|
||||
#define dpInstr(iName, iId) \
|
||||
EAPI iName (eReg Rd, eReg Rn, u32 Imm) ; \
|
||||
EAPI iName (eReg Rd, eReg Rn, eReg Rm, eShiftOp type=S_LSL, u32 Imm=0) ; \
|
||||
EAPI iName (eReg Rd, eReg Rn, eReg Rm, eShiftOp type, eReg Rs) ;
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
||||
ADC IMM 0x02A00000
|
||||
ADC REG 0x00A00000
|
||||
ADC RSR 0x00A00010
|
||||
|
||||
ADD IMM 0x02800000
|
||||
ADD REG 0x00800000
|
||||
ADD RSR 0x00800010
|
||||
ADD.SP.IMM 0x028D0000
|
||||
ADD.SP.REG 0x008D0000
|
||||
|
||||
AND IMM 0x02000000
|
||||
AND REG 0x00000000
|
||||
AND RSR 0x00000010
|
||||
|
||||
|
||||
// ASR's do not fit this pattern moved elsewhere //
|
||||
|
||||
|
||||
|
||||
BIC IMM 0x03C00000
|
||||
BIC REG 0x01C00000
|
||||
BIC RSR 0x01C00010
|
||||
|
||||
CMN IMM 0x03700000 // N imm
|
||||
CMN REG 0x01700000 // NMshift
|
||||
CMN RSR 0x01700010 // NMtypeS
|
||||
|
||||
CMP IMM 0x03500000 // N imm
|
||||
CMP REG 0x01500000
|
||||
CMP RSR 0x01500010
|
||||
|
||||
|
||||
EOR IMM 0x02200000
|
||||
EOR REG 0x00200000
|
||||
EOR REG 0x00200010
|
||||
|
||||
|
||||
*/
|
||||
|
||||
#define DP_PARAMS (eReg Rd, eReg Rn, ShiftOp Shift, ConditionCode CC=AL)
|
||||
#define DP_RPARAMS (eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
|
||||
#define DP_COMMON \
|
||||
DECL_I; \
|
||||
\
|
||||
SET_CC; \
|
||||
I |= (Rn&15)<<16; \
|
||||
I |= (Rd&15)<<12; \
|
||||
I |= (Shift&0xFFF)
|
||||
|
||||
#define DP_RCOMMON \
|
||||
DECL_I; \
|
||||
\
|
||||
SET_CC; \
|
||||
I |= (Rn&15)<<16; \
|
||||
I |= (Rd&15)<<12; \
|
||||
I |= (Rm&15)
|
||||
|
||||
#define DP_OPCODE(opcode) \
|
||||
I |= (opcode)<<21
|
||||
|
||||
|
||||
|
||||
EAPI AND DP_PARAMS { DP_COMMON; DP_OPCODE(DP_AND); EMIT_I; }
|
||||
EAPI EOR DP_PARAMS { DP_COMMON; DP_OPCODE(DP_EOR); EMIT_I; }
|
||||
EAPI SUB DP_PARAMS { DP_COMMON; DP_OPCODE(DP_SUB); EMIT_I; }
|
||||
EAPI RSB DP_PARAMS { DP_COMMON; DP_OPCODE(DP_RSB); EMIT_I; }
|
||||
EAPI ADD DP_PARAMS { DP_COMMON; DP_OPCODE(DP_ADD); EMIT_I; }
|
||||
EAPI ADC DP_PARAMS { DP_COMMON; DP_OPCODE(DP_ADC); EMIT_I; }
|
||||
EAPI SBC DP_PARAMS { DP_COMMON; DP_OPCODE(DP_SBC); EMIT_I; }
|
||||
EAPI RSC DP_PARAMS { DP_COMMON; DP_OPCODE(DP_RSC); EMIT_I; }
|
||||
EAPI TST DP_PARAMS { DP_COMMON; DP_OPCODE(DP_TST); EMIT_I; }
|
||||
EAPI TEQ DP_PARAMS { DP_COMMON; DP_OPCODE(DP_TEQ); EMIT_I; }
|
||||
// EAPI CMP DP_PARAMS { DP_COMMON; DP_OPCODE(DP_CMP); EMIT_I; }
|
||||
EAPI CMN DP_PARAMS { DP_COMMON; DP_OPCODE(DP_CMN); EMIT_I; }
|
||||
EAPI ORR DP_PARAMS { DP_COMMON; DP_OPCODE(DP_ORR); EMIT_I; }
|
||||
EAPI MOV DP_PARAMS { DP_COMMON; DP_OPCODE(DP_MOV); EMIT_I; }
|
||||
EAPI BIC DP_PARAMS { DP_COMMON; DP_OPCODE(DP_BIC); EMIT_I; }
|
||||
EAPI MVN DP_PARAMS { DP_COMMON; DP_OPCODE(DP_MVN); EMIT_I; }
|
||||
|
||||
#if defined(_DEVEL) && defined(_NODEF_) // These require testing -> CMP/MOV Shifter(Reg)? fmt broken? // Simple third reg type w/ no shifter
|
||||
EAPI AND DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_AND); EMIT_I; }
|
||||
EAPI EOR DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_EOR); EMIT_I; }
|
||||
EAPI SUB DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_SUB); EMIT_I; }
|
||||
EAPI RSB DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_RSB); EMIT_I; }
|
||||
EAPI ADD DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_ADD); EMIT_I; }
|
||||
EAPI ADC DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_ADC); EMIT_I; }
|
||||
EAPI SBC DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_SBC); EMIT_I; }
|
||||
EAPI RSC DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_RSC); EMIT_I; }
|
||||
EAPI TST DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_TST); EMIT_I; }
|
||||
EAPI TEQ DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_TEQ); EMIT_I; }
|
||||
EAPI CMP DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_CMP); EMIT_I; }
|
||||
EAPI CMN DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_CMN); EMIT_I; }
|
||||
EAPI ORR DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_ORR); EMIT_I; }
|
||||
EAPI MOV DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_MOV); EMIT_I; }
|
||||
EAPI BIC DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_BIC); EMIT_I; }
|
||||
EAPI MVN DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_MVN); EMIT_I; }
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
static u32 ARMImmid8r4_enc(u32 imm32)
|
||||
{
|
||||
for (int i=0;i<=30;i+=2)
|
||||
{
|
||||
u32 immv=(imm32<<i) | (imm32>>(32-i));
|
||||
if (immv<256)
|
||||
{
|
||||
return ((i/2)<<8) | immv;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static u32 ARMImmid8r4(u32 imm8r4)
|
||||
{
|
||||
u32 rv = ARMImmid8r4_enc(imm8r4);
|
||||
verify(rv!=-1);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static bool is_i8r4(u32 i32) { return ARMImmid8r4_enc(i32) != -1; }
|
||||
|
||||
|
||||
|
||||
EAPI ADD(eReg Rd, eReg Rn, eReg Rm, u32 RmLSL, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00800000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
I |= (RmLSL&31)<<7;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ADD(eReg Rd, eReg Rn, eReg Rm, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
ADD(Rd,Rn,Rm,0,S,CC);
|
||||
}
|
||||
|
||||
EAPI ADD(eReg Rd, eReg Rn, eReg Rm, ShiftOp Shift, u32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00800000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= Shift<<5;
|
||||
I |= (Imm8&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ADD(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
ADD(Rd,Rn,Rm,false,CC);
|
||||
}
|
||||
|
||||
|
||||
EAPI ADD(eReg Rd, eReg Rn, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x02800000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ADC(eReg Rd, eReg Rn, eReg Rm, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00A00000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ADC(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
ADC(Rd,Rn,Rm,false,CC);
|
||||
}
|
||||
|
||||
EAPI ADC(eReg Rd, eReg Rn, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x02A00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ADC(eReg Rd, eReg Rn, eReg Rm, bool S, ShiftOp Shift, u32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00A00000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= Shift<<5;
|
||||
I |= (Imm8&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
EAPI ADR(eReg Rd, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x028F0000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ADR_Zero(eReg Rd, s32 Imm8, ConditionCode CC=AL) // Special case for subtraction of 0
|
||||
{
|
||||
DECL_Id(0x024F0000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI ORR(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01800000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ORR(eReg Rd, eReg Rn, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03800000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ORR(eReg Rd, eReg Rn, eReg Rm, ShiftOp Shift, eReg Rs, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01800000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= Shift<<5;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= 1<<4;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ORR(eReg Rd, eReg Rn, eReg Rm, bool S, ShiftOp Shift, u32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01800000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= Shift<<5;
|
||||
I |= (Imm8&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI AND(eReg Rd, eReg Rn, eReg Rm, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00000000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI AND(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00000000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI AND(eReg Rd, eReg Rn, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x02000000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI AND(eReg Rd, eReg Rn, s32 Imm8, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x02000000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI EOR(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00200000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI EOR(eReg Rd, eReg Rn, eReg Rm, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00200000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI EOR(eReg Rd, eReg Rn, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x02200000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
EAPI SUB(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00400000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SUB(eReg Rd, eReg Rn, s32 Imm8, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x02400000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
EMIT_I;
|
||||
}
|
||||
EAPI SUB(eReg Rd, eReg Rn, s32 Imm8, ConditionCode CC=AL) { SUB(Rd,Rn,Imm8,false,CC); }
|
||||
|
||||
EAPI SBC(eReg Rd, eReg Rn, eReg Rm, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00C00000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SBC(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
SBC(Rd,Rn,Rm,false,CC);
|
||||
}
|
||||
|
||||
EAPI RSB(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00600000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI RSB(eReg Rd, eReg Rn, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x02600000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI RSB(eReg Rd, eReg Rn, s32 Imm8, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x02600000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI MVN(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01E00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI MVN(eReg Rd, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03E00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI TST(eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01100000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI TST(eReg Rn, u32 Imm12, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03100000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= ARMImmid8r4(Imm12);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI BIC(eReg Rd, eReg Rn, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03C00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
||||
EAPI UBFX(eReg Rd, eReg Rm, u8 lsb, u8 width, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x07E00050);
|
||||
verify(lsb+width<=32);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= (lsb&31)<<7;
|
||||
I |= ((width-1)&31)<<16;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI MOV(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI MOV(eReg Rd, eReg Rm, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI MOV(eReg Rd, eReg Rm, ShiftOp Shift, u32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= Shift<<5;
|
||||
I |= (Imm8&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI MOV(eReg Rd, eReg Rm, ShiftOp Shift, eReg Rs, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= Shift<<5;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= 1<<4;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI MOVW(eReg Rd, u32 Imm16, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03000000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Imm16&0xF000)<<4;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Imm16&0x0FFF);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI MOVT(eReg Rd, u32 Imm16, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03400000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Imm16&0xF000)<<4;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Imm16&0x0FFF);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI MOV(eReg Rd, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03A00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
EAPI CMP(eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01500000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI CMP(eReg Rn, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03500000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= ARMImmid8r4(Imm8); // *FIXME* 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI CMP(eReg Rd, eReg Rm, ShiftOp Shift, eReg Rs, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01500000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= Shift<<5;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= 1<<4;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI CMP(eReg Rd, eReg Rm, ShiftOp Shift, u32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01500000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= Shift<<5;
|
||||
I |= (Imm8&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI LSL(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00010);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<8;
|
||||
I |= (Rn&15)<<0;
|
||||
EMIT_I;
|
||||
}
|
||||
EAPI LSL(eReg Rd, eReg Rm, s32 imm5, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<0;
|
||||
I |= (imm5&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI LSR(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00030);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<8;
|
||||
I |= (Rn&15)<<0;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI RRX(eReg Rd, eReg Rm,bool S=false, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00060);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<0;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI LSR(eReg Rd, eReg Rm, s32 imm5, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00020);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<0;
|
||||
I |= (imm5&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI LSR(eReg Rd, eReg Rm, s32 imm5, ConditionCode CC=AL)
|
||||
{
|
||||
LSR(Rd,Rm,imm5,false,CC);
|
||||
}
|
||||
|
||||
|
||||
EAPI ASR(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00050);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<8;
|
||||
I |= (Rn&15)<<0;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ASR(eReg Rd, eReg Rm, s32 imm5, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00040);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<0;
|
||||
I |= (imm5&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ROR(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<8;
|
||||
I |= (Rn&15)<<0;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ROR(eReg Rd, eReg Rm, s32 imm5, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00060);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<0;
|
||||
I |= (imm5&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#undef DP_PARAMS
|
||||
#undef DP_RPARAMS
|
||||
|
||||
#undef DP_COMMON
|
||||
#undef DP_RCOMMON
|
||||
|
||||
#undef DP_OPCODE
|
||||
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* E_Extend.h
|
||||
*
|
||||
|
||||
There are six basic instructions:
|
||||
XTAB16 Extend bits[23:16] and bits[7:0] of one register to 16 bits, and add corresponding halfwordsto the values in another register.
|
||||
XTAB Extend bits[ 7: 0] of one register to 32 bits, and add to the value in another register.
|
||||
XTAH Extend bits[15: 0] of one register to 32 bits, and add to the value in another register.
|
||||
XTB16 Extend bits[23:16] and bits[7:0] to 16 bits each.
|
||||
XTB Extend bits[ 7: 0] to 32 bits.
|
||||
XTH Extend bits[15: 0] to 32 bits.
|
||||
|
||||
Each of the six instructions is available in the following variations, indicated by the prefixes shown:
|
||||
S Sign extension, with or without addition modulo 216 or 232.
|
||||
U Zero (unsigned) extension, with or without addition modulo 216 or 232.
|
||||
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
EAPI SXTAB16(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06800070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SXTAB(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06A00070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SXTAH(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06B00070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SXTB16(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x068F0070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SXTB(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06AF0070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SXTH(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06BF0070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI UXTAB16(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06C00070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI UXTAB(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06E00070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI UXTAH(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06F00070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI UXTB16(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06CF0070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI UXTB(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06EF0070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI UXTH(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06FF0070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* E_LoadStore.h
|
||||
*
|
||||
* LDR|STR{<cond>}{B}{T} Rd, <addressing_mode>
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
|
||||
enum AddrMode {
|
||||
|
||||
Offset, // [Rn, offset] - Base:Offset used for access, no writeback
|
||||
PostIndexed, // [Rn, offset] ! - Base:Offset used for access, written back to Base reg.
|
||||
PreIndexed // [Rn],offset - Base used then Base:Offset written back to Base reg.
|
||||
|
||||
// offset: imm{8,12}, Rm, Rm <Shifter> #Shift
|
||||
};
|
||||
|
||||
|
||||
#define SET_Rn I |= (Rn&15)<<16
|
||||
#define SET_Rt I |= (Rt&15)<<12
|
||||
#define SET_Rm I |= (Rm&15)
|
||||
|
||||
|
||||
#define SET_Rtn SET_Rt; SET_Rn
|
||||
#define SET_Rtnm SET_Rt; SET_Rn; SET_Rm
|
||||
|
||||
|
||||
|
||||
#define SET_I I |= (1<<25) // Sets Register (Rather than Immediate) Addressing
|
||||
#define SET_P I |= (1<<24) // If NOT Set: post-indexed, else offset||pre-indexed (W. determines which)
|
||||
#define SET_U I |= (1<<23) // If SET: Offset is added to base, else its subtracted (Sign of sImm12)
|
||||
#define SET_B I |= (1<<22) // If SET: Access is a byte access, else its a word access.
|
||||
#define SET_W I |= (1<<21) // (P==0) [ W==0: reg. post indexed, W==1: Unprivileged T variant ] (P==1) [ W==0: Offset , W==1: Pre-indexed ]
|
||||
#define SET_L I |= (1<<20) // (L==1) Load / Store
|
||||
|
||||
|
||||
#define SET_AddrMode \
|
||||
if(mode==Offset) { SET_P; } \
|
||||
else if(mode==PreIndexed) { SET_P; SET_W; }
|
||||
|
||||
/*
|
||||
#define SET_AddrMode \
|
||||
\
|
||||
switch(mode) { \
|
||||
case PostIndexed: break; \
|
||||
case Offset: SET_P; break; \
|
||||
case PreIndexed: SET_P; SET_W; break; \
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
#define SET_sImm12 \
|
||||
\
|
||||
if (0 != sImm12) { \
|
||||
if (sImm12 > 0) { SET_U; } \
|
||||
I |= (abs(sImm12) &0xFFF); \
|
||||
}
|
||||
|
||||
#define SET_sImm8 \
|
||||
\
|
||||
if(0 != sImm8) { \
|
||||
verify(is_s8(sImm8)); \
|
||||
if(sImm8 > 0) { SET_U; } \
|
||||
sImm8 = abs(sImm8); \
|
||||
I |= ((u32)sImm8 &0x0F); \
|
||||
I |= ((u32)sImm8 &0xF0)<<4; \
|
||||
}
|
||||
|
||||
|
||||
#define SET_Imm5 I |= ((Imm5&0x1F)<<7)
|
||||
#define SET_type I |= ((type&3)<<5)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* - Load/Store Encoding
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
cond 01-IPUBWL Rn Rt addr_mode -- Load/Store Word || Unsigned Byte
|
||||
|
||||
I,P,U,W - Specify type of addressing mode
|
||||
L - Specify Load (L==1), else Store
|
||||
B - Specify Byte (B==1), else Word
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
cond 000-PUIWL Rn Rt addr_mode 1SH1 addr_mode -- Load/Store Halfword || Signed byte
|
||||
|
||||
I,P,U,W - Specify type of addressing mode
|
||||
L - Specify Load (L==1), else Store
|
||||
S - Specify signed (S==1), else unsigned access
|
||||
H - Specify Halfword access, else signed byte
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
cond 010-PUBWL Rn Rt sImm12-iiii-iiii Imm Offset
|
||||
cond 011-PUBWL Rn Rt #sft sft_op 0 Rm Reg Offset
|
||||
|
||||
cond 000 PU1WL Rn Rt imHI 1011 imLO Imm Offset - halfword
|
||||
cond 000 PU0WL Rn Rt SBZ 1011 Rm Reg Offset - halfword
|
||||
cond 000 PU1W1 Rn Rt imHI 11H1 imLO Imm Offset - signed halfword/byte
|
||||
cond 000 PU0W1 Rn Rt SBZ 11H1 Rm Reg Offset - signed halfword/byte
|
||||
cond 000 PU1W0 Rn Rt imHI 11S1 imLO Imm Offset - two words
|
||||
cond 000 PU0W0 Rn Rt SBZ 11S1 Rm Reg Offset - two words
|
||||
|
||||
cond 100-PUSWL Rn Register-list Multiple
|
||||
cond 110-PUNWL Rn CRd cp_num offs8 COP & double reg xfer
|
||||
|
||||
cond 0001 0B00 Rn Rt SBZ 1001 Rm Swap [byte]
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
LDR L 010 1U001 11 Rt imm12 - *no literal stores*
|
||||
LDR I 010 PU0W1 Rn Rt imm12 - STR I 010 PU0W0 Rn Rt imm12
|
||||
LDR R 011 PU0W1 Rn Rt imm5 type 0 Rm - STR R 011 PU0W0 Rn Rt imm5 type 0 Rm
|
||||
|
||||
LDRT I 010 0U011 Rn Rt imm12 - STRT I 010 0U010 Rn Rt imm12
|
||||
LDRT R 011 0U011 Rn Rt imm5 type 0 Rm - STRT R 011 0U010 Rn Rt imm5 type 0 Rm
|
||||
|
||||
LDRB L 010 1U101 11 Rt imm12 - *^^
|
||||
LDRB I 010 PU1W1 Rn Rt imm12 - STRB I 010 PU1W0 Rn Rt imm12
|
||||
LDRB R 011 PU1W1 Rn Rt imm5 type 0 Rm - STRB R 011 PU1W0 Rn Rt imm5 type 0 Rm
|
||||
|
||||
LDRBT I
|
||||
LDRBT R
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
LDRH L 000 1U101 11 Rt imm4H 1011 imm4L - *^^
|
||||
LDRH I 000 PU1W1 Rn Rt imm4H 1011 imm4L - STRH I 000 PU1W0 Rn Rt imm4H 1011 imm4L
|
||||
LDRH R 000 PU0W1 Rn Rt 0000 1011 Rm - STRH R 000 PU0W0 Rn Rt 0000 1011 Rm
|
||||
|
||||
LDRD L 000 1U100 11 Rt imm4H 1101 imm4L - *^^
|
||||
LDRD I 000 PU1W0 Rn Rt imm4H 1101 imm4L - STRD I 000 PU1W0 Rn Rt imm4H 1111 imm4L
|
||||
LDRD R 000 PU0W0 Rn Rt 0000 1101 Rm - STRD R 000 PU0W0 Rn Rt 0000 1111 Rm
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
LDRHT/STRHT
|
||||
|
||||
LDREX/STREX
|
||||
LDREXB/STREXB
|
||||
LDREXH/STREXH
|
||||
LDREXD/STREXD
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
EAPI LDR 0x04100000 // These were all the A1 Versions for Imm IIRC
|
||||
EAPI LDRB 0x04500000
|
||||
EAPI LDRBT 0x06700000
|
||||
EAPI LDRD 0x00000000
|
||||
EAPI LDREX 0x01900090
|
||||
EAPI LDRH 0x00100090
|
||||
EAPI LDRSB 0x00100000
|
||||
EAPI LDRSH 0x00100000
|
||||
EAPI LDRT 0x04300000
|
||||
|
||||
EAPI STR 0x04000000
|
||||
EAPI STRB 0x04400000
|
||||
EAPI STRBT 0x06600000
|
||||
EAPI STRD 0x00000000
|
||||
EAPI STREX 0x01800090
|
||||
EAPI STRH 0x00000090
|
||||
EAPI STRT 0x04200000
|
||||
|
||||
*/
|
||||
|
||||
|
||||
extern u8* emit_opt;
|
||||
extern eReg reg_addr;
|
||||
extern eReg reg_dst;
|
||||
extern s32 imma;
|
||||
|
||||
EAPI LDR (eReg Rt, eReg Rn, s32 sImm12=0, AddrMode mode=Offset, ConditionCode CC=AL)
|
||||
{
|
||||
if (emit_opt+4==(u8*)EMIT_GET_PTR() && reg_addr==Rn && imma==sImm12)
|
||||
{
|
||||
if (reg_dst!=Rt)
|
||||
MOV(Rt,reg_dst);
|
||||
}
|
||||
else
|
||||
{
|
||||
DECL_Id(0x04100000); SET_CC; SET_Rtn; SET_AddrMode; SET_sImm12; EMIT_I;
|
||||
}
|
||||
}
|
||||
EAPI STR (eReg Rt, eReg Rn, s32 sImm12=0, AddrMode mode=Offset, ConditionCode CC=AL)
|
||||
{
|
||||
emit_opt=0;//(u8*)EMIT_GET_PTR();
|
||||
reg_addr=Rn;
|
||||
reg_dst=Rt;
|
||||
imma=sImm12;
|
||||
DECL_Id(0x04000000); SET_CC; SET_Rtn; SET_AddrMode; SET_sImm12; EMIT_I;
|
||||
}
|
||||
EAPI LDRB(eReg Rt, eReg Rn, s32 sImm12=0, AddrMode mode=Offset, ConditionCode CC=AL) { DECL_Id(0x04500000); SET_CC; SET_Rtn; SET_AddrMode; SET_sImm12; SET_B; EMIT_I; } // Prob don't need SET_B, in iID
|
||||
EAPI STRB(eReg Rt, eReg Rn, s32 sImm12=0, AddrMode mode=Offset, ConditionCode CC=AL) { DECL_Id(0x04400000); SET_CC; SET_Rtn; SET_AddrMode; SET_sImm12; SET_B; EMIT_I; } // Prob don't need SET_B, in iID
|
||||
|
||||
EAPI LDRT (eReg Rt, eReg Rn, s32 sImm12=0, ConditionCode CC=AL) { DECL_Id(0x04300000); SET_CC; SET_Rtn; SET_sImm12; EMIT_I; }
|
||||
EAPI STRT (eReg Rt, eReg Rn, s32 sImm12=0, ConditionCode CC=AL) { DECL_Id(0x04200000); SET_CC; SET_Rtn; SET_sImm12; EMIT_I; }
|
||||
EAPI LDRBT(eReg Rt, eReg Rn, s32 sImm12=0, ConditionCode CC=AL) { DECL_Id(0x04700000); SET_CC; SET_Rtn; SET_sImm12; SET_B; EMIT_I; } // Prob don't need SET_B, in iID
|
||||
EAPI STRBT(eReg Rt, eReg Rn, s32 sImm12=0, ConditionCode CC=AL) { DECL_Id(0x04600000); SET_CC; SET_Rtn; SET_sImm12; SET_B; EMIT_I; } // Prob don't need SET_B, in iID
|
||||
|
||||
// LDR(r1,r2,r3, Offset, true, L_LSL, 5, EQ); ... LDR r1, r2, +r3 LSL #5
|
||||
EAPI LDR (eReg Rt, eReg Rn, eReg Rm, AddrMode mode=Offset, bool Add=false, ShiftOp type=S_LSL, u32 Imm5=0, ConditionCode CC=AL) { DECL_Id(0x06100000); SET_CC; SET_Rtnm; SET_AddrMode; SET_Imm5; SET_type; if(Add){ SET_U; } EMIT_I; }
|
||||
EAPI STR (eReg Rt, eReg Rn, eReg Rm, AddrMode mode=Offset, bool Add=false, ShiftOp type=S_LSL, u32 Imm5=0, ConditionCode CC=AL) { DECL_Id(0x06000000); SET_CC; SET_Rtnm; SET_AddrMode; SET_Imm5; SET_type; if(Add){ SET_U; } EMIT_I; }
|
||||
EAPI LDRB(eReg Rt, eReg Rn, eReg Rm, AddrMode mode=Offset, bool Add=false, ShiftOp type=S_LSL, u32 Imm5=0, ConditionCode CC=AL) { DECL_Id(0x06500000); SET_CC; SET_Rtnm; SET_AddrMode; SET_Imm5; SET_type; if(Add){ SET_U; } SET_B; EMIT_I; } // Prob don't need SET_B, in iID
|
||||
EAPI STRB(eReg Rt, eReg Rn, eReg Rm, AddrMode mode=Offset, bool Add=false, ShiftOp type=S_LSL, u32 Imm5=0, ConditionCode CC=AL) { DECL_Id(0x06400000); SET_CC; SET_Rtnm; SET_AddrMode; SET_Imm5; SET_type; if(Add){ SET_U; } SET_B; EMIT_I; } // Prob don't need SET_B, in iID
|
||||
|
||||
EAPI LDRT (eReg Rt, eReg Rn, eReg Rm, bool Add=false, ShiftOp type=S_LSL, u32 Imm5=0, ConditionCode CC=AL) { DECL_Id(0x06300000); SET_CC; SET_Rtnm; SET_Imm5; SET_type; if(Add){ SET_U; } EMIT_I; }
|
||||
EAPI STRT (eReg Rt, eReg Rn, eReg Rm, bool Add=false, ShiftOp type=S_LSL, u32 Imm5=0, ConditionCode CC=AL) { DECL_Id(0x06200000); SET_CC; SET_Rtnm; SET_Imm5; SET_type; if(Add){ SET_U; } EMIT_I; }
|
||||
EAPI LDRBT(eReg Rt, eReg Rn, eReg Rm, bool Add=false, ShiftOp type=S_LSL, u32 Imm5=0, ConditionCode CC=AL) { DECL_Id(0x06700000); SET_CC; SET_Rtnm; SET_Imm5; SET_type; if(Add){ SET_U; } SET_B; EMIT_I; } // Prob don't need SET_B, in iID
|
||||
EAPI STRBT(eReg Rt, eReg Rn, eReg Rm, bool Add=false, ShiftOp type=S_LSL, u32 Imm5=0, ConditionCode CC=AL) { DECL_Id(0x06600000); SET_CC; SET_Rtnm; SET_Imm5; SET_type; if(Add){ SET_U; } SET_B; EMIT_I; } // Prob don't need SET_B, in iID
|
||||
|
||||
|
||||
|
||||
// LDR Rt,[PC, #(s:+/-)Imm12] *Special Case - Literal / PC Relative
|
||||
EAPI LDR(eReg Rt, s32 sImm12, ConditionCode CC=AL) {
|
||||
DECL_Id(0x051F0000); SET_CC; SET_Rt; SET_sImm12; EMIT_I;
|
||||
}
|
||||
|
||||
// LDRB Rt,[PC, #(s:+/-)Imm12] *Special Case - Literal / PC Relative
|
||||
EAPI LDRB(eReg Rt, s32 sImm12, ConditionCode CC=AL) {
|
||||
DECL_Id(0x055F0000); SET_CC; SET_Rt; SET_sImm12; EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Note: Following support Post-Indexed addressing only //
|
||||
|
||||
|
||||
EAPI LDRH(eReg Rt, eReg Rn, s32 sImm8, ConditionCode CC=AL) { DECL_Id(0x005000B0); SET_CC; SET_Rtn; SET_P; SET_sImm8; EMIT_I; }
|
||||
EAPI STRH(eReg Rt, eReg Rn, s32 sImm8, ConditionCode CC=AL) { DECL_Id(0x004000B0); SET_CC; SET_Rtn; SET_P; SET_sImm8; EMIT_I; }
|
||||
EAPI LDRD(eReg Rt, eReg Rn, s32 sImm8, ConditionCode CC=AL) { DECL_Id(0x004000D0); SET_CC; SET_Rtn; SET_P; SET_sImm8; EMIT_I; }
|
||||
EAPI STRD(eReg Rt, eReg Rn, s32 sImm8, ConditionCode CC=AL) { DECL_Id(0x004000F0); SET_CC; SET_Rtn; SET_P; SET_sImm8; EMIT_I; }
|
||||
|
||||
EAPI LDRH(eReg Rt, eReg Rn, eReg Rm, bool Add=true,ConditionCode CC=AL) { DECL_Id(0x001000B0); SET_CC; SET_Rtnm; SET_P; if (Add) {SET_U;} EMIT_I; }
|
||||
EAPI STRH(eReg Rt, eReg Rn, eReg Rm, bool Add=true,ConditionCode CC=AL) { DECL_Id(0x000000B0); SET_CC; SET_Rtnm; SET_P; if (Add) {SET_U;} EMIT_I; }
|
||||
EAPI LDRD(eReg Rt, eReg Rn, eReg Rm, bool Add=true, ConditionCode CC=AL) { DECL_Id(0x000000D0); SET_CC; SET_Rtnm; SET_P; if (Add) {SET_U;} EMIT_I; }
|
||||
EAPI STRD(eReg Rt, eReg Rn, eReg Rm, bool Add=true, ConditionCode CC=AL) { DECL_Id(0x000000F0); SET_CC; SET_Rtnm; SET_P; if (Add) {SET_U;} EMIT_I; }
|
||||
|
||||
EAPI LDRSB(eReg Rt, eReg Rn, eReg Rm, bool Add=true,ConditionCode CC=AL) { DECL_Id(0x001000D0); SET_CC; SET_Rtnm; SET_P; if (Add) {SET_U;} EMIT_I; }
|
||||
EAPI LDRSH(eReg Rt, eReg Rn, eReg Rm, bool Add=true,ConditionCode CC=AL) { DECL_Id(0x001000F0); SET_CC; SET_Rtnm; SET_P; if (Add) {SET_U;} EMIT_I; }
|
||||
|
||||
|
||||
EAPI LDRH(eReg Rt, s32 sImm8, ConditionCode CC=AL) { DECL_Id(0x015F00B0); SET_CC; SET_Rt; SET_sImm8; EMIT_I; } // *Special Case - Literal / PC Relative
|
||||
EAPI STRH(eReg Rt, s32 sImm8, ConditionCode CC=AL) { DECL_Id(0x014F00D0); SET_CC; SET_Rt; SET_sImm8; EMIT_I; } // *Special Case - Literal / PC Relative
|
||||
|
||||
|
||||
|
||||
// TODO: {LD,ST}R{SB,EX*} && friends (If required).
|
||||
|
||||
|
||||
// Must use _Reg format
|
||||
EAPI PUSH(u32 RegList, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x092D0000);
|
||||
|
||||
SET_CC;
|
||||
I |= (RegList&0xFFFF);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI POP(u32 RegList, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x08BD0000);
|
||||
|
||||
SET_CC;
|
||||
I |= (RegList&0xFFFF);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
#undef SET_Rtn
|
||||
#undef SET_Rtnm
|
||||
|
||||
#undef SET_Rn
|
||||
#undef SET_Rt
|
||||
#undef SET_Rm
|
||||
|
||||
#undef SET_I
|
||||
#undef SET_P
|
||||
#undef SET_U
|
||||
#undef SET_B
|
||||
#undef SET_W
|
||||
#undef SET_L
|
||||
|
||||
#undef SET_AddrMode
|
||||
|
||||
#undef SET_sImm12
|
||||
#undef SET_sImm8
|
||||
#undef SET_Imm5
|
||||
#undef SET_type
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* E_Misc.h
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
/*
|
||||
* Misc. Arithmetic Instructions
|
||||
*/
|
||||
|
||||
// Count Leading Zero's
|
||||
//
|
||||
EAPI CLZ(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x016F0F10);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
// Unsigned sum of absolute differences
|
||||
//
|
||||
EAPI USAD8(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0780F010);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<16;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI USADA8(eReg Rd, eReg Rm, eReg Rs, eReg Rn, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x07800010);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<16;
|
||||
I |= (Rn&15)<<12;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Packing Instructions
|
||||
*/
|
||||
|
||||
|
||||
EAPI PKHBT(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL) // * shift_imm
|
||||
{
|
||||
DECL_Id(0x06800010);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI PKHTB(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL) // * shift_imm
|
||||
{
|
||||
DECL_Id(0x06800050);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Swapping Instructions
|
||||
*/
|
||||
|
||||
EAPI REV(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06BF0F30);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI REV16(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06BF0FB0);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI REVSH(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06FF0F30);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI SEL(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06800FB0);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Saturate Instructions
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// SSAT{<cond>} <Rd>, #<immed>, <Rm>{, <shift>}
|
||||
//
|
||||
EAPI SSAT(eReg Rd, u32 sat_imm, eReg Rm, u32 sft_imm, ConditionCode CC=AL) // sh&1 << 6
|
||||
{
|
||||
DECL_Id(0x06A00010);
|
||||
|
||||
SET_CC;
|
||||
I |= (sat_imm&31)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (sft_imm&31)<<7;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
// SSAT16{<cond>} <Rd>, #<immed>, <Rm>
|
||||
//
|
||||
EAPI SSAT16(eReg Rd, u32 sat_imm, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06A00030);
|
||||
|
||||
SET_CC;
|
||||
I |= (sat_imm&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= 15<<8; // * SBO
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
// USAT{<cond>} <Rd>, #<immed>, <Rm>{, <shift>}
|
||||
//
|
||||
EAPI USAT(eReg Rd, u32 sat_imm, eReg Rm, u32 sft_imm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06E00010);
|
||||
|
||||
SET_CC;
|
||||
I |= (sat_imm&31)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (sft_imm&31)<<7;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
// USAT16{<cond>} <Rd>, #<immed>, <Rm>
|
||||
//
|
||||
EAPI USAT16(eReg Rd, u32 sat_imm, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06E00030);
|
||||
|
||||
SET_CC;
|
||||
I |= (sat_imm&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= 15<<8; // * SBO
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
Executable
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* E_Multiply.h
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
EAPI MLA(eReg Rd, eReg Rn, eReg Rs, eReg Rm, ConditionCode CC=AL) // *FIXME* S
|
||||
{
|
||||
DECL_Id(0x00200090);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<16;
|
||||
I |= (Rn&15)<<12;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI MUL(eReg Rd, eReg Rs, eReg Rm, ConditionCode CC=AL) // *FIXME* S
|
||||
{
|
||||
DECL_Id(0x00000090);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<16;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI UMULL(eReg Rdhi, eReg Rdlo, eReg Rs, eReg Rm, ConditionCode CC=AL) // *FIXME* S
|
||||
{
|
||||
DECL_Id(0x00800090);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rdhi&15)<<16;
|
||||
I |= (Rdlo&15)<<12;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SMULL(eReg Rdhi, eReg Rdlo, eReg Rs, eReg Rm, ConditionCode CC=AL) // *FIXME* S
|
||||
{
|
||||
DECL_Id(0x00C00090);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rdhi&15)<<16;
|
||||
I |= (Rdlo&15)<<12;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#if 0
|
||||
SMLA<x><y> Signed halfword Multiply Accumulate.
|
||||
SMLAD Signed halfword Multiply Accumulate, Dual.
|
||||
SMLAL Signed Multiply Accumulate Long.
|
||||
SMLAL<x><y> Signed halfword Multiply Accumulate Long.
|
||||
SMLALD Signed halfword Multiply Accumulate Long, Dual.
|
||||
SMLAW<y> Signed halfword by word Multiply Accumulate.
|
||||
SMLSD Signed halfword Multiply Subtract, Dual.
|
||||
SMLSLD Signed halfword Multiply Subtract Long Dual.
|
||||
SMMLA Signed Most significant word Multiply Accumulate.
|
||||
SMMLS Signed Most significant word Multiply Subtract.
|
||||
SMMUL Signed Most significant word Multiply.
|
||||
SMUAD Signed halfword Multiply, Add, Dual.
|
||||
SMUL<x><y> Signed halfword Multiply.
|
||||
SMULL Signed Multiply Long.
|
||||
SMULW<y> Signed halfword by word Multiply.
|
||||
SMUSD Signed halfword Multiply, Subtract, Dual.
|
||||
UMAAL Unsigned Multiply Accumulate significant Long.
|
||||
UMLAL Unsigned Multiply Accumulate Long.
|
||||
UMULL Unsigned Multiply Long.
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* E_Parallel.h
|
||||
*
|
||||
|
||||
ADD8 Adds each byte of the second operand register to the corresponding byte of the first operand
|
||||
register to form the corresponding byte of the result.
|
||||
|
||||
ADD16 Adds the top halfwords of two registers to form the top halfword of the result.
|
||||
Adds the bottom halfwords of the same two registers to form the bottom halfword of the result.
|
||||
|
||||
SUB8 Subtracts each byte of the second operand register from the corresponding byte of the first
|
||||
operand register to form the corresponding byte of the result.
|
||||
|
||||
SUB16 Subtracts the top halfword of the first operand register from the top halfword of the second operand register to form the top halfword of the result.
|
||||
Subtracts the bottom halfword of the second operand registers from the bottom halfword of
|
||||
the first operand register to form the bottom halfword of the result.
|
||||
|
||||
ADDSUBX Does the following:
|
||||
1. Exchanges halfwords of the second operand register.
|
||||
2. Adds top halfwords and subtracts bottom halfwords.
|
||||
|
||||
SUBADDX Does the following:
|
||||
1. Exchanges halfwords of the second operand register.
|
||||
2. Subtracts top halfwords and adds bottom halfwords.
|
||||
|
||||
|
||||
Each of the six instructions is available in the following variations, indicated by the prefixes shown:
|
||||
|
||||
S Signed arithmetic modulo 28 or 216. Sets the CPSR GE bits (see The GE[3:0] bits on page A2-13).
|
||||
Q Signed saturating arithmetic.
|
||||
SH Signed arithmetic, halving the results to avoid overflow.
|
||||
U Unsigned arithmetic modulo 28 or 216. Sets the CPSR GE bits (see The GE[3:0] bits on page A2-13).
|
||||
UQ Unsigned saturating arithmetic.
|
||||
UH Unsigned arithmetic, halving the results to avoid overflow.
|
||||
|
||||
|
||||
Status:
|
||||
These routines require implementation if needed.
|
||||
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
#if defined(_DEVEL) && 0
|
||||
|
||||
// S
|
||||
//
|
||||
EAPI SADD8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SADD16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SSUB8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SSUB16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SADDSUBX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SSUBADDX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
|
||||
|
||||
// Q
|
||||
//
|
||||
EAPI QADD8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI QADD16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI QSUB8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI QSUB16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI QADDSUBX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI QSUBADDX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
|
||||
|
||||
// SH
|
||||
//
|
||||
EAPI SHADD8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SHADD16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SHSUB8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SHSUB16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SHADDSUBX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SHSUBADDX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
|
||||
|
||||
// U
|
||||
//
|
||||
EAPI UADD8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UADD16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI USUB8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI USUB16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UADDSUBX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI USUBADDX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
|
||||
|
||||
// UQ
|
||||
//
|
||||
EAPI UQADD8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UQADD16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UQSUB8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UQSUB16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UQADDSUBX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UQSUBADDX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
|
||||
|
||||
// UH
|
||||
//
|
||||
EAPI UHADD8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UHADD16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UHSUB8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UHSUB16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UHADDSUBX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UHSUBADDX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* E_Special.h
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
EAPI ARMERROR()
|
||||
{
|
||||
DECL_Id(0xFFFFFFFF);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI NOP()
|
||||
{
|
||||
DECL_Id(0xE320F000);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SVC(u32 code)
|
||||
{
|
||||
DECL_Id(0x0F000000);
|
||||
I |= code&0xFFFFFF;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI BKPT()
|
||||
{
|
||||
DECL_Id(0x01200070);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
#define SWI SVC
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Synchronization & Barrier Instructions.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
EAPI DSB()
|
||||
{
|
||||
DECL_Id(0xF57FF04F);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI DMB()
|
||||
{
|
||||
DECL_Id(0xF57FF05F);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ISB()
|
||||
{
|
||||
DECL_Id(0xF57FF06F);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* E_Status.h
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
#if defined(_DEVEL)
|
||||
|
||||
// MRS Move PSR to General-purpose Register.
|
||||
//
|
||||
EAPI MRS(eReg Rd, u32 R, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01000000);
|
||||
|
||||
SET_CC;
|
||||
I |= (R&1) << 22;
|
||||
I |= 15<<16; // * SBO
|
||||
I |= (Rd &15)<<12;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
/* MSR Move General-purpose Register to PSR.
|
||||
|
||||
MSR{<cond>} CPSR_<fields>, #<immediate>
|
||||
MSR{<cond>} CPSR_<fields>, <Rm>
|
||||
MSR{<cond>} SPSR_<fields>, #<immediate>
|
||||
MSR{<cond>} SPSR_<fields>, <Rm>
|
||||
*/
|
||||
|
||||
|
||||
// MSR: Immediate operand
|
||||
//
|
||||
EAPI MSR(u32 R, u32 fmask, u32 rot_imm, u32 imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03200000);
|
||||
|
||||
SET_CC;
|
||||
I |= (R&1) << 22;
|
||||
I |= (fmask &15)<<16;
|
||||
I |= 15<<12; // * SBO
|
||||
I |= (rot_imm &15)<<8;
|
||||
I |= (imm8 &255);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
// MSR: Register operand
|
||||
//
|
||||
EAPI MSR(u32 R, u32 fmask, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01200000);
|
||||
|
||||
SET_CC;
|
||||
I |= (R&1) << 22;
|
||||
I |= (fmask &15)<<16;
|
||||
I |= 15<<12; // * SBO
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// CPS Change Processor State.
|
||||
//
|
||||
EAPI CPS(u32 imod, u32 mmod, u32 mode) // ** [A|I|F]
|
||||
{
|
||||
DECL_Id(0xF1000000);
|
||||
|
||||
// Note: UNconditional instruction!
|
||||
I |= (imod&3)<<18;
|
||||
I |= (mmod&1)<<17;
|
||||
I |= (mode&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// SETEND Modifies the CPSR endianness, E, bit, without changing any other bits in the CPSR.
|
||||
//
|
||||
EAPI SETEND(u32 E)
|
||||
{
|
||||
DECL_Id(0xF1010000);
|
||||
|
||||
// Note: UNconditional instruction!
|
||||
I |= (E &1) << 9;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
};
|
||||
Executable
+667
@@ -0,0 +1,667 @@
|
||||
/*
|
||||
* E_VDataOp.h * VFP/A.SIMD Data Processing Instruction Set Encoding
|
||||
*
|
||||
* V{<modifier>}<operation>{<shape>}<c><q>{.<dt>} {<dest>,} <src1>, <src2>
|
||||
*
|
||||
* <modifier> Meaning
|
||||
* Q The operation uses saturating arithmetic.
|
||||
* R The operation performs rounding.
|
||||
* D The operation doubles the result (before accumulation, if any).
|
||||
* H The operation halves the result.
|
||||
*
|
||||
* <shape> Meaning Typical register shape
|
||||
* (none) The operands and result are all the same width. Dd, Dn, Dm - Qd, Qn, Qm
|
||||
* L Long operation - result is 2 x width of both operands Qd, Dn, Dm
|
||||
* N Narrow operation - result is width/2 both operands Dd, Qn, Qm
|
||||
* W Wide operation - result and oper[1] are 2x width oper[2] Qd, Qn, Dm
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
#define SET_Qd \
|
||||
I |= (((Qd<<1)&0x0E)<<12); \
|
||||
I |= (((Qd<<1)&0x10)<<18)
|
||||
|
||||
#define SET_Qn \
|
||||
I |= (((Qn<<1)&0x0E)<<16); \
|
||||
I |= (((Qn<<1)&0x10)<<3)
|
||||
|
||||
#define SET_Qm \
|
||||
I |= (((Qm<<1)&0x0E)); \
|
||||
I |= (((Qm<<1)&0x10)<<1)
|
||||
|
||||
|
||||
|
||||
#define SET_Dd \
|
||||
I |= ((Dd&0x0F)<<12); \
|
||||
I |= ((Dd&0x10)<<18)
|
||||
|
||||
#define SET_Dn \
|
||||
I |= ((Dn&0x0F)<<16); \
|
||||
I |= ((Dn&0x10)<<3)
|
||||
|
||||
#define SET_Dm \
|
||||
I |= ((Dm&0x0F)); \
|
||||
I |= ((Dm&0x10)<<1)
|
||||
|
||||
|
||||
|
||||
|
||||
#define SET_Q I |= 0x40
|
||||
#define SET_U I |= ((U&1)<<24);
|
||||
#define SET_Size I |= (((Size>>4)&3)<<20) // 8,16,32 -> 0,1,2
|
||||
|
||||
|
||||
#define SET_Qdnm \
|
||||
SET_Qd; SET_Qn; SET_Qm; SET_Q
|
||||
|
||||
#define SET_Ddnm \
|
||||
SET_Dd; SET_Dn; SET_Dm
|
||||
|
||||
#define DECL_Qdnm
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#define VdpInstrF(viName, viId) \
|
||||
EAPI V##viName##_F32 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { DECL_Id(viId); SET_Qdnm; EMIT_I; } \
|
||||
EAPI V##viName##_F32 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { DECL_Id(viId); SET_Ddnm; EMIT_I; }
|
||||
|
||||
|
||||
#define VdpInstrI_EOR(viName, viId) \
|
||||
EAPI V##viName (eFQReg Qd, eFQReg Qn, eFQReg Qm) { DECL_Id(viId); SET_Qdnm; EMIT_I; } \
|
||||
EAPI V##viName (eFDReg Dd, eFDReg Dn, eFDReg Dm) { DECL_Id(viId); SET_Ddnm; EMIT_I; } \
|
||||
|
||||
#define VdpInstrI(viName, viId) \
|
||||
EAPI V##viName (eFQReg Qd, eFQReg Qn, eFQReg Qm, u32 Size=32) { DECL_Id(viId); SET_Qdnm; SET_Size; EMIT_I; } \
|
||||
EAPI V##viName (eFDReg Dd, eFDReg Dn, eFDReg Dm, u32 Size=32) { DECL_Id(viId); SET_Ddnm; SET_Size; EMIT_I; } \
|
||||
\
|
||||
EAPI V##viName##_I8 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 8); } \
|
||||
EAPI V##viName##_I8 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 8); } \
|
||||
EAPI V##viName##_I16 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 16); } \
|
||||
EAPI V##viName##_I16 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 16); } \
|
||||
EAPI V##viName##_I32 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 32); } \
|
||||
EAPI V##viName##_I32 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 32); }
|
||||
|
||||
#define VdpInstrU(viName, viId) \
|
||||
EAPI V##viName (eFQReg Qd, eFQReg Qn, eFQReg Qm, u32 Size=32, u32 U=0) { DECL_Id(viId); SET_Qdnm; SET_U; SET_Size; EMIT_I; } \
|
||||
EAPI V##viName (eFDReg Dd, eFDReg Dn, eFDReg Dm, u32 Size=32, u32 U=0) { DECL_Id(viId); SET_Ddnm; SET_U; SET_Size; EMIT_I; } \
|
||||
\
|
||||
EAPI V##viName##_U8 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 8, 1); } \
|
||||
EAPI V##viName##_U8 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 8, 1); } \
|
||||
EAPI V##viName##_S8 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 8, 0); } \
|
||||
EAPI V##viName##_S8 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 8, 0); } \
|
||||
EAPI V##viName##_U16 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 16, 1); } \
|
||||
EAPI V##viName##_U16 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 16, 1); } \
|
||||
EAPI V##viName##_S16 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 16, 0); } \
|
||||
EAPI V##viName##_S16 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 16, 0); } \
|
||||
EAPI V##viName##_U32 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 32, 1); } \
|
||||
EAPI V##viName##_U32 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 32, 1); } \
|
||||
EAPI V##viName##_S32 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 32, 0); } \
|
||||
EAPI V##viName##_S32 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 32, 0); }
|
||||
|
||||
//# define VdpInstrImm (viName, viId)
|
||||
|
||||
|
||||
// Another three or four like the above .. immN, above w/ size, F32 w/ sz
|
||||
|
||||
|
||||
/*
|
||||
* A.SIMD Parallel Add/Sub
|
||||
*
|
||||
Vector Add VADD (integer), VADD (floating-point)
|
||||
Vector Add and Narrow, returning High Half VADDHN
|
||||
Vector Add Long, Vector Add Wide VADDL, VADDW
|
||||
Vector Halving Add, Vector Halving Subtract VHADD, VHSUB
|
||||
Vector Pairwise Add and Accumulate Long VPADAL
|
||||
Vector Pairwise Add VPADD (integer) , VPADD (floating-point)
|
||||
Vector Pairwise Add Long VPADDL
|
||||
Vector Rounding Add & Narrow, returning High Half VRADDHN
|
||||
Vector Rounding Halving Add VRHADD
|
||||
Vector Rounding Subtract & Narrow, ret. High Half VRSUBHN
|
||||
Vector Saturating Add VQADD
|
||||
Vector Saturating Subtract VQSUB
|
||||
Vector Subtract VSUB (integer), VSUB (floating-point)
|
||||
Vector Subtract and Narrow, returning High Half VSUBHN
|
||||
Vector Subtract Long, Vector Subtract Wide VSUBL, VSUBW
|
||||
*/
|
||||
|
||||
VdpInstrI(ADD, 0xF2000800)
|
||||
VdpInstrF(ADD, 0xF2000D00)
|
||||
|
||||
|
||||
VdpInstrI(HADD, 0xF2000800)
|
||||
VdpInstrI(HSUB, 0xF2000A00)
|
||||
|
||||
VdpInstrI(PADD, 0xF2000B10)
|
||||
VdpInstrF(PADD, 0xF3000D00)
|
||||
|
||||
|
||||
VdpInstrU(RHADD, 0xF3000100)
|
||||
VdpInstrU(QADD, 0xF2000010)
|
||||
VdpInstrU(QSUB, 0xF3000210)
|
||||
|
||||
VdpInstrI(SUB, 0xF3000800)
|
||||
VdpInstrF(SUB, 0xF2200D00)
|
||||
|
||||
|
||||
// VADD I { DECL_Id(0xF2000800); SET_Qdnm; EMIT_I; }
|
||||
// VADD F { DECL_Id(0xF2000D00); SET_Qdnm; EMIT_I; }
|
||||
// VADDHN { DECL_Id(0xF2800400); SET_Qdnm; EMIT_I; } // DQQ
|
||||
// VADD{L,W} { DECL_Id(0xF2800000); SET_Qdnm; EMIT_I; } // QDD || QQD
|
||||
// VH{ADD,SUB} { DECL_Id(0xF2000000); SET_Qdnm; EMIT_I; }
|
||||
// VPADAL { DECL_Id(0xF3B00600); SET_Qdnm; EMIT_I; } // QdQm || DdDm
|
||||
// VPADD I { DECL_Id(0xF2000B10); SET_Qdnm; EMIT_I; } // DDD only
|
||||
// VPADD F { DECL_Id(0xF3000D00); SET_Qdnm; EMIT_I; } // DDD only
|
||||
// VPADDL { DECL_Id(0xF3B00200); SET_Qdnm; EMIT_I; } // QdQm || DdDm
|
||||
// VRADDHN { DECL_Id(0xF3800400); SET_Qdnm; EMIT_I; } // DQQ
|
||||
// VRHADD { DECL_Id(0xF3000100); SET_Qdnm; EMIT_I; }
|
||||
// VRSUBHN { DECL_Id(0xF3800600); SET_Qdnm; EMIT_I; } // DQQ
|
||||
// VQADD { DECL_Id(0xF2000010); SET_Qdnm; EMIT_I; }
|
||||
// VQSUB { DECL_Id(0xF3000210); SET_Qdnm; EMIT_I; }
|
||||
// VSUB I { DECL_Id(0xF3000800); SET_Qdnm; EMIT_I; }
|
||||
// VSUB F { DECL_Id(0xF2200D00); SET_Qdnm; EMIT_I; }
|
||||
// VSUBHN { DECL_Id(0xF2800600); SET_Qdnm; EMIT_I; } // DQQ
|
||||
// VSUB{L,W} { DECL_Id(0xF2800200); SET_Qdnm; EMIT_I; } // QDD || QQD
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A.SIMD Bitwise
|
||||
*
|
||||
Vector Bitwise AND VAND (register)
|
||||
Vector Bitwise Bit Clear (AND complement) VBIC (immediate), VBIC (register)
|
||||
Vector Bitwise Exclusive OR VEOR
|
||||
Vector Bitwise Move VMOV (immediate), VMOV (register)
|
||||
Vector Bitwise NOT VMVN (immediate), VMVN (register)
|
||||
Vector Bitwise OR VORR (immediate), VORR (register)
|
||||
Vector Bitwise OR NOT VORN (register)
|
||||
Vector Bitwise Insert if False VBIF
|
||||
Vector Bitwise Insert if True VBIT
|
||||
Vector Bitwise Select VBSL
|
||||
*/
|
||||
|
||||
VdpInstrI(AND, 0xF2000110)
|
||||
VdpInstrI(BIC, 0xF2100110)
|
||||
VdpInstrI_EOR(EOR, 0xF3000110)
|
||||
VdpInstrI_EOR(BSL, 0xF3100110)
|
||||
VdpInstrI_EOR(BIT, 0xF3200110)
|
||||
VdpInstrI_EOR(BIF, 0xF3300110)
|
||||
VdpInstrI(ORN, 0xF2300110)
|
||||
// VAND R { DECL_Id(0xF2000110); SET_Qdnm; EMIT_I; }
|
||||
// VBIC R { DECL_Id(0xF2100110); SET_Qdnm; EMIT_I; }
|
||||
// VEOR { DECL_Id(0xF3000110); SET_Qdnm; EMIT_I; }
|
||||
// VBSL { DECL_Id(0xF3100110); SET_Qdnm; EMIT_I; }
|
||||
// VBIT { DECL_Id(0xF3200110); SET_Qdnm; EMIT_I; }
|
||||
// VBIF { DECL_Id(0xF3300110); SET_Qdnm; EMIT_I; }
|
||||
// VORN R { DECL_Id(0xF2300110); SET_Qdnm; EMIT_I; }
|
||||
|
||||
// VBIC I { DECL_Id(0xF2800030); SET_Qd; SET_IMM??; EMIT_I; }
|
||||
// VMOV I { DECL_Id(0xF2800010); SET_Qd; SET_IMM??; EMIT_I; }
|
||||
// VMVN I { DECL_Id(0xF2800030); SET_Qd; SET_IMM??; EMIT_I; }
|
||||
// VORR I { DECL_Id(0xF2800010); SET_Qd; SET_IMM??; EMIT_I; }
|
||||
// VAND I VBIC I
|
||||
// VORN I VORR I
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A.SIMD comparison
|
||||
*
|
||||
Vector Absolute Compare VACGE, VACGT, VACLE,VACLT
|
||||
Vector Compare Equal VCEQ (register)
|
||||
Vector Compare Equal to Zer VCEQ (immediate #0)
|
||||
Vector Compare Greater Than or Equal VCGE (register)
|
||||
Vector Compare Greater Than or Equal to Zero VCGE (immediate #0)
|
||||
Vector Compare Greater Than VCGT (register)
|
||||
Vector Compare Greater Than Zero VCGT (immediate #0)
|
||||
Vector Compare Less Than or Equal to Zero VCLE (immediate #0)
|
||||
Vector Compare Less Than Zero VCLT (immediate #0)
|
||||
Vector Test Bits VTST
|
||||
*/
|
||||
// VAC{COND} { DECL_Id(0xF3000E10); SET_Vdnm; EMIT_I; }
|
||||
|
||||
// VCEQ { DECL_Id(0xF3000810); SET_Vdnm; EMIT_I; } .F32 { DECL_Id(0xF2000E00); SET_Vdnm; EMIT_I; }
|
||||
VdpInstrI(CEQ, 0xF3200810)
|
||||
// VdpInstrF(CEQ, 0xF2000e00)
|
||||
// VCGE { DECL_Id(0xF2000310); SET_Vdnm; EMIT_I; } .F32 { DECL_Id(0xF3000E00); SET_Vdnm; EMIT_I; }
|
||||
VdpInstrI(CGE, 0xF2200310)
|
||||
// VdpInstrF(CGE, 0xF3000e00)
|
||||
// VCGT { DECL_Id(0xF2000300); SET_Vdnm; EMIT_I; } .F32 { DECL_Id(0xF3200E00); SET_Vdnm; EMIT_I; }
|
||||
//*SEB* 0xF220030 for S32, 0xF3200300 for U32, 0xF2000300 is for S8.
|
||||
VdpInstrI(CGT, 0xF2200300)
|
||||
//VdpInstrF(CGT, 0xF3200e00)
|
||||
// VCLE { DECL_Id(0xF3B10180); SET_Vdnm; EMIT_I; } // R is VCGE w/ operands reversed
|
||||
// VCLT { DECL_Id(0xF3B10200); SET_Vdnm; EMIT_I; } // R is VCGT w/ operands reversed
|
||||
// VTST { DECL_Id(0xF2000810); SET_Vdnm; EMIT_I; }
|
||||
|
||||
// VCEQZ { DECL_Id(0xF3B10100); SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VCGEZ { DECL_Id(0xF3B10080); SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VCGTZ { DECL_Id(0xF3B10000); SET_Vd; SET_Vm; EMIT_I; }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A.SIMD shift ** SET_imm6; needed for non Vdnm
|
||||
*
|
||||
Vector Saturating Rounding Shift Left VQRSHL
|
||||
Vector Saturating Rounding Shift Right and Narrow VQRSHRN, VQRSHRUN
|
||||
Vector Saturating Shift Left VQSHL (register), VQSHL, VQSHLU (immediate)
|
||||
Vector Saturating Shift Right and Narrow VQSHRN, VQSHRUN
|
||||
Vector Rounding Shift Left VRSHL
|
||||
Vector Rounding Shift Right VRSHR
|
||||
Vector Rounding Shift Right and Accumulate VRSRA
|
||||
Vector Rounding Shift Right and Narrow VRSHRN
|
||||
Vector Shift Left VSHL (immediate) on page A8-750 VSHL (register)
|
||||
Vector Shift Left Long VSHLL
|
||||
Vector Shift Right VSHR
|
||||
Vector Shift Right and Narrow VSHRN
|
||||
Vector Shift Left and Insert VSLI
|
||||
Vector Shift Right and Accumulate VSRA
|
||||
Vector Shift Right and Insert VSRI
|
||||
*/
|
||||
// VQRSHL { DECL_Id(0xF SET_Vdnm; EMIT_I; } // * TODO
|
||||
// VQRSHRN { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VQRSHRUN { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VQSHL R { DECL_Id(0xF SET_Vdnm; EMIT_I; }
|
||||
// VQSHL I { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VQSHLU { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VQSHRN { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VQSHRUN { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VRSHL { DECL_Id(0xF SET_Vdnm; EMIT_I; }
|
||||
// VRSHR { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VRSRA { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VRSHRN { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VSHL I { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VSHL R { DECL_Id(0xF SET_Vdnm; EMIT_I; }
|
||||
// VSHLL { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VSHR { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VSHRN { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VSLI { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VSRA { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VSRI { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A.SIMD multiply
|
||||
*
|
||||
Vector Multiply Accumulate VMLA, VMLAL, VMLS, VMLSL (integer) , VMLA, VMLS (floating-point), VMLA, VMLAL, VMLS, VMLSL (by scalar)
|
||||
Vector Multiply Accumulate Long
|
||||
Vector Multiply Subtract
|
||||
Vector Multiply Subtract Long
|
||||
Vector Multiply VMUL, VMULL (integer and polynomial)
|
||||
Vector Multiply Long VMUL (floating-point) on page A8-664 VMUL, VMULL (by scalar)
|
||||
Vector Saturating Doubling Multiply Accumulate Long VQDMLAL, VQDMLSL
|
||||
Vector Saturating Doubling Multiply Subtract Long
|
||||
Vector Saturating Doubling Multiply Returning High Half VQDMULH
|
||||
Vector Saturating Rounding Doubling Multiply Ret. High Half VQRDMULH
|
||||
Vector Saturating Doubling Multiply Long VQDMULL
|
||||
*/
|
||||
|
||||
VdpInstrI(MLA, 0xF2000900)
|
||||
VdpInstrI(MLS, 0xF3000900)
|
||||
|
||||
VdpInstrF(MLA, 0xF2000D10)
|
||||
VdpInstrF(MLS, 0xF2200D10)
|
||||
|
||||
//by scalar
|
||||
//this should be really qd,dn,sm not dm
|
||||
EAPI VMUL_F32(eFQReg Qd,eFQReg Qn, eFDReg Dm, int idx)
|
||||
{
|
||||
DECL_Id(0xF2800840);
|
||||
|
||||
SET_Qd;
|
||||
SET_Qn;
|
||||
I |= 1<<8; //SET_F
|
||||
|
||||
SET_Dm;
|
||||
I |= 0x1<<24; //SET_Q not compatible
|
||||
|
||||
I |= 2<<20; //size to 32
|
||||
|
||||
I |= Dm&15; //only lower 15 regs are avail
|
||||
|
||||
|
||||
//set register sub index
|
||||
if (idx)
|
||||
I |= 1<<5;
|
||||
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VMLA_F32(eFQReg Qd,eFQReg Qn, eFDReg Dm, int idx)
|
||||
{
|
||||
DECL_Id(0xF2800040);
|
||||
|
||||
SET_Qd;
|
||||
SET_Qn;
|
||||
I |= 1<<8; //SET_F
|
||||
|
||||
SET_Dm;
|
||||
I |= 0x1<<24; //SET_Q not compatible
|
||||
|
||||
I |= 2<<20; //size to 32
|
||||
|
||||
I |= Dm&15; //only lower 15 regs are avail
|
||||
|
||||
|
||||
//set register sub index
|
||||
if (idx)
|
||||
I |= 1<<5;
|
||||
|
||||
EMIT_I;
|
||||
}
|
||||
// VdpInstrU(MLAL, 0xF2000D10) *QDD
|
||||
// VdpInstrU(MLSL, 0xF2200D10) *QDD
|
||||
|
||||
|
||||
VdpInstrI(MUL, 0xF2000910)
|
||||
|
||||
VdpInstrF(MUL, 0xF3000D10)
|
||||
|
||||
// VdpInstrU(MULL, 0xF3000D10) *QDD
|
||||
|
||||
// VMLA I { DECL_Id(0xF2000900); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// VMLS I { DECL_Id(0xF3000900); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// VMLAL I { DECL_Id(0xF2800800); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20) * I |= ((U&1)<<24)
|
||||
// VMLSL I { DECL_Id(0xF2800A00); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20) * I |= ((U&1)<<24)
|
||||
|
||||
// VMLA F { DECL_Id(0xF2000D10); SET_Vdnm; EMIT_I; } // * I |= ((sz&1)<<20)
|
||||
// VMLS F { DECL_Id(0xF2200D10); SET_Vdnm; EMIT_I; } // * I |= ((sz&1)<<20)
|
||||
|
||||
// VMLA S { DECL_Id(0xF2800040); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// VMLS S { DECL_Id(0xF2800440); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// CMLAL S { DECL_Id(0xF2800240); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20) * I |= ((U&1)<<24)
|
||||
// VMLSL S { DECL_Id(0xF2800640); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20) * I |= ((U&1)<<24)
|
||||
|
||||
// VMUL IP { DECL_Id(0xF2000910); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20) * I |= 1<<24 for polynomial
|
||||
// VMULL IP { DECL_Id(0xF2800C00); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20) * I |= 1<<9 for polynomial * I |= ((U&1)<<24)
|
||||
|
||||
// VMUL F { DECL_Id(0xF3000D10); SET_Vdnm; EMIT_I; } // * I |= ((sz&1)<<20)
|
||||
|
||||
// VMUL S { DECL_Id(0xF2800840); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// VMULL S { DECL_Id(0xF2800A40); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20) * I |= ((U&1)<<24)
|
||||
// VQDMLAL { DECL_Id(0xF2800900); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// VQDMLSL { DECL_Id(0xF2800B00); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// VQDMULH { DECL_Id(0xF2000B00); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// VQRDMULH { DECL_Id(0xF3000B00); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// VQDMULL { DECL_Id(0xF2800D00); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A.SIMD misc
|
||||
*
|
||||
Vector Absolute Difference and Accumulate VABA, VABAL
|
||||
Vector Absolute Difference VABD, VABDL (integer) , VABD (floating-point)
|
||||
Vector Absolute VABS
|
||||
Vector Convert between floating-point and fixed point VCVT (between floating-point and fixed-point, Advanced SIMD)
|
||||
Vector Convert between floating-point and integer VCVT (between floating-point and integer, Advanced SIMD)
|
||||
Vector Convert between half-precision and single-precision VCVT (between half-precision and single-precision, Advanced SIMD)
|
||||
Vector Count Leading Sign Bits VCLS
|
||||
Vector Count Leading Zeros VCLZ
|
||||
Vector Count Set Bits VCNT
|
||||
Vector Duplicate scalar VDUP (scalar)
|
||||
Vector Extract VEXT
|
||||
Vector Move and Narrow VMOVN
|
||||
Vector Move Long VMOVL
|
||||
Vector Maximum, Minimum VMAX, VMIN (integer) , VMAX, VMIN (floating-point)
|
||||
Vector Negate VNEG
|
||||
Vector Pairwise Maximum, Minimum VPMAX, VPMIN (integer) , VPMAX, VPMIN (floating-point)
|
||||
Vector Reciprocal Estimate VRECPE
|
||||
Vector Reciprocal Step VRECPS
|
||||
Vector Reciprocal Square Root Estimate VRSQRTE
|
||||
Vector Reciprocal Square Root Step VRSQRTS
|
||||
Vector Reverse VREV16, VREV32, VREV64
|
||||
Vector Saturating Absolute VQABS
|
||||
Vector Saturating Move and Narrow VQMOVN, VQMOVUN
|
||||
Vector Saturating Negate VQNEG
|
||||
Vector Swap VSWP
|
||||
Vector Table Lookup VTBL, VTBX
|
||||
Vector Transpose VTRN
|
||||
Vector Unzip VUZP
|
||||
Vector Zip VZIP
|
||||
*/
|
||||
// VABA { DECL_Id(0xF2000710); // WIP
|
||||
// VABAL { DECL_Id(0xF2800500);
|
||||
|
||||
// VABD I { DECL_Id(0xF);
|
||||
// VABDL I { DECL_Id(0xF);
|
||||
|
||||
// VABD F { DECL_Id(0xF);
|
||||
|
||||
// VABS { DECL_Id(0xF); SET_Vd; SET_Vm; EMIT_I; }
|
||||
|
||||
EAPI VSQRT_F32(eFSReg Sd, eFSReg Sm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0EB10AC0); SET_CC;
|
||||
|
||||
I |= ((Sd&0x1E)<<11) | ((Sd&1)<<22);
|
||||
I |= ((Sm&0x1E)>>1) | ((Sm&1)<<5);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VABS_F32(eFSReg Sd, eFSReg Sm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0EB00AC0); SET_CC;
|
||||
|
||||
I |= ((Sd&0x1E)<<11) | ((Sd&1)<<22);
|
||||
I |= ((Sm&0x1E)>>1) | ((Sm&1)<<5);
|
||||
EMIT_I;
|
||||
}
|
||||
EAPI VNEG_F32(eFSReg Sd, eFSReg Sm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0EB10A40); SET_CC;
|
||||
|
||||
I |= ((Sd&0x1E)<<11) | ((Sd&1)<<22);
|
||||
I |= ((Sm&0x1E)>>1) | ((Sm&1)<<5);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
//imm move, fpu
|
||||
EAPI VMOV(eFSReg Sd, u32 imm8_fpu, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0EB10A00); SET_CC;
|
||||
|
||||
I |= (imm8_fpu&0x0F); //bits 3:0
|
||||
I |= (imm8_fpu&0xF0)<<12; //bits 19:16
|
||||
|
||||
I |= ((Sd&0x1E)<<11) | ((Sd&1)<<22);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
const u32 fpu_imm_1=0x70;//01110000
|
||||
|
||||
EAPI VCMP_F32(eFSReg Sd, eFSReg Sm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0EB40A40); SET_CC;
|
||||
|
||||
I |= ((Sd&0x1E)<<11) | ((Sd&1)<<22);
|
||||
I |= ((Sm&0x1E)>>1) | ((Sm&1)<<5);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
VdpInstrF(CEQ, 0xF2000E00)
|
||||
VdpInstrF(CGE, 0xF3000E00)
|
||||
VdpInstrF(CGT, 0xF3200E00)
|
||||
|
||||
// VCVT FFP { DECL_Id(0xF); SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VCVT FI { DECL_Id(0xF); SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VCVT HS { DECL_Id(0xF); SET_Vd; SET_Vm; EMIT_I; }
|
||||
|
||||
// VCLS { DECL_Id(0xF); SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VCLZ { DECL_Id(0xF); SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VCNT { DECL_Id(0xF); SET_Vd; SET_Vm; EMIT_I; }
|
||||
|
||||
|
||||
// VEXT { DECL_Id(0xF); SET_Vdnm; EMIT_I; }
|
||||
|
||||
// VMAX I { DECL_Id(0xF);
|
||||
// VMIN I { DECL_Id(0xF);
|
||||
// VMAX F { DECL_Id(0xF);
|
||||
// VMIN F { DECL_Id(0xF);
|
||||
// VNEG { DECL_Id(0xF);
|
||||
|
||||
// VPMAX I { DECL_Id(0xF);
|
||||
// VPMIN I { DECL_Id(0xF);
|
||||
// VPMAX F { DECL_Id(0xF);
|
||||
// VPMIN F { DECL_Id(0xF);
|
||||
|
||||
// VRECPE { DECL_Id(0xF3B30400); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18
|
||||
// VRECPS { DECL_Id(0xF2000F10); SET_Vdnm; EMIT_I; } // sz&1<<20 (.F32)
|
||||
// VRSQRTE { DECL_Id(0xF3B30480); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18 F&1<<8 ***
|
||||
// VRSQRTS { DECL_Id(0xF2200F10); SET_Vdnm; EMIT_I; } // sz&1<<20 (.F32)
|
||||
// VREVsz { DECL_Id(0xF3B00000); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18 op&3<<7 ***
|
||||
|
||||
// VQABS { DECL_Id(0xF3B00700); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18
|
||||
// VQMOVN { DECL_Id(0xF3B20200); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18 op&3<<6 op:00=MOVN op11=srcUnsigned op:x1=dstUnsigned
|
||||
// VQMOVUN { DECL_Id(0xF3B20200); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18
|
||||
// VQNEG { DECL_Id(0xF3B00780); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18
|
||||
|
||||
// VSWP { DECL_Id(0xF3B20000); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18
|
||||
// VTBL { DECL_Id(0xF3B00800); SET_Vdnm; EMIT_I; } // len&3<<8
|
||||
// VTBX { DECL_Id(0xF3B00840); SET_Vdnm; EMIT_I; } // len&3<<8
|
||||
// VTRN { DECL_Id(0xF3B20080); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18
|
||||
// VUZP { DECL_Id(0xF3B20100); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18
|
||||
// VZIP { DECL_Id(0xF3B20180); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18
|
||||
//
|
||||
|
||||
|
||||
// VDUP & VMOVN & VMOVL are implemented in VRegXfer.h ...
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VFPv3 Instructions
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
#define SET_Sd \
|
||||
I |= ((Sd&0x1E)<<11); \
|
||||
I |= ((Sd&0x01)<<22)
|
||||
|
||||
#define SET_Sn \
|
||||
I |= ((Sn&0x1E)<<15); \
|
||||
I |= ((Sn&0x01)<<7)
|
||||
|
||||
#define SET_Sm \
|
||||
I |= ((Sm&0x1E)>>1); \
|
||||
I |= ((Sm&0x01)<<5)
|
||||
|
||||
|
||||
|
||||
#define SET_Sdnm SET_Sd; SET_Sn; SET_Sm
|
||||
|
||||
|
||||
#define VfpInstrS(viName, viId) EAPI V##viName##_VFP (eFSReg Sd, eFSReg Sn, eFSReg Sm, ConditionCode CC=CC_AL) { DECL_Id(viId); SET_CC; SET_Sdnm; EMIT_I; }
|
||||
|
||||
VfpInstrS(MLA, 0x0E000A00)
|
||||
VfpInstrS(MLS, 0x0E000A40)
|
||||
|
||||
VfpInstrS(NMLA, 0x0E100A40)
|
||||
VfpInstrS(NMLS, 0x0E100A00)
|
||||
VfpInstrS(NMUL, 0x0E200A40)
|
||||
|
||||
|
||||
VfpInstrS(MUL, 0x0E200A00)
|
||||
|
||||
VfpInstrS(ADD, 0x0E300A00)
|
||||
VfpInstrS(SUB, 0x0E300A40)
|
||||
|
||||
VfpInstrS(DIV, 0x0E800A00)
|
||||
|
||||
|
||||
EAPI VCVT_to_S32_VFP (eFSReg Sd, eFSReg Sm, ConditionCode CC=CC_AL) { DECL_Id(0x0EBD0AC0); SET_CC; SET_Sd; SET_Sm; EMIT_I; } // VfpInstrS(ABS, 0x0EB00AC0) ** {D,S}dm
|
||||
// 0x0EB80A40 is to_U32. to_S32 is 0x0EB80AC0
|
||||
EAPI VCVT_from_S32_VFP (eFSReg Sd, eFSReg Sm, ConditionCode CC=CC_AL) { DECL_Id(0x0EB80AC0); SET_CC; SET_Sd; SET_Sm; EMIT_I; } // VfpInstrS(ABS, 0x0EB00AC0) ** {D,S}dm
|
||||
|
||||
EAPI VABS_VFP (eFSReg Sd, eFSReg Sm, ConditionCode CC=CC_AL) { DECL_Id(0x0EB00AC0); SET_CC; SET_Sd; SET_Sm; EMIT_I; } // VfpInstrS(ABS, 0x0EB00AC0) ** {D,S}dm
|
||||
EAPI VNEG_VFP (eFSReg Sd, eFSReg Sm, ConditionCode CC=CC_AL) { DECL_Id(0x0EB10A40); SET_CC; SET_Sd; SET_Sm; EMIT_I; } // VfpInstrS(NEG, 0x0EB10A40) ** {D,S}dm
|
||||
EAPI VSQRT_VFP(eFSReg Sd, eFSReg Sm, ConditionCode CC=CC_AL) { DECL_Id(0x0EB10AC0); SET_CC; SET_Sd; SET_Sm; EMIT_I; } // VfpInstrS(SQRT, 0x0EB10AC0) ** {D,S}dm
|
||||
|
||||
// - x0 Vector Move VMOV (immediate) on page A8-640
|
||||
// 0000 01 Vector Move VMOV (register) on page A8-642
|
||||
|
||||
// 001x x1 Vector Convert VCVTB, VCVTT (between half-precision and single-precision, VFP) on page A8-588
|
||||
// 010x x1 Vector Compare VCMP, VCMPE on page A8-572
|
||||
// 0111 11 Vector Convert VCVT (between double-precision and single-precision) on page A8-584
|
||||
// 1000 x1 Vector Convert VCVT, VCVTR (between floating-point and integer, VFP) on page A8-578
|
||||
// 101x x1 Vector Convert VCVT (between floating-point and fixed-point, VFP) on page A8-582
|
||||
// 110x x1 Vector Convert VCVT, VCVTR (between floating-point and integer, VFP) on page A8-578
|
||||
// 111x x1 Vector Convert VCVT (between floating-point and fixed-point, VFP) on page A8-582
|
||||
|
||||
////// hack
|
||||
|
||||
EAPI VDIV_HACKF32(eFDReg Sd, eFDReg Sn, eFDReg Sm)
|
||||
{
|
||||
ConditionCode CC=AL;
|
||||
|
||||
eFSReg SdS=(eFSReg)(Sd*2);
|
||||
eFSReg SnS=(eFSReg)(Sn*2);
|
||||
eFSReg SmS=(eFSReg)(Sm*2);
|
||||
|
||||
verify(Sd<32 && Sn<32 && Sm<32);
|
||||
|
||||
VDIV_VFP(SdS,SnS,SmS);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#undef SET_Qd
|
||||
#undef SET_Dd
|
||||
|
||||
#undef SET_Qn
|
||||
#undef SET_Dn
|
||||
|
||||
#undef SET_Qm
|
||||
#undef SET_Dm
|
||||
|
||||
#undef SET_Q
|
||||
|
||||
#undef SET_Qdnm
|
||||
#undef SET_Ddnm
|
||||
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* E_VLoadStore.h * VFP/A.SIMD Load/Store Instruction Set Encoding
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
// [cond][110][Opcode][-Rn-][----][101][---------]
|
||||
//
|
||||
// cond != 0b1111 | LDC && STC consume these
|
||||
|
||||
|
||||
|
||||
#define SET_Dd \
|
||||
I |= ((Dd&0x0F)<<12); \
|
||||
I |= ((Dd&0x10)<<18)
|
||||
|
||||
#define SET_Sd \
|
||||
I |= ((Sd&0x1E)<<11); \
|
||||
I |= ((Sd&0x01)<<22)
|
||||
|
||||
|
||||
#define SET_Rn \
|
||||
I |= ((Rn&15)<<16)
|
||||
|
||||
#define SET_uImm8 \
|
||||
I |= (uImm8 & 255)
|
||||
|
||||
#define SET_sImm8 \
|
||||
if (sImm8 > 0) { \
|
||||
I |= (1<<23); \
|
||||
} \
|
||||
I |= (abs(sImm8) & 255)
|
||||
|
||||
|
||||
#define SET_PUW(_P,_U,_W) \
|
||||
I |= ( ((_P&1)<<24) | ((_U&1)<<23) | ((_W&1)<<21) )
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* V{LD,ST}R: Vector Load/Store Register
|
||||
*
|
||||
* V{LD,ST}R.64 // VFP && A.SIMD
|
||||
* V{LD,ST}R.32 // VFP
|
||||
*/
|
||||
|
||||
EAPI VLDR(eFDReg Dd, eReg Rn, s32 sImm8, ConditionCode CC=AL) // VLDR.64
|
||||
{
|
||||
DECL_Id(0x0D100B00); SET_CC;
|
||||
SET_Dd; SET_Rn; SET_sImm8;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VLDR(eFSReg Sd, eReg Rn, s32 sImm8, ConditionCode CC=AL) // VLDR.32
|
||||
{
|
||||
DECL_Id(0x0D100A00); SET_CC;
|
||||
SET_Sd; SET_Rn; SET_sImm8;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI VSTR(eFDReg Dd, eReg Rn, s32 sImm8, ConditionCode CC=AL) // VSTR.64
|
||||
{
|
||||
DECL_Id(0x0D000B00); SET_CC;
|
||||
SET_Dd; SET_Rn; SET_sImm8;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VSTR(eFSReg Sd, eReg Rn, s32 sImm8, ConditionCode CC=AL) // VSTR.32
|
||||
{
|
||||
DECL_Id(0x0D000A00); SET_CC;
|
||||
SET_Sd; SET_Rn; SET_sImm8;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* V{LD,ST}M: Vector Load/Store Multiple
|
||||
*
|
||||
* V{LD,ST}R.64 // VFP && A.SIMD
|
||||
* V{LD,ST}R.32 // VFP
|
||||
*
|
||||
* uImm8: directional count, abs(sImm8) is the reg. count
|
||||
* Dd: Register to start sequential operation from..
|
||||
*
|
||||
* suffix DB: P=1 U=0 W=1, Decrement Before, Addresses end just before the address in Rn.
|
||||
* suffix IA: P=0 U=1 W=?, Increment After, Addresses start at address in Rn
|
||||
*
|
||||
* ** These are very complicated encoding and require a lot of error checking and even more thought when used. **
|
||||
* ** Simply using, MOV32(R4, &double_array[0]); VLDM(D0, R4, 4); should however work to load an array of 4 doubles **
|
||||
*/
|
||||
|
||||
EAPI VLDM(eFDReg Dd, eReg Rn, u32 uImm8, u32 WB=0, ConditionCode CC=AL) // VLDM.64
|
||||
{
|
||||
// ASSERT( (uImm8>0) && (uImm8<=16) && ((Dd+uImm8) <= 32) )
|
||||
uImm8<<=1;
|
||||
DECL_Id(0x0C100B00); SET_CC;
|
||||
SET_Dd; SET_Rn;
|
||||
SET_uImm8; SET_PUW(0,1,WB); // Defaulting to IA w/o ! (Write-back)
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VLDM(eFSReg Sd, eReg Rn, u32 uImm8, ConditionCode CC=AL) // VLDM.32
|
||||
{
|
||||
// ASSERT( (uImm8>0) && ((Dd+uImm8) <= 32) )
|
||||
DECL_Id(0x0C100A00); SET_CC;
|
||||
SET_Sd; SET_Rn;
|
||||
SET_uImm8; SET_PUW(0,1,0); // Defaulting to IA w/o ! (Write-back)
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI VSTM(eFDReg Dd, eReg Rn, u32 uImm8, ConditionCode CC=AL) // VSTM.64
|
||||
{
|
||||
// ASSERT( (uImm8>0) && (uImm8<=16) && ((Dd+uImm8) <= 32) )
|
||||
uImm8<<=1;
|
||||
DECL_Id(0x0C000B00); SET_CC;
|
||||
SET_Dd; SET_Rn;
|
||||
SET_uImm8; SET_PUW(0,1,0); // Defaulting to IA w/o ! (Write-back)
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VSTM(eFSReg Sd, eReg Rn, u32 uImm8, ConditionCode CC=AL) // VSTM.32
|
||||
{
|
||||
// ASSERT( (uImm8>0) && ((Dd+uImm8) <= 32) )
|
||||
DECL_Id(0x0C000A00); SET_CC;
|
||||
SET_Sd; SET_Rn;
|
||||
SET_uImm8; SET_PUW(0,1,0); // Defaulting to IA w/o ! (Write-back)
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* V{LD,ST}n: Various extra load/store multiple.
|
||||
*
|
||||
* Not Implemented.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VPUSH/VPOP: Vector Load/Store multiple consecutive vector registers to the stack.
|
||||
*
|
||||
* V{PUSH,POP} .64: A.SIMD .32 VFP
|
||||
*/
|
||||
|
||||
EAPI VPUSH(eFDReg Dd, eReg Rn, u32 uImm8, ConditionCode CC=AL) // VPUSH.64
|
||||
{
|
||||
uImm8<<=1;
|
||||
DECL_Id(0x0D2D0B00); SET_CC;
|
||||
SET_Dd; SET_uImm8;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VPUSH(eFSReg Sd, eReg Rn, u32 uImm8, ConditionCode CC=AL) // VPUSH.32
|
||||
{
|
||||
DECL_Id(0x0D2D0A00); SET_CC;
|
||||
SET_Sd; SET_uImm8;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI VPOP(eFDReg Dd, eReg Rn, u32 uImm8, ConditionCode CC=AL) // VPOP.64
|
||||
{
|
||||
uImm8<<=1;
|
||||
DECL_Id(0x0CBD0B00); SET_CC;
|
||||
SET_Dd; SET_uImm8;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VPOP(eFSReg Sd, eReg Rn, u32 uImm8, ConditionCode CC=AL) // VPOP.32
|
||||
{
|
||||
DECL_Id(0x0CBD0A00); SET_CC;
|
||||
SET_Sd; SET_uImm8;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Best practice is to remove macro definitions,
|
||||
* this way they can't affect subsequent headers.
|
||||
*/
|
||||
|
||||
#undef SET_Dd
|
||||
#undef SET_Sd
|
||||
|
||||
#undef SET_Rn
|
||||
|
||||
#undef SET_uImm8
|
||||
#undef SET_sImm8
|
||||
|
||||
#undef SET_PUW
|
||||
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* E_VRegXfer.h * VFP/A.SIMD Register Transfer Instruction Set Encoding
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
|
||||
|
||||
/////// REPLACE THIS MESS W. SET_Rto16 / SET_Rto16_H22 , SET_Rto12 , SET_Rto0_H5 OR just leave it all in the fn's //////
|
||||
|
||||
|
||||
#define SET_Qd \
|
||||
I |= (((Qd<<1)&0x0E)<<16); \
|
||||
I |= (((Qd<<1)&0x10)<<3)
|
||||
|
||||
#define SET_Qm \
|
||||
I |= (((Qm<<1)&0x0E)); \
|
||||
I |= (((Qm<<1)&0x10)<<1)
|
||||
|
||||
|
||||
#define SET_Dd \
|
||||
I |= ((Dd&0x0F)<<16); \
|
||||
I |= ((Dd&0x10)<<3)
|
||||
|
||||
#define SET_Dm \
|
||||
I |= ((Dm&0x0F)); \
|
||||
I |= ((Dm&0x10)<<1)
|
||||
|
||||
|
||||
#define SET_Sn \
|
||||
I |= ((Sn&0x1E)<<15); \
|
||||
I |= ((Sn&0x01)<<7)
|
||||
|
||||
#define SET_Sm \
|
||||
I |= ((Sm&0x1E)>>1); \
|
||||
I |= ((Sm&0x01)<<5)
|
||||
|
||||
|
||||
#define SET_Rn \
|
||||
I |= ((Rn&15)<<16)
|
||||
|
||||
#define SET_Rt \
|
||||
I |= ((Rt&15)<<12)
|
||||
|
||||
#define SET_Rtx2 \
|
||||
I |= ((Rt&15)<<12) | ((Rt2&15)<<16)
|
||||
|
||||
// VDUP VMOV VMRS VMSR
|
||||
|
||||
/*
|
||||
* VDUP.SZ: Duplicates an element from ARM reg Rt into every element of {Q,D}d A.SIMD
|
||||
*
|
||||
*/
|
||||
EAPI VDUP(eFQReg Qd, eReg Rt, u32 Size=32, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0E800B10); SET_CC;
|
||||
SET_Qd; SET_Rt; I |= (1<<21); // Q
|
||||
if (Size==16) { I |= (1<<5); } // e
|
||||
if (Size==8) { I |= (1<<22); } // b
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VDUP(eFDReg Dd, eReg Rt, u32 Size=32, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0E800B10); SET_CC;
|
||||
SET_Dd; SET_Rt; // No Q
|
||||
if (Size==16) { I |= (1<<5); } // e
|
||||
if (Size==8) { I |= (1<<22); } // b
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VDUP8 (eFQReg Qd, eReg Rt, ConditionCode=AL) { VDUP(Qd,Rt,8, CC); }
|
||||
EAPI VDUP8 (eFDReg Dd, eReg Rt, ConditionCode=AL) { VDUP(Dd,Rt,8, CC); }
|
||||
EAPI VDUP16(eFQReg Qd, eReg Rt, ConditionCode=AL) { VDUP(Qd,Rt,16,CC); }
|
||||
EAPI VDUP16(eFDReg Dd, eReg Rt, ConditionCode=AL) { VDUP(Dd,Rt,16,CC); }
|
||||
EAPI VDUP32(eFQReg Qd, eReg Rt, ConditionCode=AL) { VDUP(Qd,Rt,32,CC); }
|
||||
EAPI VDUP32(eFDReg Dd, eReg Rt, ConditionCode=AL) { VDUP(Dd,Rt,32,CC); }
|
||||
|
||||
EAPI VDUP32(eFQReg Qd, eFDReg Dm, int idx)
|
||||
{
|
||||
DECL_Id(0xF3B00C00);
|
||||
|
||||
//Set_Qd seems to be incompitable here ?
|
||||
I |= (((Qd<<1)&0x0E)<<12); \
|
||||
I |= (((Qd<<1)&0x10)<<18);
|
||||
SET_Dm;
|
||||
I |= 0x40; //SET_Q
|
||||
I |= 0x4 << 16; // 32 bits 4=0100
|
||||
I |= (idx&1) << 19; // set idx
|
||||
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOV: (register)
|
||||
*
|
||||
* VMOV { <Qd, Qm> , <Dd, Dm> } A.SIMD
|
||||
* VMOV { <Dd, Dm> , <Sd, Sm> } VFP sz1 UNDEFINED in single only VFP
|
||||
*/
|
||||
|
||||
EAPI VMOV(eFQReg Qd, eFQReg Qm) // UNCONDITIONAL
|
||||
{
|
||||
DECL_Id(0xF2200110);
|
||||
|
||||
I |= ((Qd&0x0F)<<12) | ((Qd&0x10)<<18);
|
||||
I |= ((Qm&0x0F)<<16) | ((Qm&0x10)<<1); // If !Consistent(M:Qm) then its VORR
|
||||
I |= ((Qm&0x0F)) | ((Qm&0x10)<<3); // If !Consistent(M:Qm) then its VORR
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VMOV(eFDReg Dd, eFDReg Dm) // UNCONDITIONAL
|
||||
{
|
||||
DECL_Id(0xF2200110);
|
||||
|
||||
I |= ((Dd&0x0F)<<12) | ((Dd&0x10)<<18);
|
||||
I |= ((Dm&0x0F)<<16) | ((Dm&0x10)<<3); // If !Consistent(M:Dm) then its VORR
|
||||
I |= ((Dm&0x0F)) | ((Dm&0x10)<<1); // If !Consistent(M:Dm) then its VORR
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
// EAPI VMOV(eFDReg Dd, eFDReg Dm, ConditionCode CC=AL) {} VFP Double Version Not Implemented here for obvious reasons : same as below except would set SZ @bit8 : 0x0EB00B40
|
||||
|
||||
EAPI VMOV(eFSReg Sd, eFSReg Sm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0EB00A40); SET_CC;
|
||||
|
||||
I |= ((Sd&0x1E)<<11) | ((Sd&1)<<22);
|
||||
I |= ((Sm&0x1E)>>1) | ((Sm&1)<<5);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOV: (Immediate) A.SIMD / VFP
|
||||
*
|
||||
*/
|
||||
|
||||
//// TO BE IMPLEMENTED ////
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOV: (ARM to scalar) A.SIMD / VFP IF Size=32
|
||||
*
|
||||
*/
|
||||
EAPI VMOV(eFDReg Dd, u32 Index, eReg Rt, u32 Size=32, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0E000B10); SET_CC;
|
||||
SET_Dd; SET_Rt;
|
||||
// Dd[x] where x==Index Dd is 64b, 2x32[0,1](1bit) 4x16[0-3](2bits) 8x8[0-7](3bits)
|
||||
if (Size== 8) { I |= (1<<22) | ((Index&4)<<18) | ((Index&3)<<5) ; } // x -> opc1:0, opc2 (3bits) | opc1:1 SET
|
||||
if (Size==16) { I |= (1<<5) | ((Index&2)<<20) | ((Index&1<<6)) ; } // x -> opc1:0, opc2:1 (2bits) | opc2:0 SET
|
||||
if (Size==32) { I |= ((Index&1)<<21) ; } // x -> opc1:0 (1bit)
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOV: (scalar to ARM) A.SIMD / VFP IF Size=32
|
||||
*
|
||||
* Note: U (bit32) is unsigned bit, invalid for 32b and we do not handle it at all.. might want to set it for byte,short
|
||||
*/
|
||||
EAPI VMOV(eReg Rt, eFDReg Dd, u32 Index, u32 Size=32, ConditionCode CC=AL) // This is really Vn, but we'll use the same macros..
|
||||
{
|
||||
DECL_Id(0x0E100B10); SET_CC;
|
||||
SET_Dd; SET_Rt;
|
||||
// Dd[x] where x==Index Dd is 64b, 2x32[0,1](1bit) 4x16[0-3](2bits) 8x8[0-7](3bits)
|
||||
if (Size== 8) { I |= (1<<22) | ((Index&4)<<18) | ((Index&3)<<5) ; } // x -> opc1:0, opc2 (3bits) | opc1:1 SET
|
||||
if (Size==16) { I |= (1<<5) | ((Index&2)<<20) | ((Index&1<<6)) ; } // x -> opc1:0, opc2:1 (2bits) | opc2:0 SET
|
||||
if (Size==32) { I |= ((Index&1)<<21) ; } // x -> opc1:0 (1bit)
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOV: (between ARM and single either direction) VFP
|
||||
*
|
||||
*/
|
||||
EAPI VMOV(eReg Rt, eFSReg Sn, ConditionCode CC=AL) // Sn !d
|
||||
{
|
||||
DECL_Id(0x0E000A10); SET_CC;
|
||||
SET_Sn; SET_Rt; I |= (1<<20); // op set = TO ARM reg
|
||||
EMIT_I;
|
||||
}
|
||||
EAPI VMOV(eFSReg Sn, eReg Rt, ConditionCode CC=AL) // Sn !d
|
||||
{
|
||||
DECL_Id(0x0E000A10); SET_CC;
|
||||
SET_Sn; SET_Rt; // op NOT set = TO FP Single reg
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOV: (between two ARM regs and two contiguous singles either direction) VFP
|
||||
*
|
||||
*/
|
||||
EAPI VMOV(eReg Rt, eReg Rt2, eFSReg Sm, ConditionCode CC=AL) // Sn !d
|
||||
{
|
||||
DECL_Id(0x0E000A10); SET_CC;
|
||||
SET_Sm; SET_Rtx2; I |= (1<<20); // op set = TO ARM regs
|
||||
EMIT_I;
|
||||
}
|
||||
EAPI VMOV(eFSReg Sm, eReg Rt, eReg Rt2, ConditionCode CC=AL) // Sn !d
|
||||
{
|
||||
DECL_Id(0x0E000A10); SET_CC;
|
||||
SET_Sm; SET_Rtx2; // op NOT set = TO FP Single(s)
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOV: (between two ARM regs and a Double) Dm <-> Rt2:Rt A.SIMD/VFP
|
||||
*
|
||||
*/
|
||||
EAPI VMOV(eReg Rt, eReg Rt2, eFDReg Dm, ConditionCode CC=AL) // Sn !d
|
||||
{
|
||||
DECL_Id(0x0C400B10); SET_CC;
|
||||
SET_Dm; SET_Rtx2; I |= (1<<20); // op set = TO ARM regs
|
||||
EMIT_I;
|
||||
}
|
||||
EAPI VMOV(eFDReg Dm, eReg Rt, eReg Rt2, ConditionCode CC=AL) // Sn !d
|
||||
{
|
||||
DECL_Id(0x0C400B10); SET_CC;
|
||||
SET_Dm; SET_Rtx2; // op NOT set = TO FP Single(s)
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOVL: Takes each element in a VDouble && Sign or Zero extends into a VQuad A.SIMD
|
||||
*
|
||||
*/
|
||||
EAPI VMOVL(eFQReg Qd, eFDReg Dm, u32 Size=32, u32 Sign=0) // UNCONDITIONAL Q & ~1
|
||||
{
|
||||
Size >>= 3; // Sz/8 = 1,2 or 4 else if >0 its VSHLL
|
||||
Sign = (Sign>0)?0:1; // Invert to Unsigned
|
||||
DECL_Id(0xF2800A10);
|
||||
|
||||
SET_Dm;
|
||||
I |= ((Qd&0x0F)<<12);
|
||||
I |= ((Qd&0x10)<<18);
|
||||
I |= ((Size &7)<<19); // imm3
|
||||
I |= ((Sign &1)<<24); // U
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VMOVL_S8 (eFQReg Qd, eFDReg Dm) { VMOVL(Qd,Dm,8,1); }
|
||||
EAPI VMOVL_U8 (eFQReg Qd, eFDReg Dm) { VMOVL(Qd,Dm,8,0); }
|
||||
EAPI VMOVL_S16(eFQReg Qd, eFDReg Dm) { VMOVL(Qd,Dm,16,1); }
|
||||
EAPI VMOVL_U16(eFQReg Qd, eFDReg Dm) { VMOVL(Qd,Dm,16,0); }
|
||||
EAPI VMOVL_S32(eFQReg Qd, eFDReg Dm) { VMOVL(Qd,Dm,32,1); }
|
||||
EAPI VMOVL_U32(eFQReg Qd, eFDReg Dm) { VMOVL(Qd,Dm,32,0); }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOVN: Copies least significant half of each element of a VQuad into the elements of a VDouble A.SIMD
|
||||
*
|
||||
*/
|
||||
EAPI VMOVN(eFDReg Dd, eFQReg Qm, u32 Size=32) // UNCONDITIONAL Q & ~1
|
||||
{
|
||||
Size >>= 4; // Sz/32 = 0,1 or 2
|
||||
DECL_Id(0xF3B20200);
|
||||
|
||||
SET_Qm;
|
||||
I |= ((Dd&0x0F)<<12);
|
||||
I |= ((Dd&0x10)<<18);
|
||||
I |= ((Size &3)<<18); // size
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VMOVN16(eFDReg Dd, eFQReg Qm) { VMOVN(Dd,Qm,16); }
|
||||
EAPI VMOVN32(eFDReg Dd, eFQReg Qm) { VMOVN(Dd,Qm,32); }
|
||||
EAPI VMOVN64(eFDReg Dd, eFQReg Qm) { VMOVN(Dd,Qm,64); }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VM{RS,SR} Move ARM reg To/From FPSCR A.SIMD/VFP
|
||||
*/
|
||||
EAPI VMRS(eReg Rt, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0EF10A10);
|
||||
SET_CC; SET_Rt;
|
||||
EMIT_I;
|
||||
}
|
||||
EAPI VMSR(eReg Rt, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0EE10A10);
|
||||
SET_CC; SET_Rt;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#undef SET_Qd
|
||||
#undef SET_Qm
|
||||
|
||||
#undef SET_Dd
|
||||
#undef SET_Dm
|
||||
|
||||
#undef SET_Sn
|
||||
#undef SET_Sm
|
||||
|
||||
#undef SET_Rn
|
||||
#undef SET_Rt
|
||||
#undef SET_Rtx2
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* H_Branches.h
|
||||
*
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
|
||||
|
||||
inline static snat Literal(unat FnAddr)
|
||||
{
|
||||
u8* pc_addr = (u8*)EMIT_GET_PTR();
|
||||
return (snat)((snat)FnAddr - ((snat)pc_addr+8));
|
||||
//return -(snat)((pc_addr+8)-(snat)FnAddr);
|
||||
}
|
||||
|
||||
EAPI CALL(unat FnAddr, ConditionCode CC=AL)
|
||||
{
|
||||
bool isThumb = FnAddr & 1;
|
||||
FnAddr &= ~1;
|
||||
snat lit = Literal(FnAddr);
|
||||
|
||||
if(0==lit) {
|
||||
printf("Error, Compiler caught NULL literal, CALL(%08X)\n", FnAddr);
|
||||
verify(false);
|
||||
return;
|
||||
}
|
||||
if( (lit<-33554432) || (lit>33554428) ) // ..28 for BL ..30 for BLX
|
||||
{
|
||||
printf("Warning, CALL(%08X) is out of range for literal(%08X)\n", FnAddr, lit);
|
||||
// verify(false);
|
||||
|
||||
MOV32(IP, FnAddr, CC);
|
||||
BLX(IP, CC);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isThumb) {
|
||||
verify (CC==CC_EQ);
|
||||
BLX(lit, isThumb);
|
||||
} else {
|
||||
BL(lit,CC);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
EAPI JUMP(unat FnAddr, ConditionCode CC=AL)
|
||||
{
|
||||
bool isThumb = FnAddr & 1;
|
||||
FnAddr &= ~1;
|
||||
|
||||
verify(!isThumb);
|
||||
snat lit = Literal(FnAddr);
|
||||
|
||||
/*if(0==lit) {
|
||||
printf("Error, Compiler caught NULL literal, JUMP(%08X)\n", FnAddr);
|
||||
verify(false);
|
||||
return;
|
||||
}*/
|
||||
if( (lit<-33554432) || (lit>33554428) ) // ..28 for BL ..30 for BLX
|
||||
{
|
||||
printf("Warning, %X is out of range for imm jump! \n", FnAddr);
|
||||
//verify(false);
|
||||
|
||||
MOV32(IP, FnAddr, CC);
|
||||
BX(IP, CC);
|
||||
return;
|
||||
}
|
||||
B(lit,CC); // Note, wont work for THUMB*, have to use bx which is reg only !
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* H_LoadStore.h
|
||||
*
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
/*
|
||||
* Load Helpers
|
||||
*/
|
||||
|
||||
EAPI LoadImmBase(eReg Rt, u32 Base, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rt, Base, CC);
|
||||
|
||||
#if defined(_DEVEL)
|
||||
LDR(Rt,Rt,0, Offset, CC);
|
||||
#else
|
||||
LDR(Rt,Rt,0, CC);
|
||||
#endif
|
||||
}
|
||||
|
||||
EAPI LoadImmBase(eReg Rt, eReg Rn, u32 Base, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rn, Base, CC);
|
||||
|
||||
#if defined(_DEVEL)
|
||||
LDR(Rt,Rn,0, Offset, CC);
|
||||
#else
|
||||
LDR(Rt,Rn,0, CC);
|
||||
#endif
|
||||
}
|
||||
|
||||
EAPI LoadImmBase16(eReg Rt, u32 Base, bool Extend=false, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rt, Base, CC);
|
||||
LDRH(Rt,Rt,0, CC);
|
||||
|
||||
if(Extend)
|
||||
SXTH(Rt,Rt);
|
||||
}
|
||||
|
||||
EAPI LoadImmBase16(eReg Rt, eReg Rn, u32 Base, bool Extend=false, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rn, Base, CC);
|
||||
LDRH(Rt,Rn,0, CC);
|
||||
|
||||
if(Extend)
|
||||
SXTH(Rt,Rt);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Store Helpers
|
||||
*/
|
||||
|
||||
// you pick regs, loads Base with reg addr, you supply data in Rt
|
||||
EAPI StoreImmBase(eReg Rt, eReg Rn, u32 Base, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rn, Base, CC);
|
||||
|
||||
#if defined(_DEVEL)
|
||||
STR(Rt,Rn,0, Offset, CC);
|
||||
#else
|
||||
STR(Rt,Rn,0, CC);
|
||||
#endif
|
||||
}
|
||||
|
||||
// you pick regs, loads Rt with const val, you supply base for Rn
|
||||
EAPI StoreImmVal(eReg Rt, eReg Rn, u32 Val, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rt, Val, CC);
|
||||
|
||||
#if defined(_DEVEL)
|
||||
STR(Rt,Rn,0, Offset, CC);
|
||||
#else
|
||||
STR(Rt,Rn,0, CC);
|
||||
#endif
|
||||
}
|
||||
|
||||
// you pick regs, loads Base with reg addr, loads Rt with const val
|
||||
EAPI StoreImms(eReg Rt, eReg Rn, u32 Base, u32 Val, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rn, Base, CC);
|
||||
MOV32(Rt, Val, CC);
|
||||
|
||||
#if defined(_DEVEL)
|
||||
STR(Rt,Rn,0, Offset, CC);
|
||||
#else
|
||||
STR(Rt,Rn,0, CC);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if defined(_DEVEL) && 0 // These require testing //
|
||||
|
||||
EAPI LoadImmBase8(eReg Rt, u32 Base, bool Extend=false, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rt, Base, CC);
|
||||
LDRB(Rt,Rt,0, CC);
|
||||
|
||||
if(Extend)
|
||||
SXTB(Rt,Rt);
|
||||
}
|
||||
|
||||
EAPI LoadImmBase8(eReg Rt, eReg Rn, u32 Base, bool Extend=false, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rn, Base, CC);
|
||||
LDRB(Rt,Rn,0, CC);
|
||||
|
||||
if(Extend)
|
||||
SXTB(Rt,Rt);
|
||||
}
|
||||
|
||||
#endif // defined(_DEVEL)
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* H_fp.h
|
||||
*
|
||||
* ARMv7 floating point help routines.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
/// WIP ///
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
#if defined(_DEVEL)
|
||||
|
||||
|
||||
/*
|
||||
* vfpVersion(): Returns VFP Arch. Version, or -1 if not supported
|
||||
*/
|
||||
int vfpVersion()
|
||||
{
|
||||
// FPSID bits [22:16] contain version
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int neonVersion()
|
||||
{
|
||||
// ??
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#define VFP_SINGLE (1<<0)
|
||||
#define VFP_DOUBLE (1<<1)
|
||||
#define NEON_INTEGER (1<<2)
|
||||
#define NEON_SINGLE (1<<3)
|
||||
#define VFP_TRAPS (1<<8) // VFPv3U
|
||||
#define FPEXT_HALF (1<<16) // Half precision extension
|
||||
|
||||
u32 fpFeatures()
|
||||
{
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#endif // _DEVEL
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#if 0
|
||||
|
||||
Version 1 of the Common VFP subarchitecture has special behavior when the FPSCR.IXE bit is set to 1.
|
||||
The Common VFP subarchitecture version can be identified by checking FPSID bits [22:16]. This field is
|
||||
0b0000001 for version 1. In version 1 of the Common VFP subarchitecture the FPEXC.DEX bit is RAZ/WI.
|
||||
|
||||
|
||||
Detecting which VFP Common subarchitecture registers are implemented
|
||||
An implementation can choose not to implement FPINST and FPINST2, if these registers are not required.
|
||||
|
||||
Set FPEXC.EX=1 and FPEXC.FP2V=1
|
||||
Read back the FPEXC register
|
||||
if FPEXC.EX == 0 then
|
||||
Neither FPINST nor FPINST2 are implemented
|
||||
else
|
||||
if FPEXC.FP2V == 0 then
|
||||
FPINST is implemented, FPINST2 is not implemented.
|
||||
else
|
||||
Both FPINST and FPINST2 are implemented.
|
||||
Clean up
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* H_psuedo.h
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
EAPI MOV32(eReg Rd, u32 Imm32, ConditionCode CC=AL)
|
||||
{
|
||||
MOVW(Rd,((Imm32)&0xFFFF),CC);
|
||||
if (Imm32>>16)
|
||||
MOVT(Rd,((Imm32>>16)&0xFFFF),CC);
|
||||
}
|
||||
#if 0
|
||||
EAPI NEG(eReg Rd,eReg Rs)
|
||||
{
|
||||
RSB(Rd,Rs,0);
|
||||
}
|
||||
#endif
|
||||
EAPI NOT(eReg Rd,eReg Rs)
|
||||
{
|
||||
MVN(Rd,Rs);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* H_state.h
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
enum InstructionSet {
|
||||
IS_ARM,
|
||||
IS_Thumb,
|
||||
IS_Jazelle,
|
||||
IS_ThumbEE
|
||||
};
|
||||
|
||||
enum Endian {
|
||||
E_Little,
|
||||
E_Big
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* coding.h, arm binary instruction coding
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
|
||||
/*
|
||||
* Encoding Hi-Order 4bits, ConditionCode
|
||||
*
|
||||
*/
|
||||
|
||||
enum ConditionCode
|
||||
{
|
||||
EQ = 0x00, Equal = EQ, // 0000 Equal Z set
|
||||
NE = 0x01, NotEqual = NE, // 0001 Not equal Z clear
|
||||
CS = 0x02, CarrySet = CS, // 0010 Carry set/unsigned higher or same C set
|
||||
CC = 0x03, CarryClr = CC, // 0011 Carry clear/unsigned lowe C clear
|
||||
MI = 0x04, Minus = MI, // 0100 Minus/negative N set
|
||||
PL = 0x05, Plus = PL, // 0101 Plus/positive or zero N clear
|
||||
VS = 0x06, Overflow = VS, // 0110 Overflow V set
|
||||
VC = 0x07, NoOverflow = VC, // 0111 No overflow V clear
|
||||
|
||||
HI = 0x08, UnHigher = HI, // 1000 Unsigned higher C set and Z clear
|
||||
LS = 0x09, UnLower = LS, // 1001 Unsigned lower or same C clear or Z set
|
||||
|
||||
GE = 0x0A, GrOrEqual = GE, // 1010 Signed greater than or equal N set and V set, or N clear and V clear (N == V)
|
||||
LT = 0x0B, Less = LT, // 1011 Signed less than N set and V clear, or N clear and V set (N != V)
|
||||
GT = 0x0C, Greater = GT, // 1100 Signed greater than Z clear, and either N set and V set, or N clear and V clear (Z == 0,N == V)
|
||||
LE = 0x0D, LessOrEqual = LE, // 1101 Signed less than or equal Z set, or N set and V clear, or N clear and V set (Z == 1 or N != V)
|
||||
|
||||
AL = 0x0E, Always = AL, // 1110 Always (unconditional) -
|
||||
|
||||
|
||||
UC = 0x0F, Unconditional= UC, // 1111 Unconditional Special Instruction for ARMv5? and above
|
||||
|
||||
#define _ARM_COMPAT
|
||||
#if defined(_ARM_COMPAT)
|
||||
CC_EQ=EQ, CC_NE=NE, CC_CS=CS, CC_CC=CC, CC_MI=MI, CC_PL=PL, CC_VS=VS, CC_VC=VC,
|
||||
CC_HI=HI, CC_LS=LS, CC_GE=GE, CC_LT=LT, CC_GT=GT, CC_LE=LE, CC_AL=AL, CC_UC=UC,
|
||||
|
||||
CC_HS=CS, CC_LO=CC,
|
||||
#endif
|
||||
|
||||
ConditionCode_Size
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Data-processing OPCODE 4bits, DPOP
|
||||
*
|
||||
*/
|
||||
|
||||
enum DPOP
|
||||
{
|
||||
DP_AND, // 0000 Logical AND Rd := Rn AND shifter_operand
|
||||
DP_EOR, // 0001 Logical Exclusive OR Rd := Rn EOR shifter_operand
|
||||
DP_SUB, // 0010 Subtract Rd := Rn - shifter_operand
|
||||
DP_RSB, // 0011 Reverse Subtract Rd := shifter_operand - Rn
|
||||
DP_ADD, // 0100 Add Rd := Rn + shifter_operand
|
||||
DP_ADC, // 0101 Add with Carry Rd := Rn + shifter_operand + Carry Flag
|
||||
DP_SBC, // 0110 Subtract with Carry Rd := Rn - shifter_operand - NOT(Carry Flag)
|
||||
DP_RSC, // 0111 Reverse Subtract with Carry Rd := shifter_operand - Rn - NOT(Carry Flag)
|
||||
DP_TST, // 1000 Test Update flags after Rn AND shifter_operand
|
||||
DP_TEQ, // 1001 Test Equivalence Update flags after Rn EOR shifter_operand
|
||||
DP_CMP, // 1010 Compare Update flags after Rn - shifter_operand
|
||||
DP_CMN, // 1011 Compare Negated Update flags after Rn + shifter_operand
|
||||
DP_ORR, // 1100 Logical (inclusive) OR Rd := Rn OR shifter_operand
|
||||
DP_MOV, // 1101 Move Rd := shifter_operand (no first operand)
|
||||
DP_BIC, // 1110 Bit Clear Rd := Rn AND NOT(shifter_operand)
|
||||
DP_MVN // 1111 Move Not Rd := NOT shifter_operand (no first operand)
|
||||
};
|
||||
|
||||
|
||||
|
||||
enum ShiftOp {
|
||||
S_LSL,
|
||||
S_LSR,
|
||||
S_ASR,
|
||||
S_ROR,
|
||||
S_RRX=S_ROR
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* eReg: ARM Register ID
|
||||
*
|
||||
*/
|
||||
enum eReg
|
||||
{
|
||||
r0=0,r1, r2, r3,
|
||||
r4, r5, r6, r7,
|
||||
r8, r9, r10, r11,
|
||||
r12, r13, r14, r15,
|
||||
|
||||
R0=0,R1, R2, R3,
|
||||
R4, R5, R6, R7,
|
||||
R8, R9, R10, R11,
|
||||
R12, R13, R14, R15,
|
||||
|
||||
// Aliases
|
||||
|
||||
a1 = r0, a2 = r1, a3 = r2, a4 = r3,
|
||||
A1 = R0, A2 = R1, A3 = R2, A4 = R3,
|
||||
|
||||
v1 = r4, v2 = r5, v3 = r6, v4 = r7, v5 = r8, v6 = r9,
|
||||
V1 = R4, V2 = R5, V3 = R6, V4 = R7, V5 = R8, V6 = R9,
|
||||
|
||||
rfp = r9, sl = r10, fp = r11, ip = r12, sp = r13, lr = r14, pc = r15,
|
||||
RFP = R9, SL = R10, FP = R11, IP = R12, SP = R13, LR = R14, PC = R15,
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* eFQReg: Float [Quad] Register ID (A.SIMD)
|
||||
*
|
||||
*/
|
||||
enum eFQReg
|
||||
{
|
||||
q0=0,q1, q2, q3,
|
||||
q4, q5, q6, q7,
|
||||
q8, q9, q10, q11,
|
||||
q12, q13, q14, q15,
|
||||
|
||||
Q0=0,Q1, Q2, Q3,
|
||||
Q4, Q5, Q6, Q7,
|
||||
Q8, Q9, Q10, Q11,
|
||||
Q12, Q13, Q14, Q15
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* eFDReg: Float [Double] Register ID (VFP / A.SIMD)
|
||||
*
|
||||
*/
|
||||
enum eFDReg
|
||||
{
|
||||
d0=0,d1, d2, d3,
|
||||
d4, d5, d6, d7,
|
||||
d8, d9, d10, d11,
|
||||
d12, d13, d14, d15,
|
||||
d16, d17, d18, d19,
|
||||
d20, d21, d22, d23,
|
||||
d24, d25, d26, d27,
|
||||
d28, d29, d30, d31,
|
||||
|
||||
D0=0,D1, D2, D3,
|
||||
D4, D5, D6, D7,
|
||||
D8, D9, D10, D11,
|
||||
D12, D13, D14, D15,
|
||||
D16, D17, D18, D19,
|
||||
D20, D21, D22, D23,
|
||||
D24, D25, D26, D27,
|
||||
D28, D29, D30, D31
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* eFSReg: Float [Single] Register ID (VFP)
|
||||
*
|
||||
* Note: Using [f,F]regN syntax to avoid clash with s{8,16,32} types.
|
||||
*/
|
||||
enum eFSReg
|
||||
{
|
||||
f0=0,f1, f2, f3,
|
||||
f4, f5, f6, f7,
|
||||
f8, f9, f10, f11,
|
||||
f12, f13, f14, f15,
|
||||
f16, f17, f18, f19,
|
||||
f20, f21, f22, f23,
|
||||
f24, f25, f26, f27,
|
||||
f28, f29, f30, f31,
|
||||
|
||||
F0=0,F1, F2, F3,
|
||||
F4, F5, F6, F7,
|
||||
F8, F9, F10, F11,
|
||||
F12, F13, F14, F15,
|
||||
F16, F17, F18, F19,
|
||||
F20, F21, F22, F23,
|
||||
F24, F25, F26, F27,
|
||||
F28, F29, F30, F31
|
||||
};
|
||||
|
||||
|
||||
|
||||
enum eFSpecialReg
|
||||
{
|
||||
|
||||
// VM**
|
||||
|
||||
FPINST = 9,
|
||||
FPINST2=10,
|
||||
|
||||
FP_R_ERROR=0xFF
|
||||
};
|
||||
|
||||
|
||||
enum ePushPopReg
|
||||
{
|
||||
_r0 =0x0001, _r1 =0x0002, _r2 =0x0004, _r3 =0x0008,
|
||||
_r4 =0x0010, _r5 =0x0020, _r6 =0x0040, _r7 =0x0080,
|
||||
_r8 =0x0100, _r9 =0x0200, _r10=0x0400, _r11=0x0800,
|
||||
_r12=0x1000, _r13=0x2000, _r14=0x4000, _r15=0x8000,
|
||||
|
||||
_a1 = _r0, _a2 = _r1, _a3 = _r2, _a4 = _r3,
|
||||
_v1 = _r4, _v2 = _r5, _v3 = _r6, _v4 = _r7,
|
||||
_v5 = _r8, _v6 = _r9, _rfp = _r9, _sl = _r10,
|
||||
_fp = _r11, _ip = _r12, _sp = _r13, _lr = _r14,
|
||||
_pc = _r15,
|
||||
|
||||
_push_all = 0xFFFF, // Save All 15
|
||||
_push_call = _lr|_rfp, // Save lr && _rfb(cycle count)
|
||||
|
||||
_push_eabi = _lr|_v1|_v2|_v3|_v4|_v5|_v6|_sl|_fp|_ip, // this is guesswork ..
|
||||
};
|
||||
|
||||
|
||||
|
||||
/// WIP ///
|
||||
|
||||
struct FPReg
|
||||
{
|
||||
union
|
||||
{
|
||||
u8 vQ8[16];
|
||||
u8 vD8[8];
|
||||
u8 vS8[4];
|
||||
|
||||
u16 vQ16[8]; // *VFP: If half-word extensions are enabled
|
||||
u16 vD16[4]; // *VFP: If half-word extensions are enabled
|
||||
u16 vS16[2]; // *VFP: If half-word extensions are enabled
|
||||
|
||||
u32 vQ32[4];
|
||||
u32 vD32[2];
|
||||
u32 S;
|
||||
|
||||
// u64 vQ64[2];
|
||||
// u64 D;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
Executable
+242
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* disasm.h, a horribly static yet (hopefully) correct disassembler
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
|
||||
|
||||
inline static void armdis_cc(u32 cond, char *ccbuff) // Length is always 8 for our static'{n,m}ess
|
||||
{
|
||||
switch(cond)
|
||||
{
|
||||
case EQ: sprintf(ccbuff, "EQ"); return;
|
||||
case NE: sprintf(ccbuff, "NE"); return;
|
||||
case CS: sprintf(ccbuff, "CS"); return;
|
||||
case CC: sprintf(ccbuff, "CC"); return;
|
||||
case MI: sprintf(ccbuff, "MI"); return;
|
||||
case PL: sprintf(ccbuff, "PL"); return;
|
||||
case VS: sprintf(ccbuff, "VS"); return;
|
||||
case VC: sprintf(ccbuff, "VC"); return;
|
||||
|
||||
case HI: sprintf(ccbuff, "HI"); return;
|
||||
case LS: sprintf(ccbuff, "LS"); return;
|
||||
|
||||
case GE: sprintf(ccbuff, "GE"); return;
|
||||
case LT: sprintf(ccbuff, "LT"); return;
|
||||
case GT: sprintf(ccbuff, "GT"); return;
|
||||
case LE: sprintf(ccbuff, "LE"); return;
|
||||
|
||||
case AL: return; // sprintf(ccbuff, "AL"); -- ALways doesn't need to be specified
|
||||
|
||||
case UC: //
|
||||
default: return; // DIE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inline static void armdis_dp(u32 dpop, char *dpbuff) // Length is always 8 ...
|
||||
{
|
||||
switch(dpop)
|
||||
{
|
||||
case DP_AND: sprintf(dpbuff, "AND"); return;
|
||||
case DP_EOR: sprintf(dpbuff, "EOR"); return;
|
||||
case DP_SUB: sprintf(dpbuff, "SUB"); return;
|
||||
case DP_RSB: sprintf(dpbuff, "RSB"); return;
|
||||
case DP_ADD: sprintf(dpbuff, "ADD"); return;
|
||||
case DP_ADC: sprintf(dpbuff, "ADC"); return;
|
||||
case DP_SBC: sprintf(dpbuff, "SBC"); return;
|
||||
case DP_RSC: sprintf(dpbuff, "RSC"); return;
|
||||
case DP_TST: sprintf(dpbuff, "TST"); return;
|
||||
case DP_TEQ: sprintf(dpbuff, "TEQ"); return;
|
||||
case DP_CMP: sprintf(dpbuff, "CMP"); return;
|
||||
case DP_CMN: sprintf(dpbuff, "CMN"); return;
|
||||
case DP_ORR: sprintf(dpbuff, "ORR"); return;
|
||||
case DP_MOV: sprintf(dpbuff, "MOV"); return;
|
||||
case DP_BIC: sprintf(dpbuff, "BIC"); return;
|
||||
case DP_MVN: sprintf(dpbuff, "MVN"); return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
inline static void armdis(u32 op, char *disbuf, u32 len=512)
|
||||
{
|
||||
char ipref[8]={0}, isuff[8]={0}, icond[8]={0} ;
|
||||
|
||||
|
||||
// u32 uOP = ((op>>12)&0xFF00) | ((op>>4)&255) ;
|
||||
|
||||
u32 uCC = ((op>>28) & 0x0F) ; //
|
||||
|
||||
u32 uO1 = ((op>>25) & 0x07) ; //
|
||||
u32 uO2 = ((op>> 4) & 0x01) ; //
|
||||
u32 uC1 = ((op>>21) & 0x0F) ; //
|
||||
u32 uC2 = ((op>> 5) & 0x07) ; //
|
||||
u32 uSB = ((op>>20) & 0x01) ; // Sign Change Bit
|
||||
|
||||
|
||||
/*
|
||||
if (uCC == UC) {
|
||||
|
||||
printf ("DBG armdis has UC instruction %X\n", op);
|
||||
sprintf (disbuf, "UNCONDITIONAL / UNHANDLED INSTRUCTION");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (uCC != AL) {
|
||||
armdis_cc(uCC,isuff);
|
||||
}
|
||||
|
||||
|
||||
if (uO1 == 0)
|
||||
{
|
||||
|
||||
if (uO2 == 0) {
|
||||
|
||||
if ((uC1 & 0xC) == 8) {
|
||||
printf ("DBG armdis 0:0 10xx misc instruction \n", uCC);
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 0:");
|
||||
return;
|
||||
}
|
||||
|
||||
// DP imm.shift
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
else if (uO2 == 1) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 0:");
|
||||
}
|
||||
|
||||
}
|
||||
else if (uO1 == 1) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 1:");
|
||||
}
|
||||
else if (uO1 == 2) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 2:");
|
||||
}
|
||||
else if (uO1 == 3) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 3:");
|
||||
}
|
||||
else if (uO1 == 4) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 4:");
|
||||
}
|
||||
else if (uO1 == 5) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 5:");
|
||||
}
|
||||
else if (uO1 == 6) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 6:");
|
||||
}
|
||||
else if (uO1 == 7) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 7:");
|
||||
}
|
||||
else if (uO1 == 8) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 8:");
|
||||
}
|
||||
else if (uO1 == 9) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 9:");
|
||||
}
|
||||
else if (uO1 == 10) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 10:");
|
||||
}
|
||||
else if (uO1 == 11) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 11:");
|
||||
}
|
||||
else if (uO1 == 12) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 12:");
|
||||
}
|
||||
else if (uO1 == 13) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 13:");
|
||||
}
|
||||
else if (uO1 == 14) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 14:");
|
||||
}
|
||||
else if (uO1 == 15) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 15:");
|
||||
}
|
||||
else {
|
||||
|
||||
sprintf (disbuf, "INVALID INSTRUCTION");
|
||||
}
|
||||
*/
|
||||
if (!uC1 && uO1==5) {
|
||||
//B
|
||||
char tmp[20];
|
||||
tmp[0]='\0';
|
||||
armdis_cc(uCC, tmp);
|
||||
sprintf(disbuf, "B%s %08X", tmp, (op&0xffffff)<<2);
|
||||
} else {
|
||||
armdis_dp(uC1, disbuf);
|
||||
char tmp[20];
|
||||
tmp[0]='\0';
|
||||
armdis_cc(uCC, tmp);
|
||||
if (tmp[0]) {
|
||||
strcat(disbuf, ".\0");
|
||||
strcat(disbuf, tmp);
|
||||
}
|
||||
if (uSB) strcat(disbuf, ".S\0");
|
||||
bool shifter=false;
|
||||
switch (uO1) {
|
||||
case 0:
|
||||
// reg_reg
|
||||
sprintf(tmp,"\tr%d, r%d", (op>>12)&0x0f, (op)&0x0f);
|
||||
shifter=true;
|
||||
break;
|
||||
case 1:
|
||||
// reg_imm
|
||||
sprintf(tmp,"\tr%d, %04X", (op>>16)&0x0f, (op)&0xffff);
|
||||
break;
|
||||
default:
|
||||
shifter=true;
|
||||
sprintf(tmp, " 0x%0X", uO1);
|
||||
}
|
||||
strcat(disbuf, tmp);
|
||||
char* ShiftOpStr[]={"LSL","LSR","ASR","ROR"};
|
||||
u32 shiftop=(op>>5)&0x3;
|
||||
u32 shiftoptype=(op>>4)&0x1;
|
||||
u32 shiftopreg=(op>>8)&0xf;
|
||||
u32 shiftopimm=(op>>7)&0x1f;
|
||||
if (shifter) {
|
||||
if (!shiftop && !shiftoptype && !shiftopimm)
|
||||
{
|
||||
//nothing
|
||||
} else {
|
||||
if ((shiftop==1) || (shiftop==2)) if (!shiftoptype) if (!shiftopimm) shiftopimm=32;
|
||||
sprintf(tmp, " ,%s %s%d", ShiftOpStr[shiftop], (shiftoptype)?" r":" #", (shiftoptype)?shiftopreg:shiftopimm);
|
||||
strcat(disbuf, tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Emitter.h
|
||||
*
|
||||
* ARMv7 ISA Emitter for code generation.
|
||||
*
|
||||
* David Miller, 2011.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "arm_coding.h"
|
||||
#include "arm_registers.h"
|
||||
//#include "arm_disasm.h"
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
/*
|
||||
* Emitter - Reserved for use w/ static members ..
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
class Emitter
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#if defined(_DEBUG) || defined(DEBUG)
|
||||
|
||||
#define EAPI static void
|
||||
|
||||
#define DECL_I \
|
||||
u32 Instruction=0
|
||||
|
||||
#define DECL_Id(d) \
|
||||
u32 Instruction=(d)
|
||||
|
||||
#else
|
||||
|
||||
// #define _inlineExSVoidA __extension__ static __inline void __attribute__ ((__always_inline__))
|
||||
|
||||
#define EAPI \
|
||||
inline static void
|
||||
|
||||
#define DECL_I \
|
||||
static u32 Instruction; \
|
||||
Instruction=0
|
||||
|
||||
#define DECL_Id(d) \
|
||||
static u32 Instruction; \
|
||||
Instruction=(d)
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* TEMP
|
||||
*/
|
||||
|
||||
|
||||
#define I (Instruction)
|
||||
|
||||
#define SET_CC I |= (((u32)CC&15)<<28)
|
||||
|
||||
#ifndef EMIT_I
|
||||
#define EMIT_I emit_Write32((I));
|
||||
#endif
|
||||
|
||||
#ifndef EMIT_GET_PTR
|
||||
#define EMIT_GET_PTR() emit_GetCCPtr()
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* ARM Core Instructions
|
||||
*/
|
||||
|
||||
#include "E_Branches.h"
|
||||
#include "E_DataOp.h"
|
||||
#include "E_Multiply.h"
|
||||
#include "E_Parallel.h"
|
||||
#include "E_Extend.h"
|
||||
#include "E_Misc.h"
|
||||
#include "E_Status.h"
|
||||
#include "E_LoadStore.h"
|
||||
#include "E_Special.h"
|
||||
|
||||
|
||||
/*
|
||||
* ARM VFP/A.SIMD Extension Instructions
|
||||
*/
|
||||
|
||||
#include "E_VLoadStore.h"
|
||||
#include "E_VRegXfer.h"
|
||||
#include "E_VDataOp.h"
|
||||
|
||||
|
||||
/*
|
||||
* Helper Routines & Psuedo-Instructions
|
||||
*/
|
||||
|
||||
#include "H_psuedo.h"
|
||||
|
||||
#include "H_Branches.h"
|
||||
#include "H_LoadStore.h"
|
||||
|
||||
//#include "H_state.h"
|
||||
//#include "H_fp.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,487 @@
|
||||
/*
|
||||
* registers.h
|
||||
*
|
||||
* ARMv7-A system register(s).
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
/*************************************************************************************************
|
||||
* CP15 Registers for VMSA Implementation. [ref.DDI0406B B3.12]
|
||||
*************************************************************************************************/
|
||||
/*
|
||||
<CRn> <opc1> <CRm> <opc2> [NAME,] Description [Flags]
|
||||
c0 {
|
||||
0 c0 {
|
||||
0 MIDR, Main ID RO
|
||||
1 CTR, Cache Type RO
|
||||
2 TCMTR, TCM Type RO, IMPL.DEFINED
|
||||
3 TLBTR, TLB Type RO, IMPL.DEFINED
|
||||
5 MPIDR, Multiprocessor Affinity RO
|
||||
{4,6,7} MIDR$, Main ID Aliases RO
|
||||
}
|
||||
|
||||
0 c[1-7] [0-7] CPUID ID_{PFRn,DFRn,AFR0,MMFRn,ISARn} RO
|
||||
|
||||
1 c0 0 CCSIDR, Cache Size ID RO
|
||||
1 c0 1 CLIDR, Cache Level ID RO
|
||||
1 c0 7 AIDR, Aux ID RO, IMPL.DEFINED
|
||||
|
||||
2 c0 0 CSSELR, Cache Size Selection RW
|
||||
}
|
||||
|
||||
c1 0 c0 [0-2] System Control RW
|
||||
c1 0 c1 [0-2] Security Extension RW, IMPL.OPTIONAL
|
||||
|
||||
c2 0 c0 [0-2] Translation Table Base RW
|
||||
|
||||
c3 0 c0 0 DACR, Domain Access Control RW
|
||||
|
||||
c5 0 c{0,1} {0,1} Fault Status RW
|
||||
|
||||
c6 0 c0 {0,2] Fault Address RW
|
||||
|
||||
c7 0 {
|
||||
c0 4 NOP WO
|
||||
c1 {0,6} Cache Maintenance operations, Multiprocessing Extensions WO
|
||||
c4 0 PAR, Physical Address RW
|
||||
c5 {0,1,6,7} Cache and branch predictor maintenance operations WO
|
||||
c5 4 CP15ISB, Instruction barrier operation WO, USER
|
||||
c6 {1,2} Cache Maintenance operations WO
|
||||
c8 [0-7] VA to PA translation ops. WO
|
||||
c10 {1,2} Cache management ops. WO
|
||||
c10 {4,5} Data barrier ops. WO, USER
|
||||
c11 1 DCCMVAU, Cache barrier ops. WO
|
||||
c13 1 NOP WO
|
||||
c14 {1,2} Cache management ops. WO
|
||||
}
|
||||
|
||||
c8 0 c{3,5,6,7} [0-3] TLB maintenance ops. * WO
|
||||
|
||||
c9 [0-7] c{0,2,5,8} [0-7] Reserved for Branch Predictor, Cache and TCM ops. RSVD, OP.ACCESS
|
||||
c9 [0-7] c[12-15] [0-7] Reserved for Performance monitors. RSVD, OP.ACCESS
|
||||
|
||||
c10 0 c{0,1,4,8} [0-7] Reserved for TLB lockdown ops. RSVD, OP.ACCESS
|
||||
c10 0 c2 {0,1} PRRR, NMRRR, TEX Remap RW
|
||||
|
||||
c11 [0-7] c{0,8,15} [0-7] Reserved for DMA ops. TCM access. RSVD, OP.ACCESS
|
||||
|
||||
c12 0 c0 {0,1} Security Extensions RW, IMPL.OPTIONAL
|
||||
c12 0 c1 0 ISR, Security Extensions RO, IMPL.OPTIONAL
|
||||
|
||||
c13 0 c0 0 FCSEIDR, FCSE PID RO-if-FCSE-!IMPL / RW?
|
||||
c13 0 c0 [1-4] Software Thread and Context ID RW
|
||||
|
||||
c15 * * * IMPLEMENTATION DEFINED IMPL.DEFINED
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*************************************************************************************************
|
||||
* CP15 c0: ID codes registers
|
||||
*************************************************************************************************/
|
||||
|
||||
|
||||
/*
|
||||
* MIDR: Main ID Register
|
||||
*/
|
||||
struct MIDR
|
||||
{
|
||||
u32 Revision : 4;
|
||||
u32 PriPartNum : 12; // IF Impl:ARM && PriPartNo top 4bits are 0 || 7: arch&variant encoding differs
|
||||
u32 Architecture : 4;
|
||||
u32 Variant : 4;
|
||||
u32 Implementer : 8;
|
||||
};
|
||||
|
||||
enum MIDR_Implementer
|
||||
{
|
||||
ARM_Ltd = 0x41, // 'A'
|
||||
DigitalEquipment_Corp = 0x44, // 'D'
|
||||
Motorola_FreescaleSemi_Inc = 0x4D, // 'M'
|
||||
QUALCOMM_Inc = 0x51, // 'Q'
|
||||
MarvellSemi_Inc = 0x56, // 'V'
|
||||
Intel_Corp = 0x69, // 'i'
|
||||
|
||||
TexasInstruments_Inc = 0xFF // 'T' ???
|
||||
};
|
||||
|
||||
enum MIDR_Arch
|
||||
{
|
||||
ARMv4 = 1,
|
||||
ARMv4T = 2,
|
||||
ARMv5 = 3, // obselete
|
||||
ARMv5T = 4,
|
||||
ARMv5TE = 5,
|
||||
ARMv5TEJ = 6,
|
||||
ARMv6 = 7,
|
||||
CPUID_Defined = 15
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* CTR, Cache Type Register
|
||||
*/
|
||||
struct CTR
|
||||
{
|
||||
u32 IminLine : 4;
|
||||
u32 SBZ : 10;
|
||||
u32 L1Ip : 2;
|
||||
u32 DminLine : 4; //
|
||||
u32 ERG : 4; // Exclusives Reservation Granule.
|
||||
u32 CWG : 4; // Cache Writeback Granule.
|
||||
u32 RAZ : 1;
|
||||
u32 REGFMT : 3; // Set to 0b100 for ARMv7 register format, or 0b000 for <=ARMv6 format
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* TCMTR, TCM Type Register
|
||||
*/
|
||||
// High 3 bits is 0b100, the rest is IMPL.DEFINED
|
||||
typedef u32 TCMTR;
|
||||
|
||||
|
||||
/*
|
||||
* TLBTR, TLB Type Register
|
||||
*/
|
||||
// Low bit is nU : SET:1: Not unified ( separate instruction and data TLBs )
|
||||
typedef u32 TLBTR;
|
||||
|
||||
|
||||
/*
|
||||
* MPIDR, Multiprocessor Affinity Register
|
||||
*/
|
||||
|
||||
struct MPIDR
|
||||
{
|
||||
u32 AffinityLevel0 : 8;
|
||||
u32 AffinityLevel1 : 8;
|
||||
u32 AffinityLevel2 : 8;
|
||||
u32 MT : 1;
|
||||
u32 RAZ : 5; // Reserved As Zero
|
||||
u32 U : 1; // Set: Processor is part of a Uniprocessor system.
|
||||
u32 MP_Impl : 1; // RAO if MP Extensions are implemented.
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* CCSIDR, Cache Size ID Registers
|
||||
*/
|
||||
struct CCSIDR
|
||||
{
|
||||
u32 LineSize : 3;
|
||||
u32 Associativity : 10;
|
||||
u32 NumSets : 15;
|
||||
u32 WA : 1;
|
||||
u32 RA : 1;
|
||||
u32 WB : 1;
|
||||
u32 WT : 1;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* CLIDR, Cache Level ID Register
|
||||
*/
|
||||
struct CLIDR
|
||||
{
|
||||
u32 Ctype1 : 3;
|
||||
u32 Ctype2 : 3;
|
||||
u32 Ctype3 : 3;
|
||||
u32 Ctype4 : 3;
|
||||
u32 Ctype5 : 3;
|
||||
u32 Ctype6 : 3;
|
||||
u32 Ctype7 : 3;
|
||||
u32 LoUIS : 3;
|
||||
u32 LoC : 3;
|
||||
u32 LoUU : 3;
|
||||
u32 RAZ : 2; // RAZ
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* AIDR, Auxiliary ID Register.
|
||||
*/
|
||||
typedef u32 AIDR; // IMPLEMENTATION DEFINED
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* CSSELR, Cache Size Selection Register
|
||||
*/
|
||||
struct CSSELR
|
||||
{
|
||||
u32 InD : 1;
|
||||
u32 Level : 3;
|
||||
u32 SBZP : 28;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*************************************************************************************************
|
||||
* CP15 c1: System control registers
|
||||
*************************************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
// SCTRL, ACTLR ////////////////////// TODO ///////////////////////
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* CPACR: Coprocessor Access Control Register.
|
||||
*
|
||||
* Controls access to all coprocessors other than CP14 & CP15.
|
||||
* It may be used to check for their presence by testing modification to cpN bits.
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* D32DIS:1 && ASEDIS:0 is INVALID
|
||||
* ASEDIS on hw { w. VFP & w.o A.SIMD } is RAO/WI, if bit is not supported it is RAZ/WI.
|
||||
*
|
||||
* When Security Extensions are enabled, NSACR controls CP access from non-secure state.
|
||||
*
|
||||
* VFP uses CP10 && CP11, the values of .cp10 && .cp11 should be the same.
|
||||
*/
|
||||
union CPACR
|
||||
{
|
||||
struct {
|
||||
u32 cp0 : 2; // cpN [0-13]:
|
||||
u32 cp1 : 2; // Defines access rights for individual coprocessors.
|
||||
u32 cp2 : 2; // See CP_Access enum below for possible values;
|
||||
u32 cp3 : 2; //
|
||||
u32 cp4 : 2; // To test
|
||||
u32 cp5 : 2;
|
||||
u32 cp6 : 2;
|
||||
u32 cp7 : 2;
|
||||
u32 cp8 : 2;
|
||||
u32 cp9 : 2;
|
||||
u32 cp10 : 2;
|
||||
u32 cp11 : 2;
|
||||
u32 cp12 : 2;
|
||||
u32 cp13 : 2;
|
||||
u32 rsvcd: 2; // SBZP
|
||||
u32 D32DIS:1; // SET: Disables use of D16-D32 of the VFP register file.
|
||||
u32 ASEDIS:1; // SET: Disables all A.SIMD Instructions, VFPv3 shall remain valid.
|
||||
};
|
||||
|
||||
u32 R;
|
||||
};
|
||||
|
||||
/*
|
||||
* CP_Access: Enumerates access rights for CPACR.cpN
|
||||
*
|
||||
*/
|
||||
enum CP_Access
|
||||
{
|
||||
A_Deny, // Deny Access, Attempts to access cause Exception::Undefined_Instruction
|
||||
A_Privileged, // Privileged Access, Attempts to access cause Exception::Undefined_Instruction in User mode.
|
||||
A_Reserved, // Reserved Value, Use of this value is UNPREDICTABLE.
|
||||
A_Full // Full Access, Access is defined by coprocessor.
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* SCR: Secure Configuration Register.
|
||||
*
|
||||
* Requires: Security Extension.
|
||||
*/
|
||||
union SCR
|
||||
{
|
||||
struct {
|
||||
u32 NS : 1; //
|
||||
u32 IRQ : 1; //
|
||||
u32 FIQ : 1; //
|
||||
u32 EA : 1; //
|
||||
u32 FW : 1; //
|
||||
u32 AW : 1; //
|
||||
u32 nET : 1; //
|
||||
u32 SBZP:25;
|
||||
};
|
||||
|
||||
u32 R;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// SDER, Secure Debug Enable Register
|
||||
|
||||
// NSACR, Non-Secure Access Control Register
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*************************************************************************************************
|
||||
* CP15 c{2,3}: Memory protection and control registers
|
||||
*************************************************************************************************/
|
||||
|
||||
|
||||
// TTBR0 TTVR1 TTVCR
|
||||
// DACR, Domain Access Control Register
|
||||
|
||||
|
||||
|
||||
/*************************************************************************************************
|
||||
* CP15 c4: Not used
|
||||
*************************************************************************************************/
|
||||
|
||||
|
||||
|
||||
/*************************************************************************************************
|
||||
* CP15 c{5,6}: Memory system fault registers
|
||||
*************************************************************************************************/
|
||||
|
||||
// DFSR, Data Fault Status Register
|
||||
// IFSR, Instruction Fault Status Register
|
||||
// ADFSR, Aux. DFSR
|
||||
// AIFSR, Aux. IFSR
|
||||
// DFAR, Data Fault Address Register
|
||||
// IFAR, Instruction Fault Address Register
|
||||
|
||||
|
||||
|
||||
|
||||
/*************************************************************************************************
|
||||
* CP15 c7: Cache maintenance / misc
|
||||
*************************************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*************************************************************************************************
|
||||
*************************************************************************************************
|
||||
* A.SIMD and VFP extension system registers
|
||||
*************************************************************************************************
|
||||
*************************************************************************************************/
|
||||
|
||||
enum FP_SysRegs
|
||||
{
|
||||
R_FPSID = 0, // 0b0000
|
||||
R_MVFR1 = 6, // 0b0110
|
||||
R_MVFR0 = 7, // 0b0111
|
||||
};
|
||||
|
||||
|
||||
// FPSID Floating Point System ID Register
|
||||
// MVFR1 Media and VFP Feature Register 1
|
||||
// MVFR0 Media and VFP Feature Register 0
|
||||
|
||||
struct FPSID
|
||||
{
|
||||
u32 Revision : 4; // IMPL.DEFINED
|
||||
u32 Variant : 4; // IMPL.DEFINED
|
||||
u32 PartNumber : 8; // IMPL.DEFINED
|
||||
u32 SubArch : 7; // MSB:1 when designed by ARM
|
||||
u32 SW : 1; // Is a software impl. if set
|
||||
u32 Implementer : 8; // Same as MIDR.Implementer
|
||||
};
|
||||
|
||||
enum FP_SubArch
|
||||
{
|
||||
VFPv1 = 0, // Not Permitted in ARMv7
|
||||
VFPv2_Cv1, // Not Permitted in ARMv7
|
||||
VFPv3_Cv2, //
|
||||
VFPv3_Null, // Full hardware, no trap
|
||||
VFPv3_Cv3, //
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Floating-point status and control register
|
||||
//
|
||||
struct FPSCR
|
||||
{
|
||||
u32 IOC : 1; // * All * bits are cumulative exception bits
|
||||
u32 DZC : 1; // *
|
||||
u32 OFC : 1; // *
|
||||
u32 UFC : 1; // *
|
||||
u32 IXC : 1; // *
|
||||
u32 SBZP1 : 2; //
|
||||
u32 IDC : 1; // *
|
||||
u32 IOE : 1; // ** All ** bits are FP trap enable bits
|
||||
u32 DZE : 1; // ** only supported in VFPv2 && VFPv3U
|
||||
u32 OFE : 1; // ** - RAZ elsewhere -
|
||||
u32 UFE : 1; // **
|
||||
u32 IXE : 1; // **
|
||||
u32 SBZP2 : 2; //
|
||||
u32 IDE : 1; // **
|
||||
u32 Len : 3; // SBZ for ARMv7 VFP, ignored for A.SIMD
|
||||
u32 SBZP3 : 1; //
|
||||
u32 Stride : 2; // SBZ for ARMv7 VFP, ignored for A.SIMD
|
||||
u32 RMode : 2; // Rounding Mode
|
||||
u32 FZ : 1; // Flush-to-Zero
|
||||
u32 DN : 1; // Default NaN mode control
|
||||
u32 AHP : 1; // Alt. Half-precision
|
||||
u32 QC : 1; // Cumulative saturation, A.SIMD
|
||||
u32 V : 1; // CC Overflow
|
||||
u32 C : 1; // CC Carry
|
||||
u32 Z : 1; // CC Zero
|
||||
u32 N : 1; // CC Negative
|
||||
};
|
||||
|
||||
enum FP_RoundingMode // A.SIMD Always uses RN !
|
||||
{
|
||||
RN, // Round to Nearest
|
||||
RP, // Round towards Plus Infinity
|
||||
RM, // Round towards Minus Infinity
|
||||
RZ // Round towards Zero
|
||||
};
|
||||
|
||||
|
||||
struct MVFR0
|
||||
{
|
||||
u32 A_SIMD : 4;
|
||||
u32 Single : 4;
|
||||
u32 Double : 4;
|
||||
u32 Trap : 4;
|
||||
u32 Divide : 4;
|
||||
u32 Sqrt : 4;
|
||||
u32 ShortVec: 4;
|
||||
u32 Rounding: 4;
|
||||
};
|
||||
|
||||
struct MVFR1
|
||||
{
|
||||
u32 FtZ_mode : 4;
|
||||
u32 D_NaN_mode : 4;
|
||||
u32 NFP_LdStr : 4;
|
||||
u32 NFP_int : 4;
|
||||
u32 NFP_SPFP : 4;
|
||||
u32 NFP_HPFP : 4;
|
||||
u32 VFP_HPFP : 4;
|
||||
u32 RAZ : 4;
|
||||
|
||||
};
|
||||
};
|
||||
@@ -1,17 +0,0 @@
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
audiostream.h)
|
||||
|
||||
if(NOT LIBRETRO)
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
audiobackend_alsa.cpp
|
||||
audiobackend_coreaudio.cpp
|
||||
audiobackend_directsound.cpp
|
||||
audiobackend_libao.cpp
|
||||
audiobackend_null.cpp
|
||||
audiobackend_oboe.cpp
|
||||
audiobackend_omx.cpp
|
||||
audiobackend_oss.cpp
|
||||
audiobackend_pulseaudio.cpp
|
||||
audiobackend_sdl2.cpp
|
||||
audiostream.cpp)
|
||||
endif()
|
||||
@@ -1,358 +0,0 @@
|
||||
#if USE_ALSA
|
||||
#include "audiostream.h"
|
||||
#include <alsa/asoundlib.h>
|
||||
#include "cfg/cfg.h"
|
||||
#include "cfg/option.h"
|
||||
|
||||
class AlsaAudioBackend : public AudioBackend
|
||||
{
|
||||
snd_pcm_t *handle = nullptr;
|
||||
bool pcm_blocking = true;
|
||||
snd_pcm_uframes_t buffer_size = 0;
|
||||
snd_pcm_uframes_t period_size = 0;
|
||||
snd_pcm_t *handle_record = nullptr;
|
||||
|
||||
public:
|
||||
AlsaAudioBackend()
|
||||
: AudioBackend("alsa", "Advanced Linux Sound Architecture") {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
snd_pcm_hw_params_t *params;
|
||||
|
||||
std::string device = config::loadStr("alsa", "device");
|
||||
|
||||
int rc = -1;
|
||||
if (!device.empty() && device != "auto") {
|
||||
rc = snd_pcm_open(&handle, device.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
|
||||
if (rc < 0)
|
||||
WARN_LOG(AUDIO, "ALSA: Cannot open device %s. Trying auto", device.c_str());
|
||||
}
|
||||
if (rc < 0)
|
||||
{
|
||||
INFO_LOG(AUDIO, "ALSA: trying to determine audio device");
|
||||
|
||||
// trying default device
|
||||
device = "default";
|
||||
rc = snd_pcm_open(&handle, device.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
|
||||
|
||||
// "default" didn't work, try first device
|
||||
if (rc < 0)
|
||||
{
|
||||
device = "plughw:0,0,0";
|
||||
rc = snd_pcm_open(&handle, device.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
|
||||
|
||||
if (rc < 0)
|
||||
{
|
||||
device = "plughw:0,0";
|
||||
rc = snd_pcm_open(&handle, device.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// first didn't work, try second
|
||||
if (rc < 0)
|
||||
{
|
||||
device = "plughw:1,0";
|
||||
rc = snd_pcm_open(&handle, device.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
|
||||
}
|
||||
|
||||
// try pulse audio backend
|
||||
if (rc < 0)
|
||||
{
|
||||
device = "pulse";
|
||||
rc = snd_pcm_open(&handle, device.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
|
||||
}
|
||||
|
||||
if (rc < 0)
|
||||
INFO_LOG(AUDIO, "ALSA: unable to automatically determine audio device.");
|
||||
}
|
||||
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: unable to open PCM device %s: %s", device.c_str(), snd_strerror(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
INFO_LOG(AUDIO, "ALSA: Successfully initialized \"%s\"", device.c_str());
|
||||
|
||||
/* Allocate a hardware parameters object. */
|
||||
snd_pcm_hw_params_alloca(¶ms);
|
||||
|
||||
/* Fill it in with default values. */
|
||||
rc=snd_pcm_hw_params_any(handle, params);
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Error:snd_pcm_hw_params_any %s", snd_strerror(rc));
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Set the desired hardware parameters. */
|
||||
|
||||
/* Interleaved mode */
|
||||
rc=snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Error:snd_pcm_hw_params_set_access %s", snd_strerror(rc));
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Signed 16-bit little-endian format */
|
||||
rc=snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Error:snd_pcm_hw_params_set_format %s", snd_strerror(rc));
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Two channels (stereo) */
|
||||
rc=snd_pcm_hw_params_set_channels(handle, params, 2);
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Error:snd_pcm_hw_params_set_channels %s", snd_strerror(rc));
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 44100 samples/second
|
||||
rc = snd_pcm_hw_params_set_rate(handle, params, 44100, 0);
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Error:snd_pcm_hw_params_set_rate %s", snd_strerror(rc));
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Period size (512)
|
||||
period_size = std::min(SAMPLE_COUNT, (u32)config::AudioBufferSize / 4);
|
||||
rc = snd_pcm_hw_params_set_period_size_near(handle, params, &period_size, nullptr);
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Error:snd_pcm_hw_params_set_periods_near %s", snd_strerror(rc));
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
INFO_LOG(AUDIO, "ALSA: period size set to %zd", (size_t)period_size);
|
||||
|
||||
// Sample buffer size
|
||||
buffer_size = config::AudioBufferSize;
|
||||
rc = snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_size);
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Error:snd_pcm_hw_params_set_buffer_size_near %s", snd_strerror(rc));
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
INFO_LOG(AUDIO, "ALSA: buffer size set to %ld", buffer_size);
|
||||
|
||||
/* Write the parameters to the driver */
|
||||
rc = snd_pcm_hw_params(handle, params);
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Unable to set hw parameters: %s", snd_strerror(rc));
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initRecord(u32 sampling_freq) override
|
||||
{
|
||||
int err;
|
||||
if ((err = snd_pcm_open(&handle_record, "default", SND_PCM_STREAM_CAPTURE, 0)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot open default audio capture device: %s", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
snd_pcm_hw_params_t *hw_params;
|
||||
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot allocate hardware parameter structure: %s", snd_strerror(err));
|
||||
snd_pcm_close(handle_record);
|
||||
return false;
|
||||
}
|
||||
if ((err = snd_pcm_hw_params_any(handle_record, hw_params)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot initialize hardware parameter structure: %s", snd_strerror(err));
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
snd_pcm_close(handle_record);
|
||||
return false;
|
||||
}
|
||||
if ((err = snd_pcm_hw_params_set_access(handle_record, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot set access type: %s\n", snd_strerror(err));
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
snd_pcm_close(handle_record);
|
||||
return false;
|
||||
}
|
||||
if ((err = snd_pcm_hw_params_set_format(handle_record, hw_params, SND_PCM_FORMAT_S16_LE)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot set sample format: %s", snd_strerror(err));
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
snd_pcm_close(handle_record);
|
||||
return false;
|
||||
}
|
||||
if ((err = snd_pcm_hw_params_set_rate(handle_record, hw_params, sampling_freq, 0)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot set sample rate to %d Hz: %s", sampling_freq, snd_strerror(err));
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
snd_pcm_close(handle_record);
|
||||
return false;
|
||||
}
|
||||
if ((err = snd_pcm_hw_params_set_channels(handle_record, hw_params, 1)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot set channel count: %s", snd_strerror(err));
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
snd_pcm_close(handle_record);
|
||||
return false;
|
||||
}
|
||||
if ((err = snd_pcm_hw_params(handle_record, hw_params)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot set parameters: %s", snd_strerror(err));
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
snd_pcm_close(handle_record);
|
||||
return false;
|
||||
}
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
snd_pcm_nonblock(handle_record, 1);
|
||||
if ((err = snd_pcm_prepare(handle_record)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot prepare device: %s", snd_strerror(err));
|
||||
snd_pcm_close(handle_record);
|
||||
return false;
|
||||
}
|
||||
INFO_LOG(AUDIO, "ALSA: Successfully initialized capture device");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void termRecord() override
|
||||
{
|
||||
snd_pcm_close(handle_record);
|
||||
}
|
||||
|
||||
u32 record(void* frame, u32 samples) override
|
||||
{
|
||||
int err = snd_pcm_readi(handle_record, frame, samples);
|
||||
if (err < (int)samples)
|
||||
{
|
||||
if (err < 0)
|
||||
{
|
||||
DEBUG_LOG(AUDIO, "ALSA: Recording error: %s", snd_strerror(err));
|
||||
err = 0;
|
||||
err = snd_pcm_prepare(handle_record);
|
||||
}
|
||||
u8 *buffer = (u8 *)frame + err;
|
||||
memset(buffer, 0, (samples - err) * 2);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
if (wait != pcm_blocking) {
|
||||
snd_pcm_nonblock(handle, wait ? 0 : 1);
|
||||
pcm_blocking = wait;
|
||||
}
|
||||
|
||||
int rc = snd_pcm_writei(handle, frame, samples);
|
||||
if (rc < 0)
|
||||
{
|
||||
snd_pcm_recover(handle, rc, 1);
|
||||
if (rc == -EPIPE)
|
||||
{
|
||||
// EPIPE means underrun
|
||||
// Write some silence then our samples
|
||||
const size_t silence_size = buffer_size - samples;
|
||||
void *silence = alloca(silence_size * 4);
|
||||
memset(silence, 0, silence_size * 4);
|
||||
snd_pcm_writei(handle, silence, silence_size);
|
||||
snd_pcm_writei(handle, frame, samples);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
snd_pcm_drop(handle);
|
||||
snd_pcm_close(handle);
|
||||
}
|
||||
|
||||
std::vector<std::string> getDeviceList()
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
|
||||
char **hints;
|
||||
int err = snd_device_name_hint(-1, "pcm", (void***)&hints);
|
||||
|
||||
// Error initializing ALSA
|
||||
if (err != 0)
|
||||
return result;
|
||||
|
||||
// special value to automatically detect on initialization
|
||||
result.emplace_back("auto");
|
||||
|
||||
char** n = hints;
|
||||
while (*n != NULL)
|
||||
{
|
||||
// Get the type (NULL/Input/Output)
|
||||
char *type = snd_device_name_get_hint(*n, "IOID");
|
||||
char *name = snd_device_name_get_hint(*n, "NAME");
|
||||
|
||||
if (name != NULL)
|
||||
{
|
||||
// We only want output or special devices (like "default" or "pulse")
|
||||
// TODO Only those with type == NULL?
|
||||
if (type == NULL || strcmp(type, "Output") == 0)
|
||||
{
|
||||
// TODO Check if device works (however we need to hash the resulting list then)
|
||||
/*snd_pcm_t *handle;
|
||||
int rc = snd_pcm_open(&handle, name, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
|
||||
if (rc == 0)
|
||||
{
|
||||
result.push_back(name);
|
||||
snd_pcm_close(handle);
|
||||
}
|
||||
*/
|
||||
|
||||
result.emplace_back(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (type != NULL)
|
||||
free(type);
|
||||
|
||||
if (name != NULL)
|
||||
free(name);
|
||||
|
||||
n++;
|
||||
}
|
||||
|
||||
snd_device_name_free_hint((void**)hints);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Option* getOptions(int *count) override
|
||||
{
|
||||
*count = 1;
|
||||
static Option result;
|
||||
|
||||
result.name = "device";
|
||||
result.caption = "Device";
|
||||
result.type = Option::list;
|
||||
result.values = getDeviceList();
|
||||
|
||||
return &result;
|
||||
}
|
||||
};
|
||||
static AlsaAudioBackend alsaBackend;
|
||||
|
||||
#endif
|
||||
@@ -1,308 +0,0 @@
|
||||
/*
|
||||
Simple Core Audio backend for osx (and maybe ios?)
|
||||
Based off various audio core samples and dolphin's code
|
||||
|
||||
This is part of the Reicast project, please consult the
|
||||
LICENSE file for licensing & related information
|
||||
|
||||
This could do with some locking logic to avoid
|
||||
race conditions, and some variable length buffer
|
||||
logic to support chunk sizes other than 512 bytes
|
||||
|
||||
It does work on my macmini though
|
||||
*/
|
||||
#if defined(__APPLE__)
|
||||
#include "audiostream.h"
|
||||
#include "cfg/option.h"
|
||||
#include "stdclass.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include <AudioToolbox/AudioQueue.h>
|
||||
|
||||
class CoreAudioBackend : public AudioBackend
|
||||
{
|
||||
AudioUnit audioUnit;
|
||||
|
||||
u32 BUFSIZE = 0;
|
||||
u8 *samples_temp = nullptr;
|
||||
|
||||
std::atomic<int> samples_wptr;
|
||||
std::atomic<int> samples_rptr;
|
||||
cResetEvent bufferEmpty;
|
||||
|
||||
// input buffer and indexes
|
||||
u8 samples_input[2400];
|
||||
static constexpr size_t InputBufSize = sizeof(samples_input);
|
||||
std::atomic<int> input_wptr;
|
||||
std::atomic<int> input_rptr;
|
||||
AudioQueueRef recordQueue;
|
||||
|
||||
static OSStatus renderCallback(void* ctx, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* ts,
|
||||
UInt32 bus, UInt32 frames, AudioBufferList* abl)
|
||||
{
|
||||
CoreAudioBackend *backend = (CoreAudioBackend *)ctx;
|
||||
for (int i = 0; i < abl->mNumberBuffers; i++)
|
||||
{
|
||||
int size = abl->mBuffers[i].mDataByteSize;
|
||||
u8 *outBuffer = (u8 *)abl->mBuffers[i].mData;
|
||||
while (size != 0)
|
||||
{
|
||||
int avail = (backend->samples_wptr - backend->samples_rptr + backend->BUFSIZE) % backend->BUFSIZE;
|
||||
if (avail == 0)
|
||||
{
|
||||
//printf("Core Audio: buffer underrun %d bytes (%d)", size, abl->mBuffers[i].mDataByteSize);
|
||||
memset(outBuffer, '\0', size);
|
||||
return noErr;
|
||||
}
|
||||
avail = std::min(avail, size);
|
||||
avail = std::min(avail, (int)backend->BUFSIZE - backend->samples_rptr);
|
||||
memcpy(outBuffer, backend->samples_temp + backend->samples_rptr, avail);
|
||||
backend->samples_rptr = (backend->samples_rptr + avail) % backend->BUFSIZE;
|
||||
size -= avail;
|
||||
outBuffer += avail;
|
||||
// Set the mutex to allow writing
|
||||
backend->bufferEmpty.Set();
|
||||
}
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
public:
|
||||
CoreAudioBackend()
|
||||
: AudioBackend("coreaudio", "Core Audio") {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
OSStatus err;
|
||||
AURenderCallbackStruct callback_struct;
|
||||
AudioStreamBasicDescription format;
|
||||
AudioComponentDescription desc;
|
||||
AudioComponent component;
|
||||
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
#if !defined(TARGET_IPHONE)
|
||||
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
|
||||
#else
|
||||
desc.componentSubType = kAudioUnitSubType_RemoteIO;
|
||||
#endif
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
component = AudioComponentFindNext(nullptr, &desc);
|
||||
|
||||
if (component == nullptr) {
|
||||
ERROR_LOG(AUDIO, "coreaudio: AudioComponentFindNext failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
err = AudioComponentInstanceNew(component, &audioUnit);
|
||||
if (err != noErr) {
|
||||
ERROR_LOG(AUDIO, "coreaudio: AudioComponentInstanceNew failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
FillOutASBDForLPCM(format, 44100,
|
||||
2, 16, 16, false, false, false);
|
||||
err = AudioUnitSetProperty(audioUnit,
|
||||
kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0, &format,
|
||||
sizeof(AudioStreamBasicDescription));
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "coreaudio: AudioUnitSetProperty(kAudioUnitProperty_StreamFormat) failed");
|
||||
AudioComponentInstanceDispose(audioUnit);
|
||||
return false;
|
||||
}
|
||||
|
||||
callback_struct.inputProc = renderCallback;
|
||||
callback_struct.inputProcRefCon = this;
|
||||
err = AudioUnitSetProperty(audioUnit,
|
||||
kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0, &callback_struct,
|
||||
sizeof callback_struct);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "coreaudio: AudioUnitSetProperty(kAudioUnitProperty_SetRenderCallback) failed");
|
||||
AudioComponentInstanceDispose(audioUnit);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
err = AudioUnitSetParameter(audioUnit,
|
||||
kHALOutputParam_Volume,
|
||||
kAudioUnitParameterFlag_Output, 0,
|
||||
1, 0);
|
||||
verify(err == noErr);
|
||||
*/
|
||||
|
||||
err = AudioUnitInitialize(audioUnit);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "coreaudio: AudioUnitInitialize failed");
|
||||
AudioComponentInstanceDispose(audioUnit);
|
||||
return false;
|
||||
}
|
||||
|
||||
BUFSIZE = config::AudioBufferSize * 4;
|
||||
samples_temp = new u8[BUFSIZE]();
|
||||
samples_rptr = 0;
|
||||
samples_wptr = 0;
|
||||
|
||||
err = AudioOutputUnitStart(audioUnit);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "coreaudio: AudioOutputUnitStart failed");
|
||||
AudioUnitUninitialize(audioUnit);
|
||||
AudioComponentInstanceDispose(audioUnit);
|
||||
return false;
|
||||
}
|
||||
|
||||
bufferEmpty.Set();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
int size = samples * 4;
|
||||
while (size != 0)
|
||||
{
|
||||
int avail = (samples_rptr - samples_wptr - 4 + BUFSIZE) % BUFSIZE;
|
||||
if (avail == 0)
|
||||
{
|
||||
if (!wait)
|
||||
break;
|
||||
bufferEmpty.Wait();
|
||||
continue;
|
||||
}
|
||||
avail = std::min(avail, size);
|
||||
avail = std::min(avail, (int)BUFSIZE - samples_wptr);
|
||||
memcpy(&samples_temp[samples_wptr], frame, avail);
|
||||
samples_wptr = (samples_wptr + avail) % BUFSIZE;
|
||||
frame = (u8 *)frame + avail;
|
||||
size -= avail;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
AudioOutputUnitStop(audioUnit);
|
||||
AudioUnitUninitialize(audioUnit);
|
||||
AudioComponentInstanceDispose(audioUnit);
|
||||
bufferEmpty.Set();
|
||||
delete [] samples_temp;
|
||||
samples_temp = nullptr;
|
||||
}
|
||||
|
||||
static void recordCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
|
||||
const AudioTimeStamp *inStartTime, UInt32 frameSize, const AudioStreamPacketDescription *dataFormat)
|
||||
{
|
||||
//DEBUG_LOG(AUDIO, "AudioQueue callback: wptr %d rptr %d bytes %d", (int)input_wptr, (int)input_rptr, inBuffer->mAudioDataByteSize);
|
||||
CoreAudioBackend *backend = (CoreAudioBackend *)inUserData;
|
||||
UInt32 size = inBuffer->mAudioDataByteSize;
|
||||
UInt32 freeSpace = (backend->input_rptr - backend->input_wptr - 2 + InputBufSize) % InputBufSize;
|
||||
if (size > freeSpace)
|
||||
{
|
||||
DEBUG_LOG(AUDIO, "coreaudio: record overrun %d bytes", size - freeSpace);
|
||||
size = freeSpace;
|
||||
}
|
||||
while (size != 0)
|
||||
{
|
||||
UInt32 chunk = std::min(size, (UInt32)(InputBufSize - backend->input_wptr));
|
||||
memcpy(backend->samples_input + backend->input_wptr, inBuffer->mAudioData, chunk);
|
||||
backend->input_wptr = (backend->input_wptr + chunk) % InputBufSize;
|
||||
size -= chunk;
|
||||
}
|
||||
AudioQueueEnqueueBuffer(backend->recordQueue, inBuffer, 0, nullptr);
|
||||
}
|
||||
|
||||
void termRecord() override
|
||||
{
|
||||
if (recordQueue != nullptr)
|
||||
{
|
||||
AudioQueueStop(recordQueue, true);
|
||||
AudioQueueDispose(recordQueue, true);
|
||||
recordQueue = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool initRecord(u32 sampling_freq) override
|
||||
{
|
||||
AudioStreamBasicDescription desc{};
|
||||
desc.mFormatID = kAudioFormatLinearPCM;
|
||||
desc.mSampleRate = (double)sampling_freq;
|
||||
desc.mChannelsPerFrame = 1;
|
||||
desc.mBitsPerChannel = 16;
|
||||
desc.mBytesPerPacket = desc.mBytesPerFrame = 2;
|
||||
desc.mFramesPerPacket = 1;
|
||||
desc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
|
||||
desc.mReserved = 0;
|
||||
|
||||
OSStatus err = AudioQueueNewInput(&desc,
|
||||
recordCallback,
|
||||
this,
|
||||
nullptr,
|
||||
kCFRunLoopCommonModes,
|
||||
0,
|
||||
&recordQueue);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "AudioQueueNewInput failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioQueueBufferRef buffers[2];
|
||||
for (UInt32 i = 0; i < std::size(buffers) && err == noErr; i++)
|
||||
{
|
||||
err = AudioQueueAllocateBuffer(recordQueue, 480, &buffers[i]);
|
||||
if (err == noErr)
|
||||
err = AudioQueueEnqueueBuffer(recordQueue, buffers[i], 0, nullptr);
|
||||
}
|
||||
input_wptr = 0;
|
||||
input_rptr = 0;
|
||||
if (err == noErr)
|
||||
err = AudioQueueStart(recordQueue, nullptr);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "AudioQueue init failed: %d", err);
|
||||
termRecord();
|
||||
return false;
|
||||
}
|
||||
INFO_LOG(AUDIO, "AudioQueue initialized - sample rate %f", desc.mSampleRate);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 record(void* frame, u32 samples) override
|
||||
{
|
||||
// DEBUG_LOG(AUDIO, "coreaudio_record: wptr %d rptr %d", (int)input_wptr, (int)input_rptr);
|
||||
u32 size = samples * 2;
|
||||
while (size != 0)
|
||||
{
|
||||
u32 avail = (input_wptr - input_rptr + InputBufSize) % InputBufSize;
|
||||
if (avail == 0)
|
||||
{
|
||||
DEBUG_LOG(AUDIO, "coreaudio: record underrun %d bytes", size);
|
||||
break;
|
||||
}
|
||||
avail = std::min(avail, size);
|
||||
avail = std::min(avail, (u32)(InputBufSize - input_rptr));
|
||||
|
||||
memcpy(frame, &samples_input[input_rptr], avail);
|
||||
frame = (u8 *)frame + avail;
|
||||
input_rptr = (input_rptr + avail) % InputBufSize;
|
||||
size -= avail;
|
||||
}
|
||||
|
||||
return samples - size / 2;
|
||||
}
|
||||
};
|
||||
static CoreAudioBackend coreAudioBackend;
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,242 +0,0 @@
|
||||
#include "build.h"
|
||||
#if defined(_WIN32) && !defined(TARGET_UWP)
|
||||
#include "audiostream.h"
|
||||
#include "cfg/option.h"
|
||||
#include <initguid.h>
|
||||
#include <dsound.h>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include "stdclass.h"
|
||||
#include "windows/comptr.h"
|
||||
#include "oslib/oslib.h"
|
||||
|
||||
HWND getNativeHwnd();
|
||||
#define verifyc(x) verify(!FAILED(x))
|
||||
|
||||
#ifdef __CRT_UUID_DECL
|
||||
__CRT_UUID_DECL(IDirectSoundBuffer8, 0x6825A449,0x7524,0x4D82,0x92,0x0F,0x50,0xE3,0x6A,0xB3,0xAB,0x1E);
|
||||
__CRT_UUID_DECL(IDirectSoundNotify, 0xB0210783,0x89cd,0x11d0,0xAF,0x08,0x00,0xA0,0xC9,0x25,0xCD,0x16);
|
||||
__CRT_UUID_DECL(IDirectSoundCaptureBuffer8, 0x00990DF4,0x0DBB,0x4872,0x83,0x3E,0x6D,0x30,0x3E,0x80,0xAE,0xB6);
|
||||
#else
|
||||
struct __declspec(uuid("{6825A449-7524-4D82-920F-50E36AB3AB1E}")) IDirectSoundBuffer8;
|
||||
struct __declspec(uuid("{B0210783-89cd-11d0-AF08-00A0C925CD16}")) IDirectSoundNotify;
|
||||
struct __declspec(uuid("{00990DF4-0DBB-4872-833E-6D303E80AEB6}")) IDirectSoundCaptureBuffer8;
|
||||
#endif
|
||||
|
||||
class DirectSoundBackend : public AudioBackend
|
||||
{
|
||||
ComPtr<IDirectSound8> dsound;
|
||||
ComPtr<IDirectSoundBuffer8> buffer;
|
||||
std::vector<HANDLE> notificationEvents;
|
||||
|
||||
ComPtr<IDirectSoundCapture8> dcapture;
|
||||
ComPtr<IDirectSoundCaptureBuffer8> capture_buffer;
|
||||
|
||||
std::atomic_bool audioThreadRunning;
|
||||
std::thread audioThread;
|
||||
cResetEvent pushWait;
|
||||
|
||||
static constexpr u32 SAMPLE_BYTES = SAMPLE_COUNT * 4;
|
||||
|
||||
RingBuffer ringBuffer;
|
||||
|
||||
u32 notificationOffset(int index) {
|
||||
return index * SAMPLE_BYTES;
|
||||
}
|
||||
|
||||
void audioThreadMain()
|
||||
{
|
||||
ThreadName _("FlyDirectSound");
|
||||
audioThreadRunning = true;
|
||||
while (true)
|
||||
{
|
||||
u32 rv = WaitForMultipleObjects(notificationEvents.size(), ¬ificationEvents[0], false, 100);
|
||||
|
||||
if (!audioThreadRunning)
|
||||
break;
|
||||
if (rv == WAIT_TIMEOUT || rv == WAIT_FAILED)
|
||||
continue;
|
||||
rv -= WAIT_OBJECT_0;
|
||||
|
||||
void *p1, *p2;
|
||||
DWORD sz1, sz2;
|
||||
|
||||
if (SUCCEEDED(buffer->Lock(notificationOffset(rv), SAMPLE_BYTES, &p1, &sz1, &p2, &sz2, 0)))
|
||||
{
|
||||
if (!ringBuffer.read((u8*)p1, sz1))
|
||||
memset(p1, 0, sz1);
|
||||
if (sz2 != 0)
|
||||
{
|
||||
if (!ringBuffer.read((u8*)p2, sz2))
|
||||
memset(p2, 0, sz2);
|
||||
}
|
||||
buffer->Unlock(p1, sz1, p2, sz2);
|
||||
pushWait.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
DirectSoundBackend()
|
||||
: AudioBackend("directsound", "Microsoft DirectSound") {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
if (FAILED(DirectSoundCreate8(NULL, &dsound.get(), NULL))) {
|
||||
ERROR_LOG(AUDIO, "DirectSound8 initialization failed");
|
||||
return false;
|
||||
}
|
||||
if (FAILED(dsound->SetCooperativeLevel(getNativeHwnd(), DSSCL_PRIORITY))) {
|
||||
ERROR_LOG(AUDIO, "DirectSound8 SetCooperativeLevel failed");
|
||||
dsound.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up WAV format structure.
|
||||
WAVEFORMATEX wfx;
|
||||
memset(&wfx, 0, sizeof(WAVEFORMATEX));
|
||||
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
||||
wfx.nChannels = 2;
|
||||
wfx.nSamplesPerSec = 44100;
|
||||
wfx.nBlockAlign = 4;
|
||||
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
||||
wfx.wBitsPerSample = 16;
|
||||
|
||||
// Set up DSBUFFERDESC structure.
|
||||
DSBUFFERDESC desc;
|
||||
memset(&desc, 0, sizeof(DSBUFFERDESC));
|
||||
desc.dwSize = sizeof(DSBUFFERDESC);
|
||||
desc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS;
|
||||
desc.dwBufferBytes = SAMPLE_BYTES * 2;
|
||||
desc.lpwfxFormat = &wfx;
|
||||
|
||||
// Create the buffer
|
||||
ComPtr<IDirectSoundBuffer> buffer_;
|
||||
if (FAILED(dsound->CreateSoundBuffer(&desc, &buffer_.get(), 0))
|
||||
|| FAILED(buffer_.as(buffer)))
|
||||
{
|
||||
ERROR_LOG(AUDIO, "DirectSound8 CreateSoundBuffer failed");
|
||||
dsound.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up notifications
|
||||
ComPtr<IDirectSoundNotify> bufferNotify;
|
||||
verifyc(buffer.as(bufferNotify));
|
||||
notificationEvents.clear();
|
||||
std::vector<DSBPOSITIONNOTIFY> posNotify;
|
||||
for (int i = 0; notificationOffset(i) < desc.dwBufferBytes; i++)
|
||||
{
|
||||
notificationEvents.push_back(CreateEvent(nullptr, false, false, nullptr));
|
||||
posNotify.push_back({ notificationOffset(i), notificationEvents.back() });
|
||||
}
|
||||
bufferNotify->SetNotificationPositions(posNotify.size(), &posNotify[0]);
|
||||
|
||||
// Clear the buffers
|
||||
void *p1, *p2;
|
||||
DWORD sz1, sz2;
|
||||
verifyc(buffer->Lock(0, desc.dwBufferBytes, &p1, &sz1, &p2, &sz2, 0));
|
||||
verify(p2 == nullptr);
|
||||
memset(p1, 0, sz1);
|
||||
verifyc(buffer->Unlock(p1, sz1, p2, sz2));
|
||||
ringBuffer.setCapacity(config::AudioBufferSize * 4);
|
||||
|
||||
// Start the thread
|
||||
audioThread = std::thread(&DirectSoundBackend::audioThreadMain, this);
|
||||
|
||||
// Play the buffer !
|
||||
if (FAILED(buffer->Play(0, 0, DSBPLAY_LOOPING)))
|
||||
{
|
||||
ERROR_LOG(AUDIO, "DirectSound8 Play failed");
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
INFO_LOG(AUDIO, "DirectSound playback started");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
while (!ringBuffer.write((const u8 *)frame, samples * 4) && wait)
|
||||
pushWait.Wait();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
audioThreadRunning = false;
|
||||
audioThread.join();
|
||||
buffer->Stop();
|
||||
|
||||
for (HANDLE event : notificationEvents)
|
||||
CloseHandle(event);
|
||||
buffer.reset();
|
||||
dsound.reset();
|
||||
INFO_LOG(AUDIO, "DirectSound playback stopped");
|
||||
}
|
||||
|
||||
bool initRecord(u32 sampling_freq) override
|
||||
{
|
||||
if (FAILED(DirectSoundCaptureCreate8(&DSDEVID_DefaultVoiceCapture, &dcapture.get(), NULL)))
|
||||
{
|
||||
ERROR_LOG(AUDIO, "DirectSound capture device creation failed");
|
||||
return false;
|
||||
}
|
||||
HRESULT hr;
|
||||
WAVEFORMATEX wfx =
|
||||
{ WAVE_FORMAT_PCM, 1, sampling_freq, sampling_freq * 2, 2, 16, 0 };
|
||||
// wFormatTag, nChannels, nSamplesPerSec, nAvgBytesPerSec,
|
||||
// nBlockAlign, wBitsPerSample, cbSize
|
||||
|
||||
DSCBUFFERDESC dscbd;
|
||||
dscbd.dwSize = sizeof(DSCBUFFERDESC);
|
||||
dscbd.dwFlags = 0;
|
||||
dscbd.dwBufferBytes = 480 * 2;
|
||||
dscbd.dwReserved = 0;
|
||||
dscbd.lpwfxFormat = &wfx;
|
||||
dscbd.dwFXCount = 0;
|
||||
dscbd.lpDSCFXDesc = NULL;
|
||||
|
||||
ComPtr<IDirectSoundCaptureBuffer> pDSCB;
|
||||
if (FAILED(hr = dcapture->CreateCaptureBuffer(&dscbd, &pDSCB.get(), NULL)))
|
||||
{
|
||||
ERROR_LOG(AUDIO, "DirectSound capture buffer creation failed");
|
||||
dcapture.reset();
|
||||
return false;
|
||||
}
|
||||
pDSCB.as(capture_buffer);
|
||||
capture_buffer->Start(DSCBSTART_LOOPING);
|
||||
INFO_LOG(AUDIO, "DirectSound capture device and buffer created");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 record(void *buffer, u32 samples) override
|
||||
{
|
||||
DWORD readPos;
|
||||
capture_buffer->GetCurrentPosition(NULL, &readPos);
|
||||
void *p1, *p2;
|
||||
DWORD p1bytes, p2bytes;
|
||||
capture_buffer->Lock(readPos, samples * 2, &p1, &p1bytes, &p2, &p2bytes, 0);
|
||||
memcpy(buffer, p1, p1bytes);
|
||||
if (p2bytes > 0)
|
||||
memcpy((u8 *)buffer + p1bytes, p2, p2bytes);
|
||||
capture_buffer->Unlock(p1, p1bytes, p2, p2bytes);
|
||||
return (p1bytes + p2bytes) / 2;
|
||||
}
|
||||
|
||||
void termRecord() override
|
||||
{
|
||||
if (!dcapture)
|
||||
return;
|
||||
capture_buffer->Stop();
|
||||
capture_buffer.reset();
|
||||
dcapture.reset();
|
||||
}
|
||||
};
|
||||
static DirectSoundBackend directSoundBackend;
|
||||
|
||||
#endif
|
||||
@@ -1,54 +0,0 @@
|
||||
#ifdef USE_LIBAO
|
||||
#include "audiostream.h"
|
||||
#include <ao/ao.h>
|
||||
|
||||
class LibAOBackend : public AudioBackend
|
||||
{
|
||||
ao_device *aodevice = nullptr;
|
||||
|
||||
public:
|
||||
LibAOBackend()
|
||||
: AudioBackend("libao", "libao") {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
ao_initialize();
|
||||
|
||||
ao_sample_format aoformat {};
|
||||
aoformat.bits = 16;
|
||||
aoformat.channels = 2;
|
||||
aoformat.rate = 44100;
|
||||
aoformat.byte_format = AO_FMT_LITTLE;
|
||||
|
||||
aodevice = ao_open_live(ao_default_driver_id(), &aoformat, NULL); // Live output
|
||||
if (aodevice == nullptr)
|
||||
aodevice = ao_open_live(ao_driver_id("null"), &aoformat, NULL);
|
||||
if (aodevice == nullptr) {
|
||||
ERROR_LOG(AUDIO, "Cannot open libao driver");
|
||||
ao_shutdown();
|
||||
}
|
||||
|
||||
return aodevice != nullptr;
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
if (aodevice != nullptr)
|
||||
ao_play(aodevice, (char*)frame, samples * 4);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
if (aodevice != nullptr)
|
||||
{
|
||||
ao_close(aodevice);
|
||||
aodevice = nullptr;
|
||||
ao_shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
static LibAOBackend libAOBackend;
|
||||
|
||||
#endif
|
||||
@@ -1,58 +0,0 @@
|
||||
#include "audiostream.h"
|
||||
#include "oslib/i18n.h"
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
class NullAudioBackend : public AudioBackend
|
||||
{
|
||||
using the_clock = std::chrono::high_resolution_clock;
|
||||
|
||||
public:
|
||||
NullAudioBackend()
|
||||
: AudioBackend("null", Tnop("No Audio")) {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
last_time = the_clock::time_point();
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
if (wait && last_time.time_since_epoch() != the_clock::duration::zero())
|
||||
{
|
||||
auto fduration = std::chrono::nanoseconds(1'000'000'000LL * samples / 44100);
|
||||
auto duration = fduration - (the_clock::now() - last_time);
|
||||
if (duration > std::chrono::nanoseconds::zero())
|
||||
std::this_thread::sleep_for(duration);
|
||||
if (duration < -std::chrono::milliseconds(67))
|
||||
// if ~4 frames ahead, reset time (fast forward detection)
|
||||
last_time = the_clock::now();
|
||||
else
|
||||
last_time += fduration;
|
||||
}
|
||||
else {
|
||||
last_time = the_clock::now();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool initRecord(u32 sampling_freq) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 record(void *buffer, u32 samples) override
|
||||
{
|
||||
memset(buffer, 0, samples * 2);
|
||||
return samples;
|
||||
}
|
||||
|
||||
std::string getName() const override {
|
||||
return i18n::Ts(name);
|
||||
}
|
||||
|
||||
private:
|
||||
the_clock::time_point last_time;
|
||||
};
|
||||
static NullAudioBackend nullBackend;
|
||||
@@ -1,207 +0,0 @@
|
||||
/*
|
||||
Copyright 2021 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef USE_OBOE
|
||||
#include "audiostream.h"
|
||||
#include "cfg/option.h"
|
||||
#include "stdclass.h"
|
||||
#include "oslib/i18n.h"
|
||||
#include <oboe/Oboe.h>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
class OboeBackend : AudioBackend
|
||||
{
|
||||
RingBuffer ringBuffer;
|
||||
cResetEvent pushWait;
|
||||
|
||||
std::shared_ptr<oboe::AudioStream> stream;
|
||||
std::shared_ptr<oboe::AudioStream> recordStream;
|
||||
|
||||
class AudioCallback : public oboe::AudioStreamDataCallback
|
||||
{
|
||||
public:
|
||||
AudioCallback(OboeBackend *backend) : backend(backend) {}
|
||||
|
||||
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames) override
|
||||
{
|
||||
if (!backend->ringBuffer.read((u8 *)audioData, numFrames * 4))
|
||||
// underrun
|
||||
memset(audioData, 0, numFrames * 4);
|
||||
backend->pushWait.Set();
|
||||
|
||||
return oboe::DataCallbackResult::Continue;
|
||||
}
|
||||
|
||||
OboeBackend *backend;
|
||||
};
|
||||
AudioCallback audioCallback;
|
||||
|
||||
class AudioErrorCallback : public oboe::AudioStreamErrorCallback
|
||||
{
|
||||
public:
|
||||
AudioErrorCallback(OboeBackend *backend) : backend(backend) {}
|
||||
|
||||
void onErrorAfterClose(oboe::AudioStream *stream, oboe::Result error) override
|
||||
{
|
||||
// Only attempt to recover if init was successful
|
||||
if (backend->stream != nullptr)
|
||||
{
|
||||
WARN_LOG(AUDIO, "Audio device lost. Attempting to reopen the audio stream");
|
||||
// the oboe stream is already closed so make sure we don't close it twice
|
||||
backend->stream.reset();
|
||||
backend->term();
|
||||
backend->init();
|
||||
}
|
||||
}
|
||||
|
||||
OboeBackend *backend;
|
||||
};
|
||||
AudioErrorCallback errorCallback;
|
||||
|
||||
public:
|
||||
OboeBackend()
|
||||
: AudioBackend("Oboe", Tnop("Automatic AAudio / OpenSL selection")), audioCallback(this), errorCallback(this) {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
// Actual capacity is size-1 to avoid overrun so add one buffer
|
||||
ringBuffer.setCapacity((config::AudioBufferSize + SAMPLE_COUNT) * 4);
|
||||
|
||||
oboe::AudioStreamBuilder builder;
|
||||
oboe::Result result = builder.setDirection(oboe::Direction::Output)
|
||||
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
|
||||
->setSharingMode(oboe::SharingMode::Exclusive)
|
||||
->setFormat(oboe::AudioFormat::I16)
|
||||
->setChannelCount(oboe::ChannelCount::Stereo)
|
||||
->setSampleRate(44100)
|
||||
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
|
||||
->setFramesPerCallback(SAMPLE_COUNT)
|
||||
->setDataCallback(&audioCallback)
|
||||
->setErrorCallback(&errorCallback)
|
||||
->setUsage(oboe::Usage::Game)
|
||||
->openStream(stream);
|
||||
if (result != oboe::Result::OK)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Oboe open stream failed: %s", oboe::convertToText(result));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stream->getAudioApi() == oboe::AudioApi::AAudio && config::AudioBufferSize < 1764)
|
||||
{
|
||||
// Reduce internal buffer for low latency (< 40 ms)
|
||||
int bufSize = stream->getBufferSizeInFrames();
|
||||
int burst = stream->getFramesPerBurst();
|
||||
if (bufSize - burst > SAMPLE_COUNT)
|
||||
{
|
||||
while (bufSize - burst > SAMPLE_COUNT)
|
||||
bufSize -= burst;
|
||||
stream->setBufferSizeInFrames(bufSize);
|
||||
}
|
||||
}
|
||||
|
||||
stream->requestStart();
|
||||
NOTICE_LOG(AUDIO, "Oboe driver started. stream capacity: %d frames, size: %d frames, frames/callback: %d, frames/burst: %d",
|
||||
stream->getBufferCapacityInFrames(), stream->getBufferSizeInFrames(),
|
||||
stream->getFramesPerCallback(), stream->getFramesPerBurst());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
NOTICE_LOG(AUDIO, "Oboe driver stopping");
|
||||
if (stream != nullptr)
|
||||
{
|
||||
// Don't let the AudioErrorCallback term/reinit while we are stopping
|
||||
// This won't prevent shit to hit the fan if it's already in the process
|
||||
// of doing so but this is a pretty rare event and happens on devices
|
||||
// that have audio issues already.
|
||||
auto localStream = stream;
|
||||
stream.reset();
|
||||
localStream->stop();
|
||||
localStream->close();
|
||||
}
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
while (!ringBuffer.write((const u8 *)frame, samples * 4) && wait)
|
||||
pushWait.Wait();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void termRecord() override
|
||||
{
|
||||
if (recordStream != nullptr)
|
||||
{
|
||||
recordStream->stop();
|
||||
recordStream->close();
|
||||
recordStream.reset();
|
||||
}
|
||||
NOTICE_LOG(AUDIO, "Oboe recorder stopped");
|
||||
}
|
||||
|
||||
bool initRecord(u32 sampling_freq) override
|
||||
{
|
||||
oboe::AudioStreamBuilder builder;
|
||||
oboe::Result result = builder.setDirection(oboe::Direction::Input)
|
||||
->setPerformanceMode(oboe::PerformanceMode::None)
|
||||
->setSharingMode(oboe::SharingMode::Exclusive)
|
||||
->setFormat(oboe::AudioFormat::I16)
|
||||
->setChannelCount(oboe::ChannelCount::Mono)
|
||||
->setSampleRate(sampling_freq)
|
||||
->openStream(recordStream);
|
||||
if (result != oboe::Result::OK)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Oboe open record stream failed: %s", oboe::convertToText(result));
|
||||
return false;
|
||||
}
|
||||
recordStream->requestStart();
|
||||
NOTICE_LOG(AUDIO, "Oboe recorder started. stream capacity: %d frames",
|
||||
recordStream->getBufferCapacityInFrames());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 record(void *data, u32 samples) override
|
||||
{
|
||||
if (recordStream == nullptr)
|
||||
return 0;
|
||||
oboe::ResultWithValue<int32_t> result = recordStream->read(data, samples, 0);
|
||||
if (result == oboe::Result::ErrorDisconnected)
|
||||
{
|
||||
WARN_LOG(AUDIO, "Recording device lost. Attempting to reopen the audio stream");
|
||||
u32 sampleRate = recordStream->getSampleRate();
|
||||
termRecord();
|
||||
initRecord(sampleRate);
|
||||
}
|
||||
return std::max(0, result.value());
|
||||
}
|
||||
|
||||
|
||||
std::string getName() const override {
|
||||
return i18n::Ts(name);
|
||||
}
|
||||
};
|
||||
static OboeBackend oboeBackend;
|
||||
|
||||
#endif
|
||||
@@ -1,337 +0,0 @@
|
||||
#if USE_OMX
|
||||
#include "audiostream.h"
|
||||
#include "cfg/option.h"
|
||||
#include <IL/OMX_Broadcom.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define PORT_INDEX 100
|
||||
#define OUTPUT_FREQ 44100
|
||||
|
||||
class OMXAudioBackend : public AudioBackend
|
||||
{
|
||||
OMX_HANDLETYPE omx_handle = nullptr;
|
||||
OMX_STATETYPE omx_state = OMX_StateInvalid;
|
||||
size_t audio_buffer_idx = 0;
|
||||
OMX_BUFFERHEADERTYPE** audio_buffers = nullptr;
|
||||
u32 latency_max = 0;
|
||||
u32 buffer_count = 0;
|
||||
u32 buffer_size = 0;
|
||||
u32 buffer_length = 0;
|
||||
pthread_mutex_t audio_lock;
|
||||
pthread_cond_t omx_state_cond;
|
||||
|
||||
static OMX_ERRORTYPE eventHandler(
|
||||
OMX_IN OMX_HANDLETYPE hComponent,
|
||||
OMX_IN OMX_PTR pAppData,
|
||||
OMX_IN OMX_EVENTTYPE eEvent,
|
||||
OMX_IN OMX_U32 nData1,
|
||||
OMX_IN OMX_U32 nData2,
|
||||
OMX_IN OMX_PTR pEventData)
|
||||
{
|
||||
OMXAudioBackend *backend = (OMXAudioBackend *)pAddData;
|
||||
pthread_mutex_lock(&backend->audio_lock);
|
||||
if(eEvent == OMX_EventCmdComplete && nData1 == OMX_CommandStateSet)
|
||||
{
|
||||
backend->omx_state = (OMX_STATETYPE)nData2;
|
||||
pthread_cond_signal(&backend->omx_state_cond);
|
||||
}
|
||||
pthread_mutex_unlock(&backend->audio_lock);
|
||||
return OMX_ErrorNone;
|
||||
}
|
||||
|
||||
static OMX_ERRORTYPE emptyBufferDone(
|
||||
OMX_IN OMX_HANDLETYPE hComponent,
|
||||
OMX_IN OMX_PTR pAppData,
|
||||
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer)
|
||||
{
|
||||
return OMX_ErrorNone;
|
||||
}
|
||||
|
||||
static OMX_ERRORTYPE fillBufferDone(
|
||||
OMX_OUT OMX_HANDLETYPE hComponent,
|
||||
OMX_OUT OMX_PTR pAppData,
|
||||
OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer)
|
||||
{
|
||||
return OMX_ErrorNone;
|
||||
}
|
||||
|
||||
void waitForState(OMX_STATETYPE state)
|
||||
{
|
||||
pthread_mutex_lock(&audio_lock);
|
||||
while(omx_state != state)
|
||||
pthread_cond_wait(&omx_state_cond, &audio_lock);
|
||||
pthread_mutex_unlock(&audio_lock);
|
||||
}
|
||||
|
||||
u32 getLatency()
|
||||
{
|
||||
OMX_PARAM_U32TYPE param;
|
||||
memset(¶m, 0, sizeof(OMX_PARAM_U32TYPE));
|
||||
param.nSize = sizeof(OMX_PARAM_U32TYPE);
|
||||
param.nVersion.nVersion = OMX_VERSION;
|
||||
param.nPortIndex = PORT_INDEX;
|
||||
|
||||
OMX_ERRORTYPE error = OMX_GetConfig(omx_handle, OMX_IndexConfigAudioRenderingLatency, ¶m);
|
||||
if(error != OMX_ErrorNone)
|
||||
WARN_LOG(AUDIO, "OMX: failed to get OMX configuration (OMX_IndexConfigAudioRenderingLatency). Error 0x%X", error);
|
||||
|
||||
return param.nU32 * 1000 / OUTPUT_FREQ;
|
||||
}
|
||||
|
||||
public:
|
||||
OMXAudioBackend()
|
||||
: AudioBackend("omx", "OpenMAX IL") {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
error = OMX_Init();
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: OMX_Init() failed. Error 0x%X", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize settings
|
||||
latency_max = config::OmxAudioLatency;
|
||||
buffer_size = config::AudioBufferSize * 4;
|
||||
buffer_count = 2 + OUTPUT_FREQ * latency_max / (buffer_size * 1000);
|
||||
|
||||
OMX_CALLBACKTYPE callbacks;
|
||||
callbacks.EventHandler = eventHandler;
|
||||
callbacks.EmptyBufferDone = emptyBufferDone;
|
||||
callbacks.FillBufferDone = fillBufferDone;
|
||||
|
||||
error = OMX_GetHandle(&omx_handle, (OMX_STRING)"OMX.broadcom.audio_render", this, &callbacks);
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: OMX_GetHandle() failed. Error 0x%X", error);
|
||||
OMX_Deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
OMX_PARAM_PORTDEFINITIONTYPE param;
|
||||
memset(¶m, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
|
||||
param.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
|
||||
param.nVersion.nVersion = OMX_VERSION;
|
||||
param.nPortIndex = PORT_INDEX;
|
||||
param.nBufferSize = buffer_size;
|
||||
param.nBufferCountActual = buffer_count;
|
||||
param.format.audio.eEncoding = OMX_AUDIO_CodingPCM;
|
||||
|
||||
error = OMX_SetParameter(omx_handle, OMX_IndexParamPortDefinition, ¶m);
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: failed to set OMX_IndexParamPortDefinition. Error 0x%X", error);
|
||||
OMX_Deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
OMX_AUDIO_PARAM_PCMMODETYPE pcm;
|
||||
memset(&pcm, 0, sizeof(OMX_AUDIO_PARAM_PCMMODETYPE));
|
||||
pcm.nSize = sizeof(OMX_AUDIO_PARAM_PCMMODETYPE);
|
||||
pcm.nVersion.nVersion = OMX_VERSION;
|
||||
pcm.nPortIndex = PORT_INDEX;
|
||||
pcm.nChannels = 2;
|
||||
pcm.eNumData = OMX_NumericalDataSigned;
|
||||
pcm.eEndian = OMX_EndianLittle;
|
||||
pcm.nSamplingRate = OUTPUT_FREQ;
|
||||
pcm.bInterleaved = OMX_TRUE;
|
||||
pcm.nBitPerSample = 16;
|
||||
pcm.ePCMMode = OMX_AUDIO_PCMModeLinear;
|
||||
pcm.eChannelMapping[1] = OMX_AUDIO_ChannelRF;
|
||||
pcm.eChannelMapping[0] = OMX_AUDIO_ChannelLF;
|
||||
|
||||
error = OMX_SetParameter(omx_handle, OMX_IndexParamAudioPcm, &pcm);
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: failed to set OMX_IndexParamAudioPcm. Error 0x%X", error);
|
||||
OMX_Deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disable all ports
|
||||
error = OMX_SendCommand(omx_handle, OMX_CommandPortDisable, PORT_INDEX, NULL);
|
||||
if (error != OMX_ErrorNone)
|
||||
WARN_LOG(AUDIO, "OMX: failed to do OMX_CommandPortDisable. Error 0x%X", error);
|
||||
|
||||
OMX_PORT_PARAM_TYPE param2;
|
||||
memset(¶m2, 0, sizeof(OMX_PORT_PARAM_TYPE));
|
||||
param2.nSize = sizeof(OMX_PORT_PARAM_TYPE);
|
||||
param2.nVersion.nVersion = OMX_VERSION;
|
||||
error = OMX_GetParameter(omx_handle, OMX_IndexParamOtherInit, ¶m2);
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: failed to get OMX_IndexParamOtherInit. Error 0x%X", error);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(u32 i = 0; i < param2.nPorts; i++)
|
||||
{
|
||||
u32 port = param2.nStartPortNumber + i;
|
||||
error = OMX_SendCommand(omx_handle, OMX_CommandPortDisable, port, NULL);
|
||||
if(error != OMX_ErrorNone)
|
||||
WARN_LOG(AUDIO, "OMX: failed to do OMX_CommandPortDisable on port %u. Error 0x%X", port, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Go into idle state
|
||||
error = OMX_SendCommand(omx_handle, OMX_CommandStateSet, OMX_StateIdle, NULL);
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: failed to set OMX_CommandStateSet. Error 0x%X", error);
|
||||
OMX_Deinit();
|
||||
return false;
|
||||
}
|
||||
waitForState(OMX_StateIdle);
|
||||
|
||||
// Check if we're in a state able to recieve buffers
|
||||
OMX_STATETYPE state;
|
||||
error = OMX_GetState(omx_handle, &state);
|
||||
if (error != OMX_ErrorNone || !(state == OMX_StateIdle || state == OMX_StateExecuting || state == OMX_StatePause))
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: state is incorrect. State 0x%X; Error 0x%X", state, error);
|
||||
OMX_Deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create audio buffers
|
||||
INFO_LOG(AUDIO, "OMX: creating %u buffers", buffer_count);
|
||||
|
||||
// Enable port
|
||||
error = OMX_SendCommand(omx_handle, OMX_CommandPortEnable, PORT_INDEX, NULL);
|
||||
if (error != OMX_ErrorNone)
|
||||
WARN_LOG(AUDIO, "OMX: failed to do OMX_CommandPortEnable. Error 0x%X", error);
|
||||
|
||||
// Free audio buffers if they're allocated
|
||||
if (audio_buffers != NULL)
|
||||
delete[] audio_buffers;
|
||||
|
||||
// Allocate buffers
|
||||
audio_buffers = new OMX_BUFFERHEADERTYPE*[buffer_count];
|
||||
for (size_t i = 0; i < buffer_count; i++)
|
||||
{
|
||||
error = OMX_AllocateBuffer(omx_handle, &audio_buffers[i], PORT_INDEX, NULL, buffer_size);
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: failed to allocate buffer[%u]. Error 0x%X", i, error);
|
||||
OMX_Deinit();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Set state to executing
|
||||
error = OMX_SendCommand(omx_handle, OMX_CommandStateSet, OMX_StateExecuting, NULL);
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: failed to do OMX_CommandStateSet. Error 0x%X", error);
|
||||
OMX_Deinit();
|
||||
return false;
|
||||
}
|
||||
waitForState(OMX_StateExecuting);
|
||||
|
||||
// Empty buffers
|
||||
for (size_t i = 0; i < buffer_count; i++)
|
||||
{
|
||||
memset(audio_buffers[i]->pBuffer, 0, buffer_size);
|
||||
audio_buffers[i]->nOffset = 0;
|
||||
audio_buffers[i]->nFilledLen = buffer_size;
|
||||
|
||||
error = OMX_EmptyThisBuffer(omx_handle, audio_buffers[i]);
|
||||
if(error != OMX_ErrorNone)
|
||||
WARN_LOG(AUDIO, "OMX: failed to empty buffer[%u]. Error 0x%X", i, error);
|
||||
}
|
||||
|
||||
const char* output_device = "local";
|
||||
if (config::OmxAudioHdmi)
|
||||
output_device = (const char*)"hdmi";
|
||||
|
||||
// Set audio destination
|
||||
OMX_CONFIG_BRCMAUDIODESTINATIONTYPE ar_dest;
|
||||
memset(&ar_dest, 0, sizeof(ar_dest));
|
||||
ar_dest.nSize = sizeof(OMX_CONFIG_BRCMAUDIODESTINATIONTYPE);
|
||||
ar_dest.nVersion.nVersion = OMX_VERSION;
|
||||
strcpy((char *)ar_dest.sName, output_device);
|
||||
error = OMX_SetConfig(omx_handle, OMX_IndexConfigBrcmAudioDestination, &ar_dest);
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: failed to set OMX configuration (OMX_IndexConfigBrcmAudioDestination). Error 0x%X", error);
|
||||
OMX_Deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
audio_buffer_idx = 0;
|
||||
buffer_length = 0;
|
||||
|
||||
INFO_LOG(AUDIO, "OMX: audio output to '%s'", ar_dest.sName);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
if(audio_buffers == NULL)
|
||||
return 1;
|
||||
|
||||
size_t data_size = samples * 4;
|
||||
|
||||
while(data_size > 0)
|
||||
{
|
||||
size_t copy_size = std::min(buffer_size - buffer_length, data_size);
|
||||
|
||||
// Don't have more than maximum audio latency
|
||||
u32 latency = getLatency();
|
||||
if(latency > latency_max)
|
||||
{
|
||||
usleep((latency - latency_max) * 1000);
|
||||
}
|
||||
else if(latency == 0)
|
||||
{
|
||||
INFO_LOG(AUDIO, "OMX: underrun occurred");
|
||||
}
|
||||
|
||||
memcpy(audio_buffers[audio_buffer_idx]->pBuffer + buffer_length, frame, copy_size);
|
||||
buffer_length += copy_size;
|
||||
frame = (char *)frame + copy_size;
|
||||
|
||||
// Flush buffer and swap
|
||||
if(buffer_length >= buffer_size)
|
||||
{
|
||||
audio_buffers[audio_buffer_idx]->nOffset = 0;
|
||||
audio_buffers[audio_buffer_idx]->nFilledLen = buffer_size;
|
||||
|
||||
OMX_ERRORTYPE error = OMX_EmptyThisBuffer(omx_handle, audio_buffers[audio_buffer_idx]);
|
||||
if(error != OMX_ErrorNone)
|
||||
INFO_LOG(AUDIO, "OMX: failed to empty buffer[%u]. Error 0x%X", audio_buffer_idx, error);
|
||||
|
||||
audio_buffer_idx = (audio_buffer_idx + 1) % buffer_count;
|
||||
buffer_length = 0;
|
||||
}
|
||||
|
||||
data_size -= copy_size;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
// Is there anything else that needs to be done for omx?
|
||||
|
||||
error = OMX_Deinit();
|
||||
if(error != OMX_ErrorNone)
|
||||
WARN_LOG(AUDIO, "OMX: OMX_Deinit() failed. Error 0x%X", error);
|
||||
|
||||
if(audio_buffers != NULL)
|
||||
{
|
||||
delete[] audio_buffers;
|
||||
audio_buffers = NULL;
|
||||
}
|
||||
}
|
||||
};
|
||||
static OMXAudioBackend omxAudioBackend;
|
||||
|
||||
#endif
|
||||
@@ -1,151 +0,0 @@
|
||||
#ifdef USE_OSS
|
||||
#include "audiostream.h"
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/soundcard.h>
|
||||
#include <unistd.h>
|
||||
|
||||
class OSSAudioBackend : public AudioBackend
|
||||
{
|
||||
int audioFD = -1;
|
||||
int recordFD = -1;
|
||||
|
||||
static int openDevice(int flags)
|
||||
{
|
||||
const char* path = "/dev/dsp";
|
||||
int fd = open(path, flags);
|
||||
|
||||
if (fd < 0)
|
||||
ERROR_LOG(AUDIO, "OSS: open(%s) failed: %s", path, strerror(errno));
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static bool setRate(int fd, int rate)
|
||||
{
|
||||
int tmp = rate;
|
||||
|
||||
if (ioctl(fd, SNDCTL_DSP_SPEED, &tmp) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "OSS: ioctl(SNDCTL_DSP_SPEED) failed: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tmp != rate)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "OSS: sample rate unsupported: %d => %d", rate, tmp);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setChannels(int fd, int channels)
|
||||
{
|
||||
int tmp = channels;
|
||||
|
||||
if (ioctl(fd, SNDCTL_DSP_CHANNELS, &tmp) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "OSS: ioctl(SNDCTL_DSP_CHANNELS) failed: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tmp != channels)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "OSS: channels unsupported: %d => %d", channels, tmp);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setFormat(int fd, int format)
|
||||
{
|
||||
int tmp = format;
|
||||
|
||||
if (ioctl(fd, SNDCTL_DSP_SETFMT, &tmp) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "OSS: ioctl(SNDCTL_DSP_SETFMT) failed: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tmp != format)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "OSS: sample format unsupported: %#.8x => %#.8x", format, tmp);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
OSSAudioBackend()
|
||||
: AudioBackend("oss", "Open Sound System") {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
audioFD = openDevice(O_WRONLY);
|
||||
|
||||
if (audioFD < 0 || !setRate(audioFD, 44100) || !setChannels(audioFD, 2) || !setFormat(audioFD, AFMT_S16_LE))
|
||||
{
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
write(audioFD, frame, samples * 4);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
if (audioFD >= 0)
|
||||
close(audioFD);
|
||||
audioFD = -1;
|
||||
}
|
||||
|
||||
// recording untested
|
||||
|
||||
bool initRecord(u32 sampling_freq) override
|
||||
{
|
||||
recordFD = openDevice(O_RDONLY);
|
||||
|
||||
if (recordFD < 0 || !setRate(recordFD, sampling_freq) || !setChannels(recordFD, 1) || !setFormat(recordFD, AFMT_S16_NE /* Native 16 bits */ ))
|
||||
{
|
||||
termRecord();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void termRecord() override
|
||||
{
|
||||
if (recordFD >= 0)
|
||||
close(recordFD);
|
||||
recordFD = -1;
|
||||
}
|
||||
|
||||
u32 record(void *buffer, u32 samples) override
|
||||
{
|
||||
samples *= 2;
|
||||
int l = read(recordFD, buffer, samples);
|
||||
if (l < (int)samples)
|
||||
{
|
||||
if (l < 0)
|
||||
{
|
||||
INFO_LOG(AUDIO, "OSS: Recording error");
|
||||
l = 0;
|
||||
}
|
||||
memset((u8 *)buffer + l, 0, samples - l);
|
||||
}
|
||||
return l / 2;
|
||||
}
|
||||
};
|
||||
static OSSAudioBackend ossAudioBackend;
|
||||
|
||||
#endif
|
||||
@@ -1,252 +0,0 @@
|
||||
#ifdef USE_PULSEAUDIO
|
||||
#include "audiostream.h"
|
||||
#include "cfg/option.h"
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
class PulseAudioBackend : public AudioBackend
|
||||
{
|
||||
pa_threaded_mainloop *mainloop = nullptr;
|
||||
pa_context *context = nullptr;
|
||||
pa_stream *stream = nullptr;
|
||||
pa_stream *record_stream = nullptr;
|
||||
|
||||
static void context_state_cb(pa_context *c, void *userdata)
|
||||
{
|
||||
PulseAudioBackend *backend = (PulseAudioBackend *)userdata;
|
||||
switch (pa_context_get_state(c))
|
||||
{
|
||||
case PA_CONTEXT_READY:
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
case PA_CONTEXT_FAILED:
|
||||
pa_threaded_mainloop_signal(backend->mainloop, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void stream_request_cb(pa_stream *s, size_t length, void *userdata)
|
||||
{
|
||||
PulseAudioBackend *backend = (PulseAudioBackend *)userdata;
|
||||
pa_threaded_mainloop_signal(backend->mainloop, 0);
|
||||
}
|
||||
|
||||
public:
|
||||
PulseAudioBackend()
|
||||
: AudioBackend("pulse", "PulseAudio") {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
mainloop = pa_threaded_mainloop_new();
|
||||
if (!mainloop)
|
||||
{
|
||||
WARN_LOG(AUDIO, "PulseAudio: pa_threaded_mainloop_new failed");
|
||||
return false;
|
||||
}
|
||||
context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), "flycast");
|
||||
if (!context)
|
||||
{
|
||||
WARN_LOG(AUDIO, "PulseAudio: pa_context_new failed");
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
pa_context_set_state_callback(context, context_state_cb, this);
|
||||
|
||||
if (pa_context_connect(context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0)
|
||||
{
|
||||
WARN_LOG(AUDIO, "PulseAudio: pa_context_connect failed");
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
if (pa_threaded_mainloop_start(mainloop) < 0)
|
||||
{
|
||||
WARN_LOG(AUDIO, "PulseAudio: pa_threaded_mainloop_start failed");
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
|
||||
if (pa_context_get_state(context) != PA_CONTEXT_READY)
|
||||
{
|
||||
WARN_LOG(AUDIO, "PulseAudio: context isn't ready");
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
pa_sample_spec spec{ PA_SAMPLE_S16LE, 44100, 2 };
|
||||
|
||||
stream = pa_stream_new(context, "audio", &spec, NULL);
|
||||
if (!stream)
|
||||
{
|
||||
WARN_LOG(AUDIO, "PulseAudio: pa_stream_new failed");
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
//pa_stream_set_state_callback(stream, stream_state_cb, this);
|
||||
pa_stream_set_write_callback(stream, stream_request_cb, this);
|
||||
//pa_stream_set_latency_update_callback(stream, stream_latency_update_cb, this);
|
||||
//pa_stream_set_underflow_callback(stream, underrun_update_cb, this);
|
||||
//pa_stream_set_buffer_attr_callback(stream, buffer_attr_cb, this);
|
||||
|
||||
pa_buffer_attr buffer_attr;
|
||||
buffer_attr.maxlength = -1;
|
||||
buffer_attr.tlength = pa_usec_to_bytes(config::AudioBufferSize * PA_USEC_PER_SEC / 44100, &spec);
|
||||
buffer_attr.prebuf = -1;
|
||||
buffer_attr.minreq = -1;
|
||||
buffer_attr.fragsize = -1;
|
||||
|
||||
if (pa_stream_connect_playback(stream, nullptr, &buffer_attr, PA_STREAM_ADJUST_LATENCY, nullptr, nullptr) < 0)
|
||||
{
|
||||
WARN_LOG(AUDIO, "PulseAudio: pa_stream_connect_playback failed");
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
|
||||
if (pa_stream_get_state(stream) != PA_STREAM_READY)
|
||||
{
|
||||
WARN_LOG(AUDIO, "PulseAudio: stream isn't ready");
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
const pa_buffer_attr *server_attr = pa_stream_get_buffer_attr(stream);
|
||||
if (server_attr)
|
||||
DEBUG_LOG(AUDIO, "PulseAudio: requested %d samples buffer, got %d", buffer_attr.tlength / 4, server_attr->tlength / 4);
|
||||
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
const u8 *buf = (const u8 *)frame;
|
||||
size_t size = samples * 4;
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
while (size)
|
||||
{
|
||||
size_t writable = std::min(size, pa_stream_writable_size(stream));
|
||||
|
||||
if (writable)
|
||||
{
|
||||
pa_stream_write(stream, buf, writable, NULL, 0, PA_SEEK_RELATIVE);
|
||||
buf += writable;
|
||||
size -= writable;
|
||||
}
|
||||
else if (wait)
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
else
|
||||
break;
|
||||
}
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
if (mainloop)
|
||||
pa_threaded_mainloop_stop(mainloop);
|
||||
|
||||
if (stream)
|
||||
{
|
||||
pa_stream_disconnect(stream);
|
||||
pa_stream_unref(stream);
|
||||
stream = nullptr;
|
||||
}
|
||||
if (context)
|
||||
{
|
||||
pa_context_disconnect(context);
|
||||
pa_context_unref(context);
|
||||
context = nullptr;
|
||||
}
|
||||
|
||||
if (mainloop)
|
||||
pa_threaded_mainloop_free(mainloop);
|
||||
mainloop = nullptr;
|
||||
}
|
||||
|
||||
bool initRecord(u32 sampling_freq) override
|
||||
{
|
||||
pa_sample_spec spec{ PA_SAMPLE_S16LE, sampling_freq, 1 };
|
||||
|
||||
record_stream = pa_stream_new(context, "record", &spec, NULL);
|
||||
if (!record_stream)
|
||||
{
|
||||
INFO_LOG(AUDIO, "PulseAudio: pa_stream_new failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
|
||||
pa_buffer_attr buffer_attr;
|
||||
buffer_attr.fragsize = 240 * 2;
|
||||
buffer_attr.maxlength = buffer_attr.fragsize * 2;
|
||||
|
||||
if (pa_stream_connect_record(record_stream, nullptr, &buffer_attr, PA_STREAM_NOFLAGS) < 0)
|
||||
{
|
||||
INFO_LOG(AUDIO, "PulseAudio: pa_stream_connect_record failed");
|
||||
pa_stream_unref(record_stream);
|
||||
record_stream = nullptr;
|
||||
return false;
|
||||
}
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
INFO_LOG(AUDIO, "PulseAudio: Successfully initialized capture device");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void termRecord() override
|
||||
{
|
||||
if (record_stream != nullptr)
|
||||
{
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
pa_stream_disconnect(record_stream);
|
||||
pa_stream_unref(record_stream);
|
||||
record_stream = nullptr;
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
}
|
||||
|
||||
u32 record(void *buffer, u32 samples) override
|
||||
{
|
||||
if (record_stream == nullptr)
|
||||
return 0;
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
const void *data;
|
||||
size_t size;
|
||||
if (pa_stream_peek(record_stream, &data, &size) < 0)
|
||||
{
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
DEBUG_LOG(AUDIO, "PulseAudio: pa_stream_peek error");
|
||||
return 0;
|
||||
}
|
||||
if (size == 0)
|
||||
{
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
return 0;
|
||||
}
|
||||
size = std::min((size_t)samples * 2, size);
|
||||
if (data != nullptr)
|
||||
memcpy(buffer, data, size);
|
||||
else
|
||||
memset(buffer, 0, size);
|
||||
pa_stream_drop(record_stream);
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
|
||||
return size / 2;
|
||||
}
|
||||
};
|
||||
static PulseAudioBackend pulseAudioBackend;
|
||||
|
||||
#endif
|
||||
@@ -1,243 +0,0 @@
|
||||
#if defined(USE_SDL_AUDIO)
|
||||
|
||||
#include <SDL.h>
|
||||
#include "audiostream.h"
|
||||
#include "cfg/option.h"
|
||||
#include "stdclass.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
class SDLAudioBackend : AudioBackend
|
||||
{
|
||||
SDL_AudioDeviceID audiodev {};
|
||||
bool needs_resampling = false;
|
||||
cResetEvent read_wait;
|
||||
std::mutex stream_mutex;
|
||||
uint32_t *sample_buffer;
|
||||
unsigned sample_buffer_size = 0;
|
||||
unsigned sample_count = 0;
|
||||
SDL_AudioCVT audioCvt;
|
||||
|
||||
SDL_AudioDeviceID recorddev {};
|
||||
u8 recordbuf[480 * 4];
|
||||
std::atomic<size_t> rec_read;
|
||||
std::atomic<size_t> rec_write;
|
||||
|
||||
static void audioCallback(void* userdata, Uint8* stream, int len)
|
||||
{
|
||||
SDLAudioBackend *backend = (SDLAudioBackend *)userdata;
|
||||
|
||||
backend->stream_mutex.lock();
|
||||
// Wait until there's enough samples to feed the kraken
|
||||
unsigned oslen = len / sizeof(uint32_t);
|
||||
unsigned islen = backend->needs_resampling ? std::ceil(oslen / backend->audioCvt.len_ratio) : oslen;
|
||||
|
||||
if (backend->sample_count < islen)
|
||||
{
|
||||
// No data, just output a bit of silence for the underrun
|
||||
memset(stream, 0, len);
|
||||
backend->stream_mutex.unlock();
|
||||
backend->read_wait.Set();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!backend->needs_resampling) {
|
||||
// Just copy bytes for this case.
|
||||
memcpy(stream, &backend->sample_buffer[0], len);
|
||||
}
|
||||
else
|
||||
{
|
||||
SDL_AudioCVT& cvt = backend->audioCvt;
|
||||
cvt.len = islen * sizeof(uint32_t);
|
||||
memcpy(cvt.buf, &backend->sample_buffer[0], cvt.len);
|
||||
SDL_ConvertAudio(&cvt);
|
||||
memcpy(stream, cvt.buf, cvt.len_cvt);
|
||||
}
|
||||
|
||||
// Move samples in the buffer and consume them
|
||||
memmove(&backend->sample_buffer[0], &backend->sample_buffer[islen], (backend->sample_count - islen) * sizeof(uint32_t));
|
||||
backend->sample_count -= islen;
|
||||
|
||||
backend->stream_mutex.unlock();
|
||||
backend->read_wait.Set();
|
||||
}
|
||||
|
||||
public:
|
||||
SDLAudioBackend()
|
||||
: AudioBackend("sdl2", "Simple DirectMedia Layer 2") {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
if (!SDL_WasInit(SDL_INIT_AUDIO))
|
||||
{
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
||||
ERROR_LOG(AUDIO, "SDL2 error initializing audio subsystem: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
sample_buffer_size = std::max<u32>(SAMPLE_COUNT * 2, config::AudioBufferSize);
|
||||
sample_buffer = new uint32_t[sample_buffer_size]();
|
||||
sample_count = 0;
|
||||
|
||||
// Support 44.1KHz (native) but also upsampling to 48KHz
|
||||
SDL_AudioSpec wav_spec, out_spec;
|
||||
memset(&wav_spec, 0, sizeof(wav_spec));
|
||||
wav_spec.freq = 44100;
|
||||
wav_spec.format = AUDIO_S16;
|
||||
wav_spec.channels = 2;
|
||||
wav_spec.samples = SAMPLE_COUNT * 2; // Must be power of two
|
||||
wav_spec.callback = audioCallback;
|
||||
wav_spec.userdata = this;
|
||||
needs_resampling = false;
|
||||
|
||||
// Try 44.1KHz which should be faster since it's native.
|
||||
audiodev = SDL_OpenAudioDevice(NULL, 0, &wav_spec, &out_spec, 0);
|
||||
if (audiodev == 0)
|
||||
{
|
||||
INFO_LOG(AUDIO, "SDL2: SDL_OpenAudioDevice failed: %s", SDL_GetError());
|
||||
needs_resampling = true;
|
||||
wav_spec.freq = 48000;
|
||||
audiodev = SDL_OpenAudioDevice(NULL, 0, &wav_spec, &out_spec, 0);
|
||||
if (audiodev == 0)
|
||||
ERROR_LOG(AUDIO, "SDL2: SDL_OpenAudioDevice failed: %s", SDL_GetError());
|
||||
else
|
||||
{
|
||||
INFO_LOG(AUDIO, "SDL2: Using resampling to 48 KHz");
|
||||
int ret = SDL_BuildAudioCVT(&audioCvt, AUDIO_S16, 2, 44100, AUDIO_S16, 2, 48000);
|
||||
if (ret != 1 || audioCvt.needed == 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "SDL2: can't build audio converter: %s", SDL_GetError());
|
||||
SDL_CloseAudioDevice(audiodev);
|
||||
audiodev = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
audioCvt.buf = new u8[SAMPLE_COUNT * 2 * sizeof(uint32_t) * audioCvt.len_mult];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return audiodev != 0;
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
// Unpause the device shall it be paused.
|
||||
if (SDL_GetAudioDeviceStatus(audiodev) != SDL_AUDIO_PLAYING)
|
||||
SDL_PauseAudioDevice(audiodev, 0);
|
||||
|
||||
// If wait, then wait for the buffer to be smaller than a certain size.
|
||||
stream_mutex.lock();
|
||||
if (wait) {
|
||||
while (sample_count + samples > sample_buffer_size) {
|
||||
stream_mutex.unlock();
|
||||
read_wait.Wait();
|
||||
stream_mutex.lock();
|
||||
}
|
||||
}
|
||||
|
||||
// Copy as many samples as possible, drop any remaining (this should not happen usually)
|
||||
unsigned free_samples = sample_buffer_size - sample_count;
|
||||
unsigned tocopy = samples < free_samples ? samples : free_samples;
|
||||
memcpy(&sample_buffer[sample_count], frame, tocopy * sizeof(uint32_t));
|
||||
sample_count += tocopy;
|
||||
stream_mutex.unlock();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
if (audiodev)
|
||||
{
|
||||
// Stop audio playback.
|
||||
SDL_PauseAudioDevice(audiodev, 1);
|
||||
read_wait.Set();
|
||||
SDL_CloseAudioDevice(audiodev);
|
||||
audiodev = SDL_AudioDeviceID();
|
||||
}
|
||||
delete [] sample_buffer;
|
||||
sample_buffer = nullptr;
|
||||
if (needs_resampling)
|
||||
{
|
||||
delete [] audioCvt.buf;
|
||||
audioCvt.buf = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void recordCallback(void *userdata, u8 *stream, int len)
|
||||
{
|
||||
SDLAudioBackend *backend = (SDLAudioBackend *)userdata;
|
||||
DEBUG_LOG(AUDIO, "SDL2: sdl2_record_cb len %d write %zd read %zd", len, (size_t)backend->rec_write, (size_t)backend->rec_read);
|
||||
while (len > 0)
|
||||
{
|
||||
size_t plen = std::min((size_t)len, sizeof(backend->recordbuf) - backend->rec_write);
|
||||
memcpy(&backend->recordbuf[backend->rec_write], stream, plen);
|
||||
len -= plen;
|
||||
backend->rec_write = (backend->rec_write + plen) % sizeof(backend->recordbuf);
|
||||
stream += plen;
|
||||
}
|
||||
}
|
||||
|
||||
bool initRecord(u32 sampling_freq) override
|
||||
{
|
||||
rec_write = 0;
|
||||
rec_read = 0;
|
||||
|
||||
SDL_AudioSpec wav_spec, out_spec;
|
||||
memset(&wav_spec, 0, sizeof(wav_spec));
|
||||
wav_spec.freq = sampling_freq;
|
||||
wav_spec.format = AUDIO_S16;
|
||||
wav_spec.channels = 1;
|
||||
wav_spec.samples = 256; // Must be power of two
|
||||
wav_spec.callback = recordCallback;
|
||||
wav_spec.userdata = this;
|
||||
recorddev = SDL_OpenAudioDevice(NULL, 1, &wav_spec, &out_spec, 0);
|
||||
if (recorddev == 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "SDL2: Cannot open audio capture device: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
SDL_PauseAudioDevice(recorddev, 0);
|
||||
INFO_LOG(AUDIO, "SDL2: opened audio capture device");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void termRecord() override
|
||||
{
|
||||
if (recorddev != 0)
|
||||
{
|
||||
SDL_PauseAudioDevice(recorddev, 1);
|
||||
SDL_CloseAudioDevice(recorddev);
|
||||
recorddev = 0;
|
||||
}
|
||||
}
|
||||
|
||||
u32 record(void* frame, u32 samples) override
|
||||
{
|
||||
u32 count = 0;
|
||||
samples *= 2;
|
||||
while (samples > 0)
|
||||
{
|
||||
u32 avail = std::min(rec_write - rec_read, sizeof(recordbuf) - rec_read);
|
||||
if (avail == 0)
|
||||
break;
|
||||
avail = std::min(avail, samples);
|
||||
memcpy((u8 *)frame + count, &recordbuf[rec_read], avail);
|
||||
rec_read = (rec_read + avail) % sizeof(recordbuf);
|
||||
samples -= avail;
|
||||
count += avail;
|
||||
}
|
||||
DEBUG_LOG(AUDIO, "SDL2: sdl2_record len %d ret %d write %zd read %zd", samples * 2, count, (size_t)rec_write, (size_t)rec_read);
|
||||
|
||||
return count / 2;
|
||||
}
|
||||
};
|
||||
static SDLAudioBackend sdlAudioBackend;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
#include "audiostream.h"
|
||||
#include "cfg/option.h"
|
||||
#include "emulator.h"
|
||||
|
||||
static void registerForEvents();
|
||||
|
||||
struct SoundFrame { s16 l; s16 r; };
|
||||
|
||||
static SoundFrame Buffer[SAMPLE_COUNT];
|
||||
static u32 writePtr; // next sample index
|
||||
|
||||
static AudioBackend *currentBackend;
|
||||
std::vector<AudioBackend *> *AudioBackend::backends;
|
||||
|
||||
static bool audio_recording_started;
|
||||
static bool eight_khz;
|
||||
|
||||
AudioBackend *AudioBackend::getBackend(const std::string& slug)
|
||||
{
|
||||
if (backends == nullptr)
|
||||
return nullptr;
|
||||
if (slug == "auto")
|
||||
{
|
||||
// Prefer sdl2 if available and avoid the null driver
|
||||
AudioBackend *sdlBackend = nullptr;
|
||||
AudioBackend *autoBackend = nullptr;
|
||||
for (auto backend : *backends)
|
||||
{
|
||||
if (backend->slug == "sdl2")
|
||||
sdlBackend = backend;
|
||||
if (backend->slug != "null" && autoBackend == nullptr)
|
||||
autoBackend = backend;
|
||||
}
|
||||
if (sdlBackend != nullptr)
|
||||
autoBackend = sdlBackend;
|
||||
if (autoBackend == nullptr)
|
||||
autoBackend = backends->front();
|
||||
INFO_LOG(AUDIO, "Auto-selected audio backend \"%s\" (%s).", autoBackend->slug.c_str(), autoBackend->getName().c_str());
|
||||
|
||||
return autoBackend;
|
||||
}
|
||||
for (auto backend : *backends)
|
||||
{
|
||||
if (backend->slug == slug)
|
||||
return backend;
|
||||
}
|
||||
WARN_LOG(AUDIO, "WARNING: Audio backend \"%s\" not found!", slug.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void WriteSample(s16 r, s16 l)
|
||||
{
|
||||
Buffer[writePtr].r = r * config::AudioVolume.dbPower();
|
||||
Buffer[writePtr].l = l * config::AudioVolume.dbPower();
|
||||
|
||||
if (++writePtr == SAMPLE_COUNT)
|
||||
{
|
||||
if (currentBackend != nullptr)
|
||||
currentBackend->push(Buffer, SAMPLE_COUNT, config::LimitFPS);
|
||||
writePtr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void InitAudio()
|
||||
{
|
||||
registerForEvents();
|
||||
TermAudio();
|
||||
|
||||
std::string slug = config::AudioBackend;
|
||||
currentBackend = AudioBackend::getBackend(slug);
|
||||
if (currentBackend == nullptr && slug != "auto")
|
||||
{
|
||||
slug = "auto";
|
||||
currentBackend = AudioBackend::getBackend(slug);
|
||||
}
|
||||
if (currentBackend != nullptr)
|
||||
{
|
||||
INFO_LOG(AUDIO, "Initializing audio backend \"%s\" (%s)...", currentBackend->slug.c_str(), currentBackend->getName().c_str());
|
||||
if (!currentBackend->init())
|
||||
{
|
||||
currentBackend = nullptr;
|
||||
if (slug != "auto")
|
||||
{
|
||||
WARN_LOG(AUDIO, "Audio driver %s failed to initialize. Defaulting to 'auto'", slug.c_str());
|
||||
slug = "auto";
|
||||
currentBackend = AudioBackend::getBackend(slug);
|
||||
if (!currentBackend->init())
|
||||
currentBackend = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentBackend == nullptr)
|
||||
{
|
||||
WARN_LOG(AUDIO, "Running without audio!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (audio_recording_started)
|
||||
{
|
||||
// Restart recording
|
||||
audio_recording_started = false;
|
||||
StartAudioRecording(eight_khz);
|
||||
}
|
||||
}
|
||||
|
||||
void TermAudio()
|
||||
{
|
||||
if (currentBackend == nullptr)
|
||||
return;
|
||||
|
||||
// Save recording state before stopping
|
||||
bool rec_started = audio_recording_started;
|
||||
StopAudioRecording();
|
||||
audio_recording_started = rec_started;
|
||||
currentBackend->term();
|
||||
INFO_LOG(AUDIO, "Terminating audio backend \"%s\" (%s)...", currentBackend->slug.c_str(), currentBackend->getName().c_str());
|
||||
currentBackend = nullptr;
|
||||
}
|
||||
|
||||
void StartAudioRecording(bool eight_khz)
|
||||
{
|
||||
::eight_khz = eight_khz;
|
||||
if (currentBackend != nullptr)
|
||||
audio_recording_started = currentBackend->initRecord(eight_khz ? 8000 : 11025);
|
||||
else
|
||||
// might be called between TermAudio/InitAudio
|
||||
audio_recording_started = true;
|
||||
}
|
||||
|
||||
u32 RecordAudio(void *buffer, u32 samples)
|
||||
{
|
||||
if (!audio_recording_started || currentBackend == nullptr)
|
||||
return 0;
|
||||
return currentBackend->record(buffer, samples);
|
||||
}
|
||||
|
||||
void StopAudioRecording()
|
||||
{
|
||||
// might be called between TermAudio/InitAudio
|
||||
if (audio_recording_started && currentBackend != nullptr)
|
||||
currentBackend->termRecord();
|
||||
audio_recording_started = false;
|
||||
}
|
||||
|
||||
static void registerForEvents()
|
||||
{
|
||||
static bool done;
|
||||
if (done)
|
||||
return;
|
||||
done = true;
|
||||
// Empty the audio buffer when loading a state or terminating the game
|
||||
const auto& callback = [](Event, void *) {
|
||||
writePtr = 0;
|
||||
};
|
||||
EventManager::listen(Event::Terminate, callback);
|
||||
EventManager::listen(Event::LoadState, callback);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class AudioBackend
|
||||
{
|
||||
public:
|
||||
virtual ~AudioBackend() = default;
|
||||
|
||||
virtual bool init() = 0;
|
||||
virtual u32 push(const void *data, u32 frames, bool wait) = 0;
|
||||
virtual void term() {}
|
||||
|
||||
struct Option {
|
||||
std::string name;
|
||||
std::string caption;
|
||||
enum { integer, checkbox, list } type;
|
||||
|
||||
int minValue;
|
||||
int maxValue;
|
||||
std::vector<std::string> values;
|
||||
};
|
||||
virtual const Option *getOptions(int *count) {
|
||||
*count = 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual bool initRecord(u32 sampling_freq) { return false; }
|
||||
virtual u32 record(void *, u32) { return 0; }
|
||||
virtual void termRecord() {}
|
||||
|
||||
std::string slug;
|
||||
virtual std::string getName() const { return name; }
|
||||
|
||||
static size_t getCount() { return backends == nullptr ? 0 : backends->size(); }
|
||||
static AudioBackend *getBackend(size_t index) { return backends == nullptr ? nullptr : (*backends)[index]; }
|
||||
static AudioBackend *getBackend(const std::string& slug);
|
||||
|
||||
protected:
|
||||
AudioBackend(const std::string& slug, const std::string& name)
|
||||
: slug(slug), name(name) {
|
||||
registerAudioBackend(this);
|
||||
}
|
||||
std::string name;
|
||||
|
||||
private:
|
||||
static void registerAudioBackend(AudioBackend *backend)
|
||||
{
|
||||
if (backends == nullptr)
|
||||
backends = new std::vector<AudioBackend *>();
|
||||
backends->push_back(backend);
|
||||
std::sort(backends->begin(), backends->end(), [](AudioBackend *b1, AudioBackend *b2) { return b1->slug < b2->slug; });
|
||||
}
|
||||
|
||||
static std::vector<AudioBackend *> *backends;
|
||||
};
|
||||
|
||||
void InitAudio();
|
||||
void TermAudio();
|
||||
void WriteSample(s16 right, s16 left);
|
||||
|
||||
void StartAudioRecording(bool eight_khz);
|
||||
u32 RecordAudio(void *buffer, u32 samples);
|
||||
void StopAudioRecording();
|
||||
|
||||
constexpr u32 SAMPLE_COUNT = 512; // AudioBackend::push() is always called with that many frames
|
||||
|
||||
class RingBuffer
|
||||
{
|
||||
std::vector<u8> buffer;
|
||||
// The producer and the audio callback each update one cursor: keep them on
|
||||
// separate cache lines.
|
||||
alignas(64) std::atomic_int readCursor { 0 };
|
||||
alignas(64) std::atomic_int writeCursor { 0 };
|
||||
|
||||
u32 readSize() {
|
||||
return (u32)((writeCursor - readCursor + buffer.size()) % buffer.size());
|
||||
}
|
||||
u32 writeSize() {
|
||||
return (u32)((readCursor - writeCursor + buffer.size() - 1) % buffer.size());
|
||||
}
|
||||
|
||||
public:
|
||||
bool write(const u8 *data, u32 size)
|
||||
{
|
||||
if (size > writeSize())
|
||||
return false;
|
||||
u32 wc = writeCursor;
|
||||
u32 chunkSize = std::min<u32>(size, (u32)buffer.size() - wc);
|
||||
memcpy(&buffer[wc], data, chunkSize);
|
||||
wc = (wc + chunkSize) % buffer.size();
|
||||
size -= chunkSize;
|
||||
if (size > 0)
|
||||
{
|
||||
data += chunkSize;
|
||||
memcpy(&buffer[wc], data, size);
|
||||
wc = (wc + size) % buffer.size();
|
||||
}
|
||||
writeCursor = wc;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read(u8 *data, u32 size)
|
||||
{
|
||||
if (size > readSize())
|
||||
return false;
|
||||
u32 rc = readCursor;
|
||||
u32 chunkSize = std::min<u32>(size, (u32)buffer.size() - rc);
|
||||
memcpy(data, &buffer[rc], chunkSize);
|
||||
rc = (rc + chunkSize) % buffer.size();
|
||||
size -= chunkSize;
|
||||
if (size > 0)
|
||||
{
|
||||
data += chunkSize;
|
||||
memcpy(data, &buffer[rc], size);
|
||||
rc = (rc + size) % buffer.size();
|
||||
}
|
||||
readCursor = rc;
|
||||
return true;
|
||||
}
|
||||
|
||||
void setCapacity(size_t size)
|
||||
{
|
||||
std::fill(buffer.begin(), buffer.end(), 0);
|
||||
buffer.resize(size);
|
||||
readCursor = 0;
|
||||
writeCursor = 0;
|
||||
}
|
||||
};
|
||||
+223
-69
@@ -1,50 +1,211 @@
|
||||
#pragma once
|
||||
//#define STRICT_MODE
|
||||
#ifndef STRICT_MODE
|
||||
#define FAST_MMU
|
||||
#define USE_WINCE_HACK
|
||||
#endif
|
||||
/*
|
||||
reicast build options
|
||||
|
||||
#define DC_PLATFORM_DREAMCAST 0
|
||||
#define DC_PLATFORM_DEV_UNIT 1
|
||||
#define DC_PLATFORM_NAOMI 2
|
||||
#define DC_PLATFORM_NAOMI2 3
|
||||
#define DC_PLATFORM_ATOMISWAVE 4
|
||||
#define DC_PLATFORM_SYSTEMSP 5
|
||||
Reicast can support a lot of stuff, and this is an attempt
|
||||
to organize the build time options
|
||||
|
||||
Option categories
|
||||
|
||||
BUILD_* - BUILD_COMPILER, etc...
|
||||
definitions about the build machine
|
||||
|
||||
HOST_*
|
||||
definitions about the host machine
|
||||
|
||||
FEAT_*
|
||||
definitions about the features that this build targets
|
||||
This is higly related to HOST_*, but it's for options that might
|
||||
or might not be avaiable depending on the target host, or that
|
||||
features that are irrelevant of the host
|
||||
|
||||
Eg, Alsa, Pulse Audio and OSS might sense as HOST dedinitions
|
||||
but it usually makes more sense to detect them as runtime. In
|
||||
that context, HOST_ALSA makes no sense because the host might
|
||||
or might not have alsa installed/ running
|
||||
|
||||
MMU makes no sense as a HOST definition at all, so it should
|
||||
be FEAT_HAS_MMU
|
||||
|
||||
TARGET_*
|
||||
A preconfigured default. Eg TARGET_WIN86.
|
||||
|
||||
Naming of options, option values, and how to use them
|
||||
|
||||
for options that makes sense to have a list of values
|
||||
{CATEGORY}_{OPTION}
|
||||
{OPTION}_{VALUE}
|
||||
|
||||
eg.
|
||||
BUILD_COMPILER == COMPILER_GCC, HOST_CPU != CPU_X64, ...
|
||||
|
||||
for options that are boolean
|
||||
{CATEGORY}_IS_{OPTION} or {CATEGORY}_HAS_{OPTION}
|
||||
|
||||
Evaluates to 0 or 1
|
||||
|
||||
If an configuration cannot be neatly split into a set of
|
||||
of orthogonal options, then it makes sense to break things
|
||||
to "sets" or have a hierarchy of options.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
In the beggining it made sense to have an audio backend
|
||||
per operating system. It made sense to have it depend
|
||||
on HOST_OS and seleect DirectSound or alsa.
|
||||
|
||||
// no option needed
|
||||
|
||||
Then, as android was introduced, which also uses OS_LINUX
|
||||
atm, the audio could have been made an option. It could be
|
||||
a HOST_* option, or FEAT_* one. I'd prefer FEAT_*.
|
||||
FEAT_* makes more sense as future wise we might want
|
||||
to support multiple backends.
|
||||
|
||||
FEAT_AUDIO_BACKEND
|
||||
AUDIO_BACKEND_NONE
|
||||
AUDIO_BACKEND_DS
|
||||
AUDIO_BACKEND_ALSA
|
||||
AUDIO_BACKEND_ANDROID
|
||||
|
||||
Used like
|
||||
#if FEAT_AUDIO_BACKEND == AUDIO_BACKEND_DS ....
|
||||
|
||||
At some point, we might have multiple audio backends that
|
||||
can be compiled in and autodetected/selected at runtime.
|
||||
In that case, it might make sense to have the options like
|
||||
|
||||
FEAT_HAS_ALSA
|
||||
FEAT_HAS_DIRECTSOUND
|
||||
FEAT_HAS_ANDROID_AUDIO
|
||||
|
||||
or
|
||||
FEAT_HAS_AUDIO_ALSA
|
||||
FEAT_HAS_AUDIO_DS
|
||||
FEAT_HAS_AUDIO_ANDROID
|
||||
|
||||
The none option might or might not make sense. In this
|
||||
case it can be removed, as it should always be avaiable.
|
||||
|
||||
Guidelines
|
||||
----------
|
||||
|
||||
General rule of thumb, don't overcomplicate things. Start
|
||||
with a simple option, and then make it more complicated
|
||||
as new uses apply (see the example above)
|
||||
|
||||
Don't use too long names, don't use too cryptic names.
|
||||
Most team developers should be able to understand or
|
||||
figure out most of the acronyms used.
|
||||
|
||||
Try to be consistent on the acronyms across all definitions
|
||||
|
||||
Code shouldn't depend on build level options whenever possible
|
||||
|
||||
Generally, the file should compile even if the option/module is
|
||||
disabled. This makes makefiles etc much easier to write
|
||||
|
||||
TARGET_* options should generally only be used in this file
|
||||
|
||||
The current source is *not* good example of these guidelines
|
||||
|
||||
We'll try to be smart and figure out some options/defaults on this file
|
||||
but this shouldn't get too complicated
|
||||
|
||||
|
||||
*/
|
||||
|
||||
#define NO_MMU
|
||||
|
||||
#define DC_PLATFORM_MASK 7
|
||||
#define DC_PLATFORM_DREAMCAST 0 /* Works, for the most part */
|
||||
#define DC_PLATFORM_DEV_UNIT 1 /* This is missing hardware */
|
||||
#define DC_PLATFORM_NAOMI 2 /* Works, for the most part */
|
||||
#define DC_PLATFORM_NAOMI2 3 /* Needs to be done, 2xsh4 + 2xpvr + custom TNL */
|
||||
#define DC_PLATFORM_ATOMISWAVE 4 /* Needs to be done, DC-like hardware with possibly more ram */
|
||||
#define DC_PLATFORM_HIKARU 5 /* Needs to be done, 2xsh4, 2x aica , custom vpu */
|
||||
#define DC_PLATFORM_AURORA 6 /* Needs to be done, Uses newer 300 mhz sh4 + 150 mhz pvr mbx SoC */
|
||||
|
||||
|
||||
//HOST_OS
|
||||
#define OS_WINDOWS 0x10000001
|
||||
#define OS_LINUX 0x10000002
|
||||
#define OS_DARWIN 0x10000003
|
||||
|
||||
//HOST_CPU
|
||||
#define CPU_X86 0x20000001
|
||||
#define CPU_ARM 0x20000002
|
||||
#define CPU_ARM64 0x20000003
|
||||
#define CPU_MIPS 0x20000003
|
||||
#define CPU_X64 0x20000004
|
||||
#define CPU_GENERIC 0x20000005 //used for pnacl, emscripten, etc
|
||||
|
||||
//BUILD_COMPILER
|
||||
#define COMPILER_VC 0x30000001
|
||||
#define COMPILER_GCC 0x30000002
|
||||
|
||||
//FEAT_SHREC, FEAT_AREC, FEAT_DSPREC
|
||||
#define DYNAREC_NONE 0x40000001
|
||||
#define DYNAREC_JIT 0x40000002
|
||||
#define DYNAREC_CPP 0x40000003
|
||||
|
||||
|
||||
//automatic
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
#define HOST_CPU CPU_X64
|
||||
#elif defined(__i386__) || defined(_M_IX86)
|
||||
#define HOST_CPU CPU_X86
|
||||
#elif defined(__arm__) || defined (_M_ARM)
|
||||
#define HOST_CPU CPU_ARM
|
||||
#elif defined(__aarch64__) || defined(_M_ARM64)
|
||||
#define HOST_CPU CPU_ARM64
|
||||
#else
|
||||
#error Unsupported architecture
|
||||
#if defined(_WIN32) && !defined(TARGET_WIN86) && !defined(TARGET_WIN64)
|
||||
#if !defined(_M_AMD64)
|
||||
#define TARGET_WIN86
|
||||
#else
|
||||
#define TARGET_WIN64
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include "TargetConditionals.h"
|
||||
#if TARGET_OS_SIMULATOR
|
||||
// iOS simulator
|
||||
#define TARGET_NO_REC
|
||||
#ifdef __GNUC__
|
||||
#define BUILD_COMPILER COMPILER_GCC
|
||||
#else
|
||||
#define BUILD_COMPILER COMPILER_VC
|
||||
#endif
|
||||
#if defined(TARGET_MAC) && HOST_CPU == CPU_ARM64
|
||||
#define TARGET_ARM_MAC
|
||||
|
||||
//Targets
|
||||
#if defined(TARGET_WIN86)
|
||||
#define HOST_OS OS_WINDOWS
|
||||
#define HOST_CPU CPU_X86
|
||||
#elif defined(TARGET_WIN64)
|
||||
#define HOST_OS OS_WINDOWS
|
||||
#define HOST_CPU CPU_X64
|
||||
#elif defined(TARGET_PANDORA)
|
||||
#define HOST_OS OS_LINUX
|
||||
#define HOST_CPU CPU_ARM
|
||||
#elif defined(TARGET_LINUX_ARMELv7)
|
||||
#define HOST_OS OS_LINUX
|
||||
#define HOST_CPU CPU_ARM
|
||||
#elif defined(TARGET_LINUX_x86)
|
||||
#define HOST_OS OS_LINUX
|
||||
#define HOST_CPU CPU_X86
|
||||
#elif defined(TARGET_LINUX_x64)
|
||||
#define HOST_OS OS_LINUX
|
||||
#define HOST_CPU CPU_X64
|
||||
#elif defined(TARGET_LINUX_MIPS)
|
||||
#define HOST_OS OS_LINUX
|
||||
#define HOST_CPU CPU_MIPS
|
||||
#elif defined(TARGET_GCW0)
|
||||
#define HOST_OS OS_LINUX
|
||||
#define HOST_CPU CPU_MIPS
|
||||
#elif defined(TARGET_NACL32) || defined(TARGET_EMSCRIPTEN)
|
||||
#define HOST_OS OS_LINUX
|
||||
#define HOST_CPU CPU_GENERIC
|
||||
#elif defined(TARGET_IPHONE)
|
||||
#define HOST_OS OS_DARWIN
|
||||
#define HOST_CPU CPU_ARM
|
||||
#elif defined(TARGET_OSX)
|
||||
#define HOST_OS OS_DARWIN
|
||||
#define HOST_CPU CPU_GENERIC
|
||||
#else
|
||||
#error Invalid Target: TARGET_* not defined
|
||||
#endif
|
||||
|
||||
#if defined(TARGET_NAOMI)
|
||||
#define DC_PLATFORM DC_PLATFORM_NAOMI
|
||||
#undef TARGET_NAOMI
|
||||
#endif
|
||||
|
||||
#if defined(TARGET_NO_REC)
|
||||
@@ -59,22 +220,33 @@
|
||||
#define FEAT_DSPREC DYNAREC_NONE
|
||||
#endif
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#define FEAT_NO_RWX_PAGES
|
||||
#if defined(TARGET_NO_JIT)
|
||||
#define FEAT_SHREC DYNAREC_CPP
|
||||
#define FEAT_AREC DYNAREC_NONE
|
||||
#define FEAT_DSPREC DYNAREC_NONE
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(TARGET_NO_NIXPROF)
|
||||
#define FEAT_HAS_NIXPROF 0
|
||||
#endif
|
||||
|
||||
#if defined(TARGET_NO_COREIO_HTTP)
|
||||
#define FEAT_HAS_COREIO_HTTP 0
|
||||
#endif
|
||||
|
||||
//defaults
|
||||
|
||||
#ifndef DC_PLATFORM
|
||||
#define DC_PLATFORM DC_PLATFORM_DREAMCAST
|
||||
#endif
|
||||
|
||||
#ifndef FEAT_SHREC
|
||||
#if HOST_CPU == CPU_ARM || HOST_CPU == CPU_ARM64 || HOST_CPU == CPU_X86 || HOST_CPU == CPU_X64
|
||||
#define FEAT_SHREC DYNAREC_JIT
|
||||
#else
|
||||
#define FEAT_SHREC DYNAREC_NONE
|
||||
#endif
|
||||
#define FEAT_SHREC DYNAREC_JIT
|
||||
#endif
|
||||
|
||||
#ifndef FEAT_AREC
|
||||
#if HOST_CPU == CPU_ARM || HOST_CPU == CPU_ARM64 || HOST_CPU == CPU_X64
|
||||
#if HOST_CPU == CPU_ARM || HOST_CPU == CPU_X86
|
||||
#define FEAT_AREC DYNAREC_JIT
|
||||
#else
|
||||
#define FEAT_AREC DYNAREC_NONE
|
||||
@@ -82,46 +254,28 @@
|
||||
#endif
|
||||
|
||||
#ifndef FEAT_DSPREC
|
||||
#if HOST_CPU == CPU_ARM || HOST_CPU == CPU_ARM64 || HOST_CPU == CPU_X86 || HOST_CPU == CPU_X64
|
||||
#if HOST_CPU == CPU_X86
|
||||
#define FEAT_DSPREC DYNAREC_JIT
|
||||
#else
|
||||
#define FEAT_DSPREC DYNAREC_NONE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Some restrictions on FEAT_NO_RWX_PAGES
|
||||
#if defined(FEAT_NO_RWX_PAGES) && FEAT_SHREC == DYNAREC_JIT
|
||||
#if HOST_CPU != CPU_X64 && HOST_CPU != CPU_ARM64
|
||||
#error "FEAT_NO_RWX_PAGES Only implemented for X64 and ARMv8"
|
||||
#endif
|
||||
#ifndef FEAT_HAS_NIXPROF
|
||||
#if HOST_OS != OS_WINDOWS
|
||||
#define FEAT_HAS_NIXPROF 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
|
||||
#define TARGET_UWP 1
|
||||
#endif
|
||||
#ifdef HAVE_D3D11
|
||||
#define USE_DX11
|
||||
#endif
|
||||
#ifndef FEAT_HAS_COREIO_HTTP
|
||||
#define FEAT_HAS_COREIO_HTTP 1
|
||||
#endif
|
||||
|
||||
#if !defined(LIBRETRO)
|
||||
#define USE_GGPO
|
||||
//Depricated build configs
|
||||
#ifdef HOST_NO_REC
|
||||
#error Dont use HOST_NO_REC
|
||||
#endif
|
||||
|
||||
#if !defined(__ANDROID__) && !defined(TARGET_IPHONE) && !defined(TARGET_UWP) \
|
||||
&& !defined(__SWITCH__) && !defined(LIBRETRO) && !defined(__NetBSD__) && !defined(__OpenBSD__)
|
||||
#define NAOMI_MULTIBOARD
|
||||
#endif
|
||||
|
||||
// TARGET PLATFORM
|
||||
#define GD_CLOCK 33868800 //GDROM XTAL -- 768fs
|
||||
#define AICA_CORE_CLOCK (GD_CLOCK * 4 / 3) //[45158400] GD->PLL 3:4 -> AICA CORE -- 1024fs
|
||||
#define AICA_ARM_CLOCK (AICA_CORE_CLOCK / 2) //[22579200] AICA CORE -> PLL 2:1 -> ARM
|
||||
#define SH4_MAIN_CLOCK (200 * 1000 * 1000) //[200000000] XTal(13.5) -> PLL (33.3) -> PLL 1:6 (200)
|
||||
#define G2_BUS_CLOCK (25 * 1000 * 1000) //[25000000] from Holly, from SH4_RAM_CLOCK w/ 2 2:1 plls
|
||||
|
||||
#if defined(GLES) && !defined(GLES3) && !defined(GLES2)
|
||||
// Only use GL ES 2.0 API functions
|
||||
#define GLES2
|
||||
#endif
|
||||
#ifdef HOST_NO_AREC
|
||||
#error Dont use HOST_NO_AREC
|
||||
#endif
|
||||
@@ -1,12 +0,0 @@
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
option.h)
|
||||
|
||||
if(NOT LIBRETRO)
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
cfg.cpp
|
||||
cfg.h
|
||||
cl.cpp
|
||||
ini.cpp
|
||||
ini.h
|
||||
option.cpp)
|
||||
endif()
|
||||
+132
-102
@@ -1,135 +1,165 @@
|
||||
/*
|
||||
Config file crap
|
||||
Supports various things, as virtual config entries and such crap
|
||||
Works surprisingly well considering how old it is ...
|
||||
*/
|
||||
|
||||
#define _CRT_SECURE_NO_DEPRECATE (1)
|
||||
#include <errno.h>
|
||||
#include "cfg.h"
|
||||
#include "ini.h"
|
||||
#include "stdclass.h"
|
||||
#include <cerrno>
|
||||
|
||||
namespace config
|
||||
string cfgPath;
|
||||
bool save_config = true;
|
||||
|
||||
ConfigFile cfgdb;
|
||||
|
||||
void savecfgf()
|
||||
{
|
||||
|
||||
static std::string cfgPath;
|
||||
static bool autoSaving = true;
|
||||
static IniFile cfgdb;
|
||||
|
||||
static void saveFile()
|
||||
{
|
||||
hostfs::File* cfgfile = hostfs::storage().openFile(cfgPath.c_str(), "wt");
|
||||
if (!cfgfile) {
|
||||
WARN_LOG(COMMON, "Error: Unable to open file '%s' for saving", cfgPath.c_str());
|
||||
FILE* cfgfile = fopen(cfgPath.c_str(),"wt");
|
||||
if (!cfgfile)
|
||||
{
|
||||
printf("Error : Unable to open file for saving \n");
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
cfgdb.save(cfgfile);
|
||||
delete cfgfile;
|
||||
fclose(cfgfile);
|
||||
}
|
||||
}
|
||||
|
||||
static void autoSave() {
|
||||
if (autoSaving)
|
||||
saveFile();
|
||||
}
|
||||
|
||||
bool open()
|
||||
void cfgSaveStr(const wchar * Section, const wchar * Key, const wchar * String)
|
||||
{
|
||||
if (get_writable_config_path("").empty())
|
||||
// Config dir not set (android onboarding)
|
||||
return false;
|
||||
cfgdb.set(string(Section), string(Key), string(String));
|
||||
if(save_config)
|
||||
{
|
||||
savecfgf();
|
||||
}
|
||||
//WritePrivateProfileString(Section,Key,String,cfgPath);
|
||||
}
|
||||
//New config code
|
||||
|
||||
const char* filename = "emu.cfg";
|
||||
std::string config_path_read = get_readonly_config_path(filename);
|
||||
/*
|
||||
I want config to be really flexible .. so , here is the new implementation :
|
||||
|
||||
Functions :
|
||||
cfgLoadInt : Load an int , if it does not exist save the default value to it and return it
|
||||
cfgSaveInt : Save an int
|
||||
cfgLoadStr : Load a str , if it does not exist save the default value to it and return it
|
||||
cfgSaveStr : Save a str
|
||||
cfgExists : Returns true if the Section:Key exists. If Key is null , it retuns true if Section exists
|
||||
|
||||
Config parameters can be read from the config file , and can be given at the command line
|
||||
-cfg section:key=value -> defines a value at command line
|
||||
If a cfgSave* is made on a value defined by command line , then the command line value is replaced by it
|
||||
|
||||
cfg values set by command line are not written to the cfg file , unless a cfgSave* is used
|
||||
|
||||
There are some special values , all of em are on the emu namespace :)
|
||||
|
||||
These are readonly :
|
||||
|
||||
emu:AppPath : Returns the path where the emulator is stored
|
||||
emu:PluginPath : Returns the path where the plugins are loaded from
|
||||
emu:DataPath : Returns the path where the bios/data files are
|
||||
|
||||
emu:FullName : str,returns the emulator's name + version string (ex."nullDC v1.0.0 Private Beta 2 built on {datetime}")
|
||||
emu:ShortName : str,returns the emulator's name + version string , short form (ex."nullDC 1.0.0pb2")
|
||||
emu:Name : str,returns the emulator's name (ex."nullDC")
|
||||
|
||||
These are read/write
|
||||
emu:Caption : str , get/set the window caption
|
||||
*/
|
||||
|
||||
///////////////////////////////
|
||||
/*
|
||||
** This will verify there is a working file @ ./szIniFn
|
||||
** - if not present, it will write defaults
|
||||
*/
|
||||
|
||||
bool cfgOpen()
|
||||
{
|
||||
const char* filename = "/emu.cfg";
|
||||
string config_path_read = get_readonly_config_path(filename);
|
||||
cfgPath = get_writable_config_path(filename);
|
||||
|
||||
hostfs::File* cfgfile = hostfs::storage().openFile(config_path_read.c_str(), "rt");
|
||||
if (cfgfile != nullptr) {
|
||||
cfgdb.load(cfgfile);
|
||||
delete cfgfile;
|
||||
FILE* cfgfile = fopen(config_path_read.c_str(),"r");
|
||||
if(cfgfile != NULL) {
|
||||
cfgdb.parse(cfgfile);
|
||||
fclose(cfgfile);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Config file can't be opened
|
||||
int error_code = errno;
|
||||
WARN_LOG(COMMON, "Warning: Unable to open the config file '%s' for reading (%s)", config_path_read.c_str(), strerror(error_code));
|
||||
printf("Warning: Unable to open the config file '%s' for reading (%s)\n", config_path_read.c_str(), strerror(error_code));
|
||||
if (error_code == ENOENT || cfgPath != config_path_read)
|
||||
{
|
||||
// Config file didn't exist
|
||||
INFO_LOG(COMMON, "Creating new empty config file at '%s'", cfgPath.c_str());
|
||||
saveFile();
|
||||
printf("Creating new empty config file at '%s'\n", cfgPath.c_str());
|
||||
savecfgf();
|
||||
}
|
||||
else
|
||||
{
|
||||
// There was some other error (may be a permissions problem or something like that)
|
||||
save_config = false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void saveStr(const std::string& section, const std::string& key, const std::string& value) {
|
||||
cfgdb.set(section, key, value);
|
||||
autoSave();
|
||||
}
|
||||
|
||||
std::string loadStr(const std::string& section, const std::string& key, const std::string& def) {
|
||||
return cfgdb.getString(section, key, def);
|
||||
}
|
||||
|
||||
void saveInt(const std::string& section, const std::string& key, int value) {
|
||||
cfgdb.set(section, key, value);
|
||||
autoSave();
|
||||
}
|
||||
|
||||
int loadInt(const std::string& section, const std::string& key, int def) {
|
||||
return cfgdb.getInt(section, key, def);
|
||||
}
|
||||
|
||||
int64_t loadInt64(const std::string& section, const std::string& key, int64_t def) {
|
||||
return cfgdb.getInt64(section, key, def);
|
||||
}
|
||||
void saveInt64(const std::string& section, const std::string& key, int64_t value) {
|
||||
cfgdb.set(section, key, value);
|
||||
autoSave();
|
||||
}
|
||||
|
||||
void saveBool(const std::string& section, const std::string& key, bool value) {
|
||||
cfgdb.set(section, key, value);
|
||||
autoSave();
|
||||
}
|
||||
|
||||
bool loadBool(const std::string& section, const std::string& key, bool def) {
|
||||
return cfgdb.getBool(section, key, def);
|
||||
}
|
||||
|
||||
void setTransient(const std::string& section, const std::string& key, const std::string& value) {
|
||||
cfgdb.set(section, key, value, true);
|
||||
}
|
||||
|
||||
bool isTransient(const std::string& section, const std::string& key) {
|
||||
return cfgdb.isTransient(section, key);
|
||||
}
|
||||
|
||||
bool hasSection(const std::string& section) {
|
||||
return cfgdb.hasSection(section);
|
||||
}
|
||||
|
||||
void deleteSection(const std::string& section) {
|
||||
cfgdb.deleteSection(section);
|
||||
}
|
||||
|
||||
void deleteEntry(const std::string& section, const std::string& key) {
|
||||
cfgdb.deleteEntry(section, key);
|
||||
}
|
||||
|
||||
void setAutoSave(bool autoSaving)
|
||||
//Implementations of the interface :)
|
||||
//Section must be set
|
||||
//If key is 0 , it looks for the section
|
||||
//0 : not found
|
||||
//1 : found section , key was 0
|
||||
//2 : found section & key
|
||||
s32 cfgExists(const wchar * Section, const wchar * Key)
|
||||
{
|
||||
config::autoSaving = autoSaving;
|
||||
if (autoSaving)
|
||||
saveFile();
|
||||
if(cfgdb.has_entry(string(Section), string(Key)))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (cfgdb.has_section(string(Section)) ? 1 : 0);
|
||||
}
|
||||
}
|
||||
void cfgLoadStr(const wchar * Section, const wchar * Key, wchar * Return,const wchar* Default)
|
||||
{
|
||||
string value = cfgdb.get(Section, Key, Default);
|
||||
strcpy(Return, value.c_str());
|
||||
}
|
||||
|
||||
void saveFloat(const std::string& section, const std::string& key, float value) {
|
||||
cfgdb.set(section, key, value);
|
||||
autoSave();
|
||||
}
|
||||
float loadFloat(const std::string& section, const std::string& key, float def) {
|
||||
return cfgdb.getFloat(section, key, def);
|
||||
string cfgLoadStr(const wchar * Section, const wchar * Key, const wchar* Default)
|
||||
{
|
||||
if(!cfgdb.has_entry(string(Section), string(Key)))
|
||||
{
|
||||
cfgSaveStr(Section, Key, Default);
|
||||
}
|
||||
return cfgdb.get(string(Section), string(Key), string(Default));
|
||||
}
|
||||
|
||||
std::vector<std::string> getEntries(const std::string& section) {
|
||||
return cfgdb.getEntryNames(section);
|
||||
//These are helpers , mainly :)
|
||||
void cfgSaveInt(const wchar * Section, const wchar * Key, s32 Int)
|
||||
{
|
||||
cfgdb.set_int(string(Section), string(Key), Int);
|
||||
if(save_config)
|
||||
{
|
||||
savecfgf();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace config
|
||||
s32 cfgLoadInt(const wchar * Section, const wchar * Key,s32 Default)
|
||||
{
|
||||
if(!cfgdb.has_entry(string(Section), string(Key)))
|
||||
{
|
||||
cfgSaveInt(Section, Key, Default);
|
||||
}
|
||||
return cfgdb.get_int(string(Section), string(Key), Default);
|
||||
}
|
||||
|
||||
void cfgSetVirtual(const wchar * Section, const wchar * Key, const wchar * String)
|
||||
{
|
||||
cfgdb.set(string(Section), string(Key), string(String), true);
|
||||
}
|
||||
+16
-26
@@ -1,31 +1,21 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace config
|
||||
{
|
||||
#include "types.h"
|
||||
|
||||
bool open();
|
||||
int loadInt(const std::string& section, const std::string& key, int def = 0);
|
||||
void saveInt(const std::string& section, const std::string& key, int value);
|
||||
int64_t loadInt64(const std::string& section, const std::string& key, int64_t def = 0);
|
||||
void saveInt64(const std::string& section, const std::string& key, int64_t value);
|
||||
std::string loadStr(const std::string& section, const std::string& key, const std::string& def = {});
|
||||
void saveStr(const std::string& section, const std::string& key, const std::string& value);
|
||||
bool loadBool(const std::string& section, const std::string& key, bool def = false);
|
||||
void saveBool(const std::string& section, const std::string& key, bool value);
|
||||
float loadFloat(const std::string& section, const std::string& key, float def = 0.f);
|
||||
void saveFloat(const std::string& section, const std::string& key, float value);
|
||||
void setTransient(const std::string& section, const std::string& key, const std::string& value);
|
||||
bool isTransient(const std::string& section, const std::string& key);
|
||||
/*
|
||||
** cfg* prototypes, if you pass NULL to a cfgSave* it will wipe out the section
|
||||
** } if you pass it to lpKey it will wipe out that particular entry
|
||||
** } if you add write to something it will create it if its not present
|
||||
** } ** Strings passed to LoadStr should be MAX_PATH in size ! **
|
||||
*/
|
||||
|
||||
void parseCommandLine(int argc, const char * const argv[]);
|
||||
bool cfgOpen();
|
||||
s32 cfgLoadInt(const wchar * lpSection, const wchar * lpKey,s32 Default);
|
||||
void cfgSaveInt(const wchar * lpSection, const wchar * lpKey, s32 Int);
|
||||
void cfgLoadStr(const wchar * lpSection, const wchar * lpKey, wchar * lpReturn,const wchar* lpDefault);
|
||||
string cfgLoadStr(const wchar * Section, const wchar * Key, const wchar* Default);
|
||||
void cfgSaveStr(const wchar * lpSection, const wchar * lpKey, const wchar * lpString);
|
||||
s32 cfgExists(const wchar * Section, const wchar * Key);
|
||||
void cfgSetVirtual(const wchar * lpSection, const wchar * lpKey, const wchar * lpString);
|
||||
|
||||
void setAutoSave(bool autoSave);
|
||||
bool hasSection(const std::string& section);
|
||||
void deleteSection(const std::string& section);
|
||||
void deleteEntry(const std::string& section, const std::string& key);
|
||||
|
||||
std::vector<std::string> getEntries(const std::string& section);
|
||||
}
|
||||
bool ParseCommandLine(int argc,wchar* argv[]);
|
||||
+142
-211
@@ -1,226 +1,157 @@
|
||||
/*
|
||||
Copyright 2025 flyinghead
|
||||
Command line parsing
|
||||
~yay~
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
Nothing too interesting here, really
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "cfg/cfg.h"
|
||||
#include "stdclass.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace config
|
||||
wchar* trim_ws(wchar* str)
|
||||
{
|
||||
if (str==0 || strlen(str)==0)
|
||||
return 0;
|
||||
|
||||
static void usage(const char *exe)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s [option]... [<rom path>]\n", exe);
|
||||
fprintf(stderr, "Options:\n");
|
||||
fprintf(stderr, "-config section:key=value,... set a transient config value.\n");
|
||||
fprintf(stderr, " Transient config values won't be saved to emu.cfg.\n");
|
||||
fprintf(stderr, "-help display this help\n");
|
||||
}
|
||||
|
||||
static void parseConfigOption(const std::string& str)
|
||||
{
|
||||
char inQuote = '\0';
|
||||
std::string section, key, value;
|
||||
int step = 0; // section, key, value
|
||||
for (char c : str)
|
||||
while(*str)
|
||||
{
|
||||
if (inQuote != '\0' && c == inQuote)
|
||||
{
|
||||
inQuote = false;
|
||||
step = 0;
|
||||
setTransient(section, key, value);
|
||||
DEBUG_LOG(COMMON, "-config [%s] %s = %s", section.c_str(), key.c_str(), value.c_str());
|
||||
section.clear();
|
||||
key.clear();
|
||||
value.clear();
|
||||
continue;
|
||||
}
|
||||
switch (c)
|
||||
{
|
||||
case ':':
|
||||
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;
|
||||
}
|
||||
step = 1;
|
||||
break;
|
||||
case 1:
|
||||
key += c;
|
||||
break;
|
||||
case 2:
|
||||
value += c;
|
||||
break;
|
||||
}
|
||||
if (!isspace(*str))
|
||||
break;
|
||||
case '=':
|
||||
switch (step)
|
||||
{
|
||||
case 0:
|
||||
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;
|
||||
}
|
||||
step = 2;
|
||||
break;
|
||||
case 2:
|
||||
value += c;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case '\'':
|
||||
case '"':
|
||||
switch (step)
|
||||
{
|
||||
case 0:
|
||||
section += c;
|
||||
break;
|
||||
case 1:
|
||||
key += c;
|
||||
break;
|
||||
case 2:
|
||||
if (inQuote == '\0') {
|
||||
inQuote = c;
|
||||
value.clear();
|
||||
}
|
||||
else
|
||||
value += c;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ',':
|
||||
switch (step)
|
||||
{
|
||||
case 0:
|
||||
// ignore consecutive commas
|
||||
break;
|
||||
case 1:
|
||||
key += c;
|
||||
break;
|
||||
case 2:
|
||||
if (inQuote != '\0') {
|
||||
value += c;
|
||||
}
|
||||
else
|
||||
{
|
||||
step = 0;
|
||||
setTransient(section, key, value);
|
||||
DEBUG_LOG(COMMON, "-config [%s] %s = %s", section.c_str(), key.c_str(), value.c_str());
|
||||
section.clear();
|
||||
key.clear();
|
||||
value.clear();
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ' ':
|
||||
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:
|
||||
if (!key.empty())
|
||||
key += c;
|
||||
break;
|
||||
case 2:
|
||||
value += c;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
switch (step)
|
||||
{
|
||||
case 0:
|
||||
section += c;
|
||||
break;
|
||||
case 1:
|
||||
key += c;
|
||||
break;
|
||||
case 2:
|
||||
value += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
str++;
|
||||
}
|
||||
if (step == 2) {
|
||||
setTransient(section, key, value);
|
||||
DEBUG_LOG(COMMON, "-config [%s] %s = %s", section.c_str(), key.c_str(), value.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void parseCommandLine(int argc, const char * const argv[])
|
||||
{
|
||||
settings.content.path.clear();
|
||||
const char *exe = argv[0];
|
||||
for (int i = 1; i < argc; i++)
|
||||
size_t l=strlen(str);
|
||||
|
||||
if (l==0)
|
||||
return 0;
|
||||
|
||||
while(l>0)
|
||||
{
|
||||
if (!strcmp(argv[i], "-help") || !strcmp(argv[i], "--help")) {
|
||||
usage(exe);
|
||||
exit(0);
|
||||
}
|
||||
if (!strcmp(argv[i], "-config") || !strcmp(argv[i], "--config"))
|
||||
{
|
||||
if (i < argc - 1)
|
||||
parseConfigOption(argv[++i]);
|
||||
continue;
|
||||
}
|
||||
// macOS
|
||||
if (!strncmp(argv[i], "-NSDocumentRevisions", 20)) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (argv[i][0] == '-') {
|
||||
WARN_LOG(COMMON, "Ignoring unknown command line option '%s'", argv[i]);
|
||||
continue;
|
||||
}
|
||||
std::string extension = get_file_extension(argv[i]);
|
||||
if (extension == "cdi" || extension == "chd"
|
||||
|| extension == "gdi"|| extension == "cue")
|
||||
{
|
||||
INFO_LOG(COMMON, "Using '%s' as CD image", argv[i]);
|
||||
settings.content.path = argv[i];
|
||||
}
|
||||
else if (extension == "elf")
|
||||
{
|
||||
INFO_LOG(COMMON, "Using '%s' as reios elf file", argv[i]);
|
||||
setTransient("config", "bios.UseReios", "yes");
|
||||
settings.content.path = argv[i];
|
||||
}
|
||||
else {
|
||||
INFO_LOG(COMMON, "Using '%s' as rom", argv[i]);
|
||||
settings.content.path = argv[i];
|
||||
}
|
||||
if (i < argc - 1)
|
||||
WARN_LOG(COMMON, "Rest of command line ignored: '%s'...", argv[i + 1]);
|
||||
break;
|
||||
if (!isspace(str[l-1]))
|
||||
break;
|
||||
str[l-1]=0;
|
||||
l--;
|
||||
}
|
||||
|
||||
if (l==0)
|
||||
return 0;
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
} // namespace config
|
||||
int setconfig(wchar** arg,int cl)
|
||||
{
|
||||
int rv=0;
|
||||
for(;;)
|
||||
{
|
||||
if (cl<1)
|
||||
{
|
||||
printf("-config : invalid number of parameters, format is section:key=value\n");
|
||||
return rv;
|
||||
}
|
||||
wchar* sep=strstr(arg[1],":");
|
||||
if (sep==0)
|
||||
{
|
||||
printf("-config : invalid parameter %s, format is section:key=value\n",arg[1]);
|
||||
return rv;
|
||||
}
|
||||
wchar* value=strstr(sep+1,"=");
|
||||
if (value==0)
|
||||
{
|
||||
printf("-config : invalid parameter %s, format is section:key=value\n",arg[1]);
|
||||
return rv;
|
||||
}
|
||||
|
||||
*sep++=0;
|
||||
*value++=0;
|
||||
|
||||
wchar* sect=trim_ws(arg[1]);
|
||||
wchar* key=trim_ws(sep);
|
||||
value=trim_ws(value);
|
||||
|
||||
if (sect==0 || key==0)
|
||||
{
|
||||
printf("-config : invalid parameter, format is section:key=value\n");
|
||||
return rv;
|
||||
}
|
||||
|
||||
const wchar* constval = value;
|
||||
if (constval==0)
|
||||
constval="";
|
||||
printf("Virtual cfg %s:%s=%s\n",sect,key,value);
|
||||
|
||||
cfgSetVirtual(sect,key,value);
|
||||
rv++;
|
||||
|
||||
if (cl>=3 && stricmp(arg[2],",")==0)
|
||||
{
|
||||
cl-=2;
|
||||
arg+=2;
|
||||
rv++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
int showhelp(wchar** arg,int cl)
|
||||
{
|
||||
printf("Available commands :\n");
|
||||
|
||||
printf("-config section:key=value [, ..]: add a virtual config value\n Virtual config values wont be saved to the .cfg file\n unless a different value is writen to em\nNote :\n You can specify many settings in the xx:yy=zz , gg:hh=jj , ...\n format.The spaces betwen the values and ',' are needed.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
bool ParseCommandLine(int argc,wchar* argv[])
|
||||
{
|
||||
|
||||
int cl=argc-2;
|
||||
wchar** arg=argv+1;
|
||||
while(cl>=0)
|
||||
{
|
||||
if (stricmp(*arg,"-help")==0)
|
||||
{
|
||||
int as=showhelp(arg,cl);
|
||||
cl-=as;
|
||||
arg+=as;
|
||||
return true;
|
||||
}
|
||||
else if (stricmp(*arg,"-config")==0)
|
||||
{
|
||||
int as=setconfig(arg,cl);
|
||||
cl-=as;
|
||||
arg+=as;
|
||||
}
|
||||
else
|
||||
{
|
||||
char* extension = strrchr(*arg, '.');
|
||||
|
||||
if (extension
|
||||
&& (stricmp(extension, ".cdi") == 0 || stricmp(extension, ".chd") == 0
|
||||
|| stricmp(extension, ".gdi") == 0))
|
||||
{
|
||||
printf("Using '%s' as cd image\n", *arg);
|
||||
cfgSetVirtual("config", "image", *arg);
|
||||
}
|
||||
else if (extension && stricmp(extension, ".elf") == 0)
|
||||
{
|
||||
printf("Using '%s' as reios elf file\n", *arg);
|
||||
cfgSetVirtual("config", "reios.enabled", "1");
|
||||
cfgSetVirtual("reios", "ElfFile", *arg);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("wtf %s is suposed to do ?\n",*arg);
|
||||
}
|
||||
}
|
||||
arg++;
|
||||
cl--;
|
||||
}
|
||||
printf("\n");
|
||||
return false;
|
||||
}
|
||||
+239
-293
@@ -1,333 +1,279 @@
|
||||
/*
|
||||
Copyright 2025 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "ini.h"
|
||||
#include "types.h"
|
||||
#include "stdclass.h"
|
||||
#include <sstream>
|
||||
|
||||
#include <cctype>
|
||||
wchar* trim_ws(wchar* str);
|
||||
|
||||
namespace config {
|
||||
/* ConfigEntry */
|
||||
|
||||
const IniFile::Entry *IniFile::getEntry(const std::string& sectionName, const std::string& name) const
|
||||
string ConfigEntry::get_string()
|
||||
{
|
||||
auto it = sections.find(sectionName);
|
||||
if (it == sections.end())
|
||||
return nullptr;
|
||||
const Section& section = it->second;
|
||||
auto it2 = section.entries.find(name);
|
||||
if (it2 == section.entries.end())
|
||||
return nullptr;
|
||||
return &it2->second;
|
||||
return this->value;
|
||||
}
|
||||
|
||||
const std::string *IniFile::getRaw(const std::string& section, const std::string& name) const
|
||||
int ConfigEntry::get_int()
|
||||
{
|
||||
const Entry *entry = getEntry(section, name);
|
||||
if (entry == nullptr)
|
||||
return nullptr;
|
||||
if (entry->transient)
|
||||
return &entry->transientValue;
|
||||
if (strstr(this->value.c_str(), "0x") != NULL)
|
||||
{
|
||||
return strtol(this->value.c_str(), NULL, 16);
|
||||
}
|
||||
else
|
||||
return &entry->value;
|
||||
{
|
||||
return atoi(this->value.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& IniFile::getString(const std::string& section, const std::string& entry, const std::string& defaultValue) const
|
||||
bool ConfigEntry::get_bool()
|
||||
{
|
||||
const std::string *pValue = getRaw(section, entry);
|
||||
if (pValue == nullptr)
|
||||
return defaultValue;
|
||||
else
|
||||
return *pValue;
|
||||
}
|
||||
|
||||
bool IniFile::getBool(const std::string& section, const std::string& entry, bool defaultValue) const
|
||||
{
|
||||
const std::string *pValue = getRaw(section, entry);
|
||||
if (pValue == nullptr)
|
||||
return defaultValue;
|
||||
if (!stricmp("yes", pValue->c_str())
|
||||
|| !stricmp("true", pValue->c_str())
|
||||
|| !stricmp("on", pValue->c_str())
|
||||
|| !stricmp("1", pValue->c_str()))
|
||||
if (stricmp(this->value.c_str(), "yes") == 0 ||
|
||||
stricmp(this->value.c_str(), "true") == 0 ||
|
||||
stricmp(this->value.c_str(), "on") == 0 ||
|
||||
stricmp(this->value.c_str(), "1") == 0)
|
||||
{
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool hasHexPrefix(const std::string& s)
|
||||
{
|
||||
size_t pos = 0;
|
||||
while (pos + 1 < s.length() && std::isspace(static_cast<unsigned char>(s[pos])))
|
||||
pos++;
|
||||
if (pos + 1 >= s.length())
|
||||
return false;
|
||||
return (s[pos] == '0' && (s[pos + 1] == 'x' || s[pos + 1] == 'X'));
|
||||
}
|
||||
|
||||
int IniFile::getInt(const std::string& section, const std::string& entry, int defaultValue) const
|
||||
{
|
||||
const std::string *pValue = getRaw(section, entry);
|
||||
if (pValue == nullptr)
|
||||
return defaultValue;
|
||||
std::istringstream ss(*pValue);
|
||||
ss.imbue(std::locale::classic());
|
||||
if (hasHexPrefix(*pValue))
|
||||
ss.setf(std::ios_base::hex, std::ios_base::basefield);
|
||||
else
|
||||
ss.setf(std::ios_base::dec, std::ios_base::basefield);
|
||||
unsigned value = static_cast<unsigned>(defaultValue);
|
||||
ss >> value;
|
||||
if (ss)
|
||||
return static_cast<int>(value);
|
||||
// Compatibility for options migrated from Option<bool> to Option<int>.
|
||||
std::string token = *pValue;
|
||||
string_tolower(token);
|
||||
if (token == "yes" || token == "true" || token == "on")
|
||||
return 1;
|
||||
if (token == "no" || token == "false" || token == "off")
|
||||
return 0;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
int64_t IniFile::getInt64(const std::string& section, const std::string& entry, int64_t defaultValue) const
|
||||
{
|
||||
const std::string *pValue = getRaw(section, entry);
|
||||
if (pValue == nullptr)
|
||||
return defaultValue;
|
||||
std::istringstream ss(*pValue);
|
||||
ss.imbue(std::locale::classic());
|
||||
if (hasHexPrefix(*pValue))
|
||||
ss.setf(std::ios_base::hex, std::ios_base::basefield);
|
||||
else
|
||||
ss.setf(std::ios_base::dec, std::ios_base::basefield);
|
||||
int64_t value = defaultValue;
|
||||
ss >> value;
|
||||
return value;
|
||||
}
|
||||
|
||||
float IniFile::getFloat(const std::string& section, const std::string& entry, float defaultValue) const
|
||||
{
|
||||
const std::string *pValue = getRaw(section, entry);
|
||||
if (pValue == nullptr || pValue->empty())
|
||||
return defaultValue;
|
||||
std::istringstream ss(*pValue);
|
||||
ss.imbue(std::locale::classic());
|
||||
float value = defaultValue;
|
||||
ss >> value;
|
||||
return value;
|
||||
}
|
||||
|
||||
void IniFile::setRaw(const std::string& sectionName, const std::string& entryName, const std::string& value, bool transient)
|
||||
{
|
||||
Section& section = sections[sectionName];
|
||||
Entry& entry = section.entries[entryName];
|
||||
if (entry.name.empty())
|
||||
entry.name = entryName;
|
||||
if (transient) {
|
||||
entry.transientValue = value;
|
||||
entry.transient = true;
|
||||
}
|
||||
else {
|
||||
entry.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
static std::string handleEscapeSeq(const std::string& s)
|
||||
{
|
||||
std::string ret;
|
||||
ret.reserve(s.length());
|
||||
for (size_t i = 0; i < s.length(); i++)
|
||||
else
|
||||
{
|
||||
char c = s[i];
|
||||
if (c == '\\' && i < s.length() - 1)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* ConfigSection */
|
||||
|
||||
bool ConfigSection::has_entry(string name)
|
||||
{
|
||||
return (this->entries.count(name) == 1);
|
||||
};
|
||||
|
||||
ConfigEntry* ConfigSection::get_entry(string name)
|
||||
{
|
||||
if(this->has_entry(name))
|
||||
{
|
||||
return &this->entries[name];
|
||||
}
|
||||
return NULL;
|
||||
};
|
||||
|
||||
void ConfigSection::set(string name, string value)
|
||||
{
|
||||
ConfigEntry new_entry = { value };
|
||||
this->entries[name] = new_entry;
|
||||
};
|
||||
|
||||
/* ConfigFile */
|
||||
|
||||
ConfigSection* ConfigFile::add_section(string name, bool is_virtual)
|
||||
{
|
||||
ConfigSection new_section;
|
||||
if (is_virtual)
|
||||
{
|
||||
this->virtual_sections.insert(std::make_pair(name, new_section));
|
||||
return &this->virtual_sections[name];
|
||||
}
|
||||
this->sections.insert(std::make_pair(name, new_section));
|
||||
return &this->sections[name];
|
||||
};
|
||||
|
||||
bool ConfigFile::has_section(string name)
|
||||
{
|
||||
return (this->virtual_sections.count(name) == 1 || this->sections.count(name) == 1);
|
||||
}
|
||||
|
||||
bool ConfigFile::has_entry(string section_name, string entry_name)
|
||||
{
|
||||
ConfigSection* section = this->get_section(section_name, true);
|
||||
if ((section != NULL) && section->has_entry(entry_name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
section = this->get_section(section_name, false);
|
||||
return ((section != NULL) && section->has_entry(entry_name));
|
||||
}
|
||||
|
||||
ConfigSection* ConfigFile::get_section(string name, bool is_virtual)
|
||||
{
|
||||
if(is_virtual)
|
||||
{
|
||||
if (this->virtual_sections.count(name) == 1)
|
||||
{
|
||||
switch (s[++i])
|
||||
{
|
||||
case 'n':
|
||||
ret += '\n';
|
||||
break;
|
||||
case 't':
|
||||
ret += '\t';
|
||||
break;
|
||||
case 'r':
|
||||
ret += '\r';
|
||||
break;
|
||||
case 'f':
|
||||
ret += '\f';
|
||||
break;
|
||||
case '\\':
|
||||
ret += '\\';
|
||||
break;
|
||||
default:
|
||||
WARN_LOG(COMMON, "Unrecognized escape sequence [\\%c] in [%s]", s[i], s.c_str());
|
||||
ret += '\\';
|
||||
--i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ret += c;
|
||||
return &this->virtual_sections[name];
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
else
|
||||
{
|
||||
if (this->sections.count(name) == 1)
|
||||
{
|
||||
return &this->sections[name];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
};
|
||||
|
||||
ConfigEntry* ConfigFile::get_entry(string section_name, string entry_name)
|
||||
{
|
||||
ConfigSection* section = this->get_section(section_name, true);
|
||||
if(section != NULL)
|
||||
{
|
||||
return section->get_entry(entry_name);
|
||||
}
|
||||
|
||||
section = this->get_section(section_name, false);
|
||||
if(section != NULL)
|
||||
{
|
||||
return section->get_entry(entry_name);
|
||||
}
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
void IniFile::load(const std::string& data, bool cEscape)
|
||||
string ConfigFile::get(string section_name, string entry_name, string default_value)
|
||||
{
|
||||
std::istringstream fss(data);
|
||||
int cline = 1;
|
||||
std::string curSection;
|
||||
for (std::string line; std::getline(fss, line); cline++)
|
||||
ConfigEntry* entry = this->get_entry(section_name, entry_name);
|
||||
if (entry == NULL)
|
||||
{
|
||||
size_t pos = 0;
|
||||
while (pos < line.length() && std::isspace((int8_t)line[pos]))
|
||||
pos++;
|
||||
if (pos == line.length() || line[pos] == ';')
|
||||
// Ignore empty lines and comments
|
||||
return default_value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return entry->get_string();
|
||||
}
|
||||
}
|
||||
|
||||
int ConfigFile::get_int(string section_name, string entry_name, int default_value)
|
||||
{
|
||||
ConfigEntry* entry = this->get_entry(section_name, entry_name);
|
||||
if (entry == NULL)
|
||||
{
|
||||
return default_value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return entry->get_int();
|
||||
}
|
||||
}
|
||||
|
||||
bool ConfigFile::get_bool(string section_name, string entry_name, bool default_value)
|
||||
{
|
||||
ConfigEntry* entry = this->get_entry(section_name, entry_name);
|
||||
if (entry == NULL)
|
||||
{
|
||||
return default_value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return entry->get_bool();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigFile::set(string section_name, string entry_name, string value, bool is_virtual)
|
||||
{
|
||||
ConfigSection* section = this->get_section(section_name, is_virtual);
|
||||
if(section == NULL)
|
||||
{
|
||||
section = this->add_section(section_name, is_virtual);
|
||||
}
|
||||
section->set(entry_name, value);
|
||||
};
|
||||
|
||||
void ConfigFile::set_int(string section_name, string entry_name, int value, bool is_virtual)
|
||||
{
|
||||
std::stringstream str_value;
|
||||
str_value << value;
|
||||
this->set(section_name, entry_name, str_value.str(), is_virtual);
|
||||
}
|
||||
|
||||
void ConfigFile::set_bool(string section_name, string entry_name, bool value, bool is_virtual)
|
||||
{
|
||||
string str_value = (value ? "yes" : "no");
|
||||
this->set(section_name, entry_name, str_value, is_virtual);
|
||||
}
|
||||
|
||||
void ConfigFile::parse(FILE* file)
|
||||
{
|
||||
if(file == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
char line[512];
|
||||
char current_section[512] = { '\0' };
|
||||
int cline = 0;
|
||||
while(file && !feof(file))
|
||||
{
|
||||
if (fgets(line, 512, file) == NULL || feof(file))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
cline++;
|
||||
|
||||
if (strlen(line) < 3)
|
||||
{
|
||||
continue;
|
||||
if (line[pos] == '[')
|
||||
}
|
||||
|
||||
if (line[strlen(line)-1] == '\r' ||
|
||||
line[strlen(line)-1] == '\n')
|
||||
{
|
||||
// Section
|
||||
size_t end = line.find(']', ++pos);
|
||||
if (end == line.npos) {
|
||||
WARN_LOG(COMMON, "Missing ']' character - ignoring line %d: %s", cline, line.c_str());
|
||||
continue;
|
||||
}
|
||||
curSection = line.substr(pos, end - pos);
|
||||
if (cEscape)
|
||||
curSection = handleEscapeSeq(curSection);
|
||||
line[strlen(line)-1] = '\0';
|
||||
}
|
||||
|
||||
char* tl = trim_ws(line);
|
||||
|
||||
if (tl[0] == '[' && tl[strlen(tl)-1] == ']')
|
||||
{
|
||||
tl[strlen(tl)-1] = '\0';
|
||||
strcpy(current_section, tl+1);
|
||||
trim_ws(current_section);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Entry
|
||||
pos = line.find('=', pos);
|
||||
if (pos == line.npos) {
|
||||
WARN_LOG(COMMON, "Malformed entry - ignoring line %d: %s", cline, line.c_str());
|
||||
continue;
|
||||
}
|
||||
std::string entry = trim_ws(line.substr(0, pos), " \t");
|
||||
if (entry.empty()) {
|
||||
WARN_LOG(COMMON, "Invalid empty entry name - ignoring line %d: %s", cline, line.c_str());
|
||||
continue;
|
||||
}
|
||||
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.empty() && value[0] == ' ')
|
||||
value = value.substr(1);
|
||||
if (!value.empty() && value[0] == '"')
|
||||
if (strlen(current_section) == 0)
|
||||
{
|
||||
// quoted value
|
||||
size_t end = value.rfind('"');
|
||||
if (end == value.npos || end == 0)
|
||||
end = value.length();
|
||||
value = value.substr(1, end - 1);
|
||||
continue; //no open section
|
||||
}
|
||||
if (cEscape) {
|
||||
entry = handleEscapeSeq(entry);
|
||||
value = handleEscapeSeq(value);
|
||||
}
|
||||
set(curSection, entry, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IniFile::load(hostfs::File *file, bool cEscape)
|
||||
{
|
||||
std::string data;
|
||||
for (;;)
|
||||
{
|
||||
char buffer[4096];
|
||||
int n = file->read(buffer, 1, sizeof(buffer));
|
||||
if (n <= 0)
|
||||
break;
|
||||
data += std::string(buffer, buffer + n);
|
||||
}
|
||||
load(data, cEscape);
|
||||
}
|
||||
char* separator = strstr(tl, "=");
|
||||
|
||||
void IniFile::save(std::string& data) const
|
||||
{
|
||||
std::ostringstream ss;
|
||||
for (const auto& [sectionName, section] : sections)
|
||||
{
|
||||
bool sectionSaved = sectionName.empty();
|
||||
for (const auto& [name, entry] : section.entries)
|
||||
{
|
||||
if (entry.transient && entry.value.empty())
|
||||
// Don't save transient-only values
|
||||
if (!separator)
|
||||
{
|
||||
printf("Malformed entry on config - ignoring @ %d(%s)\n",cline, tl);
|
||||
continue;
|
||||
if (!sectionSaved) {
|
||||
ss << '[' << sectionName << ']' << std::endl;
|
||||
sectionSaved = true;
|
||||
}
|
||||
ss << name << " = " << entry.value << std::endl;
|
||||
|
||||
*separator = '\0';
|
||||
|
||||
char* name = trim_ws(tl);
|
||||
char* value = trim_ws(separator + 1);
|
||||
if (name == NULL || value == NULL)
|
||||
{
|
||||
printf("Malformed entry on config - ignoring @ %d(%s)\n",cline, tl);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->set(string(current_section), string(name), string(value));
|
||||
}
|
||||
}
|
||||
ss << std::endl;
|
||||
}
|
||||
data = ss.str();
|
||||
}
|
||||
|
||||
void IniFile::save(hostfs::File *file) const
|
||||
void ConfigFile::save(FILE* file)
|
||||
{
|
||||
std::string data;
|
||||
save(data);
|
||||
file->write(data.c_str(), 1, data.length());
|
||||
}
|
||||
|
||||
bool IniFile::hasSection(const std::string& section) const {
|
||||
return sections.count(section) != 0;
|
||||
}
|
||||
bool IniFile::hasEntry(const std::string& section, const std::string& name) const {
|
||||
return getEntry(section, name) != nullptr;
|
||||
}
|
||||
bool IniFile::isTransient(const std::string& section, const std::string& name) const
|
||||
{
|
||||
const Entry *entry = getEntry(section, name);
|
||||
if (entry == nullptr)
|
||||
return false;
|
||||
else
|
||||
return entry->transient;
|
||||
}
|
||||
|
||||
void IniFile::deleteSection(const std::string& section) {
|
||||
sections.erase(section);
|
||||
}
|
||||
void IniFile::deleteEntry(const std::string& sectionName, const std::string& entry)
|
||||
{
|
||||
auto it = sections.find(sectionName);
|
||||
if (it == sections.end())
|
||||
return;
|
||||
Section& section = it->second;
|
||||
section.entries.erase(entry);
|
||||
}
|
||||
|
||||
std::vector<std::string> IniFile::getEntryNames(const std::string& sectionName) const
|
||||
{
|
||||
std::vector<std::string> v;
|
||||
auto it = sections.find(sectionName);
|
||||
if (it != sections.end())
|
||||
for(std::map<string, ConfigSection>::iterator section_it = this->sections.begin();
|
||||
section_it != this->sections.end(); section_it++)
|
||||
{
|
||||
const Section& section = it->second;
|
||||
for (const auto& [name, entry] : section.entries)
|
||||
v.push_back(name);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
string section_name = section_it->first;
|
||||
ConfigSection section = section_it->second;
|
||||
|
||||
} // namespace config
|
||||
fprintf(file, "[%s]\n", section_name.c_str());
|
||||
|
||||
for(std::map<string, ConfigEntry>::iterator entry_it = section.entries.begin();
|
||||
entry_it != section.entries.end(); entry_it++)
|
||||
{
|
||||
string entry_name = entry_it->first;
|
||||
ConfigEntry entry = entry_it->second;
|
||||
fprintf(file, "%s = %s\n", entry_name.c_str(), entry.get_string().c_str());
|
||||
}
|
||||
|
||||
fputs("\n", file);
|
||||
}
|
||||
}
|
||||
+37
-99
@@ -1,105 +1,43 @@
|
||||
/*
|
||||
Copyright 2025 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "oslib/storage.h"
|
||||
#include <cstdio>
|
||||
#include "types.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <locale>
|
||||
#include <sstream>
|
||||
|
||||
namespace config {
|
||||
|
||||
class IniFile
|
||||
{
|
||||
public:
|
||||
void load(hostfs::File *file, bool cEscape = false);
|
||||
void load(const std::string& data, bool cEscape = false);
|
||||
void save(hostfs::File *file) const;
|
||||
void save(std::string& data) const;
|
||||
bool hasSection(const std::string& section) const;
|
||||
bool hasEntry(const std::string& section, const std::string& name) const;
|
||||
bool isTransient(const std::string& section, const std::string& name) const;
|
||||
void deleteSection(const std::string& section);
|
||||
void deleteEntry(const std::string& section, const std::string& name);
|
||||
|
||||
template<typename T>
|
||||
void set(const std::string& section, const std::string& entry, T value, bool transient = false) {
|
||||
setRaw(section, entry, std::to_string(value), transient);
|
||||
}
|
||||
|
||||
const std::string& getString(const std::string& section, const std::string& entry, const std::string& defaultValue = {}) const;
|
||||
const std::string& get(const std::string& section, const std::string& entry, const std::string& defaultValue = {}) const {
|
||||
return getString(section, entry, defaultValue);
|
||||
}
|
||||
bool getBool(const std::string& section, const std::string& entry, bool defaultValue = false) const;
|
||||
int getInt(const std::string& section, const std::string& entry, int defaultValue = 0) const;
|
||||
int64_t getInt64(const std::string& section, const std::string& entry, int64_t defaultValue = 0ll) const;
|
||||
float getFloat(const std::string& section, const std::string& entry, float defaultValue = 0.f) const;
|
||||
|
||||
std::vector<std::string> getEntryNames(const std::string& section) const;
|
||||
|
||||
private:
|
||||
struct Entry
|
||||
{
|
||||
std::string name;
|
||||
std::string value;
|
||||
std::string transientValue;
|
||||
bool transient = false;
|
||||
};
|
||||
struct Section
|
||||
{
|
||||
std::string name;
|
||||
std::map<std::string, Entry> entries;
|
||||
};
|
||||
const Entry *getEntry(const std::string& section, const std::string& entry) const;
|
||||
const std::string *getRaw(const std::string& section, const std::string& name) const;
|
||||
void setRaw(const std::string& section, const std::string& name, const std::string& value, bool transient);
|
||||
|
||||
std::map<std::string, Section> sections;
|
||||
struct ConfigEntry {
|
||||
string value;
|
||||
string get_string();
|
||||
int get_int();
|
||||
bool get_bool();
|
||||
};
|
||||
|
||||
template<>
|
||||
inline void IniFile::set(const std::string& section, const std::string& entry, const std::string& value, bool transient) {
|
||||
setRaw(section, entry, value, transient);
|
||||
}
|
||||
template<>
|
||||
inline void IniFile::set(const std::string& section, const std::string& entry, std::string value, bool transient) {
|
||||
setRaw(section, entry, value, transient);
|
||||
}
|
||||
template<>
|
||||
inline void IniFile::set(const std::string& section, const std::string& entry, const char *value, bool transient) {
|
||||
setRaw(section, entry, value, transient);
|
||||
}
|
||||
template<>
|
||||
inline void IniFile::set(const std::string& section, const std::string& entry, bool value, bool transient) {
|
||||
setRaw(section, entry, value ? "yes" : "no", transient);
|
||||
}
|
||||
template<>
|
||||
inline void IniFile::set(const std::string& section, const std::string& entry, float value, bool transient)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss.imbue(std::locale::classic());
|
||||
ss.precision(7);
|
||||
ss << value;
|
||||
setRaw(section, entry, ss.str(), transient);
|
||||
}
|
||||
struct ConfigSection {
|
||||
std::map<string, ConfigEntry> entries;
|
||||
bool has_entry(string name);
|
||||
void set(string name, string value);
|
||||
ConfigEntry* get_entry(string name);
|
||||
};
|
||||
|
||||
} // namespace config
|
||||
struct ConfigFile {
|
||||
private:
|
||||
std::map<string, ConfigSection> sections;
|
||||
std::map<string, ConfigSection> virtual_sections;
|
||||
ConfigSection* add_section(string name, bool is_virtual);
|
||||
ConfigSection* get_section(string name, bool is_virtual);
|
||||
ConfigEntry* get_entry(string section_name, string entry_name);
|
||||
|
||||
|
||||
public:
|
||||
bool has_section(string name);
|
||||
bool has_entry(string section_name, string entry_name);
|
||||
|
||||
void parse(FILE* fd);
|
||||
void save(FILE* fd);
|
||||
|
||||
/* getting values */
|
||||
string get(string section_name, string entry_name, string default_value = "");
|
||||
int get_int(string section_name, string entry_name, int default_value = 0);
|
||||
bool get_bool(string section_name, string entry_name, bool default_value = false);
|
||||
/* setting values */
|
||||
void set(string section_name, string entry_name, string value, bool is_virtual = false);
|
||||
void set_int(string section_name, string entry_name, int value, bool is_virtual = false);
|
||||
void set_bool(string section_name, string entry_name, bool value, bool is_virtual = false);
|
||||
};
|
||||
@@ -1,268 +0,0 @@
|
||||
/*
|
||||
Copyright 2021 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "option.h"
|
||||
#include "network/net_handshake.h"
|
||||
#include "debug/gdb_server.h"
|
||||
|
||||
namespace config {
|
||||
|
||||
// Dynarec
|
||||
|
||||
Option<bool> DynarecEnabled("Dynarec.Enabled", true);
|
||||
Option<int> Sh4Clock("Sh4Clock", 200);
|
||||
|
||||
// General
|
||||
|
||||
Option<int> Cable("Dreamcast.Cable", 3); // TV Composite
|
||||
Option<int> Region("Dreamcast.Region", 1); // USA
|
||||
Option<int> Broadcast("Dreamcast.Broadcast", 0); // NTSC
|
||||
Option<int> Language("Dreamcast.Language", 1); // English
|
||||
OptionString UILanguage("UILanguage", "");
|
||||
Option<bool> AutoLoadState("Dreamcast.AutoLoadState");
|
||||
Option<bool> AutoSaveState("Dreamcast.AutoSaveState");
|
||||
Option<int, false> SavestateSlot("Dreamcast.SavestateSlot");
|
||||
Option<bool> ForceFreePlay("ForceFreePlay", true);
|
||||
Option<bool, false> FetchBoxart("FetchBoxart", true);
|
||||
Option<bool, false> BoxartDisplayMode("BoxartDisplayMode", true);
|
||||
Option<int, false> UIScaling("UIScaling", 100);
|
||||
Option<int, false> UITheme("UITheme", 0);
|
||||
|
||||
// Sound
|
||||
|
||||
Option<bool> DSPEnabled("aica.DSPEnabled", false);
|
||||
#if HOST_CPU == CPU_ARM
|
||||
Option<int> AudioBufferSize("aica.BufferSize", 5644); // 128 ms
|
||||
#else
|
||||
Option<int> AudioBufferSize("aica.BufferSize", 2822); // 64 ms
|
||||
#endif
|
||||
Option<bool> AutoLatency("aica.AutoLatency",
|
||||
#ifdef __ANDROID__
|
||||
true
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
);
|
||||
|
||||
OptionString AudioBackend("backend", "auto", "audio");
|
||||
AudioVolumeOption AudioVolume;
|
||||
Option<bool> VmuSound("VmuSound", false, "audio");
|
||||
|
||||
// Rendering
|
||||
|
||||
RendererOption RendererType;
|
||||
Option<bool> UseMipmaps("rend.UseMipmaps", true);
|
||||
Option<bool> Widescreen("rend.WideScreen");
|
||||
Option<bool> SuperWidescreen("rend.SuperWideScreen");
|
||||
Option<bool> ShowFPS("rend.ShowFPS");
|
||||
Option<bool> RenderToTextureBuffer("rend.RenderToTextureBuffer");
|
||||
Option<bool> TranslucentPolygonDepthMask("rend.TranslucentPolygonDepthMask");
|
||||
Option<bool> ModifierVolumes("rend.ModifierVolumes", true);
|
||||
Option<int> TextureUpscale("rend.TextureUpscale2", 1);
|
||||
Option<int> MaxFilteredTextureSize("rend.MaxFilteredTextureSize", 256);
|
||||
Option<float> ExtraDepthScale("rend.ExtraDepthScale", 1.f);
|
||||
Option<bool> CustomTextures("rend.CustomTextures");
|
||||
Option<int> PreloadCustomTextures("rend.PreloadCustomTextures", 0);
|
||||
Option<bool> DumpTextures("rend.DumpTextures");
|
||||
Option<bool> DumpUniqueTextures("rend.DumpUniqueTextures");
|
||||
Option<bool> DumpReplacedTextures("rend.DumpReplacedTextures");
|
||||
Option<int> ScreenStretching("rend.ScreenStretching", 100);
|
||||
Option<bool> StretchToFill("rend.StretchToFill");
|
||||
Option<bool> Fog("rend.Fog", true);
|
||||
Option<bool> FloatVMUs("rend.FloatVMUs");
|
||||
Option<bool> Rotate90("rend.Rotate90");
|
||||
Option<bool> PerStripSorting("rend.PerStripSorting");
|
||||
#ifdef __APPLE__
|
||||
Option<bool> DelayFrameSwapping("rend.DelayFrameSwapping", false);
|
||||
#else
|
||||
Option<bool> DelayFrameSwapping("rend.DelayFrameSwapping", true);
|
||||
#endif
|
||||
Option<bool> WidescreenGameHacks("rend.WidescreenGameHacks");
|
||||
std::array<Option<int>, 4> CrosshairColor {
|
||||
Option<int>("rend.CrossHairColor1"),
|
||||
Option<int>("rend.CrossHairColor2"),
|
||||
Option<int>("rend.CrossHairColor3"),
|
||||
Option<int>("rend.CrossHairColor4"),
|
||||
};
|
||||
Option<int> CrosshairSize("rend.CrosshairSize", 40);
|
||||
Option<int> SkipFrame("ta.skip");
|
||||
Option<int> MaxThreads("pvr.MaxThreads", 3);
|
||||
Option<int> AutoSkipFrame("pvr.AutoSkipFrame", 0);
|
||||
Option<int> RenderResolution("rend.Resolution", 480);
|
||||
Option<bool> IntegerScale("rend.IntegerScale", false);
|
||||
Option<bool> LinearInterpolation("rend.LinearInterpolation", true);
|
||||
Option<bool> VSync("rend.vsync", true);
|
||||
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> DupeFrames("rend.DupeFrames", false);
|
||||
Option<int> PerPixelLayers("rend.PerPixelLayers", 32);
|
||||
#ifdef TARGET_UWP
|
||||
Option<bool> NativeDepthInterpolation("rend.NativeDepthInterpolation", true);
|
||||
#else
|
||||
Option<bool> NativeDepthInterpolation("rend.NativeDepthInterpolation", false);
|
||||
#endif
|
||||
Option<bool> EmulateFramebuffer("rend.EmulateFramebuffer", false);
|
||||
Option<bool> FixUpscaleBleedingEdge("rend.FixUpscaleBleedingEdge", true);
|
||||
Option<bool> CustomGpuDriver("rend.CustomGpuDriver", false);
|
||||
Option<bool> FramePacing("rend.FramePacing", true);
|
||||
|
||||
CustomTexturePreloadMode customTexturePreloadMode()
|
||||
{
|
||||
const auto mode = static_cast<CustomTexturePreloadMode>(PreloadCustomTextures.get());
|
||||
switch (mode)
|
||||
{
|
||||
case CustomTexturePreloadMode::Off:
|
||||
case CustomTexturePreloadMode::SystemMemory:
|
||||
case CustomTexturePreloadMode::VideoMemory:
|
||||
return mode;
|
||||
default:
|
||||
return CustomTexturePreloadMode::Off;
|
||||
}
|
||||
}
|
||||
#ifdef VIDEO_ROUTING
|
||||
Option<bool, false> VideoRouting("rend.VideoRouting", false);
|
||||
Option<bool, false> VideoRoutingScale("rend.VideoRoutingScale", false);
|
||||
Option<int, false> VideoRoutingVRes("rend.VideoRoutingVRes", 720);
|
||||
#endif
|
||||
|
||||
// Misc
|
||||
|
||||
Option<bool> SerialConsole("Debug.SerialConsoleEnabled");
|
||||
Option<bool> SerialPTY("Debug.SerialPTY");
|
||||
Option<bool> GDB("Debug.GDBEnabled");
|
||||
Option<int> GDBPort("Debug.GDBPort", debugger::DEFAULT_PORT);
|
||||
Option<bool> GDBWaitForConnection("Debug.GDBWaitForConnection");
|
||||
Option<bool> UseReios("UseReios");
|
||||
Option<bool> FastGDRomLoad("FastGDRomLoad", false);
|
||||
Option<bool> RamMod32MB("Dreamcast.RamMod32MB", false);
|
||||
|
||||
Option<bool> OpenGlChecks("OpenGlChecks", false, "validate");
|
||||
|
||||
Option<std::vector<std::string>, false> ContentPath("Dreamcast.ContentPath");
|
||||
Option<std::vector<std::string>, false> BiosPath("Dreamcast.BiosPath");
|
||||
Option<std::string, false> VMUPath("Dreamcast.VMUPath");
|
||||
Option<std::vector<std::string>, false> SavestatePath("Dreamcast.SavestatePath");
|
||||
Option<std::string, false> SavePath("Dreamcast.SavePath");
|
||||
Option<std::vector<std::string>, false> TexturePath("Dreamcast.TexturePath");
|
||||
Option<std::string, false> TextureDumpPath("Dreamcast.TextureDumpPath");
|
||||
Option<std::string, false> BoxartPath("Dreamcast.BoxartPath");
|
||||
Option<std::vector<std::string>, false> MappingsPath("Dreamcast.MappingsPath");
|
||||
Option<std::vector<std::string>, false> CheatPath("Dreamcast.CheatPath");
|
||||
Option<bool, false> HideLegacyNaomiRoms("Dreamcast.HideLegacyNaomiRoms", true);
|
||||
Option<bool, false> UploadCrashLogs("UploadCrashLogs", true);
|
||||
Option<bool, false> DiscordPresence("DiscordPresence", true);
|
||||
#if defined(__ANDROID__) && !defined(LIBRETRO)
|
||||
Option<bool, false> UseSafFilePicker("UseSafFilePicker", true);
|
||||
#endif
|
||||
OptionString LogServer("LogServer", "", "log");
|
||||
|
||||
// Profiler
|
||||
Option<bool> ProfilerEnabled("Profiler.Enabled");
|
||||
Option<bool> ProfilerDrawToGUI("Profiler.DrawGUI");
|
||||
Option<bool> ProfilerOutputTTY("Profiler.OutputTTY");
|
||||
Option<float> ProfilerFrameWarningTime("Profiler.FrameWarningTime", 1.0f / 55.0f);
|
||||
|
||||
// Network
|
||||
|
||||
Option<bool> NetworkEnable("Enable", false, "network");
|
||||
Option<bool> ActAsServer("ActAsServer", false, "network");
|
||||
Option<bool> NaomiSatellite("NaomiSatellite", false, "network");
|
||||
OptionString DNS("DNS", "dns.flyca.st", "network");
|
||||
OptionString NetworkServer("server", "", "network");
|
||||
Option<int> LocalPort("LocalPort", defaultNaomiServerPort(), "network");
|
||||
Option<bool> EmulateBBA("EmulateBBA", false, "network");
|
||||
Option<bool> EnableUPnP("EnableUPnP", true, "network");
|
||||
Option<bool> GGPOEnable("GGPO", false, "network");
|
||||
Option<int> GGPODelay("GGPODelay", 0, "network");
|
||||
Option<bool> NetworkStats("Stats", true, "network");
|
||||
Option<int> GGPOAnalogAxes("GGPOAnalogAxes", 0, "network");
|
||||
Option<bool> GGPOChat("GGPOChat", true, "network");
|
||||
Option<bool> GGPOChatTimeoutToggle("GGPOChatTimeoutToggle", true, "network");
|
||||
Option<int> GGPOChatTimeout("GGPOChatTimeout", 10, "network");
|
||||
Option<bool> NetworkOutput("NetworkOutput", false, "network");
|
||||
Option<int> MultiboardSlaves("MultiboardSlaves", 1, "network");
|
||||
Option<bool> BattleCableEnable("BattleCable", false, "network");
|
||||
Option<bool> UseDCNet("DCNet", true, "network");
|
||||
OptionString ISPUsername("ISPUsername", "flycast1", "network");
|
||||
|
||||
#ifdef USE_OMX
|
||||
Option<int> OmxAudioLatency("audio_latency", 100, "omx");
|
||||
Option<bool> OmxAudioHdmi("audio_hdmi", true, "omx");
|
||||
#endif
|
||||
|
||||
// Maple
|
||||
|
||||
Option<int> MouseSensitivity("MouseSensitivity", 100, "input");
|
||||
Option<int> VirtualGamepadVibration("VirtualGamepadVibration", 20, "input");
|
||||
Option<int> VirtualGamepadTransparency("VirtualGamepadTransparency", 37, "input");
|
||||
|
||||
std::array<Option<MapleDeviceType>, 4> MapleMainDevices {
|
||||
Option<MapleDeviceType>("device1", MDT_SegaController, "input"),
|
||||
Option<MapleDeviceType>("device2", MDT_None, "input"),
|
||||
Option<MapleDeviceType>("device3", MDT_None, "input"),
|
||||
Option<MapleDeviceType>("device4", MDT_None, "input"),
|
||||
};
|
||||
std::array<std::array<Option<MapleDeviceType>, 2>, 4> MapleExpansionDevices {{
|
||||
{{Option<MapleDeviceType>("device1.1", MDT_SegaVMU, "input"),
|
||||
Option<MapleDeviceType>("device1.2", MDT_SegaVMU, "input")}},
|
||||
|
||||
{{Option<MapleDeviceType>("device2.1", MDT_None, "input"),
|
||||
Option<MapleDeviceType>("device2.2", MDT_None, "input")}},
|
||||
|
||||
{{Option<MapleDeviceType>("device3.1", MDT_None, "input"),
|
||||
Option<MapleDeviceType>("device3.2", MDT_None, "input")}},
|
||||
|
||||
{{Option<MapleDeviceType>("device4.1", MDT_None, "input"),
|
||||
Option<MapleDeviceType>("device4.2", MDT_None, "input")}},
|
||||
}};
|
||||
|
||||
std::array<std::array<Option<int>, 2>, 4> NetworkExpansionDevices{{
|
||||
{{Option<int>("device1.1.net", 0, "input"),
|
||||
Option<int>("device1.2.net", 0, "input")}},
|
||||
|
||||
{{Option<int>("device2.1.net", 0, "input"),
|
||||
Option<int>("device2.2.net", 0, "input")}},
|
||||
|
||||
{{Option<int>("device3.1.net", 0, "input"),
|
||||
Option<int>("device3.2.net", 0, "input")}},
|
||||
|
||||
{{Option<int>("device4.1.net", 0, "input"),
|
||||
Option<int>("device4.2.net", 0, "input")}},
|
||||
}};
|
||||
Option<bool> PerGameVmu("PerGameVmu", true, "config");
|
||||
#ifdef _WIN32
|
||||
Option<bool, false> UseRawInput("RawInput", false, "input");
|
||||
#endif
|
||||
Option<bool> UsePhysicalVmuMemory("UsePhysicalVmuMemory", false);
|
||||
|
||||
#ifdef USE_LUA
|
||||
Option<std::string, false> LuaFileName("LuaFileName", "flycast.lua");
|
||||
#endif
|
||||
|
||||
// RetroAchievements
|
||||
|
||||
Option<bool> EnableAchievements("Enabled", false, "achievements");
|
||||
Option<bool> ShowAchievementPopups("Show", true, "achievements");
|
||||
Option<bool> AchievementsHardcoreMode("HardcoreMode", false, "achievements");
|
||||
OptionString AchievementsUserName("UserName", "", "achievements");
|
||||
OptionString AchievementsToken("Token", "", "achievements");
|
||||
OptionString AchievementsHostUrl("HostUrl", "", "achievements");
|
||||
|
||||
} // namespace config
|
||||
@@ -1,588 +0,0 @@
|
||||
/*
|
||||
Copyright 2021 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "cfg.h"
|
||||
#include "hw/maple/maple_cfg.h"
|
||||
#ifdef LIBRETRO
|
||||
#include <libretro.h>
|
||||
#endif
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace config {
|
||||
|
||||
class BaseOption {
|
||||
public:
|
||||
virtual ~BaseOption() = default;
|
||||
virtual void save() const = 0;
|
||||
virtual void load() = 0;
|
||||
virtual void reset() = 0;
|
||||
};
|
||||
|
||||
#ifdef LIBRETRO
|
||||
#include "option_lr.h"
|
||||
#else
|
||||
|
||||
class Settings {
|
||||
public:
|
||||
void reset() {
|
||||
for (const auto& o : options)
|
||||
o->reset();
|
||||
gameId.clear();
|
||||
perGameConfig = false;
|
||||
}
|
||||
|
||||
void load(bool gameSpecific) {
|
||||
if (gameSpecific)
|
||||
{
|
||||
if (gameId.empty())
|
||||
return;
|
||||
if (!hasSection(gameId))
|
||||
return;
|
||||
perGameConfig = true;
|
||||
}
|
||||
for (const auto& o : options)
|
||||
o->load();
|
||||
}
|
||||
|
||||
void save() {
|
||||
setAutoSave(false);
|
||||
for (const auto& o : options)
|
||||
o->save();
|
||||
setAutoSave(true);
|
||||
}
|
||||
|
||||
void setGameId(const std::string& gameId) {
|
||||
this->gameId = gameId;
|
||||
}
|
||||
|
||||
bool hasPerGameConfig() const {
|
||||
return perGameConfig;
|
||||
}
|
||||
void setPerGameConfig(bool perGameConfig) {
|
||||
this->perGameConfig = perGameConfig;
|
||||
if (!perGameConfig) {
|
||||
if (!gameId.empty())
|
||||
deleteSection(gameId);
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
static Settings& instance() {
|
||||
static Settings *_instance = new Settings();
|
||||
return *_instance;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<BaseOption *> options;
|
||||
std::string gameId;
|
||||
bool perGameConfig = false;
|
||||
|
||||
template<typename T, bool PerGameOption>
|
||||
friend class Option;
|
||||
};
|
||||
|
||||
template<typename T, bool PerGameOption = true>
|
||||
class Option : public BaseOption {
|
||||
public:
|
||||
Option(const std::string& name, T defaultValue = T(), const std::string& section = "config")
|
||||
: section(section), name(name), value(defaultValue), defaultValue(defaultValue),
|
||||
settings(Settings::instance())
|
||||
{
|
||||
settings.options.push_back(this);
|
||||
}
|
||||
|
||||
void reset() override {
|
||||
set(defaultValue);
|
||||
overridden = false;
|
||||
}
|
||||
|
||||
void load() override {
|
||||
if (PerGameOption && settings.hasPerGameConfig())
|
||||
set(doLoad(settings.gameId, section + "." + name));
|
||||
else
|
||||
{
|
||||
set(doLoad(section, name));
|
||||
if (isTransient(section, name))
|
||||
override(value);
|
||||
}
|
||||
}
|
||||
|
||||
void save() const override
|
||||
{
|
||||
if (overridden) {
|
||||
if (value == overriddenDefault)
|
||||
return;
|
||||
if (!settings.hasPerGameConfig())
|
||||
// overridden options can only be saved in per-game settings
|
||||
return;
|
||||
}
|
||||
else if (PerGameOption && settings.hasPerGameConfig())
|
||||
{
|
||||
if (value == doLoad(section, name))
|
||||
{
|
||||
// delete existing per-game option if any
|
||||
deleteEntry(settings.gameId, section + "." + name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (PerGameOption && settings.hasPerGameConfig())
|
||||
doSave(settings.gameId, section + "." + name);
|
||||
else
|
||||
doSave(section, name);
|
||||
}
|
||||
|
||||
T& get() { return value; }
|
||||
void set(T v) { value = v; }
|
||||
|
||||
void override(T v)
|
||||
{
|
||||
overriddenDefault = v;
|
||||
overridden = true;
|
||||
value = v;
|
||||
}
|
||||
bool isReadOnly() const {
|
||||
return overridden && !settings.hasPerGameConfig();
|
||||
}
|
||||
|
||||
explicit operator T() const { return value; }
|
||||
operator T&() { return value; }
|
||||
T& operator=(const T& v) { set(v); return value; }
|
||||
|
||||
protected:
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same_v<U, bool>, T>
|
||||
doLoad(const std::string& section, const std::string& name) const
|
||||
{
|
||||
return loadBool(section, name, value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same<U, int64_t>::value, T>
|
||||
doLoad(const std::string& section, const std::string& name) const
|
||||
{
|
||||
return loadInt64(section, name, value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<(std::is_integral_v<U> || std::is_enum_v<U>)
|
||||
&& !std::is_same_v<U, bool> && !std::is_same_v<U, int64_t>, T>
|
||||
doLoad(const std::string& section, const std::string& name) const
|
||||
{
|
||||
return (T)loadInt(section, name, (int)value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same_v<U, std::string>, T>
|
||||
doLoad(const std::string& section, const std::string& name) const
|
||||
{
|
||||
return loadStr(section, name, value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same_v<float, U>, T>
|
||||
doLoad(const std::string& section, const std::string& name) const
|
||||
{
|
||||
return loadFloat(section, name, value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same_v<std::vector<std::string>, U>, T>
|
||||
doLoad(const std::string& section, const std::string& name) const
|
||||
{
|
||||
std::string paths = loadStr(section, name);
|
||||
if (paths.empty())
|
||||
return value;
|
||||
std::string::size_type start = 0;
|
||||
std::vector<std::string> newValue;
|
||||
while (true)
|
||||
{
|
||||
if (paths[start] == '"')
|
||||
{
|
||||
std::string v;
|
||||
start++;
|
||||
while (true)
|
||||
{
|
||||
if (paths[start] == '"')
|
||||
{
|
||||
if (start + 1 >= paths.size())
|
||||
{
|
||||
newValue.push_back(v);
|
||||
return newValue;
|
||||
}
|
||||
if (paths[start + 1] == '"')
|
||||
{
|
||||
v += paths[start++];
|
||||
start++;
|
||||
}
|
||||
else if (paths[start + 1] == ';')
|
||||
{
|
||||
newValue.push_back(v);
|
||||
start += 2;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
v += paths[start++];
|
||||
}
|
||||
}
|
||||
else
|
||||
v += paths[start++];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string::size_type end = paths.find(';', start);
|
||||
if (end == std::string::npos)
|
||||
end = paths.size();
|
||||
if (start != end)
|
||||
newValue.push_back(paths.substr(start, end - start));
|
||||
if (end == paths.size())
|
||||
break;
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same_v<U, bool>>
|
||||
doSave(const std::string& section, const std::string& name) const
|
||||
{
|
||||
saveBool(section, name, value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same<U, int64_t>::value>
|
||||
doSave(const std::string& section, const std::string& name) const
|
||||
{
|
||||
saveInt64(section, name, value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<(std::is_integral_v<U> || std::is_enum_v<U>)
|
||||
&& !std::is_same_v<U, bool> && !std::is_same_v<U, int64_t>>
|
||||
doSave(const std::string& section, const std::string& name) const
|
||||
{
|
||||
saveInt(section, name, (int)value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same_v<U, std::string>>
|
||||
doSave(const std::string& section, const std::string& name) const
|
||||
{
|
||||
saveStr(section, name, value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same_v<float, U>>
|
||||
doSave(const std::string& section, const std::string& name) const
|
||||
{
|
||||
saveFloat(section, name, value);
|
||||
}
|
||||
|
||||
template <typename U = T>
|
||||
std::enable_if_t<std::is_same_v<std::vector<std::string>, U>>
|
||||
doSave(const std::string& section, const std::string& name) const
|
||||
{
|
||||
std::string s;
|
||||
for (const auto& v : value)
|
||||
{
|
||||
if (!s.empty())
|
||||
s += ';';
|
||||
if (v.find(';') != std::string::npos || (!v.empty() && v[0] == '"'))
|
||||
{
|
||||
s += '"';
|
||||
std::string v2 = v;
|
||||
while (true)
|
||||
{
|
||||
auto pos = v2.find('"');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
s += v2.substr(0, pos + 1) + '"';
|
||||
v2 = v2.substr(pos + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
s += v2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
s += '"';
|
||||
}
|
||||
else
|
||||
s += v;
|
||||
}
|
||||
saveStr(section, name, s);
|
||||
}
|
||||
|
||||
std::string section;
|
||||
std::string name;
|
||||
T value;
|
||||
T defaultValue;
|
||||
T overriddenDefault = T();
|
||||
bool overridden = false;
|
||||
Settings& settings;
|
||||
};
|
||||
#endif
|
||||
|
||||
using OptionString = Option<std::string>;
|
||||
|
||||
// Dynarec
|
||||
|
||||
extern Option<bool> DynarecEnabled;
|
||||
#ifndef LIBRETRO
|
||||
extern Option<int> Sh4Clock;
|
||||
#endif
|
||||
|
||||
// General
|
||||
|
||||
extern Option<int> Cable; // 0 -> VGA, 1 -> VGA, 2 -> RGB, 3 -> TV Composite
|
||||
extern Option<int> Region; // 0 -> JP, 1 -> USA, 2 -> EU, 3 -> default
|
||||
extern Option<int> Broadcast; // 0 -> NTSC, 1 -> PAL, 2 -> PAL/M, 3 -> PAL/N, 4 -> default
|
||||
extern Option<int> Language; // 0 -> JP, 1 -> EN, 2 -> DE, 3 -> FR, 4 -> SP, 5 -> IT, 6 -> default
|
||||
extern OptionString UILanguage;
|
||||
extern Option<bool> AutoLoadState;
|
||||
extern Option<bool> AutoSaveState;
|
||||
extern Option<int, false> SavestateSlot;
|
||||
extern Option<bool> ForceFreePlay;
|
||||
extern Option<bool, false> FetchBoxart;
|
||||
extern Option<bool, false> BoxartDisplayMode;
|
||||
extern Option<int, false> UIScaling;
|
||||
extern Option<int, false> UITheme; // 0 -> Dark, 1 -> Light, 2 -> Dreamcast, 3 -> High Contrast, 4 -> Nintendo, 5 -> Aqua Chill
|
||||
|
||||
// Sound
|
||||
|
||||
constexpr bool LimitFPS = true;
|
||||
extern Option<bool> DSPEnabled;
|
||||
extern Option<int> AudioBufferSize; //In samples ,*4 for bytes
|
||||
extern Option<bool> AutoLatency;
|
||||
|
||||
extern OptionString AudioBackend;
|
||||
|
||||
class AudioVolumeOption : public Option<int> {
|
||||
public:
|
||||
AudioVolumeOption() : Option<int>("aica.Volume", 100) {};
|
||||
float logarithmic_volume_scale = 1.0;
|
||||
|
||||
void load() override {
|
||||
Option<int>::load();
|
||||
calcDbPower();
|
||||
}
|
||||
|
||||
float dbPower()
|
||||
{
|
||||
return logarithmic_volume_scale;
|
||||
}
|
||||
void calcDbPower()
|
||||
{
|
||||
// dB scaling calculation: https://www.dr-lex.be/info-stuff/volumecontrols.html
|
||||
logarithmic_volume_scale = std::min(std::exp(4.605f * float(value) / 100.f) / 100.f, 1.f);
|
||||
if (value < 10)
|
||||
logarithmic_volume_scale *= value / 10.f;
|
||||
}
|
||||
};
|
||||
extern AudioVolumeOption AudioVolume;
|
||||
extern Option<bool> VmuSound;
|
||||
|
||||
// Rendering
|
||||
|
||||
class RendererOption : public Option<RenderType> {
|
||||
public:
|
||||
#ifdef LIBRETRO
|
||||
RendererOption() : Option<RenderType>("",
|
||||
#else
|
||||
RendererOption() : Option<RenderType>("pvr.rend",
|
||||
#endif
|
||||
#if defined(USE_DX11)
|
||||
RenderType::DirectX11
|
||||
#elif defined(USE_DX9)
|
||||
RenderType::DirectX9
|
||||
#elif !defined(USE_OPENGL)
|
||||
RenderType::Vulkan
|
||||
#else
|
||||
RenderType::OpenGL
|
||||
#endif
|
||||
) {}
|
||||
|
||||
RenderType& operator=(const RenderType& v) { set(v); return value; }
|
||||
|
||||
void reset() override {
|
||||
// don't reset the value to avoid quick switching when starting a game
|
||||
overridden = false;
|
||||
}
|
||||
};
|
||||
extern RendererOption RendererType;
|
||||
extern Option<bool> UseMipmaps;
|
||||
extern Option<bool> Widescreen;
|
||||
extern Option<bool> SuperWidescreen;
|
||||
extern Option<bool> ShowFPS;
|
||||
extern Option<bool> RenderToTextureBuffer;
|
||||
extern Option<bool> TranslucentPolygonDepthMask;
|
||||
extern Option<bool> ModifierVolumes;
|
||||
constexpr bool Clipping = true;
|
||||
#ifndef LIBRETRO
|
||||
extern Option<int> TextureUpscale;
|
||||
extern Option<int> MaxFilteredTextureSize;
|
||||
extern Option<int> PerPixelLayers;
|
||||
#endif
|
||||
extern Option<float> ExtraDepthScale;
|
||||
extern Option<bool> CustomTextures;
|
||||
enum class CustomTexturePreloadMode : int
|
||||
{
|
||||
Off = 0,
|
||||
SystemMemory = 1,
|
||||
VideoMemory = 2,
|
||||
};
|
||||
extern Option<int> PreloadCustomTextures;
|
||||
CustomTexturePreloadMode customTexturePreloadMode();
|
||||
extern Option<bool> DumpTextures;
|
||||
extern Option<bool> DumpUniqueTextures;
|
||||
extern Option<bool> DumpReplacedTextures;
|
||||
extern Option<int> ScreenStretching; // in percent. 150 means stretch from 4/3 to 6/3
|
||||
extern Option<bool> StretchToFill;
|
||||
extern Option<bool> Fog;
|
||||
extern Option<bool> FloatVMUs;
|
||||
extern Option<bool> Rotate90;
|
||||
extern Option<bool> PerStripSorting;
|
||||
extern Option<bool> DelayFrameSwapping; // Delay swapping frame until FB_R_SOF matches FB_W_SOF
|
||||
extern Option<bool> WidescreenGameHacks;
|
||||
extern std::array<Option<int>, 4> CrosshairColor;
|
||||
extern Option<int> CrosshairSize;
|
||||
extern Option<int> SkipFrame;
|
||||
extern Option<int> MaxThreads;
|
||||
extern Option<int> AutoSkipFrame; // 0: none, 1: some, 2: more
|
||||
extern Option<int> RenderResolution;
|
||||
extern Option<bool> IntegerScale;
|
||||
extern Option<bool> LinearInterpolation;
|
||||
extern Option<bool> VSync;
|
||||
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> DupeFrames;
|
||||
extern Option<bool> NativeDepthInterpolation;
|
||||
extern Option<bool> EmulateFramebuffer;
|
||||
extern Option<bool> FixUpscaleBleedingEdge;
|
||||
extern Option<bool> CustomGpuDriver;
|
||||
extern Option<bool> FramePacing;
|
||||
#ifdef VIDEO_ROUTING
|
||||
extern Option<bool, false> VideoRouting;
|
||||
extern Option<bool, false> VideoRoutingScale;
|
||||
extern Option<int, false> VideoRoutingVRes;
|
||||
#endif
|
||||
|
||||
// Misc
|
||||
|
||||
extern Option<bool> SerialConsole;
|
||||
extern Option<bool> SerialPTY;
|
||||
extern Option<bool> GDB;
|
||||
extern Option<int> GDBPort;
|
||||
extern Option<bool> GDBWaitForConnection;
|
||||
extern Option<bool> UseReios;
|
||||
extern Option<bool> FastGDRomLoad;
|
||||
extern Option<bool> RamMod32MB;
|
||||
|
||||
extern Option<bool> OpenGlChecks;
|
||||
|
||||
extern Option<std::vector<std::string>, false> ContentPath;
|
||||
extern Option<std::vector<std::string>, false> BiosPath;
|
||||
extern Option<std::string, false> VMUPath;
|
||||
extern Option<std::vector<std::string>, false> SavestatePath;
|
||||
extern Option<std::string, false> SavePath;
|
||||
extern Option<std::vector<std::string>, false> TexturePath;
|
||||
extern Option<std::string, false> TextureDumpPath;
|
||||
extern Option<std::string, false> BoxartPath;
|
||||
extern Option<std::vector<std::string>, false> MappingsPath;
|
||||
extern Option<std::vector<std::string>, false> CheatPath;
|
||||
extern Option<bool, false> HideLegacyNaomiRoms;
|
||||
extern Option<bool, false> UploadCrashLogs;
|
||||
extern Option<bool, false> DiscordPresence;
|
||||
#if defined(__ANDROID__) && !defined(LIBRETRO)
|
||||
extern Option<bool, false> UseSafFilePicker;
|
||||
#endif
|
||||
extern OptionString LogServer;
|
||||
|
||||
// Profiling
|
||||
extern Option<bool> ProfilerEnabled;
|
||||
extern Option<bool> ProfilerDrawToGUI;
|
||||
extern Option<bool> ProfilerOutputTTY;
|
||||
extern Option<float> ProfilerFrameWarningTime;
|
||||
|
||||
// Network
|
||||
|
||||
extern Option<bool> NetworkEnable;
|
||||
extern Option<bool> ActAsServer;
|
||||
extern Option<bool> NaomiSatellite;
|
||||
extern OptionString DNS;
|
||||
extern OptionString NetworkServer;
|
||||
extern Option<int> LocalPort;
|
||||
extern Option<bool> EmulateBBA;
|
||||
extern Option<bool> EnableUPnP;
|
||||
extern Option<bool> GGPOEnable;
|
||||
extern Option<int> GGPODelay;
|
||||
extern Option<bool> NetworkStats;
|
||||
extern Option<int> GGPOAnalogAxes;
|
||||
extern Option<bool> GGPOChat;
|
||||
extern Option<bool> GGPOChatTimeoutToggle;
|
||||
extern Option<int> GGPOChatTimeout;
|
||||
extern Option<bool> NetworkOutput;
|
||||
extern Option<int> MultiboardSlaves;
|
||||
extern Option<bool> BattleCableEnable;
|
||||
extern Option<bool> UseDCNet;
|
||||
extern OptionString ISPUsername;
|
||||
|
||||
#ifdef USE_OMX
|
||||
extern Option<int> OmxAudioLatency;
|
||||
extern Option<bool> OmxAudioHdmi;
|
||||
#endif
|
||||
|
||||
// Maple
|
||||
|
||||
extern Option<int> MouseSensitivity;
|
||||
extern Option<int> VirtualGamepadVibration;
|
||||
extern Option<int> VirtualGamepadTransparency;
|
||||
extern std::array<Option<MapleDeviceType>, 4> MapleMainDevices;
|
||||
extern std::array<std::array<Option<MapleDeviceType>, 2>, 4> MapleExpansionDevices;
|
||||
extern std::array<std::array<Option<int>, 2>, 4> NetworkExpansionDevices;
|
||||
extern Option<bool> PerGameVmu;
|
||||
#ifdef _WIN32
|
||||
extern Option<bool, false> UseRawInput;
|
||||
#else
|
||||
constexpr bool UseRawInput = false;
|
||||
#endif
|
||||
extern Option<bool> UsePhysicalVmuMemory;
|
||||
|
||||
#ifdef USE_LUA
|
||||
extern Option<std::string, false> LuaFileName;
|
||||
#endif
|
||||
|
||||
// RetroAchievements
|
||||
|
||||
extern Option<bool> EnableAchievements;
|
||||
extern Option<bool> ShowAchievementPopups;
|
||||
extern Option<bool> AchievementsHardcoreMode;
|
||||
extern OptionString AchievementsUserName;
|
||||
extern OptionString AchievementsToken;
|
||||
extern OptionString AchievementsHostUrl;
|
||||
|
||||
} // namespace config
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <libchdr/chd.h>
|
||||
|
||||
#include "oslib/storage.h"
|
||||
|
||||
namespace CHD
|
||||
{
|
||||
namespace Callbacks
|
||||
{
|
||||
// Moved out here to avoid being duplicated by the template.
|
||||
inline uint64_t fsize(struct chd_core_file *core_file)
|
||||
{
|
||||
auto file = static_cast<hostfs::File*>(core_file->argp);
|
||||
return file->size();
|
||||
}
|
||||
|
||||
inline size_t fread(void* buffer, size_t size, size_t count, struct chd_core_file *core_file)
|
||||
{
|
||||
auto file = static_cast<hostfs::File*>(core_file->argp);
|
||||
return file->read(buffer, size, count);
|
||||
}
|
||||
|
||||
inline int fclose(struct chd_core_file *core_file)
|
||||
{
|
||||
auto file = static_cast<hostfs::File*>(core_file->argp);
|
||||
delete file;
|
||||
delete core_file;
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int fseek(struct chd_core_file *core_file, int64_t offset, int whence)
|
||||
{
|
||||
auto file = static_cast<hostfs::File*>(core_file->argp);
|
||||
return file->seek(offset, whence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Ts>
|
||||
inline auto chd_open_file(hostfs::File *file, Ts &&...args)
|
||||
{
|
||||
auto core_file = new struct chd_core_file();
|
||||
|
||||
core_file->argp = file;
|
||||
core_file->fsize = CHD::Callbacks::fsize;
|
||||
core_file->fread = CHD::Callbacks::fread;
|
||||
core_file->fclose = CHD::Callbacks::fclose;
|
||||
core_file->fseek = CHD::Callbacks::fseek;
|
||||
|
||||
return chd_open_core_file(core_file, std::forward<Ts>(args)...);
|
||||
}
|
||||
-1120
File diff suppressed because it is too large.
Load diff
-104
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
Created on: Sep 23, 2019
|
||||
|
||||
Copyright 2019 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
struct WidescreenCheat
|
||||
{
|
||||
const char *game_id;
|
||||
const char *area_or_version;
|
||||
u32 addresses[16];
|
||||
u32 values[16];
|
||||
u32 origValues[16];
|
||||
};
|
||||
|
||||
struct Cheat
|
||||
{
|
||||
enum class Type {
|
||||
disabled,
|
||||
setValue,
|
||||
increase,
|
||||
decrease,
|
||||
runNextIfEq,
|
||||
runNextIfNeq,
|
||||
runNextIfGt,
|
||||
runNextIfLt,
|
||||
copy
|
||||
};
|
||||
Type type;
|
||||
std::string description;
|
||||
bool enabled;
|
||||
u32 size;
|
||||
u32 address;
|
||||
u32 value;
|
||||
u8 valueMask = 0;
|
||||
u32 repeatCount = 1;
|
||||
u32 repeatValueIncrement = 0;
|
||||
u32 repeatAddressIncrement = 0;
|
||||
u32 destAddress = 0;
|
||||
bool builtIn;
|
||||
|
||||
Cheat(Type type = Type::disabled, const std::string& description = "", bool enabled = false,
|
||||
u32 size = 0, u32 address = 0, u32 value = 0, bool builtIn = false)
|
||||
: type(type), description(description), enabled(enabled),
|
||||
size(size), address(address), value(value), builtIn(builtIn)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class CheatManager
|
||||
{
|
||||
public:
|
||||
~CheatManager() {
|
||||
reset("");
|
||||
}
|
||||
void reset(const std::string& gameId);
|
||||
void apply();
|
||||
size_t cheatCount() const { return cheats.size(); }
|
||||
const std::string& cheatDescription(size_t index) const { return cheats[index].description; }
|
||||
bool cheatEnabled(size_t index) const { return cheats[index].enabled; }
|
||||
void enableCheat(size_t index, bool enabled) { cheats[index].enabled = enabled; }
|
||||
void loadCheatFile(const std::string& filename);
|
||||
void saveCheatFile(const std::string& filename);
|
||||
// Returns true if using 16:9 anamorphic screen ratio
|
||||
bool isWidescreen() const { return widescreen_cheat != nullptr; }
|
||||
void addGameSharkCheat(const std::string& name, const std::string& s);
|
||||
|
||||
private:
|
||||
u32 readRam(u32 addr, u32 bits);
|
||||
void writeRam(u32 addr, u32 value, u32 bits);
|
||||
void setActive(bool active);
|
||||
|
||||
static const WidescreenCheat widescreen_cheats[];
|
||||
static const WidescreenCheat naomi_widescreen_cheats[];
|
||||
const WidescreenCheat *widescreen_cheat = nullptr;
|
||||
bool active = false;
|
||||
std::vector<Cheat> cheats;
|
||||
std::string gameId;
|
||||
|
||||
friend class CheatManagerTest_TestLoad_Test;
|
||||
friend class CheatManagerTest_TestGameShark_Test;
|
||||
friend class CheatManagerTest_TestSave_Test;
|
||||
};
|
||||
|
||||
extern CheatManager cheatManager;
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#LOCAL_PATH:=
|
||||
|
||||
#MFLAGS := -marm -march=armv7-a -mtune=cortex-a8 -mfpu=vfpv3-d16 -mfloat-abi=softfp
|
||||
#ASFLAGS := -march=armv7-a -mfpu=vfp-d16 -mfloat-abi=softfp
|
||||
#LDFLAGS := -Wl,-Map,$(notdir $@).map,--gc-sections -Wl,-O3 -Wl,--sort-common
|
||||
|
||||
RZDCY_SRC_DIR ?= $(call my-dir)
|
||||
|
||||
RZDCY_MODULES := cfg/ hw/arm7/ hw/aica/ hw/holly/ hw/ hw/gdrom/ hw/maple/ \
|
||||
hw/mem/ hw/pvr/ hw/sh4/ hw/sh4/interpr/ hw/sh4/modules/ plugins/ profiler/ oslib/ \
|
||||
hw/extdev/ hw/arm/ hw/naomi/ imgread/ linux/ ./ deps/coreio/ deps/zlib/ deps/chdr/ deps/crypto/ \
|
||||
deps/libelf/ deps/chdpsr/ arm_emitter/ rend/ reios/ deps/libpng/
|
||||
|
||||
|
||||
ifdef WEBUI
|
||||
RZDCY_MODULES += webui/
|
||||
RZDCY_MODULES += deps/libwebsocket/
|
||||
|
||||
ifdef FOR_ANDROID
|
||||
RZDCY_MODULES += deps/ifaddrs/
|
||||
endif
|
||||
endif
|
||||
|
||||
ifndef NO_REC
|
||||
RZDCY_MODULES += hw/sh4/dyna/
|
||||
endif
|
||||
|
||||
ifndef NOT_ARM
|
||||
RZDCY_MODULES += rec-ARM/
|
||||
endif
|
||||
|
||||
ifdef X86_REC
|
||||
RZDCY_MODULES += rec-x86/ emitter/
|
||||
endif
|
||||
|
||||
ifdef X64_REC
|
||||
RZDCY_MODULES += rec-x64/
|
||||
endif
|
||||
|
||||
ifdef CPP_REC
|
||||
RZDCY_MODULES += rec-cpp/
|
||||
endif
|
||||
|
||||
ifndef NO_REND
|
||||
RZDCY_MODULES += rend/gles/
|
||||
else
|
||||
RZDCY_MODULES += rend/norend/
|
||||
endif
|
||||
|
||||
ifndef NO_NIXPROF
|
||||
RZDCY_MODULES += linux/nixprof/
|
||||
endif
|
||||
|
||||
ifdef FOR_ANDROID
|
||||
RZDCY_MODULES += android/ deps/libandroid/ deps/libzip/
|
||||
endif
|
||||
|
||||
ifdef USE_SDL
|
||||
RZDCY_MODULES += sdl/
|
||||
endif
|
||||
|
||||
ifdef FOR_LINUX
|
||||
RZDCY_MODULES += linux-dist/
|
||||
endif
|
||||
|
||||
RZDCY_FILES := $(foreach dir,$(addprefix $(RZDCY_SRC_DIR)/,$(RZDCY_MODULES)),$(wildcard $(dir)*.cpp))
|
||||
RZDCY_FILES += $(foreach dir,$(addprefix $(RZDCY_SRC_DIR)/,$(RZDCY_MODULES)),$(wildcard $(dir)*.c))
|
||||
RZDCY_FILES += $(foreach dir,$(addprefix $(RZDCY_SRC_DIR)/,$(RZDCY_MODULES)),$(wildcard $(dir)*.S))
|
||||
|
||||
ifdef FOR_PANDORA
|
||||
RZDCY_CFLAGS := \
|
||||
$(CFLAGS) -c -O3 -I$(RZDCY_SRC_DIR) -I$(RZDCY_SRC_DIR)/deps \
|
||||
-DRELEASE -DPANDORA\
|
||||
-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=softfp \
|
||||
-frename-registers -fsingle-precision-constant -ffast-math \
|
||||
-ftree-vectorize -fomit-frame-pointer
|
||||
RZDCY_CFLAGS += -march=armv7-a -mtune=cortex-a8 -mfpu=neon
|
||||
RZDCY_CFLAGS += -DTARGET_LINUX_ARMELv7
|
||||
else
|
||||
RZDCY_CFLAGS := \
|
||||
$(CFLAGS) -c -O3 -I$(RZDCY_SRC_DIR) -I$(RZDCY_SRC_DIR)/deps \
|
||||
-D_ANDROID -DRELEASE\
|
||||
-frename-registers -fsingle-precision-constant -ffast-math \
|
||||
-ftree-vectorize -fomit-frame-pointer
|
||||
|
||||
ifndef NOT_ARM
|
||||
RZDCY_CFLAGS += -march=armv7-a -mtune=cortex-a9 -mfpu=vfpv3-d16
|
||||
RZDCY_CFLAGS += -DTARGET_LINUX_ARMELv7
|
||||
else
|
||||
ifndef ISMIPS
|
||||
RZDCY_CFLAGS += -DTARGET_LINUX_x86
|
||||
else
|
||||
RZDCY_CFLAGS += -DTARGET_LINUX_MIPS
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef NO_REC
|
||||
RZDCY_CFLAGS += -DTARGET_NO_REC
|
||||
endif
|
||||
|
||||
ifdef USE_GLES
|
||||
RZDCY_CFLAGS += -DGLES
|
||||
endif
|
||||
|
||||
RZDCY_CXXFLAGS := $(RZDCY_CFLAGS) -fno-exceptions -fno-rtti -std=gnu++11
|
||||
@@ -1,347 +0,0 @@
|
||||
/*
|
||||
Copyright 2021 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
#include "emulator.h"
|
||||
#include "hw/sh4/sh4_if.h"
|
||||
#include "hw/sh4/sh4_mem.h"
|
||||
#include "hw/sh4/dyna/shil.h"
|
||||
#include "cfg/option.h"
|
||||
#include <array>
|
||||
#include <csignal>
|
||||
#include <map>
|
||||
|
||||
#ifndef SIGTRAP
|
||||
#define SIGTRAP 5
|
||||
#endif
|
||||
#ifndef SIGBUS
|
||||
#define SIGBUS 7
|
||||
#endif
|
||||
|
||||
const std::array<Sh4RegType, 59> Sh4RegList {
|
||||
reg_r0,
|
||||
reg_r1,
|
||||
reg_r2,
|
||||
reg_r3,
|
||||
reg_r4,
|
||||
reg_r5,
|
||||
reg_r6,
|
||||
reg_r7,
|
||||
reg_r8,
|
||||
reg_r9,
|
||||
reg_r10,
|
||||
reg_r11,
|
||||
reg_r12,
|
||||
reg_r13,
|
||||
reg_r14,
|
||||
reg_r15,
|
||||
reg_nextpc,
|
||||
reg_pr,
|
||||
reg_gbr,
|
||||
reg_vbr,
|
||||
reg_mach,
|
||||
reg_macl,
|
||||
reg_sr_status,
|
||||
|
||||
reg_fpul,
|
||||
reg_fpscr,
|
||||
reg_fr_0, reg_fr_1, reg_fr_2, reg_fr_3, reg_fr_4, reg_fr_5, reg_fr_6, reg_fr_7,
|
||||
reg_fr_8, reg_fr_9, reg_fr_10, reg_fr_11, reg_fr_12, reg_fr_13, reg_fr_14, reg_fr_15,
|
||||
reg_ssr,
|
||||
reg_spc,
|
||||
// correct?
|
||||
reg_r0, reg_r1, reg_r2, reg_r3, reg_r4, reg_r5, reg_r6, reg_r7,
|
||||
reg_r0_Bank, reg_r1_Bank, reg_r2_Bank, reg_r3_Bank, reg_r4_Bank, reg_r5_Bank, reg_r6_Bank, reg_r7_Bank,
|
||||
};
|
||||
|
||||
class DebugAgent
|
||||
{
|
||||
public:
|
||||
struct Breakpoint {
|
||||
enum Type
|
||||
{
|
||||
BP_TYPE_SOFTWARE_BREAK,
|
||||
BP_TYPE_HARDWARE_BREAK,
|
||||
BP_TYPE_WRITE_WATCHPOINT,
|
||||
BP_TYPE_READ_WATCHPOINT,
|
||||
BP_TYPE_ACCESS_WATCHPOINT,
|
||||
BP_TYPE_COUNT
|
||||
};
|
||||
|
||||
Breakpoint() = default;
|
||||
Breakpoint(u16 type, u32 addr) : addr(addr), type(type) { }
|
||||
u32 addr = 0;
|
||||
u16 type = 0;
|
||||
u16 savedOp = 0;
|
||||
};
|
||||
|
||||
void doContinue(u32 pc = 1)
|
||||
{
|
||||
if (pc != 1)
|
||||
Sh4cntx.pc = pc;
|
||||
emu.start();
|
||||
}
|
||||
|
||||
void step()
|
||||
{
|
||||
bool restoreBreakpoint = removeMatchpoint(Breakpoint::BP_TYPE_SOFTWARE_BREAK, Sh4cntx.pc, 2);
|
||||
u32 savedPc = Sh4cntx.pc;
|
||||
emu.step();
|
||||
if (restoreBreakpoint)
|
||||
insertMatchpoint(Breakpoint::BP_TYPE_SOFTWARE_BREAK, savedPc, 2);
|
||||
}
|
||||
|
||||
void stepRange(u32 from, u32 to)
|
||||
{
|
||||
bool restoreBreakpoint = removeMatchpoint(Breakpoint::BP_TYPE_SOFTWARE_BREAK, Sh4cntx.pc, 2);
|
||||
u32 savedPc = Sh4cntx.pc;
|
||||
emu.stepRange(from, to);
|
||||
if (restoreBreakpoint)
|
||||
insertMatchpoint(Breakpoint::BP_TYPE_SOFTWARE_BREAK, savedPc, 2);
|
||||
}
|
||||
|
||||
int readAllRegs(u32 **regs)
|
||||
{
|
||||
static std::array<u32, Sh4RegList.size()> allregs;
|
||||
for (u32 i = 0; i < Sh4RegList.size(); i++)
|
||||
{
|
||||
if (Sh4RegList[i] == reg_sr_status)
|
||||
allregs[i] = Sh4cntx.sr.getFull();
|
||||
else if (Sh4RegList[i] != NoReg)
|
||||
allregs[i] = *GetRegPtr(Sh4cntx, Sh4RegList[i]);
|
||||
}
|
||||
*regs = &allregs[0];
|
||||
return allregs.size();
|
||||
}
|
||||
|
||||
void writeAllRegs(const std::vector<u32>& regs)
|
||||
{
|
||||
for (u32 i = 0; i < Sh4RegList.size(); i++)
|
||||
if (Sh4RegList[i] != NoReg)
|
||||
*GetRegPtr(Sh4cntx, Sh4RegList[i]) = regs[i];
|
||||
}
|
||||
|
||||
u32 readReg(u32 regNum)
|
||||
{
|
||||
if (regNum >= Sh4RegList.size())
|
||||
return 0;
|
||||
Sh4RegType reg = Sh4RegList[regNum];
|
||||
if (reg == reg_sr_status)
|
||||
return Sh4cntx.sr.getFull();
|
||||
if (reg != NoReg)
|
||||
return *GetRegPtr(Sh4cntx, reg);
|
||||
return 0;
|
||||
}
|
||||
void writeReg(u32 regNum, u32 value)
|
||||
{
|
||||
if (regNum >= Sh4RegList.size())
|
||||
return;
|
||||
Sh4RegType reg = Sh4RegList[regNum];
|
||||
if (reg == reg_sr_status)
|
||||
Sh4cntx.sr.setFull(value);
|
||||
else if (reg != NoReg)
|
||||
*GetRegPtr(Sh4cntx, reg) = value;
|
||||
}
|
||||
|
||||
const u8 *readMem(u32 addr, u32 len)
|
||||
{
|
||||
static std::vector<u8> data;
|
||||
data.resize(len);
|
||||
u8 *p = &data[0];
|
||||
while (len > 0)
|
||||
{
|
||||
if (len >= 4 && (addr & 3) == 0)
|
||||
{
|
||||
*(u32 *)p = ReadMem32_nommu(addr);
|
||||
addr += 4;
|
||||
len -= 4;
|
||||
p += 4;
|
||||
}
|
||||
else if (len >= 2 && (addr & 1) == 0)
|
||||
{
|
||||
*(u16 *)p = ReadMem16_nommu(addr);
|
||||
addr += 2;
|
||||
len -= 2;
|
||||
p += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
*p++ = ReadMem8_nommu(addr);
|
||||
addr++;
|
||||
len--;
|
||||
}
|
||||
}
|
||||
return &data[0];
|
||||
}
|
||||
void writeMem(u32 addr, const std::vector<u8>& data)
|
||||
{
|
||||
const u8 *p = &data[0];
|
||||
u32 len = data.size();
|
||||
while (len > 0)
|
||||
{
|
||||
if (len >= 4 && (addr & 3) == 0)
|
||||
{
|
||||
WriteMem32_nommu(addr, *(u32 *)p);
|
||||
addr += 4;
|
||||
len -= 4;
|
||||
p += 4;
|
||||
}
|
||||
else if (len >= 2 && (addr & 3) == 0)
|
||||
{
|
||||
WriteMem16_nommu(addr, *(u16 *)p);
|
||||
addr += 2;
|
||||
len -= 2;
|
||||
p += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteMem8_nommu(addr, *p++);
|
||||
addr++;
|
||||
len--;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool insertMatchpoint(Breakpoint::Type type, u32 addr, u32 len)
|
||||
{
|
||||
if (type == Breakpoint::BP_TYPE_SOFTWARE_BREAK && len != 2) {
|
||||
WARN_LOG(COMMON, "insertMatchpoint: length != 2: %d", len);
|
||||
return false;
|
||||
}
|
||||
// TODO other matchpoint types
|
||||
if (breakpoints[type].find(addr) != breakpoints[type].end())
|
||||
return true;
|
||||
breakpoints[type][addr] = Breakpoint(type, addr);
|
||||
breakpoints[type][addr].savedOp = ReadMem16_nommu(addr);
|
||||
WriteMem16_nommu(addr, 0xC308); // trapa #0x20
|
||||
return true;
|
||||
}
|
||||
bool removeMatchpoint(Breakpoint::Type type, u32 addr, u32 len)
|
||||
{
|
||||
if (type == Breakpoint::BP_TYPE_SOFTWARE_BREAK && len != 2) {
|
||||
WARN_LOG(COMMON, "removeMatchpoint: length != 2: %d", len);
|
||||
return false;
|
||||
}
|
||||
auto it = breakpoints[type].find(addr);
|
||||
if (it == breakpoints[type].end())
|
||||
return false;
|
||||
WriteMem16_nommu(addr, it->second.savedOp);
|
||||
breakpoints[type].erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 interrupt()
|
||||
{
|
||||
config::DynarecEnabled = false;
|
||||
exception = SIGINT;
|
||||
emu.stop();
|
||||
return exception;
|
||||
}
|
||||
|
||||
// called on the emu thread
|
||||
void debugTrap(u32 event)
|
||||
{
|
||||
exception = findException(event);
|
||||
Sh4cntx.pc -= 2; // FIXME delay slot
|
||||
}
|
||||
|
||||
void postDebugTrap()
|
||||
{
|
||||
// needed to join the emu thread since debugTrap() is called by it
|
||||
emu.stop();
|
||||
}
|
||||
|
||||
u32 currentException()
|
||||
{
|
||||
return exception;
|
||||
}
|
||||
|
||||
void restart()
|
||||
{
|
||||
emu.unloadGame();
|
||||
emu.loadGame(settings.content.path.c_str());
|
||||
emu.start();
|
||||
}
|
||||
|
||||
void detach()
|
||||
{
|
||||
emu.start();
|
||||
}
|
||||
|
||||
void kill()
|
||||
{
|
||||
dc_exit();
|
||||
}
|
||||
|
||||
void resetAgent()
|
||||
{
|
||||
stack.clear();
|
||||
}
|
||||
|
||||
int findException(u32 event)
|
||||
{
|
||||
switch (event)
|
||||
{
|
||||
case 0x0A0: // Instruction/data TLB protection violation exception (read)
|
||||
case 0x0C0: // Data TLB protection violation exception (write)
|
||||
return SIGSEGV;
|
||||
case 0x0E0: // Instruction/data address error (read)
|
||||
case 0x100: // data address error (write)
|
||||
return SIGBUS;
|
||||
case 0x160: // trapa
|
||||
case 0x1E0: // User break before/after instruction execution
|
||||
return SIGTRAP;
|
||||
case 0x180: // General illegal instruction exception
|
||||
case 0x1A0: // Slot illegal instruction exception
|
||||
return SIGILL;
|
||||
case 0x120: // FPU exception
|
||||
case 0x800: // General FPU disable exception
|
||||
case 0x820: // Slot FPU disable exception
|
||||
return SIGFPE;
|
||||
default:
|
||||
return SIGABRT;
|
||||
}
|
||||
}
|
||||
|
||||
const u32 *getStack(u32& size)
|
||||
{
|
||||
verify(!Sh4cntx.CpuRunning);
|
||||
size = stack.size() * 8;
|
||||
if (!stack.empty())
|
||||
return (const u32 *)&stack[0];
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void subroutineCall()
|
||||
{
|
||||
subroutineReturn();
|
||||
stack.push_back(std::make_pair(Sh4cntx.pc, Sh4cntx.r[15]));
|
||||
}
|
||||
|
||||
void subroutineReturn()
|
||||
{
|
||||
while (!stack.empty() && Sh4cntx.r[15] >= stack.back().second)
|
||||
stack.pop_back();
|
||||
}
|
||||
|
||||
u32 exception = 0;
|
||||
|
||||
std::map<u32, Breakpoint> breakpoints[Breakpoint::Type::BP_TYPE_COUNT];
|
||||
std::vector<std::pair<u32, u32>> stack;
|
||||
};
|
||||
@@ -1,989 +0,0 @@
|
||||
/*
|
||||
Copyright 2021 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "types.h"
|
||||
|
||||
#ifdef GDB_SERVER
|
||||
#include "gdb_server.h"
|
||||
#include "debug_agent.h"
|
||||
#include "cfg/option.h"
|
||||
#include "oslib/oslib.h"
|
||||
#include "util/shared_this.h"
|
||||
#include <asio.hpp>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
namespace debugger {
|
||||
|
||||
constexpr u32 MAX_PACKET_LEN = 4096;
|
||||
|
||||
static u8 unpack(char c)
|
||||
{
|
||||
c = std::tolower(c);
|
||||
if (c <= '9')
|
||||
return c - '0';
|
||||
else
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
|
||||
u32 unpack(const char *s, int l)
|
||||
{
|
||||
u32 r = 0;
|
||||
for (int i = 0; i < l && *s != '\0'; i += 2, s += 2) {
|
||||
r |= (unpack(s[0]) << 4 | unpack(s[1])) << (i * 4);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
class GdbServer;
|
||||
|
||||
class Connection : public SharedThis<Connection>
|
||||
{
|
||||
public:
|
||||
asio::ip::tcp::socket& getSocket() {
|
||||
return socket;
|
||||
}
|
||||
|
||||
void start() {
|
||||
asio::async_read_until(socket, asio::dynamic_string_buffer(message, MAX_PACKET_LEN), packetMatcher,
|
||||
std::bind(&Connection::handlePacket, shared_from_this(),
|
||||
asio::placeholders::error,
|
||||
asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void send(const std::string& msg)
|
||||
{
|
||||
if (!msg.empty())
|
||||
{
|
||||
if (!outMessage.empty()) {
|
||||
WARN_LOG(COMMON, "Message dropped: %s. Already sending: %s", msg.c_str(), outMessage.c_str());
|
||||
return;
|
||||
}
|
||||
outMessage = msg;
|
||||
asio::async_write(socket, asio::buffer(outMessage),
|
||||
std::bind(&Connection::writeDone, shared_from_this(),
|
||||
asio::placeholders::error,
|
||||
asio::placeholders::bytes_transferred));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Connection(GdbServer& server, asio::io_context& io_context)
|
||||
: server(server), io_context(io_context), socket(io_context) {
|
||||
}
|
||||
|
||||
using iterator = asio::buffers_iterator<asio::const_buffers_1>;
|
||||
|
||||
std::pair<iterator, bool>
|
||||
static packetMatcher(iterator begin, iterator end)
|
||||
{
|
||||
if (begin == end)
|
||||
return std::make_pair(begin, false);
|
||||
iterator i = begin;
|
||||
if (*i == '\03')
|
||||
// break
|
||||
return std::make_pair(i + 1, true);
|
||||
if (*i != '$') {
|
||||
// unexpected, or ack/nack ('+', '-')
|
||||
return std::make_pair(i + 1, true);
|
||||
}
|
||||
++i;
|
||||
while (i != end && *i != '#')
|
||||
++i;
|
||||
if (i + 3 <= end)
|
||||
// 2 chars for CRC
|
||||
return std::make_pair(i + 3, true);
|
||||
|
||||
return std::make_pair(begin, false);
|
||||
}
|
||||
|
||||
void handlePacket(const std::error_code& ec, size_t len);
|
||||
|
||||
void writeDone(const std::error_code& ec, size_t len)
|
||||
{
|
||||
if (ec)
|
||||
WARN_LOG(COMMON, "Write error: %s", ec.message().c_str());
|
||||
outMessage.clear();
|
||||
}
|
||||
|
||||
GdbServer& server;
|
||||
asio::io_context& io_context;
|
||||
asio::ip::tcp::socket socket;
|
||||
std::string message;
|
||||
std::string outMessage;
|
||||
friend super;
|
||||
};
|
||||
|
||||
class TcpAcceptor
|
||||
{
|
||||
public:
|
||||
TcpAcceptor(GdbServer& server, asio::io_context& io_context, u16 port)
|
||||
: server(server), io_context(io_context),
|
||||
acceptor(asio::ip::tcp::acceptor(io_context,
|
||||
asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)))
|
||||
{
|
||||
asio::socket_base::reuse_address option(true);
|
||||
acceptor.set_option(option);
|
||||
start();
|
||||
}
|
||||
|
||||
private:
|
||||
void start()
|
||||
{
|
||||
Connection::Ptr newConnection = Connection::create(server, io_context);
|
||||
|
||||
acceptor.async_accept(newConnection->getSocket(),
|
||||
std::bind(&TcpAcceptor::handleAccept, this, newConnection, asio::placeholders::error));
|
||||
}
|
||||
void handleAccept(Connection::Ptr newConnection, const std::error_code& error);
|
||||
|
||||
GdbServer& server;
|
||||
asio::io_context& io_context;
|
||||
asio::ip::tcp::acceptor acceptor;
|
||||
};
|
||||
|
||||
class GdbServer
|
||||
{
|
||||
public:
|
||||
struct Error : public std::runtime_error {
|
||||
Error(const char *reason) : std::runtime_error(reason) {}
|
||||
};
|
||||
|
||||
void init(int port)
|
||||
{
|
||||
this->port = port;
|
||||
EventManager::listen(Event::Resume, emuEventCallback, this);
|
||||
EventManager::listen(Event::Terminate, emuEventCallback, this);
|
||||
}
|
||||
|
||||
void term()
|
||||
{
|
||||
EventManager::unlisten(Event::Resume, emuEventCallback, this);
|
||||
EventManager::unlisten(Event::Terminate, emuEventCallback, this);
|
||||
stop();
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
if (thread.joinable())
|
||||
return;
|
||||
DEBUG_LOG(COMMON, "GdbServer starting");
|
||||
io_context = std::make_unique<asio::io_context>();
|
||||
thread = std::thread(&GdbServer::serverThread, this);
|
||||
if (config::GDBWaitForConnection)
|
||||
{
|
||||
DEBUG_LOG(COMMON, "Waiting for GDB connection...");
|
||||
agentInterrupt();
|
||||
}
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
if (thread.joinable())
|
||||
{
|
||||
DEBUG_LOG(COMMON, "GdbServer stopping");
|
||||
agent.resetAgent();
|
||||
io_context->stop();
|
||||
thread.join();
|
||||
io_context.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool isRunning() const {
|
||||
return thread.joinable();
|
||||
}
|
||||
|
||||
// called on the emu thread
|
||||
void debugTrap(u32 event)
|
||||
{
|
||||
if (!attached)
|
||||
return;
|
||||
agent.debugTrap(event);
|
||||
std::string pkt = handleCommand("?"); // report exception
|
||||
if (connection)
|
||||
io_context->post([this, pkt]() { connection->send(pkt); });
|
||||
postDebugTrapNeeded = true;
|
||||
throw Stop();
|
||||
}
|
||||
|
||||
private:
|
||||
const u32 EXCEPT_NONE = 1;
|
||||
|
||||
void serverThread()
|
||||
{
|
||||
ThreadName _("GdbServer");
|
||||
try
|
||||
{
|
||||
TcpAcceptor server(*this, *io_context, port);
|
||||
io_context->run();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
ERROR_LOG(COMMON, "Gdb server exception: %s", e.what());
|
||||
}
|
||||
attached = false;
|
||||
connection.reset();
|
||||
}
|
||||
|
||||
std::string handleCommand(const std::string& packet)
|
||||
{
|
||||
try {
|
||||
if (postDebugTrapNeeded)
|
||||
{
|
||||
postDebugTrapNeeded = false;
|
||||
try {
|
||||
agent.postDebugTrap();
|
||||
} catch (const FlycastException& e) {
|
||||
throw Error(e.what());
|
||||
}
|
||||
}
|
||||
if (packet.empty())
|
||||
return "";
|
||||
|
||||
DEBUG_LOG(NETWORK, "gdb: recv %s", packet.c_str());
|
||||
std::vector<std::string> replies;
|
||||
switch (packet[0])
|
||||
{
|
||||
case '!': // Enable extended mode
|
||||
replies.push_back("OK");
|
||||
break;
|
||||
case '?': // Sent when connection is first established to query the reason the target halted
|
||||
replies.push_back(reportException());
|
||||
break;
|
||||
case 'A': // Initialized argv[] array passed into program. not supported
|
||||
replies.push_back("E01");
|
||||
break;
|
||||
case 'b': // Change the serial line speed to baud. deprecated
|
||||
break;
|
||||
case 'B': // Set or clear a breakpoint at addr. deprecated
|
||||
break;
|
||||
case 'c': // Continue at addr, which is the address to resume.
|
||||
// If addr is omitted, resume at current address
|
||||
doContinue(packet);
|
||||
break;
|
||||
case 'C': // Continue with signal sig
|
||||
doContinue(packet);
|
||||
break;
|
||||
case 'd': // Toggle debug flag. deprecated
|
||||
break;
|
||||
case 'D': // Detach GDB from the remote system
|
||||
replies.push_back("OK");
|
||||
agent.detach();
|
||||
break;
|
||||
case 'F': // File-I/O protocol extension not currently supported
|
||||
break;
|
||||
case 'g': // Read general registers
|
||||
replies.push_back(readAllRegs());
|
||||
break;
|
||||
case 'G': // Write general registers
|
||||
replies.push_back(writeAllRegs(packet));
|
||||
break;
|
||||
case 'H': // Set thread for subsequent operations
|
||||
replies.push_back("OK");
|
||||
break;
|
||||
case 'i': // Step the remote target by a single clock cycle
|
||||
case 'I': // Signal, then cycle step
|
||||
// not supported
|
||||
replies.push_back("");
|
||||
break;
|
||||
case 'k': // Kill request. Stop process/system
|
||||
agent.kill();
|
||||
break;
|
||||
case 'm': // Read length addressable memory units
|
||||
replies.push_back(readMem(packet));
|
||||
break;
|
||||
case 'M': // Write length addressable memory units
|
||||
replies.push_back(writeMem(packet));
|
||||
break;
|
||||
case 'p': // Read the value of register
|
||||
replies.push_back(readReg(packet));
|
||||
break;
|
||||
case 'P': // Write register
|
||||
replies.push_back(writeReg(packet));
|
||||
break;
|
||||
case 'q': // General query packets
|
||||
{
|
||||
auto v = query(packet);
|
||||
replies.insert(replies.end(), v.begin(), v.end());
|
||||
}
|
||||
break;
|
||||
case 'Q': // General set packets
|
||||
{
|
||||
auto v = set(packet);
|
||||
replies.insert(replies.end(), v.begin(), v.end());
|
||||
}
|
||||
break;
|
||||
case 'r': // Reset the entire system. Deprecated (use 'R' instead)
|
||||
break;
|
||||
case 'R': // Restart the program being debugged.
|
||||
restart();
|
||||
break;
|
||||
case 's': // Single step
|
||||
replies.push_back(step(EXCEPT_NONE));
|
||||
break;
|
||||
case 'S': // Step with signal
|
||||
replies.push_back(step());
|
||||
break;
|
||||
case 't': // Search backwards. unsupported
|
||||
break;
|
||||
case 'T': // Find out if the thread is alive
|
||||
replies.push_back("OK");
|
||||
break;
|
||||
case 'v': // 'v' packets to control execution
|
||||
{
|
||||
auto v = vpacket(packet);
|
||||
replies.insert(replies.end(), v.begin(), v.end());
|
||||
}
|
||||
break;
|
||||
case 'X': // Write binary data to memory
|
||||
replies.push_back(writeMemBin(packet));
|
||||
break;
|
||||
case 'z': // Remove a breakpoint/watchpoint.
|
||||
replies.push_back(removeMatchpoint(packet));
|
||||
break;
|
||||
case 'Z': // Insert a breakpoint/watchpoint.
|
||||
replies.push_back(insertMatchpoint(packet));
|
||||
break;
|
||||
case 3:
|
||||
replies.push_back(interrupt());
|
||||
break;
|
||||
default:
|
||||
// Unknown commands are ignored
|
||||
WARN_LOG(COMMON, "Unknown gdb command: %s", packet.c_str());
|
||||
break;
|
||||
}
|
||||
std::string data;
|
||||
for (const std::string& pkt : replies)
|
||||
{
|
||||
data.push_back('$');
|
||||
u8 checksum = 0;
|
||||
for (char c : pkt)
|
||||
{
|
||||
if (c == '$' || c == '#' || c == '*' || c == '}')
|
||||
{
|
||||
c ^= 0x20;
|
||||
checksum += (u8)'}';
|
||||
data.push_back('}');
|
||||
}
|
||||
checksum += (u8)c;
|
||||
data.push_back(c);
|
||||
}
|
||||
data.push_back('#');
|
||||
char s[9];
|
||||
snprintf(s, sizeof(s), "%02x", checksum);
|
||||
data += s;
|
||||
}
|
||||
DEBUG_LOG(NETWORK, "gdb: sent %s", data.c_str());
|
||||
|
||||
return data;
|
||||
} catch (const Error& e) {
|
||||
ERROR_LOG(COMMON, "%s", e.what());
|
||||
attached = false;
|
||||
connection.reset();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
std::string reportException()
|
||||
{
|
||||
char s[4];
|
||||
snprintf(s, sizeof(s), "S%02X", agent.currentException());
|
||||
return s;
|
||||
}
|
||||
|
||||
void doContinue(const std::string& pkt)
|
||||
{
|
||||
if (pkt[0] != 'c') {
|
||||
WARN_LOG(COMMON, "Continue with signal not supported");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkt == "c")
|
||||
agent.doContinue();
|
||||
else
|
||||
{
|
||||
// Get the pc at which to resume
|
||||
u32 addr;
|
||||
if (sscanf(pkt.c_str(), "c%x", &addr) != 1)
|
||||
{
|
||||
WARN_LOG(COMMON, "Continue address invalid %s", pkt.c_str());
|
||||
return;
|
||||
}
|
||||
agent.doContinue(addr);
|
||||
}
|
||||
}
|
||||
|
||||
std::string readAllRegs()
|
||||
{
|
||||
u32 *regs;
|
||||
int c = agent.readAllRegs(®s);
|
||||
std::string outpkt;
|
||||
for (int i = 0; i < c; i++)
|
||||
outpkt += pack(regs[i]);
|
||||
return outpkt;
|
||||
}
|
||||
|
||||
std::string writeAllRegs(const std::string& pkt)
|
||||
{
|
||||
std::vector<u32> regs;
|
||||
for (auto it = pkt.begin() + 1; it <= pkt.end() - 8; it += 8)
|
||||
regs.push_back(unpack(&*it, 8));
|
||||
|
||||
agent.writeAllRegs(regs);
|
||||
return "OK";
|
||||
}
|
||||
|
||||
std::string readMem(const std::string& pkt)
|
||||
{
|
||||
u32 addr;
|
||||
u32 len;
|
||||
if (sscanf(pkt.c_str(), "m%x,%x:", &addr, &len) != 2)
|
||||
{
|
||||
WARN_LOG(COMMON, "readMem: invalid packet %s", pkt.c_str());
|
||||
return "E01";
|
||||
}
|
||||
const u8 *mem = agent.readMem(addr, len);
|
||||
std::string outpkt;
|
||||
for (u32 i = 0; i < len; i++)
|
||||
{
|
||||
char s[3];
|
||||
snprintf(s, sizeof(s), "%02x", mem[i]);
|
||||
outpkt += s;
|
||||
}
|
||||
return outpkt;
|
||||
}
|
||||
|
||||
std::string writeMem(const std::string& pkt)
|
||||
{
|
||||
u32 addr;
|
||||
u32 len;
|
||||
if (sscanf(pkt.c_str(), "M%x,%x:", &addr, &len) != 2)
|
||||
{
|
||||
WARN_LOG(COMMON, "writeMem: invalid packet %s", pkt.c_str());
|
||||
return "E01";
|
||||
}
|
||||
std::vector<u8> data(len);
|
||||
const char *p = &pkt[pkt.find(':')] + 1;
|
||||
for (u32 i = 0; i < len; i++, p += 2)
|
||||
{
|
||||
u32 b;
|
||||
sscanf(p,"%2x", &b);
|
||||
data[i] = (u8)b;
|
||||
}
|
||||
agent.writeMem(addr, data);
|
||||
return "OK";
|
||||
}
|
||||
|
||||
std::string writeMemBin(const std::string& pkt)
|
||||
{
|
||||
u32 addr;
|
||||
u32 len;
|
||||
if (sscanf(pkt.c_str(), "X%x,%x:", &addr, &len) != 2)
|
||||
{
|
||||
WARN_LOG(COMMON, "writeMemBin invalid command: %s", pkt.c_str());
|
||||
return "E01";
|
||||
}
|
||||
const char *p = &pkt[pkt.find(':')] + 1;
|
||||
std::vector<u8> data;
|
||||
data.reserve(pkt.length() - (p - &pkt[0]));
|
||||
for (; p <= &pkt.back(); p++)
|
||||
{
|
||||
u8 b = *p;
|
||||
if (b == '}')
|
||||
{
|
||||
b = *++p ^ 0x20;
|
||||
}
|
||||
data.push_back(b);
|
||||
}
|
||||
agent.writeMem(addr, data);
|
||||
return "OK";
|
||||
}
|
||||
|
||||
std::string readReg(const std::string& pkt)
|
||||
{
|
||||
u32 regNum;
|
||||
if (sscanf(pkt.c_str(), "p%x", ®Num) != 1)
|
||||
{
|
||||
WARN_LOG(COMMON, "readReg: invalid packet %s", pkt.c_str());
|
||||
return "E01";
|
||||
}
|
||||
u32 v = agent.readReg(regNum);
|
||||
return pack(v);
|
||||
}
|
||||
|
||||
std::string writeReg(const std::string& pkt)
|
||||
{
|
||||
u32 regNum;
|
||||
char vstr[9];
|
||||
if (sscanf(pkt.c_str(), "P%x=%8s", ®Num, vstr) != 2)
|
||||
{
|
||||
WARN_LOG(COMMON, "writeReg: invalid packet %s", pkt.c_str());
|
||||
return "E01";
|
||||
}
|
||||
agent.writeReg(regNum, unpack(vstr, 8));
|
||||
return "OK";
|
||||
}
|
||||
|
||||
std::vector<std::string> query(const std::string& pkt)
|
||||
{
|
||||
if (pkt == "qC")
|
||||
// Return the current thread ID. 0 is "any thread"
|
||||
return { "QC0.01" };
|
||||
else if (pkt.rfind("qCRC", 0) == 0)
|
||||
{
|
||||
WARN_LOG(COMMON, "CRC compute not supported %s", pkt.c_str());
|
||||
return { "E01" };
|
||||
}
|
||||
else if (pkt == "qfThreadInfo")
|
||||
// Obtain a list of all active thread IDs (first call)
|
||||
return { "m0" };
|
||||
else if (pkt == "qsThreadInfo")
|
||||
// Obtain a list of all active thread IDs (subsequent calls -> 'l' == end of list)
|
||||
return { "l" };
|
||||
else if (pkt.rfind("qGetTLSAddr:", 0) == 0)
|
||||
// Fetch the address associated with thread local storage
|
||||
return { "" };
|
||||
else if (pkt.rfind("qL", 0) == 0)
|
||||
// Obtain thread information. deprecated
|
||||
return { "qM001" };
|
||||
else if (pkt == "qOffsets")
|
||||
// Get section offsets. Not supported
|
||||
return { "" };
|
||||
else if (pkt.rfind("qP", 0) == 0)
|
||||
// Returns information on thread. deprecated
|
||||
return { "" };
|
||||
else if (pkt.rfind("qRcmd,", 0) == 0)
|
||||
{
|
||||
std::string customCmd;
|
||||
for (const char *p = pkt.c_str() + 6; *p != '\0'; p += 2)
|
||||
customCmd += (char)unpack(p, 2);
|
||||
DEBUG_LOG(COMMON, "query: custom command %s", customCmd.c_str());
|
||||
if (customCmd == "reset")
|
||||
restart();
|
||||
else if (customCmd == "stack")
|
||||
{
|
||||
u32 len;
|
||||
const u32 *data = agent.getStack(len);
|
||||
len /= 4;
|
||||
|
||||
#if _MSC_VER // Non-const array size is a GCC extension
|
||||
assert((len * 9 * 2 + 1) < MAX_PACKET_LEN);
|
||||
char reply[MAX_PACKET_LEN];
|
||||
#else
|
||||
char reply[len * 9 * 2 + 1];
|
||||
#endif
|
||||
char *r = reply;
|
||||
for (u32 i = 0; i < len; i++)
|
||||
{
|
||||
char n[10];
|
||||
snprintf(n, sizeof(n), "%08x ", *data++);
|
||||
for (char *p = n; *p != 0; p++)
|
||||
{
|
||||
*r++ = packnb((*p >> 4) & 0xf);
|
||||
*r++ = packnb(*p & 0xf);
|
||||
}
|
||||
}
|
||||
*r = 0;
|
||||
return { reply };
|
||||
}
|
||||
else
|
||||
return { "" };
|
||||
}
|
||||
else if (pkt.rfind("qSupported", 0) == 0)
|
||||
{
|
||||
// Tell the remote stub about features supported by GDB,
|
||||
// and query the stub for features it supports
|
||||
char qsupported[128];
|
||||
snprintf(qsupported, 128, "PacketSize=%i;vContSupported+", MAX_PACKET_LEN);
|
||||
return { qsupported };
|
||||
}
|
||||
else if (pkt.rfind("qSymbol:", 0) == 0)
|
||||
// Notify the target that GDB is prepared to serve symbol lookup requests
|
||||
return { "OK" };
|
||||
else if (pkt.rfind("qThreadExtraInfo,", 0) == 0)
|
||||
{
|
||||
// Obtain from the target OS a printable string description of thread attributes
|
||||
char s[19];
|
||||
snprintf(s, sizeof(s), "%02x%02x%02x%02x%02x%02x%02x%02x%02x", 'R', 'u', 'n', 'n', 'a', 'b', 'l', 'e', 0);
|
||||
return { std::string(s, 18) };
|
||||
}
|
||||
else if (pkt.rfind("qXfer:", 0) == 0)
|
||||
// Read uninterpreted bytes from the target’s special data area identified by the keyword object
|
||||
return { "" };
|
||||
else if (pkt.rfind("qAttached", 0) == 0)
|
||||
// Return an indication of whether the remote server attached to an existing process
|
||||
// or created a new process
|
||||
return { "1" }; // existing process
|
||||
else if (pkt.rfind("qTfV", 0) == 0)
|
||||
// request data about trace state variables
|
||||
return { "" };
|
||||
else if (pkt.rfind("qTfP", 0) == 0)
|
||||
// request data about tracepoints
|
||||
return { "" };
|
||||
else if (pkt.rfind("qTStatus", 0) == 0)
|
||||
// Ask the stub if there is a trace experiment running right now
|
||||
return { "" };
|
||||
WARN_LOG(COMMON, "query not supported %s", pkt.c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string> set(const std::string& pkt)
|
||||
{
|
||||
if (pkt.rfind("QPassSignals:", 0) == 0)
|
||||
// Passing signals not supported
|
||||
return { "" };
|
||||
else if (pkt.rfind("QTDP", 0) == 0
|
||||
|| pkt.rfind("QFrame", 0) == 0
|
||||
|| pkt.rfind("QTStart", 0) == 0
|
||||
|| pkt.rfind("QTStop", 0) == 0
|
||||
|| pkt.rfind("QTinit", 0) == 0
|
||||
|| pkt.rfind("QTro", 0) == 0)
|
||||
// No tracepoint feature supported
|
||||
return { "" };
|
||||
WARN_LOG(COMMON, "set not supported %s", pkt.c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string> vpacket(const std::string& pkt)
|
||||
{
|
||||
if (pkt.rfind("vAttach;", 0) == 0)
|
||||
return { "S05" };
|
||||
else if (pkt.rfind("vCont?", 0) == 0)
|
||||
// supported vCont actions - (c)ontinue, (s)tep, (r)ange-step
|
||||
return { "vCont;c;s;r" };
|
||||
else if (pkt.rfind("vCont", 0) == 0)
|
||||
{
|
||||
std::string vContCmd = pkt.substr(strlen("vCont;"));
|
||||
switch (vContCmd[0])
|
||||
{
|
||||
case 'c':
|
||||
doContinue(vContCmd.substr(0, 1));
|
||||
return {};
|
||||
case 's':
|
||||
return { step(EXCEPT_NONE) };
|
||||
case 'r':
|
||||
{
|
||||
std::vector<std::string> replies;
|
||||
u32 from, to;
|
||||
if (sscanf(vContCmd.c_str(), "r%x,%x", &from, &to) == 2)
|
||||
{
|
||||
auto v = stepRange(from, to);
|
||||
replies.insert(replies.end(), v.begin(), v.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(COMMON, "Unsupported vCont:r format %s", pkt.c_str());
|
||||
doContinue("c");
|
||||
}
|
||||
return replies;
|
||||
}
|
||||
default:
|
||||
WARN_LOG(COMMON, "vCont action not supported %s", pkt.c_str());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
else if (pkt.rfind("vFile:", 0) == 0)
|
||||
// not supported
|
||||
return { "" };
|
||||
else if (pkt.rfind("vFlashErase:", 0) == 0)
|
||||
// not supported
|
||||
return { "E01" };
|
||||
else if (pkt.rfind("vFlashWrite:", 0) == 0)
|
||||
// not supported
|
||||
return { "E01" };
|
||||
else if (pkt.rfind("vFlashDone:", 0) == 0)
|
||||
// not supported
|
||||
return { "E01" };
|
||||
else if (pkt.rfind("vRun;", 0) == 0)
|
||||
{
|
||||
if (pkt != "vRun;")
|
||||
WARN_LOG(COMMON, "unexpected vRun args ignored: %s", pkt.c_str());
|
||||
agent.restart();
|
||||
return { "S05" };
|
||||
}
|
||||
else if (pkt.rfind("vKill", 0) == 0)
|
||||
{
|
||||
agent.kill();
|
||||
return { "OK" };
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pkt != "vMustReplyEmpty")
|
||||
WARN_LOG(COMMON, "unknown v packet: %s", pkt.c_str());
|
||||
return { "" };
|
||||
}
|
||||
}
|
||||
|
||||
void restart() {
|
||||
agent.restart();
|
||||
}
|
||||
|
||||
std::string step(u32 what = 0)
|
||||
{
|
||||
try {
|
||||
agent.step();
|
||||
return "S05";
|
||||
} catch (const FlycastException& e) {
|
||||
throw Error(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> stepRange(u32 from, u32 to)
|
||||
{
|
||||
try {
|
||||
if (from + 2 >= to)
|
||||
agent.step();
|
||||
else
|
||||
agent.stepRange(from, to);
|
||||
return { "OK", "S05" };
|
||||
} catch (const FlycastException& e) {
|
||||
throw Error(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::string insertMatchpoint(const std::string& pkt)
|
||||
{
|
||||
u32 type;
|
||||
u32 addr;
|
||||
u32 len;
|
||||
if (sscanf(pkt.c_str(), "Z%1d,%x,%1d", &type, &addr, &len) != 3) {
|
||||
WARN_LOG(COMMON, "insertMatchpoint: unknown packet: %s", pkt.c_str());
|
||||
return "E01";
|
||||
}
|
||||
switch (type) {
|
||||
case DebugAgent::Breakpoint::BP_TYPE_SOFTWARE_BREAK: // soft bp
|
||||
if (agent.insertMatchpoint(DebugAgent::Breakpoint::BP_TYPE_SOFTWARE_BREAK,
|
||||
addr, len))
|
||||
return "OK";
|
||||
else
|
||||
return "E01";
|
||||
break;
|
||||
case DebugAgent::Breakpoint::BP_TYPE_HARDWARE_BREAK: // hardware bp
|
||||
return "";
|
||||
break;
|
||||
case DebugAgent::Breakpoint::BP_TYPE_WRITE_WATCHPOINT: // write watchpoint
|
||||
return "";
|
||||
break;
|
||||
case DebugAgent::Breakpoint::BP_TYPE_READ_WATCHPOINT: // read watchpoint
|
||||
return "";
|
||||
break;
|
||||
case DebugAgent::Breakpoint::BP_TYPE_ACCESS_WATCHPOINT: // access watchpoint
|
||||
return "";
|
||||
break;
|
||||
default:
|
||||
return "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string removeMatchpoint(const std::string& pkt)
|
||||
{
|
||||
u32 type;
|
||||
u32 addr;
|
||||
u32 len;
|
||||
if (sscanf(pkt.c_str(), "z%1d,%x,%1d", &type, &addr, &len) != 3) {
|
||||
WARN_LOG(COMMON, "removeMatchpoint: unknown packet: %s", pkt.c_str());
|
||||
return "E01";
|
||||
}
|
||||
switch (type) {
|
||||
case 0: // soft bp
|
||||
if (agent.removeMatchpoint(DebugAgent::Breakpoint::BP_TYPE_SOFTWARE_BREAK,
|
||||
addr, len))
|
||||
return "OK";
|
||||
else
|
||||
return "E01";
|
||||
break;
|
||||
case 1: // hardware bp
|
||||
return "";
|
||||
break;
|
||||
case 2: // write watchpoint
|
||||
return "";
|
||||
break;
|
||||
case 3: // read watchpoint
|
||||
return "";
|
||||
break;
|
||||
case 4: // access watchpoint
|
||||
return "";
|
||||
break;
|
||||
default:
|
||||
return "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string interrupt()
|
||||
{
|
||||
u32 signal = agentInterrupt();
|
||||
char s[10];
|
||||
snprintf(s, sizeof(s), "S%02x", signal);
|
||||
return s;
|
||||
}
|
||||
|
||||
char packnb(u8 b)
|
||||
{
|
||||
if (b < 10)
|
||||
return '0' + b;
|
||||
else
|
||||
return 'a' + b - 10;
|
||||
}
|
||||
|
||||
std::string packb(u8 v)
|
||||
{
|
||||
std::string s(1, packnb((v >> 4) & 0xf));
|
||||
s += packnb(v & 0xf);
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string pack(u32 v) {
|
||||
return packb(v & 0xff) + packb((v >> 8) & 0xff)
|
||||
+ packb((v >> 16) & 0xff) + packb((v >> 24) & 0xff);
|
||||
}
|
||||
|
||||
u32 agentInterrupt()
|
||||
{
|
||||
try {
|
||||
return agent.interrupt();
|
||||
} catch (const FlycastException& e) {
|
||||
throw Error(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void clientConnected(Connection::Ptr connection) {
|
||||
attached = true;
|
||||
this->connection = connection;
|
||||
agentInterrupt();
|
||||
}
|
||||
|
||||
static void emuEventCallback(Event event, void *arg)
|
||||
{
|
||||
GdbServer *gdbServer = static_cast<GdbServer*>(arg);
|
||||
switch (event)
|
||||
{
|
||||
case Event::Resume:
|
||||
try {
|
||||
if (!gdbServer->isRunning())
|
||||
gdbServer->run();
|
||||
} catch (const GdbServer::Error& e) {
|
||||
ERROR_LOG(COMMON, "%s", e.what());
|
||||
}
|
||||
break;
|
||||
case Event::Terminate:
|
||||
gdbServer->stop();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool attached = false;
|
||||
bool postDebugTrapNeeded = false;
|
||||
std::thread thread;
|
||||
std::unique_ptr<asio::io_context> io_context;
|
||||
int port = DEFAULT_PORT;
|
||||
Connection::Ptr connection;
|
||||
friend class TcpAcceptor;
|
||||
friend class Connection;
|
||||
|
||||
public:
|
||||
DebugAgent agent;
|
||||
};
|
||||
|
||||
static GdbServer gdbServer;
|
||||
|
||||
void TcpAcceptor::handleAccept(Connection::Ptr newConnection, const std::error_code& error)
|
||||
{
|
||||
if (!error) {
|
||||
server.clientConnected(newConnection);
|
||||
newConnection->start();
|
||||
}
|
||||
start();
|
||||
}
|
||||
|
||||
void Connection::handlePacket(const std::error_code& ec, size_t len)
|
||||
{
|
||||
std::string msg = message.substr(0, len);
|
||||
message = message.substr(len);
|
||||
if (ec || len == 0)
|
||||
{
|
||||
// terminate the connection
|
||||
if (ec != asio::error::eof && ec != asio::error::operation_aborted)
|
||||
WARN_LOG(NETWORK, "Read error %s", ec.message().c_str());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (msg[0] == '\03') { // break
|
||||
send(server.handleCommand(msg));
|
||||
}
|
||||
else if (msg[0] != '$') {
|
||||
// Ignore unexpected chars
|
||||
}
|
||||
else
|
||||
{
|
||||
u8 cksum = 0;
|
||||
for (unsigned i = 1; i < len - 3; i++)
|
||||
cksum += (u8)msg[i];
|
||||
if (cksum != (unpack(msg[len - 2]) << 4 | unpack(msg[len - 1])))
|
||||
{
|
||||
// Invalid checksum
|
||||
WARN_LOG(COMMON, "Connection::handlePacket: invalid checksum: [%s]", msg.c_str());
|
||||
send("-");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Positive ack
|
||||
std::string reply = "+";
|
||||
reply += server.handleCommand(msg.substr(1, msg.length() - 4));
|
||||
send(reply);
|
||||
}
|
||||
}
|
||||
start();
|
||||
} catch (...) {
|
||||
// terminate the connection
|
||||
}
|
||||
}
|
||||
|
||||
void init(int port)
|
||||
{
|
||||
gdbServer.init(port);
|
||||
}
|
||||
|
||||
void term()
|
||||
{
|
||||
gdbServer.term();
|
||||
}
|
||||
|
||||
void debugTrap(u32 event)
|
||||
{
|
||||
gdbServer.debugTrap(event);
|
||||
}
|
||||
|
||||
void subroutineCall()
|
||||
{
|
||||
gdbServer.agent.subroutineCall();
|
||||
}
|
||||
|
||||
void subroutineReturn()
|
||||
{
|
||||
gdbServer.agent.subroutineReturn();
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
Copyright 2021 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace debugger {
|
||||
|
||||
// exception thrown in response to trap
|
||||
struct Stop { };
|
||||
|
||||
static const int DEFAULT_PORT = 3263;
|
||||
|
||||
#ifdef GDB_SERVER
|
||||
|
||||
void init(int port);
|
||||
void term();
|
||||
void run();
|
||||
void debugTrap(u32 event);
|
||||
void subroutineCall();
|
||||
void subroutineReturn();
|
||||
|
||||
#else
|
||||
static inline void init(int port) {}
|
||||
static inline void term() {}
|
||||
static inline void run() {}
|
||||
static inline void debugTrap(u32 event) {}
|
||||
static inline void subroutineCall() {}
|
||||
static inline void subroutineReturn() {}
|
||||
#endif
|
||||
}
|
||||
Submodule core/deps/DreamPicoPort-API deleted from e06d9f6c59.
Submodule core/deps/SDL deleted from 00d4f1c2c4.
Submodule core/deps/Spout deleted from 2110a918e7.
Submodule core/deps/Syphon deleted from 53ec412a78.
Submodule core/deps/Vulkan-Headers deleted from b5c8f99619.
Submodule core/deps/VulkanMemoryAllocator deleted from 1d8f600fd4.
Submodule core/deps/asio deleted from 3321a1a753.
Submodule core/deps/basis_universal deleted from 9bebe16726.
Submodule core/deps/breakpad deleted from 6f3c4af788.
+76
-91
@@ -2,31 +2,41 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "deps/coreio/coreio.h"
|
||||
#include "cdipsr.h"
|
||||
|
||||
// Global variables
|
||||
|
||||
unsigned long temp_value;
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
unsigned long ask_type(hostfs::File *fsource, long header_position)
|
||||
#define FILE core_file
|
||||
#define fread(buff,sz,cnt,fc) core_fread(fc,buff,sz*cnt)
|
||||
#define fseek core_fseek
|
||||
#define ftell core_ftell
|
||||
|
||||
unsigned long ask_type(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);
|
||||
fseek(fsource, header_position, SEEK_SET);
|
||||
fread(&temp_value, 4, 1, fsource);
|
||||
if (temp_value != 0)
|
||||
fsource->seek(8, SEEK_CUR); // extra data (DJ 3.00.780 and up)
|
||||
fsource->seek(24, SEEK_CUR);
|
||||
fsource->read(&filename_length, 1, 1);
|
||||
fsource->seek(filename_length, SEEK_CUR);
|
||||
fsource->seek(19, SEEK_CUR);
|
||||
fsource->read(&temp_value, 4, 1);
|
||||
fseek(fsource, 8, SEEK_CUR); // extra data (DJ 3.00.780 and up)
|
||||
fseek(fsource, 24, SEEK_CUR);
|
||||
fread(&filename_length, 1, 1, fsource);
|
||||
fseek(fsource, filename_length, SEEK_CUR);
|
||||
fseek(fsource, 19, SEEK_CUR);
|
||||
fread(&temp_value, 4, 1, fsource);
|
||||
if (temp_value == 0x80000000)
|
||||
fsource->seek(8, SEEK_CUR); // DJ4
|
||||
fsource->seek(16, SEEK_CUR);
|
||||
fsource->read(&track_mode, 4, 1);
|
||||
fsource->seek(header_position, SEEK_SET);
|
||||
fseek(fsource, 8, SEEK_CUR); // DJ4
|
||||
fseek(fsource, 16, SEEK_CUR);
|
||||
fread(&track_mode, 4, 1, fsource);
|
||||
fseek(fsource, header_position, SEEK_SET);
|
||||
return (track_mode);
|
||||
}
|
||||
|
||||
@@ -34,125 +44,100 @@ uint32_t temp_value = 0;
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
bool CDI_read_track (hostfs::File *fsource, image_s *image, track_s *track)
|
||||
void CDI_read_track (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);
|
||||
fread(&temp_value, 4, 1, fsource);
|
||||
if (temp_value != 0)
|
||||
fsource->seek(8, SEEK_CUR); // extra data (DJ 3.00.780 and up)
|
||||
fseek(fsource, 8, SEEK_CUR); // extra data (DJ 3.00.780 and up)
|
||||
|
||||
fsource->read(¤t_start_mark, 10, 1);
|
||||
if (memcmp(TRACK_START_MARK, current_start_mark, 10)) {
|
||||
printf("CDI_read_track: Unsupported format: Could not find the track start mark\n");
|
||||
return false;
|
||||
}
|
||||
fread(¤t_start_mark, 10, 1, fsource);
|
||||
if (memcmp(TRACK_START_MARK, current_start_mark, 10)) printf( "Unsupported format: Could not find the track start mark");
|
||||
|
||||
fsource->read(¤t_start_mark, 10, 1);
|
||||
if (memcmp(TRACK_START_MARK, current_start_mark, 10)) {
|
||||
printf("CDI_read_track: Unsupported format: Could not find the track start mark\n");
|
||||
return false;
|
||||
}
|
||||
fread(¤t_start_mark, 10, 1, fsource);
|
||||
if (memcmp(TRACK_START_MARK, current_start_mark, 10)) printf( "Unsupported format: Could not find the track start mark");
|
||||
|
||||
fsource->seek(4, SEEK_CUR);
|
||||
fsource->read(&track->filename_length, 1, 1);
|
||||
fsource->seek(track->filename_length, SEEK_CUR);
|
||||
fsource->seek(11, SEEK_CUR);
|
||||
fsource->seek(4, SEEK_CUR);
|
||||
fsource->seek(4, SEEK_CUR);
|
||||
fsource->read(&temp_value, 4, 1);
|
||||
fseek(fsource, 4, SEEK_CUR);
|
||||
fread(&track->filename_length, 1, 1, fsource);
|
||||
fseek(fsource, track->filename_length, SEEK_CUR);
|
||||
fseek(fsource, 11, SEEK_CUR);
|
||||
fseek(fsource, 4, SEEK_CUR);
|
||||
fseek(fsource, 4, SEEK_CUR);
|
||||
fread(&temp_value, 4, 1, fsource);
|
||||
if (temp_value == 0x80000000)
|
||||
fsource->seek(8, SEEK_CUR); // DJ4
|
||||
fsource->seek(2, SEEK_CUR);
|
||||
fsource->read(&track->pregap_length, 4, 1);
|
||||
fsource->read(&track->length, 4, 1);
|
||||
fsource->seek(6, SEEK_CUR);
|
||||
fsource->read(&track->mode, 4, 1);
|
||||
fsource->seek(12, SEEK_CUR);
|
||||
fsource->read(&track->start_lba, 4, 1);
|
||||
fsource->read(&track->total_length, 4, 1);
|
||||
fsource->seek(16, SEEK_CUR);
|
||||
fsource->read(&track->sector_size_value, 4, 1);
|
||||
fseek(fsource, 8, SEEK_CUR); // DJ4
|
||||
fseek(fsource, 2, SEEK_CUR);
|
||||
fread(&track->pregap_length, 4, 1, fsource);
|
||||
fread(&track->length, 4, 1, fsource);
|
||||
fseek(fsource, 6, SEEK_CUR);
|
||||
fread(&track->mode, 4, 1, fsource);
|
||||
fseek(fsource, 12, SEEK_CUR);
|
||||
fread(&track->start_lba, 4, 1, fsource);
|
||||
fread(&track->total_length, 4, 1, fsource);
|
||||
fseek(fsource, 16, SEEK_CUR);
|
||||
fread(&track->sector_size_value, 4, 1, fsource);
|
||||
|
||||
switch(track->sector_size_value)
|
||||
{
|
||||
case 0 : track->sector_size = 2048; break;
|
||||
case 1 : track->sector_size = 2336; break;
|
||||
case 2 : track->sector_size = 2352; break;
|
||||
case 4 : track->sector_size = 2448; break;
|
||||
default:
|
||||
printf("CDI_read_track: Unsupported sector size. value %ld\n", track->sector_size_value);
|
||||
return false;
|
||||
default: printf("Unsupported sector size");
|
||||
}
|
||||
|
||||
if (track->mode > 2) {
|
||||
printf("CDI_read_track: Unsupported format: Track mode not supported\n");
|
||||
return false;
|
||||
}
|
||||
if (track->mode > 2) printf( "Unsupported format: Track mode not supported");
|
||||
|
||||
fsource->seek(29, SEEK_CUR);
|
||||
fseek(fsource, 29, SEEK_CUR);
|
||||
if (image->version != CDI_V2)
|
||||
{
|
||||
fsource->seek(5, SEEK_CUR);
|
||||
fsource->read(&temp_value, 4, 1);
|
||||
{
|
||||
fseek(fsource, 5, SEEK_CUR);
|
||||
fread(&temp_value, 4, 1, fsource);
|
||||
if (temp_value == 0xffffffff)
|
||||
fsource->seek(78, SEEK_CUR); // extra data (DJ 3.00.780 and up)
|
||||
}
|
||||
return true;
|
||||
fseek(fsource, 78, SEEK_CUR); // extra data (DJ 3.00.780 and up)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CDI_skip_next_session (hostfs::File *fsource, image_s *image)
|
||||
void CDI_skip_next_session (FILE *fsource, image_s *image)
|
||||
{
|
||||
fsource->seek(4, SEEK_CUR);
|
||||
fsource->seek(8, SEEK_CUR);
|
||||
if (image->version != CDI_V2) fsource->seek(1, SEEK_CUR);
|
||||
fseek(fsource, 4, SEEK_CUR);
|
||||
fseek(fsource, 8, SEEK_CUR);
|
||||
if (image->version != CDI_V2) fseek(fsource, 1, SEEK_CUR);
|
||||
}
|
||||
|
||||
bool CDI_get_tracks (hostfs::File *fsource, image_s *image)
|
||||
void CDI_get_tracks (FILE *fsource, image_s *image)
|
||||
{
|
||||
return fsource->read(&image->tracks, 2, 1) == 1;
|
||||
fread(&image->tracks, 2, 1, fsource);
|
||||
}
|
||||
|
||||
bool CDI_init (hostfs::File *fsource, image_s *image, const char *fsourcename)
|
||||
void CDI_init (FILE *fsource, image_s *image, char *fsourcename)
|
||||
{
|
||||
fsource->seek(0, SEEK_END);
|
||||
image->length = fsource->tell();
|
||||
image->length = core_fsize(fsource);
|
||||
|
||||
if (image->length < 8)
|
||||
{
|
||||
printf("%s: Image file is too short\n", fsourcename);
|
||||
return false;
|
||||
}
|
||||
if (image->length < 8) printf( "Image file is too short");
|
||||
|
||||
fsource->seek(image->length-8, SEEK_SET);
|
||||
if (fsource->read(&image->version, 4, 1) != 1
|
||||
|| fsource->read(&image->header_offset, 4, 1) != 1)
|
||||
return false;
|
||||
fseek(fsource, image->length-8, SEEK_SET);
|
||||
fread(&image->version, 4, 1, fsource);
|
||||
fread(&image->header_offset, 4, 1, fsource);
|
||||
|
||||
if ((image->version != CDI_V2 && image->version != CDI_V3 && image->version != CDI_V35)
|
||||
|| image->header_offset == 0)
|
||||
{
|
||||
printf("%s: Bad image format\n", fsourcename);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
// if (errno != 0) printf( fsourcename);
|
||||
if (image->header_offset == 0) printf( "Bad image format");
|
||||
}
|
||||
|
||||
bool CDI_get_sessions (hostfs::File *fsource, image_s *image)
|
||||
void CDI_get_sessions (FILE *fsource, image_s *image)
|
||||
{
|
||||
#ifndef DEBUG_CDI
|
||||
if (image->version == CDI_V35)
|
||||
fsource->seek((image->length - image->header_offset), SEEK_SET);
|
||||
fseek(fsource, (image->length - image->header_offset), SEEK_SET);
|
||||
else
|
||||
fsource->seek(image->header_offset, SEEK_SET);
|
||||
fseek(fsource, image->header_offset, SEEK_SET);
|
||||
|
||||
#else
|
||||
fsource->seek(0L, SEEK_SET);
|
||||
fseek(fsource, 0L, SEEK_SET);
|
||||
#endif
|
||||
return fsource->read(&image->sessions, 2, 1) == 1;
|
||||
fread(&image->sessions, 2, 1, fsource);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef __CDI_H__
|
||||
#define __CDI_H__
|
||||
|
||||
#include "../../oslib/storage.h"
|
||||
#include "deps/coreio/coreio.h"
|
||||
|
||||
/* Basic structures */
|
||||
|
||||
@@ -38,11 +38,11 @@ typedef struct track_s
|
||||
#define CDI_V3 0x80000005
|
||||
#define CDI_V35 0x80000006
|
||||
|
||||
unsigned long ask_type(hostfs::File *fsource, long header_position);
|
||||
bool CDI_init(hostfs::File *fsource, image_s *image, const char *fsourcename);
|
||||
bool CDI_get_sessions(hostfs::File *fsource, image_s *image);
|
||||
bool CDI_get_tracks(hostfs::File *fsource, image_s *image);
|
||||
bool CDI_read_track(hostfs::File *fsource, image_s *image, track_s *track);
|
||||
void CDI_skip_next_session(hostfs::File *fsource, image_s *image);
|
||||
unsigned long ask_type(core_file *fsource, long header_position);
|
||||
void CDI_init(core_file *fsource, image_s *image, char *fsourcename);
|
||||
void CDI_get_sessions(core_file *fsource, image_s *image);
|
||||
void CDI_get_tracks(core_file *fsource, image_s *image);
|
||||
void CDI_read_track(core_file *fsource, image_s *image, track_s *track);
|
||||
void CDI_skip_next_session(core_file *fsource, image_s *image);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,351 @@
|
||||
/***************************************************************************
|
||||
|
||||
chd.h
|
||||
|
||||
MAME Compressed Hunks of Data file format
|
||||
|
||||
****************************************************************************
|
||||
|
||||
Copyright Aaron Giles
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name 'MAME' nor the names of its contributors may be
|
||||
used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __CHD_H__
|
||||
#define __CHD_H__
|
||||
|
||||
#include "coretypes.h"
|
||||
#include "deps/coreio/coreio.h"
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
|
||||
Compressed Hunks of Data header format. All numbers are stored in
|
||||
Motorola (big-endian) byte ordering. The header is 76 (V1) or 80 (V2)
|
||||
bytes long.
|
||||
|
||||
V1 header:
|
||||
|
||||
[ 0] char tag[8]; // 'MComprHD'
|
||||
[ 8] UINT32 length; // length of header (including tag and length fields)
|
||||
[ 12] UINT32 version; // drive format version
|
||||
[ 16] UINT32 flags; // flags (see below)
|
||||
[ 20] UINT32 compression; // compression type
|
||||
[ 24] UINT32 hunksize; // 512-byte sectors per hunk
|
||||
[ 28] UINT32 totalhunks; // total # of hunks represented
|
||||
[ 32] UINT32 cylinders; // number of cylinders on hard disk
|
||||
[ 36] UINT32 heads; // number of heads on hard disk
|
||||
[ 40] UINT32 sectors; // number of sectors on hard disk
|
||||
[ 44] UINT8 md5[16]; // MD5 checksum of raw data
|
||||
[ 60] UINT8 parentmd5[16]; // MD5 checksum of parent file
|
||||
[ 76] (V1 header length)
|
||||
|
||||
V2 header:
|
||||
|
||||
[ 0] char tag[8]; // 'MComprHD'
|
||||
[ 8] UINT32 length; // length of header (including tag and length fields)
|
||||
[ 12] UINT32 version; // drive format version
|
||||
[ 16] UINT32 flags; // flags (see below)
|
||||
[ 20] UINT32 compression; // compression type
|
||||
[ 24] UINT32 hunksize; // seclen-byte sectors per hunk
|
||||
[ 28] UINT32 totalhunks; // total # of hunks represented
|
||||
[ 32] UINT32 cylinders; // number of cylinders on hard disk
|
||||
[ 36] UINT32 heads; // number of heads on hard disk
|
||||
[ 40] UINT32 sectors; // number of sectors on hard disk
|
||||
[ 44] UINT8 md5[16]; // MD5 checksum of raw data
|
||||
[ 60] UINT8 parentmd5[16]; // MD5 checksum of parent file
|
||||
[ 76] UINT32 seclen; // number of bytes per sector
|
||||
[ 80] (V2 header length)
|
||||
|
||||
V3 header:
|
||||
|
||||
[ 0] char tag[8]; // 'MComprHD'
|
||||
[ 8] UINT32 length; // length of header (including tag and length fields)
|
||||
[ 12] UINT32 version; // drive format version
|
||||
[ 16] UINT32 flags; // flags (see below)
|
||||
[ 20] UINT32 compression; // compression type
|
||||
[ 24] UINT32 totalhunks; // total # of hunks represented
|
||||
[ 28] UINT64 logicalbytes; // logical size of the data (in bytes)
|
||||
[ 36] UINT64 metaoffset; // offset to the first blob of metadata
|
||||
[ 44] UINT8 md5[16]; // MD5 checksum of raw data
|
||||
[ 60] UINT8 parentmd5[16]; // MD5 checksum of parent file
|
||||
[ 76] UINT32 hunkbytes; // number of bytes per hunk
|
||||
[ 80] UINT8 sha1[20]; // SHA1 checksum of raw data
|
||||
[100] UINT8 parentsha1[20];// SHA1 checksum of parent file
|
||||
[120] (V3 header length)
|
||||
|
||||
V4 header:
|
||||
|
||||
[ 0] char tag[8]; // 'MComprHD'
|
||||
[ 8] UINT32 length; // length of header (including tag and length fields)
|
||||
[ 12] UINT32 version; // drive format version
|
||||
[ 16] UINT32 flags; // flags (see below)
|
||||
[ 20] UINT32 compression; // compression type
|
||||
[ 24] UINT32 totalhunks; // total # of hunks represented
|
||||
[ 28] UINT64 logicalbytes; // logical size of the data (in bytes)
|
||||
[ 36] UINT64 metaoffset; // offset to the first blob of metadata
|
||||
[ 44] UINT32 hunkbytes; // number of bytes per hunk
|
||||
[ 48] UINT8 sha1[20]; // combined raw+meta SHA1
|
||||
[ 68] UINT8 parentsha1[20];// combined raw+meta SHA1 of parent
|
||||
[ 88] UINT8 rawsha1[20]; // raw data SHA1
|
||||
[108] (V4 header length)
|
||||
|
||||
Flags:
|
||||
0x00000001 - set if this drive has a parent
|
||||
0x00000002 - set if this drive allows writes
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
CONSTANTS
|
||||
***************************************************************************/
|
||||
|
||||
/* header information */
|
||||
#define CHD_HEADER_VERSION 4
|
||||
#define CHD_V1_HEADER_SIZE 76
|
||||
#define CHD_V2_HEADER_SIZE 80
|
||||
#define CHD_V3_HEADER_SIZE 120
|
||||
#define CHD_V4_HEADER_SIZE 108
|
||||
#define CHD_MAX_HEADER_SIZE CHD_V4_HEADER_SIZE
|
||||
|
||||
/* checksumming information */
|
||||
#define CHD_MD5_BYTES 16
|
||||
#define CHD_SHA1_BYTES 20
|
||||
|
||||
/* CHD global flags */
|
||||
#define CHDFLAGS_HAS_PARENT 0x00000001
|
||||
#define CHDFLAGS_IS_WRITEABLE 0x00000002
|
||||
#define CHDFLAGS_UNDEFINED 0xfffffffc
|
||||
|
||||
/* compression types */
|
||||
#define CHDCOMPRESSION_NONE 0
|
||||
#define CHDCOMPRESSION_ZLIB 1
|
||||
#define CHDCOMPRESSION_ZLIB_PLUS 2
|
||||
#define CHDCOMPRESSION_AV 3
|
||||
|
||||
/* A/V codec configuration parameters */
|
||||
#define AV_CODEC_COMPRESS_CONFIG 1
|
||||
#define AV_CODEC_DECOMPRESS_CONFIG 2
|
||||
|
||||
/* metadata parameters */
|
||||
#define CHDMETATAG_WILDCARD 0
|
||||
#define CHD_METAINDEX_APPEND ((UINT32)-1)
|
||||
|
||||
/* metadata flags */
|
||||
#define CHD_MDFLAGS_CHECKSUM 0x01 /* indicates data is checksummed */
|
||||
|
||||
/* standard hard disk metadata */
|
||||
#define HARD_DISK_METADATA_TAG 0x47444444 /* 'GDDD' */
|
||||
#define HARD_DISK_METADATA_FORMAT "CYLS:%d,HEADS:%d,SECS:%d,BPS:%d"
|
||||
|
||||
/* hard disk identify information */
|
||||
#define HARD_DISK_IDENT_METADATA_TAG 0x49444e54 /* 'IDNT' */
|
||||
|
||||
/* hard disk key information */
|
||||
#define HARD_DISK_KEY_METADATA_TAG 0x4b455920 /* 'KEY ' */
|
||||
|
||||
/* pcmcia CIS information */
|
||||
#define PCMCIA_CIS_METADATA_TAG 0x43495320 /* 'CIS ' */
|
||||
|
||||
/* standard CD-ROM metadata */
|
||||
#define CDROM_OLD_METADATA_TAG 0x43484344 /* 'CHCD' */
|
||||
#define CDROM_TRACK_METADATA_TAG 0x43485452 /* 'CHTR' */
|
||||
#define CDROM_TRACK_METADATA_FORMAT "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d"
|
||||
#define CDROM_TRACK_METADATA2_TAG 0x43485432 /* 'CHT2' */
|
||||
#define CDROM_TRACK_METADATA2_FORMAT "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d PREGAP:%d PGTYPE:%s PGSUB:%s POSTGAP:%d"
|
||||
|
||||
/* standard A/V metadata */
|
||||
#define AV_METADATA_TAG 0x41564156 /* 'AVAV' */
|
||||
#define AV_METADATA_FORMAT "FPS:%d.%06d WIDTH:%d HEIGHT:%d INTERLACED:%d CHANNELS:%d SAMPLERATE:%d"
|
||||
|
||||
/* A/V laserdisc frame metadata */
|
||||
#define AV_LD_METADATA_TAG 0x41564C44 /* 'AVLD' */
|
||||
|
||||
/* CHD open values */
|
||||
#define CHD_OPEN_READ 1
|
||||
#define CHD_OPEN_READWRITE 2
|
||||
|
||||
/* error types */
|
||||
enum _chd_error
|
||||
{
|
||||
CHDERR_NONE,
|
||||
CHDERR_NO_INTERFACE,
|
||||
CHDERR_OUT_OF_MEMORY,
|
||||
CHDERR_INVALID_FILE,
|
||||
CHDERR_INVALID_PARAMETER,
|
||||
CHDERR_INVALID_DATA,
|
||||
CHDERR_FILE_NOT_FOUND,
|
||||
CHDERR_REQUIRES_PARENT,
|
||||
CHDERR_FILE_NOT_WRITEABLE,
|
||||
CHDERR_READ_ERROR,
|
||||
CHDERR_WRITE_ERROR,
|
||||
CHDERR_CODEC_ERROR,
|
||||
CHDERR_INVALID_PARENT,
|
||||
CHDERR_HUNK_OUT_OF_RANGE,
|
||||
CHDERR_DECOMPRESSION_ERROR,
|
||||
CHDERR_COMPRESSION_ERROR,
|
||||
CHDERR_CANT_CREATE_FILE,
|
||||
CHDERR_CANT_VERIFY,
|
||||
CHDERR_NOT_SUPPORTED,
|
||||
CHDERR_METADATA_NOT_FOUND,
|
||||
CHDERR_INVALID_METADATA_SIZE,
|
||||
CHDERR_UNSUPPORTED_VERSION,
|
||||
CHDERR_VERIFY_INCOMPLETE,
|
||||
CHDERR_INVALID_METADATA,
|
||||
CHDERR_INVALID_STATE,
|
||||
CHDERR_OPERATION_PENDING,
|
||||
CHDERR_NO_ASYNC_OPERATION,
|
||||
CHDERR_UNSUPPORTED_FORMAT
|
||||
};
|
||||
typedef enum _chd_error chd_error;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
TYPE DEFINITIONS
|
||||
***************************************************************************/
|
||||
|
||||
/* opaque types */
|
||||
typedef struct _chd_file chd_file;
|
||||
|
||||
|
||||
/* extract header structure (NOT the on-disk header structure) */
|
||||
typedef struct _chd_header chd_header;
|
||||
struct _chd_header
|
||||
{
|
||||
UINT32 length; /* length of header data */
|
||||
UINT32 version; /* drive format version */
|
||||
UINT32 flags; /* flags field */
|
||||
UINT32 compression; /* compression type */
|
||||
UINT32 hunkbytes; /* number of bytes per hunk */
|
||||
UINT32 totalhunks; /* total # of hunks represented */
|
||||
UINT64 logicalbytes; /* logical size of the data */
|
||||
UINT64 metaoffset; /* offset in file of first metadata */
|
||||
UINT8 md5[CHD_MD5_BYTES]; /* overall MD5 checksum */
|
||||
UINT8 parentmd5[CHD_MD5_BYTES]; /* overall MD5 checksum of parent */
|
||||
UINT8 sha1[CHD_SHA1_BYTES]; /* overall SHA1 checksum */
|
||||
UINT8 rawsha1[CHD_SHA1_BYTES]; /* SHA1 checksum of raw data */
|
||||
UINT8 parentsha1[CHD_SHA1_BYTES]; /* overall SHA1 checksum of parent */
|
||||
|
||||
UINT32 obsolete_cylinders; /* obsolete field -- do not use! */
|
||||
UINT32 obsolete_sectors; /* obsolete field -- do not use! */
|
||||
UINT32 obsolete_heads; /* obsolete field -- do not use! */
|
||||
UINT32 obsolete_hunksize; /* obsolete field -- do not use! */
|
||||
};
|
||||
|
||||
|
||||
/* structure for returning information about a verification pass */
|
||||
typedef struct _chd_verify_result chd_verify_result;
|
||||
struct _chd_verify_result
|
||||
{
|
||||
UINT8 md5[CHD_MD5_BYTES]; /* overall MD5 checksum */
|
||||
UINT8 sha1[CHD_SHA1_BYTES]; /* overall SHA1 checksum */
|
||||
UINT8 rawsha1[CHD_SHA1_BYTES]; /* SHA1 checksum of raw data */
|
||||
UINT8 metasha1[CHD_SHA1_BYTES]; /* SHA1 checksum of metadata */
|
||||
};
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
FUNCTION PROTOTYPES
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
/* ----- CHD file management ----- */
|
||||
|
||||
/* create a new CHD file fitting the given description */
|
||||
chd_error chd_create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 compression, chd_file *parent);
|
||||
|
||||
/* same as chd_create(), but accepts an already-opened core_file object */
|
||||
chd_error chd_create_file(core_file *file, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 compression, chd_file *parent);
|
||||
|
||||
/* open an existing CHD file */
|
||||
chd_error chd_open(const wchar *filename, int mode, chd_file *parent, chd_file **chd);
|
||||
|
||||
|
||||
/* close a CHD file */
|
||||
void chd_close(chd_file *chd);
|
||||
|
||||
/* return the associated core_file */
|
||||
core_file *chd_core_file(chd_file *chd);
|
||||
|
||||
/* return an error string for the given CHD error */
|
||||
const char *chd_error_string(chd_error err);
|
||||
|
||||
|
||||
|
||||
/* ----- CHD header management ----- */
|
||||
|
||||
/* return a pointer to the extracted CHD header data */
|
||||
const chd_header *chd_get_header(chd_file *chd);
|
||||
|
||||
|
||||
|
||||
|
||||
/* ----- core data read/write ----- */
|
||||
|
||||
/* read one hunk from the CHD file */
|
||||
chd_error chd_read(chd_file *chd, UINT32 hunknum, void *buffer);
|
||||
|
||||
|
||||
|
||||
/* ----- metadata management ----- */
|
||||
|
||||
/* get indexed metadata of a particular sort */
|
||||
chd_error chd_get_metadata(chd_file *chd, UINT32 searchtag, UINT32 searchindex, void *output, UINT32 outputlen, UINT32 *resultlen, UINT32 *resulttag, UINT8 *resultflags);
|
||||
|
||||
|
||||
|
||||
|
||||
/* ----- verification management ----- */
|
||||
|
||||
/* begin verifying a CHD */
|
||||
chd_error chd_verify_begin(chd_file *chd);
|
||||
|
||||
/* verify a single hunk of data */
|
||||
chd_error chd_verify_hunk(chd_file *chd);
|
||||
|
||||
/* finish verifying a CHD, returning the computed MD5 and SHA1 */
|
||||
chd_error chd_verify_finish(chd_file *chd, chd_verify_result *result);
|
||||
|
||||
|
||||
|
||||
/* ----- codec interfaces ----- */
|
||||
|
||||
/* set internal codec parameters */
|
||||
chd_error chd_codec_config(chd_file *chd, int param, void *config);
|
||||
|
||||
/* return a string description of a codec */
|
||||
const char *chd_get_codec_name(UINT32 codec);
|
||||
|
||||
|
||||
#endif /* __CHD_H__ */
|
||||
Loaded 100 of 4886 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user