Author SHA1 Message Date
Stefanos Kornilios Mitsis Poiitidis 8855e13540 shrec: Some ideas for block graphs 2015-09-23 13:42:14 +02:00
4886 changed files with 227202 additions and 1283390 deletions

No files matched your search

-2
View File
@@ -1,2 +0,0 @@
buy_me_a_coffee: hhwtpernwq
custom: ['https://www.paypal.com/paypalme/FlycastEmu']
-41
View File
@@ -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)
-20
View File
@@ -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.
-101
View File
@@ -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' }}
-60
View File
@@ -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}
-214
View File
@@ -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' }}
-36
View File
@@ -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"
-49
View File
@@ -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 }}
-67
View File
@@ -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 != ''
-59
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
-23
View File
@@ -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"
}
}
]
}
+159 -97
View File
@@ -1,107 +1,169 @@
# Flycast
reicast
===========
reicast is a multi-platform Sega Dreamcast emulator.
[![Android CI](https://github.com/flyinghead/flycast/actions/workflows/android.yml/badge.svg)](https://github.com/flyinghead/flycast/actions/workflows/android.yml)
[![C/C++ CI](https://github.com/flyinghead/flycast/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/flyinghead/flycast/actions/workflows/c-cpp.yml)
[![Nintendo Switch CI](https://github.com/flyinghead/flycast/actions/workflows/switch.yml/badge.svg)](https://github.com/flyinghead/flycast/actions/workflows/switch.yml)
[![Windows UWP CI](https://github.com/flyinghead/flycast/actions/workflows/uwp.yml/badge.svg)](https://github.com/flyinghead/flycast/actions/workflows/uwp.yml)
[![BSD CI](https://github.com/flyinghead/flycast/actions/workflows/bsd.yml/badge.svg)](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 ![android](https://flyinghead.github.io/flycast-builds/android.jpg) ![windows](https://flyinghead.github.io/flycast-builds/windows.png) ![linux](https://flyinghead.github.io/flycast-builds/ubuntu.png) ![apple](https://flyinghead.github.io/flycast-builds/apple.png) ![switch](https://flyinghead.github.io/flycast-builds/switch.png) ![xbox](https://flyinghead.github.io/flycast-builds/xbox.png)
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 ![android](https://flyinghead.github.io/flycast-builds/android.jpg)
&emsp;Install Flycast from [**Google Play**](https://play.google.com/store/apps/details?id=com.flycast.emulator).
### Flatpak (Linux ![ubuntu logo](https://flyinghead.github.io/flycast-builds/ubuntu.png))
1. [Set up Flatpak](https://www.flatpak.org/setup/).
2. Install Flycast from [Flathub](https://flathub.org/apps/details/org.flycast.Flycast):
&emsp;`flatpak install -y org.flycast.Flycast`
3. Run Flycast:
&emsp;`flatpak run org.flycast.Flycast`
### Homebrew (macOS ![apple logo](https://flyinghead.github.io/flycast-builds/apple.png))
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
&emsp;See the <a href="https://github.com/flyinghead/homebrew-flycast#readme">Flycast tap</a> for updating, uninstalling, and switching channels.
### iOS
&emsp;Due to persistent harassment from an iOS user, support for this platform has been dropped.
### Xbox One/Series ![xbox logo](https://flyinghead.github.io/flycast-builds/xbox.png)
&emsp;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
&emsp;Right-click the bootstrap script and choose **Open**:
&emsp;`shell/apple/generate_xcode_project.command`
### Windows
&emsp;Double-click the bootstrap script:
&emsp;`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](http://i.imgur.com/nK9exQe.jpg) Android | [![Build Status](https://travis-ci.org/reicast/reicast-emulator.svg?branch=master)](https://travis-ci.org/reicast/reicast-emulator) | [Reicast CI Builds](http://builds.reicast.com)
| ![iOS](http://i.imgur.com/6bvAUUj.png) iOS | [![Build Status](https://app.ship.io/jobs/ttUMMV6QrHOy4_yx/build_status.png)](https://app.ship.io/dashboard#/jobs/9843/history) | *TODO*
| ![Windows](http://i.imgur.com/hAuMmjF.png) Windows | [![Build status](https://ci.appveyor.com/api/projects/status/353mwl73ki74tb58/branch/master?svg=true)](https://ci.appveyor.com/project/skmp/reicast-emulator/branch/master) | [Reicast CI Builds](http://builds.reicast.com)
| ![Linux](http://i.imgur.com/19aAoQD.png) Linux | [![wercker status](https://app.wercker.com/status/bcabca642a2de044c6f58203b975878b/s/master "wercker status")](https://app.wercker.com/project/bykey/bcabca642a2de044c6f58203b975878b) | *TODO*
| ![OSX](http://i.imgur.com/0YoI5Vm.png) 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 :)
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/reicast/reicast-emulator/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
[![Packaging status](https://repology.org/badge/vertical-allrepos/flycast.svg)](https://repology.org/project/flycast/versions)
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+398
View File
@@ -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

+29
View File
@@ -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
View File
@@ -1 +0,0 @@
/version.h
+2 -14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
target_sources(${PROJECT_NAME} PRIVATE
achievements.cpp
achievements.h)
File diff suppressed because it is too large. Load diff
-73
View File
@@ -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);
}
-144
View File
@@ -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);
}
}
-83
View File
@@ -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;
};
-9
View File
@@ -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)
-233
View File
@@ -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);
}
-64
View File
@@ -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;
};
-82
View File
@@ -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);
}
-51
View 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);
-176
View File
@@ -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;
}
-48
View File
@@ -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;
};
+83
View File
@@ -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
};
+844
View File
@@ -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
};
+151
View File
@@ -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;
}
};
+310
View File
@@ -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
};
+208
View File
@@ -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;
}
};
+99
View File
@@ -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
};
+112
View File
@@ -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
};
+74
View File
@@ -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;
}
};
+99
View File
@@ -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
};
+667
View File
@@ -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
};
+210
View File
@@ -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
};
+344
View File
@@ -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
};
+85
View File
@@ -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 !
}
}
+150
View File
@@ -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)
}
+102
View File
@@ -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
}
+29
View File
@@ -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);
}
}
+64
View File
@@ -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
};
}
+254
View File
@@ -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;
};
};
};
+242
View File
@@ -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);
}
}
}
}
};
+128
View File
@@ -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"
+487
View File
@@ -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;
};
};
-17
View File
@@ -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()
-358
View File
@@ -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(&params);
/* 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
-308
View File
@@ -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
-242
View File
@@ -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(), &notificationEvents[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
-54
View File
@@ -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
-58
View File
@@ -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;
-207
View File
@@ -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
-337
View File
@@ -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(&param, 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, &param);
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(&param, 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, &param);
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(&param2, 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, &param2);
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
-151
View File
@@ -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
-252
View File
@@ -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
-243
View File
@@ -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
-160
View File
@@ -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);
}
-134
View File
@@ -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
View File
@@ -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
-12
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
};
-268
View File
@@ -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
-588
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
-104
View File
@@ -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
View File
@@ -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
-347
View File
@@ -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;
};
-989
View File
@@ -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(&regs);
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", &regNum) != 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", &regNum, 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
-45
View File
@@ -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/SDL deleted from 00d4f1c2c4.
Submodule core/deps/Spout deleted from 2110a918e7.
Submodule core/deps/asio deleted from 3321a1a753.
+76 -91
View File
@@ -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(&current_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(&current_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(&current_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(&current_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);
}
+7 -7
View File
@@ -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
+351
View File
@@ -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