mirror of
https://github.com/flyinghead/flycast.git
synced 2026-11-08 08:06:48 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
382f2e5c19 | ||
|
|
6f8699a118 | ||
|
|
f97b085f92 |
No files matched your search
@@ -1 +0,0 @@
|
||||
custom: ['https://www.paypal.com/paypalme/FlycastEmu']
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Please Note: This form is the minimum required information for submitting bugs.**
|
||||
**Removing this form may lead to your issue being closed until it is completed.**
|
||||
|
||||
Platform / OS / Hardware:
|
||||
|
||||
Github hash:
|
||||
|
||||
Hardware:
|
||||
|
||||
**_Description of the Issue_**
|
||||
|
||||
Add a short, concise description of the issue here
|
||||
|
||||
**_Debugging Steps Tested_**
|
||||
|
||||
* Fill in any steps already tried here
|
||||
* Begin each new line with an asterisk
|
||||
* If no steps are required, please skip
|
||||
|
||||
**_Logs Gathered_**
|
||||
|
||||
```
|
||||
|
||||
Please paste the contents of the log / logs here
|
||||
You may leave this field blank if you have none.
|
||||
|
||||
```
|
||||
|
||||
|
||||
**_Screenshots_**
|
||||
|
||||
(Replace this line with any issue screenshots)
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -1,95 +0,0 @@
|
||||
name: Android CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Set up build environment
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install ccache libcurl4-openssl-dev ninja-build
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
cache: 'gradle'
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.ccache
|
||||
key: android-ccache-${{ github.sha }}
|
||||
restore-keys: android-ccache-
|
||||
|
||||
- name: Gradle
|
||||
working-directory: shell/android-studio
|
||||
run: ./gradlew assembleRelease --parallel
|
||||
env:
|
||||
SENTRY_UPLOAD_URL: ${{ secrets.SENTRY_UPLOAD_URL }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
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: Configure AWS Credentials
|
||||
id: aws-credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-access-key-id: AKIAJOZQS4H2PHQWYFCA
|
||||
aws-secret-access-key: ${{ secrets.S3_SECRET_KEY }}
|
||||
aws-region: us-east-2
|
||||
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push'
|
||||
|
||||
- name: Upload to S3
|
||||
run: aws s3 sync shell/android-studio/flycast/build/outputs/apk/release s3://flycast-builds/android/${GITHUB_REF#refs/}-$GITHUB_SHA --acl public-read --exclude='*.json' --follow-symlinks
|
||||
if: ${{ steps.aws-credentials.outputs.aws-account-id != '' }}
|
||||
|
||||
- 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
|
||||
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 != '' }}
|
||||
@@ -1,48 +0,0 @@
|
||||
name: BSD CI
|
||||
|
||||
on: [push, 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 ]
|
||||
include:
|
||||
- operating_system: freebsd
|
||||
version: '14.0'
|
||||
pkginstall: sudo pkg install -y alsa-lib ccache cmake evdev-proto git libao libevdev libudev-devd libzip miniupnpc ninja pkgconf pulseaudio sdl2
|
||||
- operating_system: netbsd
|
||||
version: '9.3'
|
||||
pkginstall: sudo pkgin update && sudo pkgin -y install alsa-lib ccache cmake gcc12 git libao libzip miniupnpc ninja-build pkgconf pulseaudio SDL2 && export PATH=/usr/pkg/gcc12/bin:$PATH
|
||||
- operating_system: openbsd
|
||||
version: '7.4'
|
||||
pkginstall: sudo pkg_add ccache cmake git libao libzip miniupnpc ninja pkgconf pulseaudio sdl2
|
||||
exclude:
|
||||
- architecture: arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-${{ github.sha }}
|
||||
restore-keys: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-
|
||||
|
||||
- uses: cross-platform-actions/[email protected]
|
||||
with:
|
||||
operating_system: ${{ matrix.operating_system }}
|
||||
architecture: ${{ matrix.architecture }}
|
||||
version: ${{ matrix.version }}
|
||||
environment_variables: CCACHE_DIR
|
||||
run: |
|
||||
${{ matrix.pkginstall }}
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release -G Ninja
|
||||
cmake --build build --config Release
|
||||
@@ -1,181 +0,0 @@
|
||||
name: C/C++ CI
|
||||
|
||||
on: [push, 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-latest, shell: cmd, arch: x86, cmakeArgs: -G Ninja, buildType: Release}
|
||||
- {name: apple-darwin, os: macos-latest, shell: sh, cmakeArgs: -G Xcode -DAPPLE_BREAKPAD=ON, destDir: osx, buildType: RelWithDebInfo}
|
||||
- {name: apple-ios, os: macos-latest, shell: sh, cmakeArgs: -DCMAKE_SYSTEM_NAME=iOS -G Xcode, destDir: ios, buildType: Release}
|
||||
- {name: x86_64-pc-linux-gnu, os: ubuntu-20.04, shell: sh, cmakeArgs: -G Ninja, destDir: linux, buildType: RelWithDebInfo}
|
||||
- {name: x86_64-pc-windows-msvc, os: windows-latest, shell: cmd, arch: x64, cmakeArgs: -G Ninja, buildType: Release}
|
||||
- {name: x86_64-w64-mingw32, os: windows-latest, shell: 'msys2 {0}', cmakeArgs: -G Ninja, destDir: win, buildType: RelWithDebInfo}
|
||||
- {name: libretro-x86_64-pc-linux-gnu, os: ubuntu-latest, shell: sh, cmakeArgs: -DLIBRETRO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -G Ninja, buildType: Release}
|
||||
- {name: libretro-x86_64-w64-mingw32, os: windows-latest, shell: 'msys2 {0}', cmakeArgs: -DLIBRETRO=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 ninja pulseaudio
|
||||
VULKAN_VER=1.3.261.1 && aria2c https://sdk.lunarg.com/sdk/download/$VULKAN_VER/mac/vulkansdk-macos-$VULKAN_VER.dmg
|
||||
hdiutil attach ./vulkansdk-macos-*.dmg -mountpoint /Volumes/VulkanSDK
|
||||
sudo /Volumes/VulkanSDK/InstallVulkan.app/Contents/MacOS/InstallVulkan --root $HOME/VulkanSDK --accept-licenses --default-answer --confirm-command install
|
||||
hdiutil detach /Volumes/VulkanSDK
|
||||
echo "VULKAN_SDK=$HOME/VulkanSDK/macOS" >> $GITHUB_ENV
|
||||
if: runner.os == 'macOS'
|
||||
|
||||
- name: Set up build environment (Linux)
|
||||
run: |
|
||||
sudo add-apt-repository ppa:christianrauch/libdecoration
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install ccache libao-dev libasound2-dev libevdev-dev libgl1-mesa-dev liblua5.3-dev libminiupnpc-dev libpulse-dev libsdl2-dev libudev-dev libzip-dev ninja-build libcurl4-openssl-dev
|
||||
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
|
||||
if: runner.os == 'Linux'
|
||||
|
||||
- name: Set up build environment (Windows, MinGW)
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: MINGW64
|
||||
install: git make mingw-w64-x86_64-ccache mingw-w64-x86_64-cmake mingw-w64-x86_64-lua mingw-w64-x86_64-ninja mingw-w64-x86_64-SDL2 mingw-w64-x86_64-toolchain
|
||||
if: matrix.config.shell == 'msys2 {0}'
|
||||
|
||||
- name: Set up build environment (Windows, Visual Studio)
|
||||
run: |
|
||||
choco install ccache directx-sdk ninja
|
||||
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@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
- name: Compile a universal OpenMP (macOS)
|
||||
run: HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew reinstall --build-from-source --formula ./shell/apple/libomp.rb
|
||||
if: matrix.config.name == 'apple-darwin'
|
||||
|
||||
- name: Compile OpenMP.xcframework (iOS)
|
||||
run: ./shell/apple/emulator-ios/OpenMP/build_ios_openmp.sh --disableSimulator
|
||||
if: matrix.config.name == 'apple-ios'
|
||||
|
||||
- uses: actions/cache@v4
|
||||
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'
|
||||
|
||||
- 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'
|
||||
|
||||
- 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
|
||||
mv flycast-x86_64.AppImage artifact/bin
|
||||
rm artifact/bin/flycast
|
||||
if: matrix.config.name == 'x86_64-pc-linux-gnu'
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: flycast-${{ matrix.config.name }}
|
||||
path: artifact/bin
|
||||
|
||||
- name: Package app (macos)
|
||||
run: |
|
||||
cd artifact/bin
|
||||
zip -rm flycast.app.zip Flycast.app
|
||||
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
|
||||
if: matrix.config.name == 'x86_64-w64-mingw32'
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
id: aws-credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-access-key-id: AKIAJOZQS4H2PHQWYFCA
|
||||
aws-secret-access-key: ${{ secrets.S3_SECRET_KEY }}
|
||||
aws-region: us-east-2
|
||||
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push' && matrix.config.destDir != ''
|
||||
|
||||
- name: Upload to S3
|
||||
run: aws s3 sync artifact/bin s3://flycast-builds/${{ matrix.config.destDir }}/${GITHUB_REF#refs/}-$GITHUB_SHA --acl public-read --follow-symlinks
|
||||
shell: bash
|
||||
if: ${{ steps.aws-credentials.outputs.aws-account-id != '' }}
|
||||
|
||||
- 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') }}
|
||||
|
||||
@@ -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@v4
|
||||
with:
|
||||
java-version: 18
|
||||
distribution: zulu
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Upload Source
|
||||
shell: bash
|
||||
env:
|
||||
CROWDIN_API_KEY: ${{ secrets.CROWDIN_API_KEY }}
|
||||
run: |
|
||||
python3 intl/upload_workflow.py $CROWDIN_API_KEY "flyinghead-flycast" "shell/libretro/libretro_core_options.h"
|
||||
@@ -1,49 +0,0 @@
|
||||
# Download translations form Crowdin & Recreate libretro_core_options_intl.h
|
||||
|
||||
name: Crowdin Translation Integration
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# please choose a random time & weekday to avoid all repos synching at the same time
|
||||
- cron: '15 22 * * 5' # Fridays at 10:15 PM, UTC
|
||||
|
||||
jobs:
|
||||
create_intl_file:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup Java JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 18
|
||||
distribution: zulu
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
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/[email protected]
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: ${{ github.ref }}
|
||||
@@ -1,62 +0,0 @@
|
||||
name: Nintendo Switch CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.config.name }}
|
||||
runs-on: ubuntu-latest
|
||||
container: devkitpro/devkita64:latest
|
||||
|
||||
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 awscli ccache
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
- name: Mark git repository as safe
|
||||
run: git config --global --add safe.directory $PWD
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.ccache
|
||||
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@v4
|
||||
with:
|
||||
name: flycast-switch-${{ matrix.config.name }}
|
||||
path: artifact/bin
|
||||
|
||||
- name: Package app
|
||||
run: zip -m artifact/bin/flycast.nro.zip artifact/bin/flycast.nro
|
||||
if: hashFiles('artifact/bin/flycast.nro') != ''
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
id: aws-credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-access-key-id: AKIAJOZQS4H2PHQWYFCA
|
||||
aws-secret-access-key: ${{ secrets.S3_SECRET_KEY }}
|
||||
aws-region: us-east-2
|
||||
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push' && matrix.config.destDir != ''
|
||||
|
||||
- name: Upload to S3
|
||||
run: aws s3 sync artifact/bin s3://flycast-builds/${{ matrix.config.destDir }}/${GITHUB_REF#refs/}-$GITHUB_SHA --acl public-read --follow-symlinks
|
||||
if: ${{ steps.aws-credentials.outputs.aws-account-id != '' }}
|
||||
@@ -1,54 +0,0 @@
|
||||
name: Windows UWP CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
- name: CMake
|
||||
run: |
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release -G "Visual Studio 17 2022" -A x64 -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_*_x64_Test
|
||||
mkdir tmp
|
||||
ren *.msix flycast.msix
|
||||
makeappx.exe 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@v4
|
||||
with:
|
||||
name: flycast-uwp
|
||||
path: build/artifact
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
id: aws-credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-access-key-id: AKIAJOZQS4H2PHQWYFCA
|
||||
aws-secret-access-key: ${{ secrets.S3_SECRET_KEY }}
|
||||
aws-region: us-east-2
|
||||
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push'
|
||||
|
||||
- name: Upload to S3
|
||||
run: aws s3 sync build/artifact s3://flycast-builds/xbox/${GITHUB_REF#refs/}-$GITHUB_SHA --acl public-read --follow-symlinks
|
||||
shell: bash
|
||||
if: ${{ steps.aws-credentials.outputs.aws-account-id != '' }}
|
||||
+7
-16
@@ -1,19 +1,10 @@
|
||||
build/
|
||||
artifact/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
shell/apple/emulator-ios/OpenMP/OpenMP.xcframework
|
||||
*.class
|
||||
*.orig
|
||||
|
||||
# Visual Studio
|
||||
.vs/
|
||||
.vscode/
|
||||
out/
|
||||
bin/
|
||||
gen/
|
||||
obj/
|
||||
|
||||
# JetBrains IDES
|
||||
.idea/
|
||||
cmake-build-*/
|
||||
|
||||
# Dependencies from .gitmodules
|
||||
core/deps/breakpad/
|
||||
core/deps/glslang/
|
||||
newdc/android/libnewdc/bin/libnewdc.jar
|
||||
newdc/android/libnewdc/bin/classes/com/example/newdc/JNIdc.class
|
||||
-171
@@ -1,171 +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 -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
.core-defs-linux:
|
||||
extends: .core-defs
|
||||
variables:
|
||||
CORE_ARGS: -DLIBRETRO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
.core-defs-osx-x64:
|
||||
extends: .core-defs
|
||||
variables:
|
||||
CORE_ARGS: -DLIBRETRO=ON -G Xcode -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=x86_64
|
||||
EXTRA_PATH: Release
|
||||
|
||||
.core-defs-osx-arm64:
|
||||
extends: .core-defs
|
||||
variables:
|
||||
CORE_ARGS: -DLIBRETRO=ON -G Xcode -DCMAKE_BUILD_TYPE=Release
|
||||
EXTRA_PATH: Release
|
||||
|
||||
.core-defs-ios-arm64:
|
||||
extends: .core-defs
|
||||
variables:
|
||||
CORE_ARGS: -DLIBRETRO=ON -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
.core-defs-android:
|
||||
extends: .core-defs
|
||||
script:
|
||||
- cmake $CORE_ARGS -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 "$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:
|
||||
API_LEVEL: 18
|
||||
|
||||
# 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.yml'
|
||||
|
||||
# tvOS arm64
|
||||
- project: 'libretro-infrastructure/ci-templates'
|
||||
file: '/tvos-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
|
||||
|
||||
# Linux 32-bit
|
||||
libretro-build-linux-i686:
|
||||
extends:
|
||||
- .libretro-linux-cmake-x86
|
||||
- .core-defs-linux
|
||||
|
||||
# MacOS 64-bit
|
||||
libretro-build-osx-x64:
|
||||
extends:
|
||||
- .libretro-osx-cmake-x86
|
||||
- .core-defs-osx-x64
|
||||
|
||||
# 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-retroarch-master
|
||||
# - .core-defs
|
||||
|
||||
# tvOS arm64
|
||||
libretro-build-tvos-arm64:
|
||||
extends:
|
||||
- .libretro-tvos-cmake-arm64
|
||||
- .core-defs-ios-arm64
|
||||
-31
@@ -1,31 +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/rtissera/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
|
||||
-1822
File diff suppressed because it is too large.
Load diff
@@ -1,60 +1,84 @@
|
||||
# Flycast
|
||||
reicast
|
||||
===========
|
||||
reicast is a sega dreamcast emulator
|
||||
|
||||
[](https://github.com/flyinghead/flycast/actions/workflows/android.yml)
|
||||
[](https://github.com/flyinghead/flycast/actions/workflows/c-cpp.yml)
|
||||
[](https://github.com/flyinghead/flycast/actions/workflows/switch.yml)
|
||||
[](https://github.com/flyinghead/flycast/actions/workflows/uwp.yml)
|
||||
[](https://github.com/flyinghead/flycast/actions/workflows/bsd.yml)
|
||||
This is developer oriented resource, if you just want bins head over to http://reicast.com
|
||||
|
||||

|
||||
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.
|
||||
To build for android
|
||||
--------------------
|
||||
Tools required:
|
||||
* Latest Android SDK and NDK (duh)
|
||||
* Ant
|
||||
|
||||
## Install
|
||||
From project root directory:
|
||||
```
|
||||
cd shell\android
|
||||
android update project -p .
|
||||
|
||||
### Flatpak (Linux )
|
||||
ndk-build -j 4
|
||||
|
||||
1. [Set up Flatpak](https://www.flatpak.org/setup/).
|
||||
ant debug
|
||||
```
|
||||
|
||||
2. Install Flycast from [Flathub](https://flathub.org/apps/details/org.flycast.Flycast):
|
||||
Beta versions
|
||||
-------------------
|
||||
Compiled test versions are available at recast.loungekatt.com
|
||||
|
||||
`flatpak install -y org.flycast.Flycast`
|
||||
Donations and stuff
|
||||
-------------------
|
||||
Well, glad you liked the project so far!
|
||||
|
||||
3. Run Flycast:
|
||||
We're currently short on hardware
|
||||
|
||||
`flatpak run org.flycast.Flycast`
|
||||
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
|
||||
|
||||
### Homebrew (MacOS )
|
||||
This has been tested/developed on
|
||||
* Galaxy tab 7.0 Plus
|
||||
* LG P990
|
||||
* Archos 10G9
|
||||
* Some Chinese tablet
|
||||
* OUYA
|
||||
* Various development boards
|
||||
* gcw zero
|
||||
|
||||
1. [Set up Homebrew](https://brew.sh).
|
||||
We had to buy all of these, except the gcw zero and a beaglebone which were donated (thanks! You rock!)
|
||||
|
||||
2. Install Flycast via Homebrew:
|
||||
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 :)
|
||||
|
||||
`brew install --cask flycast`
|
||||
|
||||
### Xbox One/Series 
|
||||
We're planning for an indiegogo campaign later on to help with sustained development and running costs, so follow @reicastdc on twitter for updates
|
||||
|
||||
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**.
|
||||
|
||||
### Binaries      
|
||||
Team
|
||||
----
|
||||
|
||||
Get fresh builds for your system [**on the builds page**](https://flyinghead.github.io/flycast-builds/).
|
||||
You can check the currently active committers on [the pretty graphs page](https://github.com/reicast/reicast-emulator/graphs/contributors)
|
||||
|
||||
**New:** Now automated test results are available as well.
|
||||
Chat on freenode #reicast.
|
||||
|
||||
### Build instructions:
|
||||
`git clone https://github.com/flyinghead/flycast.git`
|
||||
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
|
||||
|
||||
`cd flycast`
|
||||
|
||||
`git submodule update --init --recursive`
|
||||
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 :)
|
||||
|
||||
`mkdir build && cd build`
|
||||
|
||||
`cmake ..`
|
||||
[](https://bitdeli.com/free "Bitdeli Badge")
|
||||
|
||||
`make`
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
/version.h
|
||||
+2
-14
@@ -1,16 +1,4 @@
|
||||
# core
|
||||
newdc
|
||||
===========
|
||||
|
||||
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
|
||||
The core of the emulator
|
||||
@@ -1,124 +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 "deps/lzma/7z.h"
|
||||
#include "deps/lzma/7zCrc.h"
|
||||
#include "deps/lzma/Alloc.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#define kInputBufSize ((size_t)1 << 18)
|
||||
|
||||
static bool crc_tables_generated;
|
||||
|
||||
bool SzArchive::Open(FILE *file)
|
||||
{
|
||||
SzArEx_Init(&szarchive);
|
||||
|
||||
File_Close(&archiveStream.file);
|
||||
File_Construct(&archiveStream.file);
|
||||
archiveStream.file.file = file;
|
||||
|
||||
FileInStream_CreateVTable(&archiveStream);
|
||||
LookToRead2_CreateVTable(&lookStream, 0);
|
||||
lookStream.buf = (Byte *)ISzAlloc_Alloc(&g_Alloc, kInputBufSize);
|
||||
if (lookStream.buf == NULL)
|
||||
{
|
||||
File_Close(&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)
|
||||
{
|
||||
File_Close(&archiveStream.file);
|
||||
ISzAlloc_Free(&g_Alloc, lookStream.buf);
|
||||
if (out_buffer != NULL)
|
||||
ISzAlloc_Free(&g_Alloc, out_buffer);
|
||||
SzArEx_Free(&szarchive, &g_Alloc);
|
||||
}
|
||||
}
|
||||
@@ -1,75 +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 "deps/lzma/7z.h"
|
||||
#include "deps/lzma/7zFile.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(FILE *file) override;
|
||||
|
||||
private:
|
||||
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) */
|
||||
CFileInStream archiveStream;
|
||||
CLookToRead2 lookStream;
|
||||
|
||||
};
|
||||
|
||||
class SzArchiveFile : public ArchiveFile
|
||||
{
|
||||
public:
|
||||
SzArchiveFile(u8 *data, u32 offset, u32 length)
|
||||
: data(data), offset(offset), _length(length) {}
|
||||
u32 Read(void *buffer, u32 length) override
|
||||
{
|
||||
length = std::min(length, this->_length);
|
||||
memcpy(buffer, data + offset, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
size_t length() override {
|
||||
return _length;
|
||||
}
|
||||
|
||||
private:
|
||||
u8 *data;
|
||||
u32 offset;
|
||||
u32 _length;
|
||||
};
|
||||
@@ -1,108 +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"
|
||||
|
||||
ZipArchive::~ZipArchive()
|
||||
{
|
||||
zip_close(zip);
|
||||
}
|
||||
|
||||
bool ZipArchive::Open(FILE *file)
|
||||
{
|
||||
zip_error_t error;
|
||||
zip_source_t *source = zip_source_filep_create(file, 0, -1, &error);
|
||||
if (source == nullptr)
|
||||
{
|
||||
std::fclose(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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1,60 +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(const void *data, size_t size);
|
||||
ArchiveFile *OpenFirstFile();
|
||||
|
||||
protected:
|
||||
bool Open(FILE *file) override;
|
||||
|
||||
private:
|
||||
zip_t *zip = nullptr;
|
||||
};
|
||||
|
||||
class ZipArchiveFile : public ArchiveFile
|
||||
{
|
||||
public:
|
||||
ZipArchiveFile(zip_file_t *zip_file, size_t length)
|
||||
: zip_file(zip_file), _length(length) {}
|
||||
~ZipArchiveFile() override {
|
||||
zip_fclose(zip_file);
|
||||
}
|
||||
u32 Read(void* buffer, u32 length) override;
|
||||
size_t length() override {
|
||||
return _length;
|
||||
}
|
||||
|
||||
private:
|
||||
zip_file_t *zip_file;
|
||||
size_t _length;
|
||||
};
|
||||
@@ -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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
FILE *file = nowide::fopen(path, "rb");
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
return Open(file);
|
||||
}
|
||||
|
||||
@@ -1,49 +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"
|
||||
|
||||
class ArchiveFile
|
||||
{
|
||||
public:
|
||||
virtual ~ArchiveFile() = default;
|
||||
virtual u32 Read(void *buffer, u32 length) = 0;
|
||||
virtual size_t length() = 0;
|
||||
};
|
||||
|
||||
class Archive
|
||||
{
|
||||
public:
|
||||
virtual ~Archive() = default;
|
||||
virtual ArchiveFile *OpenFile(const char *name) = 0;
|
||||
virtual ArchiveFile *OpenFileByCrc(u32 crc) = 0;
|
||||
|
||||
protected:
|
||||
virtual bool Open(FILE *file) = 0;
|
||||
|
||||
private:
|
||||
bool Open(const char *name);
|
||||
|
||||
friend Archive *OpenArchive(const std::string& path);
|
||||
};
|
||||
|
||||
Archive *OpenArchive(const std::string& path);
|
||||
@@ -1,166 +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(const std::string& path, bool write)
|
||||
{
|
||||
verify(file == nullptr);
|
||||
this->write = write;
|
||||
|
||||
file = nowide::fopen(path.c_str(), write ? "wb" : "rb");
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
if (!write)
|
||||
{
|
||||
u8 header[sizeof(RZipHeader)];
|
||||
if (std::fread(header, sizeof(header), 1, file) != 1
|
||||
|| memcmp(header, RZipHeader, sizeof(header))
|
||||
|| std::fread(&maxChunkSize, sizeof(maxChunkSize), 1, file) != 1
|
||||
|| std::fread(&size, sizeof(size), 1, file) != 1)
|
||||
{
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
// savestates created on 32-bit platforms used to have a 32-bit size
|
||||
if (size >> 32 != 0)
|
||||
{
|
||||
size &= 0xffffffff;
|
||||
std::fseek(file, -4, SEEK_CUR);
|
||||
}
|
||||
chunk = new u8[maxChunkSize];
|
||||
chunkIndex = 0;
|
||||
chunkSize = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxChunkSize = 1_MB;
|
||||
if (std::fwrite(RZipHeader, sizeof(RZipHeader), 1, file) != 1
|
||||
|| std::fwrite(&maxChunkSize, sizeof(maxChunkSize), 1, file) != 1
|
||||
|| std::fwrite(&size, sizeof(size), 1, file) != 1)
|
||||
{
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RZipFile::Close()
|
||||
{
|
||||
if (file != nullptr)
|
||||
{
|
||||
if (write)
|
||||
{
|
||||
std::fseek(file, sizeof(RZipHeader) + sizeof(maxChunkSize), SEEK_SET);
|
||||
std::fwrite(&size, sizeof(size), 1, file);
|
||||
}
|
||||
std::fclose(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 (std::fread(&zippedSize, sizeof(zippedSize), 1, file) != 1)
|
||||
break;
|
||||
if (zippedSize == 0)
|
||||
continue;
|
||||
u8 *zipped = new u8[zippedSize];
|
||||
if (std::fread(zipped, zippedSize, 1, file) != 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 (std::fwrite(&sz, sizeof(sz), 1, file) != 1
|
||||
|| std::fwrite(zipped, zippedSize, 1, file) != 1)
|
||||
{
|
||||
rv = 0;
|
||||
break;
|
||||
}
|
||||
p += uncompressedSize;
|
||||
rv += uncompressedSize;
|
||||
}
|
||||
delete [] zipped;
|
||||
|
||||
return rv;
|
||||
}
|
||||
@@ -1,45 +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"
|
||||
|
||||
class RZipFile
|
||||
{
|
||||
public:
|
||||
~RZipFile() { Close(); }
|
||||
|
||||
bool Open(const std::string& path, 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);
|
||||
FILE *rawFile() const { return file; }
|
||||
|
||||
private:
|
||||
FILE *file = nullptr;
|
||||
u64 size = 0;
|
||||
u32 maxChunkSize = 0;
|
||||
u8 *chunk = nullptr;
|
||||
u32 chunkSize = 0;
|
||||
u32 chunkIndex = 0;
|
||||
bool write = false;
|
||||
};
|
||||
@@ -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) // Form I * H is derived so not needed, fixup sImm24 so one can just pass a real addr
|
||||
{
|
||||
DECL_Id(0xFA000000);
|
||||
|
||||
//if(sImm24&1)
|
||||
// I |= 1<<24; // SET_H
|
||||
|
||||
I |= ((sImm24>>2)&0xFFFFFF);
|
||||
EMIT_I;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
Executable
+844
@@ -0,0 +1,844 @@
|
||||
/*
|
||||
* <E_DataOp.h Title="ARMv7 ISA Emitter Middle-ware: Data-Processing Instructions" />
|
||||
*
|
||||
* <?CTYPE
|
||||
*
|
||||
* <opcode1>{<cond>}{S} <Rd>, <shifter_operand>
|
||||
* <opcode1> := MOV | MVN
|
||||
*
|
||||
* <opcode2>{<cond>} <Rn>, <shifter_operand>
|
||||
* <opcode2> := CMP | CMN | TST | TEQ
|
||||
*
|
||||
* <opcode3>{<cond>}{S} <Rd>, <Rn>, <shifter_operand>
|
||||
* <opcode3> := ADD | SUB | RSB | ADC | SBC | RSC | AND | BIC | EOR | ORR
|
||||
*
|
||||
* ?/>
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* imm Rd,[Rn,] imm
|
||||
* reg Rd,Rn,Rm shift
|
||||
* rsr Rd,Rn,Rm type Rs
|
||||
*
|
||||
* sp.imm Rd {SP} imm
|
||||
* sp.reg Rd {SP} Rm shift
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#if 0
|
||||
#define dpInstr(iName, iId) \
|
||||
EAPI iName (eReg Rd, eReg Rn, u32 Imm) ; \
|
||||
EAPI iName (eReg Rd, eReg Rn, eReg Rm, eShiftOp type=S_LSL, u32 Imm=0) ; \
|
||||
EAPI iName (eReg Rd, eReg Rn, eReg Rm, eShiftOp type, eReg Rs) ;
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
||||
ADC IMM 0x02A00000
|
||||
ADC REG 0x00A00000
|
||||
ADC RSR 0x00A00010
|
||||
|
||||
ADD IMM 0x02800000
|
||||
ADD REG 0x00800000
|
||||
ADD RSR 0x00800010
|
||||
ADD.SP.IMM 0x028D0000
|
||||
ADD.SP.REG 0x008D0000
|
||||
|
||||
AND IMM 0x02000000
|
||||
AND REG 0x00000000
|
||||
AND RSR 0x00000010
|
||||
|
||||
|
||||
// ASR's do not fit this pattern moved elsewhere //
|
||||
|
||||
|
||||
|
||||
BIC IMM 0x03C00000
|
||||
BIC REG 0x01C00000
|
||||
BIC RSR 0x01C00010
|
||||
|
||||
CMN IMM 0x03700000 // N imm
|
||||
CMN REG 0x01700000 // NMshift
|
||||
CMN RSR 0x01700010 // NMtypeS
|
||||
|
||||
CMP IMM 0x03500000 // N imm
|
||||
CMP REG 0x01500000
|
||||
CMP RSR 0x01500010
|
||||
|
||||
|
||||
EOR IMM 0x02200000
|
||||
EOR REG 0x00200000
|
||||
EOR REG 0x00200010
|
||||
|
||||
|
||||
*/
|
||||
|
||||
#define DP_PARAMS (eReg Rd, eReg Rn, ShiftOp Shift, ConditionCode CC=AL)
|
||||
#define DP_RPARAMS (eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
|
||||
#define DP_COMMON \
|
||||
DECL_I; \
|
||||
\
|
||||
SET_CC; \
|
||||
I |= (Rn&15)<<16; \
|
||||
I |= (Rd&15)<<12; \
|
||||
I |= (Shift&0xFFF)
|
||||
|
||||
#define DP_RCOMMON \
|
||||
DECL_I; \
|
||||
\
|
||||
SET_CC; \
|
||||
I |= (Rn&15)<<16; \
|
||||
I |= (Rd&15)<<12; \
|
||||
I |= (Rm&15)
|
||||
|
||||
#define DP_OPCODE(opcode) \
|
||||
I |= (opcode)<<21
|
||||
|
||||
|
||||
|
||||
EAPI AND DP_PARAMS { DP_COMMON; DP_OPCODE(DP_AND); EMIT_I; }
|
||||
EAPI EOR DP_PARAMS { DP_COMMON; DP_OPCODE(DP_EOR); EMIT_I; }
|
||||
EAPI SUB DP_PARAMS { DP_COMMON; DP_OPCODE(DP_SUB); EMIT_I; }
|
||||
EAPI RSB DP_PARAMS { DP_COMMON; DP_OPCODE(DP_RSB); EMIT_I; }
|
||||
EAPI ADD DP_PARAMS { DP_COMMON; DP_OPCODE(DP_ADD); EMIT_I; }
|
||||
EAPI ADC DP_PARAMS { DP_COMMON; DP_OPCODE(DP_ADC); EMIT_I; }
|
||||
EAPI SBC DP_PARAMS { DP_COMMON; DP_OPCODE(DP_SBC); EMIT_I; }
|
||||
EAPI RSC DP_PARAMS { DP_COMMON; DP_OPCODE(DP_RSC); EMIT_I; }
|
||||
EAPI TST DP_PARAMS { DP_COMMON; DP_OPCODE(DP_TST); EMIT_I; }
|
||||
EAPI TEQ DP_PARAMS { DP_COMMON; DP_OPCODE(DP_TEQ); EMIT_I; }
|
||||
// EAPI CMP DP_PARAMS { DP_COMMON; DP_OPCODE(DP_CMP); EMIT_I; }
|
||||
EAPI CMN DP_PARAMS { DP_COMMON; DP_OPCODE(DP_CMN); EMIT_I; }
|
||||
EAPI ORR DP_PARAMS { DP_COMMON; DP_OPCODE(DP_ORR); EMIT_I; }
|
||||
EAPI MOV DP_PARAMS { DP_COMMON; DP_OPCODE(DP_MOV); EMIT_I; }
|
||||
EAPI BIC DP_PARAMS { DP_COMMON; DP_OPCODE(DP_BIC); EMIT_I; }
|
||||
EAPI MVN DP_PARAMS { DP_COMMON; DP_OPCODE(DP_MVN); EMIT_I; }
|
||||
|
||||
#if defined(_DEVEL) && defined(_NODEF_) // These require testing -> CMP/MOV Shifter(Reg)? fmt broken? // Simple third reg type w/ no shifter
|
||||
EAPI AND DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_AND); EMIT_I; }
|
||||
EAPI EOR DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_EOR); EMIT_I; }
|
||||
EAPI SUB DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_SUB); EMIT_I; }
|
||||
EAPI RSB DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_RSB); EMIT_I; }
|
||||
EAPI ADD DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_ADD); EMIT_I; }
|
||||
EAPI ADC DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_ADC); EMIT_I; }
|
||||
EAPI SBC DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_SBC); EMIT_I; }
|
||||
EAPI RSC DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_RSC); EMIT_I; }
|
||||
EAPI TST DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_TST); EMIT_I; }
|
||||
EAPI TEQ DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_TEQ); EMIT_I; }
|
||||
EAPI CMP DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_CMP); EMIT_I; }
|
||||
EAPI CMN DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_CMN); EMIT_I; }
|
||||
EAPI ORR DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_ORR); EMIT_I; }
|
||||
EAPI MOV DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_MOV); EMIT_I; }
|
||||
EAPI BIC DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_BIC); EMIT_I; }
|
||||
EAPI MVN DP_PARAMS { DP_RCOMMON; DP_OPCODE(DP_MVN); EMIT_I; }
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
static u32 ARMImmid8r4_enc(u32 imm32)
|
||||
{
|
||||
for (int i=0;i<=30;i+=2)
|
||||
{
|
||||
u32 immv=(imm32<<i) | (imm32>>(32-i));
|
||||
if (immv<256)
|
||||
{
|
||||
return ((i/2)<<8) | immv;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static u32 ARMImmid8r4(u32 imm8r4)
|
||||
{
|
||||
u32 rv = ARMImmid8r4_enc(imm8r4);
|
||||
verify(rv!=-1);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static bool is_i8r4(u32 i32) { return ARMImmid8r4_enc(i32) != -1; }
|
||||
|
||||
|
||||
|
||||
EAPI ADD(eReg Rd, eReg Rn, eReg Rm, u32 RmLSL, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00800000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
I |= (RmLSL&31)<<7;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ADD(eReg Rd, eReg Rn, eReg Rm, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
ADD(Rd,Rn,Rm,0,S,CC);
|
||||
}
|
||||
|
||||
EAPI ADD(eReg Rd, eReg Rn, eReg Rm, ShiftOp Shift, u32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00800000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= Shift<<5;
|
||||
I |= (Imm8&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ADD(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
ADD(Rd,Rn,Rm,false,CC);
|
||||
}
|
||||
|
||||
|
||||
EAPI ADD(eReg Rd, eReg Rn, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x02800000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ADC(eReg Rd, eReg Rn, eReg Rm, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00A00000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ADC(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
ADC(Rd,Rn,Rm,false,CC);
|
||||
}
|
||||
|
||||
EAPI ADC(eReg Rd, eReg Rn, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x02A00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ADC(eReg Rd, eReg Rn, eReg Rm, bool S, ShiftOp Shift, u32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00A00000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= Shift<<5;
|
||||
I |= (Imm8&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
EAPI ADR(eReg Rd, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x028F0000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ADR_Zero(eReg Rd, s32 Imm8, ConditionCode CC=AL) // Special case for subtraction of 0
|
||||
{
|
||||
DECL_Id(0x024F0000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI ORR(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01800000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ORR(eReg Rd, eReg Rn, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03800000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ORR(eReg Rd, eReg Rn, eReg Rm, ShiftOp Shift, eReg Rs, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01800000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= Shift<<5;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= 1<<4;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ORR(eReg Rd, eReg Rn, eReg Rm, bool S, ShiftOp Shift, u32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01800000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= Shift<<5;
|
||||
I |= (Imm8&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI AND(eReg Rd, eReg Rn, eReg Rm, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00000000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI AND(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00000000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI AND(eReg Rd, eReg Rn, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x02000000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI AND(eReg Rd, eReg Rn, s32 Imm8, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x02000000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI EOR(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00200000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI EOR(eReg Rd, eReg Rn, eReg Rm, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00200000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI EOR(eReg Rd, eReg Rn, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x02200000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
EAPI SUB(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00400000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SUB(eReg Rd, eReg Rn, s32 Imm8, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x02400000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
EMIT_I;
|
||||
}
|
||||
EAPI SUB(eReg Rd, eReg Rn, s32 Imm8, ConditionCode CC=AL) { SUB(Rd,Rn,Imm8,false,CC); }
|
||||
|
||||
EAPI SBC(eReg Rd, eReg Rn, eReg Rm, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00C00000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SBC(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
SBC(Rd,Rn,Rm,false,CC);
|
||||
}
|
||||
|
||||
EAPI RSB(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x00600000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI RSB(eReg Rd, eReg Rn, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x02600000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI RSB(eReg Rd, eReg Rn, s32 Imm8, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x02600000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI MVN(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01E00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI MVN(eReg Rd, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03E00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI TST(eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01100000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI TST(eReg Rn, u32 Imm12, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03100000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= ARMImmid8r4(Imm12);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI BIC(eReg Rd, eReg Rn, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03C00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
||||
EAPI UBFX(eReg Rd, eReg Rm, u8 lsb, u8 width, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x07E00050);
|
||||
verify(lsb+width<=32);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= (lsb&31)<<7;
|
||||
I |= ((width-1)&31)<<16;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI MOV(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI MOV(eReg Rd, eReg Rm, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00000);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI MOV(eReg Rd, eReg Rm, ShiftOp Shift, u32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= Shift<<5;
|
||||
I |= (Imm8&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI MOV(eReg Rd, eReg Rm, ShiftOp Shift, eReg Rs, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= Shift<<5;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= 1<<4;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI MOVW(eReg Rd, u32 Imm16, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03000000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Imm16&0xF000)<<4;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Imm16&0x0FFF);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI MOVT(eReg Rd, u32 Imm16, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03400000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Imm16&0xF000)<<4;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Imm16&0x0FFF);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI MOV(eReg Rd, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03A00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= ARMImmid8r4(Imm8); // * 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
EAPI CMP(eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01500000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI CMP(eReg Rn, s32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03500000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= ARMImmid8r4(Imm8); // *FIXME* 12b imm is 8b imm 4b rot. spec, add rot support!
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI CMP(eReg Rd, eReg Rm, ShiftOp Shift, eReg Rs, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01500000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= Shift<<5;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= 1<<4;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI CMP(eReg Rd, eReg Rm, ShiftOp Shift, u32 Imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01500000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
I |= Shift<<5;
|
||||
I |= (Imm8&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI LSL(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00010);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<8;
|
||||
I |= (Rn&15)<<0;
|
||||
EMIT_I;
|
||||
}
|
||||
EAPI LSL(eReg Rd, eReg Rm, s32 imm5, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00000);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<0;
|
||||
I |= (imm5&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI LSR(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00030);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<8;
|
||||
I |= (Rn&15)<<0;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI RRX(eReg Rd, eReg Rm,bool S=false, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00060);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<0;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI LSR(eReg Rd, eReg Rm, s32 imm5, bool S, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00020);
|
||||
|
||||
if (S)
|
||||
I |= 1<<20;
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<0;
|
||||
I |= (imm5&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI LSR(eReg Rd, eReg Rm, s32 imm5, ConditionCode CC=AL)
|
||||
{
|
||||
LSR(Rd,Rm,imm5,false,CC);
|
||||
}
|
||||
|
||||
|
||||
EAPI ASR(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00050);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<8;
|
||||
I |= (Rn&15)<<0;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ASR(eReg Rd, eReg Rm, s32 imm5, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00040);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<0;
|
||||
I |= (imm5&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ROR(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<8;
|
||||
I |= (Rn&15)<<0;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ROR(eReg Rd, eReg Rm, s32 imm5, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01A00060);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15)<<0;
|
||||
I |= (imm5&31)<<7;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#undef DP_PARAMS
|
||||
#undef DP_RPARAMS
|
||||
|
||||
#undef DP_COMMON
|
||||
#undef DP_RCOMMON
|
||||
|
||||
#undef DP_OPCODE
|
||||
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* E_Extend.h
|
||||
*
|
||||
|
||||
There are six basic instructions:
|
||||
XTAB16 Extend bits[23:16] and bits[7:0] of one register to 16 bits, and add corresponding halfwordsto the values in another register.
|
||||
XTAB Extend bits[ 7: 0] of one register to 32 bits, and add to the value in another register.
|
||||
XTAH Extend bits[15: 0] of one register to 32 bits, and add to the value in another register.
|
||||
XTB16 Extend bits[23:16] and bits[7:0] to 16 bits each.
|
||||
XTB Extend bits[ 7: 0] to 32 bits.
|
||||
XTH Extend bits[15: 0] to 32 bits.
|
||||
|
||||
Each of the six instructions is available in the following variations, indicated by the prefixes shown:
|
||||
S Sign extension, with or without addition modulo 216 or 232.
|
||||
U Zero (unsigned) extension, with or without addition modulo 216 or 232.
|
||||
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
EAPI SXTAB16(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06800070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SXTAB(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06A00070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SXTAH(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06B00070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SXTB16(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x068F0070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SXTB(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06AF0070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SXTH(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06BF0070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI UXTAB16(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06C00070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI UXTAB(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06E00070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI UXTAH(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06F00070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI UXTB16(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06CF0070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI UXTB(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06EF0070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI UXTH(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06FF0070);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* E_LoadStore.h
|
||||
*
|
||||
* LDR|STR{<cond>}{B}{T} Rd, <addressing_mode>
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
|
||||
enum AddrMode {
|
||||
|
||||
Offset, // [Rn, offset] - Base:Offset used for access, no writeback
|
||||
PostIndexed, // [Rn, offset] ! - Base:Offset used for access, written back to Base reg.
|
||||
PreIndexed // [Rn],offset - Base used then Base:Offset written back to Base reg.
|
||||
|
||||
// offset: imm{8,12}, Rm, Rm <Shifter> #Shift
|
||||
};
|
||||
|
||||
|
||||
#define SET_Rn I |= (Rn&15)<<16
|
||||
#define SET_Rt I |= (Rt&15)<<12
|
||||
#define SET_Rm I |= (Rm&15)
|
||||
|
||||
|
||||
#define SET_Rtn SET_Rt; SET_Rn
|
||||
#define SET_Rtnm SET_Rt; SET_Rn; SET_Rm
|
||||
|
||||
|
||||
|
||||
#define SET_I I |= (1<<25) // Sets Register (Rather than Immediate) Addressing
|
||||
#define SET_P I |= (1<<24) // If NOT Set: post-indexed, else offset||pre-indexed (W. determines which)
|
||||
#define SET_U I |= (1<<23) // If SET: Offset is added to base, else its subtracted (Sign of sImm12)
|
||||
#define SET_B I |= (1<<22) // If SET: Access is a byte access, else its a word access.
|
||||
#define SET_W I |= (1<<21) // (P==0) [ W==0: reg. post indexed, W==1: Unprivileged T variant ] (P==1) [ W==0: Offset , W==1: Pre-indexed ]
|
||||
#define SET_L I |= (1<<20) // (L==1) Load / Store
|
||||
|
||||
|
||||
#define SET_AddrMode \
|
||||
if(mode==Offset) { SET_P; } \
|
||||
else if(mode==PreIndexed) { SET_P; SET_W; }
|
||||
|
||||
/*
|
||||
#define SET_AddrMode \
|
||||
\
|
||||
switch(mode) { \
|
||||
case PostIndexed: break; \
|
||||
case Offset: SET_P; break; \
|
||||
case PreIndexed: SET_P; SET_W; break; \
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
#define SET_sImm12 \
|
||||
\
|
||||
if (0 != sImm12) { \
|
||||
if (sImm12 > 0) { SET_U; } \
|
||||
I |= (abs(sImm12) &0xFFF); \
|
||||
}
|
||||
|
||||
#define SET_sImm8 \
|
||||
\
|
||||
if(0 != sImm8) { \
|
||||
verify(is_s8(sImm8)); \
|
||||
if(sImm8 > 0) { SET_U; } \
|
||||
sImm8 = abs(sImm8); \
|
||||
I |= ((u32)sImm8 &0x0F); \
|
||||
I |= ((u32)sImm8 &0xF0)<<4; \
|
||||
}
|
||||
|
||||
|
||||
#define SET_Imm5 I |= ((Imm5&0x1F)<<7)
|
||||
#define SET_type I |= ((type&3)<<5)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* - Load/Store Encoding
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
cond 01-IPUBWL Rn Rt addr_mode -- Load/Store Word || Unsigned Byte
|
||||
|
||||
I,P,U,W - Specify type of addressing mode
|
||||
L - Specify Load (L==1), else Store
|
||||
B - Specify Byte (B==1), else Word
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
cond 000-PUIWL Rn Rt addr_mode 1SH1 addr_mode -- Load/Store Halfword || Signed byte
|
||||
|
||||
I,P,U,W - Specify type of addressing mode
|
||||
L - Specify Load (L==1), else Store
|
||||
S - Specify signed (S==1), else unsigned access
|
||||
H - Specify Halfword access, else signed byte
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
cond 010-PUBWL Rn Rt sImm12-iiii-iiii Imm Offset
|
||||
cond 011-PUBWL Rn Rt #sft sft_op 0 Rm Reg Offset
|
||||
|
||||
cond 000 PU1WL Rn Rt imHI 1011 imLO Imm Offset - halfword
|
||||
cond 000 PU0WL Rn Rt SBZ 1011 Rm Reg Offset - halfword
|
||||
cond 000 PU1W1 Rn Rt imHI 11H1 imLO Imm Offset - signed halfword/byte
|
||||
cond 000 PU0W1 Rn Rt SBZ 11H1 Rm Reg Offset - signed halfword/byte
|
||||
cond 000 PU1W0 Rn Rt imHI 11S1 imLO Imm Offset - two words
|
||||
cond 000 PU0W0 Rn Rt SBZ 11S1 Rm Reg Offset - two words
|
||||
|
||||
cond 100-PUSWL Rn Register-list Multiple
|
||||
cond 110-PUNWL Rn CRd cp_num offs8 COP & double reg xfer
|
||||
|
||||
cond 0001 0B00 Rn Rt SBZ 1001 Rm Swap [byte]
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
LDR L 010 1U001 11 Rt imm12 - *no literal stores*
|
||||
LDR I 010 PU0W1 Rn Rt imm12 - STR I 010 PU0W0 Rn Rt imm12
|
||||
LDR R 011 PU0W1 Rn Rt imm5 type 0 Rm - STR R 011 PU0W0 Rn Rt imm5 type 0 Rm
|
||||
|
||||
LDRT I 010 0U011 Rn Rt imm12 - STRT I 010 0U010 Rn Rt imm12
|
||||
LDRT R 011 0U011 Rn Rt imm5 type 0 Rm - STRT R 011 0U010 Rn Rt imm5 type 0 Rm
|
||||
|
||||
LDRB L 010 1U101 11 Rt imm12 - *^^
|
||||
LDRB I 010 PU1W1 Rn Rt imm12 - STRB I 010 PU1W0 Rn Rt imm12
|
||||
LDRB R 011 PU1W1 Rn Rt imm5 type 0 Rm - STRB R 011 PU1W0 Rn Rt imm5 type 0 Rm
|
||||
|
||||
LDRBT I
|
||||
LDRBT R
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
LDRH L 000 1U101 11 Rt imm4H 1011 imm4L - *^^
|
||||
LDRH I 000 PU1W1 Rn Rt imm4H 1011 imm4L - STRH I 000 PU1W0 Rn Rt imm4H 1011 imm4L
|
||||
LDRH R 000 PU0W1 Rn Rt 0000 1011 Rm - STRH R 000 PU0W0 Rn Rt 0000 1011 Rm
|
||||
|
||||
LDRD L 000 1U100 11 Rt imm4H 1101 imm4L - *^^
|
||||
LDRD I 000 PU1W0 Rn Rt imm4H 1101 imm4L - STRD I 000 PU1W0 Rn Rt imm4H 1111 imm4L
|
||||
LDRD R 000 PU0W0 Rn Rt 0000 1101 Rm - STRD R 000 PU0W0 Rn Rt 0000 1111 Rm
|
||||
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
LDRHT/STRHT
|
||||
|
||||
LDREX/STREX
|
||||
LDREXB/STREXB
|
||||
LDREXH/STREXH
|
||||
LDREXD/STREXD
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
EAPI LDR 0x04100000 // These were all the A1 Versions for Imm IIRC
|
||||
EAPI LDRB 0x04500000
|
||||
EAPI LDRBT 0x06700000
|
||||
EAPI LDRD 0x00000000
|
||||
EAPI LDREX 0x01900090
|
||||
EAPI LDRH 0x00100090
|
||||
EAPI LDRSB 0x00100000
|
||||
EAPI LDRSH 0x00100000
|
||||
EAPI LDRT 0x04300000
|
||||
|
||||
EAPI STR 0x04000000
|
||||
EAPI STRB 0x04400000
|
||||
EAPI STRBT 0x06600000
|
||||
EAPI STRD 0x00000000
|
||||
EAPI STREX 0x01800090
|
||||
EAPI STRH 0x00000090
|
||||
EAPI STRT 0x04200000
|
||||
|
||||
*/
|
||||
|
||||
|
||||
extern u8* emit_opt;
|
||||
extern eReg reg_addr;
|
||||
extern eReg reg_dst;
|
||||
extern s32 imma;
|
||||
|
||||
EAPI LDR (eReg Rt, eReg Rn, s32 sImm12=0, AddrMode mode=Offset, ConditionCode CC=AL)
|
||||
{
|
||||
if (emit_opt+4==(u8*)EMIT_GET_PTR() && reg_addr==Rn && imma==sImm12)
|
||||
{
|
||||
if (reg_dst!=Rt)
|
||||
MOV(Rt,reg_dst);
|
||||
}
|
||||
else
|
||||
{
|
||||
DECL_Id(0x04100000); SET_CC; SET_Rtn; SET_AddrMode; SET_sImm12; EMIT_I;
|
||||
}
|
||||
}
|
||||
EAPI STR (eReg Rt, eReg Rn, s32 sImm12=0, AddrMode mode=Offset, ConditionCode CC=AL)
|
||||
{
|
||||
emit_opt=0;//(u8*)EMIT_GET_PTR();
|
||||
reg_addr=Rn;
|
||||
reg_dst=Rt;
|
||||
imma=sImm12;
|
||||
DECL_Id(0x04000000); SET_CC; SET_Rtn; SET_AddrMode; SET_sImm12; EMIT_I;
|
||||
}
|
||||
EAPI LDRB(eReg Rt, eReg Rn, s32 sImm12=0, AddrMode mode=Offset, ConditionCode CC=AL) { DECL_Id(0x04500000); SET_CC; SET_Rtn; SET_AddrMode; SET_sImm12; SET_B; EMIT_I; } // Prob don't need SET_B, in iID
|
||||
EAPI STRB(eReg Rt, eReg Rn, s32 sImm12=0, AddrMode mode=Offset, ConditionCode CC=AL) { DECL_Id(0x04400000); SET_CC; SET_Rtn; SET_AddrMode; SET_sImm12; SET_B; EMIT_I; } // Prob don't need SET_B, in iID
|
||||
|
||||
EAPI LDRT (eReg Rt, eReg Rn, s32 sImm12=0, ConditionCode CC=AL) { DECL_Id(0x04300000); SET_CC; SET_Rtn; SET_sImm12; EMIT_I; }
|
||||
EAPI STRT (eReg Rt, eReg Rn, s32 sImm12=0, ConditionCode CC=AL) { DECL_Id(0x04200000); SET_CC; SET_Rtn; SET_sImm12; EMIT_I; }
|
||||
EAPI LDRBT(eReg Rt, eReg Rn, s32 sImm12=0, ConditionCode CC=AL) { DECL_Id(0x04700000); SET_CC; SET_Rtn; SET_sImm12; SET_B; EMIT_I; } // Prob don't need SET_B, in iID
|
||||
EAPI STRBT(eReg Rt, eReg Rn, s32 sImm12=0, ConditionCode CC=AL) { DECL_Id(0x04600000); SET_CC; SET_Rtn; SET_sImm12; SET_B; EMIT_I; } // Prob don't need SET_B, in iID
|
||||
|
||||
// LDR(r1,r2,r3, Offset, true, L_LSL, 5, EQ); ... LDR r1, r2, +r3 LSL #5
|
||||
EAPI LDR (eReg Rt, eReg Rn, eReg Rm, AddrMode mode=Offset, bool Add=false, ShiftOp type=S_LSL, u32 Imm5=0, ConditionCode CC=AL) { DECL_Id(0x06100000); SET_CC; SET_Rtnm; SET_AddrMode; SET_Imm5; SET_type; if(Add){ SET_U; } EMIT_I; }
|
||||
EAPI STR (eReg Rt, eReg Rn, eReg Rm, AddrMode mode=Offset, bool Add=false, ShiftOp type=S_LSL, u32 Imm5=0, ConditionCode CC=AL) { DECL_Id(0x06000000); SET_CC; SET_Rtnm; SET_AddrMode; SET_Imm5; SET_type; if(Add){ SET_U; } EMIT_I; }
|
||||
EAPI LDRB(eReg Rt, eReg Rn, eReg Rm, AddrMode mode=Offset, bool Add=false, ShiftOp type=S_LSL, u32 Imm5=0, ConditionCode CC=AL) { DECL_Id(0x06500000); SET_CC; SET_Rtnm; SET_AddrMode; SET_Imm5; SET_type; if(Add){ SET_U; } SET_B; EMIT_I; } // Prob don't need SET_B, in iID
|
||||
EAPI STRB(eReg Rt, eReg Rn, eReg Rm, AddrMode mode=Offset, bool Add=false, ShiftOp type=S_LSL, u32 Imm5=0, ConditionCode CC=AL) { DECL_Id(0x06400000); SET_CC; SET_Rtnm; SET_AddrMode; SET_Imm5; SET_type; if(Add){ SET_U; } SET_B; EMIT_I; } // Prob don't need SET_B, in iID
|
||||
|
||||
EAPI LDRT (eReg Rt, eReg Rn, eReg Rm, bool Add=false, ShiftOp type=S_LSL, u32 Imm5=0, ConditionCode CC=AL) { DECL_Id(0x06300000); SET_CC; SET_Rtnm; SET_Imm5; SET_type; if(Add){ SET_U; } EMIT_I; }
|
||||
EAPI STRT (eReg Rt, eReg Rn, eReg Rm, bool Add=false, ShiftOp type=S_LSL, u32 Imm5=0, ConditionCode CC=AL) { DECL_Id(0x06200000); SET_CC; SET_Rtnm; SET_Imm5; SET_type; if(Add){ SET_U; } EMIT_I; }
|
||||
EAPI LDRBT(eReg Rt, eReg Rn, eReg Rm, bool Add=false, ShiftOp type=S_LSL, u32 Imm5=0, ConditionCode CC=AL) { DECL_Id(0x06700000); SET_CC; SET_Rtnm; SET_Imm5; SET_type; if(Add){ SET_U; } SET_B; EMIT_I; } // Prob don't need SET_B, in iID
|
||||
EAPI STRBT(eReg Rt, eReg Rn, eReg Rm, bool Add=false, ShiftOp type=S_LSL, u32 Imm5=0, ConditionCode CC=AL) { DECL_Id(0x06600000); SET_CC; SET_Rtnm; SET_Imm5; SET_type; if(Add){ SET_U; } SET_B; EMIT_I; } // Prob don't need SET_B, in iID
|
||||
|
||||
|
||||
|
||||
// LDR Rt,[PC, #(s:+/-)Imm12] *Special Case - Literal / PC Relative
|
||||
EAPI LDR(eReg Rt, s32 sImm12, ConditionCode CC=AL) {
|
||||
DECL_Id(0x051F0000); SET_CC; SET_Rt; SET_sImm12; EMIT_I;
|
||||
}
|
||||
|
||||
// LDRB Rt,[PC, #(s:+/-)Imm12] *Special Case - Literal / PC Relative
|
||||
EAPI LDRB(eReg Rt, s32 sImm12, ConditionCode CC=AL) {
|
||||
DECL_Id(0x055F0000); SET_CC; SET_Rt; SET_sImm12; EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Note: Following support Post-Indexed addressing only //
|
||||
|
||||
|
||||
EAPI LDRH(eReg Rt, eReg Rn, s32 sImm8, ConditionCode CC=AL) { DECL_Id(0x005000B0); SET_CC; SET_Rtn; SET_P; SET_sImm8; EMIT_I; }
|
||||
EAPI STRH(eReg Rt, eReg Rn, s32 sImm8, ConditionCode CC=AL) { DECL_Id(0x004000B0); SET_CC; SET_Rtn; SET_P; SET_sImm8; EMIT_I; }
|
||||
EAPI LDRD(eReg Rt, eReg Rn, s32 sImm8, ConditionCode CC=AL) { DECL_Id(0x004000D0); SET_CC; SET_Rtn; SET_P; SET_sImm8; EMIT_I; }
|
||||
EAPI STRD(eReg Rt, eReg Rn, s32 sImm8, ConditionCode CC=AL) { DECL_Id(0x004000F0); SET_CC; SET_Rtn; SET_P; SET_sImm8; EMIT_I; }
|
||||
|
||||
EAPI LDRH(eReg Rt, eReg Rn, eReg Rm, bool Add=true,ConditionCode CC=AL) { DECL_Id(0x001000B0); SET_CC; SET_Rtnm; SET_P; if (Add) {SET_U;} EMIT_I; }
|
||||
EAPI STRH(eReg Rt, eReg Rn, eReg Rm, bool Add=true,ConditionCode CC=AL) { DECL_Id(0x000000B0); SET_CC; SET_Rtnm; SET_P; if (Add) {SET_U;} EMIT_I; }
|
||||
EAPI LDRD(eReg Rt, eReg Rn, eReg Rm, bool Add=true, ConditionCode CC=AL) { DECL_Id(0x000000D0); SET_CC; SET_Rtnm; SET_P; if (Add) {SET_U;} EMIT_I; }
|
||||
EAPI STRD(eReg Rt, eReg Rn, eReg Rm, bool Add=true, ConditionCode CC=AL) { DECL_Id(0x000000F0); SET_CC; SET_Rtnm; SET_P; if (Add) {SET_U;} EMIT_I; }
|
||||
|
||||
EAPI LDRSB(eReg Rt, eReg Rn, eReg Rm, bool Add=true,ConditionCode CC=AL) { DECL_Id(0x001000D0); SET_CC; SET_Rtnm; SET_P; if (Add) {SET_U;} EMIT_I; }
|
||||
EAPI LDRSH(eReg Rt, eReg Rn, eReg Rm, bool Add=true,ConditionCode CC=AL) { DECL_Id(0x001000F0); SET_CC; SET_Rtnm; SET_P; if (Add) {SET_U;} EMIT_I; }
|
||||
|
||||
|
||||
EAPI LDRH(eReg Rt, s32 sImm8, ConditionCode CC=AL) { DECL_Id(0x015F00B0); SET_CC; SET_Rt; SET_sImm8; EMIT_I; } // *Special Case - Literal / PC Relative
|
||||
EAPI STRH(eReg Rt, s32 sImm8, ConditionCode CC=AL) { DECL_Id(0x014F00D0); SET_CC; SET_Rt; SET_sImm8; EMIT_I; } // *Special Case - Literal / PC Relative
|
||||
|
||||
|
||||
|
||||
// TODO: {LD,ST}R{SB,EX*} && friends (If required).
|
||||
|
||||
|
||||
// Must use _Reg format
|
||||
EAPI PUSH(u32 RegList, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x092D0000);
|
||||
|
||||
SET_CC;
|
||||
I |= (RegList&0xFFFF);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI POP(u32 RegList, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x08BD0000);
|
||||
|
||||
SET_CC;
|
||||
I |= (RegList&0xFFFF);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
#undef SET_Rtn
|
||||
#undef SET_Rtnm
|
||||
|
||||
#undef SET_Rn
|
||||
#undef SET_Rt
|
||||
#undef SET_Rm
|
||||
|
||||
#undef SET_I
|
||||
#undef SET_P
|
||||
#undef SET_U
|
||||
#undef SET_B
|
||||
#undef SET_W
|
||||
#undef SET_L
|
||||
|
||||
#undef SET_AddrMode
|
||||
|
||||
#undef SET_sImm12
|
||||
#undef SET_sImm8
|
||||
#undef SET_Imm5
|
||||
#undef SET_type
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* E_Misc.h
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
/*
|
||||
* Misc. Arithmetic Instructions
|
||||
*/
|
||||
|
||||
// Count Leading Zero's
|
||||
//
|
||||
EAPI CLZ(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x016F0F10);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
// Unsigned sum of absolute differences
|
||||
//
|
||||
EAPI USAD8(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0780F010);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<16;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI USADA8(eReg Rd, eReg Rm, eReg Rs, eReg Rn, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x07800010);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<16;
|
||||
I |= (Rn&15)<<12;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Packing Instructions
|
||||
*/
|
||||
|
||||
|
||||
EAPI PKHBT(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL) // * shift_imm
|
||||
{
|
||||
DECL_Id(0x06800010);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI PKHTB(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL) // * shift_imm
|
||||
{
|
||||
DECL_Id(0x06800050);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Swapping Instructions
|
||||
*/
|
||||
|
||||
EAPI REV(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06BF0F30);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI REV16(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06BF0FB0);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI REVSH(eReg Rd, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06FF0F30);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI SEL(eReg Rd, eReg Rn, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06800FB0);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rn&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Saturate Instructions
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// SSAT{<cond>} <Rd>, #<immed>, <Rm>{, <shift>}
|
||||
//
|
||||
EAPI SSAT(eReg Rd, u32 sat_imm, eReg Rm, u32 sft_imm, ConditionCode CC=AL) // sh&1 << 6
|
||||
{
|
||||
DECL_Id(0x06A00010);
|
||||
|
||||
SET_CC;
|
||||
I |= (sat_imm&31)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (sft_imm&31)<<7;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
// SSAT16{<cond>} <Rd>, #<immed>, <Rm>
|
||||
//
|
||||
EAPI SSAT16(eReg Rd, u32 sat_imm, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06A00030);
|
||||
|
||||
SET_CC;
|
||||
I |= (sat_imm&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= 15<<8; // * SBO
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
// USAT{<cond>} <Rd>, #<immed>, <Rm>{, <shift>}
|
||||
//
|
||||
EAPI USAT(eReg Rd, u32 sat_imm, eReg Rm, u32 sft_imm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06E00010);
|
||||
|
||||
SET_CC;
|
||||
I |= (sat_imm&31)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= (sft_imm&31)<<7;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
// USAT16{<cond>} <Rd>, #<immed>, <Rm>
|
||||
//
|
||||
EAPI USAT16(eReg Rd, u32 sat_imm, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x06E00030);
|
||||
|
||||
SET_CC;
|
||||
I |= (sat_imm&15)<<16;
|
||||
I |= (Rd&15)<<12;
|
||||
I |= 15<<8; // * SBO
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
Executable
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* E_Multiply.h
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
EAPI MLA(eReg Rd, eReg Rn, eReg Rs, eReg Rm, ConditionCode CC=AL) // *FIXME* S
|
||||
{
|
||||
DECL_Id(0x00200090);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<16;
|
||||
I |= (Rn&15)<<12;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI MUL(eReg Rd, eReg Rs, eReg Rm, ConditionCode CC=AL) // *FIXME* S
|
||||
{
|
||||
DECL_Id(0x00000090);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rd&15)<<16;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI UMULL(eReg Rdhi, eReg Rdlo, eReg Rs, eReg Rm, ConditionCode CC=AL) // *FIXME* S
|
||||
{
|
||||
DECL_Id(0x00800090);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rdhi&15)<<16;
|
||||
I |= (Rdlo&15)<<12;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SMULL(eReg Rdhi, eReg Rdlo, eReg Rs, eReg Rm, ConditionCode CC=AL) // *FIXME* S
|
||||
{
|
||||
DECL_Id(0x00C00090);
|
||||
|
||||
SET_CC;
|
||||
I |= (Rdhi&15)<<16;
|
||||
I |= (Rdlo&15)<<12;
|
||||
I |= (Rs&15)<<8;
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#if 0
|
||||
SMLA<x><y> Signed halfword Multiply Accumulate.
|
||||
SMLAD Signed halfword Multiply Accumulate, Dual.
|
||||
SMLAL Signed Multiply Accumulate Long.
|
||||
SMLAL<x><y> Signed halfword Multiply Accumulate Long.
|
||||
SMLALD Signed halfword Multiply Accumulate Long, Dual.
|
||||
SMLAW<y> Signed halfword by word Multiply Accumulate.
|
||||
SMLSD Signed halfword Multiply Subtract, Dual.
|
||||
SMLSLD Signed halfword Multiply Subtract Long Dual.
|
||||
SMMLA Signed Most significant word Multiply Accumulate.
|
||||
SMMLS Signed Most significant word Multiply Subtract.
|
||||
SMMUL Signed Most significant word Multiply.
|
||||
SMUAD Signed halfword Multiply, Add, Dual.
|
||||
SMUL<x><y> Signed halfword Multiply.
|
||||
SMULL Signed Multiply Long.
|
||||
SMULW<y> Signed halfword by word Multiply.
|
||||
SMUSD Signed halfword Multiply, Subtract, Dual.
|
||||
UMAAL Unsigned Multiply Accumulate significant Long.
|
||||
UMLAL Unsigned Multiply Accumulate Long.
|
||||
UMULL Unsigned Multiply Long.
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* E_Parallel.h
|
||||
*
|
||||
|
||||
ADD8 Adds each byte of the second operand register to the corresponding byte of the first operand
|
||||
register to form the corresponding byte of the result.
|
||||
|
||||
ADD16 Adds the top halfwords of two registers to form the top halfword of the result.
|
||||
Adds the bottom halfwords of the same two registers to form the bottom halfword of the result.
|
||||
|
||||
SUB8 Subtracts each byte of the second operand register from the corresponding byte of the first
|
||||
operand register to form the corresponding byte of the result.
|
||||
|
||||
SUB16 Subtracts the top halfword of the first operand register from the top halfword of the second operand register to form the top halfword of the result.
|
||||
Subtracts the bottom halfword of the second operand registers from the bottom halfword of
|
||||
the first operand register to form the bottom halfword of the result.
|
||||
|
||||
ADDSUBX Does the following:
|
||||
1. Exchanges halfwords of the second operand register.
|
||||
2. Adds top halfwords and subtracts bottom halfwords.
|
||||
|
||||
SUBADDX Does the following:
|
||||
1. Exchanges halfwords of the second operand register.
|
||||
2. Subtracts top halfwords and adds bottom halfwords.
|
||||
|
||||
|
||||
Each of the six instructions is available in the following variations, indicated by the prefixes shown:
|
||||
|
||||
S Signed arithmetic modulo 28 or 216. Sets the CPSR GE bits (see The GE[3:0] bits on page A2-13).
|
||||
Q Signed saturating arithmetic.
|
||||
SH Signed arithmetic, halving the results to avoid overflow.
|
||||
U Unsigned arithmetic modulo 28 or 216. Sets the CPSR GE bits (see The GE[3:0] bits on page A2-13).
|
||||
UQ Unsigned saturating arithmetic.
|
||||
UH Unsigned arithmetic, halving the results to avoid overflow.
|
||||
|
||||
|
||||
Status:
|
||||
These routines require implementation if needed.
|
||||
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
#if defined(_DEVEL) && 0
|
||||
|
||||
// S
|
||||
//
|
||||
EAPI SADD8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SADD16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SSUB8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SSUB16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SADDSUBX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SSUBADDX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
|
||||
|
||||
// Q
|
||||
//
|
||||
EAPI QADD8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI QADD16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI QSUB8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI QSUB16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI QADDSUBX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI QSUBADDX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
|
||||
|
||||
// SH
|
||||
//
|
||||
EAPI SHADD8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SHADD16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SHSUB8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SHSUB16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SHADDSUBX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI SHSUBADDX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
|
||||
|
||||
// U
|
||||
//
|
||||
EAPI UADD8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UADD16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI USUB8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI USUB16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UADDSUBX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI USUBADDX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
|
||||
|
||||
// UQ
|
||||
//
|
||||
EAPI UQADD8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UQADD16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UQSUB8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UQSUB16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UQADDSUBX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UQSUBADDX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
|
||||
|
||||
// UH
|
||||
//
|
||||
EAPI UHADD8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UHADD16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UHSUB8 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UHSUB16 (eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UHADDSUBX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
EAPI UHSUBADDX(eReg Rd, eReg Rm, eReg Rs, ConditionCode CC=AL) ;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* E_Special.h
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
EAPI ARMERROR()
|
||||
{
|
||||
DECL_Id(0xFFFFFFFF);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI NOP()
|
||||
{
|
||||
DECL_Id(0xE320F000);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI SVC(u32 code)
|
||||
{
|
||||
DECL_Id(0x0F000000);
|
||||
I |= code&0xFFFFFF;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI BKPT()
|
||||
{
|
||||
DECL_Id(0x01200070);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
#define SWI SVC
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Synchronization & Barrier Instructions.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
EAPI DSB()
|
||||
{
|
||||
DECL_Id(0xF57FF04F);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI DMB()
|
||||
{
|
||||
DECL_Id(0xF57FF05F);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI ISB()
|
||||
{
|
||||
DECL_Id(0xF57FF06F);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* E_Status.h
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
#if defined(_DEVEL)
|
||||
|
||||
// MRS Move PSR to General-purpose Register.
|
||||
//
|
||||
EAPI MRS(eReg Rd, u32 R, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01000000);
|
||||
|
||||
SET_CC;
|
||||
I |= (R&1) << 22;
|
||||
I |= 15<<16; // * SBO
|
||||
I |= (Rd &15)<<12;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
/* MSR Move General-purpose Register to PSR.
|
||||
|
||||
MSR{<cond>} CPSR_<fields>, #<immediate>
|
||||
MSR{<cond>} CPSR_<fields>, <Rm>
|
||||
MSR{<cond>} SPSR_<fields>, #<immediate>
|
||||
MSR{<cond>} SPSR_<fields>, <Rm>
|
||||
*/
|
||||
|
||||
|
||||
// MSR: Immediate operand
|
||||
//
|
||||
EAPI MSR(u32 R, u32 fmask, u32 rot_imm, u32 imm8, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x03200000);
|
||||
|
||||
SET_CC;
|
||||
I |= (R&1) << 22;
|
||||
I |= (fmask &15)<<16;
|
||||
I |= 15<<12; // * SBO
|
||||
I |= (rot_imm &15)<<8;
|
||||
I |= (imm8 &255);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
// MSR: Register operand
|
||||
//
|
||||
EAPI MSR(u32 R, u32 fmask, eReg Rm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x01200000);
|
||||
|
||||
SET_CC;
|
||||
I |= (R&1) << 22;
|
||||
I |= (fmask &15)<<16;
|
||||
I |= 15<<12; // * SBO
|
||||
I |= (Rm&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// CPS Change Processor State.
|
||||
//
|
||||
EAPI CPS(u32 imod, u32 mmod, u32 mode) // ** [A|I|F]
|
||||
{
|
||||
DECL_Id(0xF1000000);
|
||||
|
||||
// Note: UNconditional instruction!
|
||||
I |= (imod&3)<<18;
|
||||
I |= (mmod&1)<<17;
|
||||
I |= (mode&15);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// SETEND Modifies the CPSR endianness, E, bit, without changing any other bits in the CPSR.
|
||||
//
|
||||
EAPI SETEND(u32 E)
|
||||
{
|
||||
DECL_Id(0xF1010000);
|
||||
|
||||
// Note: UNconditional instruction!
|
||||
I |= (E &1) << 9;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
};
|
||||
Executable
+667
@@ -0,0 +1,667 @@
|
||||
/*
|
||||
* E_VDataOp.h * VFP/A.SIMD Data Processing Instruction Set Encoding
|
||||
*
|
||||
* V{<modifier>}<operation>{<shape>}<c><q>{.<dt>} {<dest>,} <src1>, <src2>
|
||||
*
|
||||
* <modifier> Meaning
|
||||
* Q The operation uses saturating arithmetic.
|
||||
* R The operation performs rounding.
|
||||
* D The operation doubles the result (before accumulation, if any).
|
||||
* H The operation halves the result.
|
||||
*
|
||||
* <shape> Meaning Typical register shape
|
||||
* (none) The operands and result are all the same width. Dd, Dn, Dm - Qd, Qn, Qm
|
||||
* L Long operation - result is 2 x width of both operands Qd, Dn, Dm
|
||||
* N Narrow operation - result is width/2 both operands Dd, Qn, Qm
|
||||
* W Wide operation - result and oper[1] are 2x width oper[2] Qd, Qn, Dm
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
#define SET_Qd \
|
||||
I |= (((Qd<<1)&0x0E)<<12); \
|
||||
I |= (((Qd<<1)&0x10)<<18)
|
||||
|
||||
#define SET_Qn \
|
||||
I |= (((Qn<<1)&0x0E)<<16); \
|
||||
I |= (((Qn<<1)&0x10)<<3)
|
||||
|
||||
#define SET_Qm \
|
||||
I |= (((Qm<<1)&0x0E)); \
|
||||
I |= (((Qm<<1)&0x10)<<1)
|
||||
|
||||
|
||||
|
||||
#define SET_Dd \
|
||||
I |= ((Dd&0x0F)<<12); \
|
||||
I |= ((Dd&0x10)<<18)
|
||||
|
||||
#define SET_Dn \
|
||||
I |= ((Dn&0x0F)<<16); \
|
||||
I |= ((Dn&0x10)<<3)
|
||||
|
||||
#define SET_Dm \
|
||||
I |= ((Dm&0x0F)); \
|
||||
I |= ((Dm&0x10)<<1)
|
||||
|
||||
|
||||
|
||||
|
||||
#define SET_Q I |= 0x40
|
||||
#define SET_U I |= ((U&1)<<24);
|
||||
#define SET_Size I |= (((Size>>4)&3)<<20) // 8,16,32 -> 0,1,2
|
||||
|
||||
|
||||
#define SET_Qdnm \
|
||||
SET_Qd; SET_Qn; SET_Qm; SET_Q
|
||||
|
||||
#define SET_Ddnm \
|
||||
SET_Dd; SET_Dn; SET_Dm
|
||||
|
||||
#define DECL_Qdnm
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#define VdpInstrF(viName, viId) \
|
||||
EAPI V##viName##_F32 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { DECL_Id(viId); SET_Qdnm; EMIT_I; } \
|
||||
EAPI V##viName##_F32 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { DECL_Id(viId); SET_Ddnm; EMIT_I; }
|
||||
|
||||
|
||||
#define VdpInstrI_EOR(viName, viId) \
|
||||
EAPI V##viName (eFQReg Qd, eFQReg Qn, eFQReg Qm) { DECL_Id(viId); SET_Qdnm; EMIT_I; } \
|
||||
EAPI V##viName (eFDReg Dd, eFDReg Dn, eFDReg Dm) { DECL_Id(viId); SET_Ddnm; EMIT_I; } \
|
||||
|
||||
#define VdpInstrI(viName, viId) \
|
||||
EAPI V##viName (eFQReg Qd, eFQReg Qn, eFQReg Qm, u32 Size=32) { DECL_Id(viId); SET_Qdnm; SET_Size; EMIT_I; } \
|
||||
EAPI V##viName (eFDReg Dd, eFDReg Dn, eFDReg Dm, u32 Size=32) { DECL_Id(viId); SET_Ddnm; SET_Size; EMIT_I; } \
|
||||
\
|
||||
EAPI V##viName##_I8 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 8); } \
|
||||
EAPI V##viName##_I8 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 8); } \
|
||||
EAPI V##viName##_I16 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 16); } \
|
||||
EAPI V##viName##_I16 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 16); } \
|
||||
EAPI V##viName##_I32 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 32); } \
|
||||
EAPI V##viName##_I32 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 32); }
|
||||
|
||||
#define VdpInstrU(viName, viId) \
|
||||
EAPI V##viName (eFQReg Qd, eFQReg Qn, eFQReg Qm, u32 Size=32, u32 U=0) { DECL_Id(viId); SET_Qdnm; SET_U; SET_Size; EMIT_I; } \
|
||||
EAPI V##viName (eFDReg Dd, eFDReg Dn, eFDReg Dm, u32 Size=32, u32 U=0) { DECL_Id(viId); SET_Ddnm; SET_U; SET_Size; EMIT_I; } \
|
||||
\
|
||||
EAPI V##viName##_U8 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 8, 1); } \
|
||||
EAPI V##viName##_U8 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 8, 1); } \
|
||||
EAPI V##viName##_S8 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 8, 0); } \
|
||||
EAPI V##viName##_S8 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 8, 0); } \
|
||||
EAPI V##viName##_U16 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 16, 1); } \
|
||||
EAPI V##viName##_U16 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 16, 1); } \
|
||||
EAPI V##viName##_S16 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 16, 0); } \
|
||||
EAPI V##viName##_S16 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 16, 0); } \
|
||||
EAPI V##viName##_U32 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 32, 1); } \
|
||||
EAPI V##viName##_U32 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 32, 1); } \
|
||||
EAPI V##viName##_S32 (eFQReg Qd, eFQReg Qn, eFQReg Qm) { V##viName (Qd, Qn, Qm, 32, 0); } \
|
||||
EAPI V##viName##_S32 (eFDReg Dd, eFDReg Dn, eFDReg Dm) { V##viName (Dd, Dn, Dm, 32, 0); }
|
||||
|
||||
//# define VdpInstrImm (viName, viId)
|
||||
|
||||
|
||||
// Another three or four like the above .. immN, above w/ size, F32 w/ sz
|
||||
|
||||
|
||||
/*
|
||||
* A.SIMD Parallel Add/Sub
|
||||
*
|
||||
Vector Add VADD (integer), VADD (floating-point)
|
||||
Vector Add and Narrow, returning High Half VADDHN
|
||||
Vector Add Long, Vector Add Wide VADDL, VADDW
|
||||
Vector Halving Add, Vector Halving Subtract VHADD, VHSUB
|
||||
Vector Pairwise Add and Accumulate Long VPADAL
|
||||
Vector Pairwise Add VPADD (integer) , VPADD (floating-point)
|
||||
Vector Pairwise Add Long VPADDL
|
||||
Vector Rounding Add & Narrow, returning High Half VRADDHN
|
||||
Vector Rounding Halving Add VRHADD
|
||||
Vector Rounding Subtract & Narrow, ret. High Half VRSUBHN
|
||||
Vector Saturating Add VQADD
|
||||
Vector Saturating Subtract VQSUB
|
||||
Vector Subtract VSUB (integer), VSUB (floating-point)
|
||||
Vector Subtract and Narrow, returning High Half VSUBHN
|
||||
Vector Subtract Long, Vector Subtract Wide VSUBL, VSUBW
|
||||
*/
|
||||
|
||||
VdpInstrI(ADD, 0xF2000800)
|
||||
VdpInstrF(ADD, 0xF2000D00)
|
||||
|
||||
|
||||
VdpInstrI(HADD, 0xF2000800)
|
||||
VdpInstrI(HSUB, 0xF2000A00)
|
||||
|
||||
VdpInstrI(PADD, 0xF2000B10)
|
||||
VdpInstrF(PADD, 0xF3000D00)
|
||||
|
||||
|
||||
VdpInstrU(RHADD, 0xF3000100)
|
||||
VdpInstrU(QADD, 0xF2000010)
|
||||
VdpInstrU(QSUB, 0xF3000210)
|
||||
|
||||
VdpInstrI(SUB, 0xF3000800)
|
||||
VdpInstrF(SUB, 0xF2200D00)
|
||||
|
||||
|
||||
// VADD I { DECL_Id(0xF2000800); SET_Qdnm; EMIT_I; }
|
||||
// VADD F { DECL_Id(0xF2000D00); SET_Qdnm; EMIT_I; }
|
||||
// VADDHN { DECL_Id(0xF2800400); SET_Qdnm; EMIT_I; } // DQQ
|
||||
// VADD{L,W} { DECL_Id(0xF2800000); SET_Qdnm; EMIT_I; } // QDD || QQD
|
||||
// VH{ADD,SUB} { DECL_Id(0xF2000000); SET_Qdnm; EMIT_I; }
|
||||
// VPADAL { DECL_Id(0xF3B00600); SET_Qdnm; EMIT_I; } // QdQm || DdDm
|
||||
// VPADD I { DECL_Id(0xF2000B10); SET_Qdnm; EMIT_I; } // DDD only
|
||||
// VPADD F { DECL_Id(0xF3000D00); SET_Qdnm; EMIT_I; } // DDD only
|
||||
// VPADDL { DECL_Id(0xF3B00200); SET_Qdnm; EMIT_I; } // QdQm || DdDm
|
||||
// VRADDHN { DECL_Id(0xF3800400); SET_Qdnm; EMIT_I; } // DQQ
|
||||
// VRHADD { DECL_Id(0xF3000100); SET_Qdnm; EMIT_I; }
|
||||
// VRSUBHN { DECL_Id(0xF3800600); SET_Qdnm; EMIT_I; } // DQQ
|
||||
// VQADD { DECL_Id(0xF2000010); SET_Qdnm; EMIT_I; }
|
||||
// VQSUB { DECL_Id(0xF3000210); SET_Qdnm; EMIT_I; }
|
||||
// VSUB I { DECL_Id(0xF3000800); SET_Qdnm; EMIT_I; }
|
||||
// VSUB F { DECL_Id(0xF2200D00); SET_Qdnm; EMIT_I; }
|
||||
// VSUBHN { DECL_Id(0xF2800600); SET_Qdnm; EMIT_I; } // DQQ
|
||||
// VSUB{L,W} { DECL_Id(0xF2800200); SET_Qdnm; EMIT_I; } // QDD || QQD
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A.SIMD Bitwise
|
||||
*
|
||||
Vector Bitwise AND VAND (register)
|
||||
Vector Bitwise Bit Clear (AND complement) VBIC (immediate), VBIC (register)
|
||||
Vector Bitwise Exclusive OR VEOR
|
||||
Vector Bitwise Move VMOV (immediate), VMOV (register)
|
||||
Vector Bitwise NOT VMVN (immediate), VMVN (register)
|
||||
Vector Bitwise OR VORR (immediate), VORR (register)
|
||||
Vector Bitwise OR NOT VORN (register)
|
||||
Vector Bitwise Insert if False VBIF
|
||||
Vector Bitwise Insert if True VBIT
|
||||
Vector Bitwise Select VBSL
|
||||
*/
|
||||
|
||||
VdpInstrI(AND, 0xF2000110)
|
||||
VdpInstrI(BIC, 0xF2100110)
|
||||
VdpInstrI_EOR(EOR, 0xF3000110)
|
||||
VdpInstrI_EOR(BSL, 0xF3100110)
|
||||
VdpInstrI_EOR(BIT, 0xF3200110)
|
||||
VdpInstrI_EOR(BIF, 0xF3300110)
|
||||
VdpInstrI(ORN, 0xF2300110)
|
||||
// VAND R { DECL_Id(0xF2000110); SET_Qdnm; EMIT_I; }
|
||||
// VBIC R { DECL_Id(0xF2100110); SET_Qdnm; EMIT_I; }
|
||||
// VEOR { DECL_Id(0xF3000110); SET_Qdnm; EMIT_I; }
|
||||
// VBSL { DECL_Id(0xF3100110); SET_Qdnm; EMIT_I; }
|
||||
// VBIT { DECL_Id(0xF3200110); SET_Qdnm; EMIT_I; }
|
||||
// VBIF { DECL_Id(0xF3300110); SET_Qdnm; EMIT_I; }
|
||||
// VORN R { DECL_Id(0xF2300110); SET_Qdnm; EMIT_I; }
|
||||
|
||||
// VBIC I { DECL_Id(0xF2800030); SET_Qd; SET_IMM??; EMIT_I; }
|
||||
// VMOV I { DECL_Id(0xF2800010); SET_Qd; SET_IMM??; EMIT_I; }
|
||||
// VMVN I { DECL_Id(0xF2800030); SET_Qd; SET_IMM??; EMIT_I; }
|
||||
// VORR I { DECL_Id(0xF2800010); SET_Qd; SET_IMM??; EMIT_I; }
|
||||
// VAND I VBIC I
|
||||
// VORN I VORR I
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A.SIMD comparison
|
||||
*
|
||||
Vector Absolute Compare VACGE, VACGT, VACLE,VACLT
|
||||
Vector Compare Equal VCEQ (register)
|
||||
Vector Compare Equal to Zer VCEQ (immediate #0)
|
||||
Vector Compare Greater Than or Equal VCGE (register)
|
||||
Vector Compare Greater Than or Equal to Zero VCGE (immediate #0)
|
||||
Vector Compare Greater Than VCGT (register)
|
||||
Vector Compare Greater Than Zero VCGT (immediate #0)
|
||||
Vector Compare Less Than or Equal to Zero VCLE (immediate #0)
|
||||
Vector Compare Less Than Zero VCLT (immediate #0)
|
||||
Vector Test Bits VTST
|
||||
*/
|
||||
// VAC{COND} { DECL_Id(0xF3000E10); SET_Vdnm; EMIT_I; }
|
||||
|
||||
// VCEQ { DECL_Id(0xF3000810); SET_Vdnm; EMIT_I; } .F32 { DECL_Id(0xF2000E00); SET_Vdnm; EMIT_I; }
|
||||
VdpInstrI(CEQ, 0xF3200810)
|
||||
// VdpInstrF(CEQ, 0xF2000e00)
|
||||
// VCGE { DECL_Id(0xF2000310); SET_Vdnm; EMIT_I; } .F32 { DECL_Id(0xF3000E00); SET_Vdnm; EMIT_I; }
|
||||
VdpInstrI(CGE, 0xF2200310)
|
||||
// VdpInstrF(CGE, 0xF3000e00)
|
||||
// VCGT { DECL_Id(0xF2000300); SET_Vdnm; EMIT_I; } .F32 { DECL_Id(0xF3200E00); SET_Vdnm; EMIT_I; }
|
||||
//*SEB* 0xF220030 for S32, 0xF3200300 for U32, 0xF2000300 is for S8.
|
||||
VdpInstrI(CGT, 0xF2200300)
|
||||
//VdpInstrF(CGT, 0xF3200e00)
|
||||
// VCLE { DECL_Id(0xF3B10180); SET_Vdnm; EMIT_I; } // R is VCGE w/ operands reversed
|
||||
// VCLT { DECL_Id(0xF3B10200); SET_Vdnm; EMIT_I; } // R is VCGT w/ operands reversed
|
||||
// VTST { DECL_Id(0xF2000810); SET_Vdnm; EMIT_I; }
|
||||
|
||||
// VCEQZ { DECL_Id(0xF3B10100); SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VCGEZ { DECL_Id(0xF3B10080); SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VCGTZ { DECL_Id(0xF3B10000); SET_Vd; SET_Vm; EMIT_I; }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A.SIMD shift ** SET_imm6; needed for non Vdnm
|
||||
*
|
||||
Vector Saturating Rounding Shift Left VQRSHL
|
||||
Vector Saturating Rounding Shift Right and Narrow VQRSHRN, VQRSHRUN
|
||||
Vector Saturating Shift Left VQSHL (register), VQSHL, VQSHLU (immediate)
|
||||
Vector Saturating Shift Right and Narrow VQSHRN, VQSHRUN
|
||||
Vector Rounding Shift Left VRSHL
|
||||
Vector Rounding Shift Right VRSHR
|
||||
Vector Rounding Shift Right and Accumulate VRSRA
|
||||
Vector Rounding Shift Right and Narrow VRSHRN
|
||||
Vector Shift Left VSHL (immediate) on page A8-750 VSHL (register)
|
||||
Vector Shift Left Long VSHLL
|
||||
Vector Shift Right VSHR
|
||||
Vector Shift Right and Narrow VSHRN
|
||||
Vector Shift Left and Insert VSLI
|
||||
Vector Shift Right and Accumulate VSRA
|
||||
Vector Shift Right and Insert VSRI
|
||||
*/
|
||||
// VQRSHL { DECL_Id(0xF SET_Vdnm; EMIT_I; } // * TODO
|
||||
// VQRSHRN { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VQRSHRUN { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VQSHL R { DECL_Id(0xF SET_Vdnm; EMIT_I; }
|
||||
// VQSHL I { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VQSHLU { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VQSHRN { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VQSHRUN { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VRSHL { DECL_Id(0xF SET_Vdnm; EMIT_I; }
|
||||
// VRSHR { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VRSRA { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VRSHRN { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VSHL I { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VSHL R { DECL_Id(0xF SET_Vdnm; EMIT_I; }
|
||||
// VSHLL { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VSHR { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VSHRN { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VSLI { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VSRA { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VSRI { DECL_Id(0xF SET_Vd; SET_Vm; EMIT_I; }
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A.SIMD multiply
|
||||
*
|
||||
Vector Multiply Accumulate VMLA, VMLAL, VMLS, VMLSL (integer) , VMLA, VMLS (floating-point), VMLA, VMLAL, VMLS, VMLSL (by scalar)
|
||||
Vector Multiply Accumulate Long
|
||||
Vector Multiply Subtract
|
||||
Vector Multiply Subtract Long
|
||||
Vector Multiply VMUL, VMULL (integer and polynomial)
|
||||
Vector Multiply Long VMUL (floating-point) on page A8-664 VMUL, VMULL (by scalar)
|
||||
Vector Saturating Doubling Multiply Accumulate Long VQDMLAL, VQDMLSL
|
||||
Vector Saturating Doubling Multiply Subtract Long
|
||||
Vector Saturating Doubling Multiply Returning High Half VQDMULH
|
||||
Vector Saturating Rounding Doubling Multiply Ret. High Half VQRDMULH
|
||||
Vector Saturating Doubling Multiply Long VQDMULL
|
||||
*/
|
||||
|
||||
VdpInstrI(MLA, 0xF2000900)
|
||||
VdpInstrI(MLS, 0xF3000900)
|
||||
|
||||
VdpInstrF(MLA, 0xF2000D10)
|
||||
VdpInstrF(MLS, 0xF2200D10)
|
||||
|
||||
//by scalar
|
||||
//this should be really qd,dn,sm not dm
|
||||
EAPI VMUL_F32(eFQReg Qd,eFQReg Qn, eFDReg Dm, int idx)
|
||||
{
|
||||
DECL_Id(0xF2800840);
|
||||
|
||||
SET_Qd;
|
||||
SET_Qn;
|
||||
I |= 1<<8; //SET_F
|
||||
|
||||
SET_Dm;
|
||||
I |= 0x1<<24; //SET_Q not compatible
|
||||
|
||||
I |= 2<<20; //size to 32
|
||||
|
||||
I |= Dm&15; //only lower 15 regs are avail
|
||||
|
||||
|
||||
//set register sub index
|
||||
if (idx)
|
||||
I |= 1<<5;
|
||||
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VMLA_F32(eFQReg Qd,eFQReg Qn, eFDReg Dm, int idx)
|
||||
{
|
||||
DECL_Id(0xF2800040);
|
||||
|
||||
SET_Qd;
|
||||
SET_Qn;
|
||||
I |= 1<<8; //SET_F
|
||||
|
||||
SET_Dm;
|
||||
I |= 0x1<<24; //SET_Q not compatible
|
||||
|
||||
I |= 2<<20; //size to 32
|
||||
|
||||
I |= Dm&15; //only lower 15 regs are avail
|
||||
|
||||
|
||||
//set register sub index
|
||||
if (idx)
|
||||
I |= 1<<5;
|
||||
|
||||
EMIT_I;
|
||||
}
|
||||
// VdpInstrU(MLAL, 0xF2000D10) *QDD
|
||||
// VdpInstrU(MLSL, 0xF2200D10) *QDD
|
||||
|
||||
|
||||
VdpInstrI(MUL, 0xF2000910)
|
||||
|
||||
VdpInstrF(MUL, 0xF3000D10)
|
||||
|
||||
// VdpInstrU(MULL, 0xF3000D10) *QDD
|
||||
|
||||
// VMLA I { DECL_Id(0xF2000900); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// VMLS I { DECL_Id(0xF3000900); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// VMLAL I { DECL_Id(0xF2800800); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20) * I |= ((U&1)<<24)
|
||||
// VMLSL I { DECL_Id(0xF2800A00); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20) * I |= ((U&1)<<24)
|
||||
|
||||
// VMLA F { DECL_Id(0xF2000D10); SET_Vdnm; EMIT_I; } // * I |= ((sz&1)<<20)
|
||||
// VMLS F { DECL_Id(0xF2200D10); SET_Vdnm; EMIT_I; } // * I |= ((sz&1)<<20)
|
||||
|
||||
// VMLA S { DECL_Id(0xF2800040); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// VMLS S { DECL_Id(0xF2800440); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// CMLAL S { DECL_Id(0xF2800240); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20) * I |= ((U&1)<<24)
|
||||
// VMLSL S { DECL_Id(0xF2800640); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20) * I |= ((U&1)<<24)
|
||||
|
||||
// VMUL IP { DECL_Id(0xF2000910); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20) * I |= 1<<24 for polynomial
|
||||
// VMULL IP { DECL_Id(0xF2800C00); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20) * I |= 1<<9 for polynomial * I |= ((U&1)<<24)
|
||||
|
||||
// VMUL F { DECL_Id(0xF3000D10); SET_Vdnm; EMIT_I; } // * I |= ((sz&1)<<20)
|
||||
|
||||
// VMUL S { DECL_Id(0xF2800840); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// VMULL S { DECL_Id(0xF2800A40); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20) * I |= ((U&1)<<24)
|
||||
// VQDMLAL { DECL_Id(0xF2800900); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// VQDMLSL { DECL_Id(0xF2800B00); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// VQDMULH { DECL_Id(0xF2000B00); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// VQRDMULH { DECL_Id(0xF3000B00); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
// VQDMULL { DECL_Id(0xF2800D00); SET_Vdnm; EMIT_I; } // * I |= ((size&3)<<20)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A.SIMD misc
|
||||
*
|
||||
Vector Absolute Difference and Accumulate VABA, VABAL
|
||||
Vector Absolute Difference VABD, VABDL (integer) , VABD (floating-point)
|
||||
Vector Absolute VABS
|
||||
Vector Convert between floating-point and fixed point VCVT (between floating-point and fixed-point, Advanced SIMD)
|
||||
Vector Convert between floating-point and integer VCVT (between floating-point and integer, Advanced SIMD)
|
||||
Vector Convert between half-precision and single-precision VCVT (between half-precision and single-precision, Advanced SIMD)
|
||||
Vector Count Leading Sign Bits VCLS
|
||||
Vector Count Leading Zeros VCLZ
|
||||
Vector Count Set Bits VCNT
|
||||
Vector Duplicate scalar VDUP (scalar)
|
||||
Vector Extract VEXT
|
||||
Vector Move and Narrow VMOVN
|
||||
Vector Move Long VMOVL
|
||||
Vector Maximum, Minimum VMAX, VMIN (integer) , VMAX, VMIN (floating-point)
|
||||
Vector Negate VNEG
|
||||
Vector Pairwise Maximum, Minimum VPMAX, VPMIN (integer) , VPMAX, VPMIN (floating-point)
|
||||
Vector Reciprocal Estimate VRECPE
|
||||
Vector Reciprocal Step VRECPS
|
||||
Vector Reciprocal Square Root Estimate VRSQRTE
|
||||
Vector Reciprocal Square Root Step VRSQRTS
|
||||
Vector Reverse VREV16, VREV32, VREV64
|
||||
Vector Saturating Absolute VQABS
|
||||
Vector Saturating Move and Narrow VQMOVN, VQMOVUN
|
||||
Vector Saturating Negate VQNEG
|
||||
Vector Swap VSWP
|
||||
Vector Table Lookup VTBL, VTBX
|
||||
Vector Transpose VTRN
|
||||
Vector Unzip VUZP
|
||||
Vector Zip VZIP
|
||||
*/
|
||||
// VABA { DECL_Id(0xF2000710); // WIP
|
||||
// VABAL { DECL_Id(0xF2800500);
|
||||
|
||||
// VABD I { DECL_Id(0xF);
|
||||
// VABDL I { DECL_Id(0xF);
|
||||
|
||||
// VABD F { DECL_Id(0xF);
|
||||
|
||||
// VABS { DECL_Id(0xF); SET_Vd; SET_Vm; EMIT_I; }
|
||||
|
||||
EAPI VSQRT_F32(eFSReg Sd, eFSReg Sm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0EB10AC0); SET_CC;
|
||||
|
||||
I |= ((Sd&0x1E)<<11) | ((Sd&1)<<22);
|
||||
I |= ((Sm&0x1E)>>1) | ((Sm&1)<<5);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VABS_F32(eFSReg Sd, eFSReg Sm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0EB00AC0); SET_CC;
|
||||
|
||||
I |= ((Sd&0x1E)<<11) | ((Sd&1)<<22);
|
||||
I |= ((Sm&0x1E)>>1) | ((Sm&1)<<5);
|
||||
EMIT_I;
|
||||
}
|
||||
EAPI VNEG_F32(eFSReg Sd, eFSReg Sm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0EB10A40); SET_CC;
|
||||
|
||||
I |= ((Sd&0x1E)<<11) | ((Sd&1)<<22);
|
||||
I |= ((Sm&0x1E)>>1) | ((Sm&1)<<5);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
//imm move, fpu
|
||||
EAPI VMOV(eFSReg Sd, u32 imm8_fpu, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0EB10A00); SET_CC;
|
||||
|
||||
I |= (imm8_fpu&0x0F); //bits 3:0
|
||||
I |= (imm8_fpu&0xF0)<<12; //bits 19:16
|
||||
|
||||
I |= ((Sd&0x1E)<<11) | ((Sd&1)<<22);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
const u32 fpu_imm_1=0x70;//01110000
|
||||
|
||||
EAPI VCMP_F32(eFSReg Sd, eFSReg Sm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0EB40A40); SET_CC;
|
||||
|
||||
I |= ((Sd&0x1E)<<11) | ((Sd&1)<<22);
|
||||
I |= ((Sm&0x1E)>>1) | ((Sm&1)<<5);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
VdpInstrF(CEQ, 0xF2000E00)
|
||||
VdpInstrF(CGE, 0xF3000E00)
|
||||
VdpInstrF(CGT, 0xF3200E00)
|
||||
|
||||
// VCVT FFP { DECL_Id(0xF); SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VCVT FI { DECL_Id(0xF); SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VCVT HS { DECL_Id(0xF); SET_Vd; SET_Vm; EMIT_I; }
|
||||
|
||||
// VCLS { DECL_Id(0xF); SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VCLZ { DECL_Id(0xF); SET_Vd; SET_Vm; EMIT_I; }
|
||||
// VCNT { DECL_Id(0xF); SET_Vd; SET_Vm; EMIT_I; }
|
||||
|
||||
|
||||
// VEXT { DECL_Id(0xF); SET_Vdnm; EMIT_I; }
|
||||
|
||||
// VMAX I { DECL_Id(0xF);
|
||||
// VMIN I { DECL_Id(0xF);
|
||||
// VMAX F { DECL_Id(0xF);
|
||||
// VMIN F { DECL_Id(0xF);
|
||||
// VNEG { DECL_Id(0xF);
|
||||
|
||||
// VPMAX I { DECL_Id(0xF);
|
||||
// VPMIN I { DECL_Id(0xF);
|
||||
// VPMAX F { DECL_Id(0xF);
|
||||
// VPMIN F { DECL_Id(0xF);
|
||||
|
||||
// VRECPE { DECL_Id(0xF3B30400); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18
|
||||
// VRECPS { DECL_Id(0xF2000F10); SET_Vdnm; EMIT_I; } // sz&1<<20 (.F32)
|
||||
// VRSQRTE { DECL_Id(0xF3B30480); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18 F&1<<8 ***
|
||||
// VRSQRTS { DECL_Id(0xF2200F10); SET_Vdnm; EMIT_I; } // sz&1<<20 (.F32)
|
||||
// VREVsz { DECL_Id(0xF3B00000); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18 op&3<<7 ***
|
||||
|
||||
// VQABS { DECL_Id(0xF3B00700); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18
|
||||
// VQMOVN { DECL_Id(0xF3B20200); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18 op&3<<6 op:00=MOVN op11=srcUnsigned op:x1=dstUnsigned
|
||||
// VQMOVUN { DECL_Id(0xF3B20200); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18
|
||||
// VQNEG { DECL_Id(0xF3B00780); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18
|
||||
|
||||
// VSWP { DECL_Id(0xF3B20000); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18
|
||||
// VTBL { DECL_Id(0xF3B00800); SET_Vdnm; EMIT_I; } // len&3<<8
|
||||
// VTBX { DECL_Id(0xF3B00840); SET_Vdnm; EMIT_I; } // len&3<<8
|
||||
// VTRN { DECL_Id(0xF3B20080); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18
|
||||
// VUZP { DECL_Id(0xF3B20100); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18
|
||||
// VZIP { DECL_Id(0xF3B20180); SET_Vd; SET_Vm; EMIT_I; } // size&3<<18
|
||||
//
|
||||
|
||||
|
||||
// VDUP & VMOVN & VMOVL are implemented in VRegXfer.h ...
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VFPv3 Instructions
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
#define SET_Sd \
|
||||
I |= ((Sd&0x1E)<<11); \
|
||||
I |= ((Sd&0x01)<<22)
|
||||
|
||||
#define SET_Sn \
|
||||
I |= ((Sn&0x1E)<<15); \
|
||||
I |= ((Sn&0x01)<<7)
|
||||
|
||||
#define SET_Sm \
|
||||
I |= ((Sm&0x1E)>>1); \
|
||||
I |= ((Sm&0x01)<<5)
|
||||
|
||||
|
||||
|
||||
#define SET_Sdnm SET_Sd; SET_Sn; SET_Sm
|
||||
|
||||
|
||||
#define VfpInstrS(viName, viId) EAPI V##viName##_VFP (eFSReg Sd, eFSReg Sn, eFSReg Sm, ConditionCode CC=CC_AL) { DECL_Id(viId); SET_CC; SET_Sdnm; EMIT_I; }
|
||||
|
||||
VfpInstrS(MLA, 0x0E000A00)
|
||||
VfpInstrS(MLS, 0x0E000A40)
|
||||
|
||||
VfpInstrS(NMLA, 0x0E100A40)
|
||||
VfpInstrS(NMLS, 0x0E100A00)
|
||||
VfpInstrS(NMUL, 0x0E200A40)
|
||||
|
||||
|
||||
VfpInstrS(MUL, 0x0E200A00)
|
||||
|
||||
VfpInstrS(ADD, 0x0E300A00)
|
||||
VfpInstrS(SUB, 0x0E300A40)
|
||||
|
||||
VfpInstrS(DIV, 0x0E800A00)
|
||||
|
||||
|
||||
EAPI VCVT_to_S32_VFP (eFSReg Sd, eFSReg Sm, ConditionCode CC=CC_AL) { DECL_Id(0x0EBD0AC0); SET_CC; SET_Sd; SET_Sm; EMIT_I; } // VfpInstrS(ABS, 0x0EB00AC0) ** {D,S}dm
|
||||
// 0x0EB80A40 is to_U32. to_S32 is 0x0EB80AC0
|
||||
EAPI VCVT_from_S32_VFP (eFSReg Sd, eFSReg Sm, ConditionCode CC=CC_AL) { DECL_Id(0x0EB80AC0); SET_CC; SET_Sd; SET_Sm; EMIT_I; } // VfpInstrS(ABS, 0x0EB00AC0) ** {D,S}dm
|
||||
|
||||
EAPI VABS_VFP (eFSReg Sd, eFSReg Sm, ConditionCode CC=CC_AL) { DECL_Id(0x0EB00AC0); SET_CC; SET_Sd; SET_Sm; EMIT_I; } // VfpInstrS(ABS, 0x0EB00AC0) ** {D,S}dm
|
||||
EAPI VNEG_VFP (eFSReg Sd, eFSReg Sm, ConditionCode CC=CC_AL) { DECL_Id(0x0EB10A40); SET_CC; SET_Sd; SET_Sm; EMIT_I; } // VfpInstrS(NEG, 0x0EB10A40) ** {D,S}dm
|
||||
EAPI VSQRT_VFP(eFSReg Sd, eFSReg Sm, ConditionCode CC=CC_AL) { DECL_Id(0x0EB10AC0); SET_CC; SET_Sd; SET_Sm; EMIT_I; } // VfpInstrS(SQRT, 0x0EB10AC0) ** {D,S}dm
|
||||
|
||||
// - x0 Vector Move VMOV (immediate) on page A8-640
|
||||
// 0000 01 Vector Move VMOV (register) on page A8-642
|
||||
|
||||
// 001x x1 Vector Convert VCVTB, VCVTT (between half-precision and single-precision, VFP) on page A8-588
|
||||
// 010x x1 Vector Compare VCMP, VCMPE on page A8-572
|
||||
// 0111 11 Vector Convert VCVT (between double-precision and single-precision) on page A8-584
|
||||
// 1000 x1 Vector Convert VCVT, VCVTR (between floating-point and integer, VFP) on page A8-578
|
||||
// 101x x1 Vector Convert VCVT (between floating-point and fixed-point, VFP) on page A8-582
|
||||
// 110x x1 Vector Convert VCVT, VCVTR (between floating-point and integer, VFP) on page A8-578
|
||||
// 111x x1 Vector Convert VCVT (between floating-point and fixed-point, VFP) on page A8-582
|
||||
|
||||
////// hack
|
||||
|
||||
EAPI VDIV_HACKF32(eFDReg Sd, eFDReg Sn, eFDReg Sm)
|
||||
{
|
||||
ConditionCode CC=AL;
|
||||
|
||||
eFSReg SdS=(eFSReg)(Sd*2);
|
||||
eFSReg SnS=(eFSReg)(Sn*2);
|
||||
eFSReg SmS=(eFSReg)(Sm*2);
|
||||
|
||||
verify(Sd<32 && Sn<32 && Sm<32);
|
||||
|
||||
VDIV_VFP(SdS,SnS,SmS);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#undef SET_Qd
|
||||
#undef SET_Dd
|
||||
|
||||
#undef SET_Qn
|
||||
#undef SET_Dn
|
||||
|
||||
#undef SET_Qm
|
||||
#undef SET_Dm
|
||||
|
||||
#undef SET_Q
|
||||
|
||||
#undef SET_Qdnm
|
||||
#undef SET_Ddnm
|
||||
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* E_VLoadStore.h * VFP/A.SIMD Load/Store Instruction Set Encoding
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
// [cond][110][Opcode][-Rn-][----][101][---------]
|
||||
//
|
||||
// cond != 0b1111 | LDC && STC consume these
|
||||
|
||||
|
||||
|
||||
#define SET_Dd \
|
||||
I |= ((Dd&0x0F)<<12); \
|
||||
I |= ((Dd&0x10)<<18)
|
||||
|
||||
#define SET_Sd \
|
||||
I |= ((Sd&0x1E)<<11); \
|
||||
I |= ((Sd&0x01)<<22)
|
||||
|
||||
|
||||
#define SET_Rn \
|
||||
I |= ((Rn&15)<<16)
|
||||
|
||||
#define SET_uImm8 \
|
||||
I |= (uImm8 & 255)
|
||||
|
||||
#define SET_sImm8 \
|
||||
if (sImm8 > 0) { \
|
||||
I |= (1<<23); \
|
||||
} \
|
||||
I |= (abs(sImm8) & 255)
|
||||
|
||||
|
||||
#define SET_PUW(_P,_U,_W) \
|
||||
I |= ( ((_P&1)<<24) | ((_U&1)<<23) | ((_W&1)<<21) )
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* V{LD,ST}R: Vector Load/Store Register
|
||||
*
|
||||
* V{LD,ST}R.64 // VFP && A.SIMD
|
||||
* V{LD,ST}R.32 // VFP
|
||||
*/
|
||||
|
||||
EAPI VLDR(eFDReg Dd, eReg Rn, s32 sImm8, ConditionCode CC=AL) // VLDR.64
|
||||
{
|
||||
DECL_Id(0x0D100B00); SET_CC;
|
||||
SET_Dd; SET_Rn; SET_sImm8;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VLDR(eFSReg Sd, eReg Rn, s32 sImm8, ConditionCode CC=AL) // VLDR.32
|
||||
{
|
||||
DECL_Id(0x0D100A00); SET_CC;
|
||||
SET_Sd; SET_Rn; SET_sImm8;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI VSTR(eFDReg Dd, eReg Rn, s32 sImm8, ConditionCode CC=AL) // VSTR.64
|
||||
{
|
||||
DECL_Id(0x0D000B00); SET_CC;
|
||||
SET_Dd; SET_Rn; SET_sImm8;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VSTR(eFSReg Sd, eReg Rn, s32 sImm8, ConditionCode CC=AL) // VSTR.32
|
||||
{
|
||||
DECL_Id(0x0D000A00); SET_CC;
|
||||
SET_Sd; SET_Rn; SET_sImm8;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* V{LD,ST}M: Vector Load/Store Multiple
|
||||
*
|
||||
* V{LD,ST}R.64 // VFP && A.SIMD
|
||||
* V{LD,ST}R.32 // VFP
|
||||
*
|
||||
* uImm8: directional count, abs(sImm8) is the reg. count
|
||||
* Dd: Register to start sequential operation from..
|
||||
*
|
||||
* suffix DB: P=1 U=0 W=1, Decrement Before, Addresses end just before the address in Rn.
|
||||
* suffix IA: P=0 U=1 W=?, Increment After, Addresses start at address in Rn
|
||||
*
|
||||
* ** These are very complicated encoding and require a lot of error checking and even more thought when used. **
|
||||
* ** Simply using, MOV32(R4, &double_array[0]); VLDM(D0, R4, 4); should however work to load an array of 4 doubles **
|
||||
*/
|
||||
|
||||
EAPI VLDM(eFDReg Dd, eReg Rn, u32 uImm8, u32 WB=0, ConditionCode CC=AL) // VLDM.64
|
||||
{
|
||||
// ASSERT( (uImm8>0) && (uImm8<=16) && ((Dd+uImm8) <= 32) )
|
||||
uImm8<<=1;
|
||||
DECL_Id(0x0C100B00); SET_CC;
|
||||
SET_Dd; SET_Rn;
|
||||
SET_uImm8; SET_PUW(0,1,WB); // Defaulting to IA w/o ! (Write-back)
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VLDM(eFSReg Sd, eReg Rn, u32 uImm8, ConditionCode CC=AL) // VLDM.32
|
||||
{
|
||||
// ASSERT( (uImm8>0) && ((Dd+uImm8) <= 32) )
|
||||
DECL_Id(0x0C100A00); SET_CC;
|
||||
SET_Sd; SET_Rn;
|
||||
SET_uImm8; SET_PUW(0,1,0); // Defaulting to IA w/o ! (Write-back)
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI VSTM(eFDReg Dd, eReg Rn, u32 uImm8, ConditionCode CC=AL) // VSTM.64
|
||||
{
|
||||
// ASSERT( (uImm8>0) && (uImm8<=16) && ((Dd+uImm8) <= 32) )
|
||||
uImm8<<=1;
|
||||
DECL_Id(0x0C000B00); SET_CC;
|
||||
SET_Dd; SET_Rn;
|
||||
SET_uImm8; SET_PUW(0,1,0); // Defaulting to IA w/o ! (Write-back)
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VSTM(eFSReg Sd, eReg Rn, u32 uImm8, ConditionCode CC=AL) // VSTM.32
|
||||
{
|
||||
// ASSERT( (uImm8>0) && ((Dd+uImm8) <= 32) )
|
||||
DECL_Id(0x0C000A00); SET_CC;
|
||||
SET_Sd; SET_Rn;
|
||||
SET_uImm8; SET_PUW(0,1,0); // Defaulting to IA w/o ! (Write-back)
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* V{LD,ST}n: Various extra load/store multiple.
|
||||
*
|
||||
* Not Implemented.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VPUSH/VPOP: Vector Load/Store multiple consecutive vector registers to the stack.
|
||||
*
|
||||
* V{PUSH,POP} .64: A.SIMD .32 VFP
|
||||
*/
|
||||
|
||||
EAPI VPUSH(eFDReg Dd, eReg Rn, u32 uImm8, ConditionCode CC=AL) // VPUSH.64
|
||||
{
|
||||
uImm8<<=1;
|
||||
DECL_Id(0x0D2D0B00); SET_CC;
|
||||
SET_Dd; SET_uImm8;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VPUSH(eFSReg Sd, eReg Rn, u32 uImm8, ConditionCode CC=AL) // VPUSH.32
|
||||
{
|
||||
DECL_Id(0x0D2D0A00); SET_CC;
|
||||
SET_Sd; SET_uImm8;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
EAPI VPOP(eFDReg Dd, eReg Rn, u32 uImm8, ConditionCode CC=AL) // VPOP.64
|
||||
{
|
||||
uImm8<<=1;
|
||||
DECL_Id(0x0CBD0B00); SET_CC;
|
||||
SET_Dd; SET_uImm8;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VPOP(eFSReg Sd, eReg Rn, u32 uImm8, ConditionCode CC=AL) // VPOP.32
|
||||
{
|
||||
DECL_Id(0x0CBD0A00); SET_CC;
|
||||
SET_Sd; SET_uImm8;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Best practice is to remove macro definitions,
|
||||
* this way they can't affect subsequent headers.
|
||||
*/
|
||||
|
||||
#undef SET_Dd
|
||||
#undef SET_Sd
|
||||
|
||||
#undef SET_Rn
|
||||
|
||||
#undef SET_uImm8
|
||||
#undef SET_sImm8
|
||||
|
||||
#undef SET_PUW
|
||||
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* E_VRegXfer.h * VFP/A.SIMD Register Transfer Instruction Set Encoding
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
|
||||
|
||||
/////// REPLACE THIS MESS W. SET_Rto16 / SET_Rto16_H22 , SET_Rto12 , SET_Rto0_H5 OR just leave it all in the fn's //////
|
||||
|
||||
|
||||
#define SET_Qd \
|
||||
I |= (((Qd<<1)&0x0E)<<16); \
|
||||
I |= (((Qd<<1)&0x10)<<3)
|
||||
|
||||
#define SET_Qm \
|
||||
I |= (((Qm<<1)&0x0E)); \
|
||||
I |= (((Qm<<1)&0x10)<<1)
|
||||
|
||||
|
||||
#define SET_Dd \
|
||||
I |= ((Dd&0x0F)<<16); \
|
||||
I |= ((Dd&0x10)<<3)
|
||||
|
||||
#define SET_Dm \
|
||||
I |= ((Dm&0x0F)); \
|
||||
I |= ((Dm&0x10)<<1)
|
||||
|
||||
|
||||
#define SET_Sn \
|
||||
I |= ((Sn&0x1E)<<15); \
|
||||
I |= ((Sn&0x01)<<7)
|
||||
|
||||
#define SET_Sm \
|
||||
I |= ((Sm&0x1E)>>1); \
|
||||
I |= ((Sm&0x01)<<5)
|
||||
|
||||
|
||||
#define SET_Rn \
|
||||
I |= ((Rn&15)<<16)
|
||||
|
||||
#define SET_Rt \
|
||||
I |= ((Rt&15)<<12)
|
||||
|
||||
#define SET_Rtx2 \
|
||||
I |= ((Rt&15)<<12) | ((Rt2&15)<<16)
|
||||
|
||||
// VDUP VMOV VMRS VMSR
|
||||
|
||||
/*
|
||||
* VDUP.SZ: Duplicates an element from ARM reg Rt into every element of {Q,D}d A.SIMD
|
||||
*
|
||||
*/
|
||||
EAPI VDUP(eFQReg Qd, eReg Rt, u32 Size=32, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0E800B10); SET_CC;
|
||||
SET_Qd; SET_Rt; I |= (1<<21); // Q
|
||||
if (Size==16) { I |= (1<<5); } // e
|
||||
if (Size==8) { I |= (1<<22); } // b
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VDUP(eFDReg Dd, eReg Rt, u32 Size=32, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0E800B10); SET_CC;
|
||||
SET_Dd; SET_Rt; // No Q
|
||||
if (Size==16) { I |= (1<<5); } // e
|
||||
if (Size==8) { I |= (1<<22); } // b
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VDUP8 (eFQReg Qd, eReg Rt, ConditionCode=AL) { VDUP(Qd,Rt,8, CC); }
|
||||
EAPI VDUP8 (eFDReg Dd, eReg Rt, ConditionCode=AL) { VDUP(Dd,Rt,8, CC); }
|
||||
EAPI VDUP16(eFQReg Qd, eReg Rt, ConditionCode=AL) { VDUP(Qd,Rt,16,CC); }
|
||||
EAPI VDUP16(eFDReg Dd, eReg Rt, ConditionCode=AL) { VDUP(Dd,Rt,16,CC); }
|
||||
EAPI VDUP32(eFQReg Qd, eReg Rt, ConditionCode=AL) { VDUP(Qd,Rt,32,CC); }
|
||||
EAPI VDUP32(eFDReg Dd, eReg Rt, ConditionCode=AL) { VDUP(Dd,Rt,32,CC); }
|
||||
|
||||
EAPI VDUP32(eFQReg Qd, eFDReg Dm, int idx)
|
||||
{
|
||||
DECL_Id(0xF3B00C00);
|
||||
|
||||
//Set_Qd seems to be incompitable here ?
|
||||
I |= (((Qd<<1)&0x0E)<<12); \
|
||||
I |= (((Qd<<1)&0x10)<<18);
|
||||
SET_Dm;
|
||||
I |= 0x40; //SET_Q
|
||||
I |= 0x4 << 16; // 32 bits 4=0100
|
||||
I |= (idx&1) << 19; // set idx
|
||||
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOV: (register)
|
||||
*
|
||||
* VMOV { <Qd, Qm> , <Dd, Dm> } A.SIMD
|
||||
* VMOV { <Dd, Dm> , <Sd, Sm> } VFP sz1 UNDEFINED in single only VFP
|
||||
*/
|
||||
|
||||
EAPI VMOV(eFQReg Qd, eFQReg Qm) // UNCONDITIONAL
|
||||
{
|
||||
DECL_Id(0xF2200110);
|
||||
|
||||
I |= ((Qd&0x0F)<<12) | ((Qd&0x10)<<18);
|
||||
I |= ((Qm&0x0F)<<16) | ((Qm&0x10)<<1); // If !Consistent(M:Qm) then its VORR
|
||||
I |= ((Qm&0x0F)) | ((Qm&0x10)<<3); // If !Consistent(M:Qm) then its VORR
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VMOV(eFDReg Dd, eFDReg Dm) // UNCONDITIONAL
|
||||
{
|
||||
DECL_Id(0xF2200110);
|
||||
|
||||
I |= ((Dd&0x0F)<<12) | ((Dd&0x10)<<18);
|
||||
I |= ((Dm&0x0F)<<16) | ((Dm&0x10)<<3); // If !Consistent(M:Dm) then its VORR
|
||||
I |= ((Dm&0x0F)) | ((Dm&0x10)<<1); // If !Consistent(M:Dm) then its VORR
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
// EAPI VMOV(eFDReg Dd, eFDReg Dm, ConditionCode CC=AL) {} VFP Double Version Not Implemented here for obvious reasons : same as below except would set SZ @bit8 : 0x0EB00B40
|
||||
|
||||
EAPI VMOV(eFSReg Sd, eFSReg Sm, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0EB00A40); SET_CC;
|
||||
|
||||
I |= ((Sd&0x1E)<<11) | ((Sd&1)<<22);
|
||||
I |= ((Sm&0x1E)>>1) | ((Sm&1)<<5);
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOV: (Immediate) A.SIMD / VFP
|
||||
*
|
||||
*/
|
||||
|
||||
//// TO BE IMPLEMENTED ////
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOV: (ARM to scalar) A.SIMD / VFP IF Size=32
|
||||
*
|
||||
*/
|
||||
EAPI VMOV(eFDReg Dd, u32 Index, eReg Rt, u32 Size=32, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0E000B10); SET_CC;
|
||||
SET_Dd; SET_Rt;
|
||||
// Dd[x] where x==Index Dd is 64b, 2x32[0,1](1bit) 4x16[0-3](2bits) 8x8[0-7](3bits)
|
||||
if (Size== 8) { I |= (1<<22) | ((Index&4)<<18) | ((Index&3)<<5) ; } // x -> opc1:0, opc2 (3bits) | opc1:1 SET
|
||||
if (Size==16) { I |= (1<<5) | ((Index&2)<<20) | ((Index&1<<6)) ; } // x -> opc1:0, opc2:1 (2bits) | opc2:0 SET
|
||||
if (Size==32) { I |= ((Index&1)<<21) ; } // x -> opc1:0 (1bit)
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOV: (scalar to ARM) A.SIMD / VFP IF Size=32
|
||||
*
|
||||
* Note: U (bit32) is unsigned bit, invalid for 32b and we do not handle it at all.. might want to set it for byte,short
|
||||
*/
|
||||
EAPI VMOV(eReg Rt, eFDReg Dd, u32 Index, u32 Size=32, ConditionCode CC=AL) // This is really Vn, but we'll use the same macros..
|
||||
{
|
||||
DECL_Id(0x0E100B10); SET_CC;
|
||||
SET_Dd; SET_Rt;
|
||||
// Dd[x] where x==Index Dd is 64b, 2x32[0,1](1bit) 4x16[0-3](2bits) 8x8[0-7](3bits)
|
||||
if (Size== 8) { I |= (1<<22) | ((Index&4)<<18) | ((Index&3)<<5) ; } // x -> opc1:0, opc2 (3bits) | opc1:1 SET
|
||||
if (Size==16) { I |= (1<<5) | ((Index&2)<<20) | ((Index&1<<6)) ; } // x -> opc1:0, opc2:1 (2bits) | opc2:0 SET
|
||||
if (Size==32) { I |= ((Index&1)<<21) ; } // x -> opc1:0 (1bit)
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOV: (between ARM and single either direction) VFP
|
||||
*
|
||||
*/
|
||||
EAPI VMOV(eReg Rt, eFSReg Sn, ConditionCode CC=AL) // Sn !d
|
||||
{
|
||||
DECL_Id(0x0E000A10); SET_CC;
|
||||
SET_Sn; SET_Rt; I |= (1<<20); // op set = TO ARM reg
|
||||
EMIT_I;
|
||||
}
|
||||
EAPI VMOV(eFSReg Sn, eReg Rt, ConditionCode CC=AL) // Sn !d
|
||||
{
|
||||
DECL_Id(0x0E000A10); SET_CC;
|
||||
SET_Sn; SET_Rt; // op NOT set = TO FP Single reg
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOV: (between two ARM regs and two contiguous singles either direction) VFP
|
||||
*
|
||||
*/
|
||||
EAPI VMOV(eReg Rt, eReg Rt2, eFSReg Sm, ConditionCode CC=AL) // Sn !d
|
||||
{
|
||||
DECL_Id(0x0E000A10); SET_CC;
|
||||
SET_Sm; SET_Rtx2; I |= (1<<20); // op set = TO ARM regs
|
||||
EMIT_I;
|
||||
}
|
||||
EAPI VMOV(eFSReg Sm, eReg Rt, eReg Rt2, ConditionCode CC=AL) // Sn !d
|
||||
{
|
||||
DECL_Id(0x0E000A10); SET_CC;
|
||||
SET_Sm; SET_Rtx2; // op NOT set = TO FP Single(s)
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOV: (between two ARM regs and a Double) Dm <-> Rt2:Rt A.SIMD/VFP
|
||||
*
|
||||
*/
|
||||
EAPI VMOV(eReg Rt, eReg Rt2, eFDReg Dm, ConditionCode CC=AL) // Sn !d
|
||||
{
|
||||
DECL_Id(0x0C400B10); SET_CC;
|
||||
SET_Dm; SET_Rtx2; I |= (1<<20); // op set = TO ARM regs
|
||||
EMIT_I;
|
||||
}
|
||||
EAPI VMOV(eFDReg Dm, eReg Rt, eReg Rt2, ConditionCode CC=AL) // Sn !d
|
||||
{
|
||||
DECL_Id(0x0C400B10); SET_CC;
|
||||
SET_Dm; SET_Rtx2; // op NOT set = TO FP Single(s)
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOVL: Takes each element in a VDouble && Sign or Zero extends into a VQuad A.SIMD
|
||||
*
|
||||
*/
|
||||
EAPI VMOVL(eFQReg Qd, eFDReg Dm, u32 Size=32, u32 Sign=0) // UNCONDITIONAL Q & ~1
|
||||
{
|
||||
Size >>= 3; // Sz/8 = 1,2 or 4 else if >0 its VSHLL
|
||||
Sign = (Sign>0)?0:1; // Invert to Unsigned
|
||||
DECL_Id(0xF2800A10);
|
||||
|
||||
SET_Dm;
|
||||
I |= ((Qd&0x0F)<<12);
|
||||
I |= ((Qd&0x10)<<18);
|
||||
I |= ((Size &7)<<19); // imm3
|
||||
I |= ((Sign &1)<<24); // U
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VMOVL_S8 (eFQReg Qd, eFDReg Dm) { VMOVL(Qd,Dm,8,1); }
|
||||
EAPI VMOVL_U8 (eFQReg Qd, eFDReg Dm) { VMOVL(Qd,Dm,8,0); }
|
||||
EAPI VMOVL_S16(eFQReg Qd, eFDReg Dm) { VMOVL(Qd,Dm,16,1); }
|
||||
EAPI VMOVL_U16(eFQReg Qd, eFDReg Dm) { VMOVL(Qd,Dm,16,0); }
|
||||
EAPI VMOVL_S32(eFQReg Qd, eFDReg Dm) { VMOVL(Qd,Dm,32,1); }
|
||||
EAPI VMOVL_U32(eFQReg Qd, eFDReg Dm) { VMOVL(Qd,Dm,32,0); }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VMOVN: Copies least significant half of each element of a VQuad into the elements of a VDouble A.SIMD
|
||||
*
|
||||
*/
|
||||
EAPI VMOVN(eFDReg Dd, eFQReg Qm, u32 Size=32) // UNCONDITIONAL Q & ~1
|
||||
{
|
||||
Size >>= 4; // Sz/32 = 0,1 or 2
|
||||
DECL_Id(0xF3B20200);
|
||||
|
||||
SET_Qm;
|
||||
I |= ((Dd&0x0F)<<12);
|
||||
I |= ((Dd&0x10)<<18);
|
||||
I |= ((Size &3)<<18); // size
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
EAPI VMOVN16(eFDReg Dd, eFQReg Qm) { VMOVN(Dd,Qm,16); }
|
||||
EAPI VMOVN32(eFDReg Dd, eFQReg Qm) { VMOVN(Dd,Qm,32); }
|
||||
EAPI VMOVN64(eFDReg Dd, eFQReg Qm) { VMOVN(Dd,Qm,64); }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* VM{RS,SR} Move ARM reg To/From FPSCR A.SIMD/VFP
|
||||
*/
|
||||
EAPI VMRS(eReg Rt, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0EF10A10);
|
||||
SET_CC; SET_Rt;
|
||||
EMIT_I;
|
||||
}
|
||||
EAPI VMSR(eReg Rt, ConditionCode CC=AL)
|
||||
{
|
||||
DECL_Id(0x0EE10A10);
|
||||
SET_CC; SET_Rt;
|
||||
EMIT_I;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#undef SET_Qd
|
||||
#undef SET_Qm
|
||||
|
||||
#undef SET_Dd
|
||||
#undef SET_Dm
|
||||
|
||||
#undef SET_Sn
|
||||
#undef SET_Sm
|
||||
|
||||
#undef SET_Rn
|
||||
#undef SET_Rt
|
||||
#undef SET_Rtx2
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
BL(lit,CC);
|
||||
}
|
||||
|
||||
|
||||
|
||||
EAPI JUMP(unat FnAddr, ConditionCode CC=AL)
|
||||
{
|
||||
snat lit = Literal(FnAddr);
|
||||
|
||||
/*if(0==lit) {
|
||||
printf("Error, Compiler caught NULL literal, JUMP(%08X)\n", FnAddr);
|
||||
verify(false);
|
||||
return;
|
||||
}*/
|
||||
if( (lit<-33554432) || (lit>33554428) ) // ..28 for BL ..30 for BLX
|
||||
{
|
||||
printf("Warning, %X is out of range for imm jump! \n", FnAddr);
|
||||
//verify(false);
|
||||
|
||||
MOV32(IP, FnAddr, CC);
|
||||
BX(IP, CC);
|
||||
return;
|
||||
}
|
||||
|
||||
B(lit,CC); // Note, wont work for THUMB*, have to use bx which is reg only !
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* H_LoadStore.h
|
||||
*
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
/*
|
||||
* Load Helpers
|
||||
*/
|
||||
|
||||
EAPI LoadImmBase(eReg Rt, u32 Base, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rt, Base, CC);
|
||||
|
||||
#if defined(_DEVEL)
|
||||
LDR(Rt,Rt,0, Offset, CC);
|
||||
#else
|
||||
LDR(Rt,Rt,0, CC);
|
||||
#endif
|
||||
}
|
||||
|
||||
EAPI LoadImmBase(eReg Rt, eReg Rn, u32 Base, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rn, Base, CC);
|
||||
|
||||
#if defined(_DEVEL)
|
||||
LDR(Rt,Rn,0, Offset, CC);
|
||||
#else
|
||||
LDR(Rt,Rn,0, CC);
|
||||
#endif
|
||||
}
|
||||
|
||||
EAPI LoadImmBase16(eReg Rt, u32 Base, bool Extend=false, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rt, Base, CC);
|
||||
LDRH(Rt,Rt,0, CC);
|
||||
|
||||
if(Extend)
|
||||
SXTH(Rt,Rt);
|
||||
}
|
||||
|
||||
EAPI LoadImmBase16(eReg Rt, eReg Rn, u32 Base, bool Extend=false, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rn, Base, CC);
|
||||
LDRH(Rt,Rn,0, CC);
|
||||
|
||||
if(Extend)
|
||||
SXTH(Rt,Rt);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Store Helpers
|
||||
*/
|
||||
|
||||
// you pick regs, loads Base with reg addr, you supply data in Rt
|
||||
EAPI StoreImmBase(eReg Rt, eReg Rn, u32 Base, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rn, Base, CC);
|
||||
|
||||
#if defined(_DEVEL)
|
||||
STR(Rt,Rn,0, Offset, CC);
|
||||
#else
|
||||
STR(Rt,Rn,0, CC);
|
||||
#endif
|
||||
}
|
||||
|
||||
// you pick regs, loads Rt with const val, you supply base for Rn
|
||||
EAPI StoreImmVal(eReg Rt, eReg Rn, u32 Val, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rt, Val, CC);
|
||||
|
||||
#if defined(_DEVEL)
|
||||
STR(Rt,Rn,0, Offset, CC);
|
||||
#else
|
||||
STR(Rt,Rn,0, CC);
|
||||
#endif
|
||||
}
|
||||
|
||||
// you pick regs, loads Base with reg addr, loads Rt with const val
|
||||
EAPI StoreImms(eReg Rt, eReg Rn, u32 Base, u32 Val, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rn, Base, CC);
|
||||
MOV32(Rt, Val, CC);
|
||||
|
||||
#if defined(_DEVEL)
|
||||
STR(Rt,Rn,0, Offset, CC);
|
||||
#else
|
||||
STR(Rt,Rn,0, CC);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if defined(_DEVEL) && 0 // These require testing //
|
||||
|
||||
EAPI LoadImmBase8(eReg Rt, u32 Base, bool Extend=false, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rt, Base, CC);
|
||||
LDRB(Rt,Rt,0, CC);
|
||||
|
||||
if(Extend)
|
||||
SXTB(Rt,Rt);
|
||||
}
|
||||
|
||||
EAPI LoadImmBase8(eReg Rt, eReg Rn, u32 Base, bool Extend=false, ConditionCode CC=AL)
|
||||
{
|
||||
MOV32(Rn, Base, CC);
|
||||
LDRB(Rt,Rn,0, CC);
|
||||
|
||||
if(Extend)
|
||||
SXTB(Rt,Rt);
|
||||
}
|
||||
|
||||
#endif // defined(_DEVEL)
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* H_fp.h
|
||||
*
|
||||
* ARMv7 floating point help routines.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
/// WIP ///
|
||||
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
#if defined(_DEVEL)
|
||||
|
||||
|
||||
/*
|
||||
* vfpVersion(): Returns VFP Arch. Version, or -1 if not supported
|
||||
*/
|
||||
int vfpVersion()
|
||||
{
|
||||
// FPSID bits [22:16] contain version
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int neonVersion()
|
||||
{
|
||||
// ??
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#define VFP_SINGLE (1<<0)
|
||||
#define VFP_DOUBLE (1<<1)
|
||||
#define NEON_INTEGER (1<<2)
|
||||
#define NEON_SINGLE (1<<3)
|
||||
#define VFP_TRAPS (1<<8) // VFPv3U
|
||||
#define FPEXT_HALF (1<<16) // Half precision extension
|
||||
|
||||
u32 fpFeatures()
|
||||
{
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#endif // _DEVEL
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#if 0
|
||||
|
||||
Version 1 of the Common VFP subarchitecture has special behavior when the FPSCR.IXE bit is set to 1.
|
||||
The Common VFP subarchitecture version can be identified by checking FPSID bits [22:16]. This field is
|
||||
0b0000001 for version 1. In version 1 of the Common VFP subarchitecture the FPEXC.DEX bit is RAZ/WI.
|
||||
|
||||
|
||||
Detecting which VFP Common subarchitecture registers are implemented
|
||||
An implementation can choose not to implement FPINST and FPINST2, if these registers are not required.
|
||||
|
||||
Set FPEXC.EX=1 and FPEXC.FP2V=1
|
||||
Read back the FPEXC register
|
||||
if FPEXC.EX == 0 then
|
||||
Neither FPINST nor FPINST2 are implemented
|
||||
else
|
||||
if FPEXC.FP2V == 0 then
|
||||
FPINST is implemented, FPINST2 is not implemented.
|
||||
else
|
||||
Both FPINST and FPINST2 are implemented.
|
||||
Clean up
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* H_psuedo.h
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
EAPI MOV32(eReg Rd, u32 Imm32, ConditionCode CC=AL)
|
||||
{
|
||||
MOVW(Rd,((Imm32)&0xFFFF),CC);
|
||||
if (Imm32>>16)
|
||||
MOVT(Rd,((Imm32>>16)&0xFFFF),CC);
|
||||
}
|
||||
#if 0
|
||||
EAPI NEG(eReg Rd,eReg Rs)
|
||||
{
|
||||
RSB(Rd,Rs,0);
|
||||
}
|
||||
#endif
|
||||
EAPI NOT(eReg Rd,eReg Rs)
|
||||
{
|
||||
MVN(Rd,Rs);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* H_state.h
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
enum InstructionSet {
|
||||
IS_ARM,
|
||||
IS_Thumb,
|
||||
IS_Jazelle,
|
||||
IS_ThumbEE
|
||||
};
|
||||
|
||||
enum Endian {
|
||||
E_Little,
|
||||
E_Big
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* coding.h, arm binary instruction coding
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
|
||||
/*
|
||||
* Encoding Hi-Order 4bits, ConditionCode
|
||||
*
|
||||
*/
|
||||
|
||||
enum ConditionCode
|
||||
{
|
||||
EQ = 0x00, Equal = EQ, // 0000 Equal Z set
|
||||
NE = 0x01, NotEqual = NE, // 0001 Not equal Z clear
|
||||
CS = 0x02, CarrySet = CS, // 0010 Carry set/unsigned higher or same C set
|
||||
CC = 0x03, CarryClr = CC, // 0011 Carry clear/unsigned lowe C clear
|
||||
MI = 0x04, Minus = MI, // 0100 Minus/negative N set
|
||||
PL = 0x05, Plus = PL, // 0101 Plus/positive or zero N clear
|
||||
VS = 0x06, Overflow = VS, // 0110 Overflow V set
|
||||
VC = 0x07, NoOverflow = VC, // 0111 No overflow V clear
|
||||
|
||||
HI = 0x08, UnHigher = HI, // 1000 Unsigned higher C set and Z clear
|
||||
LS = 0x09, UnLower = LS, // 1001 Unsigned lower or same C clear or Z set
|
||||
|
||||
GE = 0x0A, GrOrEqual = GE, // 1010 Signed greater than or equal N set and V set, or N clear and V clear (N == V)
|
||||
LT = 0x0B, Less = LT, // 1011 Signed less than N set and V clear, or N clear and V set (N != V)
|
||||
GT = 0x0C, Greater = GT, // 1100 Signed greater than Z clear, and either N set and V set, or N clear and V clear (Z == 0,N == V)
|
||||
LE = 0x0D, LessOrEqual = LE, // 1101 Signed less than or equal Z set, or N set and V clear, or N clear and V set (Z == 1 or N != V)
|
||||
|
||||
AL = 0x0E, Always = AL, // 1110 Always (unconditional) -
|
||||
|
||||
|
||||
UC = 0x0F, Unconditional= UC, // 1111 Unconditional Special Instruction for ARMv5? and above
|
||||
|
||||
#define _ARM_COMPAT
|
||||
#if defined(_ARM_COMPAT)
|
||||
CC_EQ=EQ, CC_NE=NE, CC_CS=CS, CC_CC=CC, CC_MI=MI, CC_PL=PL, CC_VS=VS, CC_VC=VC,
|
||||
CC_HI=HI, CC_LS=LS, CC_GE=GE, CC_LT=LT, CC_GT=GT, CC_LE=LE, CC_AL=AL, CC_UC=UC,
|
||||
|
||||
CC_HS=CS, CC_LO=CC,
|
||||
#endif
|
||||
|
||||
ConditionCode_Size
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Data-processing OPCODE 4bits, DPOP
|
||||
*
|
||||
*/
|
||||
|
||||
enum DPOP
|
||||
{
|
||||
DP_AND, // 0000 Logical AND Rd := Rn AND shifter_operand
|
||||
DP_EOR, // 0001 Logical Exclusive OR Rd := Rn EOR shifter_operand
|
||||
DP_SUB, // 0010 Subtract Rd := Rn - shifter_operand
|
||||
DP_RSB, // 0011 Reverse Subtract Rd := shifter_operand - Rn
|
||||
DP_ADD, // 0100 Add Rd := Rn + shifter_operand
|
||||
DP_ADC, // 0101 Add with Carry Rd := Rn + shifter_operand + Carry Flag
|
||||
DP_SBC, // 0110 Subtract with Carry Rd := Rn - shifter_operand - NOT(Carry Flag)
|
||||
DP_RSC, // 0111 Reverse Subtract with Carry Rd := shifter_operand - Rn - NOT(Carry Flag)
|
||||
DP_TST, // 1000 Test Update flags after Rn AND shifter_operand
|
||||
DP_TEQ, // 1001 Test Equivalence Update flags after Rn EOR shifter_operand
|
||||
DP_CMP, // 1010 Compare Update flags after Rn - shifter_operand
|
||||
DP_CMN, // 1011 Compare Negated Update flags after Rn + shifter_operand
|
||||
DP_ORR, // 1100 Logical (inclusive) OR Rd := Rn OR shifter_operand
|
||||
DP_MOV, // 1101 Move Rd := shifter_operand (no first operand)
|
||||
DP_BIC, // 1110 Bit Clear Rd := Rn AND NOT(shifter_operand)
|
||||
DP_MVN // 1111 Move Not Rd := NOT shifter_operand (no first operand)
|
||||
};
|
||||
|
||||
|
||||
|
||||
enum ShiftOp {
|
||||
S_LSL,
|
||||
S_LSR,
|
||||
S_ASR,
|
||||
S_ROR,
|
||||
S_RRX=S_ROR
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* eReg: ARM Register ID
|
||||
*
|
||||
*/
|
||||
enum eReg
|
||||
{
|
||||
r0=0,r1, r2, r3,
|
||||
r4, r5, r6, r7,
|
||||
r8, r9, r10, r11,
|
||||
r12, r13, r14, r15,
|
||||
|
||||
R0=0,R1, R2, R3,
|
||||
R4, R5, R6, R7,
|
||||
R8, R9, R10, R11,
|
||||
R12, R13, R14, R15,
|
||||
|
||||
// Aliases
|
||||
|
||||
a1 = r0, a2 = r1, a3 = r2, a4 = r3,
|
||||
A1 = R0, A2 = R1, A3 = R2, A4 = R3,
|
||||
|
||||
v1 = r4, v2 = r5, v3 = r6, v4 = r7, v5 = r8, v6 = r9,
|
||||
V1 = R4, V2 = R5, V3 = R6, V4 = R7, V5 = R8, V6 = R9,
|
||||
|
||||
rfp = r9, sl = r10, fp = r11, ip = r12, sp = r13, lr = r14, pc = r15,
|
||||
RFP = R9, SL = R10, FP = R11, IP = R12, SP = R13, LR = R14, PC = R15,
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* eFQReg: Float [Quad] Register ID (A.SIMD)
|
||||
*
|
||||
*/
|
||||
enum eFQReg
|
||||
{
|
||||
q0=0,q1, q2, q3,
|
||||
q4, q5, q6, q7,
|
||||
q8, q9, q10, q11,
|
||||
q12, q13, q14, q15,
|
||||
|
||||
Q0=0,Q1, Q2, Q3,
|
||||
Q4, Q5, Q6, Q7,
|
||||
Q8, Q9, Q10, Q11,
|
||||
Q12, Q13, Q14, Q15
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* eFDReg: Float [Double] Register ID (VFP / A.SIMD)
|
||||
*
|
||||
*/
|
||||
enum eFDReg
|
||||
{
|
||||
d0=0,d1, d2, d3,
|
||||
d4, d5, d6, d7,
|
||||
d8, d9, d10, d11,
|
||||
d12, d13, d14, d15,
|
||||
d16, d17, d18, d19,
|
||||
d20, d21, d22, d23,
|
||||
d24, d25, d26, d27,
|
||||
d28, d29, d30, d31,
|
||||
|
||||
D0=0,D1, D2, D3,
|
||||
D4, D5, D6, D7,
|
||||
D8, D9, D10, D11,
|
||||
D12, D13, D14, D15,
|
||||
D16, D17, D18, D19,
|
||||
D20, D21, D22, D23,
|
||||
D24, D25, D26, D27,
|
||||
D28, D29, D30, D31
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* eFSReg: Float [Single] Register ID (VFP)
|
||||
*
|
||||
* Note: Using [f,F]regN syntax to avoid clash with s{8,16,32} types.
|
||||
*/
|
||||
enum eFSReg
|
||||
{
|
||||
f0=0,f1, f2, f3,
|
||||
f4, f5, f6, f7,
|
||||
f8, f9, f10, f11,
|
||||
f12, f13, f14, f15,
|
||||
f16, f17, f18, f19,
|
||||
f20, f21, f22, f23,
|
||||
f24, f25, f26, f27,
|
||||
f28, f29, f30, f31,
|
||||
|
||||
F0=0,F1, F2, F3,
|
||||
F4, F5, F6, F7,
|
||||
F8, F9, F10, F11,
|
||||
F12, F13, F14, F15,
|
||||
F16, F17, F18, F19,
|
||||
F20, F21, F22, F23,
|
||||
F24, F25, F26, F27,
|
||||
F28, F29, F30, F31
|
||||
};
|
||||
|
||||
|
||||
|
||||
enum eFSpecialReg
|
||||
{
|
||||
|
||||
// VM**
|
||||
|
||||
FPINST = 9,
|
||||
FPINST2=10,
|
||||
|
||||
FP_R_ERROR=0xFF
|
||||
};
|
||||
|
||||
|
||||
enum ePushPopReg
|
||||
{
|
||||
_r0 =0x0001, _r1 =0x0002, _r2 =0x0004, _r3 =0x0008,
|
||||
_r4 =0x0010, _r5 =0x0020, _r6 =0x0040, _r7 =0x0080,
|
||||
_r8 =0x0100, _r9 =0x0200, _r10=0x0400, _r11=0x0800,
|
||||
_r12=0x1000, _r13=0x2000, _r14=0x4000, _r15=0x8000,
|
||||
|
||||
_a1 = _r0, _a2 = _r1, _a3 = _r2, _a4 = _r3,
|
||||
_v1 = _r4, _v2 = _r5, _v3 = _r6, _v4 = _r7,
|
||||
_v5 = _r8, _v6 = _r9, _rfp = _r9, _sl = _r10,
|
||||
_fp = _r11, _ip = _r12, _sp = _r13, _lr = _r14,
|
||||
_pc = _r15,
|
||||
|
||||
_push_all = 0xFFFF, // Save All 15
|
||||
_push_call = _lr|_rfp, // Save lr && _rfb(cycle count)
|
||||
|
||||
_push_eabi = _lr|_v1|_v2|_v3|_v4|_v5|_v6|_sl|_fp|_ip, // this is guesswork ..
|
||||
};
|
||||
|
||||
|
||||
|
||||
/// WIP ///
|
||||
|
||||
struct FPReg
|
||||
{
|
||||
union
|
||||
{
|
||||
u8 vQ8[16];
|
||||
u8 vD8[8];
|
||||
u8 vS8[4];
|
||||
|
||||
u16 vQ16[8]; // *VFP: If half-word extensions are enabled
|
||||
u16 vD16[4]; // *VFP: If half-word extensions are enabled
|
||||
u16 vS16[2]; // *VFP: If half-word extensions are enabled
|
||||
|
||||
u32 vQ32[4];
|
||||
u32 vD32[2];
|
||||
u32 S;
|
||||
|
||||
// u64 vQ64[2];
|
||||
// u64 D;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
Executable
+242
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* disasm.h, a horribly static yet (hopefully) correct disassembler
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
|
||||
|
||||
inline static void armdis_cc(u32 cond, char *ccbuff) // Length is always 8 for our static'{n,m}ess
|
||||
{
|
||||
switch(cond)
|
||||
{
|
||||
case EQ: sprintf(ccbuff, "EQ"); return;
|
||||
case NE: sprintf(ccbuff, "NE"); return;
|
||||
case CS: sprintf(ccbuff, "CS"); return;
|
||||
case CC: sprintf(ccbuff, "CC"); return;
|
||||
case MI: sprintf(ccbuff, "MI"); return;
|
||||
case PL: sprintf(ccbuff, "PL"); return;
|
||||
case VS: sprintf(ccbuff, "VS"); return;
|
||||
case VC: sprintf(ccbuff, "VC"); return;
|
||||
|
||||
case HI: sprintf(ccbuff, "HI"); return;
|
||||
case LS: sprintf(ccbuff, "LS"); return;
|
||||
|
||||
case GE: sprintf(ccbuff, "GE"); return;
|
||||
case LT: sprintf(ccbuff, "LT"); return;
|
||||
case GT: sprintf(ccbuff, "GT"); return;
|
||||
case LE: sprintf(ccbuff, "LE"); return;
|
||||
|
||||
case AL: return; // sprintf(ccbuff, "AL"); -- ALways doesn't need to be specified
|
||||
|
||||
case UC: //
|
||||
default: return; // DIE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inline static void armdis_dp(u32 dpop, char *dpbuff) // Length is always 8 ...
|
||||
{
|
||||
switch(dpop)
|
||||
{
|
||||
case DP_AND: sprintf(dpbuff, "AND"); return;
|
||||
case DP_EOR: sprintf(dpbuff, "EOR"); return;
|
||||
case DP_SUB: sprintf(dpbuff, "SUB"); return;
|
||||
case DP_RSB: sprintf(dpbuff, "RSB"); return;
|
||||
case DP_ADD: sprintf(dpbuff, "ADD"); return;
|
||||
case DP_ADC: sprintf(dpbuff, "ADC"); return;
|
||||
case DP_SBC: sprintf(dpbuff, "SBC"); return;
|
||||
case DP_RSC: sprintf(dpbuff, "RSC"); return;
|
||||
case DP_TST: sprintf(dpbuff, "TST"); return;
|
||||
case DP_TEQ: sprintf(dpbuff, "TEQ"); return;
|
||||
case DP_CMP: sprintf(dpbuff, "CMP"); return;
|
||||
case DP_CMN: sprintf(dpbuff, "CMN"); return;
|
||||
case DP_ORR: sprintf(dpbuff, "ORR"); return;
|
||||
case DP_MOV: sprintf(dpbuff, "MOV"); return;
|
||||
case DP_BIC: sprintf(dpbuff, "BIC"); return;
|
||||
case DP_MVN: sprintf(dpbuff, "MVN"); return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
inline static void armdis(u32 op, char *disbuf, u32 len=512)
|
||||
{
|
||||
char ipref[8]={0}, isuff[8]={0}, icond[8]={0} ;
|
||||
|
||||
|
||||
// u32 uOP = ((op>>12)&0xFF00) | ((op>>4)&255) ;
|
||||
|
||||
u32 uCC = ((op>>28) & 0x0F) ; //
|
||||
|
||||
u32 uO1 = ((op>>25) & 0x07) ; //
|
||||
u32 uO2 = ((op>> 4) & 0x01) ; //
|
||||
u32 uC1 = ((op>>21) & 0x0F) ; //
|
||||
u32 uC2 = ((op>> 5) & 0x07) ; //
|
||||
u32 uSB = ((op>>20) & 0x01) ; // Sign Change Bit
|
||||
|
||||
|
||||
/*
|
||||
if (uCC == UC) {
|
||||
|
||||
printf ("DBG armdis has UC instruction %X\n", op);
|
||||
sprintf (disbuf, "UNCONDITIONAL / UNHANDLED INSTRUCTION");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (uCC != AL) {
|
||||
armdis_cc(uCC,isuff);
|
||||
}
|
||||
|
||||
|
||||
if (uO1 == 0)
|
||||
{
|
||||
|
||||
if (uO2 == 0) {
|
||||
|
||||
if ((uC1 & 0xC) == 8) {
|
||||
printf ("DBG armdis 0:0 10xx misc instruction \n", uCC);
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 0:");
|
||||
return;
|
||||
}
|
||||
|
||||
// DP imm.shift
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
else if (uO2 == 1) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 0:");
|
||||
}
|
||||
|
||||
}
|
||||
else if (uO1 == 1) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 1:");
|
||||
}
|
||||
else if (uO1 == 2) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 2:");
|
||||
}
|
||||
else if (uO1 == 3) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 3:");
|
||||
}
|
||||
else if (uO1 == 4) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 4:");
|
||||
}
|
||||
else if (uO1 == 5) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 5:");
|
||||
}
|
||||
else if (uO1 == 6) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 6:");
|
||||
}
|
||||
else if (uO1 == 7) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 7:");
|
||||
}
|
||||
else if (uO1 == 8) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 8:");
|
||||
}
|
||||
else if (uO1 == 9) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 9:");
|
||||
}
|
||||
else if (uO1 == 10) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 10:");
|
||||
}
|
||||
else if (uO1 == 11) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 11:");
|
||||
}
|
||||
else if (uO1 == 12) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 12:");
|
||||
}
|
||||
else if (uO1 == 13) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 13:");
|
||||
}
|
||||
else if (uO1 == 14) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 14:");
|
||||
}
|
||||
else if (uO1 == 15) {
|
||||
|
||||
sprintf (disbuf, "UNHANDLED INSTRUCTION 15:");
|
||||
}
|
||||
else {
|
||||
|
||||
sprintf (disbuf, "INVALID INSTRUCTION");
|
||||
}
|
||||
*/
|
||||
if (!uC1 && uO1==5) {
|
||||
//B
|
||||
char tmp[20];
|
||||
tmp[0]='\0';
|
||||
armdis_cc(uCC, tmp);
|
||||
sprintf(disbuf, "B%s %08X", tmp, (op&0xffffff)<<2);
|
||||
} else {
|
||||
armdis_dp(uC1, disbuf);
|
||||
char tmp[20];
|
||||
tmp[0]='\0';
|
||||
armdis_cc(uCC, tmp);
|
||||
if (tmp[0]) {
|
||||
strcat(disbuf, ".\0");
|
||||
strcat(disbuf, tmp);
|
||||
}
|
||||
if (uSB) strcat(disbuf, ".S\0");
|
||||
bool shifter=false;
|
||||
switch (uO1) {
|
||||
case 0:
|
||||
// reg_reg
|
||||
sprintf(tmp,"\tr%d, r%d", (op>>12)&0x0f, (op)&0x0f);
|
||||
shifter=true;
|
||||
break;
|
||||
case 1:
|
||||
// reg_imm
|
||||
sprintf(tmp,"\tr%d, %04X", (op>>16)&0x0f, (op)&0xffff);
|
||||
break;
|
||||
default:
|
||||
shifter=true;
|
||||
sprintf(tmp, " 0x%0X", uO1);
|
||||
}
|
||||
strcat(disbuf, tmp);
|
||||
char* ShiftOpStr[]={"LSL","LSR","ASR","ROR"};
|
||||
u32 shiftop=(op>>5)&0x3;
|
||||
u32 shiftoptype=(op>>4)&0x1;
|
||||
u32 shiftopreg=(op>>8)&0xf;
|
||||
u32 shiftopimm=(op>>7)&0x1f;
|
||||
if (shifter) {
|
||||
if (!shiftop && !shiftoptype && !shiftopimm)
|
||||
{
|
||||
//nothing
|
||||
} else {
|
||||
if ((shiftop==1) || (shiftop==2)) if (!shiftoptype) if (!shiftopimm) shiftopimm=32;
|
||||
sprintf(tmp, " ,%s %s%d", ShiftOpStr[shiftop], (shiftoptype)?" r":" #", (shiftoptype)?shiftopreg:shiftopimm);
|
||||
strcat(disbuf, tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Emitter.h
|
||||
*
|
||||
* ARMv7 ISA Emitter for code generation.
|
||||
*
|
||||
* David Miller, 2011.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "arm_coding.h"
|
||||
#include "arm_registers.h"
|
||||
//#include "arm_disasm.h"
|
||||
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
/*
|
||||
* Emitter - Reserved for use w/ static members ..
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
class Emitter
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#if defined(_DEBUG) || defined(DEBUG)
|
||||
|
||||
#define EAPI void
|
||||
|
||||
#define DECL_I \
|
||||
u32 Instruction=0
|
||||
|
||||
#define DECL_Id(d) \
|
||||
u32 Instruction=(d)
|
||||
|
||||
#else
|
||||
|
||||
// #define _inlineExSVoidA __extension__ static __inline void __attribute__ ((__always_inline__))
|
||||
|
||||
#define EAPI \
|
||||
inline static void
|
||||
|
||||
#define DECL_I \
|
||||
static u32 Instruction; \
|
||||
Instruction=0
|
||||
|
||||
#define DECL_Id(d) \
|
||||
static u32 Instruction; \
|
||||
Instruction=(d)
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* TEMP
|
||||
*/
|
||||
|
||||
|
||||
#define I (Instruction)
|
||||
|
||||
#define SET_CC I |= (((u32)CC&15)<<28)
|
||||
|
||||
#ifndef EMIT_I
|
||||
#define EMIT_I emit_Write32((I));
|
||||
#endif
|
||||
|
||||
#ifndef EMIT_GET_PTR
|
||||
#define EMIT_GET_PTR() emit_GetCCPtr()
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* ARM Core Instructions
|
||||
*/
|
||||
|
||||
#include "E_Branches.h"
|
||||
#include "E_DataOp.h"
|
||||
#include "E_Multiply.h"
|
||||
#include "E_Parallel.h"
|
||||
#include "E_Extend.h"
|
||||
#include "E_Misc.h"
|
||||
#include "E_Status.h"
|
||||
#include "E_LoadStore.h"
|
||||
#include "E_Special.h"
|
||||
|
||||
|
||||
/*
|
||||
* ARM VFP/A.SIMD Extension Instructions
|
||||
*/
|
||||
|
||||
#include "E_VLoadStore.h"
|
||||
#include "E_VRegXfer.h"
|
||||
#include "E_VDataOp.h"
|
||||
|
||||
|
||||
/*
|
||||
* Helper Routines & Psuedo-Instructions
|
||||
*/
|
||||
|
||||
#include "H_psuedo.h"
|
||||
|
||||
#include "H_Branches.h"
|
||||
#include "H_LoadStore.h"
|
||||
|
||||
//#include "H_state.h"
|
||||
//#include "H_fp.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,487 @@
|
||||
/*
|
||||
* registers.h
|
||||
*
|
||||
* ARMv7-A system register(s).
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace ARM
|
||||
{
|
||||
|
||||
/*************************************************************************************************
|
||||
* CP15 Registers for VMSA Implementation. [ref.DDI0406B B3.12]
|
||||
*************************************************************************************************/
|
||||
/*
|
||||
<CRn> <opc1> <CRm> <opc2> [NAME,] Description [Flags]
|
||||
c0 {
|
||||
0 c0 {
|
||||
0 MIDR, Main ID RO
|
||||
1 CTR, Cache Type RO
|
||||
2 TCMTR, TCM Type RO, IMPL.DEFINED
|
||||
3 TLBTR, TLB Type RO, IMPL.DEFINED
|
||||
5 MPIDR, Multiprocessor Affinity RO
|
||||
{4,6,7} MIDR$, Main ID Aliases RO
|
||||
}
|
||||
|
||||
0 c[1-7] [0-7] CPUID ID_{PFRn,DFRn,AFR0,MMFRn,ISARn} RO
|
||||
|
||||
1 c0 0 CCSIDR, Cache Size ID RO
|
||||
1 c0 1 CLIDR, Cache Level ID RO
|
||||
1 c0 7 AIDR, Aux ID RO, IMPL.DEFINED
|
||||
|
||||
2 c0 0 CSSELR, Cache Size Selection RW
|
||||
}
|
||||
|
||||
c1 0 c0 [0-2] System Control RW
|
||||
c1 0 c1 [0-2] Security Extension RW, IMPL.OPTIONAL
|
||||
|
||||
c2 0 c0 [0-2] Translation Table Base RW
|
||||
|
||||
c3 0 c0 0 DACR, Domain Access Control RW
|
||||
|
||||
c5 0 c{0,1} {0,1} Fault Status RW
|
||||
|
||||
c6 0 c0 {0,2] Fault Address RW
|
||||
|
||||
c7 0 {
|
||||
c0 4 NOP WO
|
||||
c1 {0,6} Cache Maintenance operations, Multiprocessing Extensions WO
|
||||
c4 0 PAR, Physical Address RW
|
||||
c5 {0,1,6,7} Cache and branch predictor maintenance operations WO
|
||||
c5 4 CP15ISB, Instruction barrier operation WO, USER
|
||||
c6 {1,2} Cache Maintenance operations WO
|
||||
c8 [0-7] VA to PA translation ops. WO
|
||||
c10 {1,2} Cache management ops. WO
|
||||
c10 {4,5} Data barrier ops. WO, USER
|
||||
c11 1 DCCMVAU, Cache barrier ops. WO
|
||||
c13 1 NOP WO
|
||||
c14 {1,2} Cache management ops. WO
|
||||
}
|
||||
|
||||
c8 0 c{3,5,6,7} [0-3] TLB maintenance ops. * WO
|
||||
|
||||
c9 [0-7] c{0,2,5,8} [0-7] Reserved for Branch Predictor, Cache and TCM ops. RSVD, OP.ACCESS
|
||||
c9 [0-7] c[12-15] [0-7] Reserved for Performance monitors. RSVD, OP.ACCESS
|
||||
|
||||
c10 0 c{0,1,4,8} [0-7] Reserved for TLB lockdown ops. RSVD, OP.ACCESS
|
||||
c10 0 c2 {0,1} PRRR, NMRRR, TEX Remap RW
|
||||
|
||||
c11 [0-7] c{0,8,15} [0-7] Reserved for DMA ops. TCM access. RSVD, OP.ACCESS
|
||||
|
||||
c12 0 c0 {0,1} Security Extensions RW, IMPL.OPTIONAL
|
||||
c12 0 c1 0 ISR, Security Extensions RO, IMPL.OPTIONAL
|
||||
|
||||
c13 0 c0 0 FCSEIDR, FCSE PID RO-if-FCSE-!IMPL / RW?
|
||||
c13 0 c0 [1-4] Software Thread and Context ID RW
|
||||
|
||||
c15 * * * IMPLEMENTATION DEFINED IMPL.DEFINED
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*************************************************************************************************
|
||||
* CP15 c0: ID codes registers
|
||||
*************************************************************************************************/
|
||||
|
||||
|
||||
/*
|
||||
* MIDR: Main ID Register
|
||||
*/
|
||||
struct MIDR
|
||||
{
|
||||
u32 Revision : 4;
|
||||
u32 PriPartNum : 12; // IF Impl:ARM && PriPartNo top 4bits are 0 || 7: arch&variant encoding differs
|
||||
u32 Architecture : 4;
|
||||
u32 Variant : 4;
|
||||
u32 Implementer : 8;
|
||||
};
|
||||
|
||||
enum MIDR_Implementer
|
||||
{
|
||||
ARM_Ltd = 0x41, // 'A'
|
||||
DigitalEquipment_Corp = 0x44, // 'D'
|
||||
Motorola_FreescaleSemi_Inc = 0x4D, // 'M'
|
||||
QUALCOMM_Inc = 0x51, // 'Q'
|
||||
MarvellSemi_Inc = 0x56, // 'V'
|
||||
Intel_Corp = 0x69, // 'i'
|
||||
|
||||
TexasInstruments_Inc = 0xFF // 'T' ???
|
||||
};
|
||||
|
||||
enum MIDR_Arch
|
||||
{
|
||||
ARMv4 = 1,
|
||||
ARMv4T = 2,
|
||||
ARMv5 = 3, // obselete
|
||||
ARMv5T = 4,
|
||||
ARMv5TE = 5,
|
||||
ARMv5TEJ = 6,
|
||||
ARMv6 = 7,
|
||||
CPUID_Defined = 15
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* CTR, Cache Type Register
|
||||
*/
|
||||
struct CTR
|
||||
{
|
||||
u32 IminLine : 4;
|
||||
u32 SBZ : 10;
|
||||
u32 L1Ip : 2;
|
||||
u32 DminLine : 4; //
|
||||
u32 ERG : 4; // Exclusives Reservation Granule.
|
||||
u32 CWG : 4; // Cache Writeback Granule.
|
||||
u32 RAZ : 1;
|
||||
u32 REGFMT : 3; // Set to 0b100 for ARMv7 register format, or 0b000 for <=ARMv6 format
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* TCMTR, TCM Type Register
|
||||
*/
|
||||
// High 3 bits is 0b100, the rest is IMPL.DEFINED
|
||||
typedef u32 TCMTR;
|
||||
|
||||
|
||||
/*
|
||||
* TLBTR, TLB Type Register
|
||||
*/
|
||||
// Low bit is nU : SET:1: Not unified ( separate instruction and data TLBs )
|
||||
typedef u32 TLBTR;
|
||||
|
||||
|
||||
/*
|
||||
* MPIDR, Multiprocessor Affinity Register
|
||||
*/
|
||||
|
||||
struct MPIDR
|
||||
{
|
||||
u32 AffinityLevel0 : 8;
|
||||
u32 AffinityLevel1 : 8;
|
||||
u32 AffinityLevel2 : 8;
|
||||
u32 MT : 1;
|
||||
u32 RAZ : 5; // Reserved As Zero
|
||||
u32 U : 1; // Set: Processor is part of a Uniprocessor system.
|
||||
u32 MP_Impl : 1; // RAO if MP Extensions are implemented.
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* CCSIDR, Cache Size ID Registers
|
||||
*/
|
||||
struct CCSIDR
|
||||
{
|
||||
u32 LineSize : 3;
|
||||
u32 Associativity : 10;
|
||||
u32 NumSets : 15;
|
||||
u32 WA : 1;
|
||||
u32 RA : 1;
|
||||
u32 WB : 1;
|
||||
u32 WT : 1;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* CLIDR, Cache Level ID Register
|
||||
*/
|
||||
struct CLIDR
|
||||
{
|
||||
u32 Ctype1 : 3;
|
||||
u32 Ctype2 : 3;
|
||||
u32 Ctype3 : 3;
|
||||
u32 Ctype4 : 3;
|
||||
u32 Ctype5 : 3;
|
||||
u32 Ctype6 : 3;
|
||||
u32 Ctype7 : 3;
|
||||
u32 LoUIS : 3;
|
||||
u32 LoC : 3;
|
||||
u32 LoUU : 3;
|
||||
u32 RAZ : 2; // RAZ
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* AIDR, Auxiliary ID Register.
|
||||
*/
|
||||
typedef u32 AIDR; // IMPLEMENTATION DEFINED
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* CSSELR, Cache Size Selection Register
|
||||
*/
|
||||
struct CSSELR
|
||||
{
|
||||
u32 InD : 1;
|
||||
u32 Level : 3;
|
||||
u32 SBZP : 28;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*************************************************************************************************
|
||||
* CP15 c1: System control registers
|
||||
*************************************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
// SCTRL, ACTLR ////////////////////// TODO ///////////////////////
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* CPACR: Coprocessor Access Control Register.
|
||||
*
|
||||
* Controls access to all coprocessors other than CP14 & CP15.
|
||||
* It may be used to check for their presence by testing modification to cpN bits.
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* D32DIS:1 && ASEDIS:0 is INVALID
|
||||
* ASEDIS on hw { w. VFP & w.o A.SIMD } is RAO/WI, if bit is not supported it is RAZ/WI.
|
||||
*
|
||||
* When Security Extensions are enabled, NSACR controls CP access from non-secure state.
|
||||
*
|
||||
* VFP uses CP10 && CP11, the values of .cp10 && .cp11 should be the same.
|
||||
*/
|
||||
union CPACR
|
||||
{
|
||||
struct {
|
||||
u32 cp0 : 2; // cpN [0-13]:
|
||||
u32 cp1 : 2; // Defines access rights for individual coprocessors.
|
||||
u32 cp2 : 2; // See CP_Access enum below for possible values;
|
||||
u32 cp3 : 2; //
|
||||
u32 cp4 : 2; // To test
|
||||
u32 cp5 : 2;
|
||||
u32 cp6 : 2;
|
||||
u32 cp7 : 2;
|
||||
u32 cp8 : 2;
|
||||
u32 cp9 : 2;
|
||||
u32 cp10 : 2;
|
||||
u32 cp11 : 2;
|
||||
u32 cp12 : 2;
|
||||
u32 cp13 : 2;
|
||||
u32 rsvcd: 2; // SBZP
|
||||
u32 D32DIS:1; // SET: Disables use of D16-D32 of the VFP register file.
|
||||
u32 ASEDIS:1; // SET: Disables all A.SIMD Instructions, VFPv3 shall remain valid.
|
||||
};
|
||||
|
||||
u32 R;
|
||||
};
|
||||
|
||||
/*
|
||||
* CP_Access: Enumerates access rights for CPACR.cpN
|
||||
*
|
||||
*/
|
||||
enum CP_Access
|
||||
{
|
||||
A_Deny, // Deny Access, Attempts to access cause Exception::Undefined_Instruction
|
||||
A_Privileged, // Privileged Access, Attempts to access cause Exception::Undefined_Instruction in User mode.
|
||||
A_Reserved, // Reserved Value, Use of this value is UNPREDICTABLE.
|
||||
A_Full // Full Access, Access is defined by coprocessor.
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* SCR: Secure Configuration Register.
|
||||
*
|
||||
* Requires: Security Extension.
|
||||
*/
|
||||
union SCR
|
||||
{
|
||||
struct {
|
||||
u32 NS : 1; //
|
||||
u32 IRQ : 1; //
|
||||
u32 FIQ : 1; //
|
||||
u32 EA : 1; //
|
||||
u32 FW : 1; //
|
||||
u32 AW : 1; //
|
||||
u32 nET : 1; //
|
||||
u32 SBZP:25;
|
||||
};
|
||||
|
||||
u32 R;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// SDER, Secure Debug Enable Register
|
||||
|
||||
// NSACR, Non-Secure Access Control Register
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*************************************************************************************************
|
||||
* CP15 c{2,3}: Memory protection and control registers
|
||||
*************************************************************************************************/
|
||||
|
||||
|
||||
// TTBR0 TTVR1 TTVCR
|
||||
// DACR, Domain Access Control Register
|
||||
|
||||
|
||||
|
||||
/*************************************************************************************************
|
||||
* CP15 c4: Not used
|
||||
*************************************************************************************************/
|
||||
|
||||
|
||||
|
||||
/*************************************************************************************************
|
||||
* CP15 c{5,6}: Memory system fault registers
|
||||
*************************************************************************************************/
|
||||
|
||||
// DFSR, Data Fault Status Register
|
||||
// IFSR, Instruction Fault Status Register
|
||||
// ADFSR, Aux. DFSR
|
||||
// AIFSR, Aux. IFSR
|
||||
// DFAR, Data Fault Address Register
|
||||
// IFAR, Instruction Fault Address Register
|
||||
|
||||
|
||||
|
||||
|
||||
/*************************************************************************************************
|
||||
* CP15 c7: Cache maintenance / misc
|
||||
*************************************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*************************************************************************************************
|
||||
*************************************************************************************************
|
||||
* A.SIMD and VFP extension system registers
|
||||
*************************************************************************************************
|
||||
*************************************************************************************************/
|
||||
|
||||
enum FP_SysRegs
|
||||
{
|
||||
R_FPSID = 0, // 0b0000
|
||||
R_MVFR1 = 6, // 0b0110
|
||||
R_MVFR0 = 7, // 0b0111
|
||||
};
|
||||
|
||||
|
||||
// FPSID Floating Point System ID Register
|
||||
// MVFR1 Media and VFP Feature Register 1
|
||||
// MVFR0 Media and VFP Feature Register 0
|
||||
|
||||
struct FPSID
|
||||
{
|
||||
u32 Revision : 4; // IMPL.DEFINED
|
||||
u32 Variant : 4; // IMPL.DEFINED
|
||||
u32 PartNumber : 8; // IMPL.DEFINED
|
||||
u32 SubArch : 7; // MSB:1 when designed by ARM
|
||||
u32 SW : 1; // Is a software impl. if set
|
||||
u32 Implementer : 8; // Same as MIDR.Implementer
|
||||
};
|
||||
|
||||
enum FP_SubArch
|
||||
{
|
||||
VFPv1 = 0, // Not Permitted in ARMv7
|
||||
VFPv2_Cv1, // Not Permitted in ARMv7
|
||||
VFPv3_Cv2, //
|
||||
VFPv3_Null, // Full hardware, no trap
|
||||
VFPv3_Cv3, //
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Floating-point status and control register
|
||||
//
|
||||
struct FPSCR
|
||||
{
|
||||
u32 IOC : 1; // * All * bits are cumulative exception bits
|
||||
u32 DZC : 1; // *
|
||||
u32 OFC : 1; // *
|
||||
u32 UFC : 1; // *
|
||||
u32 IXC : 1; // *
|
||||
u32 SBZP1 : 2; //
|
||||
u32 IDC : 1; // *
|
||||
u32 IOE : 1; // ** All ** bits are FP trap enable bits
|
||||
u32 DZE : 1; // ** only supported in VFPv2 && VFPv3U
|
||||
u32 OFE : 1; // ** - RAZ elsewhere -
|
||||
u32 UFE : 1; // **
|
||||
u32 IXE : 1; // **
|
||||
u32 SBZP2 : 2; //
|
||||
u32 IDE : 1; // **
|
||||
u32 Len : 3; // SBZ for ARMv7 VFP, ignored for A.SIMD
|
||||
u32 SBZP3 : 1; //
|
||||
u32 Stride : 2; // SBZ for ARMv7 VFP, ignored for A.SIMD
|
||||
u32 RMode : 2; // Rounding Mode
|
||||
u32 FZ : 1; // Flush-to-Zero
|
||||
u32 DN : 1; // Default NaN mode control
|
||||
u32 AHP : 1; // Alt. Half-precision
|
||||
u32 QC : 1; // Cumulative saturation, A.SIMD
|
||||
u32 V : 1; // CC Overflow
|
||||
u32 C : 1; // CC Carry
|
||||
u32 Z : 1; // CC Zero
|
||||
u32 N : 1; // CC Negative
|
||||
};
|
||||
|
||||
enum FP_RoundingMode // A.SIMD Always uses RN !
|
||||
{
|
||||
RN, // Round to Nearest
|
||||
RP, // Round towards Plus Infinity
|
||||
RM, // Round towards Minus Infinity
|
||||
RZ // Round towards Zero
|
||||
};
|
||||
|
||||
|
||||
struct MVFR0
|
||||
{
|
||||
u32 A_SIMD : 4;
|
||||
u32 Single : 4;
|
||||
u32 Double : 4;
|
||||
u32 Trap : 4;
|
||||
u32 Divide : 4;
|
||||
u32 Sqrt : 4;
|
||||
u32 ShortVec: 4;
|
||||
u32 Rounding: 4;
|
||||
};
|
||||
|
||||
struct MVFR1
|
||||
{
|
||||
u32 FtZ_mode : 4;
|
||||
u32 D_NaN_mode : 4;
|
||||
u32 NFP_LdStr : 4;
|
||||
u32 NFP_int : 4;
|
||||
u32 NFP_SPFP : 4;
|
||||
u32 NFP_HPFP : 4;
|
||||
u32 VFP_HPFP : 4;
|
||||
u32 RAZ : 4;
|
||||
|
||||
};
|
||||
};
|
||||
@@ -1,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 = cfgLoadStr("alsa", "device", "");
|
||||
|
||||
int rc = -1;
|
||||
if (!device.empty() && device != "auto") {
|
||||
rc = snd_pcm_open(&handle, device.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
|
||||
if (rc < 0)
|
||||
WARN_LOG(AUDIO, "ALSA: Cannot open device %s. Trying auto", device.c_str());
|
||||
}
|
||||
if (rc < 0)
|
||||
{
|
||||
INFO_LOG(AUDIO, "ALSA: trying to determine audio device");
|
||||
|
||||
// trying default device
|
||||
device = "default";
|
||||
rc = snd_pcm_open(&handle, device.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
|
||||
|
||||
// "default" didn't work, try first device
|
||||
if (rc < 0)
|
||||
{
|
||||
device = "plughw:0,0,0";
|
||||
rc = snd_pcm_open(&handle, device.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
|
||||
|
||||
if (rc < 0)
|
||||
{
|
||||
device = "plughw:0,0";
|
||||
rc = snd_pcm_open(&handle, device.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// first didn't work, try second
|
||||
if (rc < 0)
|
||||
{
|
||||
device = "plughw:1,0";
|
||||
rc = snd_pcm_open(&handle, device.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
|
||||
}
|
||||
|
||||
// try pulse audio backend
|
||||
if (rc < 0)
|
||||
{
|
||||
device = "pulse";
|
||||
rc = snd_pcm_open(&handle, device.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
|
||||
}
|
||||
|
||||
if (rc < 0)
|
||||
INFO_LOG(AUDIO, "ALSA: unable to automatically determine audio device.");
|
||||
}
|
||||
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: unable to open PCM device %s: %s", device.c_str(), snd_strerror(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
INFO_LOG(AUDIO, "ALSA: Successfully initialized \"%s\"", device.c_str());
|
||||
|
||||
/* Allocate a hardware parameters object. */
|
||||
snd_pcm_hw_params_alloca(¶ms);
|
||||
|
||||
/* Fill it in with default values. */
|
||||
rc=snd_pcm_hw_params_any(handle, params);
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Error:snd_pcm_hw_params_any %s", snd_strerror(rc));
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Set the desired hardware parameters. */
|
||||
|
||||
/* Interleaved mode */
|
||||
rc=snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Error:snd_pcm_hw_params_set_access %s", snd_strerror(rc));
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Signed 16-bit little-endian format */
|
||||
rc=snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Error:snd_pcm_hw_params_set_format %s", snd_strerror(rc));
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Two channels (stereo) */
|
||||
rc=snd_pcm_hw_params_set_channels(handle, params, 2);
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Error:snd_pcm_hw_params_set_channels %s", snd_strerror(rc));
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 44100 samples/second
|
||||
rc = snd_pcm_hw_params_set_rate(handle, params, 44100, 0);
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Error:snd_pcm_hw_params_set_rate %s", snd_strerror(rc));
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Period size (512)
|
||||
period_size = std::min(SAMPLE_COUNT, (u32)config::AudioBufferSize / 4);
|
||||
rc = snd_pcm_hw_params_set_period_size_near(handle, params, &period_size, nullptr);
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Error:snd_pcm_hw_params_set_periods_near %s", snd_strerror(rc));
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
INFO_LOG(AUDIO, "ALSA: period size set to %zd", (size_t)period_size);
|
||||
|
||||
// Sample buffer size
|
||||
buffer_size = config::AudioBufferSize;
|
||||
rc = snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_size);
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Error:snd_pcm_hw_params_set_buffer_size_near %s", snd_strerror(rc));
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
INFO_LOG(AUDIO, "ALSA: buffer size set to %ld", buffer_size);
|
||||
|
||||
/* Write the parameters to the driver */
|
||||
rc = snd_pcm_hw_params(handle, params);
|
||||
if (rc < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Unable to set hw parameters: %s", snd_strerror(rc));
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initRecord(u32 sampling_freq) override
|
||||
{
|
||||
int err;
|
||||
if ((err = snd_pcm_open(&handle_record, "default", SND_PCM_STREAM_CAPTURE, 0)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot open default audio capture device: %s", snd_strerror(err));
|
||||
return false;
|
||||
}
|
||||
snd_pcm_hw_params_t *hw_params;
|
||||
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot allocate hardware parameter structure: %s", snd_strerror(err));
|
||||
snd_pcm_close(handle_record);
|
||||
return false;
|
||||
}
|
||||
if ((err = snd_pcm_hw_params_any(handle_record, hw_params)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot initialize hardware parameter structure: %s", snd_strerror(err));
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
snd_pcm_close(handle_record);
|
||||
return false;
|
||||
}
|
||||
if ((err = snd_pcm_hw_params_set_access(handle_record, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot set access type: %s\n", snd_strerror(err));
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
snd_pcm_close(handle_record);
|
||||
return false;
|
||||
}
|
||||
if ((err = snd_pcm_hw_params_set_format(handle_record, hw_params, SND_PCM_FORMAT_S16_LE)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot set sample format: %s", snd_strerror(err));
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
snd_pcm_close(handle_record);
|
||||
return false;
|
||||
}
|
||||
if ((err = snd_pcm_hw_params_set_rate(handle_record, hw_params, sampling_freq, 0)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot set sample rate to %d Hz: %s", sampling_freq, snd_strerror(err));
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
snd_pcm_close(handle_record);
|
||||
return false;
|
||||
}
|
||||
if ((err = snd_pcm_hw_params_set_channels(handle_record, hw_params, 1)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot set channel count: %s", snd_strerror(err));
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
snd_pcm_close(handle_record);
|
||||
return false;
|
||||
}
|
||||
if ((err = snd_pcm_hw_params(handle_record, hw_params)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot set parameters: %s", snd_strerror(err));
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
snd_pcm_close(handle_record);
|
||||
return false;
|
||||
}
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
snd_pcm_nonblock(handle_record, 1);
|
||||
if ((err = snd_pcm_prepare(handle_record)) < 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "ALSA: Cannot prepare device: %s", snd_strerror(err));
|
||||
snd_pcm_close(handle_record);
|
||||
return false;
|
||||
}
|
||||
INFO_LOG(AUDIO, "ALSA: Successfully initialized capture device");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void termRecord() override
|
||||
{
|
||||
snd_pcm_close(handle_record);
|
||||
}
|
||||
|
||||
u32 record(void* frame, u32 samples) override
|
||||
{
|
||||
int err = snd_pcm_readi(handle_record, frame, samples);
|
||||
if (err < (int)samples)
|
||||
{
|
||||
if (err < 0)
|
||||
{
|
||||
DEBUG_LOG(AUDIO, "ALSA: Recording error: %s", snd_strerror(err));
|
||||
err = 0;
|
||||
err = snd_pcm_prepare(handle_record);
|
||||
}
|
||||
u8 *buffer = (u8 *)frame + err;
|
||||
memset(buffer, 0, (samples - err) * 2);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
if (wait != pcm_blocking) {
|
||||
snd_pcm_nonblock(handle, wait ? 0 : 1);
|
||||
pcm_blocking = wait;
|
||||
}
|
||||
|
||||
int rc = snd_pcm_writei(handle, frame, samples);
|
||||
if (rc < 0)
|
||||
{
|
||||
snd_pcm_recover(handle, rc, 1);
|
||||
if (rc == -EPIPE)
|
||||
{
|
||||
// EPIPE means underrun
|
||||
// Write some silence then our samples
|
||||
const size_t silence_size = buffer_size - samples;
|
||||
void *silence = alloca(silence_size * 4);
|
||||
memset(silence, 0, silence_size * 4);
|
||||
snd_pcm_writei(handle, silence, silence_size);
|
||||
snd_pcm_writei(handle, frame, samples);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
snd_pcm_drop(handle);
|
||||
snd_pcm_close(handle);
|
||||
}
|
||||
|
||||
std::vector<std::string> getDeviceList()
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
|
||||
char **hints;
|
||||
int err = snd_device_name_hint(-1, "pcm", (void***)&hints);
|
||||
|
||||
// Error initializing ALSA
|
||||
if (err != 0)
|
||||
return result;
|
||||
|
||||
// special value to automatically detect on initialization
|
||||
result.emplace_back("auto");
|
||||
|
||||
char** n = hints;
|
||||
while (*n != NULL)
|
||||
{
|
||||
// Get the type (NULL/Input/Output)
|
||||
char *type = snd_device_name_get_hint(*n, "IOID");
|
||||
char *name = snd_device_name_get_hint(*n, "NAME");
|
||||
|
||||
if (name != NULL)
|
||||
{
|
||||
// We only want output or special devices (like "default" or "pulse")
|
||||
// TODO Only those with type == NULL?
|
||||
if (type == NULL || strcmp(type, "Output") == 0)
|
||||
{
|
||||
// TODO Check if device works (however we need to hash the resulting list then)
|
||||
/*snd_pcm_t *handle;
|
||||
int rc = snd_pcm_open(&handle, name, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
|
||||
if (rc == 0)
|
||||
{
|
||||
result.push_back(name);
|
||||
snd_pcm_close(handle);
|
||||
}
|
||||
*/
|
||||
|
||||
result.emplace_back(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (type != NULL)
|
||||
free(type);
|
||||
|
||||
if (name != NULL)
|
||||
free(name);
|
||||
|
||||
n++;
|
||||
}
|
||||
|
||||
snd_device_name_free_hint((void**)hints);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Option* getOptions(int *count) override
|
||||
{
|
||||
*count = 1;
|
||||
static Option result;
|
||||
|
||||
result.name = "device";
|
||||
result.caption = "Device";
|
||||
result.type = Option::list;
|
||||
result.values = getDeviceList();
|
||||
|
||||
return &result;
|
||||
}
|
||||
};
|
||||
static AlsaAudioBackend alsaBackend;
|
||||
|
||||
#endif
|
||||
@@ -1,308 +0,0 @@
|
||||
/*
|
||||
Simple Core Audio backend for osx (and maybe ios?)
|
||||
Based off various audio core samples and dolphin's code
|
||||
|
||||
This is part of the Reicast project, please consult the
|
||||
LICENSE file for licensing & related information
|
||||
|
||||
This could do with some locking logic to avoid
|
||||
race conditions, and some variable length buffer
|
||||
logic to support chunk sizes other than 512 bytes
|
||||
|
||||
It does work on my macmini though
|
||||
*/
|
||||
#if defined(__APPLE__)
|
||||
#include "audiostream.h"
|
||||
#include "cfg/option.h"
|
||||
#include "stdclass.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include <AudioToolbox/AudioQueue.h>
|
||||
|
||||
class CoreAudioBackend : public AudioBackend
|
||||
{
|
||||
AudioUnit audioUnit;
|
||||
|
||||
u32 BUFSIZE = 0;
|
||||
u8 *samples_temp = nullptr;
|
||||
|
||||
std::atomic<int> samples_wptr;
|
||||
std::atomic<int> samples_rptr;
|
||||
cResetEvent bufferEmpty;
|
||||
|
||||
// input buffer and indexes
|
||||
u8 samples_input[2400];
|
||||
static constexpr size_t InputBufSize = sizeof(samples_input);
|
||||
std::atomic<int> input_wptr;
|
||||
std::atomic<int> input_rptr;
|
||||
AudioQueueRef recordQueue;
|
||||
|
||||
static OSStatus renderCallback(void* ctx, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* ts,
|
||||
UInt32 bus, UInt32 frames, AudioBufferList* abl)
|
||||
{
|
||||
CoreAudioBackend *backend = (CoreAudioBackend *)ctx;
|
||||
for (int i = 0; i < abl->mNumberBuffers; i++)
|
||||
{
|
||||
int size = abl->mBuffers[i].mDataByteSize;
|
||||
u8 *outBuffer = (u8 *)abl->mBuffers[i].mData;
|
||||
while (size != 0)
|
||||
{
|
||||
int avail = (backend->samples_wptr - backend->samples_rptr + backend->BUFSIZE) % backend->BUFSIZE;
|
||||
if (avail == 0)
|
||||
{
|
||||
//printf("Core Audio: buffer underrun %d bytes (%d)", size, abl->mBuffers[i].mDataByteSize);
|
||||
memset(outBuffer, '\0', size);
|
||||
return noErr;
|
||||
}
|
||||
avail = std::min(avail, size);
|
||||
avail = std::min(avail, (int)backend->BUFSIZE - backend->samples_rptr);
|
||||
memcpy(outBuffer, backend->samples_temp + backend->samples_rptr, avail);
|
||||
backend->samples_rptr = (backend->samples_rptr + avail) % backend->BUFSIZE;
|
||||
size -= avail;
|
||||
outBuffer += avail;
|
||||
// Set the mutex to allow writing
|
||||
backend->bufferEmpty.Set();
|
||||
}
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
public:
|
||||
CoreAudioBackend()
|
||||
: AudioBackend("coreaudio", "Core Audio") {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
OSStatus err;
|
||||
AURenderCallbackStruct callback_struct;
|
||||
AudioStreamBasicDescription format;
|
||||
AudioComponentDescription desc;
|
||||
AudioComponent component;
|
||||
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
#if !defined(TARGET_IPHONE)
|
||||
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
|
||||
#else
|
||||
desc.componentSubType = kAudioUnitSubType_RemoteIO;
|
||||
#endif
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
component = AudioComponentFindNext(nullptr, &desc);
|
||||
|
||||
if (component == nullptr) {
|
||||
ERROR_LOG(AUDIO, "coreaudio: AudioComponentFindNext failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
err = AudioComponentInstanceNew(component, &audioUnit);
|
||||
if (err != noErr) {
|
||||
ERROR_LOG(AUDIO, "coreaudio: AudioComponentInstanceNew failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
FillOutASBDForLPCM(format, 44100,
|
||||
2, 16, 16, false, false, false);
|
||||
err = AudioUnitSetProperty(audioUnit,
|
||||
kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0, &format,
|
||||
sizeof(AudioStreamBasicDescription));
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "coreaudio: AudioUnitSetProperty(kAudioUnitProperty_StreamFormat) failed");
|
||||
AudioComponentInstanceDispose(audioUnit);
|
||||
return false;
|
||||
}
|
||||
|
||||
callback_struct.inputProc = renderCallback;
|
||||
callback_struct.inputProcRefCon = this;
|
||||
err = AudioUnitSetProperty(audioUnit,
|
||||
kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0, &callback_struct,
|
||||
sizeof callback_struct);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "coreaudio: AudioUnitSetProperty(kAudioUnitProperty_SetRenderCallback) failed");
|
||||
AudioComponentInstanceDispose(audioUnit);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
err = AudioUnitSetParameter(audioUnit,
|
||||
kHALOutputParam_Volume,
|
||||
kAudioUnitParameterFlag_Output, 0,
|
||||
1, 0);
|
||||
verify(err == noErr);
|
||||
*/
|
||||
|
||||
err = AudioUnitInitialize(audioUnit);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "coreaudio: AudioUnitInitialize failed");
|
||||
AudioComponentInstanceDispose(audioUnit);
|
||||
return false;
|
||||
}
|
||||
|
||||
BUFSIZE = config::AudioBufferSize * 4;
|
||||
samples_temp = new u8[BUFSIZE]();
|
||||
samples_rptr = 0;
|
||||
samples_wptr = 0;
|
||||
|
||||
err = AudioOutputUnitStart(audioUnit);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "coreaudio: AudioOutputUnitStart failed");
|
||||
AudioUnitUninitialize(audioUnit);
|
||||
AudioComponentInstanceDispose(audioUnit);
|
||||
return false;
|
||||
}
|
||||
|
||||
bufferEmpty.Set();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
int size = samples * 4;
|
||||
while (size != 0)
|
||||
{
|
||||
int avail = (samples_rptr - samples_wptr - 4 + BUFSIZE) % BUFSIZE;
|
||||
if (avail == 0)
|
||||
{
|
||||
if (!wait)
|
||||
break;
|
||||
bufferEmpty.Wait();
|
||||
continue;
|
||||
}
|
||||
avail = std::min(avail, size);
|
||||
avail = std::min(avail, (int)BUFSIZE - samples_wptr);
|
||||
memcpy(&samples_temp[samples_wptr], frame, avail);
|
||||
samples_wptr = (samples_wptr + avail) % BUFSIZE;
|
||||
frame = (u8 *)frame + avail;
|
||||
size -= avail;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
AudioOutputUnitStop(audioUnit);
|
||||
AudioUnitUninitialize(audioUnit);
|
||||
AudioComponentInstanceDispose(audioUnit);
|
||||
bufferEmpty.Set();
|
||||
delete [] samples_temp;
|
||||
samples_temp = nullptr;
|
||||
}
|
||||
|
||||
static void recordCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
|
||||
const AudioTimeStamp *inStartTime, UInt32 frameSize, const AudioStreamPacketDescription *dataFormat)
|
||||
{
|
||||
//DEBUG_LOG(AUDIO, "AudioQueue callback: wptr %d rptr %d bytes %d", (int)input_wptr, (int)input_rptr, inBuffer->mAudioDataByteSize);
|
||||
CoreAudioBackend *backend = (CoreAudioBackend *)inUserData;
|
||||
UInt32 size = inBuffer->mAudioDataByteSize;
|
||||
UInt32 freeSpace = (backend->input_rptr - backend->input_wptr - 2 + InputBufSize) % InputBufSize;
|
||||
if (size > freeSpace)
|
||||
{
|
||||
DEBUG_LOG(AUDIO, "coreaudio: record overrun %d bytes", size - freeSpace);
|
||||
size = freeSpace;
|
||||
}
|
||||
while (size != 0)
|
||||
{
|
||||
UInt32 chunk = std::min(size, (UInt32)(InputBufSize - backend->input_wptr));
|
||||
memcpy(backend->samples_input + backend->input_wptr, inBuffer->mAudioData, chunk);
|
||||
backend->input_wptr = (backend->input_wptr + chunk) % InputBufSize;
|
||||
size -= chunk;
|
||||
}
|
||||
AudioQueueEnqueueBuffer(backend->recordQueue, inBuffer, 0, nullptr);
|
||||
}
|
||||
|
||||
void termRecord() override
|
||||
{
|
||||
if (recordQueue != nullptr)
|
||||
{
|
||||
AudioQueueStop(recordQueue, true);
|
||||
AudioQueueDispose(recordQueue, true);
|
||||
recordQueue = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool initRecord(u32 sampling_freq) override
|
||||
{
|
||||
AudioStreamBasicDescription desc{};
|
||||
desc.mFormatID = kAudioFormatLinearPCM;
|
||||
desc.mSampleRate = (double)sampling_freq;
|
||||
desc.mChannelsPerFrame = 1;
|
||||
desc.mBitsPerChannel = 16;
|
||||
desc.mBytesPerPacket = desc.mBytesPerFrame = 2;
|
||||
desc.mFramesPerPacket = 1;
|
||||
desc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
|
||||
desc.mReserved = 0;
|
||||
|
||||
OSStatus err = AudioQueueNewInput(&desc,
|
||||
recordCallback,
|
||||
this,
|
||||
nullptr,
|
||||
kCFRunLoopCommonModes,
|
||||
0,
|
||||
&recordQueue);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "AudioQueueNewInput failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioQueueBufferRef buffers[2];
|
||||
for (UInt32 i = 0; i < std::size(buffers) && err == noErr; i++)
|
||||
{
|
||||
err = AudioQueueAllocateBuffer(recordQueue, 480, &buffers[i]);
|
||||
if (err == noErr)
|
||||
err = AudioQueueEnqueueBuffer(recordQueue, buffers[i], 0, nullptr);
|
||||
}
|
||||
input_wptr = 0;
|
||||
input_rptr = 0;
|
||||
if (err == noErr)
|
||||
err = AudioQueueStart(recordQueue, nullptr);
|
||||
if (err != noErr)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "AudioQueue init failed: %d", err);
|
||||
termRecord();
|
||||
return false;
|
||||
}
|
||||
INFO_LOG(AUDIO, "AudioQueue initialized - sample rate %f", desc.mSampleRate);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 record(void* frame, u32 samples) override
|
||||
{
|
||||
// DEBUG_LOG(AUDIO, "coreaudio_record: wptr %d rptr %d", (int)input_wptr, (int)input_rptr);
|
||||
u32 size = samples * 2;
|
||||
while (size != 0)
|
||||
{
|
||||
u32 avail = (input_wptr - input_rptr + InputBufSize) % InputBufSize;
|
||||
if (avail == 0)
|
||||
{
|
||||
DEBUG_LOG(AUDIO, "coreaudio: record underrun %d bytes", size);
|
||||
break;
|
||||
}
|
||||
avail = std::min(avail, size);
|
||||
avail = std::min(avail, (u32)(InputBufSize - input_rptr));
|
||||
|
||||
memcpy(frame, &samples_input[input_rptr], avail);
|
||||
frame = (u8 *)frame + avail;
|
||||
input_rptr = (input_rptr + avail) % InputBufSize;
|
||||
size -= avail;
|
||||
}
|
||||
|
||||
return samples - size / 2;
|
||||
}
|
||||
};
|
||||
static CoreAudioBackend coreAudioBackend;
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,240 +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"
|
||||
|
||||
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()
|
||||
{
|
||||
audioThreadRunning = true;
|
||||
while (true)
|
||||
{
|
||||
u32 rv = WaitForMultipleObjects(notificationEvents.size(), ¬ificationEvents[0], false, 100);
|
||||
|
||||
if (!audioThreadRunning)
|
||||
break;
|
||||
if (rv == WAIT_TIMEOUT || rv == WAIT_FAILED)
|
||||
continue;
|
||||
rv -= WAIT_OBJECT_0;
|
||||
|
||||
void *p1, *p2;
|
||||
DWORD sz1, sz2;
|
||||
|
||||
if (SUCCEEDED(buffer->Lock(notificationOffset(rv), SAMPLE_BYTES, &p1, &sz1, &p2, &sz2, 0)))
|
||||
{
|
||||
if (!ringBuffer.read((u8*)p1, sz1))
|
||||
memset(p1, 0, sz1);
|
||||
if (sz2 != 0)
|
||||
{
|
||||
if (!ringBuffer.read((u8*)p2, sz2))
|
||||
memset(p2, 0, sz2);
|
||||
}
|
||||
buffer->Unlock(p1, sz1, p2, sz2);
|
||||
pushWait.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
DirectSoundBackend()
|
||||
: AudioBackend("directsound", "Microsoft DirectSound") {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
if (FAILED(DirectSoundCreate8(NULL, &dsound.get(), NULL))) {
|
||||
ERROR_LOG(AUDIO, "DirectSound8 initialization failed");
|
||||
return false;
|
||||
}
|
||||
if (FAILED(dsound->SetCooperativeLevel(getNativeHwnd(), DSSCL_PRIORITY))) {
|
||||
ERROR_LOG(AUDIO, "DirectSound8 SetCooperativeLevel failed");
|
||||
dsound.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up WAV format structure.
|
||||
WAVEFORMATEX wfx;
|
||||
memset(&wfx, 0, sizeof(WAVEFORMATEX));
|
||||
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
||||
wfx.nChannels = 2;
|
||||
wfx.nSamplesPerSec = 44100;
|
||||
wfx.nBlockAlign = 4;
|
||||
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
||||
wfx.wBitsPerSample = 16;
|
||||
|
||||
// Set up DSBUFFERDESC structure.
|
||||
DSBUFFERDESC desc;
|
||||
memset(&desc, 0, sizeof(DSBUFFERDESC));
|
||||
desc.dwSize = sizeof(DSBUFFERDESC);
|
||||
desc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS;
|
||||
desc.dwBufferBytes = SAMPLE_BYTES * 2;
|
||||
desc.lpwfxFormat = &wfx;
|
||||
|
||||
// Create the buffer
|
||||
ComPtr<IDirectSoundBuffer> buffer_;
|
||||
if (FAILED(dsound->CreateSoundBuffer(&desc, &buffer_.get(), 0))
|
||||
|| FAILED(buffer_.as(buffer)))
|
||||
{
|
||||
ERROR_LOG(AUDIO, "DirectSound8 CreateSoundBuffer failed");
|
||||
dsound.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up notifications
|
||||
ComPtr<IDirectSoundNotify> bufferNotify;
|
||||
verifyc(buffer.as(bufferNotify));
|
||||
notificationEvents.clear();
|
||||
std::vector<DSBPOSITIONNOTIFY> posNotify;
|
||||
for (int i = 0; notificationOffset(i) < desc.dwBufferBytes; i++)
|
||||
{
|
||||
notificationEvents.push_back(CreateEvent(nullptr, false, false, nullptr));
|
||||
posNotify.push_back({ notificationOffset(i), notificationEvents.back() });
|
||||
}
|
||||
bufferNotify->SetNotificationPositions(posNotify.size(), &posNotify[0]);
|
||||
|
||||
// Clear the buffers
|
||||
void *p1, *p2;
|
||||
DWORD sz1, sz2;
|
||||
verifyc(buffer->Lock(0, desc.dwBufferBytes, &p1, &sz1, &p2, &sz2, 0));
|
||||
verify(p2 == nullptr);
|
||||
memset(p1, 0, sz1);
|
||||
verifyc(buffer->Unlock(p1, sz1, p2, sz2));
|
||||
ringBuffer.setCapacity(config::AudioBufferSize * 4);
|
||||
|
||||
// Start the thread
|
||||
audioThread = std::thread(&DirectSoundBackend::audioThreadMain, this);
|
||||
|
||||
// Play the buffer !
|
||||
if (FAILED(buffer->Play(0, 0, DSBPLAY_LOOPING)))
|
||||
{
|
||||
ERROR_LOG(AUDIO, "DirectSound8 Play failed");
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
INFO_LOG(AUDIO, "DirectSound playback started");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
while (!ringBuffer.write((const u8 *)frame, samples * 4) && wait)
|
||||
pushWait.Wait();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
audioThreadRunning = false;
|
||||
audioThread.join();
|
||||
buffer->Stop();
|
||||
|
||||
for (HANDLE event : notificationEvents)
|
||||
CloseHandle(event);
|
||||
buffer.reset();
|
||||
dsound.reset();
|
||||
INFO_LOG(AUDIO, "DirectSound playback stopped");
|
||||
}
|
||||
|
||||
bool initRecord(u32 sampling_freq) override
|
||||
{
|
||||
if (FAILED(DirectSoundCaptureCreate8(&DSDEVID_DefaultVoiceCapture, &dcapture.get(), NULL)))
|
||||
{
|
||||
ERROR_LOG(AUDIO, "DirectSound capture device creation failed");
|
||||
return false;
|
||||
}
|
||||
HRESULT hr;
|
||||
WAVEFORMATEX wfx =
|
||||
{ WAVE_FORMAT_PCM, 1, sampling_freq, sampling_freq * 2, 2, 16, 0 };
|
||||
// wFormatTag, nChannels, nSamplesPerSec, nAvgBytesPerSec,
|
||||
// nBlockAlign, wBitsPerSample, cbSize
|
||||
|
||||
DSCBUFFERDESC dscbd;
|
||||
dscbd.dwSize = sizeof(DSCBUFFERDESC);
|
||||
dscbd.dwFlags = 0;
|
||||
dscbd.dwBufferBytes = 480 * 2;
|
||||
dscbd.dwReserved = 0;
|
||||
dscbd.lpwfxFormat = &wfx;
|
||||
dscbd.dwFXCount = 0;
|
||||
dscbd.lpDSCFXDesc = NULL;
|
||||
|
||||
ComPtr<IDirectSoundCaptureBuffer> pDSCB;
|
||||
if (FAILED(hr = dcapture->CreateCaptureBuffer(&dscbd, &pDSCB.get(), NULL)))
|
||||
{
|
||||
ERROR_LOG(AUDIO, "DirectSound capture buffer creation failed");
|
||||
dcapture.reset();
|
||||
return false;
|
||||
}
|
||||
pDSCB.as(capture_buffer);
|
||||
capture_buffer->Start(DSCBSTART_LOOPING);
|
||||
INFO_LOG(AUDIO, "DirectSound capture device and buffer created");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 record(void *buffer, u32 samples) override
|
||||
{
|
||||
DWORD readPos;
|
||||
capture_buffer->GetCurrentPosition(NULL, &readPos);
|
||||
void *p1, *p2;
|
||||
DWORD p1bytes, p2bytes;
|
||||
capture_buffer->Lock(readPos, samples * 2, &p1, &p1bytes, &p2, &p2bytes, 0);
|
||||
memcpy(buffer, p1, p1bytes);
|
||||
if (p2bytes > 0)
|
||||
memcpy((u8 *)buffer + p1bytes, p2, p2bytes);
|
||||
capture_buffer->Unlock(p1, p1bytes, p2, p2bytes);
|
||||
return (p1bytes + p2bytes) / 2;
|
||||
}
|
||||
|
||||
void termRecord() override
|
||||
{
|
||||
if (!dcapture)
|
||||
return;
|
||||
capture_buffer->Stop();
|
||||
capture_buffer.reset();
|
||||
dcapture.reset();
|
||||
}
|
||||
};
|
||||
static DirectSoundBackend directSoundBackend;
|
||||
|
||||
#endif
|
||||
@@ -1,54 +0,0 @@
|
||||
#ifdef USE_LIBAO
|
||||
#include "audiostream.h"
|
||||
#include <ao/ao.h>
|
||||
|
||||
class LibAOBackend : public AudioBackend
|
||||
{
|
||||
ao_device *aodevice = nullptr;
|
||||
|
||||
public:
|
||||
LibAOBackend()
|
||||
: AudioBackend("libao", "libao") {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
ao_initialize();
|
||||
|
||||
ao_sample_format aoformat {};
|
||||
aoformat.bits = 16;
|
||||
aoformat.channels = 2;
|
||||
aoformat.rate = 44100;
|
||||
aoformat.byte_format = AO_FMT_LITTLE;
|
||||
|
||||
aodevice = ao_open_live(ao_default_driver_id(), &aoformat, NULL); // Live output
|
||||
if (aodevice == nullptr)
|
||||
aodevice = ao_open_live(ao_driver_id("null"), &aoformat, NULL);
|
||||
if (aodevice == nullptr) {
|
||||
ERROR_LOG(AUDIO, "Cannot open libao driver");
|
||||
ao_shutdown();
|
||||
}
|
||||
|
||||
return aodevice != nullptr;
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
if (aodevice != nullptr)
|
||||
ao_play(aodevice, (char*)frame, samples * 4);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
if (aodevice != nullptr)
|
||||
{
|
||||
ao_close(aodevice);
|
||||
aodevice = nullptr;
|
||||
ao_shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
static LibAOBackend libAOBackend;
|
||||
|
||||
#endif
|
||||
@@ -1,51 +0,0 @@
|
||||
#include "audiostream.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
class NullAudioBackend : public AudioBackend
|
||||
{
|
||||
using the_clock = std::chrono::high_resolution_clock;
|
||||
|
||||
public:
|
||||
NullAudioBackend()
|
||||
: AudioBackend("null", "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)
|
||||
{
|
||||
if (last_time.time_since_epoch() != the_clock::duration::zero())
|
||||
{
|
||||
auto fduration = std::chrono::nanoseconds(1000000000L * samples / 44100);
|
||||
auto duration = fduration - (the_clock::now() - last_time);
|
||||
std::this_thread::sleep_for(duration);
|
||||
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;
|
||||
}
|
||||
|
||||
private:
|
||||
the_clock::time_point last_time;
|
||||
};
|
||||
static NullAudioBackend nullBackend;
|
||||
@@ -1,200 +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 <oboe/Oboe.h>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include "stdclass.h"
|
||||
|
||||
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", "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)
|
||||
->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",
|
||||
stream->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());
|
||||
}
|
||||
};
|
||||
static OboeBackend oboeBackend;
|
||||
|
||||
#endif
|
||||
@@ -1,337 +0,0 @@
|
||||
#if USE_OMX
|
||||
#include "audiostream.h"
|
||||
#include "cfg/option.h"
|
||||
#include <IL/OMX_Broadcom.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define PORT_INDEX 100
|
||||
#define OUTPUT_FREQ 44100
|
||||
|
||||
class OMXAudioBackend : public AudioBackend
|
||||
{
|
||||
OMX_HANDLETYPE omx_handle = nullptr;
|
||||
OMX_STATETYPE omx_state = OMX_StateInvalid;
|
||||
size_t audio_buffer_idx = 0;
|
||||
OMX_BUFFERHEADERTYPE** audio_buffers = nullptr;
|
||||
u32 latency_max = 0;
|
||||
u32 buffer_count = 0;
|
||||
u32 buffer_size = 0;
|
||||
u32 buffer_length = 0;
|
||||
pthread_mutex_t audio_lock;
|
||||
pthread_cond_t omx_state_cond;
|
||||
|
||||
static OMX_ERRORTYPE eventHandler(
|
||||
OMX_IN OMX_HANDLETYPE hComponent,
|
||||
OMX_IN OMX_PTR pAppData,
|
||||
OMX_IN OMX_EVENTTYPE eEvent,
|
||||
OMX_IN OMX_U32 nData1,
|
||||
OMX_IN OMX_U32 nData2,
|
||||
OMX_IN OMX_PTR pEventData)
|
||||
{
|
||||
OMXAudioBackend *backend = (OMXAudioBackend *)pAddData;
|
||||
pthread_mutex_lock(&backend->audio_lock);
|
||||
if(eEvent == OMX_EventCmdComplete && nData1 == OMX_CommandStateSet)
|
||||
{
|
||||
backend->omx_state = (OMX_STATETYPE)nData2;
|
||||
pthread_cond_signal(&backend->omx_state_cond);
|
||||
}
|
||||
pthread_mutex_unlock(&backend->audio_lock);
|
||||
return OMX_ErrorNone;
|
||||
}
|
||||
|
||||
static OMX_ERRORTYPE emptyBufferDone(
|
||||
OMX_IN OMX_HANDLETYPE hComponent,
|
||||
OMX_IN OMX_PTR pAppData,
|
||||
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer)
|
||||
{
|
||||
return OMX_ErrorNone;
|
||||
}
|
||||
|
||||
static OMX_ERRORTYPE fillBufferDone(
|
||||
OMX_OUT OMX_HANDLETYPE hComponent,
|
||||
OMX_OUT OMX_PTR pAppData,
|
||||
OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer)
|
||||
{
|
||||
return OMX_ErrorNone;
|
||||
}
|
||||
|
||||
void waitForState(OMX_STATETYPE state)
|
||||
{
|
||||
pthread_mutex_lock(&audio_lock);
|
||||
while(omx_state != state)
|
||||
pthread_cond_wait(&omx_state_cond, &audio_lock);
|
||||
pthread_mutex_unlock(&audio_lock);
|
||||
}
|
||||
|
||||
u32 getLatency()
|
||||
{
|
||||
OMX_PARAM_U32TYPE param;
|
||||
memset(¶m, 0, sizeof(OMX_PARAM_U32TYPE));
|
||||
param.nSize = sizeof(OMX_PARAM_U32TYPE);
|
||||
param.nVersion.nVersion = OMX_VERSION;
|
||||
param.nPortIndex = PORT_INDEX;
|
||||
|
||||
OMX_ERRORTYPE error = OMX_GetConfig(omx_handle, OMX_IndexConfigAudioRenderingLatency, ¶m);
|
||||
if(error != OMX_ErrorNone)
|
||||
WARN_LOG(AUDIO, "OMX: failed to get OMX configuration (OMX_IndexConfigAudioRenderingLatency). Error 0x%X", error);
|
||||
|
||||
return param.nU32 * 1000 / OUTPUT_FREQ;
|
||||
}
|
||||
|
||||
public:
|
||||
OMXAudioBackend()
|
||||
: AudioBackend("omx", "OpenMAX IL") {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
error = OMX_Init();
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: OMX_Init() failed. Error 0x%X", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize settings
|
||||
latency_max = config::OmxAudioLatency;
|
||||
buffer_size = config::AudioBufferSize * 4;
|
||||
buffer_count = 2 + OUTPUT_FREQ * latency_max / (buffer_size * 1000);
|
||||
|
||||
OMX_CALLBACKTYPE callbacks;
|
||||
callbacks.EventHandler = eventHandler;
|
||||
callbacks.EmptyBufferDone = emptyBufferDone;
|
||||
callbacks.FillBufferDone = fillBufferDone;
|
||||
|
||||
error = OMX_GetHandle(&omx_handle, (OMX_STRING)"OMX.broadcom.audio_render", this, &callbacks);
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: OMX_GetHandle() failed. Error 0x%X", error);
|
||||
OMX_Deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
OMX_PARAM_PORTDEFINITIONTYPE param;
|
||||
memset(¶m, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
|
||||
param.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
|
||||
param.nVersion.nVersion = OMX_VERSION;
|
||||
param.nPortIndex = PORT_INDEX;
|
||||
param.nBufferSize = buffer_size;
|
||||
param.nBufferCountActual = buffer_count;
|
||||
param.format.audio.eEncoding = OMX_AUDIO_CodingPCM;
|
||||
|
||||
error = OMX_SetParameter(omx_handle, OMX_IndexParamPortDefinition, ¶m);
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: failed to set OMX_IndexParamPortDefinition. Error 0x%X", error);
|
||||
OMX_Deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
OMX_AUDIO_PARAM_PCMMODETYPE pcm;
|
||||
memset(&pcm, 0, sizeof(OMX_AUDIO_PARAM_PCMMODETYPE));
|
||||
pcm.nSize = sizeof(OMX_AUDIO_PARAM_PCMMODETYPE);
|
||||
pcm.nVersion.nVersion = OMX_VERSION;
|
||||
pcm.nPortIndex = PORT_INDEX;
|
||||
pcm.nChannels = 2;
|
||||
pcm.eNumData = OMX_NumericalDataSigned;
|
||||
pcm.eEndian = OMX_EndianLittle;
|
||||
pcm.nSamplingRate = OUTPUT_FREQ;
|
||||
pcm.bInterleaved = OMX_TRUE;
|
||||
pcm.nBitPerSample = 16;
|
||||
pcm.ePCMMode = OMX_AUDIO_PCMModeLinear;
|
||||
pcm.eChannelMapping[1] = OMX_AUDIO_ChannelRF;
|
||||
pcm.eChannelMapping[0] = OMX_AUDIO_ChannelLF;
|
||||
|
||||
error = OMX_SetParameter(omx_handle, OMX_IndexParamAudioPcm, &pcm);
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: failed to set OMX_IndexParamAudioPcm. Error 0x%X", error);
|
||||
OMX_Deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disable all ports
|
||||
error = OMX_SendCommand(omx_handle, OMX_CommandPortDisable, PORT_INDEX, NULL);
|
||||
if (error != OMX_ErrorNone)
|
||||
WARN_LOG(AUDIO, "OMX: failed to do OMX_CommandPortDisable. Error 0x%X", error);
|
||||
|
||||
OMX_PORT_PARAM_TYPE param2;
|
||||
memset(¶m2, 0, sizeof(OMX_PORT_PARAM_TYPE));
|
||||
param2.nSize = sizeof(OMX_PORT_PARAM_TYPE);
|
||||
param2.nVersion.nVersion = OMX_VERSION;
|
||||
error = OMX_GetParameter(omx_handle, OMX_IndexParamOtherInit, ¶m2);
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: failed to get OMX_IndexParamOtherInit. Error 0x%X", error);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(u32 i = 0; i < param2.nPorts; i++)
|
||||
{
|
||||
u32 port = param2.nStartPortNumber + i;
|
||||
error = OMX_SendCommand(omx_handle, OMX_CommandPortDisable, port, NULL);
|
||||
if(error != OMX_ErrorNone)
|
||||
WARN_LOG(AUDIO, "OMX: failed to do OMX_CommandPortDisable on port %u. Error 0x%X", port, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Go into idle state
|
||||
error = OMX_SendCommand(omx_handle, OMX_CommandStateSet, OMX_StateIdle, NULL);
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: failed to set OMX_CommandStateSet. Error 0x%X", error);
|
||||
OMX_Deinit();
|
||||
return false;
|
||||
}
|
||||
waitForState(OMX_StateIdle);
|
||||
|
||||
// Check if we're in a state able to recieve buffers
|
||||
OMX_STATETYPE state;
|
||||
error = OMX_GetState(omx_handle, &state);
|
||||
if (error != OMX_ErrorNone || !(state == OMX_StateIdle || state == OMX_StateExecuting || state == OMX_StatePause))
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: state is incorrect. State 0x%X; Error 0x%X", state, error);
|
||||
OMX_Deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create audio buffers
|
||||
INFO_LOG(AUDIO, "OMX: creating %u buffers", buffer_count);
|
||||
|
||||
// Enable port
|
||||
error = OMX_SendCommand(omx_handle, OMX_CommandPortEnable, PORT_INDEX, NULL);
|
||||
if (error != OMX_ErrorNone)
|
||||
WARN_LOG(AUDIO, "OMX: failed to do OMX_CommandPortEnable. Error 0x%X", error);
|
||||
|
||||
// Free audio buffers if they're allocated
|
||||
if (audio_buffers != NULL)
|
||||
delete[] audio_buffers;
|
||||
|
||||
// Allocate buffers
|
||||
audio_buffers = new OMX_BUFFERHEADERTYPE*[buffer_count];
|
||||
for (size_t i = 0; i < buffer_count; i++)
|
||||
{
|
||||
error = OMX_AllocateBuffer(omx_handle, &audio_buffers[i], PORT_INDEX, NULL, buffer_size);
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: failed to allocate buffer[%u]. Error 0x%X", i, error);
|
||||
OMX_Deinit();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Set state to executing
|
||||
error = OMX_SendCommand(omx_handle, OMX_CommandStateSet, OMX_StateExecuting, NULL);
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: failed to do OMX_CommandStateSet. Error 0x%X", error);
|
||||
OMX_Deinit();
|
||||
return false;
|
||||
}
|
||||
waitForState(OMX_StateExecuting);
|
||||
|
||||
// Empty buffers
|
||||
for (size_t i = 0; i < buffer_count; i++)
|
||||
{
|
||||
memset(audio_buffers[i]->pBuffer, 0, buffer_size);
|
||||
audio_buffers[i]->nOffset = 0;
|
||||
audio_buffers[i]->nFilledLen = buffer_size;
|
||||
|
||||
error = OMX_EmptyThisBuffer(omx_handle, audio_buffers[i]);
|
||||
if(error != OMX_ErrorNone)
|
||||
WARN_LOG(AUDIO, "OMX: failed to empty buffer[%u]. Error 0x%X", i, error);
|
||||
}
|
||||
|
||||
const char* output_device = "local";
|
||||
if (config::OmxAudioHdmi)
|
||||
output_device = (const char*)"hdmi";
|
||||
|
||||
// Set audio destination
|
||||
OMX_CONFIG_BRCMAUDIODESTINATIONTYPE ar_dest;
|
||||
memset(&ar_dest, 0, sizeof(ar_dest));
|
||||
ar_dest.nSize = sizeof(OMX_CONFIG_BRCMAUDIODESTINATIONTYPE);
|
||||
ar_dest.nVersion.nVersion = OMX_VERSION;
|
||||
strcpy((char *)ar_dest.sName, output_device);
|
||||
error = OMX_SetConfig(omx_handle, OMX_IndexConfigBrcmAudioDestination, &ar_dest);
|
||||
if (error != OMX_ErrorNone)
|
||||
{
|
||||
WARN_LOG(AUDIO, "OMX: failed to set OMX configuration (OMX_IndexConfigBrcmAudioDestination). Error 0x%X", error);
|
||||
OMX_Deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
audio_buffer_idx = 0;
|
||||
buffer_length = 0;
|
||||
|
||||
INFO_LOG(AUDIO, "OMX: audio output to '%s'", ar_dest.sName);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
if(audio_buffers == NULL)
|
||||
return 1;
|
||||
|
||||
size_t data_size = samples * 4;
|
||||
|
||||
while(data_size > 0)
|
||||
{
|
||||
size_t copy_size = std::min(buffer_size - buffer_length, data_size);
|
||||
|
||||
// Don't have more than maximum audio latency
|
||||
u32 latency = getLatency();
|
||||
if(latency > latency_max)
|
||||
{
|
||||
usleep((latency - latency_max) * 1000);
|
||||
}
|
||||
else if(latency == 0)
|
||||
{
|
||||
INFO_LOG(AUDIO, "OMX: underrun occurred");
|
||||
}
|
||||
|
||||
memcpy(audio_buffers[audio_buffer_idx]->pBuffer + buffer_length, frame, copy_size);
|
||||
buffer_length += copy_size;
|
||||
frame = (char *)frame + copy_size;
|
||||
|
||||
// Flush buffer and swap
|
||||
if(buffer_length >= buffer_size)
|
||||
{
|
||||
audio_buffers[audio_buffer_idx]->nOffset = 0;
|
||||
audio_buffers[audio_buffer_idx]->nFilledLen = buffer_size;
|
||||
|
||||
OMX_ERRORTYPE error = OMX_EmptyThisBuffer(omx_handle, audio_buffers[audio_buffer_idx]);
|
||||
if(error != OMX_ErrorNone)
|
||||
INFO_LOG(AUDIO, "OMX: failed to empty buffer[%u]. Error 0x%X", audio_buffer_idx, error);
|
||||
|
||||
audio_buffer_idx = (audio_buffer_idx + 1) % buffer_count;
|
||||
buffer_length = 0;
|
||||
}
|
||||
|
||||
data_size -= copy_size;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
OMX_ERRORTYPE error;
|
||||
|
||||
// Is there anything else that needs to be done for omx?
|
||||
|
||||
error = OMX_Deinit();
|
||||
if(error != OMX_ErrorNone)
|
||||
WARN_LOG(AUDIO, "OMX: OMX_Deinit() failed. Error 0x%X", error);
|
||||
|
||||
if(audio_buffers != NULL)
|
||||
{
|
||||
delete[] audio_buffers;
|
||||
audio_buffers = NULL;
|
||||
}
|
||||
}
|
||||
};
|
||||
static OMXAudioBackend omxAudioBackend;
|
||||
|
||||
#endif
|
||||
@@ -1,116 +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;
|
||||
|
||||
public:
|
||||
OSSAudioBackend()
|
||||
: AudioBackend("oss", "Open Sound System") {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
audioFD = open("/dev/dsp", O_WRONLY);
|
||||
if (audioFD < 0)
|
||||
{
|
||||
WARN_LOG(AUDIO, "Couldn't open /dev/dsp.");
|
||||
return false;
|
||||
}
|
||||
INFO_LOG(AUDIO, "sound enabled, dsp opened for write");
|
||||
int tmp=44100;
|
||||
int err_ret;
|
||||
err_ret=ioctl(audioFD,SNDCTL_DSP_SPEED,&tmp);
|
||||
INFO_LOG(AUDIO, "set Frequency to %i, return %i (rate=%i)", 44100, err_ret, tmp);
|
||||
int channels=2;
|
||||
err_ret=ioctl(audioFD, SNDCTL_DSP_CHANNELS, &channels);
|
||||
INFO_LOG(AUDIO, "set dsp to stereo (%i => %i)", channels, err_ret);
|
||||
int format=AFMT_S16_LE;
|
||||
err_ret=ioctl(audioFD, SNDCTL_DSP_SETFMT, &format);
|
||||
INFO_LOG(AUDIO, "set dsp to %s audio (%i/%i => %i)", "16bits signed", AFMT_S16_LE, format, err_ret);
|
||||
|
||||
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 = open("/dev/dsp", O_RDONLY);
|
||||
if (recordFD < 0)
|
||||
{
|
||||
INFO_LOG(AUDIO, "OSS: can't open default audio capture device");
|
||||
return false;
|
||||
}
|
||||
int tmp = AFMT_S16_NE; // Native 16 bits
|
||||
if (ioctl(recordFD, SNDCTL_DSP_SETFMT, &tmp) == -1 || tmp != AFMT_S16_NE)
|
||||
{
|
||||
INFO_LOG(AUDIO, "OSS: can't set sample format");
|
||||
close(recordFD);
|
||||
recordFD = -1;
|
||||
return false;
|
||||
}
|
||||
tmp = 1;
|
||||
if (ioctl(recordFD, SNDCTL_DSP_CHANNELS, &tmp) == -1)
|
||||
{
|
||||
INFO_LOG(AUDIO, "OSS: can't set channel count");
|
||||
close(recordFD);
|
||||
recordFD = -1;
|
||||
return false;
|
||||
}
|
||||
tmp = sampling_freq;
|
||||
if (ioctl(recordFD, SNDCTL_DSP_SPEED, &tmp) == -1)
|
||||
{
|
||||
INFO_LOG(AUDIO, "OSS: can't set sample rate");
|
||||
close(recordFD);
|
||||
recordFD = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void termRecord() override
|
||||
{
|
||||
if (recordFD >= 0)
|
||||
close(recordFD);
|
||||
recordFD = -1;
|
||||
}
|
||||
|
||||
u32 record(void *buffer, u32 samples) override
|
||||
{
|
||||
samples *= 2;
|
||||
int l = read(recordFD, buffer, samples);
|
||||
if (l < (int)samples)
|
||||
{
|
||||
if (l < 0)
|
||||
{
|
||||
INFO_LOG(AUDIO, "OSS: Recording error");
|
||||
l = 0;
|
||||
}
|
||||
memset((u8 *)buffer + l, 0, samples - l);
|
||||
}
|
||||
return l / 2;
|
||||
}
|
||||
};
|
||||
static OSSAudioBackend ossAudioBackend;
|
||||
|
||||
#endif
|
||||
@@ -1,252 +0,0 @@
|
||||
#ifdef USE_PULSEAUDIO
|
||||
#include "audiostream.h"
|
||||
#include "cfg/option.h"
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
class PulseAudioBackend : public AudioBackend
|
||||
{
|
||||
pa_threaded_mainloop *mainloop = nullptr;
|
||||
pa_context *context = nullptr;
|
||||
pa_stream *stream = nullptr;
|
||||
pa_stream *record_stream = nullptr;
|
||||
|
||||
static void context_state_cb(pa_context *c, void *userdata)
|
||||
{
|
||||
PulseAudioBackend *backend = (PulseAudioBackend *)userdata;
|
||||
switch (pa_context_get_state(c))
|
||||
{
|
||||
case PA_CONTEXT_READY:
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
case PA_CONTEXT_FAILED:
|
||||
pa_threaded_mainloop_signal(backend->mainloop, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void stream_request_cb(pa_stream *s, size_t length, void *userdata)
|
||||
{
|
||||
PulseAudioBackend *backend = (PulseAudioBackend *)userdata;
|
||||
pa_threaded_mainloop_signal(backend->mainloop, 0);
|
||||
}
|
||||
|
||||
public:
|
||||
PulseAudioBackend()
|
||||
: AudioBackend("pulse", "PulseAudio") {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
mainloop = pa_threaded_mainloop_new();
|
||||
if (!mainloop)
|
||||
{
|
||||
WARN_LOG(AUDIO, "PulseAudio: pa_threaded_mainloop_new failed");
|
||||
return false;
|
||||
}
|
||||
context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), "flycast");
|
||||
if (!context)
|
||||
{
|
||||
WARN_LOG(AUDIO, "PulseAudio: pa_context_new failed");
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
pa_context_set_state_callback(context, context_state_cb, this);
|
||||
|
||||
if (pa_context_connect(context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0)
|
||||
{
|
||||
WARN_LOG(AUDIO, "PulseAudio: pa_context_connect failed");
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
if (pa_threaded_mainloop_start(mainloop) < 0)
|
||||
{
|
||||
WARN_LOG(AUDIO, "PulseAudio: pa_threaded_mainloop_start failed");
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
|
||||
if (pa_context_get_state(context) != PA_CONTEXT_READY)
|
||||
{
|
||||
WARN_LOG(AUDIO, "PulseAudio: context isn't ready");
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
pa_sample_spec spec{ PA_SAMPLE_S16LE, 44100, 2 };
|
||||
|
||||
stream = pa_stream_new(context, "audio", &spec, NULL);
|
||||
if (!stream)
|
||||
{
|
||||
WARN_LOG(AUDIO, "PulseAudio: pa_stream_new failed");
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
//pa_stream_set_state_callback(stream, stream_state_cb, this);
|
||||
pa_stream_set_write_callback(stream, stream_request_cb, this);
|
||||
//pa_stream_set_latency_update_callback(stream, stream_latency_update_cb, this);
|
||||
//pa_stream_set_underflow_callback(stream, underrun_update_cb, this);
|
||||
//pa_stream_set_buffer_attr_callback(stream, buffer_attr_cb, this);
|
||||
|
||||
pa_buffer_attr buffer_attr;
|
||||
buffer_attr.maxlength = -1;
|
||||
buffer_attr.tlength = pa_usec_to_bytes(config::AudioBufferSize * PA_USEC_PER_SEC / 44100, &spec);
|
||||
buffer_attr.prebuf = -1;
|
||||
buffer_attr.minreq = -1;
|
||||
buffer_attr.fragsize = -1;
|
||||
|
||||
if (pa_stream_connect_playback(stream, nullptr, &buffer_attr, PA_STREAM_ADJUST_LATENCY, nullptr, nullptr) < 0)
|
||||
{
|
||||
WARN_LOG(AUDIO, "PulseAudio: pa_stream_connect_playback failed");
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
|
||||
if (pa_stream_get_state(stream) != PA_STREAM_READY)
|
||||
{
|
||||
WARN_LOG(AUDIO, "PulseAudio: stream isn't ready");
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
term();
|
||||
return false;
|
||||
}
|
||||
|
||||
const pa_buffer_attr *server_attr = pa_stream_get_buffer_attr(stream);
|
||||
if (server_attr)
|
||||
DEBUG_LOG(AUDIO, "PulseAudio: requested %d samples buffer, got %d", buffer_attr.tlength / 4, server_attr->tlength / 4);
|
||||
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
const u8 *buf = (const u8 *)frame;
|
||||
size_t size = samples * 4;
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
while (size)
|
||||
{
|
||||
size_t writable = std::min(size, pa_stream_writable_size(stream));
|
||||
|
||||
if (writable)
|
||||
{
|
||||
pa_stream_write(stream, buf, writable, NULL, 0, PA_SEEK_RELATIVE);
|
||||
buf += writable;
|
||||
size -= writable;
|
||||
}
|
||||
else if (wait)
|
||||
pa_threaded_mainloop_wait(mainloop);
|
||||
else
|
||||
break;
|
||||
}
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
if (mainloop)
|
||||
pa_threaded_mainloop_stop(mainloop);
|
||||
|
||||
if (stream)
|
||||
{
|
||||
pa_stream_disconnect(stream);
|
||||
pa_stream_unref(stream);
|
||||
stream = nullptr;
|
||||
}
|
||||
if (context)
|
||||
{
|
||||
pa_context_disconnect(context);
|
||||
pa_context_unref(context);
|
||||
context = nullptr;
|
||||
}
|
||||
|
||||
if (mainloop)
|
||||
pa_threaded_mainloop_free(mainloop);
|
||||
mainloop = nullptr;
|
||||
}
|
||||
|
||||
bool initRecord(u32 sampling_freq) override
|
||||
{
|
||||
pa_sample_spec spec{ PA_SAMPLE_S16LE, sampling_freq, 1 };
|
||||
|
||||
record_stream = pa_stream_new(context, "record", &spec, NULL);
|
||||
if (!record_stream)
|
||||
{
|
||||
INFO_LOG(AUDIO, "PulseAudio: pa_stream_new failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
|
||||
pa_buffer_attr buffer_attr;
|
||||
buffer_attr.fragsize = 240 * 2;
|
||||
buffer_attr.maxlength = buffer_attr.fragsize * 2;
|
||||
|
||||
if (pa_stream_connect_record(record_stream, nullptr, &buffer_attr, PA_STREAM_NOFLAGS) < 0)
|
||||
{
|
||||
INFO_LOG(AUDIO, "PulseAudio: pa_stream_connect_record failed");
|
||||
pa_stream_unref(record_stream);
|
||||
record_stream = nullptr;
|
||||
return false;
|
||||
}
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
INFO_LOG(AUDIO, "PulseAudio: Successfully initialized capture device");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void termRecord() override
|
||||
{
|
||||
if (record_stream != nullptr)
|
||||
{
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
pa_stream_disconnect(record_stream);
|
||||
pa_stream_unref(record_stream);
|
||||
record_stream = nullptr;
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
}
|
||||
|
||||
u32 record(void *buffer, u32 samples) override
|
||||
{
|
||||
if (record_stream == nullptr)
|
||||
return 0;
|
||||
pa_threaded_mainloop_lock(mainloop);
|
||||
const void *data;
|
||||
size_t size;
|
||||
if (pa_stream_peek(record_stream, &data, &size) < 0)
|
||||
{
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
DEBUG_LOG(AUDIO, "PulseAudio: pa_stream_peek error");
|
||||
return 0;
|
||||
}
|
||||
if (size == 0)
|
||||
{
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
return 0;
|
||||
}
|
||||
size = std::min((size_t)samples * 2, size);
|
||||
if (data != nullptr)
|
||||
memcpy(buffer, data, size);
|
||||
else
|
||||
memset(buffer, 0, size);
|
||||
pa_stream_drop(record_stream);
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
|
||||
return size / 2;
|
||||
}
|
||||
};
|
||||
static PulseAudioBackend pulseAudioBackend;
|
||||
|
||||
#endif
|
||||
@@ -1,243 +0,0 @@
|
||||
#if defined(USE_SDL_AUDIO)
|
||||
|
||||
#include <SDL.h>
|
||||
#include "audiostream.h"
|
||||
#include "cfg/option.h"
|
||||
#include "stdclass.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
class SDLAudioBackend : AudioBackend
|
||||
{
|
||||
SDL_AudioDeviceID audiodev {};
|
||||
bool needs_resampling = false;
|
||||
cResetEvent read_wait;
|
||||
std::mutex stream_mutex;
|
||||
uint32_t *sample_buffer;
|
||||
unsigned sample_buffer_size = 0;
|
||||
unsigned sample_count = 0;
|
||||
SDL_AudioCVT audioCvt;
|
||||
|
||||
SDL_AudioDeviceID recorddev {};
|
||||
u8 recordbuf[480 * 4];
|
||||
std::atomic<size_t> rec_read;
|
||||
std::atomic<size_t> rec_write;
|
||||
|
||||
static void audioCallback(void* userdata, Uint8* stream, int len)
|
||||
{
|
||||
SDLAudioBackend *backend = (SDLAudioBackend *)userdata;
|
||||
|
||||
backend->stream_mutex.lock();
|
||||
// Wait until there's enough samples to feed the kraken
|
||||
unsigned oslen = len / sizeof(uint32_t);
|
||||
unsigned islen = backend->needs_resampling ? std::ceil(oslen / backend->audioCvt.len_ratio) : oslen;
|
||||
|
||||
if (backend->sample_count < islen)
|
||||
{
|
||||
// No data, just output a bit of silence for the underrun
|
||||
memset(stream, 0, len);
|
||||
backend->stream_mutex.unlock();
|
||||
backend->read_wait.Set();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!backend->needs_resampling) {
|
||||
// Just copy bytes for this case.
|
||||
memcpy(stream, &backend->sample_buffer[0], len);
|
||||
}
|
||||
else
|
||||
{
|
||||
SDL_AudioCVT& cvt = backend->audioCvt;
|
||||
cvt.len = islen * sizeof(uint32_t);
|
||||
memcpy(cvt.buf, &backend->sample_buffer[0], cvt.len);
|
||||
SDL_ConvertAudio(&cvt);
|
||||
memcpy(stream, cvt.buf, cvt.len_cvt);
|
||||
}
|
||||
|
||||
// Move samples in the buffer and consume them
|
||||
memmove(&backend->sample_buffer[0], &backend->sample_buffer[islen], (backend->sample_count - islen) * sizeof(uint32_t));
|
||||
backend->sample_count -= islen;
|
||||
|
||||
backend->stream_mutex.unlock();
|
||||
backend->read_wait.Set();
|
||||
}
|
||||
|
||||
public:
|
||||
SDLAudioBackend()
|
||||
: AudioBackend("sdl2", "Simple DirectMedia Layer 2 Audio") {}
|
||||
|
||||
bool init() override
|
||||
{
|
||||
if (!SDL_WasInit(SDL_INIT_AUDIO))
|
||||
{
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
||||
ERROR_LOG(AUDIO, "SDL2 error initializing audio subsystem: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
sample_buffer_size = std::max<u32>(SAMPLE_COUNT * 2, config::AudioBufferSize);
|
||||
sample_buffer = new uint32_t[sample_buffer_size]();
|
||||
sample_count = 0;
|
||||
|
||||
// Support 44.1KHz (native) but also upsampling to 48KHz
|
||||
SDL_AudioSpec wav_spec, out_spec;
|
||||
memset(&wav_spec, 0, sizeof(wav_spec));
|
||||
wav_spec.freq = 44100;
|
||||
wav_spec.format = AUDIO_S16;
|
||||
wav_spec.channels = 2;
|
||||
wav_spec.samples = SAMPLE_COUNT * 2; // Must be power of two
|
||||
wav_spec.callback = audioCallback;
|
||||
wav_spec.userdata = this;
|
||||
needs_resampling = false;
|
||||
|
||||
// Try 44.1KHz which should be faster since it's native.
|
||||
audiodev = SDL_OpenAudioDevice(NULL, 0, &wav_spec, &out_spec, 0);
|
||||
if (audiodev == 0)
|
||||
{
|
||||
INFO_LOG(AUDIO, "SDL2: SDL_OpenAudioDevice failed: %s", SDL_GetError());
|
||||
needs_resampling = true;
|
||||
wav_spec.freq = 48000;
|
||||
audiodev = SDL_OpenAudioDevice(NULL, 0, &wav_spec, &out_spec, 0);
|
||||
if (audiodev == 0)
|
||||
ERROR_LOG(AUDIO, "SDL2: SDL_OpenAudioDevice failed: %s", SDL_GetError());
|
||||
else
|
||||
{
|
||||
INFO_LOG(AUDIO, "SDL2: Using resampling to 48 KHz");
|
||||
int ret = SDL_BuildAudioCVT(&audioCvt, AUDIO_S16, 2, 44100, AUDIO_S16, 2, 48000);
|
||||
if (ret != 1 || audioCvt.needed == 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "SDL2: can't build audio converter: %s", SDL_GetError());
|
||||
SDL_CloseAudioDevice(audiodev);
|
||||
audiodev = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
audioCvt.buf = new u8[SAMPLE_COUNT * 2 * sizeof(uint32_t) * audioCvt.len_mult];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return audiodev != 0;
|
||||
}
|
||||
|
||||
u32 push(const void* frame, u32 samples, bool wait) override
|
||||
{
|
||||
// Unpause the device shall it be paused.
|
||||
if (SDL_GetAudioDeviceStatus(audiodev) != SDL_AUDIO_PLAYING)
|
||||
SDL_PauseAudioDevice(audiodev, 0);
|
||||
|
||||
// If wait, then wait for the buffer to be smaller than a certain size.
|
||||
stream_mutex.lock();
|
||||
if (wait) {
|
||||
while (sample_count + samples > sample_buffer_size) {
|
||||
stream_mutex.unlock();
|
||||
read_wait.Wait();
|
||||
stream_mutex.lock();
|
||||
}
|
||||
}
|
||||
|
||||
// Copy as many samples as possible, drop any remaining (this should not happen usually)
|
||||
unsigned free_samples = sample_buffer_size - sample_count;
|
||||
unsigned tocopy = samples < free_samples ? samples : free_samples;
|
||||
memcpy(&sample_buffer[sample_count], frame, tocopy * sizeof(uint32_t));
|
||||
sample_count += tocopy;
|
||||
stream_mutex.unlock();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void term() override
|
||||
{
|
||||
if (audiodev)
|
||||
{
|
||||
// Stop audio playback.
|
||||
SDL_PauseAudioDevice(audiodev, 1);
|
||||
read_wait.Set();
|
||||
SDL_CloseAudioDevice(audiodev);
|
||||
audiodev = SDL_AudioDeviceID();
|
||||
}
|
||||
delete [] sample_buffer;
|
||||
sample_buffer = nullptr;
|
||||
if (needs_resampling)
|
||||
{
|
||||
delete [] audioCvt.buf;
|
||||
audioCvt.buf = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void recordCallback(void *userdata, u8 *stream, int len)
|
||||
{
|
||||
SDLAudioBackend *backend = (SDLAudioBackend *)userdata;
|
||||
DEBUG_LOG(AUDIO, "SDL2: sdl2_record_cb len %d write %zd read %zd", len, (size_t)backend->rec_write, (size_t)backend->rec_read);
|
||||
while (len > 0)
|
||||
{
|
||||
size_t plen = std::min((size_t)len, sizeof(backend->recordbuf) - backend->rec_write);
|
||||
memcpy(&backend->recordbuf[backend->rec_write], stream, plen);
|
||||
len -= plen;
|
||||
backend->rec_write = (backend->rec_write + plen) % sizeof(backend->recordbuf);
|
||||
stream += plen;
|
||||
}
|
||||
}
|
||||
|
||||
bool initRecord(u32 sampling_freq) override
|
||||
{
|
||||
rec_write = 0;
|
||||
rec_read = 0;
|
||||
|
||||
SDL_AudioSpec wav_spec, out_spec;
|
||||
memset(&wav_spec, 0, sizeof(wav_spec));
|
||||
wav_spec.freq = sampling_freq;
|
||||
wav_spec.format = AUDIO_S16;
|
||||
wav_spec.channels = 1;
|
||||
wav_spec.samples = 256; // Must be power of two
|
||||
wav_spec.callback = recordCallback;
|
||||
wav_spec.userdata = this;
|
||||
recorddev = SDL_OpenAudioDevice(NULL, 1, &wav_spec, &out_spec, 0);
|
||||
if (recorddev == 0)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "SDL2: Cannot open audio capture device: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
SDL_PauseAudioDevice(recorddev, 0);
|
||||
INFO_LOG(AUDIO, "SDL2: opened audio capture device");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void termRecord() override
|
||||
{
|
||||
if (recorddev != 0)
|
||||
{
|
||||
SDL_PauseAudioDevice(recorddev, 1);
|
||||
SDL_CloseAudioDevice(recorddev);
|
||||
recorddev = 0;
|
||||
}
|
||||
}
|
||||
|
||||
u32 record(void* frame, u32 samples) override
|
||||
{
|
||||
u32 count = 0;
|
||||
samples *= 2;
|
||||
while (samples > 0)
|
||||
{
|
||||
u32 avail = std::min(rec_write - rec_read, sizeof(recordbuf) - rec_read);
|
||||
if (avail == 0)
|
||||
break;
|
||||
avail = std::min(avail, samples);
|
||||
memcpy((u8 *)frame + count, &recordbuf[rec_read], avail);
|
||||
rec_read = (rec_read + avail) % sizeof(recordbuf);
|
||||
samples -= avail;
|
||||
count += avail;
|
||||
}
|
||||
DEBUG_LOG(AUDIO, "SDL2: sdl2_record len %d ret %d write %zd read %zd", samples * 2, count, (size_t)rec_write, (size_t)rec_read);
|
||||
|
||||
return count / 2;
|
||||
}
|
||||
};
|
||||
static SDLAudioBackend sdlAudioBackend;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
#include "audiostream.h"
|
||||
#include "cfg/option.h"
|
||||
|
||||
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->name.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()
|
||||
{
|
||||
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->name.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->name.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;
|
||||
}
|
||||
@@ -1,131 +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;
|
||||
std::string 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);
|
||||
}
|
||||
|
||||
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;
|
||||
std::atomic_int readCursor { 0 };
|
||||
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;
|
||||
}
|
||||
};
|
||||
+60
-107
@@ -1,122 +1,75 @@
|
||||
#pragma once
|
||||
//#define STRICT_MODE
|
||||
#ifndef STRICT_MODE
|
||||
#define FAST_MMU
|
||||
#define USE_WINCE_HACK
|
||||
#endif
|
||||
/*
|
||||
|
||||
|
||||
#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
|
||||
nullDC-Beagle build configuration options
|
||||
|
||||
fine grained options
|
||||
HOST_OS,HOST_CPU, ..
|
||||
|
||||
build-level options
|
||||
TARGET_BEAGLE, TARGET_WIN86, ...
|
||||
|
||||
code shouldn't depend on build level options whenever possible
|
||||
*/
|
||||
|
||||
//ndc configs
|
||||
|
||||
#define NO_MMU
|
||||
//#define HOST_NO_REC
|
||||
|
||||
#define DC_PLATFORM_MASK 7
|
||||
#define DC_PLATFORM_NORMAL 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 */
|
||||
|
||||
|
||||
#define DC_PLATFORM DC_PLATFORM_NORMAL
|
||||
|
||||
|
||||
//Target platform configs
|
||||
//HOST_OS
|
||||
#define OS_WINDOWS 0x10000001
|
||||
#define OS_LINUX 0x10000002
|
||||
|
||||
//HOST_CPU
|
||||
#define CPU_X86 0x20000001
|
||||
#define CPU_ARM 0x20000002
|
||||
#define CPU_MIPS 0x20000003
|
||||
#define CPU_X64 0x20000004
|
||||
#define CPU_GENERIC 0x20000005 //used for pnacl, emscripten, etc
|
||||
#define CPU_PPC 0x20000006
|
||||
#define CPU_PPC64 0x20000007
|
||||
#define CPU_ARM64 0x20000008
|
||||
|
||||
//FEAT_SHREC, FEAT_AREC, FEAT_DSPREC
|
||||
#define DYNAREC_NONE 0x40000001
|
||||
#define DYNAREC_JIT 0x40000002
|
||||
//BUILD_COMPILER
|
||||
#define COMPILER_VC 0x30000001
|
||||
#define COMPILER_GCC 0x30000002
|
||||
|
||||
//automatic
|
||||
///
|
||||
#if (!defined(TARGET_WIN86) && !defined(TARGET_BEAGLE) && !defined(TARGET_NACL32) && !defined(TARGET_GCW0) && !defined(TARGET_PANDORA))
|
||||
#define TARGET_WIN86
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
#define HOST_CPU CPU_X64
|
||||
#elif defined(__i386__) || defined(_M_IX86)
|
||||
#ifdef TARGET_WIN86
|
||||
#define HOST_OS OS_WINDOWS
|
||||
#define HOST_CPU CPU_X86
|
||||
#elif defined(__arm__) || defined (_M_ARM)
|
||||
#define BUILD_COMPILER COMPILER_VC
|
||||
#elif TARGET_PANDORA
|
||||
#define HOST_OS OS_LINUX
|
||||
#define HOST_CPU CPU_ARM
|
||||
#elif defined(__aarch64__) || defined(_M_ARM64)
|
||||
#define HOST_CPU CPU_ARM64
|
||||
#elif defined(__mips__)
|
||||
#define BUILD_COMPILER COMPILER_GCC
|
||||
#elif TARGET_BEAGLE
|
||||
#define HOST_OS OS_LINUX
|
||||
#define HOST_CPU CPU_ARM
|
||||
#define BUILD_COMPILER COMPILER_GCC
|
||||
#elif TARGET_GCW0
|
||||
#define HOST_OS OS_LINUX
|
||||
#define HOST_CPU CPU_MIPS
|
||||
#define BUILD_COMPILER COMPILER_GCC
|
||||
#elif TARGET_NACL32
|
||||
#define HOST_OS OS_LINUX
|
||||
#define HOST_CPU CPU_X86
|
||||
#define BUILD_COMPILER COMPILER_GCC
|
||||
#else
|
||||
#define HOST_CPU CPU_GENERIC
|
||||
#error Invalid Target: TARGET_* not defined
|
||||
#endif
|
||||
|
||||
#if defined(TARGET_NO_REC)
|
||||
#define FEAT_SHREC DYNAREC_NONE
|
||||
#define FEAT_AREC DYNAREC_NONE
|
||||
#define FEAT_DSPREC DYNAREC_NONE
|
||||
#endif
|
||||
|
||||
#if defined(TARGET_NO_AREC)
|
||||
#define FEAT_SHREC DYNAREC_JIT
|
||||
#define FEAT_AREC DYNAREC_NONE
|
||||
#define FEAT_DSPREC DYNAREC_NONE
|
||||
#endif
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#define FEAT_NO_RWX_PAGES
|
||||
#endif
|
||||
|
||||
//defaults
|
||||
|
||||
#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
|
||||
#endif
|
||||
|
||||
#ifndef FEAT_AREC
|
||||
#if HOST_CPU == CPU_ARM || HOST_CPU == CPU_ARM64 || HOST_CPU == CPU_X64
|
||||
#define FEAT_AREC DYNAREC_JIT
|
||||
#else
|
||||
#define FEAT_AREC DYNAREC_NONE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef FEAT_DSPREC
|
||||
#if HOST_CPU == CPU_ARM || HOST_CPU == CPU_ARM64 || HOST_CPU == CPU_X86 || HOST_CPU == CPU_X64
|
||||
#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
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
|
||||
#define TARGET_UWP
|
||||
#endif
|
||||
#ifdef HAVE_D3D11
|
||||
#define USE_DX11
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(LIBRETRO) && !defined(TARGET_NO_EXCEPTIONS)
|
||||
#define USE_GGPO
|
||||
#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
|
||||
+404
-101
@@ -1,132 +1,435 @@
|
||||
/*
|
||||
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 "cfg.h"
|
||||
#include "ini.h"
|
||||
#include "stdclass.h"
|
||||
|
||||
#include <cerrno>
|
||||
string cfgPath;
|
||||
|
||||
static std::string cfgPath;
|
||||
static bool save_config = true;
|
||||
static bool autoSave = true;
|
||||
//A config remains virtual only as long as a write at it
|
||||
//doesn't override the virtual value.While a config is virtual, a copy of its 'real' value is held and preserved
|
||||
|
||||
static emucfg::ConfigFile cfgdb;
|
||||
//Is this a virtual entry ?
|
||||
#define CEM_VIRTUAL 1
|
||||
//Should the value be saved ?
|
||||
#define CEM_SAVE 2
|
||||
//is this entry readonly ?
|
||||
#define CEM_READONLY 4
|
||||
//the move is from loading ?
|
||||
#define CEM_LOAD 8
|
||||
|
||||
static void saveConfigFile()
|
||||
struct ConfigEntry
|
||||
{
|
||||
FILE* cfgfile = nowide::fopen(cfgPath.c_str(), "wt");
|
||||
if (!cfgfile)
|
||||
ConfigEntry(ConfigEntry* pp)
|
||||
{
|
||||
WARN_LOG(COMMON, "Error: Unable to open file '%s' for saving", cfgPath.c_str());
|
||||
next=pp;
|
||||
flags=0;
|
||||
}
|
||||
else
|
||||
|
||||
|
||||
u32 flags;
|
||||
string name;
|
||||
string value;
|
||||
string valueVirtual;
|
||||
ConfigEntry* next;
|
||||
void SaveFile(FILE* file)
|
||||
{
|
||||
cfgdb.save(cfgfile);
|
||||
std::fclose(cfgfile);
|
||||
if (flags & CEM_SAVE)
|
||||
fprintf(file,"%s=%s\n",name.c_str(),value.c_str());
|
||||
}
|
||||
|
||||
string GetValue()
|
||||
{
|
||||
if (flags&CEM_VIRTUAL)
|
||||
return valueVirtual;
|
||||
else
|
||||
return value;
|
||||
}
|
||||
}
|
||||
void cfgSaveStr(const std::string& section, const std::string& key, const std::string& value)
|
||||
};
|
||||
struct ConfigSection
|
||||
{
|
||||
cfgdb.set(section, key, value);
|
||||
|
||||
if (save_config && autoSave)
|
||||
saveConfigFile();
|
||||
}
|
||||
|
||||
bool cfgOpen()
|
||||
{
|
||||
if (get_writable_config_path("").empty())
|
||||
// Config dir not set (android onboarding)
|
||||
return false;
|
||||
|
||||
const char* filename = "emu.cfg";
|
||||
std::string config_path_read = get_readonly_config_path(filename);
|
||||
cfgPath = get_writable_config_path(filename);
|
||||
|
||||
FILE* cfgfile = nowide::fopen(config_path_read.c_str(), "r");
|
||||
if(cfgfile != NULL) {
|
||||
cfgdb.parse(cfgfile);
|
||||
std::fclose(cfgfile);
|
||||
}
|
||||
else
|
||||
u32 flags;
|
||||
string name;
|
||||
ConfigEntry* entrys;
|
||||
ConfigSection* next;
|
||||
|
||||
ConfigSection(ConfigSection* pp)
|
||||
{
|
||||
// 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));
|
||||
if (error_code == ENOENT || cfgPath != config_path_read)
|
||||
next=pp;
|
||||
flags=0;
|
||||
entrys=0;
|
||||
}
|
||||
ConfigEntry* FindEntry(string name)
|
||||
{
|
||||
ConfigEntry* c= entrys;
|
||||
while(c)
|
||||
{
|
||||
// Config file didn't exist
|
||||
INFO_LOG(COMMON, "Creating new empty config file at '%s'", cfgPath.c_str());
|
||||
saveConfigFile();
|
||||
if (stricmp(name.c_str(),c->name.c_str())==0)
|
||||
return c;
|
||||
c=c->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
void SetEntry(string name,string value,u32 eflags)
|
||||
{
|
||||
ConfigEntry* c=FindEntry(name);
|
||||
if (c)
|
||||
{
|
||||
//readonly is read only =)
|
||||
if (c->flags & CEM_READONLY)
|
||||
return;
|
||||
|
||||
//virtual : save only if different value
|
||||
if (c->flags & CEM_VIRTUAL)
|
||||
{
|
||||
|
||||
if(stricmp(c->valueVirtual.c_str(),value.c_str())==0)
|
||||
return;
|
||||
c->flags&=~CEM_VIRTUAL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// There was some other error (may be a permissions problem or something like that)
|
||||
save_config = false;
|
||||
entrys=c= new ConfigEntry(entrys);
|
||||
c->name=name;
|
||||
}
|
||||
|
||||
verify(!(c->flags&(CEM_VIRTUAL|CEM_READONLY)));
|
||||
//Virtual
|
||||
//Virtual | ReadOnly
|
||||
//Save
|
||||
if (eflags & CEM_VIRTUAL)
|
||||
{
|
||||
verify(!(eflags & CEM_SAVE));
|
||||
c->flags|=eflags;
|
||||
c->valueVirtual=value;
|
||||
}
|
||||
else if (eflags & CEM_SAVE)
|
||||
{
|
||||
verify(!(eflags & (CEM_VIRTUAL|CEM_READONLY)));
|
||||
flags|=CEM_SAVE;
|
||||
c->flags|=CEM_SAVE;
|
||||
|
||||
c->value=value;
|
||||
}
|
||||
else
|
||||
{
|
||||
die("Invalid eflags value");
|
||||
}
|
||||
|
||||
}
|
||||
~ConfigSection()
|
||||
{
|
||||
ConfigEntry* n=entrys;
|
||||
|
||||
while(n)
|
||||
{
|
||||
ConfigEntry* p=n;
|
||||
n=n->next;
|
||||
delete p;
|
||||
}
|
||||
}
|
||||
void SaveFile(FILE* file)
|
||||
{
|
||||
if (flags&CEM_SAVE)
|
||||
{
|
||||
fprintf(file,"[%s]\n",name.c_str());
|
||||
|
||||
vector<ConfigEntry*> stuff;
|
||||
|
||||
ConfigEntry* n=entrys;
|
||||
|
||||
while(n)
|
||||
{
|
||||
stuff.push_back(n);
|
||||
n=n->next;
|
||||
}
|
||||
|
||||
for (int i=stuff.size()-1;i>=0;i--)
|
||||
{
|
||||
stuff[i]->SaveFile(file);
|
||||
}
|
||||
|
||||
fprintf(file,"\n");
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
struct ConfigFile
|
||||
{
|
||||
ConfigSection* entrys;
|
||||
ConfigSection* FindSection(string name)
|
||||
{
|
||||
ConfigSection* c= entrys;
|
||||
while(c)
|
||||
{
|
||||
if (stricmp(name.c_str(),c->name.c_str())==0)
|
||||
return c;
|
||||
c=c->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
ConfigSection* GetEntry(string name)
|
||||
{
|
||||
ConfigSection* c=FindSection(name);
|
||||
if (!c)
|
||||
{
|
||||
entrys=c= new ConfigSection(entrys);
|
||||
c->name=name;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
~ConfigFile()
|
||||
{
|
||||
ConfigSection* n=entrys;
|
||||
|
||||
while(n)
|
||||
{
|
||||
ConfigSection* p=n;
|
||||
n=n->next;
|
||||
delete p;
|
||||
}
|
||||
}
|
||||
|
||||
void ParseFile(FILE* file)
|
||||
{
|
||||
|
||||
}
|
||||
void SaveFile(FILE* file)
|
||||
{
|
||||
vector<ConfigSection*> stuff;
|
||||
|
||||
ConfigSection* n=entrys;
|
||||
|
||||
while(n)
|
||||
{
|
||||
stuff.push_back(n);
|
||||
n=n->next;
|
||||
}
|
||||
|
||||
for (int i=stuff.size()-1;i>=0;i--)
|
||||
{
|
||||
if (stuff[i]->name!="emu")
|
||||
stuff[i]->SaveFile(file);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ConfigFile cfgdb;
|
||||
|
||||
void savecfgf()
|
||||
{
|
||||
FILE* cfgfile = fopen(cfgPath.c_str(),"wt");
|
||||
if (!cfgfile)
|
||||
printf("Error : Unable to open file for saving \n");
|
||||
else
|
||||
{
|
||||
cfgdb.SaveFile(cfgfile);
|
||||
fclose(cfgfile);
|
||||
}
|
||||
}
|
||||
void cfgSaveStr(const wchar * Section, const wchar * Key, const wchar * String)
|
||||
{
|
||||
cfgdb.GetEntry(Section)->SetEntry(Key,String,CEM_SAVE);
|
||||
savecfgf();
|
||||
//WritePrivateProfileString(Section,Key,String,cfgPath);
|
||||
}
|
||||
//New config code
|
||||
|
||||
/*
|
||||
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
|
||||
*/
|
||||
struct vitem
|
||||
{
|
||||
string s;
|
||||
string n;
|
||||
string v;
|
||||
vitem(string a,string b,string c){s=a;n=b;v=c;}
|
||||
};
|
||||
vector<vitem> vlist;
|
||||
wchar* trim_ws(wchar* str);
|
||||
|
||||
bool cfgOpen()
|
||||
{
|
||||
cfgPath=GetPath("/emu.cfg");
|
||||
FILE* cfgfile = fopen(cfgPath.c_str(),"r");
|
||||
if(!cfgfile) {
|
||||
cfgfile = fopen(cfgPath.c_str(),"wt");
|
||||
if(!cfgfile)
|
||||
printf("Unable to open the config file for reading or writing\nfile : %s\n",cfgPath.c_str());
|
||||
else
|
||||
{
|
||||
fseek(cfgfile,0,SEEK_SET);
|
||||
fclose(cfgfile);
|
||||
cfgfile = fopen(cfgPath.c_str(),"r");
|
||||
if(!cfgfile)
|
||||
printf("Unable to open the config file for reading\nfile : %s\n",cfgPath.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
wchar line[512];
|
||||
wchar cur_sect[512]={0};
|
||||
int cline=0;
|
||||
while(cfgfile && !feof(cfgfile))
|
||||
{
|
||||
fgets(line,512,cfgfile);
|
||||
if (feof(cfgfile))
|
||||
break;
|
||||
|
||||
cline++;
|
||||
|
||||
if (strlen(line)<3)
|
||||
continue;
|
||||
if (line[strlen(line)-1]=='\r' || line[strlen(line)-1]=='\n')
|
||||
line[strlen(line)-1]=0;
|
||||
|
||||
wchar* tl=trim_ws(line);
|
||||
if (tl[0]=='[' && tl[strlen(tl)-1]==']')
|
||||
{
|
||||
tl[strlen(tl)-1]=0;
|
||||
strcpy(cur_sect,tl+1);
|
||||
trim_ws(cur_sect);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cur_sect[0]==0)
|
||||
continue;//no open section
|
||||
wchar* str1=strstr(tl,"=");
|
||||
if (!str1)
|
||||
{
|
||||
printf("Malformed entry on config - ignoring @ %d(%s)\n",cline,tl);
|
||||
continue;
|
||||
}
|
||||
*str1=0;
|
||||
str1++;
|
||||
wchar* v=trim_ws(str1);
|
||||
wchar* k=trim_ws(tl);
|
||||
if (v && k)
|
||||
{
|
||||
ConfigSection*cs=cfgdb.GetEntry(cur_sect);
|
||||
|
||||
//if (!cs->FindEntry(k))
|
||||
cs->SetEntry(k,v,CEM_SAVE|CEM_LOAD);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Malformed entry on config - ignoring @ %d(%s)\n",cline,tl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i=0;i<vlist.size();i++)
|
||||
{
|
||||
cfgdb.GetEntry(vlist[i].s)->SetEntry(vlist[i].n,vlist[i].v,CEM_VIRTUAL);
|
||||
}
|
||||
if (cfgfile)
|
||||
{
|
||||
cfgdb.SaveFile(cfgfile);
|
||||
fclose(cfgfile);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string cfgLoadStr(const std::string& section, const std::string& key, const std::string& def)
|
||||
//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)
|
||||
{
|
||||
return cfgdb.get(section, key, def);
|
||||
if (Section==0)
|
||||
return -1;
|
||||
//return cfgRead(Section,Key,0);
|
||||
ConfigSection*cs= cfgdb.FindSection(Section);
|
||||
if (cs == 0)
|
||||
return 0;
|
||||
|
||||
if (Key==0)
|
||||
return 1;
|
||||
|
||||
ConfigEntry* ce=cs->FindEntry(Key);
|
||||
if (ce!=0)
|
||||
return 2;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
void cfgLoadStr(const wchar * Section, const wchar * Key, wchar * Return,const wchar* Default)
|
||||
{
|
||||
verify(Section!=0 && strlen(Section)!=0);
|
||||
verify(Key!=0 && strlen(Key)!=0);
|
||||
verify(Return!=0);
|
||||
if (Default==0)
|
||||
Default="";
|
||||
ConfigSection* cs= cfgdb.GetEntry(Section);
|
||||
ConfigEntry* ce=cs->FindEntry(Key);
|
||||
if (!ce)
|
||||
{
|
||||
cs->SetEntry(Key,Default,CEM_SAVE);
|
||||
strcpy(Return,Default);
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(Return,ce->GetValue().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void cfgSaveInt(const std::string& section, const std::string& key, s32 value)
|
||||
//These are helpers , mainly :)
|
||||
s32 cfgLoadInt(const wchar * Section, const wchar * Key,s32 Default)
|
||||
{
|
||||
cfgSaveStr(section, key, std::to_string(value));
|
||||
wchar temp_d[30];
|
||||
wchar temp_o[30];
|
||||
sprintf(temp_d,"%d",Default);
|
||||
cfgLoadStr(Section,Key,temp_o,temp_d);
|
||||
return atoi(temp_o);
|
||||
}
|
||||
|
||||
s32 cfgLoadInt(const std::string& section, const std::string& key, s32 def)
|
||||
void cfgSaveInt(const wchar * Section, const wchar * Key, s32 Int)
|
||||
{
|
||||
return cfgdb.get_int(section, key, def);
|
||||
wchar tmp[32];
|
||||
sprintf(tmp,"%d", Int);
|
||||
cfgSaveStr(Section,Key,tmp);
|
||||
}
|
||||
|
||||
int64_t cfgLoadInt64(const std::string& section, const std::string& key, int64_t def) {
|
||||
return cfgdb.get_int64(section, key, def);
|
||||
}
|
||||
void cfgSaveInt64(const std::string& section, const std::string& key, int64_t value) {
|
||||
cfgdb.set_int64(section, key, value);
|
||||
}
|
||||
|
||||
void cfgSaveBool(const std::string& section, const std::string& key, bool value)
|
||||
void cfgSetVitual(const wchar * Section, const wchar * Key, const wchar * String)
|
||||
{
|
||||
cfgSaveStr(section, key, value ? "yes" : "no");
|
||||
}
|
||||
|
||||
bool cfgLoadBool(const std::string& section, const std::string& key, bool def)
|
||||
{
|
||||
return cfgdb.get_bool(section, key, def);
|
||||
}
|
||||
|
||||
void cfgSetVirtual(const std::string& section, const std::string& key, const std::string& value)
|
||||
{
|
||||
cfgdb.set(section, key, value, true);
|
||||
}
|
||||
|
||||
bool cfgIsVirtual(const std::string& section, const std::string& key)
|
||||
{
|
||||
return cfgdb.is_virtual(section, key);
|
||||
}
|
||||
|
||||
bool cfgHasSection(const std::string& section)
|
||||
{
|
||||
return cfgdb.has_section(section);
|
||||
}
|
||||
|
||||
void cfgDeleteSection(const std::string& section)
|
||||
{
|
||||
cfgdb.delete_section(section);
|
||||
}
|
||||
|
||||
void cfgDeleteEntry(const std::string& section, const std::string& key)
|
||||
{
|
||||
cfgdb.delete_entry(section, key);
|
||||
}
|
||||
|
||||
void cfgSetAutoSave(bool autoSave)
|
||||
{
|
||||
::autoSave = autoSave;
|
||||
if (autoSave)
|
||||
saveConfigFile();
|
||||
}
|
||||
vlist.push_back(vitem(Section,Key,String));
|
||||
//cfgdb.GetEntry(Section,CEM_VIRTUAL)->SetEntry(Key,String,CEM_VIRTUAL);
|
||||
}
|
||||
+14
-16
@@ -2,21 +2,19 @@
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/*
|
||||
** 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 ! **
|
||||
*/
|
||||
|
||||
bool cfgOpen();
|
||||
s32 cfgLoadInt(const std::string& section, const std::string& key, s32 def);
|
||||
void cfgSaveInt(const std::string& section, const std::string& key, s32 value);
|
||||
int64_t cfgLoadInt64(const std::string& section, const std::string& key, int64_t def);
|
||||
void cfgSaveInt64(const std::string& section, const std::string& key, int64_t value);
|
||||
std::string cfgLoadStr(const std::string& section, const std::string& key, const std::string& def);
|
||||
void cfgSaveStr(const std::string& section, const std::string& key, const std::string& value);
|
||||
void cfgSaveBool(const std::string& section, const std::string& key, bool value);
|
||||
bool cfgLoadBool(const std::string& section, const std::string& key, bool def);
|
||||
void cfgSetVirtual(const std::string& section, const std::string& key, const std::string& value);
|
||||
bool cfgIsVirtual(const std::string& section, const std::string& key);
|
||||
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);
|
||||
void cfgSaveStr(const wchar * lpSection, const wchar * lpKey, const wchar * lpString);
|
||||
s32 cfgExists(const wchar * Section, const wchar * Key);
|
||||
void cfgSetVitual(const wchar * lpSection, const wchar * lpKey, const wchar * lpString);
|
||||
|
||||
void ParseCommandLine(int argc, char *argv[]);
|
||||
|
||||
void cfgSetAutoSave(bool autoSave);
|
||||
bool cfgHasSection(const std::string& section);
|
||||
void cfgDeleteSection(const std::string& section);
|
||||
void cfgDeleteEntry(const std::string& section, const std::string& key);
|
||||
bool ParseCommandLine(int argc,wchar* argv[]);
|
||||
+80
-85
@@ -1,66 +1,91 @@
|
||||
/*
|
||||
Command line parsing
|
||||
~yay~
|
||||
|
||||
Nothing too interesting here, really
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "cfg/cfg.h"
|
||||
#include "stdclass.h"
|
||||
|
||||
static int setconfig(char *arg[], int cl)
|
||||
wchar* trim_ws(wchar* str)
|
||||
{
|
||||
int rv=0;
|
||||
if (str==0 || strlen(str)==0)
|
||||
return 0;
|
||||
|
||||
if (cl < 1)
|
||||
while(*str)
|
||||
{
|
||||
WARN_LOG(COMMON, "-config : invalid number of parameters, format is section:key=value,section:key=value,...");
|
||||
return rv;
|
||||
if (!isspace(*str))
|
||||
break;
|
||||
str++;
|
||||
}
|
||||
|
||||
std::string value(arg[1]);
|
||||
size_t l=strlen(str);
|
||||
|
||||
if (l==0)
|
||||
return 0;
|
||||
|
||||
while(l>0)
|
||||
{
|
||||
if (!isspace(str[l-1]))
|
||||
break;
|
||||
str[l-1]=0;
|
||||
l--;
|
||||
}
|
||||
|
||||
if (l==0)
|
||||
return 0;
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
int setconfig(wchar** arg,int cl)
|
||||
{
|
||||
int rv=0;
|
||||
for(;;)
|
||||
{
|
||||
auto seppos = value.find(':');
|
||||
if (seppos == std::string::npos)
|
||||
{
|
||||
if (cl<1)
|
||||
{
|
||||
WARN_LOG(COMMON, "-config : invalid parameter %s, format is section:key=value,section:key=value,...", value.c_str());
|
||||
break;
|
||||
printf("-config : invalid number of parameters, format is section:key=value\n");
|
||||
return rv;
|
||||
}
|
||||
auto eqpos = value.find('=', seppos);
|
||||
if (eqpos == std::string::npos)
|
||||
wchar* sep=strstr(arg[1],":");
|
||||
if (sep==0)
|
||||
{
|
||||
WARN_LOG(COMMON, "-config : invalid parameter %s, format is section:key=value,section:key=value,...", value.c_str());
|
||||
break;
|
||||
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;
|
||||
}
|
||||
|
||||
auto commapos = value.find(',', eqpos);
|
||||
*sep++=0;
|
||||
*value++=0;
|
||||
|
||||
std::string sect = trim_ws(value.substr(0, seppos));
|
||||
std::string key = trim_ws(value.substr(seppos + 1, eqpos - seppos - 1));
|
||||
auto endofcurrent = (commapos == std::string::npos) ? value.size() : commapos;
|
||||
std::string next = (commapos == std::string::npos) ? "" : trim_ws(value.substr(endofcurrent + 1, value.size() - endofcurrent));
|
||||
value = trim_ws(value.substr(eqpos + 1, endofcurrent - eqpos - 1));
|
||||
wchar* sect=trim_ws(arg[1]);
|
||||
wchar* key=trim_ws(sep);
|
||||
value=trim_ws(value);
|
||||
|
||||
if (sect.empty() || key.empty())
|
||||
if (sect==0 || key==0)
|
||||
{
|
||||
WARN_LOG(COMMON, "-config : invalid parameter, format is section:key=value,section:key=value,...");
|
||||
break;
|
||||
printf("-config : invalid parameter, format is section:key=value\n");
|
||||
return rv;
|
||||
}
|
||||
|
||||
INFO_LOG(COMMON, "Virtual cfg %s:%s=%s", sect.c_str(), key.c_str(), value.c_str());
|
||||
if (value==0)
|
||||
value="";
|
||||
printf("Virtual cfg %s:%s=%s\n",sect,key,value);
|
||||
|
||||
cfgSetVirtual(sect, key, value);
|
||||
cfgSetVitual(sect,key,value);
|
||||
rv++;
|
||||
|
||||
if (commapos == std::string::npos)
|
||||
rv++;
|
||||
|
||||
if (commapos != std::string::npos)
|
||||
{
|
||||
value = next;
|
||||
continue;
|
||||
}
|
||||
else if (cl>=3 && strcmp(arg[2],",")==0)
|
||||
if (cl>=3 && stricmp(arg[2],",")==0)
|
||||
{
|
||||
cl-=2;
|
||||
arg+=2;
|
||||
@@ -73,71 +98,41 @@ static int setconfig(char *arg[], int cl)
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int showhelp()
|
||||
int showhelp(wchar** arg,int cl)
|
||||
{
|
||||
printf("Usage: flycast [OPTION]... [CONTENT]\n\n");
|
||||
printf("Options:\n");
|
||||
printf("-config section:key=value add a virtual config value;\n");
|
||||
printf(" virtual config values won't be saved to the .cfg file\n");
|
||||
printf(" unless a different value is written to them\n");
|
||||
printf("-help display this help\n");
|
||||
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.");
|
||||
|
||||
exit(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ParseCommandLine(int argc,char* argv[])
|
||||
bool ParseCommandLine(int argc,wchar* argv[])
|
||||
{
|
||||
settings.content.path.clear();
|
||||
|
||||
int cl=argc-2;
|
||||
char** arg=argv+1;
|
||||
wchar** arg=argv+1;
|
||||
while(cl>=0)
|
||||
{
|
||||
if (stricmp(*arg,"-help")==0 || stricmp(*arg,"--help")==0)
|
||||
if (stricmp(*arg,"-help")==0)
|
||||
{
|
||||
showhelp();
|
||||
int as=showhelp(arg,cl);
|
||||
cl-=as;
|
||||
arg+=as;
|
||||
return true;
|
||||
}
|
||||
else if (stricmp(*arg,"-config")==0 || stricmp(*arg,"--config")==0)
|
||||
else if (stricmp(*arg,"-config")==0)
|
||||
{
|
||||
int as=setconfig(arg,cl);
|
||||
cl-=as;
|
||||
arg+=as;
|
||||
}
|
||||
#if defined(__APPLE__)
|
||||
else if (!strncmp(*arg, "-NSDocumentRevisions", 20))
|
||||
{
|
||||
arg++;
|
||||
cl--;
|
||||
}
|
||||
#endif
|
||||
else if ((*arg)[0] == '-')
|
||||
{
|
||||
WARN_LOG(COMMON, "Ignoring unknown command line option '%s'", *arg);
|
||||
}
|
||||
else
|
||||
{
|
||||
char* extension = strrchr(*arg, '.');
|
||||
|
||||
if (extension
|
||||
&& (stricmp(extension, ".cdi") == 0 || stricmp(extension, ".chd") == 0
|
||||
|| stricmp(extension, ".gdi") == 0 || stricmp(extension, ".cue") == 0))
|
||||
{
|
||||
INFO_LOG(COMMON, "Using '%s' as cd image", *arg);
|
||||
settings.content.path = *arg;
|
||||
}
|
||||
else if (extension && stricmp(extension, ".elf") == 0)
|
||||
{
|
||||
INFO_LOG(COMMON, "Using '%s' as reios elf file", *arg);
|
||||
cfgSetVirtual("config", "bios.UseReios", "yes");
|
||||
settings.content.path = *arg;
|
||||
}
|
||||
else
|
||||
{
|
||||
INFO_LOG(COMMON, "Using '%s' as rom", *arg);
|
||||
settings.content.path = *arg;
|
||||
}
|
||||
printf("wtf %s is suposed to do ?\n",*arg);
|
||||
}
|
||||
arg++;
|
||||
cl--;
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
return false;
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
#include "ini.h"
|
||||
#include "types.h"
|
||||
#include <sstream>
|
||||
#include "stdclass.h"
|
||||
|
||||
namespace emucfg {
|
||||
|
||||
/* ConfigEntry */
|
||||
|
||||
const std::string& ConfigEntry::get_string() const
|
||||
{
|
||||
return this->value;
|
||||
}
|
||||
|
||||
int ConfigEntry::get_int()
|
||||
{
|
||||
if (strstr(this->value.c_str(), "0x") != NULL)
|
||||
{
|
||||
return (int)strtoul(this->value.c_str(), NULL, 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
return atoi(this->value.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
int64_t ConfigEntry::get_int64()
|
||||
{
|
||||
int base = 10;
|
||||
if (strstr(this->value.c_str(), "0x") != nullptr)
|
||||
base = 16;
|
||||
return (int64_t)strtoll(this->value.c_str(), nullptr, base);
|
||||
}
|
||||
|
||||
bool ConfigEntry::get_bool()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* ConfigSection */
|
||||
|
||||
bool ConfigSection::has_entry(const std::string& name)
|
||||
{
|
||||
return (this->entries.count(name) == 1);
|
||||
}
|
||||
|
||||
ConfigEntry* ConfigSection::get_entry(const std::string& name)
|
||||
{
|
||||
if(this->has_entry(name))
|
||||
{
|
||||
return &this->entries[name];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ConfigSection::set(const std::string& name, const std::string& value)
|
||||
{
|
||||
ConfigEntry new_entry = { value };
|
||||
this->entries[name] = new_entry;
|
||||
}
|
||||
|
||||
void ConfigSection::delete_entry(const std::string& name)
|
||||
{
|
||||
this->entries.erase(name);
|
||||
}
|
||||
|
||||
/* ConfigFile */
|
||||
|
||||
ConfigSection* ConfigFile::add_section(const std::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(const std::string& name)
|
||||
{
|
||||
return (this->virtual_sections.count(name) == 1 || this->sections.count(name) == 1);
|
||||
}
|
||||
|
||||
bool ConfigFile::has_entry(const std::string& section_name, const std::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(const std::string& name, bool is_virtual)
|
||||
{
|
||||
if(is_virtual)
|
||||
{
|
||||
if (this->virtual_sections.count(name) == 1)
|
||||
{
|
||||
return &this->virtual_sections[name];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this->sections.count(name) == 1)
|
||||
{
|
||||
return &this->sections[name];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ConfigEntry* ConfigFile::get_entry(const std::string& section_name, const std::string& entry_name)
|
||||
{
|
||||
ConfigSection* section = this->get_section(section_name, true);
|
||||
if(section != NULL && section->has_entry(entry_name))
|
||||
{
|
||||
return section->get_entry(entry_name);
|
||||
}
|
||||
|
||||
section = this->get_section(section_name, false);
|
||||
if(section != NULL)
|
||||
{
|
||||
return section->get_entry(entry_name);
|
||||
}
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
std::string ConfigFile::get(const std::string& section_name, const std::string& entry_name, const std::string& default_value)
|
||||
{
|
||||
ConfigEntry* entry = this->get_entry(section_name, entry_name);
|
||||
if (entry == NULL)
|
||||
{
|
||||
return default_value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return entry->get_string();
|
||||
}
|
||||
}
|
||||
|
||||
int ConfigFile::get_int(const std::string& section_name, const std::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();
|
||||
}
|
||||
}
|
||||
|
||||
int64_t ConfigFile::get_int64(const std::string& section_name, const std::string& entry_name, int64_t default_value)
|
||||
{
|
||||
ConfigEntry* entry = this->get_entry(section_name, entry_name);
|
||||
if (entry == NULL)
|
||||
{
|
||||
return default_value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return entry->get_int64();
|
||||
}
|
||||
}
|
||||
|
||||
bool ConfigFile::get_bool(const std::string& section_name, const std::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(const std::string& section_name, const std::string& entry_name, const std::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(const std::string& section_name, const std::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_int64(const std::string& section_name, const std::string& entry_name, int64_t 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(const std::string& section_name, const std::string& entry_name, bool value, bool is_virtual)
|
||||
{
|
||||
std::string str_value = (value ? "yes" : "no");
|
||||
this->set(section_name, entry_name, str_value, is_virtual);
|
||||
}
|
||||
|
||||
void ConfigFile::parse(FILE* file)
|
||||
{
|
||||
if (file == nullptr)
|
||||
return;
|
||||
|
||||
char line[512];
|
||||
std::string section;
|
||||
int cline = 0;
|
||||
while (true)
|
||||
{
|
||||
if (std::fgets(line, sizeof(line), file) == nullptr)
|
||||
break;
|
||||
cline++;
|
||||
std::string s(line);
|
||||
s = trim_ws(s, " \r\n");
|
||||
if (s.empty())
|
||||
continue;
|
||||
if (s.length() >= 3 && s[0] == '[' && s[s.length() - 1] == ']')
|
||||
{
|
||||
section = s.substr(1, s.length() - 2);
|
||||
continue;
|
||||
}
|
||||
auto eqpos = s.find('=');
|
||||
if (eqpos == std::string::npos)
|
||||
{
|
||||
WARN_LOG(COMMON, "Malformed entry on config - ignoring line %d: %s", cline, s.c_str());
|
||||
continue;
|
||||
}
|
||||
std::string property = trim_ws(s.substr(0, eqpos));
|
||||
std::string value = trim_ws(s.substr(eqpos + 1));
|
||||
if (value.length() >= 2 && value[0] == '"' && value[value.length() - 1] == '"')
|
||||
value = value.substr(1, value.length() - 2);
|
||||
set(section, property, value);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigFile::save(FILE* file)
|
||||
{
|
||||
for (const auto& [section_name, section] : this->sections)
|
||||
{
|
||||
if (!section_name.empty())
|
||||
std::fprintf(file, "[%s]\n", section_name.c_str());
|
||||
|
||||
for (const auto& [entry_name, entry] : section.entries)
|
||||
{
|
||||
std::fprintf(file, "%s = %s\n", entry_name.c_str(), entry.get_string().c_str());
|
||||
}
|
||||
if (!section_name.empty())
|
||||
std::fputc('\n', file);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigFile::delete_section(const std::string& section_name) {
|
||||
sections.erase(section_name);
|
||||
virtual_sections.erase(section_name);
|
||||
}
|
||||
|
||||
void ConfigFile::delete_entry(const std::string& section_name, const std::string& entry_name)
|
||||
{
|
||||
ConfigSection *section = get_section(section_name, false);
|
||||
if (section != NULL)
|
||||
section->delete_entry(entry_name);
|
||||
}
|
||||
|
||||
bool ConfigFile::is_virtual(const std::string& section_name, const std::string& entry_name)
|
||||
{
|
||||
ConfigSection *section = get_section(section_name, true);
|
||||
if (section == nullptr)
|
||||
return false;
|
||||
return section->has_entry(entry_name);
|
||||
}
|
||||
|
||||
} // namespace emucfg
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
namespace emucfg {
|
||||
|
||||
struct ConfigEntry {
|
||||
std::string value;
|
||||
const std::string& get_string() const;
|
||||
int get_int();
|
||||
bool get_bool();
|
||||
int64_t get_int64();
|
||||
};
|
||||
|
||||
struct ConfigSection {
|
||||
std::map<std::string, ConfigEntry> entries;
|
||||
bool has_entry(const std::string& name);
|
||||
void set(const std::string& name, const std::string& value);
|
||||
void delete_entry(const std::string& name);
|
||||
ConfigEntry* get_entry(const std::string& name);
|
||||
};
|
||||
|
||||
struct ConfigFile {
|
||||
private:
|
||||
std::map<std::string, ConfigSection> sections;
|
||||
std::map<std::string, ConfigSection> virtual_sections;
|
||||
ConfigSection* add_section(const std::string& name, bool is_virtual);
|
||||
ConfigSection* get_section(const std::string& name, bool is_virtual);
|
||||
ConfigEntry* get_entry(const std::string& section_name, const std::string& entry_name);
|
||||
|
||||
|
||||
public:
|
||||
bool has_section(const std::string& name);
|
||||
bool has_entry(const std::string& section_name, const std::string& entry_name);
|
||||
bool is_virtual(const std::string& section_name, const std::string& entry_name);
|
||||
|
||||
void parse(FILE* file);
|
||||
void save(FILE* file);
|
||||
|
||||
/* getting values */
|
||||
std::string get(const std::string& section_name, const std::string& entry_name, const std::string& default_value = "");
|
||||
int get_int(const std::string& section_name, const std::string& entry_name, int default_value = 0);
|
||||
int64_t get_int64(const std::string& section_name, const std::string& entry_name, int64_t default_value = 0);
|
||||
bool get_bool(const std::string& section_name, const std::string& entry_name, bool default_value = false);
|
||||
/* setting values */
|
||||
void set(const std::string& section_name, const std::string& entry_name, const std::string& value, bool is_virtual = false);
|
||||
void set_int(const std::string& section_name, const std::string& entry_name, int value, bool is_virtual = false);
|
||||
void set_int64(const std::string& section_name, const std::string& entry_name, int64_t value, bool is_virtual = false);
|
||||
void set_bool(const std::string& section_name, const std::string& entry_name, bool value, bool is_virtual = false);
|
||||
|
||||
void delete_section(const std::string& section_name);
|
||||
void delete_entry(const std::string& section_name, const std::string& entry_name);
|
||||
};
|
||||
|
||||
} // namespace emucfg
|
||||
|
||||
@@ -1,202 +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/naomi_network.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
|
||||
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);
|
||||
|
||||
// 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.TextureUpscale", 1);
|
||||
Option<int> MaxFilteredTextureSize("rend.MaxFilteredTextureSize", 256);
|
||||
Option<float> ExtraDepthScale("rend.ExtraDepthScale", 1.f);
|
||||
Option<bool> CustomTextures("rend.CustomTextures");
|
||||
Option<bool> DumpTextures("rend.DumpTextures");
|
||||
Option<int> ScreenStretching("rend.ScreenStretching", 100);
|
||||
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> 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);
|
||||
Option<bool> NativeDepthInterpolation("rend.NativeDepthInterpolation", false);
|
||||
Option<bool> EmulateFramebuffer("rend.EmulateFramebuffer", false);
|
||||
Option<bool> FixUpscaleBleedingEdge("rend.FixUpscaleBleedingEdge", true);
|
||||
#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<bool, false> HideLegacyNaomiRoms("Dreamcast.HideLegacyNaomiRoms", true);
|
||||
Option<bool, false> UploadCrashLogs("UploadCrashLogs", true);
|
||||
|
||||
// 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");
|
||||
OptionString DNS("DNS", "46.101.91.123", "network");
|
||||
OptionString NetworkServer("server", "", "network");
|
||||
Option<int> LocalPort("LocalPort", NaomiNetwork::SERVER_PORT, "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");
|
||||
|
||||
#ifdef SUPPORT_DISPMANX
|
||||
Option<bool> DispmanxMaintainAspect("maintain_aspect", true, "dispmanx");
|
||||
#endif
|
||||
|
||||
#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");
|
||||
|
||||
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"),
|
||||
};
|
||||
Option<bool> PerGameVmu("PerGameVmu", false, "config");
|
||||
#ifdef _WIN32
|
||||
Option<bool, false> UseRawInput("RawInput", false, "input");
|
||||
#endif
|
||||
|
||||
#ifdef USE_LUA
|
||||
Option<std::string, false> LuaFileName("LuaFileName", "flycast.lua");
|
||||
#endif
|
||||
|
||||
} // namespace config
|
||||
@@ -1,552 +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 <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <type_traits>
|
||||
#include "cfg.h"
|
||||
#include "hw/maple/maple_cfg.h"
|
||||
#ifdef LIBRETRO
|
||||
#include <libretro.h>
|
||||
#endif
|
||||
|
||||
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 (!cfgHasSection(gameId))
|
||||
return;
|
||||
perGameConfig = true;
|
||||
}
|
||||
for (const auto& o : options)
|
||||
o->load();
|
||||
}
|
||||
|
||||
void save() {
|
||||
cfgSetAutoSave(false);
|
||||
for (const auto& o : options)
|
||||
o->save();
|
||||
cfgSetAutoSave(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())
|
||||
cfgDeleteSection(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 (cfgIsVirtual(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
|
||||
cfgDeleteEntry(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 cfgLoadBool(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 cfgLoadInt64(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)cfgLoadInt(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 cfgLoadStr(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
|
||||
{
|
||||
std::string strValue = cfgLoadStr(section, name, "");
|
||||
if (strValue.empty())
|
||||
return value;
|
||||
else
|
||||
return (float)atof(strValue.c_str());
|
||||
}
|
||||
|
||||
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 = cfgLoadStr(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
|
||||
{
|
||||
cfgSaveBool(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
|
||||
{
|
||||
cfgSaveInt64(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
|
||||
{
|
||||
cfgSaveInt(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
|
||||
{
|
||||
cfgSaveStr(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
|
||||
{
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "%f", value);
|
||||
cfgSaveStr(section, name, buf);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
cfgSaveStr(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 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;
|
||||
|
||||
// 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;
|
||||
extern Option<bool> DumpTextures;
|
||||
extern Option<int> ScreenStretching; // in percent. 150 means stretch from 4/3 to 6/3
|
||||
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> 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;
|
||||
#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<bool, false> HideLegacyNaomiRoms;
|
||||
extern Option<bool, false> UploadCrashLogs;
|
||||
|
||||
// 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 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;
|
||||
|
||||
#ifdef SUPPORT_DISPMANX
|
||||
extern Option<bool> DispmanxMaintainAspect;
|
||||
#endif
|
||||
|
||||
#ifdef USE_OMX
|
||||
extern Option<int> OmxAudioLatency;
|
||||
extern Option<bool> OmxAudioHdmi;
|
||||
#endif
|
||||
|
||||
// Maple
|
||||
|
||||
extern Option<int> MouseSensitivity;
|
||||
extern Option<int> VirtualGamepadVibration;
|
||||
extern std::array<Option<MapleDeviceType>, 4> MapleMainDevices;
|
||||
extern std::array<std::array<Option<MapleDeviceType>, 2>, 4> MapleExpansionDevices;
|
||||
extern Option<bool> PerGameVmu;
|
||||
#ifdef _WIN32
|
||||
extern Option<bool, false> UseRawInput;
|
||||
#else
|
||||
constexpr bool UseRawInput = false;
|
||||
#endif
|
||||
|
||||
#ifdef USE_LUA
|
||||
extern Option<std::string, false> LuaFileName;
|
||||
#endif
|
||||
|
||||
} // namespace config
|
||||
-943
@@ -1,943 +0,0 @@
|
||||
/*
|
||||
Created on: Sep 23, 2019
|
||||
|
||||
Credits for cheats: Esppiral, S4pph4rad, yzb37859365, Shenmue_Trilogy, Radaron, Virgin KLM, Joel, Zorlon,
|
||||
ELOTROLADO.NET, SEGARETRO.ORG, Sakuragi @ emutalk.net
|
||||
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/>.
|
||||
*/
|
||||
#include "cheats.h"
|
||||
#include "hw/sh4/sh4_mem.h"
|
||||
#include "reios/reios.h"
|
||||
#include "cfg/cfg.h"
|
||||
#include "cfg/ini.h"
|
||||
#include "cfg/option.h"
|
||||
#include "emulator.h"
|
||||
#include "oslib/storage.h"
|
||||
|
||||
const WidescreenCheat CheatManager::widescreen_cheats[] =
|
||||
{
|
||||
{ "T36803N", nullptr, { 0xBC2CC }, { 0xC00 } }, // 102 Dalmatians (USA)
|
||||
{ "T36813D 50", nullptr, { 0xBDE8C }, { 0xC00 } }, // 102 Dalmatians (PAL)
|
||||
{ "MK-51064", nullptr, { 0x39EFF4 }, { 0x43700000 } }, // 18 wheeler (USA)
|
||||
{ "MK-5106450", nullptr, { 0x39EFF4 }, { 0x43700000 } }, // 18 wheeler (PAL)
|
||||
{ "HDR-0080", nullptr, { 0x6625C0 }, { 0x43700000 } }, // 18 wheeler (JP)
|
||||
{ "T-9708N", nullptr, { 0x0AD5B0, 0x112E00 }, { 0x4A2671C4, 0x401CCCCD } }, // 4 Wheel Thunder (USA)
|
||||
{ "MK-5119050", nullptr, { 0x104A34 }, { 0x3F52CCCD } }, // 90 Minutes - Sega Championship Football (PAL)
|
||||
{ "T40209D 50", nullptr, { 0x2A8750 }, { 0x3F400000 } }, // AeroWings 2 (PAL)
|
||||
{ "T34101M", nullptr, { 0x24CF20, 0x24CEEC }, { 0x3FAAAAAB, 0x43F00000 } }, // Animastar (JP)
|
||||
{ "MK-51171", nullptr, { 0xCA20B8 }, { 0x43700000 } }, // Alien Front Online (USA)
|
||||
{ "T2101M", nullptr, { 0x13E8B4, 0x13E8E4 }, { 0x43F00000, 0x3F400000 } }, // Berserk (JP)
|
||||
{ "T13001D 18", nullptr, { 0x2DAC44 }, { 0x3F400000 } }, // Blue Stinger (En, De) (PAL)
|
||||
{ "xxxxxxxxxx", nullptr, { 0x2DAB84 }, { 0x3F400000 } }, // Blue Stinger (Fr) (PAL)
|
||||
{ "T13001D-05", nullptr, { 0x2D9C84 }, { 0x3F400000 } }, // Blue Stinger (Es, It?) (PAL)
|
||||
{ "T13001N", nullptr, { 0x2D9CA4 }, { 0x3F400000 } }, // Blue Stinger (USA)
|
||||
{ "HDR-0003", nullptr, { 0x2D6D80 }, { 0x3F400000 } }, // Blue Stinger (JP)
|
||||
{ "T42903M", nullptr, { 0x286E2C, 0x286E5C }, { 0x43F00000, 0x3F400000 } }, // Bomber Hehhe! (JP)
|
||||
{ "MK-51065", nullptr, { 0x387B84, 0x387BB4 }, { 0x43F00000, 0x3F400000 } }, // Bomberman Online (USA)
|
||||
{ "T6801M", nullptr, { 0x042F3C }, { 0x4384BC09 } }, // Buggy Heat (JP)
|
||||
{ "T46601D 05", nullptr, { 0xBB3D14 }, { 0x440A7C9A } }, // Cannon Spike (PAL)
|
||||
{ "T1215N", nullptr, { 0xBB3C74 }, { 0x440A7C9A } }, // Cannon Spike (USA)
|
||||
{ "HDR-0115", nullptr, { 0x10ACFC }, { 0x3F400000 } }, // Cardcaptor Sakura: Tomoyo no Video Daisakusen (JPN)
|
||||
{ "T44901D 50", nullptr, { 0xB0109C, 0xB010CC }, { 0x43F00000, 0x3F400000 } }, // Carrier (PAL)
|
||||
{ "T5701N", nullptr, { 0xAFD93C, 0xAFD96C }, { 0x43F00000, 0x3F400000 } }, // Carrier (USA)
|
||||
// Capcom vs. SNK Pro (JP)
|
||||
// You can see the end of the backgrounds on each side. The HUD is shifted to the right.
|
||||
// Code 2 sets HUD and movements of the characters to a 4:3 zone
|
||||
{ "T1247M", nullptr, { 0x1E8238, 0x3D3658, 0x3D3628 }, { 0x43F00000, 0x43D20000, 0x43700000 } },
|
||||
// Capcom vs. SNK 2 (JP)
|
||||
// You can see the end of the backgrounds on each side.
|
||||
{ "T1249M", nullptr, { 0x39BFD8, 0x34E9E8, 0x34EFBC }, { 0x43700000, 0x3F400000, 0x3F400000 } },
|
||||
{ "T44902D 50", nullptr, { 0x1DBFF8 }, { 0x43700000 } }, // Charge ‘n Blast (PAL)
|
||||
{ "T4402M", nullptr, { 0x1DA9E0 }, { 0x43700000 } }, // Charge ‘n Blast (JP)
|
||||
{ "MK-5104950", nullptr, { 0x2D2D40, 0x2D2D70 }, { 0x3F400000, 0xC2700000 } }, // ChuChu Rocket! (PAL)
|
||||
{ "T44903D 50", nullptr, { 0x315300, 0x315334 }, { 0x43F00000, 0x3FAAAAAB } }, // Coaster Works (PAL)
|
||||
// Confidential Mission (PAL) 022F0D58 43700000 - Only works on real Dreamcast
|
||||
{ "T36901M", nullptr, { 0x1C8A98 }, { 0x3F400000 } }, // Cool Boarders (JP)
|
||||
{ "T3106M", nullptr, { 0x60B59C }, { 0x3F400000 } }, // Cool Cool Toon (JP)
|
||||
{ "HDR-0176", nullptr, { 0x240FAC }, { 0x3F400000 } }, // Cosmic Smash (JP)
|
||||
{ "MK-51035", " U ", { 0x2B08B0 }, { 0x43700000 } }, // Crazy Taxi (USA)
|
||||
{ "MK-51035", " E ", { 0x2B3410 }, { 0x43700000 } }, // Crazy Taxi (PAL)
|
||||
{ "MK-5113650", nullptr, { 0x2BFB70 }, { 0x43700000 } }, // Crazy Taxi 2 (PAL)
|
||||
{ "MK-51136", nullptr, { 0x2BDDD0 }, { 0x43700000 } }, // Crazy Taxi 2 (USA)
|
||||
// { "HDR-0159", nullptr, { 0x2FBBD0 }, { 0x43700000 } }, // Crazy Taxi 2 (JP) not working
|
||||
{ "T13004N", nullptr, { 0x016D94 }, { 0x44234E73 } }, // Cyber Troopers - Virtual On - Oratorio Tangram (USA)
|
||||
// D2 (USA)
|
||||
{ "MK-51036", nullptr, { 0x4B5CF4, 0x4B5CC4, 0x3E92A0, 0x3E92A8, 0x3E92C0, 0x3E92C8 },
|
||||
{ 0x3F400000, 0x43F00000, 0, 0, 0, 0 } },
|
||||
// D2 (JP)
|
||||
{ "T30006M", nullptr, { 0x4CF42C, 0x4CF45C, 0x3E1A36, 0x3E1A34, 0x3E1A3C, 0x3E1A54, 0x3E1A5C },
|
||||
{ 0x43F00000, 0x3F400000, 0x08010000, 0, 0, 0, 0 } },
|
||||
{ "MK-5103750", nullptr, { 0x1FE270 }, { 0x43700000 } }, // Daytona USA (PAL)
|
||||
{ "MK-51037", nullptr, { 0x1FC6D0 }, { 0x43700000 } }, // Daytona USA (USA)
|
||||
{ "T9501N-50", nullptr, { 0x9821D4 }, { 0x3F400000 } }, // Deadly Skies (PAL)
|
||||
{ "T8116D 50", nullptr, { 0x2E5530 }, { 0x43700000 } }, // Dead or Alive 2 (PAL)
|
||||
{ "T3601N", nullptr, { 0x2F0670 }, { 0x43700000 } }, // Dead or Alive 2 (USA)
|
||||
{ "T3602M", nullptr, { 0x2FF798 }, { 0x43700000 } }, // Dead or Alive 2 (JP)
|
||||
{ "T3601M", nullptr, { 0x2FBBD0 }, { 0x43700000 } }, // Dead or Alive 2: Limited Edition (JP)
|
||||
{ "T2401N", nullptr, { 0x8BD5B4, 0x8BD5E4 }, { 0x43F00000, 0x3F400000 } }, // Death Crimson OX (USA)
|
||||
{ "T23201M", nullptr, { 0x819F44, 0x819F74 }, { 0x43F00000, 0x3F400000 } }, // Death Crimson 2 (JP)
|
||||
{ "T17714D50", nullptr, { 0x0D2ED0, 0x0D2ED4 }, { 0x3FAAAAAB, 0x3F400000 } }, // Donald Duck: Quack Attack (PAL) (Code 1 corrects the HUD)
|
||||
{ "T12503D-50", nullptr, { 0x49CB24 }, { 0x3F0CCCCD } }, // Dragons Blood (PAL)
|
||||
{ "T17716D 50", nullptr, { 0xF97F40, 0x08DCF8 }, { 0x00000168, 0 } }, // Dragonriders: Chronicles of Pern (PAL)
|
||||
{ "T17720N", nullptr, { 0xF97F40, 0x08DCF4 }, { 0x00000168, 0 } }, // Dragonriders: Chronicles of Pern (USA) (Code 1 removes black bars)
|
||||
{ "MK-5101350", nullptr, { 0x4FCBC0 }, { 0x43700000 } }, // Dynamite Cop (PAL)
|
||||
{ "MK-51013", nullptr, { 0x4FCBC0 }, { 0x43700000 } }, // Dynamite Cop (USA)
|
||||
{ "HDR-0020", nullptr, { 0x4FA848 }, { 0x43700000 } }, // Dynamite Deka 2 (JP)
|
||||
// Draconus: Cult of the Wyrm (USA)
|
||||
// Code 1-2 increases drawing distance
|
||||
{ "T40203N", nullptr, { 0x49D7F4, 0x49D8CC, 0x49D6F8 }, { 0x3F07C3BB, 0x447A0000, 0x3FAAAAAB } },
|
||||
// Ecco the Dolphin: Defender of the Future (PAL)
|
||||
{ "MK-5103350", nullptr, { 0x275418, 0x040E68, 0x040D1C }, { 0x49D9A5DA, 0x3F100000, 0x3F100000 } },
|
||||
{ "T17705D 05", nullptr, { 0x304870 }, { 0x3F400000 } }, // Evolution - The World of Sacred Device (PAL)
|
||||
{ "T45005D 50", nullptr, { 0x36CE5C, 0x36CE8C }, { 0x43F00000, 0x3F400000 } }, // Evolution 2 - Far Off Promise (PAL)
|
||||
{ "T1711N", nullptr, { 0x36C76C, 0x36C73C }, { 0x3F400000, 0x43F00000 } }, // Evolution 2 - Far Off Promise (USA)
|
||||
{ "T8118D 50", nullptr, { 0x2C6B7C }, { 0x00004000 } }, // Ferrari F355 Challenge (PAL) vga mode only
|
||||
{ "HDR-0100", nullptr, { 0x3235D4 }, { 0x00004000 } }, // Ferrari F355 Challenge (JP) vga mode only
|
||||
{ "MK-5115450", nullptr, { 0x3D3B10 }, { 0x43700000 } }, // Fighting Vipers 2 (PAL)
|
||||
{ "HDR-0133", nullptr, { 0x3D3AF0 }, { 0x43700000 } }, // Fighting Vipers 2 (JP)
|
||||
{ "T18805M", nullptr, { 0x1A39C0 }, { 0x3F400000 } }, // Fire Pro Wrestling D (JP)
|
||||
{ "MK-51114", nullptr, { 0x132DD8, 0xA26CA8, 0xA26738, 0xA275B8, 0xA26AD8, 0xA26908 },
|
||||
{ 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000 } }, // Floigan Bros. Ep. 1 (PAL)
|
||||
{ "T34201M", nullptr, { 0x586290, 0x586260 }, { 0x3F400000, 0x43F00000 } }, // Frame Gride (JP)
|
||||
{ "T-8113D-50", nullptr, { 0x55A354 }, { 0x3FAAAAAB } }, // Fur Fighters (PAL)
|
||||
{ "T-8107N", nullptr, { 0x55A374 }, { 0x3FAAAAAB } }, // Fur Fighters (USA)
|
||||
{ "T9707D 50", nullptr, { 0x2E7CD0, 0x2E7D04, 0x2E7D10, 0x2E7D0C },
|
||||
{ 0x43BEAE39, 0x3DCCCCCD , 0x44A00000, 0xC4200000 } }, // Gauntlet Legends (PAL)
|
||||
{ "HDR-0004", nullptr, { 0x364A64 }, { 0x43BA0000 } }, // Godzilla Generations (JP)
|
||||
{ "HDR-0047", nullptr, { 0x7C3F24, 0x7C3F54 }, { 0x43F00000, 0x3F400000 } }, // Godzilla Generations Maximum Impact (JP)
|
||||
{ "T41501M", nullptr, { 0x282604, 0x282634 }, { 0x43F00000, 0x3F400000 } }, // Golem no Maigo (aka The Lost Golem) (JP)
|
||||
{ "T1219M", nullptr, { 0xBC3C94 }, { 0x440A7C9A } }, // Gun Spike (JP)
|
||||
{ "T13301N", nullptr, { 0x88E780 }, { 0x3F400000 } }, // Gundam Side Story (USA)
|
||||
// { "T11001N", nullptr, { 0xC3D6BC, 0xD204A8, 0xD32FC8, 0xC7CF84, 0xD20548 },
|
||||
// { 0x00363031, 0x00000356, 0x00000280, 0x00000280, 0x00000280 } }, // Half-Life. Not working
|
||||
{ "MK-5104150", nullptr, { 0x23FCC4 }, { 0x44558000 } }, // Headhunter (PAL)
|
||||
// gun coords issue
|
||||
//{ "MK-5100250", nullptr, { 0x4C6708 }, { 0x43700000 } }, // House of the Dead 2, The (PAL)
|
||||
//{ "MK-51002", nullptr, { 0x4C6088 }, { 0x43700000 } }, // House of the Dead 2, The (USA)
|
||||
//not working { "T38706M", nullptr, { 0xC0CFA0 }, { 0x3F400000 } }, // Ikaruga (JP)
|
||||
{ "T46001N", nullptr, { 0x1C8A98 }, { 0x3F400000 } }, // Illbleed (USA)
|
||||
{ "T44904D 50", nullptr, { 0x18C15C, 0x18C18C }, { 0x43F00000, 0x3F400000 } }, // Iron Aces (PAL)
|
||||
{ "MK-51058", nullptr, { 0x32E0FC, 0x32E12C }, { 0x43F00000, 0x3F400000 } }, // Jet Grind Radio (USA)
|
||||
{ "MK-5105850", nullptr, { 0x32F9EC, 0x32F9BC }, { 0x3F400000, 0x43F00000 } }, // Jet Set Radio (PAL)
|
||||
{ "HDR-0078", nullptr, { 0x327A8C, 0x327A5C }, { 0x3F400000, 0x43F00000 } }, // Jet Set Radio (De La) (JP)
|
||||
{ "T22902D 50", nullptr, { 0x278508 }, { 0x43700000 } }, // Kao The Kangaroo (PAL)
|
||||
{ "T22903N", nullptr, { 0x2780A8 }, { 0x43700000 } }, // Kao The Kangaroo (USA)
|
||||
{ "T47803M", nullptr, { 0x0FDFAC }, { 0x3F400000 } }, // Karous (JP)
|
||||
// { "T41901N", nullptr, { 0x53F580, 0xEFB748, 0xEFB750 }, { 0xC4200000, 0x43A00000, 0x43200000 } }, // KISS Psycho Circus – The Nightmare Child (USA)
|
||||
{ "T2501M", nullptr, { 0x24A878, 0x24A8A8 }, { 0x43F00000, 0x3F400000 } }, // Langrisser Millenium (JP)
|
||||
{ "T15111D 50", nullptr, { 0x29B90C }, { 0x3F400000 } }, // Le Mans 24 Hours (PAL)
|
||||
{ "T15116N", nullptr, { 0x2198EC }, { 0x3F400000 } }, // Looney Tunes: Space Race (USA)
|
||||
{ "MK-5105050", nullptr, { 0x33818C }, { 0x3FA66666 } }, // Maken X (PAL)
|
||||
{ "MK-51050", nullptr, { 0x30CB4C }, { 0x3F400000 } }, // Maken X (USA)
|
||||
{ "T1212N", nullptr, { 0x2D6B18, 0x268390, 0x268ED8, 0x268934, 0x26947C, 0x269A20, 0x269FC4 },
|
||||
{ 0x43700000, 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000 } }, // Marvel vs. Capcom 2 (USA)
|
||||
{ "T1215M", nullptr, { 0x2FE2C0, 0x28FB38, 0x290680, 0x2900DC, 0x290C24, 0x2911C8, 0x29176C },
|
||||
{ 0x43700000, 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000, 0x3F400000 } }, // Marvel vs. Capcom 2 (JP)
|
||||
{ "T41402N", nullptr, { 0x551B80 }, { 0x3F400000 } }, // Max Steel - Covert Missions (USA)
|
||||
{ "MK-5102250", "V1.001", { 0x107FDC, 0x11253C }, { 0x3F99999A, 0x3F900000 } }, // Metropolis Street Racer (v1.001) (PAL)
|
||||
{ "MK-5102250", "V1.009", { 0x106B5C, 0x1111F4 }, { 0x3F99999A, 0x3F900000 } }, // Metropolis Street Racer (v1.009) (PAL)
|
||||
{ "MK-51012", nullptr, { 0x10A01C, 0x1146FC }, { 0x3F99999A, 0x3F900000 } }, // Metropolis Street Racer (USA)
|
||||
{ "T0000M", "MILLENNIUM RACER - Y2K FIGHTERS ",
|
||||
{ 0x1CAEAC, 0x1CAEDC }, { 0x43F00000, 0x3F400000 } }, // Millenium Racer Y2K Fighters
|
||||
{ "T1221M", nullptr, { 0x426B74 }, { 0x43F00000 } }, // Moero! Justice Gauken (JP)
|
||||
{ "T9701D", nullptr, { 0x31290C }, { 0x3F400000 } }, // Mortal Kombat Gold (PAL)
|
||||
{ "T-9701N", nullptr, { 0x337B8C }, { 0x3FA66666 } }, // Mortal Kombat Gold (USA)
|
||||
{ "T7020D", nullptr, { 0x1A5D40, 0x1A5D18, 0x1A5D38, 0x1A5D34, 0x1A5D30, 0x1A5D3C, 0x1A5D0C, 0x1A5D08, 0x1ACB78 },
|
||||
{ 0x3F59999A, 0x44084000, 0x3FB1EB85, 0x3FAAAAAB, 0x3F800000, 0x3FA66666, 0xC31B0000, 0x42700000, 0x44160000 } }, // Mr Driller (PAL)
|
||||
{ "T7604M", nullptr, { 0x43BDA4, 0x43BDD8, 0x4319E8, 0x431A08, 0x431A28, 0x431A48 },
|
||||
{ 0x43700000, 0x3F9AF5C2, 0x42200000, 0x42200000, 0x438C0000, 0x438C0000 } }, // Nanatsu no Hikan - Senritsu no Bishou (JP)
|
||||
{ "HDR-0079", nullptr, { 0x353744, 0x353774, 0x353778 },
|
||||
{ 0x43F00000, 0x3F400000, 0x3F800000 } }, // Napple Tale: Arsia in Daydream (JP)
|
||||
//crash { "MK-51178", nullptr, { 0x23AF00, 0x23B160, 0x144D40, 0x2105B4, 0x705B40 },
|
||||
// { 0xBFAAAAAB, 0xBFAAAAAB, 0xBFAAAAAB, 0xBFAAAAAB, 0x44558000 } }, // NBA 2K2
|
||||
{ "T9504M", nullptr, { 0xCDE848, 0xCDE844 }, { 0x3F400000, 0x3FA00000 } }, // Nightmare Creatures II (USA)
|
||||
{ "T-9502D-50", nullptr, { 0xBDE9B0, 0xBDE9C4 }, { 0x3F400000, 0x3FA00000 } }, // Nightmare Creatures II (PAL)
|
||||
{ "MK-5110250", nullptr, { 0x87B5A4 }, { 0x43700000 } }, // Outtrigger (PAL)
|
||||
{ "HDR-0118", nullptr, { 0x83E284 }, { 0x43700000 } }, // Outtrigger (JP)
|
||||
{ "T15103D 50", nullptr, { 0x1EEE78 }, { 0x3F400000 } }, // PenPen (PAL)
|
||||
{ "T17001M", nullptr, { 0x1C3828 }, { 0x3F400000 } }, // PenPen TriIcelon (JP)
|
||||
{ "MK-5110050", nullptr, { 0x548E04, 0x0923C0 }, { 0x43E80000, 0x3F966666 } }, // Phantasy Star Online (PAL) TODO
|
||||
{ "MK-51100", nullptr, { 0x548E84, 0x0925A4 }, { 0x43D20000, 0x3FA66666 } }, // Phantasy Star Online (USA) TODO
|
||||
{ "HDR-0129", nullptr, { 0x57E344, 0x091794 }, { 0x43D20000, 0x3FA66666 } }, // Phantasy Star Online (JP) TODO
|
||||
{ "MK-5119350", nullptr, { 0x57552C, 0x0A2DD4 }, { 0x43F00000, 0x3F911111 } }, // Phantasy Star Online Ver. 2 (PAL) TODO
|
||||
{ "MK-51193", nullptr, { 0x0A3138, 0x58C8CC }, { 0x3F911111, 0x43F00000 } }, // Phantasy Star Online Ver. 2 (USA) TODO
|
||||
{ "T1207N", nullptr, { 0x7D463C }, { 0x3F400000 } }, // Plasma Sword - Nightmare of Bilstein (USA)
|
||||
{ "T36801D 50", nullptr, { 0x7FD3C8 }, { 0x43700000 } }, // Power Stone (PAL)
|
||||
{ "T1201N", nullptr, { 0x7FCEE8 }, { 0x43700000 } }, // Power Stone (USA)
|
||||
{ "T36812D 50", nullptr, { 0x868BA8 }, { 0x43700000 } }, // Power Stone 2 (PAL)
|
||||
{ "T1211N", nullptr, { 0x8689A8 }, { 0x43700000 } }, // Power Stone 2 (USA)
|
||||
{ "T1218M", nullptr, { 0x849AA0 }, { 0x43700000 } }, // Power Stone 2 (JP)
|
||||
{ "T7022D 50", nullptr, { 0x33414C }, { 0x43F00000 } }, // Project Justice (PAL)
|
||||
{ "T1219N", nullptr, { 0x33374C }, { 0x43F00000 } }, // Project Justice (USA)
|
||||
{ "MK-51162", nullptr, { 0x204308 }, { 0x3FE38E39 } }, // Propeller Arena
|
||||
{ "T-8106D-50", nullptr, { 0x7DA39C }, { 0x3F400000 } }, // Psychic Force 2012 (PAL)
|
||||
{ "T9901M", nullptr, { 0x1AD848 }, { 0x3F400000 } }, // Rainbow Cotton (JP)
|
||||
{ "T9711D 50", nullptr, { 0x1574D8 }, { 0x43700000 } }, // Ready 2 Rumble Boxing Round 2 (PAL)
|
||||
{ "T7012D 05", nullptr, { 0x4FF93C, 0x4FF96C }, { 0x43F00000, 0x3F400000 } }, // Record of Lodoss War (En) (PAL)
|
||||
{ "xxxxxxxxxx", nullptr, { 0x4FF25C, 0x4FF28C }, { 0x43F00000, 0x3F400000 } }, // Record of Lodoss War (De) (PAL)
|
||||
{ "T7012D 09", nullptr, { 0x50499C, 0x5049CC }, { 0x43F00000, 0x3F400000 } }, // Record of Lodoss War (Fr) (PAL)
|
||||
{ "MK-5102151", nullptr, { 0x3511A0 }, { 0x3FC58577 } }, // Red Dog (PAL)
|
||||
{ "HDR-0074", nullptr, { 0x1FF60C, 0x1FF610, 0x1FF5DC }, { 0x3F400000, 0x3F800000, 0x43DC0000 } }, //Rent a Hero n°1
|
||||
// Resident Evil: Code Veronica (De) (PAL)
|
||||
// Code 1-4 removes the black bars on top and bottom in FMV
|
||||
{ "xxxxxxxxxx", nullptr, { 0x32A380, 0x383E18, 0x383E38, 0x383E58, 0x383E78 },
|
||||
{ 0x3F400000, 0x43F00000, 0, 0x43F00000, 0 } },
|
||||
// Resident Evil: Code Veronica (Fr) (PAL)
|
||||
// Code 1-4 removes the black bars on top and bottom in FMV
|
||||
{ "T36806D 09", nullptr, { 0x32A380, 0x383E18, 0x383E38, 0x383E58, 0x383E78 },
|
||||
{ 0x3F400000, 0x43F00000, 0, 0x43F00000, 0 } },
|
||||
// Resident Evil: Code Veronica (Sp) (PAL)
|
||||
{ "xxxxxxxxxx", nullptr, { 0x32A3A0 /*, 0x383E18, 0x383E38, 0x383E58, 0x383E78 */ },
|
||||
{ 0x3F400000, 0x43F00000, 0, 0x43F00000, 0 } },
|
||||
// Resident Evil: Code Veronica (USA)
|
||||
// Code 1-4 removes the black bars on top and bottom in FMV
|
||||
{ "T1204N", "RESIDENT EVIL CODE VERONICA ",
|
||||
{ 0x329E40, 0x3838D8, 0x3838F8, 0x383918, 0x383938 },
|
||||
{ 0x3F400000, 0x43F00000, 0, 0x43F00000, 0 } },
|
||||
// Biohazard: Code Veronica: Kanzenban (JP)
|
||||
{ "T1240M", "BIOHAZARD CODE VERONICA PLUS ",
|
||||
{ 0x344AE1 }, { 0x003F6000 } },
|
||||
// Resident Evil: Code Veronica X Kanzenban (Eng. translation)
|
||||
{ "T1204N", "RESIDENT EVIL CODE VERONICA X ",
|
||||
{ 0x344AE1 }, { 0x003F6000 } },
|
||||
{ "T8107D 50", nullptr, { 0x0464FC, 0x046210 }, { 0x3A888889, 0x44200000 } }, // Re-Volt (PAL) Code 1 is a render fix
|
||||
{ "MK-5119250", nullptr, { 0x0C5EB4 }, { 0x3A888889 } }, // Rez (PAL)
|
||||
{ "T15122N", nullptr, { 0x8E7A80, 0x8E7AB4 }, { 0x43E10000, 0x3FAAAAAB } }, // Ring, The - Terror's Realm (USA)
|
||||
{ "MK-51010", nullptr, { 0x1A4D2C, 0x1A4D5C }, { 0x43F00000, 0x3F400000 } }, // Rippin' Riders (USA)
|
||||
{ "HDR-0044", nullptr, { 0x298A94, 0x298AC4 }, { 0x43F00000, 0x3F400000 } }, // Roommania #203 (JP)
|
||||
{ "MK-5109250", nullptr, { 0x29E6C4 }, { 0x3F400000 } }, // Samba De Amigo (PAL)
|
||||
{ "T41301N", nullptr, { 0x77A178 }, { 0x3F400000 } }, // Seventh Cross Evolution (USA)
|
||||
{ "T8104D 58", nullptr, { 0x2C03F8 }, { 0x44558000 } }, // Shadow Man (PAL)
|
||||
{ "T-8106N", nullptr, { 0x2C03F4 }, { 0x3F400000 } }, // Shadow Man (USA)
|
||||
{ "MK-51048", nullptr, { 0x4AA4DC, 0x2B4E30 }, { 0x3F400000, 0x3F400000 } }, // Seaman (USA)
|
||||
{ "MK-5105350", nullptr, { 0x5D613C }, { 0x3F400000 } }, // Sega GT (PAL)
|
||||
{ "MK-51096", nullptr, { 0x495050 }, { 0x43700000 } }, // Sega Marine Fishing (USA)
|
||||
// { "MK-51019", nullptr, { 0xB83A48 }, { 0x3F400000 } }, // Sega Rally 2 (USA) not working?
|
||||
// { "HDR0010", nullptr, { 0xBD9BA0 }, { 0x3F400000 } }, // Sega Rally 2 (JP) not working?
|
||||
{ "HDR-0151", nullptr, { 0xAF57DC, 0xAF580C, 0x2122A0 },
|
||||
{ 0x43F00000, 0x3F400000, 0x3F400000 } }, // SGGG – Segagaga (JP)
|
||||
{ "MK-5105950", nullptr, { 0x231EF8, 0x1EF370 }, { 0x43800000, 0x7C1EF400 } }, // Shenmue (PAL) code 1 reduces clipping
|
||||
{ "MK-51059", nullptr, { 0x230250 }, { 0x43800000 }, { 0x43a00000 } }, // Shenmue (USA) Clipping
|
||||
{ "HDR-0016", nullptr, { 0x22E8A0, 0x1EBE70 }, { 0x43800000, 0x7C1EBF00 } }, // Shenmue (JP) code 1 reduces clipping
|
||||
{ "MK-5118450", nullptr, { 0x31186C }, { 0x43800000 } }, // Shenmue II (PAL) 01160FF4 0000E100 for black bars in cutscenes
|
||||
// Shenmue II (PAL) Alternative code without clipping or black bars. Might be demanding on real hardware.
|
||||
// 02311880 C3A00000, 0227E198 35AA359E, 01160FF4 0000E100
|
||||
{ "HDR-0164", nullptr, { 0x30D67C }, { 0x43700000 } }, // Shenmue II (JP) Clipping
|
||||
{ "T9505D", nullptr, { 0xD1FB14, 0x096A4C }, { 0x3F400000, 0x3FAAAAAB } }, // Silent Scope (PAL) Choose 60Hz in game options
|
||||
{ "MK-51052", " E ", { 0x502A84 }, { 0x3F400000 } }, // Skies of Arcadia (PAL)
|
||||
{ "MK-51052", " U ", { 0x599158 }, { 0x3F400000 } }, // Skies of Arcadia (USA)
|
||||
{ "T15104D 50", nullptr, { 0x17EF68 }, { 0x43F00010 } }, // Slave Zero (PAL) Widescreen, but a bit zoomed in
|
||||
{ "MK-5101050", nullptr, { 0x1A50EC, 0x1A511C }, { 0x43F00000, 0x3F400000 } }, // Snow Surfers (PAL)
|
||||
{ "MK-5100050", nullptr, { 0x88F528, 0x88F55C }, { 0x43F00000, 0x3FA66666 } }, // Sonic Adventure (PAL)
|
||||
{ "MK-51000", nullptr, { 0x88F5E8, 0x88F61C }, { 0x43F00000, 0x3FAAAAAB } }, // Sonic Adventure (USA)
|
||||
{ "MK-51117", nullptr, { 0x28DEF8, 0x28DF28 }, { 0x43F00000, 0x3f400000 } }, // Sonic Adventure 2 (USA)
|
||||
{ "HDR-0165", nullptr, { 0x28DF28, 0x28DEF8 }, { 0x3F400000, 0x43F00000 } }, // Sonic Adventure 2 (JP)
|
||||
{ "MK-51060", nullptr, { 0x112A2C }, { 0x3F400000 } }, // Sonic Shuffle (US)
|
||||
{ "MK-5106050", nullptr, { 0x110B4C }, { 0x3F400000 } }, // Sonic Shuffle (PAL)
|
||||
{ "T9103M", nullptr, { 0x25C714, 0x25C744 }, { 0x43F00000, 0x3F400000 } }, // Sorcerian - Shichisei Mahou no Shito
|
||||
{ "T1401D 50", nullptr, { 0x2D6138 }, { 0x3F400000 } }, // Soul Calibur (PAL)
|
||||
{ "T1401N", nullptr, { 0x266C28 }, { 0x3F400000 } }, // Soul Calibur (USA)
|
||||
{ "T36802N", " E ", { 0x129FA0, 0x12A9BC, 0x1C9FDC },
|
||||
{ 0x3EF55555, 0x3EF55555, 0x000000F0 } }, // Soul Reaver (PAL) Code 2 is a Render Fix
|
||||
{ "HDR-0190", nullptr, { 0x14D3E0 }, { 0x3F400000 } }, // Space Channel 5 Part 2 (JP)
|
||||
{ "T1216M", nullptr, { 0x017C38, 0x17F00 }, { 0x3A99999A, 0x3A99999A } }, // Spawn - In the Demon's Hand v1.003 (JP)
|
||||
{ "T1216N", nullptr, { 0x017C58, 0x17F20 }, { 0x3A99999A, 0x3A99999A } }, // Spawn - In the Demon's Hand v1.000 (US)
|
||||
{ "T36816D 50", nullptr, { 0x017C78, 0x17F40 }, { 0x3A99999A, 0x3A99999A } }, // Spawn - In the Demon's Hand v1.000 (EU)
|
||||
// Star Wars Episode I Racer (USA)
|
||||
// Code 1-4 removes the black bars on top and bottom in FMV
|
||||
{ "T23001N", nullptr, { 0x17AE20, 0x29A96C, 0x29A98C, 0x29A9AC, 0x29A9CC },
|
||||
{ 0x3F400000, 0x42900000, 0x42900000, 0x43CE0000, 0x43CE0000 } },
|
||||
{ "T40206N", nullptr, { 0x43296C }, { 0x3F400000 } }, // Super Magnetic Neo (US)
|
||||
{ "T40206D 50", nullptr, { 0x43E34C }, { 0x3F400000 } }, // Super Magnetic Neo (EU)
|
||||
// { "T7014D 50", nullptr, { 0xE2B234 }, { 0x3F800000 } }, // Super Runabout (PAL) doesn't work?
|
||||
{ "T17721D 50", nullptr, { 0x45CED4 }, { 0x3F400000 } }, // Surf Rocket Racers (PAL) alt: 021EBF40 3F400000
|
||||
{ "T17703D 50", nullptr, { 0xCD8950 }, { 0x3F111111 } }, // Suzuki Alstare Extreme Racing
|
||||
{ "T36807D 05", nullptr, { 0x140F74, 0x140FA4 }, { 0x43FA0000, 0x3F400000 } }, // Sword of Bersek (PAL)
|
||||
{ "T-36805N", nullptr, { 0x13F1C4, 0x13F194 }, { 0x3F400000, 0x43F00000 } }, // Sword of Bersek (USA)
|
||||
{ "MK-51186", nullptr, { 0x4A19B0 }, { 0x43700000 } }, // Tennis 2K2 (USA)
|
||||
{ "T15123N", nullptr, { 0x29B7BC }, { 0x3F400000 } }, // Test Drive Le Mans (USA) doesn't work?
|
||||
{ "T20801M", nullptr, { 0x1AAC80, 0x1AACB0 }, { 0x43F00000, 0x3F400000 } }, // Tetris 4D (JP)
|
||||
{ "MK-5101153", nullptr, { 0x14EFA8, 0x14EFD8 }, { 0x43F00000, 0x3F400000 } }, // Timestalkers (PAL)
|
||||
{ "T7009D50", nullptr, { 0x39173C }, { 0x3F400000 } }, // Tech Romancer (PAL)
|
||||
{ "T35402M", nullptr, { 0x315370, 0x3153A0 }, { 0x43F00000, 0x3F400000 } }, // Tokyo Bus Guide (JP) doesn't work?
|
||||
{ "T40201D 50", nullptr, { 0x1D9F10 }, { 0x3F400000 } }, // Tokyo Highway Challenge (PAL)
|
||||
{ "T40210D 50", nullptr, { 0x21E4F8 }, { 0x43700000 } }, // Tokyo Highway Challenge 2 (PAL)
|
||||
{ "xxxxxxxxxx", nullptr, { 0x21DEF8 }, { 0x3F400000 } }, // Tokyo Street Racer 2 (USA)
|
||||
// { "T36804D05", nullptr, { 0xB75E28 }, { 0x3EC00000 } }, // Tomb Raider: The Last Revelation (UK) (PAL) clipping, use hex patch instead
|
||||
// { "T40205N", nullptr, { 0x160D80, 0x160D7C }, { 0xA, 0xA } }, // Tony Hawk's Pro Skater (USA) -> missing character on selection screen
|
||||
{ "T13008D 05", nullptr, { 0x1D7C20 }, { 0x3FA66666 } }, // Tony Hawk's Pro Skater 2 (PAL)
|
||||
{ "T13006N", nullptr, { 0x1D77A0 }, { 0x3FA66666 } }, // Tony Hawk's Pro Skater 2 (USA)
|
||||
{ "MK-5102050", nullptr, { 0x0D592C }, { 0x3FD00000 } }, // Toy Commander (PAL)
|
||||
{ "xxxxxxxxxx", nullptr, { 0x469FCC, 0x469FFC }, { 0x44700000, 0x3F400000 } }, // Toyota Doricatch Series: Land Cruiser 100/Cygnus
|
||||
{ "MK-5109505", nullptr, { 0x1B2718 }, { 0x3F400000 } }, // UEFA Dream Soccer (PAL)
|
||||
{ "T40203D 50", nullptr, { 0x1D74E8, 0x1D7518 }, { 0x43F00000, 0x3F400000 } }, // Ultimate Fighting Championship (PAL)
|
||||
{ "T40204N", nullptr, { 0x1A9684 }, { 0x3F400000 } }, // Ultimate Fighting Championship (USA) problems with cam in game
|
||||
{ "T-8110D-50", nullptr, { 0x0C6B90, 0x0C6B94 }, { 0x43F00000, 0x43870000 } }, // Vanishing Point (PAL)
|
||||
// { "MK-5109450", nullptr, { 0x244134, 0x7A73B8, 0x7830D8, 0x7A74B8, 0x783138, 0x7A85A0, 0x6C7928, 0x6C7930, 0x6C7948, 0x6C7950 },
|
||||
//crash { 0x43F00000, 0x3F9C932E, 0x3F9C932E, 0x3F9C932E, 0x3F9C932E, 0x3F400000, 0, 0, 0, 0 } }, // Virtua Athlete 2K (PAL)
|
||||
{ "MK-51001", nullptr, { 0x19D518 }, { 0x43700000 } }, // Virtua Fighter 3 TB (NTSC)
|
||||
{ "MK-5100150", nullptr, { 0x19D718 }, { 0x43700000 } }, // Virtua Fighter 3 TB (PAL)
|
||||
{ "HDR-0002", nullptr, { 0x199FB0 }, { 0x43700000 } }, // Virtua Fighter 3 TB (JP)
|
||||
{ "MK-5105450", nullptr, { 0x456378 }, { 0x43700000 } }, // Virtua Tennis (v1.001) (PAL)
|
||||
{ "MK-51054", nullptr, { 0x450A90 }, { 0x43700000 } }, // Virtua Tennis (USA)
|
||||
{ "MK-5118650", nullptr, { 0x4A4A20 }, { 0x43700000 } }, // Virtua Tennis 2 (PAL)
|
||||
// { "T0000", nullptr, { 0x3A514C, 0x3A6170 }, { 0x3F400000, 0x00000356 } }, // Volgarr the Viking. Not working
|
||||
{ "xxxxxxxxxx", nullptr, { 0x20BB68, 0x1ACBD0, 0x1B9ADC }, // Code 1 reduces clipping. Code 2 fixes the clock.
|
||||
{ 0x43700000, 0x7C1ACC60, 0x3F400000 } }, // What's Shenmue (JP)
|
||||
{ "T40504D 50", nullptr, { 0x75281C }, { 0x3F400000 } }, // Wetrix+ (PAL) not working?
|
||||
{ "MK-51152", nullptr, { 0x014E90 }, { 0x43700000 } }, // World Series Baseball 2K2 (USA)
|
||||
{ "T20401M", nullptr, { 0x323CB0, 0x1ACBD0, 0x1B9ADC }, // Code 1 reduces clipping. Code 2 fixes the HUD.
|
||||
{ 0x43700000, 0x1ACC60, 0x3F400000 } }, // Zero Gunner 2 (JP)
|
||||
{ "MK-5103850", nullptr, { 0x9484E8 }, { 0x43700000 } }, // Zombie Revenge (PAL)
|
||||
{ "MK-51038", nullptr, { 0x948058 }, { 0x43700000 } }, // Zombie Revenge (USA)
|
||||
{ "HDR-0026", nullptr, { 0x948B18 }, { 0x43700000 } }, // Zombie Revenge (JP)
|
||||
{ "T43301M", nullptr, { 0x4B0218 }, { 0x3F400000 } }, // Zusar Vasar (JP)
|
||||
|
||||
{ nullptr },
|
||||
};
|
||||
const WidescreenCheat CheatManager::naomi_widescreen_cheats[] =
|
||||
{
|
||||
{ "KNIGHTS OF VALOUR THE 7 SPIRITS", nullptr, { 0x475B70, 0x475B40 }, { 0x3F400000, 0x43F00000 } },
|
||||
{ "Dolphin Blue", nullptr, { 0x3F2E2C, 0x3F2190, 0x3F2E6C, 0x3F215C },
|
||||
{ 0x43B90000, 0x3FAA9FBE, 0x43B90000, 0x43F00000 } },
|
||||
{ "METAL SLUG 6", nullptr, { 0xE93478, 0xE9347C }, { 0x3F400000, 0x3F8872B0 } },
|
||||
{ "TOY FIGHTER", nullptr, { 0x133E58 }, { 0x43700000 } },
|
||||
{ "LUPIN THE THIRD -THE SHOOTING-", nullptr, { 0x045490 }, { 0x3F400000 } },
|
||||
{ "VF4 FINAL TUNED JAPAN", nullptr, { 0x02B834, 0x0AFB90 }, { 0x3FE38E39, 0x3FE38E39 } },
|
||||
{ "DYNAMITE DEKA EX", nullptr, { 0x0E3598, 0x0C8E84 }, { 0x3FE38E38, 0x3FE38E38 } },
|
||||
{ "DEAD OR ALIVE 2", "doa2m", { 0x085B5C, 0x086AE8 }, { 0x3FE38E39, 0x3FE38E39 } },
|
||||
{ "SLASHOUT JAPAN VERSION", nullptr, { 0x3DDBE4 }, { 0x43CFDC86 } },
|
||||
{ "SPIKERS BATTLE JAPAN VERSION", nullptr, { 0x3626C4 }, { 0x43A551B0 } },
|
||||
{ " 18WHEELER", "18wheelr", { 0x5C64A8 }, { 0x43700000 } },
|
||||
{ "BEACH SPIKERS JAPAN", nullptr, { 0x065A7C }, { 0x44558000 } },
|
||||
{ "GUN SPIKE", nullptr, { 0x21A94 }, { 0x3FE38E39 } },
|
||||
{ "INITIAL D", "initd", { 0x155434 }, { 0x3FE38E39 } },
|
||||
{ "INITIAL D", "initdexp", { 0x159674 }, { 0x3FE38E39 } },
|
||||
{ "INITIAL D", "initdexpo", { 0x159634 }, { 0x3FE38E39 } },
|
||||
{ "INITIAL D", "initdo", { 0x14F5F4 }, { 0x3FE38E39 } },
|
||||
{ "INITIAL D Ver.2", "initdv2e", { 0x1B4F74 }, { 0x3FE38E39 } },
|
||||
{ "INITIAL D Ver.2", "initdv2j", { 0x1B4F34 }, { 0x3FE38E39 } },
|
||||
{ "INITIAL D Ver.2", "initdv2jo", { 0x1AD1F4 }, { 0x3FE38E39 } },
|
||||
{ "INITIAL D Ver.3", "initdv3e", { 0x1D0B34 }, { 0x3FE38E39 } },
|
||||
{ "INITIAL D Ver.3", "initdv3j", { 0x1D7C74 }, { 0x3FE38E39 } },
|
||||
{ "INITIAL D Ver.3", "initdv3jb", { 0x1D7774 }, { 0x3FE38E39 } },
|
||||
{ "AIRLINE PILOTS IN JAPAN", "alpilotj", { 0x1D62550 }, { 0x43700000 } },
|
||||
{ "MONKEY BALL JAPAN VERSION", nullptr, { 0x345B4, 0x45244, 0x454CC }, { 0x3FE38E39, 0x3FE38E39, 0x3FE38E39 } },
|
||||
{ "ZOMBIE REVENGE IN JAPAN", "zombrvn", { 0x7A4808 }, { 0x43700000 } },
|
||||
{ "ZOMBIE REVENGE IN JAPAN", "zombrvno", { 0x7A2E50 }, { 0x43700000 } },
|
||||
{ " BIOHAZARD GUN SURVIVOR2", "gunsur2", { 0x42EE80 }, { 0x3F400000 } },
|
||||
{ " BIOHAZARD GUN SURVIVOR2", "gunsur2j", { 0x42EBE0 }, { 0x3F400000 } },
|
||||
{ "SPAWN JAPAN", nullptr, { 0x02DEF8, 0x02E1C0 }, { 0x3A99999A, 0x3A99999A } },
|
||||
// gun coords problem with these 2 cheats
|
||||
// { " CONFIDENTIAL MISSION ---------", nullptr, { 0x24F798 }, { 0x43700000 } },
|
||||
// { "hotd2", nullptr, { 0x9C2AD8 }, { 0x43700000 } },
|
||||
{ "GUN SPIKE", nullptr, { 0xBACD7C }, { 0x440A7C9A } },
|
||||
{ " JAMBO SAFARI ------------", nullptr, { 0x2B1DE0 }, { 0x3FE38E39 } },
|
||||
{ "SOUL SURFER IN JAPAN", nullptr, { 0x8962C8 }, { 0x3FE38E39 } },
|
||||
|
||||
{ nullptr },
|
||||
};
|
||||
CheatManager cheatManager;
|
||||
|
||||
static void vblankCallback(Event event, void *param)
|
||||
{
|
||||
((CheatManager *)param)->apply();
|
||||
}
|
||||
|
||||
void CheatManager::setActive(bool active)
|
||||
{
|
||||
this->active = active;
|
||||
if (active || widescreen_cheat != nullptr)
|
||||
EventManager::listen(Event::VBlank, vblankCallback, this);
|
||||
else
|
||||
EventManager::unlisten(Event::VBlank, vblankCallback, this);
|
||||
}
|
||||
|
||||
void CheatManager::loadCheatFile(const std::string& filename)
|
||||
{
|
||||
#ifndef LIBRETRO
|
||||
FILE* cheatfile = hostfs::storage().openFile(filename, "r");
|
||||
if (cheatfile == nullptr)
|
||||
{
|
||||
WARN_LOG(COMMON, "Cannot open cheat file '%s'", filename.c_str());
|
||||
return;
|
||||
}
|
||||
emucfg::ConfigFile cfg;
|
||||
cfg.parse(cheatfile);
|
||||
fclose(cheatfile);
|
||||
|
||||
int count = cfg.get_int("", "cheats", 0);
|
||||
cheats.clear();
|
||||
for (int i = 0; count == 0 || i < count; i++)
|
||||
{
|
||||
std::string prefix = "cheat" + std::to_string(i) + "_";
|
||||
Cheat cheat{};
|
||||
cheat.description = cfg.get("", prefix + "desc", "Cheat " + std::to_string(i + 1));
|
||||
cheat.address = cfg.get_int("", prefix + "address", -1);
|
||||
if (count == 0 && cheat.address == (u32)-1)
|
||||
break;
|
||||
if (cheat.address >= RAM_SIZE)
|
||||
{
|
||||
WARN_LOG(COMMON, "Invalid address %x", cheat.address);
|
||||
continue;
|
||||
}
|
||||
cheat.type = (Cheat::Type)cfg.get_int("", prefix + "cheat_type", (int)Cheat::Type::disabled);
|
||||
cheat.size = 1 << cfg.get_int("", prefix + "memory_search_size", 0);
|
||||
cheat.value = cfg.get_int("", prefix + "value", cheat.value);
|
||||
cheat.repeatCount = cfg.get_int("", prefix + "repeat_count", cheat.repeatCount);
|
||||
cheat.repeatValueIncrement = cfg.get_int("", prefix + "repeat_add_to_value", cheat.repeatValueIncrement);
|
||||
cheat.repeatAddressIncrement = cfg.get_int("", prefix + "repeat_add_to_address", cheat.repeatAddressIncrement);
|
||||
cheat.enabled = cfg.get_bool("", prefix + "enable", false);
|
||||
cheat.destAddress = cfg.get_int("", prefix + "dest_address", 0);
|
||||
if (cheat.destAddress >= RAM_SIZE)
|
||||
{
|
||||
WARN_LOG(COMMON, "Invalid address %x", cheat.destAddress);
|
||||
continue;
|
||||
}
|
||||
cheat.valueMask = cfg.get_int("", prefix + "address_bit_position", 0);
|
||||
if (cheat.type != Cheat::Type::disabled)
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
setActive(!cheats.empty());
|
||||
INFO_LOG(COMMON, "%d cheats loaded", (int)cheats.size());
|
||||
cfgSaveStr("cheats", gameId, filename);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CheatManager::reset(const std::string& gameId)
|
||||
{
|
||||
widescreen_cheat = nullptr;
|
||||
if (this->gameId != gameId)
|
||||
{
|
||||
cheats.clear();
|
||||
setActive(false);
|
||||
this->gameId = gameId;
|
||||
#ifndef LIBRETRO
|
||||
std::string cheatFile = cfgLoadStr("cheats", gameId, "");
|
||||
if (!cheatFile.empty())
|
||||
loadCheatFile(cheatFile);
|
||||
#endif
|
||||
size_t cheatCount = cheats.size();
|
||||
if (gameId == "Fixed BOOT strapper") // Extreme Hunting 2
|
||||
{
|
||||
cheats.emplace_back(Cheat::Type::runNextIfEq, "skip netbd check ifeq", true, 32, 0x00067b04, 0, true);
|
||||
cheats.emplace_back(Cheat::Type::setValue, "skip netbd check", true, 32, 0x00067b04, 1, true); // 1 skips initNetwork()
|
||||
cheats.emplace_back(Cheat::Type::setValue, "skip netbd check 2", true, 16, 0x0009acc8, 0x0009, true); // not acceptable by main board
|
||||
// ac010000 should be d202 9302, but is changed to 78c0 8c93
|
||||
cheats.emplace_back(Cheat::Type::runNextIfEq, "fix boot ifeq", true, 32, 0x00010000, 0x8c9378c0, true);
|
||||
cheats.emplace_back(Cheat::Type::setValue, "fix boot", true, 32, 0x00010000, 0x9302d202, true);
|
||||
}
|
||||
else if (gameId == "THE KING OF ROUTE66") {
|
||||
cheats.emplace_back(Cheat::Type::setValue, "ignore drive error", true, 32, 0x00023ee0, 0x0009000B, true); // rts, nop
|
||||
}
|
||||
else if (gameId.substr(0, 8) == "MKG TKOB")
|
||||
{
|
||||
const auto& setMushikingCheats = [this](u32 addr) {
|
||||
cheats.emplace_back(Cheat::Type::setValue, "ignore rfid1 error", true, 32, addr, 0, true); // rfid[0].error = 0
|
||||
cheats.emplace_back(Cheat::Type::setValue, "ignore rfid2 error", true, 32, addr + 0x48, 0, true); // rfid[1].error = 0
|
||||
cheats.emplace_back(Cheat::Type::setValue, "ignore rfid1 status", true, 32, addr + 8, 0, true); // rfid[0].data18 = 0
|
||||
cheats.emplace_back(Cheat::Type::setValue, "ignore rfid2 status", true, 32, addr + 0x50, 0, true); // rfid[1].data18 = 0
|
||||
};
|
||||
if (gameId == "MKG TKOB 2 EXP VER1.001-") // mushi2eo
|
||||
setMushikingCheats(0x6fe1bc);
|
||||
else if (gameId == "MKG TKOB 2 JPN VER2.001-") // mushik2e
|
||||
setMushikingCheats(0x6ffe54);
|
||||
else if (gameId == "MKG TKOB 2K3 2ND VER1.003-")// mushike
|
||||
setMushikingCheats(0x4ad7ec);
|
||||
else if (gameId == "MKG TKOB 4 JPN VER2.000-") // mushik4e
|
||||
setMushikingCheats(0xb0e538);
|
||||
else if (gameId == "MKG TKOB 2K3 2ND VER1.002-")// mushikeo
|
||||
setMushikingCheats(0x4ad56c);
|
||||
else if (gameId == "MKG TKOB 2K3 2ND KOR VER1.000-") // mushikk
|
||||
setMushikingCheats(0x4ac9b8);
|
||||
else if (gameId == "MKG TKOB 2K3 2ND CHN VER1.000-") // mushikc
|
||||
setMushikingCheats(0x4aa9b8);
|
||||
else if (gameId == "MKG TKOB 2 KOR VER1.000-") // mushik2k
|
||||
setMushikingCheats(0x706084);
|
||||
}
|
||||
else if (gameId == "T-8120N") { // Dave Mirra BMX (US)
|
||||
cheats.emplace_back(Cheat::Type::setValue, "fix main loop time", true, 32, 0x0030b8cc, 0x42040000, true); // 33.0 ms
|
||||
}
|
||||
else if (gameId == "T8120D 50") { // Dave Mirra BMX (EU)
|
||||
cheats.emplace_back(Cheat::Type::setValue, "fix main loop time", true, 32, 0x003011cc, 0x42200000, true); // 40.0 ms
|
||||
}
|
||||
else if (gameId == "MK-0100") { // F355 US
|
||||
cheats.emplace_back(Cheat::Type::setValue, "increase datapump timeout", true, 16, 0x00131668, 1000, true);
|
||||
}
|
||||
else if (gameId == "T8118D 50") { // F355 EU
|
||||
cheats.emplace_back(Cheat::Type::setValue, "increase datapump timeout", true, 16, 0x00135588, 1000, true);
|
||||
}
|
||||
else if (gameId == "SAMURAI SPIRITS 6" || gameId == "T0002M") {
|
||||
cheats.emplace_back(Cheat::Type::setValue, "fix depth", true, 16, 0x0003e602, 0x0009, true); // nop (shift by 8 bits instead of 10)
|
||||
}
|
||||
if (cheats.size() > cheatCount)
|
||||
setActive(true);
|
||||
}
|
||||
if (config::WidescreenGameHacks)
|
||||
{
|
||||
if (settings.platform.isConsole())
|
||||
{
|
||||
for (int i = 0; widescreen_cheats[i].game_id != nullptr; i++)
|
||||
{
|
||||
if (!strcmp(gameId.c_str(), widescreen_cheats[i].game_id)
|
||||
&& (widescreen_cheats[i].area_or_version == nullptr
|
||||
|| !strncmp(ip_meta.area_symbols, widescreen_cheats[i].area_or_version, sizeof(ip_meta.area_symbols))
|
||||
|| !strncmp(ip_meta.product_version, widescreen_cheats[i].area_or_version, sizeof(ip_meta.product_version))
|
||||
|| !strncmp(ip_meta.software_name, widescreen_cheats[i].area_or_version, sizeof(ip_meta.software_name))))
|
||||
{
|
||||
widescreen_cheat = &widescreen_cheats[i];
|
||||
NOTICE_LOG(COMMON, "Applying widescreen hack to game %s", gameId.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string romName = get_file_basename(settings.content.fileName);
|
||||
|
||||
for (int i = 0; naomi_widescreen_cheats[i].game_id != nullptr; i++)
|
||||
{
|
||||
if (!strcmp(gameId.c_str(), naomi_widescreen_cheats[i].game_id)
|
||||
&& (naomi_widescreen_cheats[i].area_or_version == nullptr
|
||||
|| !strcmp(romName.c_str(), naomi_widescreen_cheats[i].area_or_version)))
|
||||
{
|
||||
widescreen_cheat = &naomi_widescreen_cheats[i];
|
||||
NOTICE_LOG(COMMON, "Applying widescreen hack to game %s", gameId.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (widescreen_cheat != nullptr)
|
||||
for (size_t i = 0; i < std::size(widescreen_cheat->addresses) && widescreen_cheat->addresses[i] != 0; i++)
|
||||
verify(widescreen_cheat->addresses[i] < RAM_SIZE);
|
||||
}
|
||||
setActive(active);
|
||||
}
|
||||
|
||||
u32 CheatManager::readRam(u32 addr, u32 bits)
|
||||
{
|
||||
switch (bits)
|
||||
{
|
||||
case 8:
|
||||
default:
|
||||
return ReadMem8_nommu(0x8C000000 + addr);
|
||||
case 16:
|
||||
return ReadMem16_nommu(0x8C000000 + addr);
|
||||
case 32:
|
||||
return ReadMem32_nommu(0x8C000000 + addr);
|
||||
}
|
||||
}
|
||||
|
||||
void CheatManager::writeRam(u32 addr, u32 value, u32 bits)
|
||||
{
|
||||
switch (bits)
|
||||
{
|
||||
case 8:
|
||||
default:
|
||||
WriteMem8_nommu(0x8C000000 + addr, (u8)value);
|
||||
break;
|
||||
case 16:
|
||||
WriteMem16_nommu(0x8C000000 + addr, (u16)value);
|
||||
break;
|
||||
case 32:
|
||||
WriteMem32_nommu(0x8C000000 + addr, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CheatManager::apply()
|
||||
{
|
||||
if (widescreen_cheat != nullptr)
|
||||
{
|
||||
for (size_t i = 0; i < std::size(widescreen_cheat->addresses) && widescreen_cheat->addresses[i] != 0; i++)
|
||||
{
|
||||
u32 address = widescreen_cheat->addresses[i];
|
||||
u32 curVal = readRam(address, 32);
|
||||
if (curVal != widescreen_cheat->values[i] && (widescreen_cheat->origValues[i] == 0 || widescreen_cheat->origValues[i] == curVal))
|
||||
writeRam(address, widescreen_cheat->values[i], 32);
|
||||
}
|
||||
}
|
||||
if (active)
|
||||
{
|
||||
bool skipCheat = false;
|
||||
for (const Cheat& cheat : cheats)
|
||||
{
|
||||
if (!cheat.builtIn && settings.network.online)
|
||||
continue;
|
||||
if (skipCheat) {
|
||||
skipCheat = false;
|
||||
continue;
|
||||
}
|
||||
if (!cheat.enabled)
|
||||
continue;
|
||||
|
||||
bool setValue = false;
|
||||
u32 valueToSet = 0;
|
||||
switch (cheat.type)
|
||||
{
|
||||
case Cheat::Type::disabled:
|
||||
default:
|
||||
break;
|
||||
case Cheat::Type::setValue:
|
||||
setValue = true;
|
||||
valueToSet = cheat.value;
|
||||
break;
|
||||
case Cheat::Type::increase:
|
||||
setValue = true;
|
||||
valueToSet = readRam(cheat.address, cheat.size) + cheat.value;
|
||||
break;
|
||||
case Cheat::Type::decrease:
|
||||
setValue = true;
|
||||
valueToSet = readRam(cheat.address, cheat.size) - cheat.value;
|
||||
break;
|
||||
case Cheat::Type::runNextIfEq:
|
||||
skipCheat = readRam(cheat.address, cheat.size) != cheat.value;
|
||||
break;
|
||||
case Cheat::Type::runNextIfNeq:
|
||||
skipCheat = readRam(cheat.address, cheat.size) == cheat.value;
|
||||
break;
|
||||
case Cheat::Type::runNextIfGt:
|
||||
skipCheat = readRam(cheat.address, cheat.size) <= cheat.value;
|
||||
break;
|
||||
case Cheat::Type::runNextIfLt:
|
||||
skipCheat = readRam(cheat.address, cheat.size) >= cheat.value;
|
||||
break;
|
||||
case Cheat::Type::copy:
|
||||
for (u32 i = 0; i < cheat.repeatCount; i++)
|
||||
writeRam(cheat.destAddress + i, readRam(cheat.address + i, cheat.size), cheat.size);
|
||||
break;
|
||||
}
|
||||
if (setValue)
|
||||
{
|
||||
u32 address = cheat.address;
|
||||
for (u32 repeat = 0; repeat < cheat.repeatCount; repeat++)
|
||||
{
|
||||
u32 curVal = readRam(address, cheat.size);
|
||||
if (cheat.size < 8)
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
int bitmask = 1 << i;
|
||||
if ((cheat.valueMask & bitmask) == 0)
|
||||
// keep current bit value
|
||||
valueToSet = (valueToSet & ~bitmask) | (curVal & bitmask);
|
||||
}
|
||||
}
|
||||
if (curVal != valueToSet)
|
||||
writeRam(address, valueToSet, cheat.size);
|
||||
address += cheat.repeatAddressIncrement * cheat.size / 8;
|
||||
valueToSet += cheat.repeatValueIncrement;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<u32> parseCodes(const std::string& s)
|
||||
{
|
||||
std::vector<u32> codes;
|
||||
std::string curCode;
|
||||
for (u8 c : s)
|
||||
{
|
||||
if (std::isxdigit(c))
|
||||
{
|
||||
curCode += c;
|
||||
if (curCode.length() == 8)
|
||||
{
|
||||
codes.push_back(strtoul(curCode.c_str(), nullptr, 16));
|
||||
curCode.clear();
|
||||
}
|
||||
}
|
||||
else if (!curCode.empty())
|
||||
throw FlycastException("Invalid cheat code");
|
||||
}
|
||||
if (!curCode.empty())
|
||||
{
|
||||
if (curCode.length() != 8)
|
||||
throw FlycastException("Invalid cheat code");
|
||||
codes.push_back(strtoul(curCode.c_str(), nullptr, 16));
|
||||
}
|
||||
|
||||
return codes;
|
||||
}
|
||||
|
||||
void CheatManager::addGameSharkCheat(const std::string& name, const std::string& s)
|
||||
{
|
||||
std::vector<u32> codes = parseCodes(s);
|
||||
Cheat conditionCheat;
|
||||
unsigned conditionLimit = 0;
|
||||
|
||||
const size_t prevSize = cheats.size();
|
||||
try {
|
||||
for (unsigned i = 0; i < codes.size(); i++)
|
||||
{
|
||||
if (i < conditionLimit)
|
||||
cheats.push_back(conditionCheat);
|
||||
Cheat cheat{};
|
||||
cheat.description = name;
|
||||
u32 code = (codes[i] & 0xff000000) >> 24;
|
||||
switch (code)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
{
|
||||
// 8/16/32-bit write
|
||||
if (i + 1 >= codes.size())
|
||||
throw FlycastException("Missing value");
|
||||
cheat.type = Cheat::Type::setValue;
|
||||
cheat.size = code == 0 ? 8 : code == 1 ? 16 : 32;
|
||||
cheat.address = codes[i] & 0x00ffffff;
|
||||
cheat.value = codes[++i];
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
u32 subcode = (codes[i] & 0x00ff0000) >> 16;
|
||||
switch (subcode)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
// Group write
|
||||
int count = codes[i] & 0xffff;
|
||||
if (i + count + 1 >= codes.size())
|
||||
throw FlycastException("Missing values");
|
||||
cheat.type = Cheat::Type::setValue;
|
||||
cheat.size = 32;
|
||||
cheat.address = codes[++i] & 0x00ffffff;
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
if (j == 1)
|
||||
cheat.description += " (cont'd)";
|
||||
cheat.value = codes[++i];
|
||||
cheats.push_back(cheat);
|
||||
cheat.address += 4;
|
||||
if (j < count - 1 && i < conditionLimit)
|
||||
cheats.push_back(conditionCheat);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
{
|
||||
// 8-bit inc/decrement
|
||||
if (i + 1 >= codes.size())
|
||||
throw FlycastException("Missing value");
|
||||
cheat.type = subcode == 1 ? Cheat::Type::increase : Cheat::Type::decrease;
|
||||
cheat.size = 8;
|
||||
cheat.value = codes[i] & 0xff;
|
||||
cheat.address = codes[++i] & 0x00ffffff;
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
{
|
||||
// 16-bit inc/decrement
|
||||
if (i + 1 >= codes.size())
|
||||
throw FlycastException("Missing value");
|
||||
cheat.type = subcode == 3 ? Cheat::Type::increase : Cheat::Type::decrease;
|
||||
cheat.size = 16;
|
||||
cheat.value = codes[i] & 0xffff;
|
||||
cheat.address = codes[++i] & 0x00ffffff;
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
case 6:
|
||||
{
|
||||
// 32-bit inc/decrement
|
||||
if (i + 2 >= codes.size())
|
||||
throw FlycastException("Missing address or value");
|
||||
cheat.type = subcode == 5 ? Cheat::Type::increase : Cheat::Type::decrease;
|
||||
cheat.size = 32;
|
||||
cheat.address = codes[++i] & 0x00ffffff;
|
||||
cheat.value = codes[++i];
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw FlycastException("Unsupported cheat type");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
{
|
||||
// 32-bit repeat write
|
||||
if (i + 2 >= codes.size())
|
||||
throw FlycastException("Missing count or value");
|
||||
cheat.type = Cheat::Type::setValue;
|
||||
cheat.size = 32;
|
||||
cheat.address = codes[i] & 0x00ffffff;
|
||||
cheat.repeatCount = codes[++i] >> 16;
|
||||
cheat.repeatAddressIncrement = codes[i] & 0xffff;
|
||||
cheat.value = codes[++i];
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
{
|
||||
// copy bytes
|
||||
if (i + 2 >= codes.size())
|
||||
throw FlycastException("Missing count or destination address");
|
||||
cheat.type = Cheat::Type::copy;
|
||||
cheat.size = 8;
|
||||
cheat.address = codes[i] & 0x00ffffff;
|
||||
cheat.destAddress = codes[++i] & 0x00ffffff;
|
||||
cheat.repeatCount = codes[++i];
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
break;
|
||||
// TODO 7 change decryption type
|
||||
// TODO 0xb delay applying codes
|
||||
// TODO 0xc global enable test
|
||||
case 0xd:
|
||||
{
|
||||
// enable next code if eq/neq/lt/gt
|
||||
if (i + 1 >= codes.size())
|
||||
throw FlycastException("Missing count or destination address");
|
||||
cheat.size = 16;
|
||||
cheat.address = codes[i] & 0x00ffffff;
|
||||
switch (codes[++i] >> 16)
|
||||
{
|
||||
case 0:
|
||||
cheat.type = Cheat::Type::runNextIfEq;
|
||||
break;
|
||||
case 1:
|
||||
cheat.type = Cheat::Type::runNextIfNeq;
|
||||
break;
|
||||
case 2:
|
||||
cheat.type = Cheat::Type::runNextIfLt;
|
||||
break;
|
||||
case 3:
|
||||
cheat.type = Cheat::Type::runNextIfGt;
|
||||
break;
|
||||
default:
|
||||
throw FlycastException("Unsupported conditional code");
|
||||
}
|
||||
cheat.value = codes[i] & 0xffff;
|
||||
cheats.push_back(cheat);
|
||||
}
|
||||
break;
|
||||
case 0xe:
|
||||
{
|
||||
// multiline enable codes if eq/neq/lt/gt
|
||||
if (i + 1 >= codes.size())
|
||||
throw FlycastException("Missing test address");
|
||||
cheat.size = 16;
|
||||
cheat.value = codes[i] & 0xffff;
|
||||
conditionLimit = i + 1 + ((codes[i] >> 16) & 0xff);
|
||||
switch (codes[++i] >> 24)
|
||||
{
|
||||
case 0:
|
||||
cheat.type = Cheat::Type::runNextIfEq;
|
||||
break;
|
||||
case 1:
|
||||
cheat.type = Cheat::Type::runNextIfNeq;
|
||||
break;
|
||||
case 2:
|
||||
cheat.type = Cheat::Type::runNextIfLt;
|
||||
break;
|
||||
case 3:
|
||||
cheat.type = Cheat::Type::runNextIfGt;
|
||||
break;
|
||||
default:
|
||||
throw FlycastException("Unsupported conditional code");
|
||||
}
|
||||
cheat.address = codes[i] & 0x00ffffff;
|
||||
conditionCheat = cheat;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw FlycastException("Unsupported cheat type");
|
||||
}
|
||||
}
|
||||
#ifndef LIBRETRO
|
||||
std::string path = cfgLoadStr("cheats", gameId, "");
|
||||
if (path == "")
|
||||
{
|
||||
path = get_game_save_prefix() + ".cht";
|
||||
cfgSaveStr("cheats", gameId, path);
|
||||
}
|
||||
saveCheatFile(path);
|
||||
#endif
|
||||
setActive(!cheats.empty());
|
||||
} catch (...) {
|
||||
cheats.erase(cheats.begin() + prevSize, cheats.end());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void CheatManager::saveCheatFile(const std::string& filename)
|
||||
{
|
||||
#ifndef LIBRETRO
|
||||
emucfg::ConfigFile cfg;
|
||||
|
||||
int i = 0;
|
||||
for (const Cheat& cheat : cheats)
|
||||
{
|
||||
if (cheat.builtIn)
|
||||
continue;
|
||||
std::string prefix = "cheat" + std::to_string(i) + "_";
|
||||
cfg.set_int("", prefix + "address", cheat.address);
|
||||
cfg.set_int("", prefix + "address_bit_position", cheat.valueMask);
|
||||
cfg.set_bool("", prefix + "big_endian", false);
|
||||
cfg.set_int("", prefix + "cheat_type", (int)cheat.type);
|
||||
cfg.set("", prefix + "code", "");
|
||||
cfg.set("", prefix + "desc", cheat.description);
|
||||
cfg.set_int("", prefix + "dest_address", cheat.destAddress);
|
||||
cfg.set_bool("", prefix + "enable", false); // force all cheats disabled at start
|
||||
cfg.set_int("", prefix + "handler", 1);
|
||||
int memSize;
|
||||
switch (cheat.size) {
|
||||
case 1:
|
||||
memSize = 0;
|
||||
break;
|
||||
case 2:
|
||||
memSize = 1;
|
||||
break;
|
||||
case 4:
|
||||
memSize = 2;
|
||||
break;
|
||||
case 8:
|
||||
memSize = 3;
|
||||
break;
|
||||
case 16:
|
||||
memSize = 4;
|
||||
break;
|
||||
case 32:
|
||||
default:
|
||||
memSize = 5;
|
||||
break;
|
||||
}
|
||||
cfg.set_int("", prefix + "memory_search_size", memSize);
|
||||
cfg.set_int("", prefix + "value", cheat.value);
|
||||
cfg.set_int("", prefix + "repeat_count", cheat.repeatCount);
|
||||
cfg.set_int("", prefix + "repeat_add_to_value", cheat.repeatValueIncrement);
|
||||
cfg.set_int("", prefix + "repeat_add_to_address", cheat.repeatAddressIncrement);
|
||||
i++;
|
||||
}
|
||||
cfg.set_int("", "cheats", i);
|
||||
FILE *fp = hostfs::storage().openFile(filename.c_str(), "w");
|
||||
if (fp == nullptr)
|
||||
throw FlycastException("Can't save cheat file");
|
||||
cfg.save(fp);
|
||||
fclose(fp);
|
||||
#endif
|
||||
}
|
||||
-101
@@ -1,101 +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:
|
||||
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
+58
@@ -0,0 +1,58 @@
|
||||
#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 := -g -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/ imgread/ linux/ ./ deps/zlib/ deps/chdr/ deps/crypto/ deps/libelf/ deps/chdpsr/ arm_emitter/ \
|
||||
deps/libzip/ deps/libpng/ rend/
|
||||
|
||||
ifndef NO_REC
|
||||
RZDCY_MODULES += hw/sh4/dyna/
|
||||
endif
|
||||
|
||||
ifndef NOT_ARM
|
||||
RZDCY_MODULES += rec-ARM/
|
||||
endif
|
||||
|
||||
ifndef NO_REND
|
||||
RZDCY_MODULES += rend/gles/
|
||||
else
|
||||
RZDCY_MODULES += rend/norend/
|
||||
endif
|
||||
|
||||
ifdef FOR_ANDROID
|
||||
RZDCY_MODULES += android/ deps/libandroid/
|
||||
endif
|
||||
|
||||
ifdef FOR_LINUX
|
||||
ifdef USE_SDL
|
||||
RZDCY_MODULES += sdl/
|
||||
else
|
||||
RZDCY_MODULES += linux-dist/
|
||||
endif
|
||||
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_CXXFLAGS := \
|
||||
$(CFLAGS) -c -g -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 -fno-exceptions -fno-rtti -std=gnu++11
|
||||
else
|
||||
RZDCY_CXXFLAGS := \
|
||||
$(CFLAGS) -c -g -O3 -I$(RZDCY_SRC_DIR) -I$(RZDCY_SRC_DIR)/deps \
|
||||
-D_ANDROID -DRELEASE -DTARGET_BEAGLE\
|
||||
-march=armv7-a -mtune=cortex-a9 -mfpu=vfpv3-d16 \
|
||||
-frename-registers -fsingle-precision-constant -ffast-math \
|
||||
-ftree-vectorize -fomit-frame-pointer -fno-exceptions -fno-rtti -std=gnu++11
|
||||
endif
|
||||
@@ -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/sh4_interpreter.h"
|
||||
#include "cfg/option.h"
|
||||
#include <array>
|
||||
#include <signal.h>
|
||||
#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] = sh4_sr_GetFull();
|
||||
else if (Sh4RegList[i] != NoReg)
|
||||
allregs[i] = *GetRegPtr(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(Sh4RegList[i]) = regs[i];
|
||||
}
|
||||
|
||||
u32 readReg(u32 regNum)
|
||||
{
|
||||
if (regNum >= Sh4RegList.size())
|
||||
return 0;
|
||||
Sh4RegType reg = Sh4RegList[regNum];
|
||||
if (reg == reg_sr_status)
|
||||
return sh4_sr_GetFull();
|
||||
if (reg != NoReg)
|
||||
return *GetRegPtr(reg);
|
||||
return 0;
|
||||
}
|
||||
void writeReg(u32 regNum, u32 value)
|
||||
{
|
||||
if (regNum >= Sh4RegList.size())
|
||||
return;
|
||||
Sh4RegType reg = Sh4RegList[regNum];
|
||||
if (reg == reg_sr_status)
|
||||
sh4_sr_SetFull(value);
|
||||
else if (reg != NoReg)
|
||||
*GetRegPtr(reg) = value;
|
||||
}
|
||||
|
||||
const u8 *readMem(u32 addr, u32 len)
|
||||
{
|
||||
static std::vector<u8> data;
|
||||
data.resize(len);
|
||||
u8 *p = &data[0];
|
||||
while (len > 0)
|
||||
{
|
||||
if (len >= 4 && (addr & 3) == 0)
|
||||
{
|
||||
*(u32 *)p = ReadMem32_nommu(addr);
|
||||
addr += 4;
|
||||
len -= 4;
|
||||
p += 4;
|
||||
}
|
||||
else if (len >= 2 && (addr & 1) == 0)
|
||||
{
|
||||
*(u16 *)p = ReadMem16_nommu(addr);
|
||||
addr += 2;
|
||||
len -= 2;
|
||||
p += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
*p++ = ReadMem8_nommu(addr);
|
||||
addr++;
|
||||
len--;
|
||||
}
|
||||
}
|
||||
return &data[0];
|
||||
}
|
||||
void writeMem(u32 addr, const std::vector<u8>& data)
|
||||
{
|
||||
const u8 *p = &data[0];
|
||||
u32 len = data.size();
|
||||
while (len > 0)
|
||||
{
|
||||
if (len >= 4 && (addr & 3) == 0)
|
||||
{
|
||||
WriteMem32_nommu(addr, *(u32 *)p);
|
||||
addr += 4;
|
||||
len -= 4;
|
||||
p += 4;
|
||||
}
|
||||
else if (len >= 2 && (addr & 3) == 0)
|
||||
{
|
||||
WriteMem16_nommu(addr, *(u16 *)p);
|
||||
addr += 2;
|
||||
len -= 2;
|
||||
p += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteMem8_nommu(addr, *p++);
|
||||
addr++;
|
||||
len--;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool insertMatchpoint(Breakpoint::Type type, u32 addr, u32 len)
|
||||
{
|
||||
if (type == Breakpoint::BP_TYPE_SOFTWARE_BREAK && len != 2) {
|
||||
WARN_LOG(COMMON, "insertMatchpoint: length != 2: %d", len);
|
||||
return false;
|
||||
}
|
||||
// TODO other matchpoint types
|
||||
if (breakpoints[type].find(addr) != breakpoints[type].end())
|
||||
return true;
|
||||
breakpoints[type][addr] = Breakpoint(type, addr);
|
||||
breakpoints[type][addr].savedOp = ReadMem16_nommu(addr);
|
||||
WriteMem16_nommu(addr, 0xC308); // trapa #0x20
|
||||
return true;
|
||||
}
|
||||
bool removeMatchpoint(Breakpoint::Type type, u32 addr, u32 len)
|
||||
{
|
||||
if (type == Breakpoint::BP_TYPE_SOFTWARE_BREAK && len != 2) {
|
||||
WARN_LOG(COMMON, "removeMatchpoint: length != 2: %d", len);
|
||||
return false;
|
||||
}
|
||||
auto it = breakpoints[type].find(addr);
|
||||
if (it == breakpoints[type].end())
|
||||
return false;
|
||||
WriteMem16_nommu(addr, it->second.savedOp);
|
||||
breakpoints[type].erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 interrupt()
|
||||
{
|
||||
config::DynarecEnabled = false;
|
||||
exception = SIGINT;
|
||||
emu.stop();
|
||||
return exception;
|
||||
}
|
||||
|
||||
// called on the emu thread
|
||||
void debugTrap(u32 event)
|
||||
{
|
||||
exception = findException(event);
|
||||
Sh4cntx.pc -= 2; // FIXME delay slot
|
||||
}
|
||||
|
||||
void postDebugTrap()
|
||||
{
|
||||
// needed to join the emu thread since debugTrap() is called by it
|
||||
emu.stop();
|
||||
}
|
||||
|
||||
u32 currentException()
|
||||
{
|
||||
return exception;
|
||||
}
|
||||
|
||||
void restart()
|
||||
{
|
||||
emu.unloadGame();
|
||||
emu.loadGame(settings.content.path.c_str());
|
||||
emu.start();
|
||||
}
|
||||
|
||||
void detach()
|
||||
{
|
||||
emu.start();
|
||||
}
|
||||
|
||||
void kill()
|
||||
{
|
||||
dc_exit();
|
||||
}
|
||||
|
||||
void resetAgent()
|
||||
{
|
||||
stack.clear();
|
||||
}
|
||||
|
||||
int findException(u32 event)
|
||||
{
|
||||
switch (event)
|
||||
{
|
||||
case 0x0A0: // Instruction/data TLB protection violation exception (read)
|
||||
case 0x0C0: // Data TLB protection violation exception (write)
|
||||
return SIGSEGV;
|
||||
case 0x0E0: // Instruction/data address error (read)
|
||||
case 0x100: // data address error (write)
|
||||
return SIGBUS;
|
||||
case 0x160: // trapa
|
||||
case 0x1E0: // User break before/after instruction execution
|
||||
return SIGTRAP;
|
||||
case 0x180: // General illegal instruction exception
|
||||
case 0x1A0: // Slot illegal instruction exception
|
||||
return SIGILL;
|
||||
case 0x120: // FPU exception
|
||||
case 0x800: // General FPU disable exception
|
||||
case 0x820: // Slot FPU disable exception
|
||||
return SIGFPE;
|
||||
default:
|
||||
return SIGABRT;
|
||||
}
|
||||
}
|
||||
|
||||
const u32 *getStack(u32& size)
|
||||
{
|
||||
verify(!Sh4cntx.CpuRunning);
|
||||
size = stack.size() * 8;
|
||||
if (!stack.empty())
|
||||
return (const u32 *)&stack[0];
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void subroutineCall()
|
||||
{
|
||||
subroutineReturn();
|
||||
stack.push_back(std::make_pair(Sh4cntx.pc, Sh4cntx.r[15]));
|
||||
}
|
||||
|
||||
void subroutineReturn()
|
||||
{
|
||||
while (!stack.empty() && Sh4cntx.r[15] >= stack.back().second)
|
||||
stack.pop_back();
|
||||
}
|
||||
|
||||
u32 exception = 0;
|
||||
|
||||
std::map<u32, Breakpoint> breakpoints[Breakpoint::Type::BP_TYPE_COUNT];
|
||||
std::vector<std::pair<u32, u32>> stack;
|
||||
};
|
||||
@@ -1,974 +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 "network/net_platform.h"
|
||||
#include "cfg/option.h"
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <cassert>
|
||||
|
||||
#define MAX_PACKET_LEN 4096
|
||||
|
||||
namespace debugger {
|
||||
|
||||
static void emuEventCallback(Event event, void *);
|
||||
|
||||
class GdbServer
|
||||
{
|
||||
public:
|
||||
struct Error : public std::runtime_error {
|
||||
Error(const char *reason) : std::runtime_error(reason) {}
|
||||
};
|
||||
|
||||
void init(int port)
|
||||
{
|
||||
if (VALID(serverSocket))
|
||||
return;
|
||||
|
||||
serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (!VALID(serverSocket))
|
||||
throw Error("gdb: Cannot create server socket");
|
||||
|
||||
int option = 1;
|
||||
setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&option, sizeof(option));
|
||||
|
||||
struct sockaddr_in serveraddr;
|
||||
memset(&serveraddr, 0, sizeof(serveraddr));
|
||||
serveraddr.sin_family = AF_INET;
|
||||
serveraddr.sin_port = htons(port);
|
||||
|
||||
if (::bind(serverSocket, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
|
||||
{
|
||||
closesocket(serverSocket);
|
||||
throw Error("gdb: bind() failed");
|
||||
}
|
||||
if (::listen(serverSocket, 5) < 0)
|
||||
{
|
||||
closesocket(serverSocket);
|
||||
throw Error("gdb: listen() failed");
|
||||
}
|
||||
EventManager::listen(Event::Resume, emuEventCallback);
|
||||
EventManager::listen(Event::Terminate, emuEventCallback);
|
||||
|
||||
initialised = true;
|
||||
}
|
||||
|
||||
void term()
|
||||
{
|
||||
if (!initialised)
|
||||
return;
|
||||
EventManager::unlisten(Event::Resume, emuEventCallback);
|
||||
EventManager::unlisten(Event::Terminate, emuEventCallback);
|
||||
stop();
|
||||
if (VALID(clientSocket))
|
||||
{
|
||||
closesocket(clientSocket);
|
||||
clientSocket = INVALID_SOCKET;
|
||||
}
|
||||
if (VALID(serverSocket))
|
||||
{
|
||||
closesocket(serverSocket);
|
||||
serverSocket = INVALID_SOCKET;
|
||||
}
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
if (!initialised || thread.joinable())
|
||||
return;
|
||||
DEBUG_LOG(COMMON, "GdbServer starting");
|
||||
thread = std::thread(&GdbServer::serverThread, this);
|
||||
if (config::GDBWaitForConnection)
|
||||
{
|
||||
DEBUG_LOG(COMMON, "Waiting for GDB connection...");
|
||||
agentInterrupt();
|
||||
}
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
if (!initialised)
|
||||
return;
|
||||
if (thread.joinable())
|
||||
{
|
||||
DEBUG_LOG(COMMON, "GdbServer stopping");
|
||||
agent.resetAgent();
|
||||
stopRequested = true;
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
bool isRunning() const {
|
||||
return initialised && thread.joinable();
|
||||
}
|
||||
|
||||
// called on the emu thread
|
||||
void debugTrap(u32 event)
|
||||
{
|
||||
if (!attached)
|
||||
return;
|
||||
agent.debugTrap(event);
|
||||
reportException();
|
||||
postDebugTrapNeeded = true;
|
||||
throw Stop();
|
||||
}
|
||||
|
||||
private:
|
||||
const u32 EXCEPT_NONE = 1;
|
||||
|
||||
void serverThread()
|
||||
{
|
||||
while (!stopRequested)
|
||||
{
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
sock_t max_fd = serverSocket;
|
||||
FD_SET(serverSocket, &fds);
|
||||
if (VALID(clientSocket))
|
||||
{
|
||||
max_fd = std::max(max_fd, clientSocket);
|
||||
FD_SET(clientSocket, &fds);
|
||||
}
|
||||
timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 100 * 1000;
|
||||
if (::select(max_fd + 1, &fds, nullptr, nullptr, &tv) > 0)
|
||||
{
|
||||
if (FD_ISSET(serverSocket, &fds))
|
||||
{
|
||||
try {
|
||||
acceptClientConnection();
|
||||
} catch (const Error& e) {
|
||||
ERROR_LOG(COMMON, "%s", e.what());
|
||||
closesocket(serverSocket);
|
||||
serverSocket = INVALID_SOCKET;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (FD_ISSET(clientSocket, &fds))
|
||||
{
|
||||
readCommand();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (VALID(clientSocket))
|
||||
{
|
||||
closesocket(clientSocket);
|
||||
clientSocket = INVALID_SOCKET;
|
||||
}
|
||||
attached = false;
|
||||
stopRequested = false;
|
||||
}
|
||||
|
||||
void acceptClientConnection()
|
||||
{
|
||||
if (VALID(clientSocket))
|
||||
closesocket(clientSocket);
|
||||
sockaddr_in src_addr{};
|
||||
socklen_t addr_len = sizeof(src_addr);
|
||||
clientSocket = ::accept(serverSocket, (sockaddr *)&src_addr, &addr_len);
|
||||
if (!VALID(clientSocket))
|
||||
{
|
||||
if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
|
||||
throw Error("accept failed");
|
||||
}
|
||||
else
|
||||
{
|
||||
NOTICE_LOG(NETWORK, "gdb: client connection");
|
||||
attached = true;
|
||||
agentInterrupt();
|
||||
}
|
||||
}
|
||||
|
||||
void readCommand()
|
||||
{
|
||||
try {
|
||||
if (postDebugTrapNeeded)
|
||||
{
|
||||
postDebugTrapNeeded = false;
|
||||
try {
|
||||
agent.postDebugTrap();
|
||||
} catch (const FlycastException& e) {
|
||||
throw Error(e.what());
|
||||
}
|
||||
}
|
||||
std::string packet = recvPacket();
|
||||
if (packet.empty())
|
||||
return;
|
||||
|
||||
DEBUG_LOG(NETWORK, "gdb: recv %s", packet.c_str());
|
||||
switch (packet[0])
|
||||
{
|
||||
case '!': // Enable extended mode
|
||||
sendPacket("OK");
|
||||
break;
|
||||
case '?': // Sent when connection is first established to query the reason the target halted
|
||||
reportException();
|
||||
break;
|
||||
case 'A': // Initialized argv[] array passed into program. not supported
|
||||
sendPacket("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
|
||||
sendContinue(packet);
|
||||
break;
|
||||
case 'C': // Continue with signal sig
|
||||
sendContinue(packet);
|
||||
break;
|
||||
case 'd': // Toggle debug flag. deprecated
|
||||
break;
|
||||
case 'D': // Detach GDB from the remote system
|
||||
sendPacket("OK");
|
||||
agent.detach();
|
||||
break;
|
||||
case 'F': // File-I/O protocol extension not currently supported
|
||||
break;;
|
||||
case 'g': // Read general registers
|
||||
readAllRegs();
|
||||
break;
|
||||
case 'G': // Write general registers
|
||||
writeAllRegs(packet);
|
||||
break;
|
||||
case 'H': // Set thread for subsequent operations
|
||||
sendPacket("OK");
|
||||
break;
|
||||
case 'i': // Step the remote target by a single clock cycle
|
||||
case 'I': // Signal, then cycle step
|
||||
// not supported
|
||||
sendPacket("");
|
||||
break;
|
||||
case 'k': // Kill request. Stop process/system
|
||||
agent.kill();
|
||||
break;
|
||||
case 'm': // Read length addressable memory units
|
||||
readMem(packet);
|
||||
break;
|
||||
case 'M': // Write length addressable memory units
|
||||
writeMem(packet);
|
||||
break;
|
||||
case 'p': // Read the value of register
|
||||
readReg(packet);
|
||||
break;
|
||||
case 'P': // Write register
|
||||
writeReg(packet);
|
||||
break;
|
||||
case 'q': // General query packets
|
||||
query(packet);
|
||||
break;
|
||||
case 'Q': // General set packets
|
||||
set(packet);
|
||||
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
|
||||
step(EXCEPT_NONE);
|
||||
break;
|
||||
case 'S': // Step with signal
|
||||
step();
|
||||
break;
|
||||
case 't': // Search backwards. unsupported
|
||||
break;
|
||||
case 'T': // Find out if the thread is alive
|
||||
sendPacket("OK");
|
||||
break;
|
||||
case 'v': // 'v' packets to control execution
|
||||
vpacket(packet);
|
||||
break;
|
||||
case 'X': // Write binary data to memory
|
||||
writeMemBin(packet);
|
||||
break;
|
||||
case 'z': // Remove a breakpoint/watchpoint.
|
||||
removeMatchpoint(packet);
|
||||
break;
|
||||
case 'Z': // Insert a breakpoint/watchpoint.
|
||||
insertMatchpoint(packet);
|
||||
break;
|
||||
case 3:
|
||||
interrupt();
|
||||
break;
|
||||
default:
|
||||
// Unknown commands are ignored
|
||||
WARN_LOG(COMMON, "Unknown gdb command: %s", packet.c_str());
|
||||
break;;
|
||||
}
|
||||
} catch (const Error& e) {
|
||||
ERROR_LOG(COMMON, "%s", e.what());
|
||||
closesocket(clientSocket);
|
||||
clientSocket = INVALID_SOCKET;
|
||||
attached = false;
|
||||
}
|
||||
}
|
||||
|
||||
void reportException()
|
||||
{
|
||||
char s[4];
|
||||
sprintf(s, "S%02X", agent.currentException());
|
||||
sendPacket(s);
|
||||
}
|
||||
|
||||
void sendContinue(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);
|
||||
}
|
||||
}
|
||||
|
||||
void readAllRegs()
|
||||
{
|
||||
u32 *regs;
|
||||
int c = agent.readAllRegs(®s);
|
||||
std::string outpkt;
|
||||
for (int i = 0; i < c; i++)
|
||||
outpkt += pack(regs[i]);
|
||||
sendPacket(outpkt);
|
||||
}
|
||||
|
||||
void 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);
|
||||
sendPacket("OK");
|
||||
}
|
||||
|
||||
void 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());
|
||||
sendPacket("E01");
|
||||
return;
|
||||
}
|
||||
const u8 *mem = agent.readMem(addr, len);
|
||||
std::string outpkt;
|
||||
for (u32 i = 0; i < len; i++)
|
||||
{
|
||||
char s[3];
|
||||
sprintf(s,"%02x", mem[i]);
|
||||
outpkt += s;
|
||||
}
|
||||
sendPacket(outpkt);
|
||||
}
|
||||
|
||||
void 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());
|
||||
sendPacket("E01");
|
||||
return;
|
||||
}
|
||||
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);
|
||||
sendPacket("OK");
|
||||
}
|
||||
|
||||
void 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());
|
||||
sendPacket("E01");
|
||||
return;
|
||||
}
|
||||
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);
|
||||
sendPacket("OK");
|
||||
}
|
||||
|
||||
void readReg(const std::string& pkt)
|
||||
{
|
||||
u32 regNum;
|
||||
if (sscanf(pkt.c_str(), "p%x", ®Num) != 1)
|
||||
{
|
||||
WARN_LOG(COMMON, "readReg: invalid packet %s", pkt.c_str());
|
||||
sendPacket("E01");
|
||||
return;
|
||||
}
|
||||
u32 v = agent.readReg(regNum);
|
||||
sendPacket(pack(v));
|
||||
}
|
||||
|
||||
void writeReg(const std::string& pkt)
|
||||
{
|
||||
u32 regNum;
|
||||
char vstr[9];
|
||||
if (sscanf(pkt.c_str(), "P%x=%8s", ®Num, vstr) != 2)
|
||||
{
|
||||
WARN_LOG(COMMON, "writeReg: invalid packet %s", pkt.c_str());
|
||||
sendPacket("E01");
|
||||
return;
|
||||
}
|
||||
agent.writeReg(regNum, unpack(vstr, 8));
|
||||
sendPacket("OK");
|
||||
}
|
||||
|
||||
void query(const std::string& pkt)
|
||||
{
|
||||
if (pkt == "qC")
|
||||
// Return the current thread ID. 0 is "any thread"
|
||||
sendPacket("QC0.01");
|
||||
else if (pkt.rfind("qCRC", 0) == 0)
|
||||
{
|
||||
WARN_LOG(COMMON, "CRC compute not supported %s", pkt.c_str());
|
||||
sendPacket("E01");
|
||||
}
|
||||
else if (pkt == "qfThreadInfo")
|
||||
// Obtain a list of all active thread IDs (first call)
|
||||
sendPacket("m0");
|
||||
else if (pkt == "qsThreadInfo")
|
||||
// Obtain a list of all active thread IDs (subsequent calls -> 'l' == end of list)
|
||||
sendPacket("l");
|
||||
else if (pkt.rfind("qGetTLSAddr:", 0) == 0)
|
||||
// Fetch the address associated with thread local storage
|
||||
sendPacket("");
|
||||
else if (pkt.rfind("qL", 0) == 0)
|
||||
// Obtain thread information. deprecated
|
||||
sendPacket("qM001");
|
||||
else if (pkt == "qOffsets")
|
||||
// Get section offsets. Not supported
|
||||
sendPacket("");
|
||||
else if (pkt.rfind("qP", 0) == 0)
|
||||
// Returns information on thread. deprecated
|
||||
sendPacket("");
|
||||
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];
|
||||
sprintf(n, "%08x ", *data++);
|
||||
for (char *p = n; *p != 0; p++)
|
||||
{
|
||||
*r++ = packnb((*p >> 4) & 0xf);
|
||||
*r++ = packnb(*p & 0xf);
|
||||
}
|
||||
}
|
||||
*r = 0;
|
||||
sendPacket(reply);
|
||||
}
|
||||
else
|
||||
sendPacket("");
|
||||
}
|
||||
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);
|
||||
sendPacket(qsupported);
|
||||
}
|
||||
else if (pkt.rfind("qSymbol:", 0) == 0)
|
||||
// Notify the target that GDB is prepared to serve symbol lookup requests
|
||||
sendPacket("OK");
|
||||
else if (pkt.rfind("qThreadExtraInfo,", 0) == 0)
|
||||
{
|
||||
// Obtain from the target OS a printable string description of thread attributes
|
||||
char s[19];
|
||||
sprintf(s, "%02x%02x%02x%02x%02x%02x%02x%02x%02x", 'R', 'u', 'n', 'n', 'a', 'b', 'l', 'e', 0);
|
||||
sendPacket(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
|
||||
sendPacket("");
|
||||
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
|
||||
sendPacket("1"); // existing process
|
||||
else if (pkt.rfind("qTfV", 0) == 0)
|
||||
// request data about trace state variables
|
||||
sendPacket("");
|
||||
else if (pkt.rfind("qTfP", 0) == 0)
|
||||
// request data about tracepoints
|
||||
sendPacket("");
|
||||
else if (pkt.rfind("qTStatus", 0) == 0)
|
||||
// Ask the stub if there is a trace experiment running right now
|
||||
sendPacket("");
|
||||
else
|
||||
WARN_LOG(COMMON, "query not supported %s", pkt.c_str());
|
||||
}
|
||||
|
||||
void set(const std::string& pkt)
|
||||
{
|
||||
if (pkt.rfind("QPassSignals:", 0) == 0)
|
||||
// Passing signals not supported
|
||||
sendPacket("");
|
||||
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
|
||||
sendPacket("");
|
||||
else
|
||||
WARN_LOG(COMMON, "set not supported %s", pkt.c_str());
|
||||
}
|
||||
|
||||
void vpacket(const std::string& pkt)
|
||||
{
|
||||
if (pkt.rfind("vAttach;", 0) == 0)
|
||||
sendPacket("S05");
|
||||
else if (pkt.rfind("vCont?", 0) == 0)
|
||||
// supported vCont actions - (c)ontinue, (C)ontinue with signal, (s)tep, (S)tep with signal, (r)ange-step
|
||||
sendPacket("vCont;c;C;s;S;t;r");
|
||||
else if (pkt.rfind("vCont", 0) == 0)
|
||||
{
|
||||
std::string vContCmd = pkt.substr(strlen("vCont;"));
|
||||
switch (vContCmd[0])
|
||||
{
|
||||
case 'c':
|
||||
case 'C':
|
||||
sendContinue(vContCmd);
|
||||
break;
|
||||
case 's':
|
||||
step(EXCEPT_NONE);
|
||||
break;
|
||||
case 'S':
|
||||
step();
|
||||
case 'r':
|
||||
{
|
||||
u32 from, to;
|
||||
if (sscanf(vContCmd.c_str(), "r%x,%x", &from, &to) == 2)
|
||||
{
|
||||
stepRange(from, to);
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(COMMON, "Unsupported vCont:r format %s", pkt.c_str());
|
||||
sendContinue("c");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
WARN_LOG(COMMON, "vCont action not supported %s", pkt.c_str());
|
||||
}
|
||||
}
|
||||
else if (pkt.rfind("vFile:", 0) == 0)
|
||||
// not supported
|
||||
sendPacket("");
|
||||
else if (pkt.rfind("vFlashErase:", 0) == 0)
|
||||
// not supported
|
||||
sendPacket("E01");
|
||||
else if (pkt.rfind("vFlashWrite:", 0) == 0)
|
||||
// not supported
|
||||
sendPacket("E01");
|
||||
else if (pkt.rfind("vFlashDone:", 0) == 0)
|
||||
// not supported
|
||||
sendPacket("E01");
|
||||
else if (pkt.rfind("vRun;", 0) == 0)
|
||||
{
|
||||
if (pkt != "vRun;")
|
||||
WARN_LOG(COMMON, "unexpected vRun args ignored: %s", pkt.c_str());
|
||||
agent.restart();
|
||||
sendPacket("S05");
|
||||
}
|
||||
else if (pkt.rfind("vKill", 0) == 0)
|
||||
{
|
||||
sendPacket("OK");
|
||||
agent.kill();
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(COMMON, "unknown v packet: %s", pkt.c_str());
|
||||
sendPacket("");
|
||||
}
|
||||
}
|
||||
|
||||
void restart()
|
||||
{
|
||||
agent.restart();
|
||||
}
|
||||
|
||||
void step(u32 what = 0)
|
||||
{
|
||||
try {
|
||||
agent.step();
|
||||
sendPacket("S05");
|
||||
} catch (const FlycastException& e) {
|
||||
throw Error(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void stepRange(u32 from, u32 to)
|
||||
{
|
||||
try {
|
||||
sendPacket("OK");
|
||||
agent.stepRange(from, to);
|
||||
sendPacket("S05");
|
||||
} catch (const FlycastException& e) {
|
||||
throw Error(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void 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());
|
||||
sendPacket("E01");
|
||||
}
|
||||
switch (type) {
|
||||
case DebugAgent::Breakpoint::BP_TYPE_SOFTWARE_BREAK: // soft bp
|
||||
if (agent.insertMatchpoint(DebugAgent::Breakpoint::BP_TYPE_SOFTWARE_BREAK,
|
||||
addr, len))
|
||||
sendPacket("OK");
|
||||
else
|
||||
sendPacket("E01");
|
||||
break;
|
||||
case DebugAgent::Breakpoint::BP_TYPE_HARDWARE_BREAK: // hardware bp
|
||||
sendPacket("");
|
||||
break;
|
||||
case DebugAgent::Breakpoint::BP_TYPE_WRITE_WATCHPOINT: // write watchpoint
|
||||
sendPacket("");
|
||||
break;
|
||||
case DebugAgent::Breakpoint::BP_TYPE_READ_WATCHPOINT: // read watchpoint
|
||||
sendPacket("");
|
||||
break;
|
||||
case DebugAgent::Breakpoint::BP_TYPE_ACCESS_WATCHPOINT: // access watchpoint
|
||||
sendPacket("");
|
||||
break;
|
||||
default:
|
||||
sendPacket("");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void 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());
|
||||
sendPacket("E01");
|
||||
}
|
||||
switch (type) {
|
||||
case 0: // soft bp
|
||||
if (agent.removeMatchpoint(DebugAgent::Breakpoint::BP_TYPE_SOFTWARE_BREAK,
|
||||
addr, len))
|
||||
sendPacket("OK");
|
||||
else
|
||||
sendPacket("E01");
|
||||
break;
|
||||
case 1: // hardware bp
|
||||
sendPacket("");
|
||||
break;
|
||||
case 2: // write watchpoint
|
||||
sendPacket("");
|
||||
break;
|
||||
case 3: // read watchpoint
|
||||
sendPacket("");
|
||||
break;
|
||||
case 4: // access watchpoint
|
||||
sendPacket("");
|
||||
break;
|
||||
default:
|
||||
sendPacket("");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void interrupt()
|
||||
{
|
||||
u32 signal = agentInterrupt();
|
||||
char s[10];
|
||||
sprintf(s, "S%02x", signal);
|
||||
sendPacket(s);
|
||||
}
|
||||
|
||||
char recvChar()
|
||||
{
|
||||
char c;
|
||||
int rc = ::recv(clientSocket, &c, 1, 0);
|
||||
if (rc <= 0)
|
||||
throw Error("gdb: I/O error");
|
||||
return c;
|
||||
}
|
||||
|
||||
void sendChar(char c)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(outMutex);
|
||||
int rc = ::send(clientSocket, &c, 1, 0);
|
||||
if (rc <= 0)
|
||||
throw Error("gdb: I/O error");
|
||||
}
|
||||
|
||||
u8 unpack(char c)
|
||||
{
|
||||
c = std::tolower(c);
|
||||
if (c <= '9')
|
||||
return c - '0';
|
||||
else
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
std::string recvPacket()
|
||||
{
|
||||
std::string pkt;
|
||||
// look for start character ('$') or BREAK
|
||||
char c = recvChar();
|
||||
if (c == 3)
|
||||
return std::string("\03");
|
||||
if (c != '$')
|
||||
return pkt;
|
||||
|
||||
// read until '#'
|
||||
u8 checksum = 0;
|
||||
while (!stopRequested)
|
||||
{
|
||||
c = recvChar();
|
||||
if (c == '$')
|
||||
{
|
||||
checksum = 0;
|
||||
pkt.clear();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '#')
|
||||
break;
|
||||
|
||||
checksum += (u8)c;
|
||||
pkt.push_back(c);
|
||||
}
|
||||
if (stopRequested)
|
||||
{
|
||||
pkt.clear();
|
||||
return pkt;
|
||||
}
|
||||
u8 recvchk = unpack(recvChar()) << 4;
|
||||
recvchk |= unpack(recvChar());
|
||||
|
||||
// If the checksums don't match print a warning, and put the
|
||||
// negative ack back to the client. Otherwise put a positive ack.
|
||||
if (checksum != recvchk)
|
||||
{
|
||||
sendChar('-'); // Failed checksum
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
sendChar('+'); // Successful transfer
|
||||
return pkt;
|
||||
}
|
||||
}
|
||||
|
||||
void sendPacket(const std::string& pkt)
|
||||
{ DEBUG_LOG(NETWORK, "gdb: sending pkt");
|
||||
std::unique_lock<std::mutex> lock(outMutex);
|
||||
std::string data{'$'};
|
||||
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];
|
||||
sprintf(s, "%02x", checksum);
|
||||
data += s;
|
||||
DEBUG_LOG(NETWORK, "gdb: sent %s", data.c_str());
|
||||
int ret = ::send(clientSocket, data.c_str(), data.length(), 0);
|
||||
if (ret < (int)data.length())
|
||||
throw Error("I/O error");
|
||||
}
|
||||
|
||||
u32 agentInterrupt()
|
||||
{
|
||||
try {
|
||||
return agent.interrupt();
|
||||
} catch (const FlycastException& e) {
|
||||
throw Error(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool initialised = false;
|
||||
bool stopRequested = false;
|
||||
bool attached = false;
|
||||
bool postDebugTrapNeeded = false;
|
||||
sock_t serverSocket = INVALID_SOCKET;
|
||||
sock_t clientSocket = INVALID_SOCKET;
|
||||
std::thread thread;
|
||||
std::mutex outMutex;
|
||||
public:
|
||||
DebugAgent agent;
|
||||
};
|
||||
|
||||
static GdbServer gdbServer;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
static void emuEventCallback(Event event, void *)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
Copyright 2021 flyinghead
|
||||
|
||||
This file is part of Flycast.
|
||||
|
||||
Flycast is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Flycast is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace debugger {
|
||||
|
||||
// exception thrown in response to trap
|
||||
struct Stop { };
|
||||
|
||||
static const int DEFAULT_PORT = 3263;
|
||||
|
||||
#ifdef GDB_SERVER
|
||||
|
||||
void init(int port);
|
||||
void term();
|
||||
void run();
|
||||
void debugTrap(u32 event);
|
||||
void subroutineCall();
|
||||
void subroutineReturn();
|
||||
|
||||
#else
|
||||
static inline void init(int port) {}
|
||||
static inline void term() {}
|
||||
static inline void run() {}
|
||||
static inline void debugTrap(u32 event) {}
|
||||
static inline void subroutineCall() {}
|
||||
static inline void subroutineReturn() {}
|
||||
#endif
|
||||
}
|
||||
Submodule core/deps/SDL deleted from 5adbf3765a.
Submodule core/deps/Spout deleted from 2110a918e7.
Submodule core/deps/Syphon deleted from 486dd8e4ef.
Submodule core/deps/Vulkan-Headers deleted from 85c2334e92.
Submodule core/deps/VulkanMemoryAllocator deleted from 6eb62e1515.
Submodule core/deps/breakpad deleted from 1ab24bcc81.
+13
-23
@@ -11,6 +11,7 @@ unsigned long temp_value;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
unsigned long ask_type(FILE *fsource, long header_position)
|
||||
{
|
||||
|
||||
@@ -41,8 +42,8 @@ unsigned long track_mode;
|
||||
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];
|
||||
char TRACK_START_MARK[10] = { 0, 0, 0x01, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
char current_start_mark[10];
|
||||
|
||||
fread(&temp_value, 4, 1, fsource);
|
||||
if (temp_value != 0)
|
||||
@@ -79,9 +80,7 @@ void CDI_read_track (FILE *fsource, image_s *image, track_s *track)
|
||||
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("Unsupported sector size. value %ld\n", track->sector_size_value);
|
||||
break;
|
||||
default: printf("Unsupported sector size");
|
||||
}
|
||||
|
||||
if (track->mode > 2) printf( "Unsupported format: Track mode not supported");
|
||||
@@ -109,28 +108,19 @@ void CDI_get_tracks (FILE *fsource, image_s *image)
|
||||
fread(&image->tracks, 2, 1, fsource);
|
||||
}
|
||||
|
||||
bool CDI_init (FILE *fsource, image_s *image, const char *fsourcename)
|
||||
void CDI_init (FILE *fsource, image_s *image, char *fsourcename)
|
||||
{
|
||||
fseek(fsource, 0, SEEK_END);
|
||||
image->length = ftell(fsource);
|
||||
fseek(fsource, 0L, SEEK_END);
|
||||
image->length = ftell(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");
|
||||
|
||||
fseek(fsource, image->length-8, SEEK_SET);
|
||||
fread(&image->version, 4, 1, fsource);
|
||||
fread(&image->header_offset, 4, 1, fsource);
|
||||
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");
|
||||
}
|
||||
|
||||
void CDI_get_sessions (FILE *fsource, image_s *image)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef __CDI_H__
|
||||
#define __CDI_H__
|
||||
|
||||
|
||||
|
||||
/* Basic structures */
|
||||
|
||||
typedef struct image_s
|
||||
@@ -37,10 +39,11 @@ typedef struct track_s
|
||||
#define CDI_V35 0x80000006
|
||||
|
||||
unsigned long ask_type(FILE *fsource, long header_position);
|
||||
bool CDI_init(FILE *fsource, image_s *image, const char *fsourcename);
|
||||
void CDI_get_sessions(FILE *fsource, image_s *image);
|
||||
void CDI_get_tracks(FILE *fsource, image_s *image);
|
||||
void CDI_read_track(FILE *fsource, image_s *image, track_s *track);
|
||||
void CDI_skip_next_session(FILE *fsource, image_s *image);
|
||||
void CDI_init (FILE *fsource, image_s *image, char *fsourcename);
|
||||
void CDI_get_sessions (FILE *fsource, image_s *image);
|
||||
void CDI_get_tracks (FILE *fsource, image_s *image);
|
||||
void CDI_read_track (FILE *fsource, image_s *image, track_s *track);
|
||||
void CDI_skip_next_session (FILE *fsource, image_s *image);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -42,12 +42,8 @@
|
||||
#ifndef __CHD_H__
|
||||
#define __CHD_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "coretypes.h"
|
||||
#include <streams/file_stream.h>
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
|
||||
@@ -125,64 +121,20 @@ extern "C" {
|
||||
0x00000001 - set if this drive has a parent
|
||||
0x00000002 - set if this drive allows writes
|
||||
|
||||
=========================================================================
|
||||
|
||||
V5 header:
|
||||
|
||||
[ 0] char tag[8]; // 'MComprHD'
|
||||
[ 8] uint32_t length; // length of header (including tag and length fields)
|
||||
[ 12] uint32_t version; // drive format version
|
||||
[ 16] uint32_t compressors[4];// which custom compressors are used?
|
||||
[ 32] uint64_t logicalbytes; // logical size of the data (in bytes)
|
||||
[ 40] uint64_t mapoffset; // offset to the map
|
||||
[ 48] uint64_t metaoffset; // offset to the first blob of metadata
|
||||
[ 56] uint32_t hunkbytes; // number of bytes per hunk (512k maximum)
|
||||
[ 60] uint32_t unitbytes; // number of bytes per unit within each hunk
|
||||
[ 64] uint8_t rawsha1[20]; // raw data SHA1
|
||||
[ 84] uint8_t sha1[20]; // combined raw+meta SHA1
|
||||
[104] uint8_t parentsha1[20];// combined raw+meta SHA1 of parent
|
||||
[124] (V5 header length)
|
||||
|
||||
If parentsha1 != 0, we have a parent (no need for flags)
|
||||
If compressors[0] == 0, we are uncompressed (including maps)
|
||||
|
||||
V5 uncompressed map format:
|
||||
|
||||
[ 0] uint32_t offset; // starting offset / hunk size
|
||||
|
||||
V5 compressed map format header:
|
||||
|
||||
[ 0] uint32_t length; // length of compressed map
|
||||
[ 4] UINT48 datastart; // offset of first block
|
||||
[ 10] uint16_t crc; // crc-16 of the map
|
||||
[ 12] uint8_t lengthbits; // bits used to encode complength
|
||||
[ 13] uint8_t hunkbits; // bits used to encode self-refs
|
||||
[ 14] uint8_t parentunitbits; // bits used to encode parent unit refs
|
||||
[ 15] uint8_t reserved; // future use
|
||||
[ 16] (compressed header length)
|
||||
|
||||
Each compressed map entry, once expanded, looks like:
|
||||
|
||||
[ 0] uint8_t compression; // compression type
|
||||
[ 1] UINT24 complength; // compressed length
|
||||
[ 4] UINT48 offset; // offset
|
||||
[ 10] uint16_t crc; // crc-16 of the data
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
CONSTANTS
|
||||
***************************************************************************/
|
||||
|
||||
/* header information */
|
||||
#define CHD_HEADER_VERSION 5
|
||||
#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_V5_HEADER_SIZE 124
|
||||
|
||||
#define CHD_MAX_HEADER_SIZE CHD_V5_HEADER_SIZE
|
||||
#define CHD_MAX_HEADER_SIZE CHD_V4_HEADER_SIZE
|
||||
|
||||
/* checksumming information */
|
||||
#define CHD_MD5_BYTES 16
|
||||
@@ -212,7 +164,7 @@ extern "C" {
|
||||
|
||||
/* standard hard disk metadata */
|
||||
#define HARD_DISK_METADATA_TAG 0x47444444 /* 'GDDD' */
|
||||
#define HARD_DISK_METADATA_FORMAT "CYLS:%u,HEADS:%u,SECS:%u,BPS:%u"
|
||||
#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' */
|
||||
@@ -226,11 +178,9 @@ extern "C" {
|
||||
/* standard CD-ROM metadata */
|
||||
#define CDROM_OLD_METADATA_TAG 0x43484344 /* 'CHCD' */
|
||||
#define CDROM_TRACK_METADATA_TAG 0x43485452 /* 'CHTR' */
|
||||
#define CDROM_TRACK_METADATA_FORMAT "TRACK:%u TYPE:%s SUBTYPE:%s FRAMES:%u"
|
||||
#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:%u TYPE:%s SUBTYPE:%s FRAMES:%u PREGAP:%u PGTYPE:%s PGSUB:%s POSTGAP:%u"
|
||||
#define GDROM_TRACK_METADATA_TAG 0x43484744 /* 'CHTD' */
|
||||
#define GDROM_TRACK_METADATA_FORMAT "TRACK:%u TYPE:%s SUBTYPE:%s FRAMES:%u PAD:%u PREGAP:%u PGTYPE:%s PGSUB:%s POSTGAP:%u"
|
||||
#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' */
|
||||
@@ -277,6 +227,8 @@ enum _chd_error
|
||||
};
|
||||
typedef enum _chd_error chd_error;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
TYPE DEFINITIONS
|
||||
***************************************************************************/
|
||||
@@ -284,6 +236,7 @@ typedef enum _chd_error chd_error;
|
||||
/* 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
|
||||
@@ -291,24 +244,16 @@ struct _chd_header
|
||||
UINT32 length; /* length of header data */
|
||||
UINT32 version; /* drive format version */
|
||||
UINT32 flags; /* flags field */
|
||||
UINT32 compression[4]; /* compression type */
|
||||
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 */
|
||||
UINT64 mapoffset; /* TOOD V5 */
|
||||
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 unitbytes; /* TODO V5 */
|
||||
UINT64 unitcount; /* TODO V5 */
|
||||
UINT32 hunkcount; /* TODO V5 */
|
||||
|
||||
/* map information */
|
||||
UINT32 mapentrybytes; /* length of each entry in a map (V5) */
|
||||
UINT8* rawmap; /* raw map data */
|
||||
|
||||
UINT32 obsolete_cylinders; /* obsolete field -- do not use! */
|
||||
UINT32 obsolete_sectors; /* obsolete field -- do not use! */
|
||||
@@ -316,6 +261,7 @@ struct _chd_header
|
||||
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
|
||||
@@ -326,50 +272,72 @@ struct _chd_verify_result
|
||||
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); */
|
||||
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); */
|
||||
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_file(RFILE *file, int mode, chd_file *parent, chd_file **chd);
|
||||
chd_error chd_open(const wchar *filename, int mode, chd_file *parent, chd_file **chd);
|
||||
|
||||
chd_error chd_open(const char *filename, int mode, chd_file *parent, chd_file **chd);
|
||||
|
||||
/* precache underlying file */
|
||||
chd_error chd_precache(chd_file *chd);
|
||||
|
||||
/* close a CHD file */
|
||||
void chd_close(chd_file *chd);
|
||||
|
||||
/* return the associated core_file */
|
||||
RFILE *chd_core_file(chd_file *chd);
|
||||
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 */
|
||||
@@ -378,10 +346,5 @@ 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);
|
||||
|
||||
extern const uint8_t s_cd_sync_header[12];
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __CHD_H__ */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,20 @@
|
||||
|
||||
#include "types.h"
|
||||
|
||||
typedef u64 UINT64;
|
||||
typedef u32 UINT32;
|
||||
typedef u16 UINT16;
|
||||
typedef u8 UINT8;
|
||||
|
||||
typedef s64 INT64;
|
||||
typedef s32 INT32;
|
||||
typedef s16 INT16;
|
||||
typedef s8 INT8;
|
||||
|
||||
#ifndef INLINE
|
||||
#define INLINE inline
|
||||
#endif
|
||||
|
||||
#define core_file FILE
|
||||
|
||||
#define ARRAY_LENGTH(x) (sizeof(x)/sizeof(x[0]))
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* This code implements the MD5 message-digest algorithm.
|
||||
* The algorithm is due to Ron Rivest. This code was
|
||||
* written by Colin Plumb in 1993, no copyright is claimed.
|
||||
* This code is in the public domain; do with it what you wish.
|
||||
*
|
||||
* Equivalent code is available from RSA Data Security, Inc.
|
||||
* This code has been tested against that, and is equivalent,
|
||||
* except that you don't need to include two pages of legalese
|
||||
* with every copy.
|
||||
*
|
||||
* To compute the message digest of a chunk of bytes, declare an
|
||||
* MD5Context structure, pass it to MD5Init, call MD5Update as
|
||||
* needed on buffers full of bytes, and then call MD5Final, which
|
||||
* will fill a supplied 16-byte array with the digest.
|
||||
*
|
||||
* Changed so as no longer to depend on Colin Plumb's `usual.h' header
|
||||
* definitions; now uses stuff from dpkg's config.h.
|
||||
* - Ian Jackson <[email protected]>.
|
||||
* Still in the public domain.
|
||||
*/
|
||||
|
||||
#include <string.h> /* for memcpy() */
|
||||
|
||||
#include "md5.h"
|
||||
|
||||
#ifndef LSB_FIRST
|
||||
void
|
||||
byteSwap(UWORD32 *buf, unsigned words)
|
||||
{
|
||||
md5byte *p = (md5byte *)buf;
|
||||
|
||||
do {
|
||||
*buf++ = (UWORD32)((unsigned)p[3] << 8 | p[2]) << 16 |
|
||||
((unsigned)p[1] << 8 | p[0]);
|
||||
p += 4;
|
||||
} while (--words);
|
||||
}
|
||||
#else
|
||||
#define byteSwap(buf,words)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
|
||||
* initialization constants.
|
||||
*/
|
||||
void
|
||||
MD5Init(struct MD5Context *ctx)
|
||||
{
|
||||
ctx->buf[0] = 0x67452301;
|
||||
ctx->buf[1] = 0xefcdab89;
|
||||
ctx->buf[2] = 0x98badcfe;
|
||||
ctx->buf[3] = 0x10325476;
|
||||
|
||||
ctx->bytes[0] = 0;
|
||||
ctx->bytes[1] = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update context to reflect the concatenation of another buffer full
|
||||
* of bytes.
|
||||
*/
|
||||
void
|
||||
MD5Update(struct MD5Context *ctx, md5byte const *buf, unsigned len)
|
||||
{
|
||||
UWORD32 t;
|
||||
|
||||
/* Update byte count */
|
||||
|
||||
t = ctx->bytes[0];
|
||||
if ((ctx->bytes[0] = t + len) < t)
|
||||
ctx->bytes[1]++; /* Carry from low to high */
|
||||
|
||||
t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */
|
||||
if (t > len) {
|
||||
memcpy((md5byte *)ctx->in + 64 - t, buf, len);
|
||||
return;
|
||||
}
|
||||
/* First chunk is an odd size */
|
||||
memcpy((md5byte *)ctx->in + 64 - t, buf, t);
|
||||
byteSwap(ctx->in, 16);
|
||||
MD5Transform(ctx->buf, ctx->in);
|
||||
buf += t;
|
||||
len -= t;
|
||||
|
||||
/* Process data in 64-byte chunks */
|
||||
while (len >= 64) {
|
||||
memcpy(ctx->in, buf, 64);
|
||||
byteSwap(ctx->in, 16);
|
||||
MD5Transform(ctx->buf, ctx->in);
|
||||
buf += 64;
|
||||
len -= 64;
|
||||
}
|
||||
|
||||
/* Handle any remaining bytes of data. */
|
||||
memcpy(ctx->in, buf, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Final wrapup - pad to 64-byte boundary with the bit pattern
|
||||
* 1 0* (64-bit count of bits processed, MSB-first)
|
||||
*/
|
||||
void
|
||||
MD5Final(md5byte digest[16], struct MD5Context *ctx)
|
||||
{
|
||||
int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */
|
||||
md5byte *p = (md5byte *)ctx->in + count;
|
||||
|
||||
/* Set the first char of padding to 0x80. There is always room. */
|
||||
*p++ = 0x80;
|
||||
|
||||
/* Bytes of padding needed to make 56 bytes (-8..55) */
|
||||
count = 56 - 1 - count;
|
||||
|
||||
if (count < 0) { /* Padding forces an extra block */
|
||||
memset(p, 0, count + 8);
|
||||
byteSwap(ctx->in, 16);
|
||||
MD5Transform(ctx->buf, ctx->in);
|
||||
p = (md5byte *)ctx->in;
|
||||
count = 56;
|
||||
}
|
||||
memset(p, 0, count);
|
||||
byteSwap(ctx->in, 14);
|
||||
|
||||
/* Append length in bits and transform */
|
||||
ctx->in[14] = ctx->bytes[0] << 3;
|
||||
ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29;
|
||||
MD5Transform(ctx->buf, ctx->in);
|
||||
|
||||
byteSwap(ctx->buf, 4);
|
||||
memcpy(digest, ctx->buf, 16);
|
||||
memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */
|
||||
}
|
||||
|
||||
#ifndef ASM_MD5
|
||||
|
||||
/* The four core functions - F1 is optimized somewhat */
|
||||
|
||||
/* #define F1(x, y, z) (x & y | ~x & z) */
|
||||
#define F1(x, y, z) (z ^ (x & (y ^ z)))
|
||||
#define F2(x, y, z) F1(z, x, y)
|
||||
#define F3(x, y, z) (x ^ y ^ z)
|
||||
#define F4(x, y, z) (y ^ (x | ~z))
|
||||
|
||||
/* This is the central step in the MD5 algorithm. */
|
||||
#define MD5STEP(f,w,x,y,z,in,s) \
|
||||
(w += f(x,y,z) + in, w = (w<<s | w>>(32-s)) + x)
|
||||
|
||||
/*
|
||||
* The core of the MD5 algorithm, this alters an existing MD5 hash to
|
||||
* reflect the addition of 16 longwords of new data. MD5Update blocks
|
||||
* the data and converts bytes into longwords for this routine.
|
||||
*/
|
||||
void
|
||||
MD5Transform(UWORD32 buf[4], UWORD32 const in[16])
|
||||
{
|
||||
register UWORD32 a, b, c, d;
|
||||
|
||||
a = buf[0];
|
||||
b = buf[1];
|
||||
c = buf[2];
|
||||
d = buf[3];
|
||||
|
||||
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
|
||||
|
||||
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
|
||||
|
||||
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
|
||||
|
||||
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
|
||||
|
||||
buf[0] += a;
|
||||
buf[1] += b;
|
||||
buf[2] += c;
|
||||
buf[3] += d;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* This is the header file for the MD5 message-digest algorithm.
|
||||
* The algorithm is due to Ron Rivest. This code was
|
||||
* written by Colin Plumb in 1993, no copyright is claimed.
|
||||
* This code is in the public domain; do with it what you wish.
|
||||
*
|
||||
* Equivalent code is available from RSA Data Security, Inc.
|
||||
* This code has been tested against that, and is equivalent,
|
||||
* except that you don't need to include two pages of legalese
|
||||
* with every copy.
|
||||
*
|
||||
* To compute the message digest of a chunk of bytes, declare an
|
||||
* MD5Context structure, pass it to MD5Init, call MD5Update as
|
||||
* needed on buffers full of bytes, and then call MD5Final, which
|
||||
* will fill a supplied 16-byte array with the digest.
|
||||
*
|
||||
* Changed so as no longer to depend on Colin Plumb's `usual.h'
|
||||
* header definitions; now uses stuff from dpkg's config.h
|
||||
* - Ian Jackson <[email protected]>.
|
||||
* Still in the public domain.
|
||||
*/
|
||||
|
||||
#ifndef MD5_H
|
||||
#define MD5_H
|
||||
|
||||
typedef unsigned int UWORD32;
|
||||
|
||||
#define md5byte unsigned char
|
||||
|
||||
struct MD5Context {
|
||||
UWORD32 buf[4];
|
||||
UWORD32 bytes[2];
|
||||
UWORD32 in[16];
|
||||
};
|
||||
|
||||
void MD5Init(struct MD5Context *context);
|
||||
void MD5Update(struct MD5Context *context, md5byte const *buf, unsigned len);
|
||||
void MD5Final(unsigned char digest[16], struct MD5Context *context);
|
||||
void MD5Transform(UWORD32 buf[4], UWORD32 const in[16]);
|
||||
|
||||
#endif /* !MD5_H */
|
||||
|
||||
@@ -0,0 +1,387 @@
|
||||
/* sha1.h
|
||||
*
|
||||
* The sha1 hash function.
|
||||
*/
|
||||
|
||||
/* nettle, low-level cryptographics library
|
||||
*
|
||||
* Copyright 2001 Peter Gutmann, Andrew Kuchling, Niels Moeller
|
||||
*
|
||||
* The nettle library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* The nettle library 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 Lesser General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with the nettle library; see the file COPYING.LIB. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
* MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#include "sha1.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static unsigned int READ_UINT32(const UINT8* data)
|
||||
{
|
||||
return ((UINT32)data[0] << 24) |
|
||||
((UINT32)data[1] << 16) |
|
||||
((UINT32)data[2] << 8) |
|
||||
((UINT32)data[3]);
|
||||
}
|
||||
|
||||
static void WRITE_UINT32(unsigned char* data, UINT32 val)
|
||||
{
|
||||
data[0] = (val >> 24) & 0xFF;
|
||||
data[1] = (val >> 16) & 0xFF;
|
||||
data[2] = (val >> 8) & 0xFF;
|
||||
data[3] = (val >> 0) & 0xFF;
|
||||
}
|
||||
|
||||
|
||||
/* A block, treated as a sequence of 32-bit words. */
|
||||
#define SHA1_DATA_LENGTH 16
|
||||
|
||||
/* The SHA f()-functions. The f1 and f3 functions can be optimized to
|
||||
save one boolean operation each - thanks to Rich Schroeppel,
|
||||
[email protected] for discovering this */
|
||||
|
||||
/* #define f1(x,y,z) ( ( x & y ) | ( ~x & z ) ) Rounds 0-19 */
|
||||
#define f1(x,y,z) ( z ^ ( x & ( y ^ z ) ) ) /* Rounds 0-19 */
|
||||
#define f2(x,y,z) ( x ^ y ^ z ) /* Rounds 20-39 */
|
||||
/* #define f3(x,y,z) ( ( x & y ) | ( x & z ) | ( y & z ) ) Rounds 40-59 */
|
||||
#define f3(x,y,z) ( ( x & y ) | ( z & ( x | y ) ) ) /* Rounds 40-59 */
|
||||
#define f4(x,y,z) ( x ^ y ^ z ) /* Rounds 60-79 */
|
||||
|
||||
/* The SHA Mysterious Constants */
|
||||
|
||||
#define K1 0x5A827999L /* Rounds 0-19 */
|
||||
#define K2 0x6ED9EBA1L /* Rounds 20-39 */
|
||||
#define K3 0x8F1BBCDCL /* Rounds 40-59 */
|
||||
#define K4 0xCA62C1D6L /* Rounds 60-79 */
|
||||
|
||||
/* SHA initial values */
|
||||
|
||||
#define h0init 0x67452301L
|
||||
#define h1init 0xEFCDAB89L
|
||||
#define h2init 0x98BADCFEL
|
||||
#define h3init 0x10325476L
|
||||
#define h4init 0xC3D2E1F0L
|
||||
|
||||
/* 32-bit rotate left - kludged with shifts */
|
||||
#ifdef _MSC_VER
|
||||
#define ROTL(n,X) _lrotl(X, n)
|
||||
#else
|
||||
#define ROTL(n,X) ( ( (X) << (n) ) | ( (X) >> ( 32 - (n) ) ) )
|
||||
#endif
|
||||
|
||||
/* The initial expanding function. The hash function is defined over an
|
||||
80-word expanded input array W, where the first 16 are copies of the input
|
||||
data, and the remaining 64 are defined by
|
||||
|
||||
W[ i ] = W[ i - 16 ] ^ W[ i - 14 ] ^ W[ i - 8 ] ^ W[ i - 3 ]
|
||||
|
||||
This implementation generates these values on the fly in a circular
|
||||
buffer - thanks to Colin Plumb, [email protected] for this
|
||||
optimization.
|
||||
|
||||
The updated SHA changes the expanding function by adding a rotate of 1
|
||||
bit. Thanks to Jim Gillogly, [email protected], and an anonymous contributor
|
||||
for this information */
|
||||
|
||||
#define expand(W,i) ( W[ i & 15 ] = \
|
||||
ROTL( 1, ( W[ i & 15 ] ^ W[ (i - 14) & 15 ] ^ \
|
||||
W[ (i - 8) & 15 ] ^ W[ (i - 3) & 15 ] ) ) )
|
||||
|
||||
|
||||
/* The prototype SHA sub-round. The fundamental sub-round is:
|
||||
|
||||
a' = e + ROTL( 5, a ) + f( b, c, d ) + k + data;
|
||||
b' = a;
|
||||
c' = ROTL( 30, b );
|
||||
d' = c;
|
||||
e' = d;
|
||||
|
||||
but this is implemented by unrolling the loop 5 times and renaming the
|
||||
variables ( e, a, b, c, d ) = ( a', b', c', d', e' ) each iteration.
|
||||
This code is then replicated 20 times for each of the 4 functions, using
|
||||
the next 20 values from the W[] array each time */
|
||||
|
||||
#define subRound(a, b, c, d, e, f, k, data) \
|
||||
( e += ROTL( 5, a ) + f( b, c, d ) + k + data, b = ROTL( 30, b ) )
|
||||
|
||||
/* Initialize the SHA values */
|
||||
|
||||
void
|
||||
sha1_init(struct sha1_ctx *ctx)
|
||||
{
|
||||
/* Set the h-vars to their initial values */
|
||||
ctx->digest[ 0 ] = h0init;
|
||||
ctx->digest[ 1 ] = h1init;
|
||||
ctx->digest[ 2 ] = h2init;
|
||||
ctx->digest[ 3 ] = h3init;
|
||||
ctx->digest[ 4 ] = h4init;
|
||||
|
||||
/* Initialize bit count */
|
||||
ctx->count_low = ctx->count_high = 0;
|
||||
|
||||
/* Initialize buffer */
|
||||
ctx->index = 0;
|
||||
}
|
||||
|
||||
/* Perform the SHA transformation. Note that this code, like MD5, seems to
|
||||
break some optimizing compilers due to the complexity of the expressions
|
||||
and the size of the basic block. It may be necessary to split it into
|
||||
sections, e.g. based on the four subrounds
|
||||
|
||||
Note that this function destroys the data area */
|
||||
|
||||
static void
|
||||
sha1_transform(UINT32 *state, UINT32 *data)
|
||||
{
|
||||
UINT32 A, B, C, D, E; /* Local vars */
|
||||
|
||||
/* Set up first buffer and local data buffer */
|
||||
A = state[0];
|
||||
B = state[1];
|
||||
C = state[2];
|
||||
D = state[3];
|
||||
E = state[4];
|
||||
|
||||
/* Heavy mangling, in 4 sub-rounds of 20 interations each. */
|
||||
subRound( A, B, C, D, E, f1, K1, data[ 0] );
|
||||
subRound( E, A, B, C, D, f1, K1, data[ 1] );
|
||||
subRound( D, E, A, B, C, f1, K1, data[ 2] );
|
||||
subRound( C, D, E, A, B, f1, K1, data[ 3] );
|
||||
subRound( B, C, D, E, A, f1, K1, data[ 4] );
|
||||
subRound( A, B, C, D, E, f1, K1, data[ 5] );
|
||||
subRound( E, A, B, C, D, f1, K1, data[ 6] );
|
||||
subRound( D, E, A, B, C, f1, K1, data[ 7] );
|
||||
subRound( C, D, E, A, B, f1, K1, data[ 8] );
|
||||
subRound( B, C, D, E, A, f1, K1, data[ 9] );
|
||||
subRound( A, B, C, D, E, f1, K1, data[10] );
|
||||
subRound( E, A, B, C, D, f1, K1, data[11] );
|
||||
subRound( D, E, A, B, C, f1, K1, data[12] );
|
||||
subRound( C, D, E, A, B, f1, K1, data[13] );
|
||||
subRound( B, C, D, E, A, f1, K1, data[14] );
|
||||
subRound( A, B, C, D, E, f1, K1, data[15] );
|
||||
subRound( E, A, B, C, D, f1, K1, expand( data, 16 ) );
|
||||
subRound( D, E, A, B, C, f1, K1, expand( data, 17 ) );
|
||||
subRound( C, D, E, A, B, f1, K1, expand( data, 18 ) );
|
||||
subRound( B, C, D, E, A, f1, K1, expand( data, 19 ) );
|
||||
|
||||
subRound( A, B, C, D, E, f2, K2, expand( data, 20 ) );
|
||||
subRound( E, A, B, C, D, f2, K2, expand( data, 21 ) );
|
||||
subRound( D, E, A, B, C, f2, K2, expand( data, 22 ) );
|
||||
subRound( C, D, E, A, B, f2, K2, expand( data, 23 ) );
|
||||
subRound( B, C, D, E, A, f2, K2, expand( data, 24 ) );
|
||||
subRound( A, B, C, D, E, f2, K2, expand( data, 25 ) );
|
||||
subRound( E, A, B, C, D, f2, K2, expand( data, 26 ) );
|
||||
subRound( D, E, A, B, C, f2, K2, expand( data, 27 ) );
|
||||
subRound( C, D, E, A, B, f2, K2, expand( data, 28 ) );
|
||||
subRound( B, C, D, E, A, f2, K2, expand( data, 29 ) );
|
||||
subRound( A, B, C, D, E, f2, K2, expand( data, 30 ) );
|
||||
subRound( E, A, B, C, D, f2, K2, expand( data, 31 ) );
|
||||
subRound( D, E, A, B, C, f2, K2, expand( data, 32 ) );
|
||||
subRound( C, D, E, A, B, f2, K2, expand( data, 33 ) );
|
||||
subRound( B, C, D, E, A, f2, K2, expand( data, 34 ) );
|
||||
subRound( A, B, C, D, E, f2, K2, expand( data, 35 ) );
|
||||
subRound( E, A, B, C, D, f2, K2, expand( data, 36 ) );
|
||||
subRound( D, E, A, B, C, f2, K2, expand( data, 37 ) );
|
||||
subRound( C, D, E, A, B, f2, K2, expand( data, 38 ) );
|
||||
subRound( B, C, D, E, A, f2, K2, expand( data, 39 ) );
|
||||
|
||||
subRound( A, B, C, D, E, f3, K3, expand( data, 40 ) );
|
||||
subRound( E, A, B, C, D, f3, K3, expand( data, 41 ) );
|
||||
subRound( D, E, A, B, C, f3, K3, expand( data, 42 ) );
|
||||
subRound( C, D, E, A, B, f3, K3, expand( data, 43 ) );
|
||||
subRound( B, C, D, E, A, f3, K3, expand( data, 44 ) );
|
||||
subRound( A, B, C, D, E, f3, K3, expand( data, 45 ) );
|
||||
subRound( E, A, B, C, D, f3, K3, expand( data, 46 ) );
|
||||
subRound( D, E, A, B, C, f3, K3, expand( data, 47 ) );
|
||||
subRound( C, D, E, A, B, f3, K3, expand( data, 48 ) );
|
||||
subRound( B, C, D, E, A, f3, K3, expand( data, 49 ) );
|
||||
subRound( A, B, C, D, E, f3, K3, expand( data, 50 ) );
|
||||
subRound( E, A, B, C, D, f3, K3, expand( data, 51 ) );
|
||||
subRound( D, E, A, B, C, f3, K3, expand( data, 52 ) );
|
||||
subRound( C, D, E, A, B, f3, K3, expand( data, 53 ) );
|
||||
subRound( B, C, D, E, A, f3, K3, expand( data, 54 ) );
|
||||
subRound( A, B, C, D, E, f3, K3, expand( data, 55 ) );
|
||||
subRound( E, A, B, C, D, f3, K3, expand( data, 56 ) );
|
||||
subRound( D, E, A, B, C, f3, K3, expand( data, 57 ) );
|
||||
subRound( C, D, E, A, B, f3, K3, expand( data, 58 ) );
|
||||
subRound( B, C, D, E, A, f3, K3, expand( data, 59 ) );
|
||||
|
||||
subRound( A, B, C, D, E, f4, K4, expand( data, 60 ) );
|
||||
subRound( E, A, B, C, D, f4, K4, expand( data, 61 ) );
|
||||
subRound( D, E, A, B, C, f4, K4, expand( data, 62 ) );
|
||||
subRound( C, D, E, A, B, f4, K4, expand( data, 63 ) );
|
||||
subRound( B, C, D, E, A, f4, K4, expand( data, 64 ) );
|
||||
subRound( A, B, C, D, E, f4, K4, expand( data, 65 ) );
|
||||
subRound( E, A, B, C, D, f4, K4, expand( data, 66 ) );
|
||||
subRound( D, E, A, B, C, f4, K4, expand( data, 67 ) );
|
||||
subRound( C, D, E, A, B, f4, K4, expand( data, 68 ) );
|
||||
subRound( B, C, D, E, A, f4, K4, expand( data, 69 ) );
|
||||
subRound( A, B, C, D, E, f4, K4, expand( data, 70 ) );
|
||||
subRound( E, A, B, C, D, f4, K4, expand( data, 71 ) );
|
||||
subRound( D, E, A, B, C, f4, K4, expand( data, 72 ) );
|
||||
subRound( C, D, E, A, B, f4, K4, expand( data, 73 ) );
|
||||
subRound( B, C, D, E, A, f4, K4, expand( data, 74 ) );
|
||||
subRound( A, B, C, D, E, f4, K4, expand( data, 75 ) );
|
||||
subRound( E, A, B, C, D, f4, K4, expand( data, 76 ) );
|
||||
subRound( D, E, A, B, C, f4, K4, expand( data, 77 ) );
|
||||
subRound( C, D, E, A, B, f4, K4, expand( data, 78 ) );
|
||||
subRound( B, C, D, E, A, f4, K4, expand( data, 79 ) );
|
||||
|
||||
/* Build message digest */
|
||||
state[0] += A;
|
||||
state[1] += B;
|
||||
state[2] += C;
|
||||
state[3] += D;
|
||||
state[4] += E;
|
||||
}
|
||||
|
||||
static void
|
||||
sha1_block(struct sha1_ctx *ctx, const UINT8 *block)
|
||||
{
|
||||
UINT32 data[SHA1_DATA_LENGTH];
|
||||
int i;
|
||||
|
||||
/* Update block count */
|
||||
if (!++ctx->count_low)
|
||||
++ctx->count_high;
|
||||
|
||||
/* Endian independent conversion */
|
||||
for (i = 0; i<SHA1_DATA_LENGTH; i++, block += 4)
|
||||
data[i] = READ_UINT32(block);
|
||||
|
||||
sha1_transform(ctx->digest, data);
|
||||
}
|
||||
|
||||
void
|
||||
sha1_update(struct sha1_ctx *ctx,
|
||||
unsigned length, const UINT8 *buffer)
|
||||
{
|
||||
if (ctx->index)
|
||||
{ /* Try to fill partial block */
|
||||
unsigned left = SHA1_DATA_SIZE - ctx->index;
|
||||
if (length < left)
|
||||
{
|
||||
memcpy(ctx->block + ctx->index, buffer, length);
|
||||
ctx->index += length;
|
||||
return; /* Finished */
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(ctx->block + ctx->index, buffer, left);
|
||||
sha1_block(ctx, ctx->block);
|
||||
buffer += left;
|
||||
length -= left;
|
||||
}
|
||||
}
|
||||
while (length >= SHA1_DATA_SIZE)
|
||||
{
|
||||
sha1_block(ctx, buffer);
|
||||
buffer += SHA1_DATA_SIZE;
|
||||
length -= SHA1_DATA_SIZE;
|
||||
}
|
||||
ctx->index = length;
|
||||
if (length)
|
||||
/* Buffer leftovers */
|
||||
memcpy(ctx->block, buffer, length);
|
||||
}
|
||||
|
||||
/* Final wrapup - pad to SHA1_DATA_SIZE-byte boundary with the bit pattern
|
||||
1 0* (64-bit count of bits processed, MSB-first) */
|
||||
|
||||
void
|
||||
sha1_final(struct sha1_ctx *ctx)
|
||||
{
|
||||
UINT32 data[SHA1_DATA_LENGTH];
|
||||
int i;
|
||||
int words;
|
||||
|
||||
i = ctx->index;
|
||||
|
||||
/* Set the first char of padding to 0x80. This is safe since there is
|
||||
always at least one byte free */
|
||||
|
||||
assert(i < SHA1_DATA_SIZE);
|
||||
ctx->block[i++] = 0x80;
|
||||
|
||||
/* Fill rest of word */
|
||||
for( ; i & 3; i++)
|
||||
ctx->block[i] = 0;
|
||||
|
||||
/* i is now a multiple of the word size 4 */
|
||||
words = i >> 2;
|
||||
for (i = 0; i < words; i++)
|
||||
data[i] = READ_UINT32(ctx->block + 4*i);
|
||||
|
||||
if (words > (SHA1_DATA_LENGTH-2))
|
||||
{ /* No room for length in this block. Process it and
|
||||
* pad with another one */
|
||||
for (i = words ; i < SHA1_DATA_LENGTH; i++)
|
||||
data[i] = 0;
|
||||
sha1_transform(ctx->digest, data);
|
||||
for (i = 0; i < (SHA1_DATA_LENGTH-2); i++)
|
||||
data[i] = 0;
|
||||
}
|
||||
else
|
||||
for (i = words ; i < SHA1_DATA_LENGTH - 2; i++)
|
||||
data[i] = 0;
|
||||
|
||||
/* There are 512 = 2^9 bits in one block */
|
||||
data[SHA1_DATA_LENGTH-2] = (ctx->count_high << 9) | (ctx->count_low >> 23);
|
||||
data[SHA1_DATA_LENGTH-1] = (ctx->count_low << 9) | (ctx->index << 3);
|
||||
sha1_transform(ctx->digest, data);
|
||||
}
|
||||
|
||||
void
|
||||
sha1_digest(const struct sha1_ctx *ctx,
|
||||
unsigned length,
|
||||
UINT8 *digest)
|
||||
{
|
||||
unsigned i;
|
||||
unsigned words;
|
||||
unsigned leftover;
|
||||
|
||||
assert(length <= SHA1_DIGEST_SIZE);
|
||||
|
||||
words = length / 4;
|
||||
leftover = length % 4;
|
||||
|
||||
for (i = 0; i < words; i++, digest += 4)
|
||||
WRITE_UINT32(digest, ctx->digest[i]);
|
||||
|
||||
if (leftover)
|
||||
{
|
||||
UINT32 word;
|
||||
unsigned j = leftover;
|
||||
|
||||
assert(i < _SHA1_DIGEST_LENGTH);
|
||||
|
||||
word = ctx->digest[i];
|
||||
|
||||
switch (leftover)
|
||||
{
|
||||
default:
|
||||
/* this is just here to keep the compiler happy; it can never happen */
|
||||
case 3:
|
||||
digest[--j] = (word >> 8) & 0xff;
|
||||
/* Fall through */
|
||||
case 2:
|
||||
digest[--j] = (word >> 16) & 0xff;
|
||||
/* Fall through */
|
||||
case 1:
|
||||
digest[--j] = (word >> 24) & 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/* sha1.h
|
||||
*
|
||||
* The sha1 hash function.
|
||||
*/
|
||||
|
||||
/* nettle, low-level cryptographics library
|
||||
*
|
||||
* Copyright 2001 Niels Moeller
|
||||
*
|
||||
* The nettle library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* The nettle library 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 Lesser General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with the nettle library; see the file COPYING.LIB. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||||
* MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef NETTLE_SHA1_H_INCLUDED
|
||||
#define NETTLE_SHA1_H_INCLUDED
|
||||
|
||||
#include "../chdr/coretypes.h"
|
||||
|
||||
|
||||
|
||||
#define SHA1_DIGEST_SIZE 20
|
||||
#define SHA1_DATA_SIZE 64
|
||||
|
||||
/* Digest is kept internally as 4 32-bit words. */
|
||||
#define _SHA1_DIGEST_LENGTH 5
|
||||
|
||||
struct sha1_ctx
|
||||
{
|
||||
UINT32 digest[_SHA1_DIGEST_LENGTH]; /* Message digest */
|
||||
UINT32 count_low, count_high; /* 64-bit block count */
|
||||
UINT8 block[SHA1_DATA_SIZE]; /* SHA1 data buffer */
|
||||
unsigned int index; /* index into buffer */
|
||||
};
|
||||
|
||||
void
|
||||
sha1_init(struct sha1_ctx *ctx);
|
||||
|
||||
void
|
||||
sha1_update(struct sha1_ctx *ctx,
|
||||
unsigned length,
|
||||
const UINT8 *data);
|
||||
|
||||
void
|
||||
sha1_final(struct sha1_ctx *ctx);
|
||||
|
||||
void
|
||||
sha1_digest(const struct sha1_ctx *ctx,
|
||||
unsigned length,
|
||||
UINT8 *digest);
|
||||
|
||||
#endif /* NETTLE_SHA1_H_INCLUDED */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,598 +0,0 @@
|
||||
/* -----------------------------------------------------------------------
|
||||
* GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC.
|
||||
*
|
||||
* Use of this software is governed by the MIT license that can be found
|
||||
* in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef _GGPONET_H_
|
||||
#define _GGPONET_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
// On windows, export at build time and import at runtime.
|
||||
// ELF systems don't need an explicit export/import.
|
||||
#ifdef _WIN32
|
||||
# if defined(GGPO_SHARED_LIB)
|
||||
# ifdef GGPO_SDK_EXPORT
|
||||
# define GGPO_API __declspec(dllexport)
|
||||
# else
|
||||
# define GGPO_API __declspec(dllimport)
|
||||
# endif
|
||||
# else
|
||||
# define GGPO_API
|
||||
# endif
|
||||
#else
|
||||
# define GGPO_API
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#if defined(_WIN32) && !defined(_WIN64)
|
||||
#define __cdecl __attribute__((cdecl))
|
||||
#elif !defined(__cdecl)
|
||||
#define __cdecl
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define GGPO_MAX_PLAYERS 4
|
||||
#define GGPO_MAX_PREDICTION_FRAMES 8
|
||||
#define GGPO_MAX_SPECTATORS 32
|
||||
|
||||
#define GGPO_SPECTATOR_INPUT_INTERVAL 4
|
||||
|
||||
typedef struct GGPOSession GGPOSession;
|
||||
|
||||
typedef int GGPOPlayerHandle;
|
||||
|
||||
typedef enum {
|
||||
GGPO_PLAYERTYPE_LOCAL,
|
||||
GGPO_PLAYERTYPE_REMOTE,
|
||||
GGPO_PLAYERTYPE_SPECTATOR,
|
||||
} GGPOPlayerType;
|
||||
|
||||
/*
|
||||
* The GGPOPlayer structure used to describe players in ggpo_add_player
|
||||
*
|
||||
* size: Should be set to the sizeof(GGPOPlayer)
|
||||
*
|
||||
* type: One of the GGPOPlayerType values describing how inputs should be handled
|
||||
* Local players must have their inputs updated every frame via
|
||||
* ggpo_add_local_inputs. Remote players values will come over the
|
||||
* network.
|
||||
*
|
||||
* player_num: The player number. Should be between 1 and the number of players
|
||||
* In the game (e.g. in a 2 player game, either 1 or 2).
|
||||
*
|
||||
* If type == GGPO_PLAYERTYPE_REMOTE:
|
||||
*
|
||||
* u.remote.ip_address: The ip address of the ggpo session which will host this
|
||||
* player.
|
||||
*
|
||||
* u.remote.port: The port where udp packets should be sent to reach this player.
|
||||
* All the local inputs for this session will be sent to this player at
|
||||
* ip_address:port.
|
||||
*
|
||||
*/
|
||||
|
||||
typedef struct GGPOPlayer {
|
||||
int size;
|
||||
GGPOPlayerType type;
|
||||
int player_num;
|
||||
union {
|
||||
struct {
|
||||
bool _unused;
|
||||
} local;
|
||||
struct {
|
||||
char ip_address[32];
|
||||
unsigned short port;
|
||||
} remote;
|
||||
} u;
|
||||
} GGPOPlayer;
|
||||
|
||||
typedef struct GGPOLocalEndpoint {
|
||||
int player_num;
|
||||
} GGPOLocalEndpoint;
|
||||
|
||||
|
||||
#define GGPO_ERRORLIST \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_OK, 0) \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_SUCCESS, 0) \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_GENERAL_FAILURE, -1) \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_INVALID_SESSION, 1) \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_INVALID_PLAYER_HANDLE, 2) \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_PLAYER_OUT_OF_RANGE, 3) \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_PREDICTION_THRESHOLD, 4) \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_UNSUPPORTED, 5) \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_NOT_SYNCHRONIZED, 6) \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_IN_ROLLBACK, 7) \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_INPUT_DROPPED, 8) \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_PLAYER_DISCONNECTED, 9) \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_TOO_MANY_SPECTATORS, 10) \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_INVALID_REQUEST, 11) \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_INPUT_SIZE_DIFF, 12) \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_NETWORK_ERROR, 13) \
|
||||
GGPO_ERRORLIST_ENTRY(GGPO_ERRORCODE_VERIFICATION_ERROR, 14)
|
||||
|
||||
#define GGPO_ERRORLIST_ENTRY(name, value) name = value,
|
||||
typedef enum {
|
||||
GGPO_ERRORLIST
|
||||
} GGPOErrorCode;
|
||||
#undef GGPO_ERRORLIST_ENTRY
|
||||
|
||||
#define GGPO_SUCCEEDED(result) ((result) == GGPO_ERRORCODE_SUCCESS)
|
||||
|
||||
|
||||
#define GGPO_INVALID_HANDLE (-1)
|
||||
|
||||
|
||||
/*
|
||||
* The GGPOEventCode enumeration describes what type of event just happened.
|
||||
*
|
||||
* GGPO_EVENTCODE_CONNECTED_TO_PEER - Handshake with the game running on the
|
||||
* other side of the network has been completed.
|
||||
*
|
||||
* GGPO_EVENTCODE_SYNCHRONIZING_WITH_PEER - Beginning the synchronization
|
||||
* process with the client on the other end of the networking. The count
|
||||
* and total fields in the u.synchronizing struct of the GGPOEvent
|
||||
* object indicate progress.
|
||||
*
|
||||
* GGPO_EVENTCODE_SYNCHRONIZED_WITH_PEER - The synchronziation with this
|
||||
* peer has finished.
|
||||
*
|
||||
* GGPO_EVENTCODE_RUNNING - All the clients have synchronized. You may begin
|
||||
* sending inputs with ggpo_synchronize_inputs.
|
||||
*
|
||||
* GGPO_EVENTCODE_DISCONNECTED_FROM_PEER - The network connection on
|
||||
* the other end of the network has closed.
|
||||
*
|
||||
* GGPO_EVENTCODE_TIMESYNC - The time synchronziation code has determined
|
||||
* that this client is too far ahead of the other one and should slow
|
||||
* down to ensure fairness. The u.timesync.frames_ahead parameter in
|
||||
* the GGPOEvent object indicates how many frames the client is.
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
GGPO_EVENTCODE_CONNECTED_TO_PEER = 1000,
|
||||
GGPO_EVENTCODE_SYNCHRONIZING_WITH_PEER = 1001,
|
||||
GGPO_EVENTCODE_SYNCHRONIZED_WITH_PEER = 1002,
|
||||
GGPO_EVENTCODE_RUNNING = 1003,
|
||||
GGPO_EVENTCODE_DISCONNECTED_FROM_PEER = 1004,
|
||||
GGPO_EVENTCODE_TIMESYNC = 1005,
|
||||
GGPO_EVENTCODE_CONNECTION_INTERRUPTED = 1006,
|
||||
GGPO_EVENTCODE_CONNECTION_RESUMED = 1007,
|
||||
} GGPOEventCode;
|
||||
|
||||
/*
|
||||
* The GGPOEvent structure contains an asynchronous event notification sent
|
||||
* by the on_event callback. See GGPOEventCode, above, for a detailed
|
||||
* explanation of each event.
|
||||
*/
|
||||
typedef struct {
|
||||
GGPOEventCode code;
|
||||
union {
|
||||
struct {
|
||||
GGPOPlayerHandle player;
|
||||
} connected;
|
||||
struct {
|
||||
GGPOPlayerHandle player;
|
||||
int count;
|
||||
int total;
|
||||
} synchronizing;
|
||||
struct {
|
||||
GGPOPlayerHandle player;
|
||||
} synchronized;
|
||||
struct {
|
||||
GGPOPlayerHandle player;
|
||||
} disconnected;
|
||||
struct {
|
||||
int frames_ahead;
|
||||
} timesync;
|
||||
struct {
|
||||
GGPOPlayerHandle player;
|
||||
int disconnect_timeout;
|
||||
} connection_interrupted;
|
||||
struct {
|
||||
GGPOPlayerHandle player;
|
||||
} connection_resumed;
|
||||
} u;
|
||||
} GGPOEvent;
|
||||
|
||||
/*
|
||||
* The GGPOSessionCallbacks structure contains the callback functions that
|
||||
* your application must implement. GGPO.net will periodically call these
|
||||
* functions during the game. All callback functions must be implemented.
|
||||
*/
|
||||
typedef struct {
|
||||
/*
|
||||
* begin_game callback - This callback has been deprecated. You must
|
||||
* implement it, but should ignore the 'game' parameter.
|
||||
*/
|
||||
bool (__cdecl *begin_game)(const char *game);
|
||||
|
||||
/*
|
||||
* save_game_state - The client should allocate a buffer, copy the
|
||||
* entire contents of the current game state into it, and copy the
|
||||
* length into the *len parameter. Optionally, the client can compute
|
||||
* a checksum of the data and store it in the *checksum argument.
|
||||
*/
|
||||
bool (__cdecl *save_game_state)(unsigned char **buffer, int *len, int *checksum, int frame);
|
||||
|
||||
/*
|
||||
* load_game_state - GGPO.net will call this function at the beginning
|
||||
* of a rollback. The buffer and len parameters contain a previously
|
||||
* saved state returned from the save_game_state function. The client
|
||||
* should make the current game state match the state contained in the
|
||||
* buffer.
|
||||
*/
|
||||
bool (__cdecl *load_game_state)(unsigned char *buffer, int len);
|
||||
|
||||
/*
|
||||
* log_game_state - Used in diagnostic testing. The client should use
|
||||
* the ggpo_log function to write the contents of the specified save
|
||||
* state in a human readible form.
|
||||
*/
|
||||
bool (__cdecl *log_game_state)(char *filename, unsigned char *buffer, int len);
|
||||
|
||||
/*
|
||||
* free_buffer - Frees a game state allocated in save_game_state. You
|
||||
* should deallocate the memory contained in the buffer.
|
||||
*/
|
||||
void (__cdecl *free_buffer)(void *buffer);
|
||||
|
||||
/*
|
||||
* advance_frame - Called during a rollback. You should advance your game
|
||||
* state by exactly one frame. Before each frame, call ggpo_synchronize_input
|
||||
* to retrieve the inputs you should use for that frame. After each frame,
|
||||
* you should call ggpo_advance_frame to notify GGPO.net that you're
|
||||
* finished.
|
||||
*
|
||||
* The flags parameter is reserved. It can safely be ignored at this time.
|
||||
*/
|
||||
bool (__cdecl *advance_frame)(int flags);
|
||||
|
||||
/*
|
||||
* on_event - Notification that something has happened. See the GGPOEventCode
|
||||
* structure above for more information.
|
||||
*/
|
||||
bool (__cdecl *on_event)(GGPOEvent *info);
|
||||
|
||||
/*
|
||||
* on_message - Delivers an application message. Allows arbitrary data to be
|
||||
* exchanged with peers. This callback is optional.
|
||||
*/
|
||||
void (__cdecl *on_message)(unsigned char *msg, int len);
|
||||
} GGPOSessionCallbacks;
|
||||
|
||||
/*
|
||||
* The GGPONetworkStats function contains some statistics about the current
|
||||
* session.
|
||||
*
|
||||
* network.send_queue_len - The length of the queue containing UDP packets
|
||||
* which have not yet been acknowledged by the end client. The length of
|
||||
* the send queue is a rough indication of the quality of the connection.
|
||||
* The longer the send queue, the higher the round-trip time between the
|
||||
* clients. The send queue will also be longer than usual during high
|
||||
* packet loss situations.
|
||||
*
|
||||
* network.recv_queue_len - The number of inputs currently buffered by the
|
||||
* GGPO.net network layer which have yet to be validated. The length of
|
||||
* the prediction queue is roughly equal to the current frame number
|
||||
* minus the frame number of the last packet in the remote queue.
|
||||
*
|
||||
* network.ping - The roundtrip packet transmission time as calcuated
|
||||
* by GGPO.net. This will be roughly equal to the actual round trip
|
||||
* packet transmission time + 2 the interval at which you call ggpo_idle
|
||||
* or ggpo_advance_frame.
|
||||
*
|
||||
* network.kbps_sent - The estimated bandwidth used between the two
|
||||
* clients, in kilobits per second.
|
||||
*
|
||||
* timesync.local_frames_behind - The number of frames GGPO.net calculates
|
||||
* that the local client is behind the remote client at this instant in
|
||||
* time. For example, if at this instant the current game client is running
|
||||
* frame 1002 and the remote game client is running frame 1009, this value
|
||||
* will mostly likely roughly equal 7.
|
||||
*
|
||||
* timesync.remote_frames_behind - The same as local_frames_behind, but
|
||||
* calculated from the perspective of the remote player.
|
||||
*
|
||||
*/
|
||||
typedef struct GGPONetworkStats {
|
||||
struct {
|
||||
int send_queue_len;
|
||||
int recv_queue_len;
|
||||
int ping;
|
||||
int kbps_sent;
|
||||
} network;
|
||||
struct {
|
||||
int local_frames_behind;
|
||||
int remote_frames_behind;
|
||||
} timesync;
|
||||
struct {
|
||||
int predicted_frames;
|
||||
} sync;
|
||||
} GGPONetworkStats;
|
||||
|
||||
/*
|
||||
* ggpo_start_session --
|
||||
*
|
||||
* Used to being a new GGPO.net session. The ggpo object returned by ggpo_start_session
|
||||
* uniquely identifies the state for this session and should be passed to all other
|
||||
* functions.
|
||||
*
|
||||
* session - An out parameter to the new ggpo session object.
|
||||
*
|
||||
* cb - A GGPOSessionCallbacks structure which contains the callbacks you implement
|
||||
* to help GGPO.net synchronize the two games. You must implement all functions in
|
||||
* cb, even if they do nothing but 'return true';
|
||||
*
|
||||
* game - The name of the game. This is used internally for GGPO for logging purposes only.
|
||||
*
|
||||
* num_players - The number of players which will be in this game. The number of players
|
||||
* per session is fixed. If you need to change the number of players or any player
|
||||
* disconnects, you must start a new session.
|
||||
*
|
||||
* input_size - The size of the game inputs which will be passsed to ggpo_add_local_input.
|
||||
*
|
||||
* local_port - The port GGPO should bind to for UDP traffic.
|
||||
*
|
||||
* verification - Some optional data that will be matched with the peers during initial sync.
|
||||
* Can be set to null if verification_size is zero. This can be used to check that all peers are
|
||||
* running the same version of the application and of the game, are using the same settings, etc.
|
||||
*
|
||||
* verification_size - Size of the verification data. Can be set to zero to not exchange verification data.
|
||||
* Maximum size is 256 bytes.
|
||||
*/
|
||||
GGPO_API GGPOErrorCode __cdecl ggpo_start_session(GGPOSession **session,
|
||||
GGPOSessionCallbacks *cb,
|
||||
const char *game,
|
||||
int num_players,
|
||||
int input_size,
|
||||
unsigned short localport,
|
||||
const void *verification,
|
||||
int verification_size);
|
||||
|
||||
|
||||
/*
|
||||
* ggpo_add_player --
|
||||
*
|
||||
* Must be called for each player in the session (e.g. in a 3 player session, must
|
||||
* be called 3 times).
|
||||
*
|
||||
* player - A GGPOPlayer struct used to describe the player.
|
||||
*
|
||||
* handle - An out parameter to a handle used to identify this player in the future.
|
||||
* (e.g. in the on_event callbacks).
|
||||
*/
|
||||
GGPO_API GGPOErrorCode __cdecl ggpo_add_player(GGPOSession *session,
|
||||
GGPOPlayer *player,
|
||||
GGPOPlayerHandle *handle);
|
||||
|
||||
|
||||
/*
|
||||
* ggpo_start_synctest --
|
||||
*
|
||||
* Used to being a new GGPO.net sync test session. During a sync test, every
|
||||
* frame of execution is run twice: once in prediction mode and once again to
|
||||
* verify the result of the prediction. If the checksums of your save states
|
||||
* do not match, the test is aborted.
|
||||
*
|
||||
* cb - A GGPOSessionCallbacks structure which contains the callbacks you implement
|
||||
* to help GGPO.net synchronize the two games. You must implement all functions in
|
||||
* cb, even if they do nothing but 'return true';
|
||||
*
|
||||
* game - The name of the game. This is used internally for GGPO for logging purposes only.
|
||||
*
|
||||
* num_players - The number of players which will be in this game. The number of players
|
||||
* per session is fixed. If you need to change the number of players or any player
|
||||
* disconnects, you must start a new session.
|
||||
*
|
||||
* input_size - The size of the game inputs which will be passsed to ggpo_add_local_input.
|
||||
*
|
||||
* frames - The number of frames to run before verifying the prediction. The
|
||||
* recommended value is 1.
|
||||
*
|
||||
*/
|
||||
GGPO_API GGPOErrorCode __cdecl ggpo_start_synctest(GGPOSession **session,
|
||||
GGPOSessionCallbacks *cb,
|
||||
const char *game,
|
||||
int num_players,
|
||||
int input_size,
|
||||
int frames);
|
||||
|
||||
|
||||
/*
|
||||
* ggpo_start_spectating --
|
||||
*
|
||||
* Start a spectator session.
|
||||
*
|
||||
* cb - A GGPOSessionCallbacks structure which contains the callbacks you implement
|
||||
* to help GGPO.net synchronize the two games. You must implement all functions in
|
||||
* cb, even if they do nothing but 'return true';
|
||||
*
|
||||
* game - The name of the game. This is used internally for GGPO for logging purposes only.
|
||||
*
|
||||
* num_players - The number of players which will be in this game. The number of players
|
||||
* per session is fixed. If you need to change the number of players or any player
|
||||
* disconnects, you must start a new session.
|
||||
*
|
||||
* input_size - The size of the game inputs which will be passsed to ggpo_add_local_input.
|
||||
*
|
||||
* local_port - The port GGPO should bind to for UDP traffic.
|
||||
*
|
||||
* host_ip - The IP address of the host who will serve you the inputs for the game. Any
|
||||
* player partcipating in the session can serve as a host.
|
||||
*
|
||||
* host_port - The port of the session on the host
|
||||
*
|
||||
* verification - Some optional data that will be matched with the peers during initial sync.
|
||||
* Can be set to null if verification_size is zero. This can be used to check that all peers are
|
||||
* running the same version of the application and of the game, are using the same settings, etc.
|
||||
*
|
||||
* verification_size - Size of the verification data. Can be set to zero to not exchange verification data.
|
||||
* Maximum size is 256 bytes.
|
||||
*/
|
||||
GGPO_API GGPOErrorCode __cdecl ggpo_start_spectating(GGPOSession **session,
|
||||
GGPOSessionCallbacks *cb,
|
||||
const char *game,
|
||||
int num_players,
|
||||
int input_size,
|
||||
unsigned short local_port,
|
||||
char *host_ip,
|
||||
unsigned short host_port,
|
||||
const void *verification,
|
||||
int verification_size);
|
||||
|
||||
/*
|
||||
* ggpo_close_session --
|
||||
* Used to close a session. You must call ggpo_close_session to
|
||||
* free the resources allocated in ggpo_start_session.
|
||||
*/
|
||||
GGPO_API GGPOErrorCode __cdecl ggpo_close_session(GGPOSession *);
|
||||
|
||||
|
||||
/*
|
||||
* ggpo_set_frame_delay --
|
||||
*
|
||||
* Change the amount of frames ggpo will delay local input. Must be called
|
||||
* before the first call to ggpo_synchronize_input.
|
||||
*/
|
||||
GGPO_API GGPOErrorCode __cdecl ggpo_set_frame_delay(GGPOSession *,
|
||||
GGPOPlayerHandle player,
|
||||
int frame_delay);
|
||||
|
||||
/*
|
||||
* ggpo_idle --
|
||||
* Should be called periodically by your application to give GGPO.net
|
||||
* a chance to do some work. Most packet transmissions and rollbacks occur
|
||||
* in ggpo_idle.
|
||||
*
|
||||
* timeout - The amount of time GGPO.net is allowed to spend in this function,
|
||||
* in milliseconds.
|
||||
*/
|
||||
GGPO_API GGPOErrorCode __cdecl ggpo_idle(GGPOSession *,
|
||||
int timeout);
|
||||
|
||||
/*
|
||||
* ggpo_add_local_input --
|
||||
*
|
||||
* Used to notify GGPO.net of inputs that should be trasmitted to remote
|
||||
* players. ggpo_add_local_input must be called once every frame for
|
||||
* all player of type GGPO_PLAYERTYPE_LOCAL.
|
||||
*
|
||||
* player - The player handle returned for this player when you called
|
||||
* ggpo_add_local_player.
|
||||
*
|
||||
* values - The controller inputs for this player.
|
||||
*
|
||||
* size - The size of the controller inputs. This must be exactly equal to the
|
||||
* size passed into ggpo_start_session.
|
||||
*/
|
||||
GGPO_API GGPOErrorCode __cdecl ggpo_add_local_input(GGPOSession *,
|
||||
GGPOPlayerHandle player,
|
||||
void *values,
|
||||
int size);
|
||||
|
||||
/*
|
||||
* ggpo_synchronize_input --
|
||||
*
|
||||
* You should call ggpo_synchronize_input before every frame of execution,
|
||||
* including those frames which happen during rollback.
|
||||
*
|
||||
* values - When the function returns, the values parameter will contain
|
||||
* inputs for this frame for all players. The values array must be at
|
||||
* least (size * players) large.
|
||||
*
|
||||
* size - The size of the values array.
|
||||
*
|
||||
* disconnect_flags - Indicated whether the input in slot (1 << flag) is
|
||||
* valid. If a player has disconnected, the input in the values array for
|
||||
* that player will be zeroed and the i-th flag will be set. For example,
|
||||
* if only player 3 has disconnected, disconnect flags will be 8 (i.e. 1 << 3).
|
||||
*/
|
||||
GGPO_API GGPOErrorCode __cdecl ggpo_synchronize_input(GGPOSession *,
|
||||
void *values,
|
||||
int size,
|
||||
int *disconnect_flags);
|
||||
|
||||
/*
|
||||
* ggpo_disconnect_player --
|
||||
*
|
||||
* Disconnects a remote player from a game. Will return GGPO_ERRORCODE_PLAYER_DISCONNECTED
|
||||
* if you try to disconnect a player who has already been disconnected.
|
||||
*/
|
||||
GGPO_API GGPOErrorCode __cdecl ggpo_disconnect_player(GGPOSession *,
|
||||
GGPOPlayerHandle player);
|
||||
|
||||
/*
|
||||
* ggpo_advance_frame --
|
||||
*
|
||||
* You should call ggpo_advance_frame to notify GGPO.net that you have
|
||||
* advanced your gamestate by a single frame. You should call this everytime
|
||||
* you advance the gamestate by a frame, even during rollbacks. GGPO.net
|
||||
* may call your save_state callback before this function returns.
|
||||
*/
|
||||
GGPO_API GGPOErrorCode __cdecl ggpo_advance_frame(GGPOSession *);
|
||||
|
||||
/*
|
||||
* ggpo_get_network_stats --
|
||||
*
|
||||
* Used to fetch some statistics about the quality of the network connection.
|
||||
*
|
||||
* player - The player handle returned from the ggpo_add_player function you used
|
||||
* to add the remote player.
|
||||
*
|
||||
* stats - Out parameter to the network statistics.
|
||||
*/
|
||||
GGPO_API GGPOErrorCode __cdecl ggpo_get_network_stats(GGPOSession *,
|
||||
GGPOPlayerHandle player,
|
||||
GGPONetworkStats *stats);
|
||||
|
||||
/*
|
||||
* ggpo_set_disconnect_timeout --
|
||||
*
|
||||
* Sets the disconnect timeout. The session will automatically disconnect
|
||||
* from a remote peer if it has not received a packet in the timeout window.
|
||||
* You will be notified of the disconnect via a GGPO_EVENTCODE_DISCONNECTED_FROM_PEER
|
||||
* event.
|
||||
*
|
||||
* Setting a timeout value of 0 will disable automatic disconnects.
|
||||
*
|
||||
* timeout - The time in milliseconds to wait before disconnecting a peer.
|
||||
*/
|
||||
GGPO_API GGPOErrorCode __cdecl ggpo_set_disconnect_timeout(GGPOSession *,
|
||||
int timeout);
|
||||
|
||||
/*
|
||||
* ggpo_set_disconnect_notify_start --
|
||||
*
|
||||
* The time to wait before the first GGPO_EVENTCODE_NETWORK_INTERRUPTED timeout
|
||||
* will be sent.
|
||||
*
|
||||
* timeout - The amount of time which needs to elapse without receiving a packet
|
||||
* before the GGPO_EVENTCODE_NETWORK_INTERRUPTED event is sent.
|
||||
*/
|
||||
GGPO_API GGPOErrorCode __cdecl ggpo_set_disconnect_notify_start(GGPOSession *,
|
||||
int timeout);
|
||||
|
||||
/*
|
||||
* ggpo_send_message --
|
||||
*
|
||||
* Sends application-specific data to other peers. The message is
|
||||
* delivered by the on_user_message callback.
|
||||
* If spectators is true, the message is also sent to spectators.
|
||||
*/
|
||||
GGPO_API GGPOErrorCode __cdecl ggpo_send_message(GGPOSession *,
|
||||
const void *msg,
|
||||
int len,
|
||||
bool spectators);
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,33 +0,0 @@
|
||||
/* -----------------------------------------------------------------------
|
||||
* GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC.
|
||||
*
|
||||
* Use of this software is governed by the MIT license that can be found
|
||||
* in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef _BACKEND_H
|
||||
#define _BACKEND_H
|
||||
|
||||
#include "../ggpo_types.h"
|
||||
#include "ggponet.h"
|
||||
|
||||
struct GGPOSession {
|
||||
virtual ~GGPOSession() { }
|
||||
virtual GGPOErrorCode DoPoll(int timeout) { return GGPO_OK; }
|
||||
virtual GGPOErrorCode AddPlayer(GGPOPlayer *player, GGPOPlayerHandle *handle) = 0;
|
||||
virtual GGPOErrorCode AddLocalInput(GGPOPlayerHandle player, void *values, int size) = 0;
|
||||
virtual GGPOErrorCode SyncInput(void *values, int size, int *disconnect_flags) = 0;
|
||||
virtual GGPOErrorCode IncrementFrame(void) { return GGPO_OK; }
|
||||
virtual GGPOErrorCode DisconnectPlayer(GGPOPlayerHandle handle) { return GGPO_OK; }
|
||||
virtual GGPOErrorCode GetNetworkStats(GGPONetworkStats *stats, GGPOPlayerHandle handle) { return GGPO_OK; }
|
||||
|
||||
virtual GGPOErrorCode SetFrameDelay(GGPOPlayerHandle player, int delay) { return GGPO_ERRORCODE_UNSUPPORTED; }
|
||||
virtual GGPOErrorCode SetDisconnectTimeout(int timeout) { return GGPO_ERRORCODE_UNSUPPORTED; }
|
||||
virtual GGPOErrorCode SetDisconnectNotifyStart(int timeout) { return GGPO_ERRORCODE_UNSUPPORTED; }
|
||||
virtual GGPOErrorCode SendMessage(const void *msg, int len, bool spectators) { return GGPO_ERRORCODE_UNSUPPORTED; }
|
||||
};
|
||||
|
||||
typedef struct GGPOSession Quark, IQuarkBackend; /* XXX: nuke this */
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,664 +0,0 @@
|
||||
/* -----------------------------------------------------------------------
|
||||
* GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC.
|
||||
*
|
||||
* Use of this software is governed by the MIT license that can be found
|
||||
* in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "p2p.h"
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
static const int RECOMMENDATION_INTERVAL = 240;
|
||||
static const int DEFAULT_DISCONNECT_TIMEOUT = 5000;
|
||||
static const int DEFAULT_DISCONNECT_NOTIFY_START = 750;
|
||||
|
||||
Peer2PeerBackend::Peer2PeerBackend(GGPOSessionCallbacks *cb,
|
||||
const char *gamename,
|
||||
uint16 localport,
|
||||
int num_players,
|
||||
int input_size,
|
||||
const void *verification,
|
||||
int verification_size) :
|
||||
_sync(_local_connect_status),
|
||||
_endpoints(nullptr),
|
||||
_num_spectators(0),
|
||||
_input_size(input_size),
|
||||
_num_players(num_players),
|
||||
_next_spectator_frame(0),
|
||||
_disconnect_timeout(DEFAULT_DISCONNECT_TIMEOUT),
|
||||
_disconnect_notify_start(DEFAULT_DISCONNECT_NOTIFY_START)
|
||||
{
|
||||
_callbacks = *cb;
|
||||
_synchronizing = true;
|
||||
_next_recommended_sleep = 0;
|
||||
|
||||
/*
|
||||
* Initialize the synchronziation layer
|
||||
*/
|
||||
Sync::Config config = { 0 };
|
||||
config.num_players = num_players;
|
||||
config.input_size = input_size;
|
||||
config.callbacks = _callbacks;
|
||||
config.num_prediction_frames = MAX_PREDICTION_FRAMES;
|
||||
_sync.Init(config);
|
||||
|
||||
/*
|
||||
* Initialize the UDP port
|
||||
*/
|
||||
_udp.Init(localport, &_poll, this);
|
||||
|
||||
_endpoints = new UdpProtocol[_num_players];
|
||||
for (int i = 0; i < _num_players; i++)
|
||||
_endpoints[i].SetVerificationData(verification, verification_size);
|
||||
memset(_local_connect_status, 0, sizeof(_local_connect_status));
|
||||
for (unsigned i = 0; i < ARRAY_SIZE(_local_connect_status); i++) {
|
||||
_local_connect_status[i].last_frame = -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Preload the ROM
|
||||
*/
|
||||
_callbacks.begin_game(gamename);
|
||||
}
|
||||
|
||||
Peer2PeerBackend::~Peer2PeerBackend()
|
||||
{
|
||||
delete [] _endpoints;
|
||||
}
|
||||
|
||||
void
|
||||
Peer2PeerBackend::AddRemotePlayer(char *ip,
|
||||
uint16 port,
|
||||
int queue)
|
||||
{
|
||||
/*
|
||||
* Start the state machine (xxx: no)
|
||||
*/
|
||||
_synchronizing = true;
|
||||
|
||||
_endpoints[queue].Init(&_udp, _poll, queue, ip, port, _local_connect_status);
|
||||
_endpoints[queue].SetDisconnectTimeout(_disconnect_timeout);
|
||||
_endpoints[queue].SetDisconnectNotifyStart(_disconnect_notify_start);
|
||||
_endpoints[queue].Synchronize();
|
||||
}
|
||||
|
||||
GGPOErrorCode Peer2PeerBackend::AddSpectator(char *ip,
|
||||
uint16 port)
|
||||
{
|
||||
if (_num_spectators == GGPO_MAX_SPECTATORS) {
|
||||
return GGPO_ERRORCODE_TOO_MANY_SPECTATORS;
|
||||
}
|
||||
/*
|
||||
* Currently, we can only add spectators before the game starts.
|
||||
*/
|
||||
if (!_synchronizing) {
|
||||
return GGPO_ERRORCODE_INVALID_REQUEST;
|
||||
}
|
||||
int queue = _num_spectators++;
|
||||
|
||||
_spectators[queue].Init(&_udp, _poll, queue + 1000, ip, port, _local_connect_status);
|
||||
_spectators[queue].SetDisconnectTimeout(_disconnect_timeout);
|
||||
_spectators[queue].SetDisconnectNotifyStart(_disconnect_notify_start);
|
||||
_spectators[queue].Synchronize();
|
||||
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
GGPOErrorCode
|
||||
Peer2PeerBackend::DoPoll(int timeout)
|
||||
{
|
||||
if (!_sync.InRollback()) {
|
||||
_poll.Pump(0);
|
||||
|
||||
PollUdpProtocolEvents();
|
||||
|
||||
if (!_synchronizing) {
|
||||
_sync.CheckSimulation(timeout);
|
||||
|
||||
// notify all of our endpoints of their local frame number for their
|
||||
// next connection quality report
|
||||
int current_frame = _sync.GetFrameCount();
|
||||
for (int i = 0; i < _num_players; i++) {
|
||||
_endpoints[i].SetLocalFrameNumber(current_frame);
|
||||
}
|
||||
|
||||
int total_min_confirmed;
|
||||
if (_num_players <= 2) {
|
||||
total_min_confirmed = Poll2Players(current_frame);
|
||||
} else {
|
||||
total_min_confirmed = PollNPlayers(current_frame);
|
||||
}
|
||||
|
||||
Log("last confirmed frame in p2p backend is %d.", total_min_confirmed);
|
||||
if (total_min_confirmed >= 0) {
|
||||
ASSERT(total_min_confirmed != INT_MAX);
|
||||
if (_num_spectators > 0) {
|
||||
while (_next_spectator_frame <= total_min_confirmed) {
|
||||
Log("pushing frame %d to spectators.", _next_spectator_frame);
|
||||
|
||||
GameInput input;
|
||||
input.frame = _next_spectator_frame;
|
||||
input.size = _input_size * _num_players;
|
||||
_sync.GetConfirmedInputs(input.bits, _input_size * _num_players, _next_spectator_frame);
|
||||
for (int i = 0; i < _num_spectators; i++) {
|
||||
_spectators[i].SendInput(input);
|
||||
}
|
||||
_next_spectator_frame++;
|
||||
}
|
||||
}
|
||||
Log("setting confirmed frame in sync to %d.", total_min_confirmed);
|
||||
_sync.SetLastConfirmedFrame(total_min_confirmed);
|
||||
}
|
||||
|
||||
// send timesync notifications if now is the proper time
|
||||
if (current_frame > _next_recommended_sleep) {
|
||||
int interval = 0;
|
||||
for (int i = 0; i < _num_players; i++) {
|
||||
interval = MAX(interval, _endpoints[i].RecommendFrameDelay());
|
||||
}
|
||||
|
||||
if (interval > 0) {
|
||||
GGPOEvent info;
|
||||
info.code = GGPO_EVENTCODE_TIMESYNC;
|
||||
info.u.timesync.frames_ahead = interval;
|
||||
_callbacks.on_event(&info);
|
||||
_next_recommended_sleep = current_frame + RECOMMENDATION_INTERVAL;
|
||||
}
|
||||
}
|
||||
// XXX: this is obviously a farce...
|
||||
if (timeout)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
int Peer2PeerBackend::Poll2Players(int current_frame)
|
||||
{
|
||||
int i;
|
||||
|
||||
// discard confirmed frames as appropriate
|
||||
int total_min_confirmed = MAX_INT;
|
||||
for (i = 0; i < _num_players; i++) {
|
||||
bool queue_connected = true;
|
||||
if (_endpoints[i].IsRunning()) {
|
||||
int ignore;
|
||||
queue_connected = _endpoints[i].GetPeerConnectStatus(i, &ignore);
|
||||
}
|
||||
if (!_local_connect_status[i].disconnected) {
|
||||
total_min_confirmed = MIN(_local_connect_status[i].last_frame, total_min_confirmed);
|
||||
}
|
||||
Log(" local endp: connected = %d, last_received = %d, total_min_confirmed = %d.", !_local_connect_status[i].disconnected, _local_connect_status[i].last_frame, total_min_confirmed);
|
||||
if (!queue_connected && !_local_connect_status[i].disconnected) {
|
||||
Log("disconnecting i %d by remote request.", i);
|
||||
DisconnectPlayerQueue(i, total_min_confirmed);
|
||||
}
|
||||
Log(" total_min_confirmed = %d.", total_min_confirmed);
|
||||
}
|
||||
return total_min_confirmed;
|
||||
}
|
||||
|
||||
int Peer2PeerBackend::PollNPlayers(int current_frame)
|
||||
{
|
||||
int i, queue, last_received;
|
||||
|
||||
// discard confirmed frames as appropriate
|
||||
int total_min_confirmed = MAX_INT;
|
||||
for (queue = 0; queue < _num_players; queue++) {
|
||||
bool queue_connected = true;
|
||||
int queue_min_confirmed = MAX_INT;
|
||||
Log("considering queue %d.", queue);
|
||||
for (i = 0; i < _num_players; i++) {
|
||||
// we're going to do a lot of logic here in consideration of endpoint i.
|
||||
// keep accumulating the minimum confirmed point for all n*n packets and
|
||||
// throw away the rest.
|
||||
if (_endpoints[i].IsRunning()) {
|
||||
bool connected = _endpoints[i].GetPeerConnectStatus(queue, &last_received);
|
||||
|
||||
queue_connected = queue_connected && connected;
|
||||
queue_min_confirmed = MIN(last_received, queue_min_confirmed);
|
||||
Log(" endpoint %d: connected = %d, last_received = %d, queue_min_confirmed = %d.", i, connected, last_received, queue_min_confirmed);
|
||||
} else {
|
||||
Log(" endpoint %d: ignoring... not running.", i);
|
||||
}
|
||||
}
|
||||
// merge in our local status only if we're still connected!
|
||||
if (!_local_connect_status[queue].disconnected) {
|
||||
queue_min_confirmed = MIN(_local_connect_status[queue].last_frame, queue_min_confirmed);
|
||||
}
|
||||
Log(" local endp: connected = %d, last_received = %d, queue_min_confirmed = %d.", !_local_connect_status[queue].disconnected, _local_connect_status[queue].last_frame, queue_min_confirmed);
|
||||
|
||||
if (queue_connected) {
|
||||
total_min_confirmed = MIN(queue_min_confirmed, total_min_confirmed);
|
||||
} else {
|
||||
// check to see if this disconnect notification is further back than we've been before. If
|
||||
// so, we need to re-adjust. This can happen when we detect our own disconnect at frame n
|
||||
// and later receive a disconnect notification for frame n-1.
|
||||
if (!_local_connect_status[queue].disconnected || _local_connect_status[queue].last_frame > queue_min_confirmed) {
|
||||
Log("disconnecting queue %d by remote request.", queue);
|
||||
DisconnectPlayerQueue(queue, queue_min_confirmed);
|
||||
}
|
||||
}
|
||||
Log(" total_min_confirmed = %d.", total_min_confirmed);
|
||||
}
|
||||
return total_min_confirmed;
|
||||
}
|
||||
|
||||
|
||||
GGPOErrorCode
|
||||
Peer2PeerBackend::AddPlayer(GGPOPlayer *player,
|
||||
GGPOPlayerHandle *handle)
|
||||
{
|
||||
if (player->type == GGPO_PLAYERTYPE_SPECTATOR) {
|
||||
return AddSpectator(player->u.remote.ip_address, player->u.remote.port);
|
||||
}
|
||||
|
||||
int queue = player->player_num - 1;
|
||||
if (player->player_num < 1 || player->player_num > _num_players) {
|
||||
return GGPO_ERRORCODE_PLAYER_OUT_OF_RANGE;
|
||||
}
|
||||
*handle = QueueToPlayerHandle(queue);
|
||||
|
||||
if (player->type == GGPO_PLAYERTYPE_REMOTE) {
|
||||
AddRemotePlayer(player->u.remote.ip_address, player->u.remote.port, queue);
|
||||
}
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
GGPOErrorCode
|
||||
Peer2PeerBackend::AddLocalInput(GGPOPlayerHandle player,
|
||||
void *values,
|
||||
int size)
|
||||
{
|
||||
int queue;
|
||||
GameInput input;
|
||||
GGPOErrorCode result;
|
||||
|
||||
if (_sync.InRollback()) {
|
||||
return GGPO_ERRORCODE_IN_ROLLBACK;
|
||||
}
|
||||
if (_synchronizing) {
|
||||
return GGPO_ERRORCODE_NOT_SYNCHRONIZED;
|
||||
}
|
||||
|
||||
result = PlayerHandleToQueue(player, &queue);
|
||||
if (!GGPO_SUCCEEDED(result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
input.init(-1, (char *)values, size);
|
||||
|
||||
// Feed the input for the current frame into the synchronzation layer.
|
||||
if (!_sync.AddLocalInput(queue, input)) {
|
||||
return GGPO_ERRORCODE_PREDICTION_THRESHOLD;
|
||||
}
|
||||
|
||||
if (input.frame != GameInput::NullFrame) { // xxx: <- comment why this is the case
|
||||
// Update the local connect status state to indicate that we've got a
|
||||
// confirmed local frame for this player. this must come first so it
|
||||
// gets incorporated into the next packet we send.
|
||||
|
||||
Log("setting local connect status for local queue %d to %d", queue, input.frame);
|
||||
_local_connect_status[queue].last_frame = input.frame;
|
||||
|
||||
// Send the input to all the remote players.
|
||||
for (int i = 0; i < _num_players; i++) {
|
||||
if (_endpoints[i].IsInitialized()) {
|
||||
_endpoints[i].SendInput(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
|
||||
GGPOErrorCode
|
||||
Peer2PeerBackend::SyncInput(void *values,
|
||||
int size,
|
||||
int *disconnect_flags)
|
||||
{
|
||||
int flags;
|
||||
|
||||
// Wait until we've started to return inputs.
|
||||
if (_synchronizing) {
|
||||
return GGPO_ERRORCODE_NOT_SYNCHRONIZED;
|
||||
}
|
||||
flags = _sync.SynchronizeInputs(values, size);
|
||||
if (disconnect_flags) {
|
||||
*disconnect_flags = flags;
|
||||
}
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
GGPOErrorCode
|
||||
Peer2PeerBackend::IncrementFrame(void)
|
||||
{
|
||||
Log("End of frame (%d)...", _sync.GetFrameCount());
|
||||
_sync.IncrementFrame();
|
||||
DoPoll(0);
|
||||
PollSyncEvents();
|
||||
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Peer2PeerBackend::PollSyncEvents(void)
|
||||
{
|
||||
Sync::Event e;
|
||||
while (_sync.GetEvent(e)) {
|
||||
OnSyncEvent(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
Peer2PeerBackend::PollUdpProtocolEvents(void)
|
||||
{
|
||||
UdpProtocol::Event evt;
|
||||
for (int i = 0; i < _num_players; i++) {
|
||||
while (_endpoints[i].GetEvent(evt)) {
|
||||
OnUdpProtocolPeerEvent(evt, i);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < _num_spectators; i++) {
|
||||
while (_spectators[i].GetEvent(evt)) {
|
||||
OnUdpProtocolSpectatorEvent(evt, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Peer2PeerBackend::OnUdpProtocolPeerEvent(UdpProtocol::Event &evt, int queue)
|
||||
{
|
||||
OnUdpProtocolEvent(evt, QueueToPlayerHandle(queue));
|
||||
switch (evt.type) {
|
||||
case UdpProtocol::Event::Input:
|
||||
if (!_local_connect_status[queue].disconnected) {
|
||||
int current_remote_frame = _local_connect_status[queue].last_frame;
|
||||
int new_remote_frame = evt.u.input.input.frame;
|
||||
ASSERT(current_remote_frame == -1 || new_remote_frame == (current_remote_frame + 1));
|
||||
|
||||
_sync.AddRemoteInput(queue, evt.u.input.input);
|
||||
// Notify the other endpoints which frame we received from a peer
|
||||
Log("setting remote connect status for queue %d to %d", queue, evt.u.input.input.frame);
|
||||
_local_connect_status[queue].last_frame = evt.u.input.input.frame;
|
||||
}
|
||||
break;
|
||||
|
||||
case UdpProtocol::Event::Disconnected:
|
||||
DisconnectPlayer(QueueToPlayerHandle(queue));
|
||||
break;
|
||||
|
||||
case UdpProtocol::Event::AppData:
|
||||
if (evt.u.app_data.spectators)
|
||||
for (int i = 0; i < _num_spectators; i++)
|
||||
if (_spectators[i].IsInitialized())
|
||||
_spectators[i].SendAppData(evt.u.app_data.data, evt.u.app_data.size, true);
|
||||
Log("calling on_message callback with %d bytes", evt.u.app_data.size);
|
||||
if (_callbacks.on_message != nullptr)
|
||||
_callbacks.on_message(evt.u.app_data.data, evt.u.app_data.size);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Peer2PeerBackend::OnUdpProtocolSpectatorEvent(UdpProtocol::Event &evt, int queue)
|
||||
{
|
||||
GGPOPlayerHandle handle = QueueToSpectatorHandle(queue);
|
||||
OnUdpProtocolEvent(evt, handle);
|
||||
|
||||
if (evt.type == UdpProtocol::Event::Disconnected)
|
||||
{
|
||||
_spectators[queue].Disconnect();
|
||||
|
||||
GGPOEvent info;
|
||||
info.code = GGPO_EVENTCODE_DISCONNECTED_FROM_PEER;
|
||||
info.u.disconnected.player = handle;
|
||||
_callbacks.on_event(&info);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Peer2PeerBackend::OnUdpProtocolEvent(UdpProtocol::Event &evt, GGPOPlayerHandle handle)
|
||||
{
|
||||
GGPOEvent info;
|
||||
|
||||
switch (evt.type) {
|
||||
case UdpProtocol::Event::Connected:
|
||||
info.code = GGPO_EVENTCODE_CONNECTED_TO_PEER;
|
||||
info.u.connected.player = handle;
|
||||
_callbacks.on_event(&info);
|
||||
break;
|
||||
case UdpProtocol::Event::Synchronizing:
|
||||
info.code = GGPO_EVENTCODE_SYNCHRONIZING_WITH_PEER;
|
||||
info.u.synchronizing.player = handle;
|
||||
info.u.synchronizing.count = evt.u.synchronizing.count;
|
||||
info.u.synchronizing.total = evt.u.synchronizing.total;
|
||||
_callbacks.on_event(&info);
|
||||
break;
|
||||
case UdpProtocol::Event::Synchronzied:
|
||||
info.code = GGPO_EVENTCODE_SYNCHRONIZED_WITH_PEER;
|
||||
info.u.synchronized.player = handle;
|
||||
_callbacks.on_event(&info);
|
||||
|
||||
CheckInitialSync();
|
||||
break;
|
||||
|
||||
case UdpProtocol::Event::NetworkInterrupted:
|
||||
info.code = GGPO_EVENTCODE_CONNECTION_INTERRUPTED;
|
||||
info.u.connection_interrupted.player = handle;
|
||||
info.u.connection_interrupted.disconnect_timeout = evt.u.network_interrupted.disconnect_timeout;
|
||||
_callbacks.on_event(&info);
|
||||
break;
|
||||
|
||||
case UdpProtocol::Event::NetworkResumed:
|
||||
info.code = GGPO_EVENTCODE_CONNECTION_RESUMED;
|
||||
info.u.connection_resumed.player = handle;
|
||||
_callbacks.on_event(&info);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Called only as the result of a local decision to disconnect. The remote
|
||||
* decisions to disconnect are a result of us parsing the peer_connect_settings
|
||||
* blob in every endpoint periodically.
|
||||
*/
|
||||
GGPOErrorCode
|
||||
Peer2PeerBackend::DisconnectPlayer(GGPOPlayerHandle player)
|
||||
{
|
||||
int queue;
|
||||
GGPOErrorCode result;
|
||||
|
||||
result = PlayerHandleToQueue(player, &queue);
|
||||
if (!GGPO_SUCCEEDED(result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (_local_connect_status[queue].disconnected) {
|
||||
return GGPO_ERRORCODE_PLAYER_DISCONNECTED;
|
||||
}
|
||||
|
||||
if (!_endpoints[queue].IsInitialized()) {
|
||||
int current_frame = _sync.GetFrameCount();
|
||||
// xxx: we should be tracking who the local player is, but for now assume
|
||||
// that if the endpoint is not initalized, this must be the local player.
|
||||
Log("Disconnecting local player %d at frame %d by user request.", queue, _local_connect_status[queue].last_frame);
|
||||
for (int i = 0; i < _num_players; i++) {
|
||||
if (_endpoints[i].IsInitialized()) {
|
||||
DisconnectPlayerQueue(i, current_frame);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log("Disconnecting queue %d at frame %d by user request.", queue, _local_connect_status[queue].last_frame);
|
||||
DisconnectPlayerQueue(queue, _local_connect_status[queue].last_frame);
|
||||
}
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
void
|
||||
Peer2PeerBackend::DisconnectPlayerQueue(int queue, int syncto)
|
||||
{
|
||||
GGPOEvent info;
|
||||
int framecount = _sync.GetFrameCount();
|
||||
|
||||
_endpoints[queue].Disconnect();
|
||||
|
||||
Log("Changing queue %d local connect status for last frame from %d to %d on disconnect request (current: %d).",
|
||||
queue, _local_connect_status[queue].last_frame, syncto, framecount);
|
||||
|
||||
_local_connect_status[queue].disconnected = 1;
|
||||
_local_connect_status[queue].last_frame = syncto;
|
||||
|
||||
if (syncto != GameInput::NullFrame && syncto < framecount) {
|
||||
Log("adjusting simulation to account for the fact that %d disconnected @ %d.", queue, syncto);
|
||||
_sync.AdjustSimulation(syncto);
|
||||
Log("finished adjusting simulation.");
|
||||
}
|
||||
|
||||
info.code = GGPO_EVENTCODE_DISCONNECTED_FROM_PEER;
|
||||
info.u.disconnected.player = QueueToPlayerHandle(queue);
|
||||
_callbacks.on_event(&info);
|
||||
|
||||
CheckInitialSync();
|
||||
}
|
||||
|
||||
|
||||
GGPOErrorCode
|
||||
Peer2PeerBackend::GetNetworkStats(GGPONetworkStats *stats, GGPOPlayerHandle player)
|
||||
{
|
||||
int queue;
|
||||
GGPOErrorCode result;
|
||||
|
||||
result = PlayerHandleToQueue(player, &queue);
|
||||
if (!GGPO_SUCCEEDED(result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
memset(stats, 0, sizeof *stats);
|
||||
_endpoints[queue].GetNetworkStats(stats);
|
||||
stats->sync.predicted_frames = _sync.GetPredictedFrames();
|
||||
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
GGPOErrorCode
|
||||
Peer2PeerBackend::SetFrameDelay(GGPOPlayerHandle player, int delay)
|
||||
{
|
||||
int queue;
|
||||
GGPOErrorCode result;
|
||||
|
||||
result = PlayerHandleToQueue(player, &queue);
|
||||
if (!GGPO_SUCCEEDED(result)) {
|
||||
return result;
|
||||
}
|
||||
_sync.SetFrameDelay(queue, delay);
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
GGPOErrorCode
|
||||
Peer2PeerBackend::SetDisconnectTimeout(int timeout)
|
||||
{
|
||||
_disconnect_timeout = timeout;
|
||||
for (int i = 0; i < _num_players; i++) {
|
||||
if (_endpoints[i].IsInitialized()) {
|
||||
_endpoints[i].SetDisconnectTimeout(_disconnect_timeout);
|
||||
}
|
||||
}
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
GGPOErrorCode
|
||||
Peer2PeerBackend::SetDisconnectNotifyStart(int timeout)
|
||||
{
|
||||
_disconnect_notify_start = timeout;
|
||||
for (int i = 0; i < _num_players; i++) {
|
||||
if (_endpoints[i].IsInitialized()) {
|
||||
_endpoints[i].SetDisconnectNotifyStart(_disconnect_notify_start);
|
||||
}
|
||||
}
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
GGPOErrorCode
|
||||
Peer2PeerBackend::PlayerHandleToQueue(GGPOPlayerHandle player, int *queue)
|
||||
{
|
||||
int offset = ((int)player - 1);
|
||||
if (offset < 0 || offset >= _num_players) {
|
||||
return GGPO_ERRORCODE_INVALID_PLAYER_HANDLE;
|
||||
}
|
||||
*queue = offset;
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Peer2PeerBackend::OnMsg(sockaddr_in &from, UdpMsg *msg, int len)
|
||||
{
|
||||
for (int i = 0; i < _num_players; i++) {
|
||||
if (_endpoints[i].HandlesMsg(from, msg)) {
|
||||
_endpoints[i].OnMsg(msg, len);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < _num_spectators; i++) {
|
||||
if (_spectators[i].HandlesMsg(from, msg)) {
|
||||
_spectators[i].OnMsg(msg, len);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Peer2PeerBackend::CheckInitialSync()
|
||||
{
|
||||
int i;
|
||||
|
||||
if (_synchronizing) {
|
||||
// Check to see if everyone is now synchronized. If so,
|
||||
// go ahead and tell the client that we're ok to accept input.
|
||||
for (i = 0; i < _num_players; i++) {
|
||||
// xxx: IsInitialized() must go... we're actually using it as a proxy for "represents the local player"
|
||||
if (_endpoints[i].IsInitialized() && !_endpoints[i].IsSynchronized() && !_local_connect_status[i].disconnected) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < _num_spectators; i++) {
|
||||
if (_spectators[i].IsInitialized() && !_spectators[i].IsSynchronized()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
GGPOEvent info;
|
||||
info.code = GGPO_EVENTCODE_RUNNING;
|
||||
_callbacks.on_event(&info);
|
||||
_synchronizing = false;
|
||||
}
|
||||
}
|
||||
|
||||
GGPOErrorCode Peer2PeerBackend::SendMessage(const void *msg, int len, bool spectators)
|
||||
{
|
||||
if (_synchronizing)
|
||||
return GGPO_ERRORCODE_NOT_SYNCHRONIZED;
|
||||
if (len > MAX_APPDATA_SIZE)
|
||||
return GGPO_ERRORCODE_INVALID_REQUEST;
|
||||
for (int i = 0; i < _num_players; i++)
|
||||
if (_endpoints[i].IsInitialized())
|
||||
_endpoints[i].SendAppData(msg, len, spectators);
|
||||
if (spectators)
|
||||
for (int i = 0; i < _num_spectators; i++)
|
||||
if (_spectators[i].IsInitialized())
|
||||
_spectators[i].SendAppData(msg, len, spectators);
|
||||
|
||||
return GGPO_ERRORCODE_SUCCESS;
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/* -----------------------------------------------------------------------
|
||||
* GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC.
|
||||
*
|
||||
* Use of this software is governed by the MIT license that can be found
|
||||
* in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef _P2P_H
|
||||
#define _P2P_H
|
||||
|
||||
#include "ggpo_types.h"
|
||||
#include "ggpo_poll.h"
|
||||
#include "sync.h"
|
||||
#include "backend.h"
|
||||
#include "timesync.h"
|
||||
#include "network/udp_proto.h"
|
||||
|
||||
class Peer2PeerBackend : public IQuarkBackend, IPollSink, Udp::Callbacks {
|
||||
public:
|
||||
Peer2PeerBackend(GGPOSessionCallbacks *cb, const char *gamename, uint16 localport, int num_players, int input_size,
|
||||
const void *verification, int verification_size);
|
||||
virtual ~Peer2PeerBackend();
|
||||
|
||||
|
||||
public:
|
||||
GGPOErrorCode DoPoll(int timeout) override;
|
||||
GGPOErrorCode AddPlayer(GGPOPlayer *player, GGPOPlayerHandle *handle) override;
|
||||
GGPOErrorCode AddLocalInput(GGPOPlayerHandle player, void *values, int size) override;
|
||||
GGPOErrorCode SyncInput(void *values, int size, int *disconnect_flags) override;
|
||||
GGPOErrorCode IncrementFrame(void) override;
|
||||
GGPOErrorCode DisconnectPlayer(GGPOPlayerHandle handle) override;
|
||||
GGPOErrorCode GetNetworkStats(GGPONetworkStats *stats, GGPOPlayerHandle handle) override;
|
||||
GGPOErrorCode SetFrameDelay(GGPOPlayerHandle player, int delay) override;
|
||||
GGPOErrorCode SetDisconnectTimeout(int timeout) override;
|
||||
GGPOErrorCode SetDisconnectNotifyStart(int timeout) override;
|
||||
GGPOErrorCode SendMessage(const void *msg, int len, bool spectators) override;
|
||||
|
||||
public:
|
||||
void OnMsg(sockaddr_in &from, UdpMsg *msg, int len) override;
|
||||
|
||||
protected:
|
||||
GGPOErrorCode PlayerHandleToQueue(GGPOPlayerHandle player, int *queue);
|
||||
GGPOPlayerHandle QueueToPlayerHandle(int queue) { return (GGPOPlayerHandle)(queue + 1); }
|
||||
GGPOPlayerHandle QueueToSpectatorHandle(int queue) { return (GGPOPlayerHandle)(queue + 1000); } /* out of range of the player array, basically */
|
||||
void DisconnectPlayerQueue(int queue, int syncto);
|
||||
void PollSyncEvents(void);
|
||||
void PollUdpProtocolEvents(void);
|
||||
void CheckInitialSync(void);
|
||||
int Poll2Players(int current_frame);
|
||||
int PollNPlayers(int current_frame);
|
||||
void AddRemotePlayer(char *remoteip, uint16 reportport, int queue);
|
||||
GGPOErrorCode AddSpectator(char *remoteip, uint16 reportport);
|
||||
virtual void OnSyncEvent(Sync::Event &e) { }
|
||||
virtual void OnUdpProtocolEvent(UdpProtocol::Event &e, GGPOPlayerHandle handle);
|
||||
virtual void OnUdpProtocolPeerEvent(UdpProtocol::Event &e, int queue);
|
||||
virtual void OnUdpProtocolSpectatorEvent(UdpProtocol::Event &e, int queue);
|
||||
|
||||
protected:
|
||||
GGPOSessionCallbacks _callbacks;
|
||||
Poll _poll;
|
||||
Sync _sync;
|
||||
Udp _udp;
|
||||
UdpProtocol *_endpoints;
|
||||
UdpProtocol _spectators[GGPO_MAX_SPECTATORS];
|
||||
int _num_spectators;
|
||||
int _input_size;
|
||||
|
||||
bool _synchronizing;
|
||||
int _num_players;
|
||||
int _next_recommended_sleep;
|
||||
|
||||
int _next_spectator_frame;
|
||||
int _disconnect_timeout;
|
||||
int _disconnect_notify_start;
|
||||
|
||||
UdpMsg::connect_status _local_connect_status[UDP_MSG_MAX_PLAYERS];
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,189 +0,0 @@
|
||||
/* -----------------------------------------------------------------------
|
||||
* GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC.
|
||||
*
|
||||
* Use of this software is governed by the MIT license that can be found
|
||||
* in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "spectator.h"
|
||||
|
||||
SpectatorBackend::SpectatorBackend(GGPOSessionCallbacks *cb,
|
||||
const char* gamename,
|
||||
uint16 localport,
|
||||
int num_players,
|
||||
int input_size,
|
||||
char *hostip,
|
||||
u_short hostport,
|
||||
const void *verification,
|
||||
int verification_size) :
|
||||
_input_size(input_size),
|
||||
_num_players(num_players),
|
||||
_next_input_to_send(0)
|
||||
{
|
||||
_callbacks = *cb;
|
||||
_synchronizing = true;
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(_inputs); i++) {
|
||||
_inputs[i].frame = -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the UDP port
|
||||
*/
|
||||
_udp.Init(localport, &_poll, this);
|
||||
|
||||
/*
|
||||
* Init the host endpoint
|
||||
*/
|
||||
_host.SetVerificationData(verification, verification_size);
|
||||
_host.Init(&_udp, _poll, 0, hostip, hostport, NULL);
|
||||
_host.Synchronize();
|
||||
|
||||
/*
|
||||
* Preload the ROM
|
||||
*/
|
||||
_callbacks.begin_game(gamename);
|
||||
}
|
||||
|
||||
SpectatorBackend::~SpectatorBackend()
|
||||
{
|
||||
}
|
||||
|
||||
GGPOErrorCode
|
||||
SpectatorBackend::DoPoll(int timeout)
|
||||
{
|
||||
_poll.Pump(0);
|
||||
|
||||
PollUdpProtocolEvents();
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
GGPOErrorCode
|
||||
SpectatorBackend::SyncInput(void *values,
|
||||
int size,
|
||||
int *disconnect_flags)
|
||||
{
|
||||
// Wait until we've started to return inputs.
|
||||
if (_synchronizing) {
|
||||
return GGPO_ERRORCODE_NOT_SYNCHRONIZED;
|
||||
}
|
||||
|
||||
GameInput &input = _inputs[_next_input_to_send % SPECTATOR_FRAME_BUFFER_SIZE];
|
||||
if (input.frame < _next_input_to_send) {
|
||||
// Haven't received the input from the host yet. Wait
|
||||
return GGPO_ERRORCODE_PREDICTION_THRESHOLD;
|
||||
}
|
||||
if (input.frame > _next_input_to_send) {
|
||||
// The host is way way way far ahead of the spectator. How'd this
|
||||
// happen? Anyway, the input we need is gone forever.
|
||||
return GGPO_ERRORCODE_GENERAL_FAILURE;
|
||||
}
|
||||
|
||||
ASSERT(size >= _input_size * _num_players);
|
||||
memcpy(values, input.bits, _input_size * _num_players);
|
||||
if (disconnect_flags) {
|
||||
*disconnect_flags = 0; // xxx: should get them from the host!
|
||||
}
|
||||
_next_input_to_send++;
|
||||
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
GGPOErrorCode
|
||||
SpectatorBackend::IncrementFrame(void)
|
||||
{
|
||||
Log("End of frame (%d)...", _next_input_to_send - 1);
|
||||
DoPoll(0);
|
||||
PollUdpProtocolEvents();
|
||||
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
void
|
||||
SpectatorBackend::PollUdpProtocolEvents(void)
|
||||
{
|
||||
UdpProtocol::Event evt;
|
||||
while (_host.GetEvent(evt)) {
|
||||
OnUdpProtocolEvent(evt);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SpectatorBackend::OnUdpProtocolEvent(UdpProtocol::Event &evt)
|
||||
{
|
||||
GGPOEvent info;
|
||||
|
||||
switch (evt.type) {
|
||||
case UdpProtocol::Event::Connected:
|
||||
info.code = GGPO_EVENTCODE_CONNECTED_TO_PEER;
|
||||
info.u.connected.player = 0;
|
||||
_callbacks.on_event(&info);
|
||||
break;
|
||||
case UdpProtocol::Event::Synchronizing:
|
||||
info.code = GGPO_EVENTCODE_SYNCHRONIZING_WITH_PEER;
|
||||
info.u.synchronizing.player = 0;
|
||||
info.u.synchronizing.count = evt.u.synchronizing.count;
|
||||
info.u.synchronizing.total = evt.u.synchronizing.total;
|
||||
_callbacks.on_event(&info);
|
||||
break;
|
||||
case UdpProtocol::Event::Synchronzied:
|
||||
if (_synchronizing) {
|
||||
info.code = GGPO_EVENTCODE_SYNCHRONIZED_WITH_PEER;
|
||||
info.u.synchronized.player = 0;
|
||||
_callbacks.on_event(&info);
|
||||
|
||||
info.code = GGPO_EVENTCODE_RUNNING;
|
||||
_callbacks.on_event(&info);
|
||||
_synchronizing = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case UdpProtocol::Event::NetworkInterrupted:
|
||||
info.code = GGPO_EVENTCODE_CONNECTION_INTERRUPTED;
|
||||
info.u.connection_interrupted.player = 0;
|
||||
info.u.connection_interrupted.disconnect_timeout = evt.u.network_interrupted.disconnect_timeout;
|
||||
_callbacks.on_event(&info);
|
||||
break;
|
||||
|
||||
case UdpProtocol::Event::NetworkResumed:
|
||||
info.code = GGPO_EVENTCODE_CONNECTION_RESUMED;
|
||||
info.u.connection_resumed.player = 0;
|
||||
_callbacks.on_event(&info);
|
||||
break;
|
||||
|
||||
case UdpProtocol::Event::Disconnected:
|
||||
info.code = GGPO_EVENTCODE_DISCONNECTED_FROM_PEER;
|
||||
info.u.disconnected.player = 0;
|
||||
_callbacks.on_event(&info);
|
||||
break;
|
||||
|
||||
case UdpProtocol::Event::Input:
|
||||
{
|
||||
GameInput& input = evt.u.input.input;
|
||||
|
||||
_host.SetLocalFrameNumber(input.frame);
|
||||
_host.SendInputAck();
|
||||
_inputs[input.frame % SPECTATOR_FRAME_BUFFER_SIZE] = input;
|
||||
}
|
||||
break;
|
||||
|
||||
case UdpProtocol::Event::AppData:
|
||||
Log("calling on_message callback with %d bytes", evt.u.app_data.size);
|
||||
if (_callbacks.on_message != nullptr)
|
||||
_callbacks.on_message(evt.u.app_data.data, evt.u.app_data.size);
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SpectatorBackend::OnMsg(sockaddr_in &from, UdpMsg *msg, int len)
|
||||
{
|
||||
if (_host.HandlesMsg(from, msg)) {
|
||||
_host.OnMsg(msg, len);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/* -----------------------------------------------------------------------
|
||||
* GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC.
|
||||
*
|
||||
* Use of this software is governed by the MIT license that can be found
|
||||
* in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef _SPECTATOR_H
|
||||
#define _SPECTATOR_H
|
||||
|
||||
#include "../ggpo_poll.h"
|
||||
#include "../ggpo_types.h"
|
||||
#include "sync.h"
|
||||
#include "backend.h"
|
||||
#include "timesync.h"
|
||||
#include "network/udp_proto.h"
|
||||
|
||||
#define SPECTATOR_FRAME_BUFFER_SIZE 64
|
||||
|
||||
class SpectatorBackend : public IQuarkBackend, IPollSink, Udp::Callbacks {
|
||||
public:
|
||||
SpectatorBackend(GGPOSessionCallbacks *cb, const char *gamename, uint16 localport, int num_players, int input_size, char *hostip, u_short hostport,
|
||||
const void *verification, int verification_size);
|
||||
virtual ~SpectatorBackend();
|
||||
|
||||
|
||||
public:
|
||||
GGPOErrorCode DoPoll(int timeout) override;
|
||||
GGPOErrorCode AddPlayer(GGPOPlayer *player, GGPOPlayerHandle *handle) override { return GGPO_ERRORCODE_UNSUPPORTED; }
|
||||
GGPOErrorCode AddLocalInput(GGPOPlayerHandle player, void *values, int size) override { return GGPO_OK; }
|
||||
GGPOErrorCode SyncInput(void *values, int size, int *disconnect_flags) override;
|
||||
GGPOErrorCode IncrementFrame(void) override;
|
||||
GGPOErrorCode DisconnectPlayer(GGPOPlayerHandle handle) override { return GGPO_ERRORCODE_UNSUPPORTED; }
|
||||
GGPOErrorCode GetNetworkStats(GGPONetworkStats *stats, GGPOPlayerHandle handle) override { return GGPO_ERRORCODE_UNSUPPORTED; }
|
||||
GGPOErrorCode SetFrameDelay(GGPOPlayerHandle player, int delay) override { return GGPO_ERRORCODE_UNSUPPORTED; }
|
||||
GGPOErrorCode SetDisconnectTimeout(int timeout) override { return GGPO_ERRORCODE_UNSUPPORTED; }
|
||||
GGPOErrorCode SetDisconnectNotifyStart(int timeout) override { return GGPO_ERRORCODE_UNSUPPORTED; }
|
||||
|
||||
public:
|
||||
void OnMsg(sockaddr_in &from, UdpMsg *msg, int len) override;
|
||||
|
||||
protected:
|
||||
void PollUdpProtocolEvents(void);
|
||||
void CheckInitialSync(void);
|
||||
|
||||
void OnUdpProtocolEvent(UdpProtocol::Event &e);
|
||||
|
||||
protected:
|
||||
GGPOSessionCallbacks _callbacks;
|
||||
Poll _poll;
|
||||
Udp _udp;
|
||||
UdpProtocol _host;
|
||||
bool _synchronizing;
|
||||
int _input_size;
|
||||
int _num_players;
|
||||
int _next_input_to_send;
|
||||
GameInput _inputs[SPECTATOR_FRAME_BUFFER_SIZE];
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,184 +0,0 @@
|
||||
/* -----------------------------------------------------------------------
|
||||
* GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC.
|
||||
*
|
||||
* Use of this software is governed by the MIT license that can be found
|
||||
* in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "synctest.h"
|
||||
|
||||
SyncTestBackend::SyncTestBackend(GGPOSessionCallbacks *cb,
|
||||
const char *gamename,
|
||||
int frames,
|
||||
int num_players) :
|
||||
_sync(NULL)
|
||||
{
|
||||
_callbacks = *cb;
|
||||
_num_players = num_players;
|
||||
_check_distance = frames;
|
||||
_last_verified = 0;
|
||||
_rollingback = false;
|
||||
_running = false;
|
||||
_logfp = NULL;
|
||||
_current_input.erase();
|
||||
strcpy(_game, gamename);
|
||||
|
||||
/*
|
||||
* Initialize the synchronziation layer
|
||||
*/
|
||||
Sync::Config config = { 0 };
|
||||
config.callbacks = _callbacks;
|
||||
config.num_prediction_frames = MAX_PREDICTION_FRAMES;
|
||||
_sync.Init(config);
|
||||
|
||||
/*
|
||||
* Preload the ROM
|
||||
*/
|
||||
_callbacks.begin_game(gamename);
|
||||
}
|
||||
|
||||
SyncTestBackend::~SyncTestBackend()
|
||||
{
|
||||
}
|
||||
|
||||
GGPOErrorCode
|
||||
SyncTestBackend::DoPoll(int timeout)
|
||||
{
|
||||
if (!_running) {
|
||||
GGPOEvent info;
|
||||
|
||||
info.code = GGPO_EVENTCODE_RUNNING;
|
||||
_callbacks.on_event(&info);
|
||||
_running = true;
|
||||
}
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
GGPOErrorCode
|
||||
SyncTestBackend::AddPlayer(GGPOPlayer *player, GGPOPlayerHandle *handle)
|
||||
{
|
||||
if (player->player_num < 1 || player->player_num > _num_players) {
|
||||
return GGPO_ERRORCODE_PLAYER_OUT_OF_RANGE;
|
||||
}
|
||||
*handle = (GGPOPlayerHandle)(player->player_num - 1);
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
GGPOErrorCode
|
||||
SyncTestBackend::AddLocalInput(GGPOPlayerHandle player, void *values, int size)
|
||||
{
|
||||
if (!_running) {
|
||||
return GGPO_ERRORCODE_NOT_SYNCHRONIZED;
|
||||
}
|
||||
|
||||
int index = (int)player;
|
||||
for (int i = 0; i < size; i++) {
|
||||
_current_input.bits[(index * size) + i] |= ((char *)values)[i];
|
||||
}
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
GGPOErrorCode
|
||||
SyncTestBackend::SyncInput(void *values,
|
||||
int size,
|
||||
int *disconnect_flags)
|
||||
{
|
||||
if (_rollingback) {
|
||||
_last_input = _saved_frames.front().input;
|
||||
} else {
|
||||
if (_sync.GetFrameCount() == 0 && _sync.GetLastSavedFrame().buf == nullptr) {
|
||||
_sync.SaveCurrentFrame();
|
||||
}
|
||||
_last_input = _current_input;
|
||||
}
|
||||
memcpy(values, _last_input.bits, size);
|
||||
if (disconnect_flags) {
|
||||
*disconnect_flags = 0;
|
||||
}
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
GGPOErrorCode
|
||||
SyncTestBackend::IncrementFrame(void)
|
||||
{
|
||||
_sync.IncrementFrame();
|
||||
_current_input.erase();
|
||||
|
||||
DEBUG_LOG(NETWORK, "End of frame(%d)...", _sync.GetFrameCount());
|
||||
|
||||
if (_rollingback) {
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
int frame = _sync.GetFrameCount();
|
||||
// Hold onto the current frame in our queue of saved states. We'll need
|
||||
// the checksum later to verify that our replay of the same frame got the
|
||||
// same results.
|
||||
SavedInfo info;
|
||||
info.frame = frame;
|
||||
info.input = _last_input;
|
||||
info.cbuf = _sync.GetLastSavedFrame().cbuf;
|
||||
info.buf = (char *)malloc(info.cbuf);
|
||||
memcpy(info.buf, _sync.GetLastSavedFrame().buf, info.cbuf);
|
||||
info.checksum = _sync.GetLastSavedFrame().checksum;
|
||||
_saved_frames.push(info);
|
||||
|
||||
if (frame - _last_verified == _check_distance) {
|
||||
// We've gone far enough ahead and should now start replaying frames.
|
||||
// Load the last verified frame and set the rollback flag to true.
|
||||
_sync.LoadFrame(_last_verified);
|
||||
|
||||
_rollingback = true;
|
||||
while(!_saved_frames.empty()) {
|
||||
_callbacks.advance_frame(0);
|
||||
|
||||
// Verify that the checksumn of this frame is the same as the one in our
|
||||
// list.
|
||||
info = _saved_frames.front();
|
||||
_saved_frames.pop();
|
||||
|
||||
if (info.frame != _sync.GetFrameCount()) {
|
||||
RaiseSyncError("Frame number %d does not match saved frame number %d", info.frame, frame);
|
||||
}
|
||||
int checksum = _sync.GetLastSavedFrame().checksum;
|
||||
if (info.checksum != checksum) {
|
||||
LogSaveStates(info);
|
||||
RaiseSyncError("Checksum for frame %d does not match saved (%d != %d)", frame, checksum, info.checksum);
|
||||
}
|
||||
else
|
||||
printf("Checksum %08d for frame %d matches.\n", checksum, info.frame);
|
||||
free(info.buf);
|
||||
}
|
||||
_last_verified = frame;
|
||||
_rollingback = false;
|
||||
}
|
||||
|
||||
return GGPO_OK;
|
||||
}
|
||||
|
||||
void
|
||||
SyncTestBackend::RaiseSyncError(const char *fmt, ...)
|
||||
{
|
||||
char buf[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buf, ARRAY_SIZE(buf), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
puts(buf);
|
||||
#ifdef _WIN32
|
||||
OutputDebugStringA(buf);
|
||||
#endif
|
||||
// DebugBreak();
|
||||
}
|
||||
|
||||
void
|
||||
SyncTestBackend::LogSaveStates(SavedInfo &info)
|
||||
{
|
||||
char filename[MAX_PATH];
|
||||
snprintf(filename, ARRAY_SIZE(filename), "synclogs/state-%04d-original.log", _sync.GetFrameCount());
|
||||
_callbacks.log_game_state(filename, (unsigned char *)info.buf, info.cbuf);
|
||||
|
||||
snprintf(filename, ARRAY_SIZE(filename), "synclogs/state-%04d-replay.log", _sync.GetFrameCount());
|
||||
_callbacks.log_game_state(filename, _sync.GetLastSavedFrame().buf, _sync.GetLastSavedFrame().cbuf);
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/* -----------------------------------------------------------------------
|
||||
* GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC.
|
||||
*
|
||||
* Use of this software is governed by the MIT license that can be found
|
||||
* in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef _SYNCTEST_H
|
||||
#define _SYNCTEST_H
|
||||
|
||||
#include "ggpo_types.h"
|
||||
#include "backend.h"
|
||||
#include "sync.h"
|
||||
|
||||
#include "ring_buffer.h"
|
||||
|
||||
class SyncTestBackend : public IQuarkBackend {
|
||||
public:
|
||||
SyncTestBackend(GGPOSessionCallbacks *cb, const char *gamename, int frames, int num_players);
|
||||
virtual ~SyncTestBackend();
|
||||
|
||||
virtual GGPOErrorCode DoPoll(int timeout);
|
||||
virtual GGPOErrorCode AddPlayer(GGPOPlayer *player, GGPOPlayerHandle *handle);
|
||||
virtual GGPOErrorCode AddLocalInput(GGPOPlayerHandle player, void *values, int size);
|
||||
virtual GGPOErrorCode SyncInput(void *values, int size, int *disconnect_flags);
|
||||
virtual GGPOErrorCode IncrementFrame(void);
|
||||
|
||||
protected:
|
||||
struct SavedInfo {
|
||||
int frame;
|
||||
int checksum;
|
||||
char *buf;
|
||||
int cbuf;
|
||||
GameInput input;
|
||||
};
|
||||
|
||||
void RaiseSyncError(const char *fmt, ...);
|
||||
void LogSaveStates(SavedInfo &info);
|
||||
|
||||
protected:
|
||||
GGPOSessionCallbacks _callbacks;
|
||||
Sync _sync;
|
||||
int _num_players;
|
||||
int _check_distance;
|
||||
int _last_verified;
|
||||
bool _rollingback;
|
||||
bool _running;
|
||||
FILE *_logfp;
|
||||
char _game[128];
|
||||
|
||||
GameInput _current_input;
|
||||
GameInput _last_input;
|
||||
RingBuffer<SavedInfo, 32> _saved_frames;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Loaded 100 of 4426 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user